diff --git a/README.md b/README.md index 0a6d2a2..69fafe3 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@ The goal of this project is to be a crowd sourced resource to find out if a prod # Project organization -- api: +- handlers: gets query parameters to call service and responds with service output +- services: takes parameters and performs business logic, calls functions to query database +- user_claims.go in database package: queries database ## Task list - figure out how we want to handle database migrations diff --git a/go.mod b/go.mod index 8b19069..ee2277c 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.24.1 require ( github.com/gin-gonic/gin v1.10.0 - github.com/labstack/gommon v0.4.2 github.com/sirupsen/logrus v1.9.3 ) @@ -32,7 +31,6 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -41,8 +39,6 @@ require ( github.com/stretchr/testify v1.9.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.2 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sys v0.31.0 // indirect diff --git a/go.sum b/go.sum index 3599f7d..87c814b 100644 --- a/go.sum +++ b/go.sum @@ -154,8 +154,6 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= -github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -166,12 +164,9 @@ github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= @@ -245,10 +240,6 @@ github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2 github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= -github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/vingarcia/ksql v1.12.3 h1:1LVRGW39XPaYltPHNQsvHms+bWHp8e99sxQx+aEXDMQ= github.com/vingarcia/ksql v1.12.3/go.mod h1:DHp/nhVu1nHpBBXH/FRw6JLgIcvcM3+uo2+PfUNdo0g= github.com/vingarcia/ksql/adapters/kpgx v1.12.3 h1:vzH0vMw1NTCang2DcZI9sV975BFUg5ONQjEZ27tuCng= @@ -351,7 +342,6 @@ golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/application/application.go b/internal/application/application.go index 7af28a6..181c872 100644 --- a/internal/application/application.go +++ b/internal/application/application.go @@ -1,8 +1,8 @@ package application import ( - "vegan-barcode/internal/application/utils" "vegan-barcode/internal/database" + "vegan-barcode/internal/utils" "github.com/sirupsen/logrus" ) diff --git a/internal/application/handlers.go b/internal/application/handlers.go index 4f5d673..31bc986 100644 --- a/internal/application/handlers.go +++ b/internal/application/handlers.go @@ -1,11 +1,19 @@ package application -import "github.com/gin-gonic/gin" +import ( + "net/http" -func (application *Application) GetClaimsHandler(c *gin.Context) { + "github.com/gin-gonic/gin" +) + +func (a *Application) GetClaimsHandler(c *gin.Context) { system := c.DefaultQuery("system", "upc") barcode := c.Query("barcode") - application.GetClaims(system, barcode) - // TODO: 404 when claims are not found + productClaims, err := a.GetClaims(system, barcode) + if err != nil { + c.JSON(http.StatusInternalServerError, nil) + return + } + c.JSON(http.StatusOK, productClaims) } diff --git a/internal/application/routes.go b/internal/application/routes.go index 22d8784..f921a21 100644 --- a/internal/application/routes.go +++ b/internal/application/routes.go @@ -4,7 +4,6 @@ import ( "github.com/gin-gonic/gin" ) -// TODO: Service should get moved somewhere else. Not sure on naming. func (application *Application) bindRoutes() { router := gin.Default() diff --git a/internal/application/services.go b/internal/application/services.go index bb47226..ed0b596 100644 --- a/internal/application/services.go +++ b/internal/application/services.go @@ -1,5 +1,19 @@ package application -func (application *Application) GetClaims(system string, barcode string) { - application.db.FindClaimsByBarcode(system, barcode) +import ( + "vegan-barcode/internal/models" +) + +func (a *Application) GetClaims(system string, barcode string) (*models.ProductClaims, error) { + id, err := a.db.FindProductIDByBarcode(system, barcode) + if err != nil { + return nil, err + } + + claims, err := a.db.FindClaimsByProductID(id) + if err != nil { + return nil, err + } + + return &models.ProductClaims{Id: id, Claims: claims}, nil } diff --git a/internal/database/claims.go b/internal/database/claims.go index 5c366be..aa320a7 100644 --- a/internal/database/claims.go +++ b/internal/database/claims.go @@ -2,9 +2,53 @@ package database import ( "context" + "vegan-barcode/internal/models" ) -func (database *Database) FindClaimsByBarcode(system string, barcode string) { +func (database *Database) FindClaimsByProductID(product_id int) (claims []models.Claim, err error) { ctx := context.Background() - database.db.Exec(ctx, "SELECT * FROM user_claims") + err = database.db.Query(ctx, claims, `SELECT + cluster, + id, + worker_type, + evidence_type, + evidence, + category, + polarity, + created_at, + created_by +FROM ( + SELECT + cluster, + id, + worker_type, + evidence_type, + evidence, + category, + polarity, + created_at, + created_by, + ROW_NUMBER() OVER (PARTITION BY category ORDER BY created_at DESC) AS rn + FROM ( + ( + SELECT "user" as cluster, id, product_id, null as worker_type, evidence_type, evidence, unnest(claim) as category, true as polarity, created_at, created_by FROM user_claims + UNION ALL + SELECT "automated" as cluster, id, product_id, worker_type, null as evidence_type, evidence, unnest(claim) as category, true as polarity, created_at, null as created_by FROM automated_claims + ) + UNION ALL + ( + SELECT "user" as cluster, id, product_id, null as worker_type, evidence_type, evidence, unnest(counter_claim) as category, false as polarity, created_at, created_by FROM user_claims + UNION ALL + SELECT "automated" as cluster, id, product_id, worker_type, null as evidence_type, evidence, unnest(counter_claim) as category, false as polarity, created_at, null as created_by FROM automated_claims + ) + ) + WHERE product_id = ? +) +WHERE rn = 1 +ORDER BY created_at; +`, product_id) + if err != nil { + return claims, err + } + return } diff --git a/internal/database/database.go b/internal/database/database.go index bd09f42..c605b11 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -16,6 +16,7 @@ var UserClaimsTable = ksql.NewTable("user_claims", "id") var AutomatedClaimsTable = ksql.NewTable("automated_claims", "id") +// Struct used in dependency injection can be replaced with a mock for testing. type Database struct { db *ksql.DB } diff --git a/internal/database/models.go b/internal/database/models.go index 3bbb2cb..27af2de 100644 --- a/internal/database/models.go +++ b/internal/database/models.go @@ -2,6 +2,7 @@ package database import ( "time" + "vegan-barcode/internal/models" ) type Product struct { @@ -11,54 +12,23 @@ type Product struct { Created_at time.Time `ksql:"created_at,timeNowUTC"` } -type WorkerType int - -const ( - Barnivore WorkerType = iota -) - -type EvidenceType int - -const ( - ManufactureWebsite EvidenceType = iota - IngredientsList -) - -type Claim int - -const ( - ContainsMeat Claim = iota - ContainsFish - ContainsEggs - ContainsMilk - ContainsHoney - ContainsWax - ContainsFur - ContainsLeather - ContainsAnimalFibers - ContainsWool - ContainsFeathers - AnimalTesting - MonkeySlavery -) - type AutomatedClaim struct { - id int `ksql:"id"` - product_id int `ksql:"product_id"` - worker_type WorkerType `ksql:"worker_type"` - evidence struct{} `ksql:"evidence,json"` - claim Claim `ksql:"claim"` - counter_claim Claim `ksql:"counter_claim"` - created_at time.Time `ksql:"created_at,timeNowUTC"` + id int `ksql:"id"` + product_id int `ksql:"product_id"` + worker_type models.WorkerType `ksql:"worker_type"` + evidence struct{} `ksql:"evidence,json"` + claim models.ClaimType `ksql:"claim"` + counter_claim models.ClaimType `ksql:"counter_claim"` + created_at time.Time `ksql:"created_at,timeNowUTC"` } type UserClaim struct { - id int `ksql:"id"` - product_id int `ksql:"product_id"` - evidence_type EvidenceType `ksql:"evidence_type"` - evidence struct{} `ksql:"evidence,json"` - claim Claim `ksql:"claim"` - counter_claim Claim `ksql:"counter_claim"` - created_at time.Time `ksql:"created_at,timeNowUTC"` - created_by string `ksql:"created_by"` + id int `ksql:"id"` + product_id int `ksql:"product_id"` + evidence_type models.EvidenceType `ksql:"evidence_type"` + evidence struct{} `ksql:"evidence,json"` + claim models.ClaimType `ksql:"claim"` + counter_claim models.ClaimType `ksql:"counter_claim"` + created_at time.Time `ksql:"created_at,timeNowUTC"` + created_by string `ksql:"created_by"` } diff --git a/internal/database/products.go b/internal/database/products.go new file mode 100644 index 0000000..b6191d7 --- /dev/null +++ b/internal/database/products.go @@ -0,0 +1,14 @@ +package database + +import "context" + +func (d *Database) FindProductIDByBarcode(system string, barcode string) (id int, err error) { + + ctx := context.Background() + + err = d.db.QueryOne(ctx, &id, "SELECT id FROM products WHERE system = ? AND barcode = ?", system, barcode) + if err != nil { + return -1, err + } + return +} diff --git a/internal/models/models.go b/internal/models/models.go new file mode 100644 index 0000000..9cf290b --- /dev/null +++ b/internal/models/models.go @@ -0,0 +1,61 @@ +package models + +import ( + "time" +) + +type ClaimType int + +const ( + ContainsMeat ClaimType = iota + ContainsFish + ContainsEggs + ContainsMilk + ContainsHoney + ContainsWax + ContainsFur + ContainsLeather + ContainsAnimalFibers + ContainsWool + ContainsFeathers + AnimalTesting + MonkeySlavery +) + +type WorkerType int + +const ( + Barnivore WorkerType = iota +) + +type EvidenceType int + +const ( + ManufactureWebsite EvidenceType = iota + IngredientsList +) + +type ClusterType int + +const ( + User ClusterType = iota + Automated +) + +// Generic claim type for combining both automated and user claims. +type Claim struct { + Id int + Worker_type WorkerType + Evidence_type EvidenceType + Evidence struct{} + Category ClaimType + Polarity bool + Created_at time.Time + Created_by string + Cluster ClusterType +} + +type ProductClaims struct { + Id int + Claims []Claim +} diff --git a/internal/application/utils/logger.go b/internal/utils/logger.go similarity index 100% rename from internal/application/utils/logger.go rename to internal/utils/logger.go diff --git a/query_draft.sql b/query_draft.sql index 19a6283..082d649 100644 --- a/query_draft.sql +++ b/query_draft.sql @@ -1,7 +1,7 @@ SELECT cluster, id, - worker, + worker_type, evidence_type, evidence, category, @@ -12,7 +12,7 @@ FROM ( SELECT cluster, id, - worker, + worker_type, evidence_type, evidence, category, @@ -22,15 +22,15 @@ FROM ( ROW_NUMBER() OVER (PARTITION BY category ORDER BY created_at DESC) AS rn FROM ( ( - SELECT "user" as cluster, id, product_id, null as worker, evidence_type, evidence, unnest(claim) as category, true as polarity, created_at, created_by FROM user_claims + SELECT "user" as cluster, id, product_id, null as worker_type, evidence_type, evidence, unnest(claim) as category, true as polarity, created_at, created_by FROM user_claims UNION ALL - SELECT "automated" as cluster, id, product_id, worker, null as evidence_type, evidence, unnest(claim) as category, true as polarity, created_at, null as created_by FROM automated_claims + SELECT "automated" as cluster, id, product_id, worker_type, null as evidence_type, evidence, unnest(claim) as category, true as polarity, created_at, null as created_by FROM automated_claims ) UNION ALL ( - SELECT "user" as cluster, id, product_id, null as worker, evidence_type, evidence, unnest(counter_claim) as category, false as polarity, created_at, created_by FROM user_claims + SELECT "user" as cluster, id, product_id, null as worker_type, evidence_type, evidence, unnest(counter_claim) as category, false as polarity, created_at, created_by FROM user_claims UNION ALL - SELECT "automated" as cluster, id, product_id, worker, null as evidence_type, evidence, unnest(counter_claim) as category, false as polarity, created_at, null as created_by FROM automated_claims + SELECT "automated" as cluster, id, product_id, worker_type, null as evidence_type, evidence, unnest(counter_claim) as category, false as polarity, created_at, null as created_by FROM automated_claims ) ) WHERE product_id = ???