diff --git a/api/platform/create.go b/api/platform/create.go index 7867535..db2c3a4 100644 --- a/api/platform/create.go +++ b/api/platform/create.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" "net/http" - "products/internal/db" + db "products/internal/db/platform" "time" "github.com/jackc/pgx/v5/pgtype" diff --git a/api/platform/get.go b/api/platform/get.go index 42c3144..40f18c8 100644 --- a/api/platform/get.go +++ b/api/platform/get.go @@ -6,7 +6,7 @@ import ( "errors" "net/http" "products/internal" - "products/internal/db" + db "products/internal/db/platform" "time" "github.com/jackc/pgx/v5" diff --git a/api/platform/platform.go b/api/platform/platform.go index 7b977d6..6a870b3 100644 --- a/api/platform/platform.go +++ b/api/platform/platform.go @@ -1,10 +1,12 @@ package platformHandler +import "products/internal/db/platform" + type PlatformHandler struct { - queries PlatformQuerier + queries platform.Querier } -func NewPlatformHandler(q PlatformQuerier) *PlatformHandler { +func NewPlatformHandler(q platform.Querier) *PlatformHandler { return &PlatformHandler{ queries: q, } diff --git a/api/platform/platform_querier.go b/api/platform/platform_querier.go deleted file mode 100644 index c0c330c..0000000 --- a/api/platform/platform_querier.go +++ /dev/null @@ -1,14 +0,0 @@ -package platformHandler - -import ( - "context" - "products/internal/db" -) - -type PlatformQuerier interface { - CreatePlatform(ctx context.Context, arg db.CreatePlatformParams) error - DeletePlatform(ctx context.Context, id int32) (int32, error) - GetPlatform(ctx context.Context, id int32) (db.Platform, error) - GetPlatforms(ctx context.Context) ([]db.Platform, error) - UpdatePlatform(ctx context.Context, arg db.UpdatePlatformParams) (int32, error) -} diff --git a/api/platform/platform_test.go b/api/platform/platform_test.go index b061f26..33ec606 100644 --- a/api/platform/platform_test.go +++ b/api/platform/platform_test.go @@ -7,7 +7,7 @@ import ( "errors" "net/http" "net/http/httptest" - "products/internal/db" + "products/internal/db/platform" "strconv" "testing" @@ -17,14 +17,14 @@ import ( type mockPlatformQuerier struct { err error - createPlatform func(ctx context.Context, arg db.CreatePlatformParams) error - getPlatforms func(ctx context.Context) ([]db.Platform, error) - getPlatform func(ctx context.Context, id int32) (db.Platform, error) + createPlatform func(ctx context.Context, arg platform.CreatePlatformParams) error + getPlatforms func(ctx context.Context) ([]platform.Platform, error) + getPlatform func(ctx context.Context, id int32) (platform.Platform, error) deletePlatform func(ctx context.Context, id int32) (int32, error) - updatePlatform func(ctx context.Context, arg db.UpdatePlatformParams) (int32, error) + updatePlatform func(ctx context.Context, arg platform.UpdatePlatformParams) (int32, error) } -func (m *mockPlatformQuerier) CreatePlatform(ctx context.Context, arg db.CreatePlatformParams) error { +func (m *mockPlatformQuerier) CreatePlatform(ctx context.Context, arg platform.CreatePlatformParams) error { if m.createPlatform != nil { return m.createPlatform(ctx, arg) } @@ -38,21 +38,21 @@ func (m *mockPlatformQuerier) DeletePlatform(ctx context.Context, id int32) (int return -1, m.err } -func (m *mockPlatformQuerier) GetPlatform(ctx context.Context, id int32) (db.Platform, error) { +func (m *mockPlatformQuerier) GetPlatform(ctx context.Context, id int32) (platform.Platform, error) { if m.getPlatform != nil { return m.getPlatform(ctx, id) } - return db.Platform{}, m.err + return platform.Platform{}, m.err } -func (m *mockPlatformQuerier) GetPlatforms(ctx context.Context) ([]db.Platform, error) { +func (m *mockPlatformQuerier) GetPlatforms(ctx context.Context) ([]platform.Platform, error) { if m.getPlatforms != nil { return m.getPlatforms(ctx) } return nil, m.err } -func (m *mockPlatformQuerier) UpdatePlatform(ctx context.Context, arg db.UpdatePlatformParams) (int32, error) { +func (m *mockPlatformQuerier) UpdatePlatform(ctx context.Context, arg platform.UpdatePlatformParams) (int32, error) { if m.updatePlatform != nil { return m.updatePlatform(ctx, arg) } @@ -129,12 +129,12 @@ func TestGetPlatforms(t *testing.T) { tests := []struct { name string dbErr error - platforms []db.Platform + platforms []platform.Platform expectedStatus int }{ { name: "Success", - platforms: []db.Platform{ + platforms: []platform.Platform{ {ID: 1, Name: "Platform 1", Description: pgtype.Text{String: "Desc 1", Valid: true}}, {ID: 2, Name: "Platform 2", Description: pgtype.Text{String: "Desc 2", Valid: true}}, }, @@ -143,7 +143,7 @@ func TestGetPlatforms(t *testing.T) { }, { name: "Empty Success", - platforms: []db.Platform{}, + platforms: []platform.Platform{}, dbErr: nil, expectedStatus: http.StatusOK, }, @@ -159,7 +159,7 @@ func TestGetPlatforms(t *testing.T) { t.Run(tt.name, func(t *testing.T) { mDB := &mockPlatformQuerier{ err: tt.dbErr, - getPlatforms: func(ctx context.Context) ([]db.Platform, error) { + getPlatforms: func(ctx context.Context) ([]platform.Platform, error) { return tt.platforms, tt.dbErr }, } @@ -175,7 +175,7 @@ func TestGetPlatforms(t *testing.T) { } if tt.expectedStatus == http.StatusOK { - var got []db.Platform + var got []platform.Platform err := json.Unmarshal(rr.Body.Bytes(), &got) if err != nil { t.Fatalf("failed to unmarshal response: %v", err) @@ -192,56 +192,56 @@ func TestGetPlatform(t *testing.T) { tests := []struct { name string id string - dbPlatform db.Platform + dbPlatform platform.Platform dbErr error expectedStatus int }{ { name: "Success", id: "1", - dbPlatform: db.Platform{ID: 1, Name: "Platform 1"}, + dbPlatform: platform.Platform{ID: 1, Name: "Platform 1"}, dbErr: nil, expectedStatus: http.StatusOK, }, { name: "Invalid ID", id: "abc", - dbPlatform: db.Platform{}, + dbPlatform: platform.Platform{}, dbErr: nil, expectedStatus: http.StatusBadRequest, }, { name: "Invalid ID (Zero)", id: "0", - dbPlatform: db.Platform{}, + dbPlatform: platform.Platform{}, dbErr: nil, expectedStatus: http.StatusBadRequest, }, { name: "Invalid ID (Negative)", id: "-1", - dbPlatform: db.Platform{}, + dbPlatform: platform.Platform{}, dbErr: nil, expectedStatus: http.StatusBadRequest, }, { name: "Invalid ID (Overflow)", id: "2147483648", - dbPlatform: db.Platform{}, + dbPlatform: platform.Platform{}, dbErr: nil, expectedStatus: http.StatusBadRequest, }, { name: "Not Found", id: "999", - dbPlatform: db.Platform{}, + dbPlatform: platform.Platform{}, dbErr: pgx.ErrNoRows, expectedStatus: http.StatusNotFound, }, { name: "DB Error", id: "1", - dbPlatform: db.Platform{}, + dbPlatform: platform.Platform{}, dbErr: errors.New("db error"), expectedStatus: http.StatusInternalServerError, }, @@ -251,7 +251,7 @@ func TestGetPlatform(t *testing.T) { t.Run(tt.name, func(t *testing.T) { mDB := &mockPlatformQuerier{ err: tt.dbErr, - getPlatform: func(ctx context.Context, id int32) (db.Platform, error) { + getPlatform: func(ctx context.Context, id int32) (platform.Platform, error) { return tt.dbPlatform, tt.dbErr }, } @@ -268,7 +268,7 @@ func TestGetPlatform(t *testing.T) { } if tt.expectedStatus == http.StatusOK { - var got db.Platform + var got platform.Platform err := json.Unmarshal(rr.Body.Bytes(), &got) if err != nil { t.Fatalf("failed to unmarshal response: %v", err) @@ -369,7 +369,7 @@ func TestUpdatePlatform(t *testing.T) { { name: "Success", pathID: "1", - requestBody: db.Platform{ + requestBody: platform.Platform{ ID: 1, Name: "Updated Platform", Description: pgtype.Text{String: "Updated Description", Valid: true}, @@ -380,7 +380,7 @@ func TestUpdatePlatform(t *testing.T) { { name: "Invalid Path ID", pathID: "abc", - requestBody: db.Platform{ + requestBody: platform.Platform{ ID: 1, Name: "Updated Platform", }, @@ -390,7 +390,7 @@ func TestUpdatePlatform(t *testing.T) { { name: "Invalid Path ID (Zero)", pathID: "0", - requestBody: db.Platform{ + requestBody: platform.Platform{ ID: 0, Name: "Updated Platform", }, @@ -400,7 +400,7 @@ func TestUpdatePlatform(t *testing.T) { { name: "Invalid Path ID (Negative)", pathID: "-1", - requestBody: db.Platform{ + requestBody: platform.Platform{ ID: -1, Name: "Updated Platform", }, @@ -410,7 +410,7 @@ func TestUpdatePlatform(t *testing.T) { { name: "Invalid Path ID (Overflow)", pathID: "2147483648", - requestBody: db.Platform{ + requestBody: platform.Platform{ ID: 1, Name: "Updated Platform", }, @@ -420,7 +420,7 @@ func TestUpdatePlatform(t *testing.T) { { name: "ID Mismatch", pathID: "2", - requestBody: db.Platform{ + requestBody: platform.Platform{ ID: 1, Name: "Updated Platform", }, @@ -430,7 +430,7 @@ func TestUpdatePlatform(t *testing.T) { { name: "Missing Name", pathID: "1", - requestBody: db.Platform{ + requestBody: platform.Platform{ ID: 1, Name: "", }, @@ -447,7 +447,7 @@ func TestUpdatePlatform(t *testing.T) { { name: "Platform Not Found", pathID: "999", - requestBody: db.Platform{ + requestBody: platform.Platform{ ID: 999, Name: "Non-existent", }, @@ -457,7 +457,7 @@ func TestUpdatePlatform(t *testing.T) { { name: "DB Error", pathID: "1", - requestBody: db.Platform{ + requestBody: platform.Platform{ ID: 1, Name: "Test Platform", }, @@ -470,9 +470,9 @@ func TestUpdatePlatform(t *testing.T) { t.Run(tt.name, func(t *testing.T) { mDB := &mockPlatformQuerier{ err: tt.dbErr, - updatePlatform: func(ctx context.Context, arg db.UpdatePlatformParams) (int32, error) { + updatePlatform: func(ctx context.Context, arg platform.UpdatePlatformParams) (int32, error) { if tt.name == "Success" { - expectedBody := tt.requestBody.(db.Platform) + expectedBody := tt.requestBody.(platform.Platform) if arg.ID != expectedBody.ID { t.Errorf("expected ID %d, got %d", expectedBody.ID, arg.ID) } diff --git a/api/platform/update.go b/api/platform/update.go index e36545a..f4f9272 100644 --- a/api/platform/update.go +++ b/api/platform/update.go @@ -6,7 +6,7 @@ import ( "errors" "net/http" "products/internal" - "products/internal/db" + db "products/internal/db/platform" "time" "github.com/jackc/pgx/v5" diff --git a/api/product/product.go b/api/product/product.go new file mode 100644 index 0000000..e987bb7 --- /dev/null +++ b/api/product/product.go @@ -0,0 +1,7 @@ +package productHandler + +import "products/internal/db/product" + +type ProductHandler struct { + queries product.Querier +} diff --git a/internal/db/platform/db.go b/internal/db/platform/db.go new file mode 100644 index 0000000..855ef6e --- /dev/null +++ b/internal/db/platform/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package platform + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/db/platform/models.go b/internal/db/platform/models.go new file mode 100644 index 0000000..9fa4abc --- /dev/null +++ b/internal/db/platform/models.go @@ -0,0 +1,42 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package platform + +import ( + "github.com/jackc/pgx/v5/pgtype" +) + +type Flow struct { + ID int32 + ProductID int32 + Name string + Description pgtype.Text + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz +} + +type FlowStep struct { + ID int32 + FlowID int32 + Current pgtype.UUID + Next pgtype.UUID +} + +type Platform struct { + ID int32 + Name string + Description pgtype.Text + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz +} + +type Product struct { + ID int32 + PlatformID int32 + Name string + Description pgtype.Text + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz +} diff --git a/internal/db/platforms.sql.go b/internal/db/platform/platforms.sql.go similarity index 99% rename from internal/db/platforms.sql.go rename to internal/db/platform/platforms.sql.go index 7725825..a0ce04d 100644 --- a/internal/db/platforms.sql.go +++ b/internal/db/platform/platforms.sql.go @@ -3,7 +3,7 @@ // sqlc v1.30.0 // source: platforms.sql -package db +package platform import ( "context" diff --git a/internal/db/querier.go b/internal/db/platform/querier.go similarity index 96% rename from internal/db/querier.go rename to internal/db/platform/querier.go index ea78ddd..0f6d185 100644 --- a/internal/db/querier.go +++ b/internal/db/platform/querier.go @@ -2,7 +2,7 @@ // versions: // sqlc v1.30.0 -package db +package platform import ( "context" diff --git a/internal/db/db.go b/internal/db/product/db.go similarity index 97% rename from internal/db/db.go rename to internal/db/product/db.go index 9d485b5..e28f192 100644 --- a/internal/db/db.go +++ b/internal/db/product/db.go @@ -2,7 +2,7 @@ // versions: // sqlc v1.30.0 -package db +package product import ( "context" diff --git a/internal/db/models.go b/internal/db/product/models.go similarity index 97% rename from internal/db/models.go rename to internal/db/product/models.go index 8432cc8..3519f1d 100644 --- a/internal/db/models.go +++ b/internal/db/product/models.go @@ -2,7 +2,7 @@ // versions: // sqlc v1.30.0 -package db +package product import ( "github.com/jackc/pgx/v5/pgtype" diff --git a/internal/db/product/products.sql.go b/internal/db/product/products.sql.go new file mode 100644 index 0000000..6a0dda0 --- /dev/null +++ b/internal/db/product/products.sql.go @@ -0,0 +1,114 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: products.sql + +package product + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const createProduct = `-- name: CreateProduct :exec +INSERT INTO products (platform_id, name, description, created_at, updated_at) VALUES ($1, $2, $3, $4, $5) +` + +type CreateProductParams struct { + PlatformID int32 + Name string + Description pgtype.Text + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz +} + +func (q *Queries) CreateProduct(ctx context.Context, arg CreateProductParams) error { + _, err := q.db.Exec(ctx, createProduct, + arg.PlatformID, + arg.Name, + arg.Description, + arg.CreatedAt, + arg.UpdatedAt, + ) + return err +} + +const deleteProduct = `-- name: DeleteProduct :exec +DELETE FROM products WHERE id = $1 RETURNING id +` + +func (q *Queries) DeleteProduct(ctx context.Context, id int32) error { + _, err := q.db.Exec(ctx, deleteProduct, id) + return err +} + +const getProductById = `-- name: GetProductById :one +SELECT id, platform_id, name, description, created_at, updated_at FROM products WHERE id = $1 +` + +func (q *Queries) GetProductById(ctx context.Context, id int32) (Product, error) { + row := q.db.QueryRow(ctx, getProductById, id) + var i Product + err := row.Scan( + &i.ID, + &i.PlatformID, + &i.Name, + &i.Description, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const getProductsByPlatform = `-- name: GetProductsByPlatform :many +SELECT id, platform_id, name, description, created_at, updated_at FROM products WHERE platform_id = $1 +` + +func (q *Queries) GetProductsByPlatform(ctx context.Context, platformID int32) ([]Product, error) { + rows, err := q.db.Query(ctx, getProductsByPlatform, platformID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Product + for rows.Next() { + var i Product + if err := rows.Scan( + &i.ID, + &i.PlatformID, + &i.Name, + &i.Description, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const updateProduct = `-- name: UpdateProduct :exec +UPDATE products SET name = $1, description = $2, updated_at = $3 WHERE id = $4 RETURNING id +` + +type UpdateProductParams struct { + Name string + Description pgtype.Text + UpdatedAt pgtype.Timestamptz + ID int32 +} + +func (q *Queries) UpdateProduct(ctx context.Context, arg UpdateProductParams) error { + _, err := q.db.Exec(ctx, updateProduct, + arg.Name, + arg.Description, + arg.UpdatedAt, + arg.ID, + ) + return err +} diff --git a/internal/db/product/querier.go b/internal/db/product/querier.go new file mode 100644 index 0000000..55a8fc3 --- /dev/null +++ b/internal/db/product/querier.go @@ -0,0 +1,19 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package product + +import ( + "context" +) + +type Querier interface { + CreateProduct(ctx context.Context, arg CreateProductParams) error + DeleteProduct(ctx context.Context, id int32) error + GetProductById(ctx context.Context, id int32) (Product, error) + GetProductsByPlatform(ctx context.Context, platformID int32) ([]Product, error) + UpdateProduct(ctx context.Context, arg UpdateProductParams) error +} + +var _ Querier = (*Queries)(nil) diff --git a/internal/db/store.go b/internal/db/store.go new file mode 100644 index 0000000..bf21c90 --- /dev/null +++ b/internal/db/store.go @@ -0,0 +1,29 @@ +package db + +import ( + "context" + "products/internal/db/platform" + "products/internal/db/product" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type Store struct { + Platform *platform.Queries + Product *product.Queries +} + +func New(db DBTX) *Store { + return &Store{ + Platform: platform.New(db), + Product: product.New(db), + } +} + +// This interface is repeated in every db package, it is auto-generated by sqlc +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} diff --git a/queries/products.sql b/queries/products.sql new file mode 100644 index 0000000..0e2f15b --- /dev/null +++ b/queries/products.sql @@ -0,0 +1,14 @@ +-- name: GetProductsByPlatform :many +SELECT id, platform_id, name, description, created_at, updated_at FROM products WHERE platform_id = @platform_id; + +-- name: GetProductById :one +SELECT id, platform_id, name, description, created_at, updated_at FROM products WHERE id = @id; + +-- name: CreateProduct :exec +INSERT INTO products (platform_id, name, description, created_at, updated_at) VALUES (@platform_id, @name, @description, @created_at, @updated_at); + +-- name: UpdateProduct :exec +UPDATE products SET name = @name, description = @description, updated_at = @updated_at WHERE id = @id RETURNING id; + +-- name: DeleteProduct :exec +DELETE FROM products WHERE id = @id RETURNING id; \ No newline at end of file diff --git a/router/router.go b/router/router.go index 42c7ef2..d1463b7 100644 --- a/router/router.go +++ b/router/router.go @@ -7,6 +7,7 @@ import ( systemHandler "products/api/system" "products/internal" "products/internal/db" + platformDb "products/internal/db/platform" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" @@ -17,7 +18,7 @@ func SetupRouter(dbConn db.DBTX) http.Handler { slog.Debug("Setting up router") router := chi.NewRouter() - queries := db.New(dbConn) + store := db.New(dbConn) router.Use(internal.StructuredLogger(slog.Default())) router.Use(middleware.Recoverer) @@ -31,7 +32,7 @@ func SetupRouter(dbConn db.DBTX) http.Handler { })) registerSystemCallHandler(router) - registerPlatformCallHandler(queries, router) + registerPlatformCallHandler(store.Platform, router) slog.Debug("Router setup complete") return router } @@ -41,7 +42,7 @@ func registerSystemCallHandler(r *chi.Mux) { r.Get("/api/time", h.GetTime) } -func registerPlatformCallHandler(q db.Querier, r *chi.Mux) { +func registerPlatformCallHandler(q platformDb.Querier, r *chi.Mux) { handler := platformHandler.NewPlatformHandler(q) r.Route("/api/platforms", func(u chi.Router) { u.Post("/", handler.CreatePlatform) diff --git a/sqlc.yml b/sqlc.yml index 017b0aa..ad67b35 100644 --- a/sqlc.yml +++ b/sqlc.yml @@ -1,14 +1,21 @@ version: "2" sql: - engine: "postgresql" - queries: "queries/" - schema: "schema.sql" # still required but can be a dummy/empty file -# database: -# uri: "postgresql://postgres:postgres@localhost:5432/products" + queries: "queries/platforms.sql" + schema: "schema.sql" gen: go: - package: "db" - out: "internal/db" + out: "internal/db/platform" + sql_package: "pgx/v5" + output_models_file_name: "models.go" + emit_interface: true + + - engine: "postgresql" + queries: "queries/products.sql" + schema: "schema.sql" + gen: + go: + out: "internal/db/product" sql_package: "pgx/v5" output_models_file_name: "models.go" emit_interface: true \ No newline at end of file