redesign structure, add get endpoint

This commit is contained in:
katefort 2025-04-21 18:28:55 -05:00
parent 3dacf06005
commit a216cc14dd
14 changed files with 176 additions and 77 deletions

View file

@ -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

4
go.mod
View file

@ -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

10
go.sum
View file

@ -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=

View file

@ -1,8 +1,8 @@
package application
import (
"vegan-barcode/internal/application/utils"
"vegan-barcode/internal/database"
"vegan-barcode/internal/utils"
"github.com/sirupsen/logrus"
)

View file

@ -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)
}

View file

@ -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()

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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"`
}

View file

@ -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
}

61
internal/models/models.go Normal file
View file

@ -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
}

View file

@ -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 = ???