From d26e7b83452ab4390d102fbcd53532cc3c2d179f Mon Sep 17 00:00:00 2001
From: katefort <katherine.d.fort@gmail.com>
Date: Mon, 31 Mar 2025 18:15:51 -0500
Subject: [PATCH 01/15] Rearrange folders, and separate functionality

---
 cmd/main.go                            |  0
 internal/controllers/routes.go         |  8 ++++
 {src => internal}/database/database.go | 62 ++-----------------------
 internal/database/models.go            | 64 ++++++++++++++++++++++++++
 internal/services/userService.go       |  1 +
 5 files changed, 76 insertions(+), 59 deletions(-)
 create mode 100644 cmd/main.go
 create mode 100644 internal/controllers/routes.go
 rename {src => internal}/database/database.go (58%)
 create mode 100644 internal/database/models.go
 create mode 100644 internal/services/userService.go

diff --git a/cmd/main.go b/cmd/main.go
new file mode 100644
index 0000000..e69de29
diff --git a/internal/controllers/routes.go b/internal/controllers/routes.go
new file mode 100644
index 0000000..161a77a
--- /dev/null
+++ b/internal/controllers/routes.go
@@ -0,0 +1,8 @@
+package main
+
+func main() {
+	router := gin.Default()
+	router.GET("/test", runTest)
+
+	router.Run("localhost:8080")
+}
diff --git a/src/database/database.go b/internal/database/database.go
similarity index 58%
rename from src/database/database.go
rename to internal/database/database.go
index 2c2a14a..824a3fa 100644
--- a/src/database/database.go
+++ b/internal/database/database.go
@@ -12,69 +12,10 @@ import (
 
 var ProductsTable = ksql.NewTable("products", "product_id")
 
-type Product struct {
-	id         int       `ksql:"id"`
-	system     string    `ksql:"system"`
-	barcode    string    `ksql:"barcode"`
-	created_at time.Time `ksql:"created_at,timeNowUTC"`
-}
-
 var UserClaimsTable = ksql.NewTable("user_claims", "user_claim_id")
 
-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"`
-}
-
-type EvidenceType int
-
-const (
-	ManufactureWebsite EvidenceType = iota
-	IngredientsList
-)
-
 var AutomatedClaimsTable = ksql.NewTable("automated_claims", "automated_claim_id")
 
-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"`
-}
-
-type WorkerType int
-
-const (
-	Barnivore WorkerType = iota
-)
-
-type Claim int
-
-const (
-	ContainsMeat Claim = iota
-	ContainsFish
-	ContainsEggs
-	ContainsMilk
-	ContainsHoney
-	ContainsWax
-	ContainsFur
-	ContainsLeather
-	ContainsAnimalFibers
-	ContainsWool
-	ContainsFeathers
-	AnimalTesting
-	MonkeySlavery
-)
-
 func main() {
 	ctx := context.Background()
 
@@ -92,6 +33,9 @@ func main() {
 	}
 	defer db.Close()
 
+}
+
+func createTables(db) {
 	_, err = db.Exec(ctx, `
 		CREATE TABLE IF NOT EXISTS products (
 			id INTEGER PRIMARY KEY,
diff --git a/internal/database/models.go b/internal/database/models.go
new file mode 100644
index 0000000..034b110
--- /dev/null
+++ b/internal/database/models.go
@@ -0,0 +1,64 @@
+package main
+
+import (
+	"github.com/vingarcia/ksql"
+)
+
+type Product struct {
+	id         int       `ksql:"id"`
+	system     string    `ksql:"system"`
+	barcode    string    `ksql:"barcode"`
+	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"`
+}
+
+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"`
+}
diff --git a/internal/services/userService.go b/internal/services/userService.go
new file mode 100644
index 0000000..06ab7d0
--- /dev/null
+++ b/internal/services/userService.go
@@ -0,0 +1 @@
+package main

From 735de8d6571ed20c408d911194137df1bf000d1b Mon Sep 17 00:00:00 2001
From: katefort <katherine.d.fort@gmail.com>
Date: Tue, 8 Apr 2025 17:10:49 -0500
Subject: [PATCH 02/15] add logger, rearrange database initialization

---
 go.mod                           | 27 ++++++++++++++-
 go.sum                           | 59 ++++++++++++++++++++++++++++++++
 internal/controllers/routes.go   | 10 ++++++
 internal/database/database.go    | 40 +++++++++++++++-------
 internal/database/models.go      |  2 +-
 internal/services/userService.go |  7 ++++
 internal/swagger.yml             |  0
 7 files changed, 131 insertions(+), 14 deletions(-)
 create mode 100644 internal/swagger.yml

