From 4df3949d308cdb6bb2404aa75c5fba608fe932e4 Mon Sep 17 00:00:00 2001 From: plutov Date: Fri, 7 Mar 2025 21:39:22 +0100 Subject: [PATCH 1/2] sqlc part 1 Signed-off-by: plutov --- api/cmd/test/main.go | 34 +++ api/go.mod | 4 +- api/go.sum | 12 +- .../postgres/000002_webhooks.up.sql | 18 +- api/pkg/db/db.go | 32 +++ api/pkg/db/models.go | 227 ++++++++++++++++++ api/pkg/db/queries/surveys.sql | 5 + api/pkg/db/surveys.sql.go | 52 ++++ api/sqlc.yaml | 10 + 9 files changed, 381 insertions(+), 13 deletions(-) create mode 100644 api/cmd/test/main.go create mode 100644 api/pkg/db/db.go create mode 100644 api/pkg/db/models.go create mode 100644 api/pkg/db/queries/surveys.sql create mode 100644 api/pkg/db/surveys.sql.go create mode 100644 api/sqlc.yaml diff --git a/api/cmd/test/main.go b/api/cmd/test/main.go new file mode 100644 index 00000000..a92360d4 --- /dev/null +++ b/api/cmd/test/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "context" + "fmt" + "log" + + "github.com/jackc/pgx/v5/pgxpool" + "github.com/plutov/formulosity/api/pkg/db" +) + +func main() { + dsn := "postgres://test:test@localhost:5432/surveys" + + poolConf, err := pgxpool.ParseConfig(dsn) + if err != nil { + log.Fatalf("unable to parse dsn: %v", err) + } + + connPool, err := pgxpool.NewWithConfig(context.Background(), poolConf) + if err != nil { + log.Fatalf("unable to create db pool: %v", err) + } + + queries := db.New(connPool) + survey, err := queries.CreateSurvey(context.Background(), db.CreateSurveyParams{ + Name: "test", + }) + if err != nil { + log.Fatalf("unable to create db pool: %v", err) + } + + fmt.Printf("survey created, id: %s", survey.Uuid) +} diff --git a/api/go.mod b/api/go.mod index 56228fa6..4ca2a126 100644 --- a/api/go.mod +++ b/api/go.mod @@ -7,6 +7,7 @@ require ( github.com/go-ozzo/ozzo-validation/v4 v4.3.0 github.com/golang-migrate/migrate/v4 v4.17.1 github.com/google/uuid v1.6.0 + github.com/jackc/pgx/v5 v5.5.4 github.com/labstack/echo/v4 v4.12.0 github.com/matoous/go-nanoid/v2 v2.0.0 github.com/mattn/go-sqlite3 v1.14.22 @@ -23,6 +24,8 @@ require ( github.com/gorilla/css v1.0.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/kr/pretty v0.3.1 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/lib/pq v1.10.9 // indirect @@ -40,5 +43,4 @@ require ( golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.25.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/api/go.sum b/api/go.sum index dc27f7ee..85cc36f9 100644 --- a/api/go.sum +++ b/api/go.sum @@ -40,11 +40,16 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= +github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -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/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= @@ -87,6 +92,7 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= diff --git a/api/migrations/postgres/000002_webhooks.up.sql b/api/migrations/postgres/000002_webhooks.up.sql index 54a33b86..34864941 100644 --- a/api/migrations/postgres/000002_webhooks.up.sql +++ b/api/migrations/postgres/000002_webhooks.up.sql @@ -1,9 +1,9 @@ -CREATE TABLE - surveys_webhook_responses ( - id serial NOT NULL PRIMARY KEY, - created_at timestamp without time zone default (now () at time zone 'utc'), - session_id integer NOT NULL, - response_status integer NOT NULL, - response TEXT, - CONSTRAINT fk_surveys_webhooks1 FOREIGN KEY (session_id) REFERENCES surveys_sessions (id) ON DELETE CASCADE, - ); \ No newline at end of file +CREATE TABLE surveys_webhook_responses ( + id serial NOT NULL PRIMARY KEY, + created_at timestamp without time zone default (now () at time zone 'utc'), + session_id integer NOT NULL, + response_status integer NOT NULL, + response TEXT, + CONSTRAINT fk_surveys_webhooks1 FOREIGN KEY (session_id) REFERENCES surveys_sessions (id) ON DELETE CASCADE +); + diff --git a/api/pkg/db/db.go b/api/pkg/db/db.go new file mode 100644 index 00000000..eeee39e4 --- /dev/null +++ b/api/pkg/db/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 + +package db + +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/api/pkg/db/models.go b/api/pkg/db/models.go new file mode 100644 index 00000000..83198f9a --- /dev/null +++ b/api/pkg/db/models.go @@ -0,0 +1,227 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 + +package db + +import ( + "database/sql/driver" + "fmt" + + "github.com/jackc/pgx/v5/pgtype" +) + +type CommonStatuses string + +const ( + CommonStatusesActive CommonStatuses = "active" + CommonStatusesInactive CommonStatuses = "inactive" +) + +func (e *CommonStatuses) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = CommonStatuses(s) + case string: + *e = CommonStatuses(s) + default: + return fmt.Errorf("unsupported scan type for CommonStatuses: %T", src) + } + return nil +} + +type NullCommonStatuses struct { + CommonStatuses CommonStatuses + Valid bool // Valid is true if CommonStatuses is not NULL +} + +// Scan implements the Scanner interface. +func (ns *NullCommonStatuses) Scan(value interface{}) error { + if value == nil { + ns.CommonStatuses, ns.Valid = "", false + return nil + } + ns.Valid = true + return ns.CommonStatuses.Scan(value) +} + +// Value implements the driver Valuer interface. +func (ns NullCommonStatuses) Value() (driver.Value, error) { + if !ns.Valid { + return nil, nil + } + return string(ns.CommonStatuses), nil +} + +type SurveyDeliveryStatuses string + +const ( + SurveyDeliveryStatusesLaunched SurveyDeliveryStatuses = "launched" + SurveyDeliveryStatusesStopped SurveyDeliveryStatuses = "stopped" +) + +func (e *SurveyDeliveryStatuses) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = SurveyDeliveryStatuses(s) + case string: + *e = SurveyDeliveryStatuses(s) + default: + return fmt.Errorf("unsupported scan type for SurveyDeliveryStatuses: %T", src) + } + return nil +} + +type NullSurveyDeliveryStatuses struct { + SurveyDeliveryStatuses SurveyDeliveryStatuses + Valid bool // Valid is true if SurveyDeliveryStatuses is not NULL +} + +// Scan implements the Scanner interface. +func (ns *NullSurveyDeliveryStatuses) Scan(value interface{}) error { + if value == nil { + ns.SurveyDeliveryStatuses, ns.Valid = "", false + return nil + } + ns.Valid = true + return ns.SurveyDeliveryStatuses.Scan(value) +} + +// Value implements the driver Valuer interface. +func (ns NullSurveyDeliveryStatuses) Value() (driver.Value, error) { + if !ns.Valid { + return nil, nil + } + return string(ns.SurveyDeliveryStatuses), nil +} + +type SurveyParseStatuses string + +const ( + SurveyParseStatusesSuccess SurveyParseStatuses = "success" + SurveyParseStatusesError SurveyParseStatuses = "error" + SurveyParseStatusesDeleted SurveyParseStatuses = "deleted" +) + +func (e *SurveyParseStatuses) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = SurveyParseStatuses(s) + case string: + *e = SurveyParseStatuses(s) + default: + return fmt.Errorf("unsupported scan type for SurveyParseStatuses: %T", src) + } + return nil +} + +type NullSurveyParseStatuses struct { + SurveyParseStatuses SurveyParseStatuses + Valid bool // Valid is true if SurveyParseStatuses is not NULL +} + +// Scan implements the Scanner interface. +func (ns *NullSurveyParseStatuses) Scan(value interface{}) error { + if value == nil { + ns.SurveyParseStatuses, ns.Valid = "", false + return nil + } + ns.Valid = true + return ns.SurveyParseStatuses.Scan(value) +} + +// Value implements the driver Valuer interface. +func (ns NullSurveyParseStatuses) Value() (driver.Value, error) { + if !ns.Valid { + return nil, nil + } + return string(ns.SurveyParseStatuses), nil +} + +type SurveysSessionsStatus string + +const ( + SurveysSessionsStatusInProgress SurveysSessionsStatus = "in_progress" + SurveysSessionsStatusCompleted SurveysSessionsStatus = "completed" +) + +func (e *SurveysSessionsStatus) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = SurveysSessionsStatus(s) + case string: + *e = SurveysSessionsStatus(s) + default: + return fmt.Errorf("unsupported scan type for SurveysSessionsStatus: %T", src) + } + return nil +} + +type NullSurveysSessionsStatus struct { + SurveysSessionsStatus SurveysSessionsStatus + Valid bool // Valid is true if SurveysSessionsStatus is not NULL +} + +// Scan implements the Scanner interface. +func (ns *NullSurveysSessionsStatus) Scan(value interface{}) error { + if value == nil { + ns.SurveysSessionsStatus, ns.Valid = "", false + return nil + } + ns.Valid = true + return ns.SurveysSessionsStatus.Scan(value) +} + +// Value implements the driver Valuer interface. +func (ns NullSurveysSessionsStatus) Value() (driver.Value, error) { + if !ns.Valid { + return nil, nil + } + return string(ns.SurveysSessionsStatus), nil +} + +type Survey struct { + ID int32 + Uuid pgtype.UUID + CreatedAt pgtype.Timestamp + ParseStatus NullSurveyParseStatuses + DeliveryStatus NullSurveyDeliveryStatuses + ErrorLog pgtype.Text + Name string + UrlSlug string + Config []byte +} + +type SurveysAnswer struct { + ID int32 + Uuid pgtype.UUID + CreatedAt pgtype.Timestamp + SessionID int32 + QuestionID int32 + Answer []byte +} + +type SurveysQuestion struct { + ID int32 + Uuid pgtype.UUID + SurveyID int32 + QuestionID string +} + +type SurveysSession struct { + ID int32 + Uuid pgtype.UUID + CreatedAt pgtype.Timestamp + CompletedAt pgtype.Timestamp + Status NullSurveysSessionsStatus + SurveyID int32 + IpAddr pgtype.Text +} + +type SurveysWebhookResponse struct { + ID int32 + CreatedAt pgtype.Timestamp + SessionID int32 + ResponseStatus int32 + Response pgtype.Text +} diff --git a/api/pkg/db/queries/surveys.sql b/api/pkg/db/queries/surveys.sql new file mode 100644 index 00000000..a998c542 --- /dev/null +++ b/api/pkg/db/queries/surveys.sql @@ -0,0 +1,5 @@ +-- name: CreateSurvey :one +INSERT INTO surveys +(parse_status, delivery_status, error_log, name, config, url_slug) +VALUES ($1, $2, $3, $4, $5, $6) +RETURNING *; diff --git a/api/pkg/db/surveys.sql.go b/api/pkg/db/surveys.sql.go new file mode 100644 index 00000000..f0d98190 --- /dev/null +++ b/api/pkg/db/surveys.sql.go @@ -0,0 +1,52 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 +// source: surveys.sql + +package db + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const createSurvey = `-- name: CreateSurvey :one +INSERT INTO surveys +(parse_status, delivery_status, error_log, name, config, url_slug) +VALUES ($1, $2, $3, $4, $5, $6) +RETURNING id, uuid, created_at, parse_status, delivery_status, error_log, name, url_slug, config +` + +type CreateSurveyParams struct { + ParseStatus NullSurveyParseStatuses + DeliveryStatus NullSurveyDeliveryStatuses + ErrorLog pgtype.Text + Name string + Config []byte + UrlSlug string +} + +func (q *Queries) CreateSurvey(ctx context.Context, arg CreateSurveyParams) (Survey, error) { + row := q.db.QueryRow(ctx, createSurvey, + arg.ParseStatus, + arg.DeliveryStatus, + arg.ErrorLog, + arg.Name, + arg.Config, + arg.UrlSlug, + ) + var i Survey + err := row.Scan( + &i.ID, + &i.Uuid, + &i.CreatedAt, + &i.ParseStatus, + &i.DeliveryStatus, + &i.ErrorLog, + &i.Name, + &i.UrlSlug, + &i.Config, + ) + return i, err +} diff --git a/api/sqlc.yaml b/api/sqlc.yaml new file mode 100644 index 00000000..2f2228f0 --- /dev/null +++ b/api/sqlc.yaml @@ -0,0 +1,10 @@ +version: "2" +sql: + - engine: "postgresql" + queries: "./pkg/db/queries/" + schema: "./migrations/postgres/" + gen: + go: + package: "db" + out: "./pkg/db" + sql_package: "pgx/v5" From aea59bddaeba92f0901c6b6c2bcdd5f95efb2e1b Mon Sep 17 00:00:00 2001 From: plutov Date: Sat, 8 Mar 2025 19:01:45 +0100 Subject: [PATCH 2/2] sqlc part 2 Signed-off-by: plutov --- api/cmd/test/main.go | 16 +++++++++++--- api/pkg/db/queries/surveys.sql | 5 +++++ api/pkg/db/surveys.sql.go | 36 ++++++++++++++++++++++++++++++++ compose.yaml | 38 +++++++++++++++------------------- 4 files changed, 71 insertions(+), 24 deletions(-) diff --git a/api/cmd/test/main.go b/api/cmd/test/main.go index a92360d4..a97d5981 100644 --- a/api/cmd/test/main.go +++ b/api/cmd/test/main.go @@ -4,13 +4,14 @@ import ( "context" "fmt" "log" + "time" "github.com/jackc/pgx/v5/pgxpool" "github.com/plutov/formulosity/api/pkg/db" ) func main() { - dsn := "postgres://test:test@localhost:5432/surveys" + dsn := "postgres://postgres:postgres@localhost:5432/formulosity" poolConf, err := pgxpool.ParseConfig(dsn) if err != nil { @@ -24,11 +25,20 @@ func main() { queries := db.New(connPool) survey, err := queries.CreateSurvey(context.Background(), db.CreateSurveyParams{ - Name: "test", + Name: string(time.Now().Unix()), + UrlSlug: string(time.Now().Unix()), }) if err != nil { - log.Fatalf("unable to create db pool: %v", err) + log.Fatalf("unable to create survey: %v", err) } fmt.Printf("survey created, id: %s", survey.Uuid) + + surveys, err := queries.GetSurveys(context.Background()) + if err != nil { + log.Fatalf("unable to get surveys: %v", err) + } + + fmt.Printf("surveys: %v", surveys) + } diff --git a/api/pkg/db/queries/surveys.sql b/api/pkg/db/queries/surveys.sql index a998c542..50502726 100644 --- a/api/pkg/db/queries/surveys.sql +++ b/api/pkg/db/queries/surveys.sql @@ -3,3 +3,8 @@ INSERT INTO surveys (parse_status, delivery_status, error_log, name, config, url_slug) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *; + +-- name: GetSurveys :many +SELECT * +FROM surveys +ORDER BY id DESC; diff --git a/api/pkg/db/surveys.sql.go b/api/pkg/db/surveys.sql.go index f0d98190..399e642a 100644 --- a/api/pkg/db/surveys.sql.go +++ b/api/pkg/db/surveys.sql.go @@ -50,3 +50,39 @@ func (q *Queries) CreateSurvey(ctx context.Context, arg CreateSurveyParams) (Sur ) return i, err } + +const getSurveys = `-- name: GetSurveys :many +SELECT id, uuid, created_at, parse_status, delivery_status, error_log, name, url_slug, config +FROM surveys +ORDER BY id DESC +` + +func (q *Queries) GetSurveys(ctx context.Context) ([]Survey, error) { + rows, err := q.db.Query(ctx, getSurveys) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Survey + for rows.Next() { + var i Survey + if err := rows.Scan( + &i.ID, + &i.Uuid, + &i.CreatedAt, + &i.ParseStatus, + &i.DeliveryStatus, + &i.ErrorLog, + &i.Name, + &i.UrlSlug, + &i.Config, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/compose.yaml b/compose.yaml index 1543997a..993492ca 100644 --- a/compose.yaml +++ b/compose.yaml @@ -5,19 +5,17 @@ services: context: ./api ports: - "9900:8080" - # depends_on: - # - postgres + depends_on: + - postgres environment: - - DATABASE_TYPE=sqlite # postgres|sqlite - # - DATABASE_URL=postgres://postgres:postgres@postgres:5432/formulosity?sslmode=disable - - DATABASE_URL=/root/sqlite3/formulosity.db + - DATABASE_TYPE=postgres + - DATABASE_URL=postgres://postgres:postgres@postgres:5432/formulosity?sslmode=disable - SURVEYS_DIR=/root/surveys - UPLOADS_DIR=/root/uploads volumes: - ./api/surveys:/root/surveys - ./api/sqlite3:/root/sqlite3 - ./api/uploads:/root/uploads - ui: restart: always build: @@ -31,21 +29,19 @@ services: - HTTP_BASIC_AUTH=user:pass depends_on: - api - - # postgres: - # image: postgres:16.0-alpine - # restart: always - # environment: - # - POSTGRES_USER=postgres - # - POSTGRES_PASSWORD=postgres - # - POSTGRES_DB=formulosity - # ports: - # - "5432:5432" - # volumes: - # - ./api/postgres-data:/var/lib/postgresql/data - # logging: - # driver: none - + postgres: + image: postgres:16.0-alpine + restart: always + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + - POSTGRES_DB=formulosity + ports: + - "5432:5432" + volumes: + - ./api/postgres-data:/var/lib/postgresql/data + logging: + driver: none volumes: dbvolume: driver: local