diff --git a/go.mod b/go.mod
index 8ab901a..07191b7 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,18 @@ module vegan-barcode/backend
 go 1.24.1
 
 require (
+	github.com/bytedance/sonic v1.11.6 // indirect
+	github.com/bytedance/sonic/loader v0.1.1 // indirect
+	github.com/cloudwego/base64x v0.1.4 // indirect
+	github.com/cloudwego/iasm v0.2.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.3 // indirect
+	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/gin-gonic/gin v1.10.0 // indirect
+	github.com/go-playground/locales v0.14.1 // indirect
+	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/go-playground/validator/v10 v10.20.0 // indirect
+	github.com/goccy/go-json v0.10.2 // indirect
 	github.com/jackc/chunkreader/v2 v2.0.1 // indirect
 	github.com/jackc/pgconn v1.14.1 // indirect
 	github.com/jackc/pgio v1.0.0 // indirect
@@ -13,8 +24,22 @@ require (
 	github.com/jackc/pgtype v1.14.0 // indirect
 	github.com/jackc/pgx/v4 v4.18.1 // indirect
 	github.com/jackc/puddle v1.3.0 // indirect
+	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-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
+	github.com/pelletier/go-toml/v2 v2.2.2 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/stretchr/testify v1.8.4 // indirect
+	github.com/sirupsen/logrus v1.9.3 // indirect
+	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
+	golang.org/x/arch v0.8.0 // indirect
+	golang.org/x/net v0.25.0 // indirect
+	golang.org/x/sys v0.31.0 // indirect
+	google.golang.org/protobuf v1.34.1 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
diff --git a/go.sum b/go.sum
index 623c258..e680649 100644
--- a/go.sum
+++ b/go.sum
@@ -8,10 +8,18 @@ github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2y
 github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
 github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
 github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
+github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
+github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
+github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
+github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
 github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
 github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
 github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
 github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
+github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
+github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
+github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
+github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
 github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
 github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
 github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
@@ -37,10 +45,24 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh
 github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
+github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
+github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
+github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
 github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
+github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
@@ -53,6 +75,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
@@ -109,8 +132,14 @@ github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0f
 github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0=
 github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
+github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -122,6 +151,8 @@ 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/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=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@@ -133,11 +164,18 @@ github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
 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.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=
 github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
 github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
 github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
@@ -149,6 +187,8 @@ github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.m
 github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
 github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
 github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
+github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
+github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -172,12 +212,15 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -188,7 +231,13 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+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/vingarcia/ksql v1.12.3 h1:1LVRGW39XPaYltPHNQsvHms+bWHp8e99sxQx+aEXDMQ=
 github.com/vingarcia/ksql v1.12.3/go.mod h1:DHp/nhVu1nHpBBXH/FRw6JLgIcvcM3+uo2+PfUNdo0g=
@@ -217,6 +266,9 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E
 go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
+golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -253,6 +305,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -285,6 +339,7 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -339,6 +394,8 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
+google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -354,3 +411,5 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
 gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/internal/controllers/routes.go b/internal/controllers/routes.go
index 161a77a..271f9d0 100644
--- a/internal/controllers/routes.go
+++ b/internal/controllers/routes.go
@@ -1,8 +1,18 @@
 package main
 
+import "github.com/gin-gonic/gin"
+
 func main() {
 	router := gin.Default()
 	router.GET("/test", runTest)
 
+	// Search for item info
+	router.GET("/test", runTest)
+	// Update item info
+	router.GET("/test", runTest)
+	// Add new item info
+
+	// Delete item (debug)
+
 	router.Run("localhost:8080")
 }
diff --git a/internal/database/database.go b/internal/database/database.go
index 824a3fa..46e2cfa 100644
--- a/internal/database/database.go
+++ b/internal/database/database.go
@@ -4,8 +4,8 @@ import (
 	"context"
 	"fmt"
 	"os"
-	"time"
 
+	"github.com/sirupsen/logrus"
 	"github.com/vingarcia/ksql"
 	"github.com/vingarcia/ksql/adapters/kpgx"
 )
@@ -16,27 +16,43 @@ var UserClaimsTable = ksql.NewTable("user_claims", "user_claim_id")
 
 var AutomatedClaimsTable = ksql.NewTable("automated_claims", "automated_claim_id")
 
+// log is the global error logging interface.
+var log *logrus.Logger
+
 func main() {
+	setupLogger()
+	// Do we want to handle the error here instead?
+	db := initializeDatabase()
+
+}
+func setupLogger() {
+	log = logrus.New()
+	log.SetFormatter(&logrus.TextFormatter{
+		FullTimestamp: true, // Include the full timestamp (with date and time)
+	})
+}
+
+// initializeDatabase creates the database and then calls createTables.
+func initializeDatabase() ksql.DB {
 	ctx := context.Background()
 
 	// urlExample := "postgres://username:password@localhost:5432/database_name"
 	host := os.Getenv("PGHOST")
-	database := os.Getenv("DB_NAME")
+	dbName := os.Getenv("DB_NAME")
 
-	connectString :=
-		fmt.Sprintf("postgres:///?host=%s&database=%s", host, database)
+	connectString := fmt.Sprintf("postgres:///?host=%s&database=%s", host, dbName)
 
 	db, err := kpgx.New(ctx, connectString, ksql.Config{})
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
-		os.Exit(1)
+		log.Fatalf("Unable to connect to database: %v\n", err)
 	}
-	defer db.Close()
 
+	createTables(ctx, db)
 }
 
-func createTables(db) {
-	_, err = db.Exec(ctx, `
+// createTables adds the product, automated_claims, and user_claims tables to the initialized database.
+func createTables(ctx context.Context, db ksql.DB) {
+	_, err := db.Exec(ctx, `
 		CREATE TABLE IF NOT EXISTS products (
 			id INTEGER PRIMARY KEY,
 			system TEXT,
@@ -45,7 +61,7 @@ func createTables(db) {
 		);
 	`)
 	if err != nil {
-		panic(err.Error())
+		log.Fatal(err)
 	}
 	_, err = db.Exec(ctx, `
 		CREATE TABLE IF NOT EXISTS user_claims (
@@ -64,7 +80,7 @@ func createTables(db) {
 		CREATE INDEX idx_user_claims_product_id ON user_claims USING HASH (product_id);
 	`)
 	if err != nil {
-		panic(err.Error())
+		log.Fatal(err)
 	}
 	_, err = db.Exec(ctx, `
 		CREATE TABLE IF NOT EXISTS automated_claims (
@@ -82,6 +98,6 @@ func createTables(db) {
 		CREATE INDEX idx_automated_claims_product_id ON automated_claims USING HASH (product_id);
 	`)
 	if err != nil {
-		panic(err.Error())
+		log.Fatal(err)
 	}
 }
diff --git a/internal/database/models.go b/internal/database/models.go
index 034b110..86d4178 100644
--- a/internal/database/models.go
+++ b/internal/database/models.go
@@ -1,7 +1,7 @@
 package main
 
 import (
-	"github.com/vingarcia/ksql"
+	"time"
 )
 
 type Product struct {
diff --git a/internal/services/userService.go b/internal/services/userService.go
index 06ab7d0..4f5a7d8 100644
--- a/internal/services/userService.go
+++ b/internal/services/userService.go
@@ -1 +1,8 @@
 package main
+
+func testService(c *gin.Context) {
+	log.Debug("Test was successful.")
+	c.JSON(http.StatusOK, gin.H{
+		"message": "Hello World!"
+	})
+}
\ No newline at end of file
diff --git a/internal/swagger.yml b/internal/swagger.yml
new file mode 100644
index 0000000..e69de29

From 0254ec2525e70a49e9cb49528b0333c0f6ad6cac Mon Sep 17 00:00:00 2001
From: katefort <katherine.d.fort@gmail.com>
Date: Tue, 8 Apr 2025 19:11:23 -0500
Subject: [PATCH 03/15] add swagger.yml and test route

---
 internal/controllers/routes.go   |   3 +-
 internal/services/userService.go |   8 ++-
 internal/swagger.yml             | 115 +++++++++++++++++++++++++++++++
 3 files changed, 124 insertions(+), 2 deletions(-)

diff --git a/internal/controllers/routes.go b/internal/controllers/routes.go
index 271f9d0..7e5e873 100644
--- a/internal/controllers/routes.go
+++ b/internal/controllers/routes.go
@@ -7,7 +7,8 @@ func main() {
 	router.GET("/test", runTest)
 
 	// Search for item info
-	router.GET("/test", runTest)
+	router.GET("/claims/{barcode}", runTest)
+	router.DELETE("/claims/{barcode}", runTest)
 	// Update item info
 	router.GET("/test", runTest)
 	// Add new item info
diff --git a/internal/services/userService.go b/internal/services/userService.go
index 4f5a7d8..090d4e5 100644
--- a/internal/services/userService.go
+++ b/internal/services/userService.go
@@ -5,4 +5,10 @@ func testService(c *gin.Context) {
 	c.JSON(http.StatusOK, gin.H{
 		"message": "Hello World!"
 	})
-}
\ No newline at end of file
+}
+
+func claimsByBarcode(c *gin.Context) {
+	system := c.DefaultQuery("system", "upc")
+	barcode := c.Query("barcode")
+}
+
diff --git a/internal/swagger.yml b/internal/swagger.yml
index e69de29..05a8408 100644
--- a/internal/swagger.yml
+++ b/internal/swagger.yml
@@ -0,0 +1,115 @@
+openapi: 3.0.0
+info:
+    title: Automated Claims API
+    version: 1.0.0
+paths:
+    /claims/{barcode}:
+        get:
+            summary: Get claims for a barcode and system
+            parameters:
+                - name: barcode
+                  in: path
+                  description: Barcode value
+                  required: true
+                  schema:
+                      type: string
+                - name: system
+                  in: query
+                  description: Name of barcode standard
+                  required: false
+                  schema:
+                      type: string
+                      default: UPC
+            responses:
+                "200":
+                    description: Claims found
+                    content:
+                        application/json:
+                            type: array
+                            items:
+                                $ref: "#/components/schemas/Claim"
+                "204":
+                    description: No claims found for product
+                "404":
+                    description: Product not found
+        delete:
+            summary: Delete claims by user ID
+            parameters:
+                - name: id
+                  in: path
+                  description: ID of the claim to delete
+                  required: true
+                  schema:
+                      type: long
+            responses:
+                "200":  
+                    description: Successful deletion
+                    content: 
+                        application/json:
+                            schema:
+                                $ref: "#/components/schemas/Claim"
+                "404":
+                    description: Claim not found
+        post:
+            summary: Make a new user claim
+            parameters:
+                - name: barcode
+                  in: path
+                  description: ID of the automated claim
+                  required: true
+                  schema:
+                      type: integer
+                - name: claim
+                  in: body
+                  description: Claim information
+                  required: true
+                  schema:
+                      $ref: "#/components/schemas/AutomatedClaim"
+            responses:
+                "200":
+                    description: Successfully added
+                    content:
+                        schema:
+                            $ref: "#/components/schemas/AutomatedClaim"
+                "400":
+                    description: Missing information
+components:
+    schemas:
+        Claim:
+            type: object
+            properties:
+                id:
+                    type: long
+                product_id:
+                    type: long
+                evidence_type: # only in user claims
+                    type: string
+                    enum: [image, link, text]
+                worker: # only in auto claims
+                    type: string
+                    enum: [barnivore] # TODO add other workers
+                evidence:
+                    type: object
+                category: # single category claims
+                    type: string
+                    enum:
+                    - ContainsMeat
+                    - ContainsFish
+                    - ContainsEggs
+                    - ContainsMilk
+                    - ContainsHoney
+                    - ContainsWax
+                    - ContainsFur
+                    - ContainsLeather
+                    - ContainsAnimalFibers
+                    - ContainsWool
+                    - ContainsFeathers
+                    - AnimalTesting
+                    - MonkeySlavery
+                polarity: # true means it does do the thing
+                    type: boolean
+                created_at:
+                    type: string
+                    format: date-time
+                created_by:
+                    type: string

From c1930d68214de6c16e465aba57f2fae6a08d549e Mon Sep 17 00:00:00 2001
From: katefort <katherine.d.fort@gmail.com>
Date: Thu, 17 Apr 2025 15:54:33 -0500
Subject: [PATCH 04/15] create working test endpoint

---
 README.md                        | 17 ++++++++-
 cmd/main.go                      |  0
 cmd/server/main.go               |  9 +++++
 go.mod                           | 13 +++++--
 go.sum                           | 16 +++++++-
 internal/api/routes.go           | 65 ++++++++++++++++++++++++++++++++
 internal/controllers/routes.go   | 19 ----------
 internal/database/database.go    | 26 +++----------
 internal/database/models.go      |  2 +-
 internal/services/userService.go | 20 +---------
 internal/utils/logger.go         | 13 +++++++
 11 files changed, 134 insertions(+), 66 deletions(-)
 delete mode 100644 cmd/main.go
 create mode 100644 cmd/server/main.go
 create mode 100644 internal/api/routes.go
 delete mode 100644 internal/controllers/routes.go
 create mode 100644 internal/utils/logger.go

diff --git a/README.md b/README.md
index 85b3fb2..1d771cd 100644
--- a/README.md
+++ b/README.md
@@ -13,4 +13,19 @@ The goal of this project is to be a crowd sourced resource to find out if a prod
     - barnivore lookup
     - alergen ingredient database lookup?
 - create mobile and desktop front ends
-- moderation tooling?
\ No newline at end of file
+- moderation tooling?
+
+
+Created database by
+
+
+## How to start database
+
+- Run `brew services start postgresql`
+- Create database
+    - Setting environment variables:
+        - PGHOST=localhost
+        - DB_NAME=veganDB (Can be arbitrary)
+    - Run `createdb veganDB`
+(To delete in future just run dropdb <name>)
+
diff --git a/cmd/main.go b/cmd/main.go
deleted file mode 100644
index e69de29..0000000
diff --git a/cmd/server/main.go b/cmd/server/main.go
new file mode 100644
index 0000000..426e8ff
--- /dev/null
+++ b/cmd/server/main.go
@@ -0,0 +1,9 @@
+package main
+
+import (
+	"vegan-barcode/internal/api"
+)
+
+func main() {
+	api.BindRoutes()
+}
diff --git a/go.mod b/go.mod
index 07191b7..8b19069 100644
--- a/go.mod
+++ b/go.mod
@@ -1,7 +1,13 @@
-module vegan-barcode/backend
+module vegan-barcode
 
 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
+)
+
 require (
 	github.com/bytedance/sonic v1.11.6 // indirect
 	github.com/bytedance/sonic/loader v0.1.1 // indirect
@@ -10,7 +16,6 @@ require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/gabriel-vasile/mimetype v1.4.3 // indirect
 	github.com/gin-contrib/sse v0.1.0 // indirect
-	github.com/gin-gonic/gin v1.10.0 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
 	github.com/go-playground/validator/v10 v10.20.0 // indirect
@@ -27,15 +32,17 @@ 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
 	github.com/pelletier/go-toml/v2 v2.2.2 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/sirupsen/logrus v1.9.3 // indirect
 	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 e680649..3599f7d 100644
--- a/go.sum
+++ b/go.sum
@@ -53,6 +53,8 @@ github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
 github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
 github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
@@ -74,6 +76,7 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
@@ -151,6 +154,8 @@ 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=
@@ -161,9 +166,12 @@ 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=
@@ -210,7 +218,6 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@@ -229,7 +236,6 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
@@ -239,6 +245,10 @@ 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=
@@ -341,6 +351,7 @@ 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=
@@ -390,6 +401,7 @@ golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
diff --git a/internal/api/routes.go b/internal/api/routes.go
new file mode 100644
index 0000000..97eefc9
--- /dev/null
+++ b/internal/api/routes.go
@@ -0,0 +1,65 @@
+package api
+
+import (
+	"net/http"
+
+	"vegan-barcode/internal/database"
+	"vegan-barcode/internal/utils"
+
+	"github.com/gin-gonic/gin"
+	"github.com/labstack/gommon/log"
+	"github.com/sirupsen/logrus"
+	"github.com/vingarcia/ksql"
+)
+
+// TODO Figure out where this should be
+// This exists so that you don't have to individually pass around the logger and database.
+type ApiService struct {
+	db  *ksql.DB
+	log *logrus.Logger
+}
+
+var s *ApiService
+
+// TODO: Service should get moved somewhere else. Not sure on naming.
+func BindRoutes() {
+
+	s = &ApiService{db: database.InitializeDatabase(), log: utils.InitializeLogger()}
+	// s = &ApiService{db: &ksql.DB{}, log: utils.InitializeLogger()}
+	router := gin.Default()
+	router.GET("/test/:id", s.runTest)
+	router.GET("/", s.runTest)
+
+	// // Search for item info
+	// router.GET("/claims/{barcode}", runTest)
+
+	// // by user ID or worker
+	// router.DELETE("/claims/{barcode}", deleteClaims)
+
+	// // Update item info
+	// router.GET("/test", runTest)
+	// // Add new item info
+
+	router.Run("localhost:8080")
+}
+
+func (s *ApiService) runTest(c *gin.Context) {
+	queryParam := c.Param("id")
+	log.Debug("Test was successful.")
+	c.JSON(http.StatusOK, gin.H{"message": "Hello World!", "query_param": queryParam})
+}
+
+// func claimsByBarcode(c *gin.Context) {
+// 	system := c.DefaultQuery("system", "upc")
+// 	barcode := c.Query("barcode")
+
+// }
+
+// // deleteClaims will delete
+// func deleteClaims(c *gin.Context) {
+// 	userID := c.Param("user")
+// 	workerID := c.Param("worker")
+// 	// TODO query database
+// 	database.DB.Query()
+// 	c.JSON(http.StatusOK, gin.H{"message": "Hello World!"})
+// }
diff --git a/internal/controllers/routes.go b/internal/controllers/routes.go
deleted file mode 100644
index 7e5e873..0000000
--- a/internal/controllers/routes.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package main
-
-import "github.com/gin-gonic/gin"
-
-func main() {
-	router := gin.Default()
-	router.GET("/test", runTest)
-
-	// Search for item info
-	router.GET("/claims/{barcode}", runTest)
-	router.DELETE("/claims/{barcode}", runTest)
-	// Update item info
-	router.GET("/test", runTest)
-	// Add new item info
-
-	// Delete item (debug)
-
-	router.Run("localhost:8080")
-}
diff --git a/internal/database/database.go b/internal/database/database.go
index 46e2cfa..6bdf77d 100644
--- a/internal/database/database.go
+++ b/internal/database/database.go
@@ -1,11 +1,11 @@
-package main
+package database
 
 import (
 	"context"
 	"fmt"
+	"log"
 	"os"
 
-	"github.com/sirupsen/logrus"
 	"github.com/vingarcia/ksql"
 	"github.com/vingarcia/ksql/adapters/kpgx"
 )
@@ -16,24 +16,8 @@ var UserClaimsTable = ksql.NewTable("user_claims", "user_claim_id")
 
 var AutomatedClaimsTable = ksql.NewTable("automated_claims", "automated_claim_id")
 
-// log is the global error logging interface.
-var log *logrus.Logger
-
-func main() {
-	setupLogger()
-	// Do we want to handle the error here instead?
-	db := initializeDatabase()
-
-}
-func setupLogger() {
-	log = logrus.New()
-	log.SetFormatter(&logrus.TextFormatter{
-		FullTimestamp: true, // Include the full timestamp (with date and time)
-	})
-}
-
-// initializeDatabase creates the database and then calls createTables.
-func initializeDatabase() ksql.DB {
+// initializeDatabase creates the database and calls createTables.
+func InitializeDatabase() *ksql.DB {
 	ctx := context.Background()
 
 	// urlExample := "postgres://username:password@localhost:5432/database_name"
@@ -46,8 +30,8 @@ func initializeDatabase() ksql.DB {
 	if err != nil {
 		log.Fatalf("Unable to connect to database: %v\n", err)
 	}
-
 	createTables(ctx, db)
+	return &db
 }
 
 // createTables adds the product, automated_claims, and user_claims tables to the initialized database.
diff --git a/internal/database/models.go b/internal/database/models.go
index 86d4178..9a7ca59 100644
--- a/internal/database/models.go
+++ b/internal/database/models.go
@@ -1,4 +1,4 @@
-package main
+package database
 
 import (
 	"time"
diff --git a/internal/services/userService.go b/internal/services/userService.go
index 3de7c0a..5e568ea 100644
--- a/internal/services/userService.go
+++ b/internal/services/userService.go
@@ -1,19 +1 @@
-package main
-
-import (
-	"net/http"
-
-	"github.com/gin-gonic/gin"
-	"github.com/labstack/gommon/log"
-)
-
-func testService(c *gin.Context) {
-	log.Debug("Test was successful.")
-	c.JSON(http.StatusOK, gin.H{"message": "Hello World!"})
-}
-
-func claimsByBarcode(c *gin.Context) {
-	system := c.DefaultQuery("system", "upc")
-	barcode := c.Query("barcode")
-
-}
+package services
diff --git a/internal/utils/logger.go b/internal/utils/logger.go
new file mode 100644
index 0000000..a83abc3
--- /dev/null
+++ b/internal/utils/logger.go
@@ -0,0 +1,13 @@
+package utils
+
+import (
+	"github.com/sirupsen/logrus"
+)
+
+func InitializeLogger() *logrus.Logger {
+	log := logrus.New()
+	log.SetFormatter(&logrus.TextFormatter{
+		FullTimestamp: true, // Include the full timestamp (with date and time)
+	})
+	return log
+}

From 51a34008f1541eb7e4d9d4f93d67fc9937804402 Mon Sep 17 00:00:00 2001
From: katefort <katherine.d.fort@gmail.com>
Date: Thu, 17 Apr 2025 17:29:47 -0500
Subject: [PATCH 05/15] verify successful database interactions with test
 endpoint

---
 .gitignore                    |  4 ++++
 README.md                     | 15 ++++++++++++++-
 internal/api/routes.go        |  7 +++++++
 internal/database/database.go | 17 +++++++++--------
 internal/database/models.go   |  8 ++++----
 5 files changed, 38 insertions(+), 13 deletions(-)

diff --git a/.gitignore b/.gitignore
index c329722..ea63aaa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,8 +2,12 @@
 # development environment
 .envrc
 .direnv
+cmd/server/server
 
 # postgres
 postgres.db
 postgres.log
 .s.PGSQL.5432*
+cmd/server/logfile
+
+
diff --git a/README.md b/README.md
index 1d771cd..051ef3f 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,20 @@ The goal of this project is to be a crowd sourced resource to find out if a prod
 - moderation tooling?
 
 
-Created database by
+Troubleshooting:
+
+Error:
+`2025/04/17 16:21:21 ERROR: relation "idx_user_claims_product_id" already exists (SQLSTATE 42P07)`
+Explanation: The database tables already exist and the program was trying to create them again.
+
+Fix: Either don't create the databases again in the code, or run `dropdb veganDB` to allow it to recreate it.
+
+Error:
+`psql: error: connection to server at "localhost" (::1), port 5432 failed: FATAL:  database "<user>" does not exist`
+Explanation: Postgres automatically tryes to connect to a database with the same name as the user. Specify user and database:
+`psql -U username databaseName `
+
+
 
 
 ## How to start database
diff --git a/internal/api/routes.go b/internal/api/routes.go
index 97eefc9..98f48e4 100644
--- a/internal/api/routes.go
+++ b/internal/api/routes.go
@@ -2,6 +2,7 @@ package api
 
 import (
 	"net/http"
+	"time"
 
 	"vegan-barcode/internal/database"
 	"vegan-barcode/internal/utils"
@@ -46,6 +47,12 @@ func BindRoutes() {
 func (s *ApiService) runTest(c *gin.Context) {
 	queryParam := c.Param("id")
 	log.Debug("Test was successful.")
+
+	err := s.db.Insert(c, database.ProductsTable, &database.Product{System: "upc", Barcode: "fubar", Created_at: time.Now()})
+	if err != nil {
+		// TODO: Figure out correct status code.
+		c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to insert item to product table", "error": err.Error()})
+	}
 	c.JSON(http.StatusOK, gin.H{"message": "Hello World!", "query_param": queryParam})
 }
 
diff --git a/internal/database/database.go b/internal/database/database.go
index 6bdf77d..112f888 100644
--- a/internal/database/database.go
+++ b/internal/database/database.go
@@ -10,11 +10,11 @@ import (
 	"github.com/vingarcia/ksql/adapters/kpgx"
 )
 
-var ProductsTable = ksql.NewTable("products", "product_id")
+var ProductsTable = ksql.NewTable("products", "id")
 
-var UserClaimsTable = ksql.NewTable("user_claims", "user_claim_id")
+var UserClaimsTable = ksql.NewTable("user_claims", "id")
 
-var AutomatedClaimsTable = ksql.NewTable("automated_claims", "automated_claim_id")
+var AutomatedClaimsTable = ksql.NewTable("automated_claims", "id")
 
 // initializeDatabase creates the database and calls createTables.
 func InitializeDatabase() *ksql.DB {
@@ -36,9 +36,10 @@ func InitializeDatabase() *ksql.DB {
 
 // createTables adds the product, automated_claims, and user_claims tables to the initialized database.
 func createTables(ctx context.Context, db ksql.DB) {
+
 	_, err := db.Exec(ctx, `
 		CREATE TABLE IF NOT EXISTS products (
-			id INTEGER PRIMARY KEY,
+			id BIGSERIAL PRIMARY KEY,
 			system TEXT,
 			barcode TEXT,
 			created_at TIMESTAMPTZ
@@ -49,7 +50,7 @@ func createTables(ctx context.Context, db ksql.DB) {
 	}
 	_, err = db.Exec(ctx, `
 		CREATE TABLE IF NOT EXISTS user_claims (
-			id INTEGER PRIMARY KEY,
+			id BIGSERIAL PRIMARY KEY,
 			product_id INTEGER,
 			evidence_type INTEGER,
 			evidence  JSONB,
@@ -61,14 +62,14 @@ func createTables(ctx context.Context, db ksql.DB) {
 			CONSTRAINT fk_product_id FOREIGN KEY(product_id) REFERENCES products(id)
 		);
 
-		CREATE INDEX idx_user_claims_product_id ON user_claims USING HASH (product_id);
+		-- CREATE INDEX idx_user_claims_product_id ON user_claims USING HASH (product_id);
 	`)
 	if err != nil {
 		log.Fatal(err)
 	}
 	_, err = db.Exec(ctx, `
 		CREATE TABLE IF NOT EXISTS automated_claims (
-			id INTEGER PRIMARY KEY,
+			id BIGSERIAL PRIMARY KEY,
 			product_id INTEGER,
 			worker_type INTEGER,
 			evidence  JSONB,
@@ -79,7 +80,7 @@ func createTables(ctx context.Context, db ksql.DB) {
 			CONSTRAINT fk_product_id FOREIGN KEY(product_id) REFERENCES products(id)
 		);
 
-		CREATE INDEX idx_automated_claims_product_id ON automated_claims USING HASH (product_id);
+		-- CREATE INDEX idx_automated_claims_product_id ON automated_claims USING HASH (product_id);
 	`)
 	if err != nil {
 		log.Fatal(err)
diff --git a/internal/database/models.go b/internal/database/models.go
index 9a7ca59..3bbb2cb 100644
--- a/internal/database/models.go
+++ b/internal/database/models.go
@@ -5,10 +5,10 @@ import (
 )
 
 type Product struct {
-	id         int       `ksql:"id"`
-	system     string    `ksql:"system"`
-	barcode    string    `ksql:"barcode"`
-	created_at time.Time `ksql:"created_at,timeNowUTC"`
+	Id         int       `ksql:"id"`
+	System     string    `ksql:"system"`
+	Barcode    string    `ksql:"barcode"`
+	Created_at time.Time `ksql:"created_at,timeNowUTC"`
 }
 
 type WorkerType int

From 4ebc663d3b1540a7a386afc8b8afbcae9dfbceec Mon Sep 17 00:00:00 2001
From: katefort <katherine.d.fort@gmail.com>
Date: Mon, 21 Apr 2025 18:30:12 -0500
Subject: [PATCH 06/15] restructure application, add get endpoint

---
 README.md                           | 33 +++++++------
 cmd/server/main.go                  |  6 +--
 go.mod                              |  4 --
 go.sum                              | 10 ----
 internal/api/routes.go              | 72 -----------------------------
 internal/application/application.go | 22 +++++++++
 internal/application/handlers.go    | 19 ++++++++
 internal/application/routes.go      | 17 +++++++
 internal/application/services.go    | 19 ++++++++
 internal/database/claims.go         | 54 ++++++++++++++++++++++
 internal/database/database.go       | 25 ++++++----
 internal/database/models.go         | 62 +++++++------------------
 internal/database/products.go       | 14 ++++++
 internal/models/models.go           | 61 ++++++++++++++++++++++++
 internal/services/userService.go    |  1 -
 query_draft.sql                     | 12 ++---
 16 files changed, 266 insertions(+), 165 deletions(-)
 delete mode 100644 internal/api/routes.go
 create mode 100644 internal/application/application.go
 create mode 100644 internal/application/handlers.go
 create mode 100644 internal/application/routes.go
 create mode 100644 internal/application/services.go
 create mode 100644 internal/database/claims.go
 create mode 100644 internal/database/products.go
 create mode 100644 internal/models/models.go
 delete mode 100644 internal/services/userService.go

diff --git a/README.md b/README.md
index 051ef3f..69fafe3 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,12 @@
 The goal of this project is to be a crowd sourced resource to find out if a product is vegan
 
 
+# Project organization
+
+- 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
 - database object models should be separated out from database migration function
@@ -16,7 +22,19 @@ The goal of this project is to be a crowd sourced resource to find out if a prod
 - moderation tooling?
 
 
-Troubleshooting:
+
+## How to start database
+
+- Run `brew services start postgresql`
+- Create database
+    - Setting environment variables:
+        - PGHOST=localhost
+        - DB_NAME=veganDB (Can be arbitrary)
+    - Run `createdb veganDB`
+(To delete in future just run dropdb <name>)
+
+
+## Troubleshooting
 
 Error:
 `2025/04/17 16:21:21 ERROR: relation "idx_user_claims_product_id" already exists (SQLSTATE 42P07)`
@@ -29,16 +47,3 @@ Error:
 Explanation: Postgres automatically tryes to connect to a database with the same name as the user. Specify user and database:
 `psql -U username databaseName `
 
-
-
-
-## How to start database
-
-- Run `brew services start postgresql`
-- Create database
-    - Setting environment variables:
-        - PGHOST=localhost
-        - DB_NAME=veganDB (Can be arbitrary)
-    - Run `createdb veganDB`
-(To delete in future just run dropdb <name>)
-
diff --git a/cmd/server/main.go b/cmd/server/main.go
index 426e8ff..aee577a 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -1,9 +1,7 @@
 package main
 
-import (
-	"vegan-barcode/internal/api"
-)
+import "vegan-barcode/internal/api"
 
 func main() {
-	api.BindRoutes()
+	api.Start()
 }
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/api/routes.go b/internal/api/routes.go
deleted file mode 100644
index 98f48e4..0000000
--- a/internal/api/routes.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package api
-
-import (
-	"net/http"
-	"time"
-
-	"vegan-barcode/internal/database"
-	"vegan-barcode/internal/utils"
-
-	"github.com/gin-gonic/gin"
-	"github.com/labstack/gommon/log"
-	"github.com/sirupsen/logrus"
-	"github.com/vingarcia/ksql"
-)
-
-// TODO Figure out where this should be
-// This exists so that you don't have to individually pass around the logger and database.
-type ApiService struct {
-	db  *ksql.DB
-	log *logrus.Logger
-}
-
-var s *ApiService
-
-// TODO: Service should get moved somewhere else. Not sure on naming.
-func BindRoutes() {
-
-	s = &ApiService{db: database.InitializeDatabase(), log: utils.InitializeLogger()}
-	// s = &ApiService{db: &ksql.DB{}, log: utils.InitializeLogger()}
-	router := gin.Default()
-	router.GET("/test/:id", s.runTest)
-	router.GET("/", s.runTest)
-
-	// // Search for item info
-	// router.GET("/claims/{barcode}", runTest)
-
-	// // by user ID or worker
-	// router.DELETE("/claims/{barcode}", deleteClaims)
-
-	// // Update item info
-	// router.GET("/test", runTest)
-	// // Add new item info
-
-	router.Run("localhost:8080")
-}
-
-func (s *ApiService) runTest(c *gin.Context) {
-	queryParam := c.Param("id")
-	log.Debug("Test was successful.")
-
-	err := s.db.Insert(c, database.ProductsTable, &database.Product{System: "upc", Barcode: "fubar", Created_at: time.Now()})
-	if err != nil {
-		// TODO: Figure out correct status code.
-		c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to insert item to product table", "error": err.Error()})
-	}
-	c.JSON(http.StatusOK, gin.H{"message": "Hello World!", "query_param": queryParam})
-}
-
-// func claimsByBarcode(c *gin.Context) {
-// 	system := c.DefaultQuery("system", "upc")
-// 	barcode := c.Query("barcode")
-
-// }
-
-// // deleteClaims will delete
-// func deleteClaims(c *gin.Context) {
-// 	userID := c.Param("user")
-// 	workerID := c.Param("worker")
-// 	// TODO query database
-// 	database.DB.Query()
-// 	c.JSON(http.StatusOK, gin.H{"message": "Hello World!"})
-// }
diff --git a/internal/application/application.go b/internal/application/application.go
new file mode 100644
index 0000000..181c872
--- /dev/null
+++ b/internal/application/application.go
@@ -0,0 +1,22 @@
+package application
+
+import (
+	"vegan-barcode/internal/database"
+	"vegan-barcode/internal/utils"
+
+	"github.com/sirupsen/logrus"
+)
+
+type Application struct {
+	db  database.Database
+	log *logrus.Logger
+}
+
+func Start() {
+	application := Application{
+		db:  database.InitializeDatabase(),
+		log: utils.InitializeLogger(),
+	}
+
+	application.bindRoutes()
+}
diff --git a/internal/application/handlers.go b/internal/application/handlers.go
new file mode 100644
index 0000000..31bc986
--- /dev/null
+++ b/internal/application/handlers.go
@@ -0,0 +1,19 @@
+package application
+
+import (
+	"net/http"
+
+	"github.com/gin-gonic/gin"
+)
+
+func (a *Application) GetClaimsHandler(c *gin.Context) {
+	system := c.DefaultQuery("system", "upc")
+	barcode := c.Query("barcode")
+
+	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
new file mode 100644
index 0000000..f921a21
--- /dev/null
+++ b/internal/application/routes.go
@@ -0,0 +1,17 @@
+package application
+
+import (
+	"github.com/gin-gonic/gin"
+)
+
+func (application *Application) bindRoutes() {
+
+	router := gin.Default()
+
+	router.Group("/claims")
+	{
+		router.GET("/:barcode", application.GetClaimsHandler)
+	}
+
+	router.Run("localhost:8080")
+}
diff --git a/internal/application/services.go b/internal/application/services.go
new file mode 100644
index 0000000..ed0b596
--- /dev/null
+++ b/internal/application/services.go
@@ -0,0 +1,19 @@
+package application
+
+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
new file mode 100644
index 0000000..aa320a7
--- /dev/null
+++ b/internal/database/claims.go
@@ -0,0 +1,54 @@
+package database
+
+import (
+	"context"
+	"vegan-barcode/internal/models"
+)
+
+func (database *Database) FindClaimsByProductID(product_id int) (claims []models.Claim, err error) {
+	ctx := context.Background()
+	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 112f888..c605b11 100644
--- a/internal/database/database.go
+++ b/internal/database/database.go
@@ -16,8 +16,13 @@ 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
+}
+
 // initializeDatabase creates the database and calls createTables.
-func InitializeDatabase() *ksql.DB {
+func InitializeDatabase() Database {
 	ctx := context.Background()
 
 	// urlExample := "postgres://username:password@localhost:5432/database_name"
@@ -30,14 +35,18 @@ func InitializeDatabase() *ksql.DB {
 	if err != nil {
 		log.Fatalf("Unable to connect to database: %v\n", err)
 	}
-	createTables(ctx, db)
-	return &db
+
+	database := Database{
+		db: &db,
+	}
+
+	database.createTables(ctx)
+	return database
 }
 
 // createTables adds the product, automated_claims, and user_claims tables to the initialized database.
-func createTables(ctx context.Context, db ksql.DB) {
-
-	_, err := db.Exec(ctx, `
+func (database *Database) createTables(ctx context.Context) {
+	_, err := database.db.Exec(ctx, `
 		CREATE TABLE IF NOT EXISTS products (
 			id BIGSERIAL PRIMARY KEY,
 			system TEXT,
@@ -48,7 +57,7 @@ func createTables(ctx context.Context, db ksql.DB) {
 	if err != nil {
 		log.Fatal(err)
 	}
-	_, err = db.Exec(ctx, `
+	_, err = database.db.Exec(ctx, `
 		CREATE TABLE IF NOT EXISTS user_claims (
 			id BIGSERIAL PRIMARY KEY,
 			product_id INTEGER,
@@ -67,7 +76,7 @@ func createTables(ctx context.Context, db ksql.DB) {
 	if err != nil {
 		log.Fatal(err)
 	}
-	_, err = db.Exec(ctx, `
+	_, err = database.db.Exec(ctx, `
 		CREATE TABLE IF NOT EXISTS automated_claims (
 			id BIGSERIAL PRIMARY KEY,
 			product_id INTEGER,
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/services/userService.go b/internal/services/userService.go
deleted file mode 100644
index 5e568ea..0000000
--- a/internal/services/userService.go
+++ /dev/null
@@ -1 +0,0 @@
-package services
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 = ???

From 43c183c29a4ff8684f65f42b5a27724117f2795c Mon Sep 17 00:00:00 2001
From: Leyla Becker <git@jan-leila.com>
Date: Mon, 21 Apr 2025 18:54:50 -0500
Subject: [PATCH 07/15] formatted query in FindClaimsByProductID

---
 internal/database/claims.go | 81 +++++++++++++++++++------------------
 1 file changed, 42 insertions(+), 39 deletions(-)

diff --git a/internal/database/claims.go b/internal/database/claims.go
index aa320a7..b568596 100644
--- a/internal/database/claims.go
+++ b/internal/database/claims.go
@@ -7,46 +7,49 @@ import (
 
 func (database *Database) FindClaimsByProductID(product_id int) (claims []models.Claim, err error) {
 	ctx := context.Background()
-	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
+
+	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 = ?
         )
-        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)
+        WHERE rn = 1
+        ORDER BY created_at;
+    `, product_id)
+
 	if err != nil {
 		return claims, err
 	}

From 3f94f5bf99e35ff758308ebb2abc58d639e6fa57 Mon Sep 17 00:00:00 2001
From: Leyla Becker <git@jan-leila.com>
Date: Mon, 21 Apr 2025 19:10:36 -0500
Subject: [PATCH 08/15] created FindUserClaimById

---
 internal/database/claims.go | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/internal/database/claims.go b/internal/database/claims.go
index b568596..fc41339 100644
--- a/internal/database/claims.go
+++ b/internal/database/claims.go
@@ -3,6 +3,8 @@ package database
 import (
 	"context"
 	"vegan-barcode/internal/models"
+
+	"github.com/vingarcia/ksql"
 )
 
 func (database *Database) FindClaimsByProductID(product_id int) (claims []models.Claim, err error) {
@@ -55,3 +57,16 @@ func (database *Database) FindClaimsByProductID(product_id int) (claims []models
 	}
 	return
 }
+
+func (database *Database) FindUserClaimById(claim_id int) (*UserClaim, error) {
+	ctx := context.Background()
+
+	var claim UserClaim
+
+	err := database.db.QueryOne(ctx, claim, "FROM user_claims WHERE id = ?", claim_id)
+
+	if err == ksql.ErrRecordNotFound {
+		return nil, err
+	}
+	return &claim, err
+}

From 561b8841fc6853e6cab20ba34ec09548fddad943 Mon Sep 17 00:00:00 2001
From: katefort <katherine.d.fort@gmail.com>
Date: Mon, 21 Apr 2025 19:13:58 -0500
Subject: [PATCH 09/15] add create product endpoint

---
 internal/application/handlers.go | 13 ++++++++++++-
 internal/application/services.go | 21 +++++++++++++++++++++
 internal/database/claims.go      | 22 ++++++++++++++++++++--
 internal/database/database.go    |  8 ++++----
 internal/database/models.go      | 30 +++++++++++++++---------------
 internal/database/products.go    | 25 ++++++++++++++++++++++++-
 internal/models/models.go        |  8 ++++++++
 7 files changed, 104 insertions(+), 23 deletions(-)

diff --git a/internal/application/handlers.go b/internal/application/handlers.go
index 31bc986..fd95bc3 100644
--- a/internal/application/handlers.go
+++ b/internal/application/handlers.go
@@ -2,12 +2,13 @@ package application
 
 import (
 	"net/http"
+	"vegan-barcode/internal/models"
 
 	"github.com/gin-gonic/gin"
 )
 
 func (a *Application) GetClaimsHandler(c *gin.Context) {
-	system := c.DefaultQuery("system", "upc")
+	system := c.Query("system")
 	barcode := c.Query("barcode")
 
 	productClaims, err := a.GetClaims(system, barcode)
@@ -17,3 +18,13 @@ func (a *Application) GetClaimsHandler(c *gin.Context) {
 	}
 	c.JSON(http.StatusOK, productClaims)
 }
+
+func (a *Application) PostClaimHandler(c *gin.Context) {
+	system := c.Query("system")
+	barcode := c.Query("barcode")
+
+	var requestBody models.UserClaimForm
+	c.BindJSON(&requestBody)
+
+	a.CreateClaim(system, barcode, requestBody)
+}
diff --git a/internal/application/services.go b/internal/application/services.go
index ed0b596..e5d9424 100644
--- a/internal/application/services.go
+++ b/internal/application/services.go
@@ -1,6 +1,7 @@
 package application
 
 import (
+	"errors"
 	"vegan-barcode/internal/models"
 )
 
@@ -9,6 +10,9 @@ func (a *Application) GetClaims(system string, barcode string) (*models.ProductC
 	if err != nil {
 		return nil, err
 	}
+	if id == -1 {
+		return nil, errors.New("Product not found")
+	}
 
 	claims, err := a.db.FindClaimsByProductID(id)
 	if err != nil {
@@ -17,3 +21,20 @@ func (a *Application) GetClaims(system string, barcode string) (*models.ProductC
 
 	return &models.ProductClaims{Id: id, Claims: claims}, nil
 }
+
+func (a *Application) CreateClaim(system string, barcode string, form models.UserClaimForm) {
+	id, err := a.db.FindProductIDByBarcode(system, barcode)
+	if err != nil {
+		return nil, err
+	}
+	// no product, create new
+	if id == -1 {
+		product, err := a.db.CreateProduct()
+		if err != nil {
+			return nil, err
+		}
+		id = product.ID
+	}
+
+	claim, err := a.db.CreateUserClaim(id, form)
+}
diff --git a/internal/database/claims.go b/internal/database/claims.go
index b568596..c00ee43 100644
--- a/internal/database/claims.go
+++ b/internal/database/claims.go
@@ -5,10 +5,10 @@ import (
 	"vegan-barcode/internal/models"
 )
 
-func (database *Database) FindClaimsByProductID(product_id int) (claims []models.Claim, err error) {
+func (d *Database) FindClaimsByProductID(product_id int) (claims []models.Claim, err error) {
 	ctx := context.Background()
 
-	err = database.db.Query(ctx, claims, `
+	err = d.db.Query(ctx, claims, `
         SELECT
             cluster,
             id,
@@ -55,3 +55,21 @@ func (database *Database) FindClaimsByProductID(product_id int) (claims []models
 	}
 	return
 }
+
+func (d *Database) CreateUserClaim(id int, form models.UserClaimForm) (*UserClaim, error) {
+	ctx := context.Background()
+	var uc = UserClaim{
+		Id:            id,
+		Evidence_type: form.Evidence_type,
+		Evidence:      form.Evidence,
+		Claims:        form.Claims,
+		Counterclaims: form.Counterclaims,
+	}
+
+	err := d.db.Insert(ctx, UserClaimsTable, uc)
+	if err != nil {
+		return nil, err
+	}
+
+	return &uc, nil
+}
diff --git a/internal/database/database.go b/internal/database/database.go
index c605b11..1c818a6 100644
--- a/internal/database/database.go
+++ b/internal/database/database.go
@@ -63,8 +63,8 @@ func (database *Database) createTables(ctx context.Context) {
 			product_id INTEGER,
 			evidence_type INTEGER,
 			evidence  JSONB,
-			claim INTEGER,
-			counter_claim INTEGER,
+			claims INTEGER[],
+			counterclaims INTEGER[],
 			created_at TIMESTAMPTZ,
 			created_by TEXT,
 
@@ -82,8 +82,8 @@ func (database *Database) createTables(ctx context.Context) {
 			product_id INTEGER,
 			worker_type INTEGER,
 			evidence  JSONB,
-			claim INTEGER,
-			counter_claim INTEGER,
+			claims INTEGER[],
+			counterclaims INTEGER[],
 			created_at TIMESTAMPTZ,
 
 			CONSTRAINT fk_product_id FOREIGN KEY(product_id) REFERENCES products(id)
diff --git a/internal/database/models.go b/internal/database/models.go
index 27af2de..e5cdc0a 100644
--- a/internal/database/models.go
+++ b/internal/database/models.go
@@ -13,22 +13,22 @@ type Product struct {
 }
 
 type AutomatedClaim struct {
-	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"`
+	Id            int                `ksql:"id"`
+	Product_id    int                `ksql:"product_id"`
+	Worker_type   models.WorkerType  `ksql:"worker_type"`
+	Evidence      struct{}           `ksql:"evidence,json"`
+	Claims        []models.ClaimType `ksql:"claims"`
+	Counterclaims []models.ClaimType `ksql:"counterclaims"`
+	Created_at    time.Time          `ksql:"created_at,timeNowUTC"`
 }
 
 type UserClaim struct {
-	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"`
+	Id            int                 `ksql:"id"`
+	Product_id    int                 `ksql:"product_id"`
+	Evidence_type models.EvidenceType `ksql:"evidence_type"`
+	Evidence      struct{}            `ksql:"evidence,json"`
+	Claims        []models.ClaimType  `ksql:"claims"`
+	Counterclaims []models.ClaimType  `ksql:"counterclaims"`
+	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
index b6191d7..8ffd943 100644
--- a/internal/database/products.go
+++ b/internal/database/products.go
@@ -1,14 +1,37 @@
 package database
 
-import "context"
+import (
+	"context"
+	"time"
+
+	"github.com/vingarcia/ksql"
+)
 
 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 == ksql.ErrRecordNotFound {
+		return -1, nil
+	}
 	if err != nil {
 		return -1, err
 	}
 	return
 }
+
+func (d *Database) CreateProduct(system string, barcode string) (*Product, error) {
+	ctx := context.Background()
+	var product = Product{
+		System:     system,
+		Barcode:    barcode,
+		Created_at: time.Now(),
+	}
+
+	if err := d.db.Insert(ctx, ProductsTable, &product); err != nil {
+		return nil, err
+	}
+
+	return &product, nil
+}
diff --git a/internal/models/models.go b/internal/models/models.go
index 9cf290b..79eb1e1 100644
--- a/internal/models/models.go
+++ b/internal/models/models.go
@@ -59,3 +59,11 @@ type ProductClaims struct {
 	Id     int
 	Claims []Claim
 }
+
+type UserClaimForm struct {
+	Evidence_type EvidenceType
+	Evidence      struct{}
+	Claims        []ClaimType
+	Counterclaims []ClaimType
+	Created_by    string
+}

From a042866e86de6dbfd9dcfea4140622479322e88d Mon Sep 17 00:00:00 2001
From: Leyla Becker <git@jan-leila.com>
Date: Mon, 21 Apr 2025 19:43:16 -0500
Subject: [PATCH 10/15] created test for create product

---
 internal/database/claims.go      |  6 ++---
 internal/database/claims_test.go | 41 ++++++++++++++++++++++++++++++++
 internal/database/models.go      |  4 ++--
 internal/models/models.go        |  6 ++++-
 4 files changed, 51 insertions(+), 6 deletions(-)
 create mode 100644 internal/database/claims_test.go

diff --git a/internal/database/claims.go b/internal/database/claims.go
index 2d40478..8578160 100644
--- a/internal/database/claims.go
+++ b/internal/database/claims.go
@@ -71,17 +71,17 @@ func (database *Database) FindUserClaimById(claim_id int) (*UserClaim, error) {
 	return &claim, err
 }
 
-func (d *Database) CreateUserClaim(id int, form models.UserClaimForm) (*UserClaim, error) {
+func (d *Database) CreateUserClaim(product_id int, form models.UserClaimForm) (*UserClaim, error) {
 	ctx := context.Background()
 	var uc = UserClaim{
-		Id:            id,
+		Product_id:    product_id,
 		Evidence_type: form.Evidence_type,
 		Evidence:      form.Evidence,
 		Claims:        form.Claims,
 		Counterclaims: form.Counterclaims,
 	}
 
-	err := d.db.Insert(ctx, UserClaimsTable, uc)
+	err := d.db.Insert(ctx, UserClaimsTable, &uc)
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/database/claims_test.go b/internal/database/claims_test.go
new file mode 100644
index 0000000..8fcc192
--- /dev/null
+++ b/internal/database/claims_test.go
@@ -0,0 +1,41 @@
+package database
+
+import (
+	"testing"
+	"vegan-barcode/internal/models"
+)
+
+func TestCreateClaimByProductId(t *testing.T) {
+	database := InitializeDatabase()
+
+	product, _ := database.CreateProduct("upc", "1234567890")
+
+	form := models.UserClaimForm{
+		Evidence_type: models.IngredientsList,
+		Evidence: models.IngredientsListEvidence{
+			Ingredients: "flour,egg,milk,water,salt,butter",
+		},
+		Claims: []models.ClaimType{
+			models.ContainsEggs,
+			models.ContainsMilk,
+		},
+		Counterclaims: []models.ClaimType{
+			models.ContainsMeat,
+			models.ContainsFish,
+			models.ContainsHoney,
+		},
+		Created_by: "1",
+	}
+
+	claim, err := database.CreateUserClaim(product.Id, form)
+
+	if err != nil {
+		t.Errorf("Got error while creating user claim %v", err)
+	}
+
+	foundClaim, _ := database.FindUserClaimById(claim.Id)
+
+	if foundClaim == nil {
+		t.Errorf("Could not find created claim %d", claim.Id)
+	}
+}
diff --git a/internal/database/models.go b/internal/database/models.go
index e5cdc0a..d93dc5c 100644
--- a/internal/database/models.go
+++ b/internal/database/models.go
@@ -16,7 +16,7 @@ type AutomatedClaim struct {
 	Id            int                `ksql:"id"`
 	Product_id    int                `ksql:"product_id"`
 	Worker_type   models.WorkerType  `ksql:"worker_type"`
-	Evidence      struct{}           `ksql:"evidence,json"`
+	Evidence      interface{}        `ksql:"evidence,json"`
 	Claims        []models.ClaimType `ksql:"claims"`
 	Counterclaims []models.ClaimType `ksql:"counterclaims"`
 	Created_at    time.Time          `ksql:"created_at,timeNowUTC"`
@@ -26,7 +26,7 @@ type UserClaim struct {
 	Id            int                 `ksql:"id"`
 	Product_id    int                 `ksql:"product_id"`
 	Evidence_type models.EvidenceType `ksql:"evidence_type"`
-	Evidence      struct{}            `ksql:"evidence,json"`
+	Evidence      interface{}         `ksql:"evidence,json"`
 	Claims        []models.ClaimType  `ksql:"claims"`
 	Counterclaims []models.ClaimType  `ksql:"counterclaims"`
 	Created_at    time.Time           `ksql:"created_at,timeNowUTC"`
diff --git a/internal/models/models.go b/internal/models/models.go
index 79eb1e1..b6efda3 100644
--- a/internal/models/models.go
+++ b/internal/models/models.go
@@ -35,6 +35,10 @@ const (
 	IngredientsList
 )
 
+type IngredientsListEvidence struct {
+	Ingredients string
+}
+
 type ClusterType int
 
 const (
@@ -62,7 +66,7 @@ type ProductClaims struct {
 
 type UserClaimForm struct {
 	Evidence_type EvidenceType
-	Evidence      struct{}
+	Evidence      interface{}
 	Claims        []ClaimType
 	Counterclaims []ClaimType
 	Created_by    string

From fa4561d90bb47cac703d7925fe87545b31012601 Mon Sep 17 00:00:00 2001
From: katefort <katherine.d.fort@gmail.com>
Date: Mon, 21 Apr 2025 20:43:02 -0500
Subject: [PATCH 11/15] fix create claim

---
 cmd/server/main.go               |  4 ++--
 internal/application/handlers.go | 26 ++++++++++++++++++++------
 internal/application/routes.go   |  8 ++++----
 internal/application/services.go | 20 ++++++++++----------
 internal/database/products.go    | 12 +++++++-----
 internal/models/models.go        |  2 +-
 6 files changed, 44 insertions(+), 28 deletions(-)

diff --git a/cmd/server/main.go b/cmd/server/main.go
index aee577a..f8bd9ef 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -1,7 +1,7 @@
 package main
 
-import "vegan-barcode/internal/api"
+import "vegan-barcode/internal/application"
 
 func main() {
-	api.Start()
+	application.Start()
 }
diff --git a/internal/application/handlers.go b/internal/application/handlers.go
index fd95bc3..0d0e490 100644
--- a/internal/application/handlers.go
+++ b/internal/application/handlers.go
@@ -7,9 +7,13 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
+func (a *Application) TestHandler(c *gin.Context) {
+	c.JSON(http.StatusOK, gin.H{"message": "Hello World!"})
+}
+
 func (a *Application) GetClaimsHandler(c *gin.Context) {
-	system := c.Query("system")
-	barcode := c.Query("barcode")
+	system := c.Param("system")
+	barcode := c.Param("barcode")
 
 	productClaims, err := a.GetClaims(system, barcode)
 	if err != nil {
@@ -20,11 +24,21 @@ func (a *Application) GetClaimsHandler(c *gin.Context) {
 }
 
 func (a *Application) PostClaimHandler(c *gin.Context) {
-	system := c.Query("system")
-	barcode := c.Query("barcode")
+	system := c.Param("system")
+	barcode := c.Param("barcode")
 
 	var requestBody models.UserClaimForm
-	c.BindJSON(&requestBody)
+	err := c.BindJSON(&requestBody)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, nil)
+		return
+	}
 
-	a.CreateClaim(system, barcode, requestBody)
+	claim, err := a.CreateClaim(system, barcode, requestBody)
+	if err != nil {
+		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+		return
+	}
+
+	c.JSON(http.StatusOK, claim)
 }
diff --git a/internal/application/routes.go b/internal/application/routes.go
index f921a21..f7af42a 100644
--- a/internal/application/routes.go
+++ b/internal/application/routes.go
@@ -8,10 +8,10 @@ func (application *Application) bindRoutes() {
 
 	router := gin.Default()
 
-	router.Group("/claims")
-	{
-		router.GET("/:barcode", application.GetClaimsHandler)
-	}
+	router.GET("/", application.TestHandler)
+
+	router.GET("/claims/:system/:barcode", application.GetClaimsHandler)
+	router.POST("/claims/:system/:barcode", application.PostClaimHandler)
 
 	router.Run("localhost:8080")
 }
diff --git a/internal/application/services.go b/internal/application/services.go
index e5d9424..ae26cf7 100644
--- a/internal/application/services.go
+++ b/internal/application/services.go
@@ -2,39 +2,39 @@ package application
 
 import (
 	"errors"
+	"vegan-barcode/internal/database"
 	"vegan-barcode/internal/models"
 )
 
 func (a *Application) GetClaims(system string, barcode string) (*models.ProductClaims, error) {
-	id, err := a.db.FindProductIDByBarcode(system, barcode)
+	product, err := a.db.FindProductByBarcode(system, barcode)
 	if err != nil {
 		return nil, err
 	}
-	if id == -1 {
+	if product == nil {
 		return nil, errors.New("Product not found")
 	}
 
-	claims, err := a.db.FindClaimsByProductID(id)
+	claims, err := a.db.FindClaimsByProductID(product.Id)
 	if err != nil {
 		return nil, err
 	}
 
-	return &models.ProductClaims{Id: id, Claims: claims}, nil
+	return &models.ProductClaims{Id: product.Id, Claims: claims}, nil
 }
 
-func (a *Application) CreateClaim(system string, barcode string, form models.UserClaimForm) {
-	id, err := a.db.FindProductIDByBarcode(system, barcode)
+func (a *Application) CreateClaim(system string, barcode string, form models.UserClaimForm) (*database.UserClaim, error) {
+	product, err := a.db.FindProductByBarcode(system, barcode)
 	if err != nil {
 		return nil, err
 	}
 	// no product, create new
-	if id == -1 {
-		product, err := a.db.CreateProduct()
+	if product == nil {
+		product, err = a.db.CreateProduct(system, barcode)
 		if err != nil {
 			return nil, err
 		}
-		id = product.ID
 	}
 
-	claim, err := a.db.CreateUserClaim(id, form)
+	return a.db.CreateUserClaim(product.Id, form)
 }
diff --git a/internal/database/products.go b/internal/database/products.go
index 8ffd943..75dc6ab 100644
--- a/internal/database/products.go
+++ b/internal/database/products.go
@@ -7,18 +7,20 @@ import (
 	"github.com/vingarcia/ksql"
 )
 
-func (d *Database) FindProductIDByBarcode(system string, barcode string) (id int, err error) {
+func (d *Database) FindProductByBarcode(system string, barcode string) (*Product, error) {
 
 	ctx := context.Background()
+	ctx = ksql.InjectLogger(ctx, ksql.Logger)
 
-	err = d.db.QueryOne(ctx, &id, "SELECT id FROM products WHERE system = ? AND barcode = ?", system, barcode)
+	var product Product
+	err := d.db.QueryOne(ctx, &product, "FROM products WHERE system = $1 AND barcode = $2", system, barcode)
 	if err == ksql.ErrRecordNotFound {
-		return -1, nil
+		return nil, nil
 	}
 	if err != nil {
-		return -1, err
+		return nil, err
 	}
-	return
+	return &product, nil
 }
 
 func (d *Database) CreateProduct(system string, barcode string) (*Product, error) {
diff --git a/internal/models/models.go b/internal/models/models.go
index b6efda3..3b1b77a 100644
--- a/internal/models/models.go
+++ b/internal/models/models.go
@@ -31,7 +31,7 @@ const (
 type EvidenceType int
 
 const (
-	ManufactureWebsite EvidenceType = iota
+	ManufacturerWebsite EvidenceType = iota
 	IngredientsList
 )
 

From 79977f1a181a59645eb16c4834395bcc9445c91b Mon Sep 17 00:00:00 2001
From: katefort <katherine.d.fort@gmail.com>
Date: Mon, 21 Apr 2025 20:53:31 -0500
Subject: [PATCH 12/15] change unknown query params to

---
 internal/application/models.go             | 1 +
 internal/{ => application}/utils/logger.go | 0
 internal/database/claims.go                | 4 ++--
 3 files changed, 3 insertions(+), 2 deletions(-)
 create mode 100644 internal/application/models.go
 rename internal/{ => application}/utils/logger.go (100%)

diff --git a/internal/application/models.go b/internal/application/models.go
new file mode 100644
index 0000000..b584a8a
--- /dev/null
+++ b/internal/application/models.go
@@ -0,0 +1 @@
+package application
diff --git a/internal/utils/logger.go b/internal/application/utils/logger.go
similarity index 100%
rename from internal/utils/logger.go
rename to internal/application/utils/logger.go
diff --git a/internal/database/claims.go b/internal/database/claims.go
index 8578160..92a5f1f 100644
--- a/internal/database/claims.go
+++ b/internal/database/claims.go
@@ -46,7 +46,7 @@ func (d *Database) FindClaimsByProductID(product_id int) (claims []models.Claim,
                     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 product_id = $1
         )
         WHERE rn = 1
         ORDER BY created_at;
@@ -63,7 +63,7 @@ func (database *Database) FindUserClaimById(claim_id int) (*UserClaim, error) {
 
 	var claim UserClaim
 
-	err := database.db.QueryOne(ctx, claim, "FROM user_claims WHERE id = ?", claim_id)
+	err := database.db.QueryOne(ctx, claim, "FROM user_claims WHERE id = $1", claim_id)
 
 	if err == ksql.ErrRecordNotFound {
 		return nil, err

From e762fc2abd071a2d174e2bdab70e30adbfd0a8ee Mon Sep 17 00:00:00 2001
From: katefort <katherine.d.fort@gmail.com>
Date: Wed, 23 Apr 2025 15:34:19 -0500
Subject: [PATCH 13/15] make get and post endpoints work, update enums to be
 strings, add log file output

---
 cmd/server/main.go                   |  4 +-
 internal/application/application.go  | 12 +++---
 internal/application/handlers.go     |  7 +++-
 internal/application/models.go       |  1 -
 internal/application/services.go     |  2 +-
 internal/application/utils/logger.go | 13 ------
 internal/database/claims.go          | 19 +++++----
 internal/database/claims_test.go     |  2 +-
 internal/database/database.go        | 12 +++---
 internal/database/products.go        |  9 +++-
 internal/models/models.go            | 63 ++++++++++++++--------------
 internal/utils/logger.go             | 22 ++++++++++
 12 files changed, 94 insertions(+), 72 deletions(-)
 delete mode 100644 internal/application/models.go
 delete mode 100644 internal/application/utils/logger.go
 create mode 100644 internal/utils/logger.go

diff --git a/cmd/server/main.go b/cmd/server/main.go
index f8bd9ef..e932f38 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -1,6 +1,8 @@
 package main
 
-import "vegan-barcode/internal/application"
+import (
+	"vegan-barcode/internal/application"
+)
 
 func main() {
 	application.Start()
diff --git a/internal/application/application.go b/internal/application/application.go
index 181c872..1df7485 100644
--- a/internal/application/application.go
+++ b/internal/application/application.go
@@ -3,20 +3,18 @@ package application
 import (
 	"vegan-barcode/internal/database"
 	"vegan-barcode/internal/utils"
-
-	"github.com/sirupsen/logrus"
 )
 
 type Application struct {
-	db  database.Database
-	log *logrus.Logger
+	db database.Database
+	// TODO: possibly include logger?
 }
 
 func Start() {
 	application := Application{
-		db:  database.InitializeDatabase(),
-		log: utils.InitializeLogger(),
+		db: database.InitializeDatabase(),
 	}
-
+	utils.InitializeLogger()
 	application.bindRoutes()
+
 }
diff --git a/internal/application/handlers.go b/internal/application/handlers.go
index 0d0e490..46b4bf5 100644
--- a/internal/application/handlers.go
+++ b/internal/application/handlers.go
@@ -1,10 +1,12 @@
 package application
 
 import (
+	"fmt"
 	"net/http"
 	"vegan-barcode/internal/models"
 
 	"github.com/gin-gonic/gin"
+	log "github.com/sirupsen/logrus"
 )
 
 func (a *Application) TestHandler(c *gin.Context) {
@@ -17,7 +19,7 @@ func (a *Application) GetClaimsHandler(c *gin.Context) {
 
 	productClaims, err := a.GetClaims(system, barcode)
 	if err != nil {
-		c.JSON(http.StatusInternalServerError, nil)
+		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
 		return
 	}
 	c.JSON(http.StatusOK, productClaims)
@@ -30,9 +32,10 @@ func (a *Application) PostClaimHandler(c *gin.Context) {
 	var requestBody models.UserClaimForm
 	err := c.BindJSON(&requestBody)
 	if err != nil {
-		c.JSON(http.StatusBadRequest, nil)
+		c.JSON(http.StatusBadRequest, fmt.Errorf("improperly formatted request: %w", err))
 		return
 	}
+	log.Debugf("requestbody: %v", requestBody)
 
 	claim, err := a.CreateClaim(system, barcode, requestBody)
 	if err != nil {
diff --git a/internal/application/models.go b/internal/application/models.go
deleted file mode 100644
index b584a8a..0000000
--- a/internal/application/models.go
+++ /dev/null
@@ -1 +0,0 @@
-package application
diff --git a/internal/application/services.go b/internal/application/services.go
index ae26cf7..9120055 100644
--- a/internal/application/services.go
+++ b/internal/application/services.go
@@ -12,7 +12,7 @@ func (a *Application) GetClaims(system string, barcode string) (*models.ProductC
 		return nil, err
 	}
 	if product == nil {
-		return nil, errors.New("Product not found")
+		return nil, errors.New("product not found")
 	}
 
 	claims, err := a.db.FindClaimsByProductID(product.Id)
diff --git a/internal/application/utils/logger.go b/internal/application/utils/logger.go
deleted file mode 100644
index a83abc3..0000000
--- a/internal/application/utils/logger.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package utils
-
-import (
-	"github.com/sirupsen/logrus"
-)
-
-func InitializeLogger() *logrus.Logger {
-	log := logrus.New()
-	log.SetFormatter(&logrus.TextFormatter{
-		FullTimestamp: true, // Include the full timestamp (with date and time)
-	})
-	return log
-}
diff --git a/internal/database/claims.go b/internal/database/claims.go
index 92a5f1f..033ea4b 100644
--- a/internal/database/claims.go
+++ b/internal/database/claims.go
@@ -2,6 +2,7 @@ package database
 
 import (
 	"context"
+	"fmt"
 	"vegan-barcode/internal/models"
 
 	"github.com/vingarcia/ksql"
@@ -10,7 +11,7 @@ import (
 func (d *Database) FindClaimsByProductID(product_id int) (claims []models.Claim, err error) {
 	ctx := context.Background()
 
-	err = d.db.Query(ctx, claims, `
+	err = d.db.Query(ctx, &claims, `
         SELECT
             cluster,
             id,
@@ -35,29 +36,30 @@ func (d *Database) FindClaimsByProductID(product_id int) (claims []models.Claim,
                 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
+                    SELECT 'user' as cluster, id, product_id, null as worker_type, evidence_type, evidence, unnest(claims) 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
+                    SELECT 'automated' as cluster, id, product_id, worker_type, null as evidence_type, evidence, unnest(claims) 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
+                    SELECT 'user' as cluster, id, product_id, null as worker_type, evidence_type, evidence, unnest(counterclaims) 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
+                    SELECT 'automated' as cluster, id, product_id, worker_type, null as evidence_type, evidence, unnest(counterclaims) as category, false as polarity, created_at, null as created_by FROM automated_claims
                 )
-            )
+            ) AS combined_claims
             WHERE product_id = $1
-        )
+        ) AS ranked_claims
         WHERE rn = 1
         ORDER BY created_at;
     `, product_id)
 
 	if err != nil {
-		return claims, err
+		return nil, fmt.Errorf("query failed: %w", err)
 	}
 	return
 }
 
+// exists only for testing
 func (database *Database) FindUserClaimById(claim_id int) (*UserClaim, error) {
 	ctx := context.Background()
 
@@ -79,6 +81,7 @@ func (d *Database) CreateUserClaim(product_id int, form models.UserClaimForm) (*
 		Evidence:      form.Evidence,
 		Claims:        form.Claims,
 		Counterclaims: form.Counterclaims,
+		// TODO: Add created by
 	}
 
 	err := d.db.Insert(ctx, UserClaimsTable, &uc)
diff --git a/internal/database/claims_test.go b/internal/database/claims_test.go
index 8fcc192..81769a6 100644
--- a/internal/database/claims_test.go
+++ b/internal/database/claims_test.go
@@ -16,7 +16,7 @@ func TestCreateClaimByProductId(t *testing.T) {
 			Ingredients: "flour,egg,milk,water,salt,butter",
 		},
 		Claims: []models.ClaimType{
-			models.ContainsEggs,
+			models.ContainsEgg,
 			models.ContainsMilk,
 		},
 		Counterclaims: []models.ClaimType{
diff --git a/internal/database/database.go b/internal/database/database.go
index 1c818a6..71555d8 100644
--- a/internal/database/database.go
+++ b/internal/database/database.go
@@ -61,10 +61,10 @@ func (database *Database) createTables(ctx context.Context) {
 		CREATE TABLE IF NOT EXISTS user_claims (
 			id BIGSERIAL PRIMARY KEY,
 			product_id INTEGER,
-			evidence_type INTEGER,
+			evidence_type TEXT,
 			evidence  JSONB,
-			claims INTEGER[],
-			counterclaims INTEGER[],
+			claims TEXT[],
+			counterclaims TEXT[],
 			created_at TIMESTAMPTZ,
 			created_by TEXT,
 
@@ -80,10 +80,10 @@ func (database *Database) createTables(ctx context.Context) {
 		CREATE TABLE IF NOT EXISTS automated_claims (
 			id BIGSERIAL PRIMARY KEY,
 			product_id INTEGER,
-			worker_type INTEGER,
+			worker_type TEXT,
 			evidence  JSONB,
-			claims INTEGER[],
-			counterclaims INTEGER[],
+			claims TEXT[],
+			counterclaims TEXT[],
 			created_at TIMESTAMPTZ,
 
 			CONSTRAINT fk_product_id FOREIGN KEY(product_id) REFERENCES products(id)
diff --git a/internal/database/products.go b/internal/database/products.go
index 75dc6ab..1eb3e5c 100644
--- a/internal/database/products.go
+++ b/internal/database/products.go
@@ -2,8 +2,10 @@ package database
 
 import (
 	"context"
+	"fmt"
 	"time"
 
+	log "github.com/sirupsen/logrus"
 	"github.com/vingarcia/ksql"
 )
 
@@ -14,6 +16,8 @@ func (d *Database) FindProductByBarcode(system string, barcode string) (*Product
 
 	var product Product
 	err := d.db.QueryOne(ctx, &product, "FROM products WHERE system = $1 AND barcode = $2", system, barcode)
+
+	// No error because product may just not exist.
 	if err == ksql.ErrRecordNotFound {
 		return nil, nil
 	}
@@ -23,6 +27,7 @@ func (d *Database) FindProductByBarcode(system string, barcode string) (*Product
 	return &product, nil
 }
 
+// Doesnt handle checking if product exists.
 func (d *Database) CreateProduct(system string, barcode string) (*Product, error) {
 	ctx := context.Background()
 	var product = Product{
@@ -31,8 +36,10 @@ func (d *Database) CreateProduct(system string, barcode string) (*Product, error
 		Created_at: time.Now(),
 	}
 
+	log.Debugf("made new product: %v", product)
+
 	if err := d.db.Insert(ctx, ProductsTable, &product); err != nil {
-		return nil, err
+		return nil, fmt.Errorf("failed to insert new product: %w", err)
 	}
 
 	return &product, nil
diff --git a/internal/models/models.go b/internal/models/models.go
index 3b1b77a..a299443 100644
--- a/internal/models/models.go
+++ b/internal/models/models.go
@@ -4,59 +4,60 @@ import (
 	"time"
 )
 
-type ClaimType int
+type ClaimType string
 
 const (
-	ContainsMeat ClaimType = iota
-	ContainsFish
-	ContainsEggs
-	ContainsMilk
-	ContainsHoney
-	ContainsWax
-	ContainsFur
-	ContainsLeather
-	ContainsAnimalFibers
-	ContainsWool
-	ContainsFeathers
-	AnimalTesting
-	MonkeySlavery
+	ContainsMeat         ClaimType = "meat"
+	ContainsFish         ClaimType = "fish"
+	ContainsEgg          ClaimType = "egg"
+	ContainsMilk         ClaimType = "milk"
+	ContainsHoney        ClaimType = "honey"
+	ContainsWax          ClaimType = "wax"
+	ContainsFur          ClaimType = "fur"
+	ContainsLeather      ClaimType = "leather"
+	ContainsAnimalFibers ClaimType = "animal_fibers"
+	ContainsWool         ClaimType = "wool"
+	ContainsFeathers     ClaimType = "feathers"
+	AnimalTesting        ClaimType = "animal_testing"
+	MonkeySlavery        ClaimType = "monkey_slavery"
 )
 
-type WorkerType int
+type WorkerType string
 
 const (
-	Barnivore WorkerType = iota
+	Barnivore WorkerType = "barnivore"
 )
 
-type EvidenceType int
+// Using a string here to make database modification easier.
+type EvidenceType string
 
 const (
-	ManufacturerWebsite EvidenceType = iota
-	IngredientsList
+	ManufacturerWebsite EvidenceType = "manufacturer"
+	IngredientsList     EvidenceType = "ingredients"
 )
 
 type IngredientsListEvidence struct {
 	Ingredients string
 }
 
-type ClusterType int
+type ClusterType string
 
 const (
-	User ClusterType = iota
-	Automated
+	User      ClusterType = "user"
+	Automated ClusterType = "auto"
 )
 
 // 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
+	Id            int          `ksql:"id"`
+	Worker_type   *WorkerType  `ksql:"worker_type"`
+	Evidence_type EvidenceType `ksql:"evidence_type"`
+	Evidence      struct{}     `ksql:"evidence"`
+	Category      ClaimType    `ksql:"category"`
+	Polarity      bool         `ksql:"polarity"`
+	Created_at    time.Time    `ksql:"created_at"`
+	Created_by    string       `ksql:"created_by"`
+	Cluster       ClusterType  `ksql:"cluster"`
 }
 
 type ProductClaims struct {
diff --git a/internal/utils/logger.go b/internal/utils/logger.go
new file mode 100644
index 0000000..16e3d09
--- /dev/null
+++ b/internal/utils/logger.go
@@ -0,0 +1,22 @@
+package utils
+
+import (
+	"io"
+	"os"
+
+	log "github.com/sirupsen/logrus"
+)
+
+func InitializeLogger() {
+	file, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
+	if err != nil {
+		log.Fatal(err)
+	}
+	log.SetOutput(io.MultiWriter(os.Stdout, file))
+
+	log.SetFormatter(&log.TextFormatter{
+		FullTimestamp: true,
+	})
+
+	log.SetLevel(log.DebugLevel)
+}

From 34218e730602c05134de4838c0e382e644010911 Mon Sep 17 00:00:00 2001
From: katefort <katherine.d.fort@gmail.com>
Date: Wed, 23 Apr 2025 16:23:59 -0500
Subject: [PATCH 14/15] add comments, make errors more consistent

---
 .gitignore                       |  2 +-
 README.md                        |  3 +++
 internal/application/handlers.go |  6 +++---
 internal/application/services.go | 15 +++++----------
 internal/database/claims.go      |  7 ++++---
 internal/database/database.go    |  3 ++-
 internal/database/products.go    | 22 +++++++++++++++++++---
 7 files changed, 37 insertions(+), 21 deletions(-)

diff --git a/.gitignore b/.gitignore
index ea63aaa..434c14b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,7 @@
 .envrc
 .direnv
 cmd/server/server
-
+cmd/server/app.log
 # postgres
 postgres.db
 postgres.log
diff --git a/README.md b/README.md
index 69fafe3..86efcab 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,9 @@ The goal of this project is to be a crowd sourced resource to find out if a prod
 - create mobile and desktop front ends
 - moderation tooling?
 
+## Tip
+Paste this to log the SQL queries in stdout.
+ctx = ksql.InjectLogger(ctx, ksql.Logger)
 
 
 ## How to start database
diff --git a/internal/application/handlers.go b/internal/application/handlers.go
index 46b4bf5..aaa1a54 100644
--- a/internal/application/handlers.go
+++ b/internal/application/handlers.go
@@ -1,7 +1,6 @@
 package application
 
 import (
-	"fmt"
 	"net/http"
 	"vegan-barcode/internal/models"
 
@@ -25,6 +24,7 @@ func (a *Application) GetClaimsHandler(c *gin.Context) {
 	c.JSON(http.StatusOK, productClaims)
 }
 
+// PostClaimHandler takes the parameters and body form
 func (a *Application) PostClaimHandler(c *gin.Context) {
 	system := c.Param("system")
 	barcode := c.Param("barcode")
@@ -32,12 +32,12 @@ func (a *Application) PostClaimHandler(c *gin.Context) {
 	var requestBody models.UserClaimForm
 	err := c.BindJSON(&requestBody)
 	if err != nil {
-		c.JSON(http.StatusBadRequest, fmt.Errorf("improperly formatted request: %w", err))
+		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
 		return
 	}
 	log.Debugf("requestbody: %v", requestBody)
 
-	claim, err := a.CreateClaim(system, barcode, requestBody)
+	claim, err := a.CreateUserClaim(system, barcode, requestBody)
 	if err != nil {
 		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
 		return
diff --git a/internal/application/services.go b/internal/application/services.go
index 9120055..8177447 100644
--- a/internal/application/services.go
+++ b/internal/application/services.go
@@ -23,18 +23,13 @@ func (a *Application) GetClaims(system string, barcode string) (*models.ProductC
 	return &models.ProductClaims{Id: product.Id, Claims: claims}, nil
 }
 
-func (a *Application) CreateClaim(system string, barcode string, form models.UserClaimForm) (*database.UserClaim, error) {
-	product, err := a.db.FindProductByBarcode(system, barcode)
+// CreateUserClaim gets the product ID or creates a new entry if not found,
+// then calls InsertUserClaim to create the claim object and put it in the database.
+func (a *Application) CreateUserClaim(system string, barcode string, form models.UserClaimForm) (*database.UserClaim, error) {
+	product, err := a.db.FindOrCreateProduct(system, barcode)
 	if err != nil {
 		return nil, err
 	}
-	// no product, create new
-	if product == nil {
-		product, err = a.db.CreateProduct(system, barcode)
-		if err != nil {
-			return nil, err
-		}
-	}
 
-	return a.db.CreateUserClaim(product.Id, form)
+	return a.db.InsertUserClaim(product.Id, form)
 }
diff --git a/internal/database/claims.go b/internal/database/claims.go
index 033ea4b..73532b8 100644
--- a/internal/database/claims.go
+++ b/internal/database/claims.go
@@ -59,7 +59,7 @@ func (d *Database) FindClaimsByProductID(product_id int) (claims []models.Claim,
 	return
 }
 
-// exists only for testing
+// Testing function
 func (database *Database) FindUserClaimById(claim_id int) (*UserClaim, error) {
 	ctx := context.Background()
 
@@ -73,7 +73,8 @@ func (database *Database) FindUserClaimById(claim_id int) (*UserClaim, error) {
 	return &claim, err
 }
 
-func (d *Database) CreateUserClaim(product_id int, form models.UserClaimForm) (*UserClaim, error) {
+// InsertUserClaim puts creates a new user claim database object and inserts in DB.
+func (d *Database) InsertUserClaim(product_id int, form models.UserClaimForm) (*UserClaim, error) {
 	ctx := context.Background()
 	var uc = UserClaim{
 		Product_id:    product_id,
@@ -81,7 +82,7 @@ func (d *Database) CreateUserClaim(product_id int, form models.UserClaimForm) (*
 		Evidence:      form.Evidence,
 		Claims:        form.Claims,
 		Counterclaims: form.Counterclaims,
-		// TODO: Add created by
+		Created_by:    form.Created_by,
 	}
 
 	err := d.db.Insert(ctx, UserClaimsTable, &uc)
diff --git a/internal/database/database.go b/internal/database/database.go
index 71555d8..42604f3 100644
--- a/internal/database/database.go
+++ b/internal/database/database.go
@@ -51,7 +51,8 @@ func (database *Database) createTables(ctx context.Context) {
 			id BIGSERIAL PRIMARY KEY,
 			system TEXT,
 			barcode TEXT,
-			created_at TIMESTAMPTZ
+			created_at TIMESTAMPTZ,
+			UNIQUE (system, barcode)
 		);
 	`)
 	if err != nil {
diff --git a/internal/database/products.go b/internal/database/products.go
index 1eb3e5c..77f5ad0 100644
--- a/internal/database/products.go
+++ b/internal/database/products.go
@@ -9,10 +9,26 @@ import (
 	"github.com/vingarcia/ksql"
 )
 
+func (d *Database) FindOrCreateProduct(system string, barcode string) (*Product, error) {
+	product, err := d.FindProductByBarcode(system, barcode)
+	if err != nil {
+		return nil, err
+	}
+
+	// If the product doesn't exist yet, create it
+	if product == nil {
+		product, err = d.CreateProduct(system, barcode)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return product, nil
+}
+
+// FindProductByBarcode will return nil with no error if the product doesn't exist.
 func (d *Database) FindProductByBarcode(system string, barcode string) (*Product, error) {
 
 	ctx := context.Background()
-	ctx = ksql.InjectLogger(ctx, ksql.Logger)
 
 	var product Product
 	err := d.db.QueryOne(ctx, &product, "FROM products WHERE system = $1 AND barcode = $2", system, barcode)
@@ -27,7 +43,7 @@ func (d *Database) FindProductByBarcode(system string, barcode string) (*Product
 	return &product, nil
 }
 
-// Doesnt handle checking if product exists.
+// CreateProduct simply makes an entry in the products table. Prefer FindOrCreateProduct
 func (d *Database) CreateProduct(system string, barcode string) (*Product, error) {
 	ctx := context.Background()
 	var product = Product{
@@ -36,7 +52,7 @@ func (d *Database) CreateProduct(system string, barcode string) (*Product, error
 		Created_at: time.Now(),
 	}
 
-	log.Debugf("made new product: %v", product)
+	log.Debugf("successfully created new product: %v", product)
 
 	if err := d.db.Insert(ctx, ProductsTable, &product); err != nil {
 		return nil, fmt.Errorf("failed to insert new product: %w", err)

From 57dce100975eaf39b5321ddcc0f49750ec752bf9 Mon Sep 17 00:00:00 2001
From: katefort <katherine.d.fort@gmail.com>
Date: Wed, 23 Apr 2025 16:29:01 -0500
Subject: [PATCH 15/15] organize routes into route groups, add comments

---
 internal/application/routes.go   | 7 +++++--
 internal/application/services.go | 2 ++
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/internal/application/routes.go b/internal/application/routes.go
index f7af42a..8a8f21c 100644
--- a/internal/application/routes.go
+++ b/internal/application/routes.go
@@ -10,8 +10,11 @@ func (application *Application) bindRoutes() {
 
 	router.GET("/", application.TestHandler)
 
-	router.GET("/claims/:system/:barcode", application.GetClaimsHandler)
-	router.POST("/claims/:system/:barcode", application.PostClaimHandler)
+	claims := router.Group("/claims/:system/:barcode")
+	{
+		claims.GET("", application.GetClaimsHandler)
+		claims.POST("", application.PostClaimHandler)
+	}
 
 	router.Run("localhost:8080")
 }
diff --git a/internal/application/services.go b/internal/application/services.go
index 8177447..3dae047 100644
--- a/internal/application/services.go
+++ b/internal/application/services.go
@@ -6,6 +6,8 @@ import (
 	"vegan-barcode/internal/models"
 )
 
+// GetClaims doesn't automatically create a new product because maybe the user mistyped.
+// Only create a new product when they want to add claims.
 func (a *Application) GetClaims(system string, barcode string) (*models.ProductClaims, error) {
 	product, err := a.db.FindProductByBarcode(system, barcode)
 	if err != nil {