From 16899a281bbb1b6589f3ff316d561ba8e9064db2 Mon Sep 17 00:00:00 2001 From: Rafael Piovesan <96007155+rafael-piovesan@users.noreply.github.com> Date: Wed, 19 Jan 2022 11:06:16 -0300 Subject: [PATCH] feat(sqlc): add sqlc datastore (#2) * refactor: add bun datastore package * feat(sqlc): add sqlc dep and config * feat(sqlc): add sqlc statements * feat(sqlc): add sqlc datastore * feat(sqlc): add sqlc toggle env var --- Makefile | 4 + cmd/api/main.go | 27 ++- config.go | 10 +- .../bun}/audit_record.go | 2 +- .../bun}/audit_record_test.go | 12 +- .../bun}/idempotency_key.go | 2 +- .../bun}/idempotency_key_test.go | 12 +- {adapters/datastore => datastore/bun}/ride.go | 2 +- .../datastore => datastore/bun}/ride_test.go | 12 +- datastore/bun/sqlstore.go | 75 +++++++ .../bun}/sqlstore_test.go | 12 +- .../datastore => datastore/bun}/staged_job.go | 2 +- .../bun}/staged_job_test.go | 12 +- {adapters/datastore => datastore/bun}/user.go | 2 +- .../datastore => datastore/bun}/user_test.go | 12 +- datastore/sqlc/audit_record.go | 43 ++++ datastore/sqlc/audit_record.sql.go | 58 ++++++ datastore/sqlc/audit_record_test.go | 66 ++++++ datastore/sqlc/db.go | 29 +++ datastore/sqlc/idempotency_key.go | 159 +++++++++++++++ datastore/sqlc/idempotency_key.sql.go | 154 ++++++++++++++ datastore/sqlc/idempotency_key_test.go | 108 ++++++++++ datastore/sqlc/models.go | 61 ++++++ datastore/sqlc/ride.go | 99 +++++++++ datastore/sqlc/ride.sql.go | 135 +++++++++++++ datastore/sqlc/ride_test.go | 92 +++++++++ .../datastore => datastore/sqlc}/sqlstore.go | 27 ++- datastore/sqlc/sqlstore_test.go | 189 ++++++++++++++++++ datastore/sqlc/staged_job.go | 33 +++ datastore/sqlc/staged_job.sql.go | 30 +++ datastore/sqlc/staged_job_test.go | 47 +++++ datastore/sqlc/user.go | 33 +++ datastore/sqlc/user.sql.go | 32 +++ datastore/sqlc/user_test.go | 55 +++++ db/queries/audit_record.sql | 12 ++ db/queries/idempotency_key.sql | 30 +++ db/queries/ride.sql | 27 +++ db/queries/staged_job.sql | 7 + db/queries/user.sql | 7 + go.mod | 4 +- go.sum | 1 + sqlc.yaml | 18 ++ 42 files changed, 1669 insertions(+), 85 deletions(-) rename {adapters/datastore => datastore/bun}/audit_record.go (93%) rename {adapters/datastore => datastore/bun}/audit_record_test.go (85%) rename {adapters/datastore => datastore/bun}/idempotency_key.go (98%) rename {adapters/datastore => datastore/bun}/idempotency_key_test.go (90%) rename {adapters/datastore => datastore/bun}/ride.go (97%) rename {adapters/datastore => datastore/bun}/ride_test.go (87%) create mode 100644 datastore/bun/sqlstore.go rename {adapters/datastore => datastore/bun}/sqlstore_test.go (94%) rename {adapters/datastore => datastore/bun}/staged_job.go (93%) rename {adapters/datastore => datastore/bun}/staged_job_test.go (78%) rename {adapters/datastore => datastore/bun}/user.go (95%) rename {adapters/datastore => datastore/bun}/user_test.go (82%) create mode 100644 datastore/sqlc/audit_record.go create mode 100644 datastore/sqlc/audit_record.sql.go create mode 100644 datastore/sqlc/audit_record_test.go create mode 100644 datastore/sqlc/db.go create mode 100644 datastore/sqlc/idempotency_key.go create mode 100644 datastore/sqlc/idempotency_key.sql.go create mode 100644 datastore/sqlc/idempotency_key_test.go create mode 100644 datastore/sqlc/models.go create mode 100644 datastore/sqlc/ride.go create mode 100644 datastore/sqlc/ride.sql.go create mode 100644 datastore/sqlc/ride_test.go rename {adapters/datastore => datastore/sqlc}/sqlstore.go (74%) create mode 100644 datastore/sqlc/sqlstore_test.go create mode 100644 datastore/sqlc/staged_job.go create mode 100644 datastore/sqlc/staged_job.sql.go create mode 100644 datastore/sqlc/staged_job_test.go create mode 100644 datastore/sqlc/user.go create mode 100644 datastore/sqlc/user.sql.go create mode 100644 datastore/sqlc/user_test.go create mode 100644 db/queries/audit_record.sql create mode 100644 db/queries/idempotency_key.sql create mode 100644 db/queries/ride.sql create mode 100644 db/queries/staged_job.sql create mode 100644 db/queries/user.sql create mode 100644 sqlc.yaml diff --git a/Makefile b/Makefile index b8ed323..251707c 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ deps: ## Install dependencies go mod tidy go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest go install github.com/segmentio/golines@latest + go install github.com/kyleconroy/sqlc/cmd/sqlc@latest go install github.com/vektra/mockery/cmd/mockery migrate: ## Run db migrations (expects $DSN env var, so, run it with: 'DSN= make fixtures') @@ -22,6 +23,9 @@ lint: ## Run linter format: ## Format source code golines . -m 120 -w --ignore-generated +sqlc: ## Generate sqlc files + sqlc generate + mock: ## Generate interfaces mocks mockery -name Datastore mockery -name RideUseCase diff --git a/cmd/api/main.go b/cmd/api/main.go index ada58fd..46f9e63 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -1,16 +1,14 @@ package main import ( - "database/sql" "log" + "strings" rocketride "github.com/rafael-piovesan/go-rocket-ride" - "github.com/rafael-piovesan/go-rocket-ride/adapters/datastore" "github.com/rafael-piovesan/go-rocket-ride/api/http" + bunstore "github.com/rafael-piovesan/go-rocket-ride/datastore/bun" + sqlcstore "github.com/rafael-piovesan/go-rocket-ride/datastore/sqlc" "github.com/rafael-piovesan/go-rocket-ride/pkg/stripemock" - "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/pgdialect" - _ "github.com/uptrace/bun/driver/pgdriver" ) func main() { @@ -21,15 +19,24 @@ func main() { } // database connection - dsn := cfg.DBSource - sqldb, err := sql.Open("pg", dsn) + da := strings.ToLower(cfg.DatastoreAccess) + var store rocketride.Datastore + + switch da { + case "bun": + store, err = bunstore.NewStore(cfg.DBSource) + case "sqlc": + store, err = sqlcstore.NewStore(cfg.DBSource) + default: + log.Printf("invalid store type %v", cfg.DatastoreAccess) + store, err = bunstore.NewStore(cfg.DBSource) + } + + log.Printf("using [%v] datastore access", da) if err != nil { log.Fatalf("cannot open database: %v", err) } - db := bun.NewDB(sqldb, pgdialect.New()) - store := datastore.NewStore(db) - // Replace the original Stripe API Backend with its mock stripemock.Init() diff --git a/config.go b/config.go index 94d3e3a..5764425 100644 --- a/config.go +++ b/config.go @@ -17,10 +17,11 @@ const ( // Config stores all configuration of the application. // The values are read by viper from a config file or environment variable. type Config struct { - IdemKeyTimeout int `mapstructure:"IDEM_KEY_TIMEOUT" validate:"required"` - DBSource string `mapstructure:"DB_SOURCE" validate:"required"` - ServerAddress string `mapstructure:"SERVER_ADDRESS" validate:"required"` - StripeKey string `mapstructure:"STRIPE_KEY" validate:"required"` + IdemKeyTimeout int `mapstructure:"IDEM_KEY_TIMEOUT" validate:"required"` + DBSource string `mapstructure:"DB_SOURCE" validate:"required"` + ServerAddress string `mapstructure:"SERVER_ADDRESS" validate:"required"` + StripeKey string `mapstructure:"STRIPE_KEY" validate:"required"` + DatastoreAccess string `mapstructure:"DATASTORE_ACCESS" validate:"required,oneof=bun sqlc"` } // LoadConfig reads configuration from file or environment variables. @@ -38,6 +39,7 @@ func LoadConfig(cfgPath string) (config Config, err error) { // default config values viper.SetDefault("IDEM_KEY_TIMEOUT", 5) + viper.SetDefault("DATASTORE_ACCESS", "bun") // enable loading of env vars values // it'll override the values in the config file diff --git a/adapters/datastore/audit_record.go b/datastore/bun/audit_record.go similarity index 93% rename from adapters/datastore/audit_record.go rename to datastore/bun/audit_record.go index 70c9451..e1225e7 100644 --- a/adapters/datastore/audit_record.go +++ b/datastore/bun/audit_record.go @@ -1,4 +1,4 @@ -package datastore +package bun import ( "context" diff --git a/adapters/datastore/audit_record_test.go b/datastore/bun/audit_record_test.go similarity index 85% rename from adapters/datastore/audit_record_test.go rename to datastore/bun/audit_record_test.go index f0f316b..58f3205 100644 --- a/adapters/datastore/audit_record_test.go +++ b/datastore/bun/audit_record_test.go @@ -1,11 +1,10 @@ //go:build integration // +build integration -package datastore +package bun import ( "context" - "database/sql" "testing" "github.com/brianvoe/gofakeit/v6" @@ -17,9 +16,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tabbed/pqtype" - "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/pgdialect" - "github.com/uptrace/bun/driver/pgdriver" ) func TestAuditRecord(t *testing.T) { @@ -43,10 +39,8 @@ func TestAuditRecord(t *testing.T) { require.NoError(t, err) // conntect to database - sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn))) - db := bun.NewDB(sqldb, pgdialect.New()) - - store := NewStore(db) + store, err := NewStore(dsn) + require.NoError(t, err) t.Run("Create Audit Record", func(t *testing.T) { ip := pqtype.CIDR{} diff --git a/adapters/datastore/idempotency_key.go b/datastore/bun/idempotency_key.go similarity index 98% rename from adapters/datastore/idempotency_key.go rename to datastore/bun/idempotency_key.go index 0e5ce30..f15e689 100644 --- a/adapters/datastore/idempotency_key.go +++ b/datastore/bun/idempotency_key.go @@ -1,4 +1,4 @@ -package datastore +package bun import ( "context" diff --git a/adapters/datastore/idempotency_key_test.go b/datastore/bun/idempotency_key_test.go similarity index 90% rename from adapters/datastore/idempotency_key_test.go rename to datastore/bun/idempotency_key_test.go index 3bda925..a1335cb 100644 --- a/adapters/datastore/idempotency_key_test.go +++ b/datastore/bun/idempotency_key_test.go @@ -1,11 +1,10 @@ //go:build integration // +build integration -package datastore +package bun import ( "context" - "database/sql" "fmt" "testing" "time" @@ -18,9 +17,6 @@ import ( "github.com/rafael-piovesan/go-rocket-ride/pkg/testfixtures" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/pgdialect" - "github.com/uptrace/bun/driver/pgdriver" ) func TestIdempotencyKey(t *testing.T) { @@ -45,10 +41,8 @@ func TestIdempotencyKey(t *testing.T) { require.NoError(t, err) // conntect to database - sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn))) - db := bun.NewDB(sqldb, pgdialect.New()) - - store := NewStore(db) + store, err := NewStore(dsn) + require.NoError(t, err) // test entity now := time.Now() diff --git a/adapters/datastore/ride.go b/datastore/bun/ride.go similarity index 97% rename from adapters/datastore/ride.go rename to datastore/bun/ride.go index a44a57a..7358141 100644 --- a/adapters/datastore/ride.go +++ b/datastore/bun/ride.go @@ -1,4 +1,4 @@ -package datastore +package bun import ( "context" diff --git a/adapters/datastore/ride_test.go b/datastore/bun/ride_test.go similarity index 87% rename from adapters/datastore/ride_test.go rename to datastore/bun/ride_test.go index f6d4b51..bc2b285 100644 --- a/adapters/datastore/ride_test.go +++ b/datastore/bun/ride_test.go @@ -1,11 +1,10 @@ //go:build integration // +build integration -package datastore +package bun import ( "context" - "database/sql" "testing" "github.com/brianvoe/gofakeit/v6" @@ -15,9 +14,6 @@ import ( "github.com/rafael-piovesan/go-rocket-ride/pkg/testfixtures" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/pgdialect" - "github.com/uptrace/bun/driver/pgdriver" ) func TestRide(t *testing.T) { @@ -51,10 +47,8 @@ func TestRide(t *testing.T) { require.NoError(t, err) // conntect to database - sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn))) - db := bun.NewDB(sqldb, pgdialect.New()) - - store := NewStore(db) + store, err := NewStore(dsn) + require.NoError(t, err) // test entity ride := &entity.Ride{ diff --git a/datastore/bun/sqlstore.go b/datastore/bun/sqlstore.go new file mode 100644 index 0000000..1c15250 --- /dev/null +++ b/datastore/bun/sqlstore.go @@ -0,0 +1,75 @@ +package bun + +import ( + "context" + "database/sql" + "fmt" + "runtime" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/pgdialect" + + // loading bun's official Postgres driver. + _ "github.com/uptrace/bun/driver/pgdriver" + + rocketride "github.com/rafael-piovesan/go-rocket-ride" +) + +type sqlStore struct { + conn *bun.DB + db bun.IDB +} + +func NewStore(dsn string) (s rocketride.Datastore, err error) { + sqldb, err := sql.Open("pg", dsn) + if err != nil { + return nil, err + } + + db := bun.NewDB(sqldb, pgdialect.New()) + s = &sqlStore{ + conn: db, + db: db, + } + return +} + +func (s *sqlStore) Atomic(ctx context.Context, fn func(store rocketride.Datastore) error) (err error) { + tx, err := s.conn.BeginTx(ctx, &sql.TxOptions{}) + if err != nil { + return err + } + + defer func() { + if p := recover(); p != nil { + _ = tx.Rollback() + + switch e := p.(type) { + case runtime.Error: + panic(e) + case error: + err = fmt.Errorf("panic err: %v", p) + return + default: + panic(e) + } + } + if err != nil { + if rbErr := tx.Rollback(); rbErr != nil { + err = fmt.Errorf("tx err: %v, rb err: %v", err, rbErr) + } + } else { + err = tx.Commit() + } + }() + + // TODO: check if it works for nested transactions as well + newStore := &sqlStore{ + conn: s.conn, + db: tx, + } + err = fn(newStore) + return err +} + +var _ rocketride.Datastore = (*sqlStore)(nil) diff --git a/adapters/datastore/sqlstore_test.go b/datastore/bun/sqlstore_test.go similarity index 94% rename from adapters/datastore/sqlstore_test.go rename to datastore/bun/sqlstore_test.go index a0226fb..d4601df 100644 --- a/adapters/datastore/sqlstore_test.go +++ b/datastore/bun/sqlstore_test.go @@ -1,11 +1,10 @@ //go:build integration // +build integration -package datastore +package bun import ( "context" - "database/sql" "errors" "testing" @@ -19,9 +18,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tabbed/pqtype" - "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/pgdialect" - "github.com/uptrace/bun/driver/pgdriver" ) func TestSQLStore(t *testing.T) { @@ -55,10 +51,8 @@ func TestSQLStore(t *testing.T) { require.NoError(t, err) // connect to database - sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn))) - db := bun.NewDB(sqldb, pgdialect.New()) - - store := NewStore(db) + store, err := NewStore(dsn) + require.NoError(t, err) // test entities ride := &entity.Ride{ diff --git a/adapters/datastore/staged_job.go b/datastore/bun/staged_job.go similarity index 93% rename from adapters/datastore/staged_job.go rename to datastore/bun/staged_job.go index d1872c2..9f1861a 100644 --- a/adapters/datastore/staged_job.go +++ b/datastore/bun/staged_job.go @@ -1,4 +1,4 @@ -package datastore +package bun import ( "context" diff --git a/adapters/datastore/staged_job_test.go b/datastore/bun/staged_job_test.go similarity index 78% rename from adapters/datastore/staged_job_test.go rename to datastore/bun/staged_job_test.go index 2ce8306..5f5c55e 100644 --- a/adapters/datastore/staged_job_test.go +++ b/datastore/bun/staged_job_test.go @@ -1,11 +1,10 @@ //go:build integration // +build integration -package datastore +package bun import ( "context" - "database/sql" "testing" "github.com/rafael-piovesan/go-rocket-ride/entity" @@ -14,9 +13,6 @@ import ( "github.com/rafael-piovesan/go-rocket-ride/pkg/testcontainer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/pgdialect" - "github.com/uptrace/bun/driver/pgdriver" ) func TestStagedJob(t *testing.T) { @@ -32,10 +28,8 @@ func TestStagedJob(t *testing.T) { require.NoError(t, err) // connect to database - sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn))) - db := bun.NewDB(sqldb, pgdialect.New()) - - store := NewStore(db) + store, err := NewStore(dsn) + require.NoError(t, err) t.Run("Create Staged Job", func(t *testing.T) { sj := &entity.StagedJob{ diff --git a/adapters/datastore/user.go b/datastore/bun/user.go similarity index 95% rename from adapters/datastore/user.go rename to datastore/bun/user.go index 1e82f2c..fe492b0 100644 --- a/adapters/datastore/user.go +++ b/datastore/bun/user.go @@ -1,4 +1,4 @@ -package datastore +package bun import ( "context" diff --git a/adapters/datastore/user_test.go b/datastore/bun/user_test.go similarity index 82% rename from adapters/datastore/user_test.go rename to datastore/bun/user_test.go index 30b51ab..a26fbd9 100644 --- a/adapters/datastore/user_test.go +++ b/datastore/bun/user_test.go @@ -1,11 +1,10 @@ //go:build integration // +build integration -package datastore +package bun import ( "context" - "database/sql" "testing" "github.com/brianvoe/gofakeit/v6" @@ -15,9 +14,6 @@ import ( "github.com/rafael-piovesan/go-rocket-ride/pkg/testfixtures" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/pgdialect" - "github.com/uptrace/bun/driver/pgdriver" ) func TestUser(t *testing.T) { @@ -42,10 +38,8 @@ func TestUser(t *testing.T) { require.NoError(t, err) // conntect to database - sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn))) - db := bun.NewDB(sqldb, pgdialect.New()) - - store := NewStore(db) + store, err := NewStore(dsn) + require.NoError(t, err) t.Run("User not found", func(t *testing.T) { _, err := store.GetUserByEmail(ctx, gofakeit.FarmAnimal()) diff --git a/datastore/sqlc/audit_record.go b/datastore/sqlc/audit_record.go new file mode 100644 index 0000000..c38195e --- /dev/null +++ b/datastore/sqlc/audit_record.go @@ -0,0 +1,43 @@ +package sqlc + +import ( + "context" + + "github.com/rafael-piovesan/go-rocket-ride/entity" + "github.com/rafael-piovesan/go-rocket-ride/entity/audit" +) + +func (s *sqlStore) CreateAuditRecord(ctx context.Context, ar *entity.AuditRecord) (*entity.AuditRecord, error) { + arg := CreateAuditRecordParams{ + Action: ar.Action.String(), + CreatedAt: ar.CreatedAt, + Data: ar.Data, + OriginIp: ar.OriginIP, + ResourceID: ar.ResourceID, + ResourceType: ar.ResourceType.String(), + UserID: ar.UserID, + } + + model, err := s.q.CreateAuditRecord(ctx, arg) + if err != nil { + return nil, err + } + return toAuditRecordEntity(&model, ar), nil +} + +func toAuditRecordEntity(model *AuditRecord, ent *entity.AuditRecord) *entity.AuditRecord { + if ent == nil { + ent = &entity.AuditRecord{} + } + + ent.Action = audit.Action(model.Action) + ent.CreatedAt = model.CreatedAt + ent.Data = model.Data + ent.ID = model.ID + ent.OriginIP = model.OriginIp + ent.ResourceID = model.ResourceID + ent.ResourceType = audit.ResourceType(model.ResourceType) + ent.UserID = model.UserID + + return ent +} diff --git a/datastore/sqlc/audit_record.sql.go b/datastore/sqlc/audit_record.sql.go new file mode 100644 index 0000000..1f499e5 --- /dev/null +++ b/datastore/sqlc/audit_record.sql.go @@ -0,0 +1,58 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: audit_record.sql + +package sqlc + +import ( + "context" + "encoding/json" + "time" +) + +const createAuditRecord = `-- name: CreateAuditRecord :one +INSERT INTO audit_records( + action, + created_at, + data, + origin_ip, + resource_id, + resource_type, + user_id +) +VALUES ($1, $2, $3, $4, $5, $6, $7) +RETURNING id, action, created_at, data, origin_ip, resource_id, resource_type, user_id +` + +type CreateAuditRecordParams struct { + Action string + CreatedAt time.Time + Data json.RawMessage + OriginIp string + ResourceID int64 + ResourceType string + UserID int64 +} + +func (q *Queries) CreateAuditRecord(ctx context.Context, arg CreateAuditRecordParams) (AuditRecord, error) { + row := q.db.QueryRowContext(ctx, createAuditRecord, + arg.Action, + arg.CreatedAt, + arg.Data, + arg.OriginIp, + arg.ResourceID, + arg.ResourceType, + arg.UserID, + ) + var i AuditRecord + err := row.Scan( + &i.ID, + &i.Action, + &i.CreatedAt, + &i.Data, + &i.OriginIp, + &i.ResourceID, + &i.ResourceType, + &i.UserID, + ) + return i, err +} diff --git a/datastore/sqlc/audit_record_test.go b/datastore/sqlc/audit_record_test.go new file mode 100644 index 0000000..3af2df0 --- /dev/null +++ b/datastore/sqlc/audit_record_test.go @@ -0,0 +1,66 @@ +//go:build integration +// +build integration + +package sqlc + +import ( + "context" + "testing" + + "github.com/brianvoe/gofakeit/v6" + "github.com/rafael-piovesan/go-rocket-ride/entity" + "github.com/rafael-piovesan/go-rocket-ride/entity/audit" + "github.com/rafael-piovesan/go-rocket-ride/pkg/migrate" + "github.com/rafael-piovesan/go-rocket-ride/pkg/testcontainer" + "github.com/rafael-piovesan/go-rocket-ride/pkg/testfixtures" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tabbed/pqtype" +) + +func TestAuditRecord(t *testing.T) { + ctx := context.Background() + + // database up + dsn, terminate, err := testcontainer.NewPostgresContainer() + require.NoError(t, err) + defer terminate(ctx) + + // migrations up + err = migrate.Up(dsn, "db/migrations") + require.NoError(t, err) + + // test fixtures up + userID := int64(gofakeit.Number(0, 1000)) + err = testfixtures.Load(dsn, []string{"db/fixtures/users"}, map[string]interface{}{ + "UserId": userID, + "UserEmail": gofakeit.Email(), + }) + require.NoError(t, err) + + // conntect to database + store, err := NewStore(dsn) + require.NoError(t, err) + + t.Run("Create Audit Record", func(t *testing.T) { + ip := pqtype.CIDR{} + err := ip.Scan(gofakeit.IPv4Address()) + require.NoError(t, err) + + ar := &entity.AuditRecord{ + Action: audit.ActionCreateRide, + Data: []byte("{\"data\": \"foo\"}"), + OriginIP: ip.IPNet.String(), + ResourceID: int64(gofakeit.Number(0, 1000)), + ResourceType: audit.ResourceTypeRide, + UserID: userID, + } + + res, err := store.CreateAuditRecord(ctx, ar) + + if assert.NoError(t, err) { + ar.ID = res.ID + assert.Equal(t, ar, res) + } + }) +} diff --git a/datastore/sqlc/db.go b/datastore/sqlc/db.go new file mode 100644 index 0000000..934e4dc --- /dev/null +++ b/datastore/sqlc/db.go @@ -0,0 +1,29 @@ +// Code generated by sqlc. DO NOT EDIT. + +package sqlc + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/datastore/sqlc/idempotency_key.go b/datastore/sqlc/idempotency_key.go new file mode 100644 index 0000000..245d937 --- /dev/null +++ b/datastore/sqlc/idempotency_key.go @@ -0,0 +1,159 @@ +package sqlc + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "time" + + "github.com/rafael-piovesan/go-rocket-ride/entity" + "github.com/rafael-piovesan/go-rocket-ride/entity/idempotency" + "github.com/tabbed/pqtype" +) + +func (s *sqlStore) CreateIdempotencyKey( + ctx context.Context, + ik *entity.IdempotencyKey, +) (*entity.IdempotencyKey, error) { + lockedAt := sql.NullTime{} + if ik.LockedAt != nil { + lockedAt.Time = *ik.LockedAt + lockedAt.Valid = true + } + + rCode := sql.NullInt32{} + if ik.ResponseCode != nil { + rCode.Int32 = int32(*ik.ResponseCode) + rCode.Valid = true + } + + rBody := pqtype.NullRawMessage{} + if ik.ResponseBody != nil { + j, err := ik.ResponseBody.Marshal() + if err != nil { + return nil, err + } + + rBody.RawMessage = j + rBody.Valid = true + } + + arg := CreateIdempotencyKeyParams{ + CreatedAt: ik.CreatedAt, + IdempotencyKey: ik.IdempotencyKey, + LastRunAt: ik.LastRunAt, + LockedAt: lockedAt, + RequestMethod: ik.RequestMethod, + RequestParams: ik.RequestParams, + RequestPath: ik.RequestPath, + ResponseCode: rCode, + ResponseBody: rBody, + RecoveryPoint: ik.RecoveryPoint.String(), + UserID: ik.UserID, + } + + model, err := s.q.CreateIdempotencyKey(ctx, arg) + if err != nil { + return nil, err + } + + return toIdempotencyKeyEntity(&model, ik) +} + +func (s *sqlStore) GetIdempotencyKey(ctx context.Context, key string, userID int64) (*entity.IdempotencyKey, error) { + arg := GetIdempotencyKeyParams{ + UserID: userID, + IdempotencyKey: key, + } + model, err := s.q.GetIdempotencyKey(ctx, arg) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, entity.ErrNotFound + } + return nil, err + } + + return toIdempotencyKeyEntity(&model, nil) +} + +func (s *sqlStore) UpdateIdempotencyKey( + ctx context.Context, + ik *entity.IdempotencyKey, +) (*entity.IdempotencyKey, error) { + lockedAt := sql.NullTime{} + if ik.LockedAt != nil { + lockedAt.Time = *ik.LockedAt + lockedAt.Valid = true + } + + rCode := sql.NullInt32{} + if ik.ResponseCode != nil { + rCode.Int32 = int32(*ik.ResponseCode) + rCode.Valid = true + } + + rBody := pqtype.NullRawMessage{} + if ik.ResponseBody != nil { + j, err := ik.ResponseBody.Marshal() + if err != nil { + return nil, err + } + + rBody.RawMessage = j + rBody.Valid = true + } + + arg := UpdateIdempotencyKeyParams{ + ID: ik.ID, + LastRunAt: ik.LastRunAt, + LockedAt: lockedAt, + ResponseCode: rCode, + ResponseBody: rBody, + RecoveryPoint: ik.RecoveryPoint.String(), + } + model, err := s.q.UpdateIdempotencyKey(ctx, arg) + if err != nil { + return nil, err + } + + return toIdempotencyKeyEntity(&model, ik) +} + +func toIdempotencyKeyEntity(model *IdempotencyKey, ent *entity.IdempotencyKey) (*entity.IdempotencyKey, error) { + if ent == nil { + ent = &entity.IdempotencyKey{} + } + var lockedAt *time.Time + if model.LockedAt.Valid { + lockedAt = &model.LockedAt.Time + } + + var rCode *int32 + if model.ResponseCode.Valid { + rCode = &model.ResponseCode.Int32 + } + + var rBody *idempotency.ResponseBody + if model.ResponseBody.Valid { + err := json.Unmarshal(model.ResponseBody.RawMessage, &rBody) + if err != nil { + return nil, err + } + } + + ent.ID = model.ID + ent.CreatedAt = model.CreatedAt + ent.IdempotencyKey = model.IdempotencyKey + ent.LastRunAt = model.LastRunAt + ent.LockedAt = lockedAt + ent.RequestMethod = model.RequestMethod + ent.RequestParams = model.RequestParams + ent.RequestPath = model.RequestPath + ent.ResponseCode = (*idempotency.ResponseCode)(rCode) + ent.ResponseBody = rBody + ent.RecoveryPoint = idempotency.RecoveryPoint(model.RecoveryPoint) + ent.UserID = model.UserID + + return ent, nil +} diff --git a/datastore/sqlc/idempotency_key.sql.go b/datastore/sqlc/idempotency_key.sql.go new file mode 100644 index 0000000..c2ad61d --- /dev/null +++ b/datastore/sqlc/idempotency_key.sql.go @@ -0,0 +1,154 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: idempotency_key.sql + +package sqlc + +import ( + "context" + "database/sql" + "encoding/json" + "time" + + "github.com/tabbed/pqtype" +) + +const createIdempotencyKey = `-- name: CreateIdempotencyKey :one +INSERT INTO idempotency_keys( + created_at, + idempotency_key, + last_run_at, + locked_at, + request_method, + request_params, + request_path, + response_code, + response_body, + recovery_point, + user_id +) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) +RETURNING id, created_at, idempotency_key, last_run_at, locked_at, request_method, request_params, request_path, response_code, response_body, recovery_point, user_id +` + +type CreateIdempotencyKeyParams struct { + CreatedAt time.Time + IdempotencyKey string + LastRunAt time.Time + LockedAt sql.NullTime + RequestMethod string + RequestParams json.RawMessage + RequestPath string + ResponseCode sql.NullInt32 + ResponseBody pqtype.NullRawMessage + RecoveryPoint string + UserID int64 +} + +func (q *Queries) CreateIdempotencyKey(ctx context.Context, arg CreateIdempotencyKeyParams) (IdempotencyKey, error) { + row := q.db.QueryRowContext(ctx, createIdempotencyKey, + arg.CreatedAt, + arg.IdempotencyKey, + arg.LastRunAt, + arg.LockedAt, + arg.RequestMethod, + arg.RequestParams, + arg.RequestPath, + arg.ResponseCode, + arg.ResponseBody, + arg.RecoveryPoint, + arg.UserID, + ) + var i IdempotencyKey + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.IdempotencyKey, + &i.LastRunAt, + &i.LockedAt, + &i.RequestMethod, + &i.RequestParams, + &i.RequestPath, + &i.ResponseCode, + &i.ResponseBody, + &i.RecoveryPoint, + &i.UserID, + ) + return i, err +} + +const getIdempotencyKey = `-- name: GetIdempotencyKey :one +SELECT id, created_at, idempotency_key, last_run_at, locked_at, request_method, request_params, request_path, response_code, response_body, recovery_point, user_id FROM idempotency_keys +WHERE user_id = $1 AND idempotency_key = $2 LIMIT 1 +` + +type GetIdempotencyKeyParams struct { + UserID int64 + IdempotencyKey string +} + +func (q *Queries) GetIdempotencyKey(ctx context.Context, arg GetIdempotencyKeyParams) (IdempotencyKey, error) { + row := q.db.QueryRowContext(ctx, getIdempotencyKey, arg.UserID, arg.IdempotencyKey) + var i IdempotencyKey + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.IdempotencyKey, + &i.LastRunAt, + &i.LockedAt, + &i.RequestMethod, + &i.RequestParams, + &i.RequestPath, + &i.ResponseCode, + &i.ResponseBody, + &i.RecoveryPoint, + &i.UserID, + ) + return i, err +} + +const updateIdempotencyKey = `-- name: UpdateIdempotencyKey :one +UPDATE idempotency_keys SET + last_run_at=$2, + locked_at=$3, + response_code=$4, + response_body=$5, + recovery_point=$6 +WHERE id = $1 +RETURNING id, created_at, idempotency_key, last_run_at, locked_at, request_method, request_params, request_path, response_code, response_body, recovery_point, user_id +` + +type UpdateIdempotencyKeyParams struct { + ID int64 + LastRunAt time.Time + LockedAt sql.NullTime + ResponseCode sql.NullInt32 + ResponseBody pqtype.NullRawMessage + RecoveryPoint string +} + +func (q *Queries) UpdateIdempotencyKey(ctx context.Context, arg UpdateIdempotencyKeyParams) (IdempotencyKey, error) { + row := q.db.QueryRowContext(ctx, updateIdempotencyKey, + arg.ID, + arg.LastRunAt, + arg.LockedAt, + arg.ResponseCode, + arg.ResponseBody, + arg.RecoveryPoint, + ) + var i IdempotencyKey + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.IdempotencyKey, + &i.LastRunAt, + &i.LockedAt, + &i.RequestMethod, + &i.RequestParams, + &i.RequestPath, + &i.ResponseCode, + &i.ResponseBody, + &i.RecoveryPoint, + &i.UserID, + ) + return i, err +} diff --git a/datastore/sqlc/idempotency_key_test.go b/datastore/sqlc/idempotency_key_test.go new file mode 100644 index 0000000..91d6a89 --- /dev/null +++ b/datastore/sqlc/idempotency_key_test.go @@ -0,0 +1,108 @@ +//go:build integration +// +build integration + +package sqlc + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/brianvoe/gofakeit/v6" + "github.com/rafael-piovesan/go-rocket-ride/entity" + "github.com/rafael-piovesan/go-rocket-ride/entity/idempotency" + "github.com/rafael-piovesan/go-rocket-ride/pkg/migrate" + "github.com/rafael-piovesan/go-rocket-ride/pkg/testcontainer" + "github.com/rafael-piovesan/go-rocket-ride/pkg/testfixtures" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestIdempotencyKey(t *testing.T) { + ctx := context.Background() + + // database up + dsn, terminate, err := testcontainer.NewPostgresContainer() + require.NoError(t, err) + defer terminate(ctx) + + // migrations up + err = migrate.Up(dsn, "db/migrations") + require.NoError(t, err) + + // test fixtures up + userID := int64(gofakeit.Number(0, 1000)) + idemKey := gofakeit.UUID() + err = testfixtures.Load(dsn, []string{"db/fixtures/users"}, map[string]interface{}{ + "UserId": userID, + "UserEmail": gofakeit.Email(), + }) + require.NoError(t, err) + + // conntect to database + store, err := NewStore(dsn) + require.NoError(t, err) + + // test entity + now := time.Now() + ttime := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), 0, 0, time.UTC) + ik := &entity.IdempotencyKey{ + IdempotencyKey: idemKey, + LastRunAt: ttime, + LockedAt: &ttime, + RequestMethod: gofakeit.HTTPMethod(), + RequestParams: []byte("{\"data\": \"foo\"}"), + RequestPath: fmt.Sprintf("/%s/%s", gofakeit.AnimalType(), gofakeit.Animal()), + RecoveryPoint: idempotency.RecoveryPointStarted, + UserID: userID, + } + + t.Run("Idempotency Key not found", func(t *testing.T) { + _, err := store.GetIdempotencyKey(ctx, idemKey, userID) + assert.ErrorIs(t, err, entity.ErrNotFound) + }) + + t.Run("Create Idempotency Key", func(t *testing.T) { + res, err := store.CreateIdempotencyKey(ctx, ik) + if assert.NoError(t, err) { + ik.ID = res.ID + assert.Equal(t, ik, res) + } + }) + + t.Run("Update Idempotency Key", func(t *testing.T) { + now = time.Now() + + rps := []idempotency.RecoveryPoint{ + idempotency.RecoveryPointCreated, + idempotency.RecoveryPointCharged, + idempotency.RecoveryPointFinished, + } + + idx := gofakeit.Number(0, len(rps)-1) + + resCode := idempotency.ResponseCodeOK + resBody := idempotency.ResponseBody{Message: "OK"} + + ik.LastRunAt = time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), 0, 0, time.UTC) + ik.LockedAt = nil + ik.RecoveryPoint = rps[idx] + ik.ResponseCode = &resCode + ik.ResponseBody = &resBody + + res, err := store.UpdateIdempotencyKey(ctx, ik) + if assert.NoError(t, err) { + assert.Equal(t, ik, res) + } + }) + + t.Run("Get Idempotency Key", func(t *testing.T) { + res, err := store.GetIdempotencyKey(ctx, idemKey, userID) + if assert.NoError(t, err) { + assert.Equal(t, ik, res) + t.Logf("ik %v", ik.ResponseBody) + t.Logf("res %v", res.ResponseBody) + } + }) +} diff --git a/datastore/sqlc/models.go b/datastore/sqlc/models.go new file mode 100644 index 0000000..4ab5d40 --- /dev/null +++ b/datastore/sqlc/models.go @@ -0,0 +1,61 @@ +// Code generated by sqlc. DO NOT EDIT. + +package sqlc + +import ( + "database/sql" + "encoding/json" + "time" + + "github.com/tabbed/pqtype" +) + +type AuditRecord struct { + ID int64 + Action string + CreatedAt time.Time + Data json.RawMessage + OriginIp string + ResourceID int64 + ResourceType string + UserID int64 +} + +type IdempotencyKey struct { + ID int64 + CreatedAt time.Time + IdempotencyKey string + LastRunAt time.Time + LockedAt sql.NullTime + RequestMethod string + RequestParams json.RawMessage + RequestPath string + ResponseCode sql.NullInt32 + ResponseBody pqtype.NullRawMessage + RecoveryPoint string + UserID int64 +} + +type Ride struct { + ID int64 + CreatedAt time.Time + IdempotencyKeyID sql.NullInt64 + OriginLat float64 + OriginLon float64 + TargetLat float64 + TargetLon float64 + StripeChargeID sql.NullString + UserID int64 +} + +type StagedJob struct { + ID int64 + JobName string + JobArgs json.RawMessage +} + +type User struct { + ID int64 + Email string + StripeCustomerID string +} diff --git a/datastore/sqlc/ride.go b/datastore/sqlc/ride.go new file mode 100644 index 0000000..a146ce7 --- /dev/null +++ b/datastore/sqlc/ride.go @@ -0,0 +1,99 @@ +package sqlc + +import ( + "context" + "database/sql" + "errors" + + "github.com/rafael-piovesan/go-rocket-ride/entity" +) + +func (s *sqlStore) CreateRide(ctx context.Context, rd *entity.Ride) (*entity.Ride, error) { + keyID := sql.NullInt64{} + if rd.IdempotencyKeyID != nil { + keyID.Int64 = *rd.IdempotencyKeyID + keyID.Valid = true + } + + scID := sql.NullString{} + if rd.StripeChargeID != nil { + scID.String = *rd.StripeChargeID + scID.Valid = true + } + + arg := CreateRideParams{ + CreatedAt: rd.CreatedAt, + IdempotencyKeyID: keyID, + OriginLat: rd.OriginLat, + OriginLon: rd.OriginLon, + TargetLat: rd.TargetLat, + TargetLon: rd.TargetLon, + StripeChargeID: scID, + UserID: rd.UserID, + } + model, err := s.q.CreateRide(ctx, arg) + if err != nil { + return nil, err + } + + return toRideEntity(&model, rd), nil +} + +func (s *sqlStore) GetRideByIdempotencyKeyID(ctx context.Context, keyID int64) (*entity.Ride, error) { + model, err := s.q.GetRideByIdempotencyKeyID(ctx, sql.NullInt64{Int64: keyID, Valid: true}) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, entity.ErrNotFound + } + return nil, err + } + + return toRideEntity(&model, nil), nil +} + +func (s *sqlStore) UpdateRide(ctx context.Context, rd *entity.Ride) (*entity.Ride, error) { + scID := sql.NullString{} + if rd.StripeChargeID != nil { + scID.String = *rd.StripeChargeID + scID.Valid = true + } + + arg := UpdateRideParams{ + ID: rd.ID, + StripeChargeID: scID, + } + + model, err := s.q.UpdateRide(ctx, arg) + if err != nil { + return nil, err + } + + return toRideEntity(&model, rd), nil +} + +func toRideEntity(model *Ride, ent *entity.Ride) *entity.Ride { + if ent == nil { + ent = &entity.Ride{} + } + var keyID *int64 + if model.IdempotencyKeyID.Valid { + keyID = &model.IdempotencyKeyID.Int64 + } + + var scID *string + if model.StripeChargeID.Valid { + scID = &model.StripeChargeID.String + } + + ent.ID = model.ID + ent.CreatedAt = model.CreatedAt + ent.IdempotencyKeyID = keyID + ent.OriginLat = model.OriginLat + ent.OriginLon = model.OriginLon + ent.TargetLat = model.TargetLat + ent.TargetLon = model.TargetLon + ent.StripeChargeID = scID + ent.UserID = model.UserID + + return ent +} diff --git a/datastore/sqlc/ride.sql.go b/datastore/sqlc/ride.sql.go new file mode 100644 index 0000000..26e26c0 --- /dev/null +++ b/datastore/sqlc/ride.sql.go @@ -0,0 +1,135 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: ride.sql + +package sqlc + +import ( + "context" + "database/sql" + "time" +) + +const createRide = `-- name: CreateRide :one +INSERT INTO rides( + created_at, + idempotency_key_id, + origin_lat, + origin_lon, + target_lat, + target_lon, + stripe_charge_id, + user_id +) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +RETURNING id, created_at, idempotency_key_id, origin_lat, origin_lon, target_lat, target_lon, stripe_charge_id, user_id +` + +type CreateRideParams struct { + CreatedAt time.Time + IdempotencyKeyID sql.NullInt64 + OriginLat float64 + OriginLon float64 + TargetLat float64 + TargetLon float64 + StripeChargeID sql.NullString + UserID int64 +} + +func (q *Queries) CreateRide(ctx context.Context, arg CreateRideParams) (Ride, error) { + row := q.db.QueryRowContext(ctx, createRide, + arg.CreatedAt, + arg.IdempotencyKeyID, + arg.OriginLat, + arg.OriginLon, + arg.TargetLat, + arg.TargetLon, + arg.StripeChargeID, + arg.UserID, + ) + var i Ride + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.IdempotencyKeyID, + &i.OriginLat, + &i.OriginLon, + &i.TargetLat, + &i.TargetLon, + &i.StripeChargeID, + &i.UserID, + ) + return i, err +} + +const getRideByID = `-- name: GetRideByID :one +SELECT id, created_at, idempotency_key_id, origin_lat, origin_lon, target_lat, target_lon, stripe_charge_id, user_id FROM rides +WHERE id = $1 LIMIT 1 +` + +func (q *Queries) GetRideByID(ctx context.Context, id int64) (Ride, error) { + row := q.db.QueryRowContext(ctx, getRideByID, id) + var i Ride + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.IdempotencyKeyID, + &i.OriginLat, + &i.OriginLon, + &i.TargetLat, + &i.TargetLon, + &i.StripeChargeID, + &i.UserID, + ) + return i, err +} + +const getRideByIdempotencyKeyID = `-- name: GetRideByIdempotencyKeyID :one +SELECT id, created_at, idempotency_key_id, origin_lat, origin_lon, target_lat, target_lon, stripe_charge_id, user_id FROM rides +WHERE idempotency_key_id = $1 LIMIT 1 +` + +func (q *Queries) GetRideByIdempotencyKeyID(ctx context.Context, idempotencyKeyID sql.NullInt64) (Ride, error) { + row := q.db.QueryRowContext(ctx, getRideByIdempotencyKeyID, idempotencyKeyID) + var i Ride + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.IdempotencyKeyID, + &i.OriginLat, + &i.OriginLon, + &i.TargetLat, + &i.TargetLon, + &i.StripeChargeID, + &i.UserID, + ) + return i, err +} + +const updateRide = `-- name: UpdateRide :one +UPDATE rides SET + stripe_charge_id=$2 +WHERE id = $1 +RETURNING id, created_at, idempotency_key_id, origin_lat, origin_lon, target_lat, target_lon, stripe_charge_id, user_id +` + +type UpdateRideParams struct { + ID int64 + StripeChargeID sql.NullString +} + +func (q *Queries) UpdateRide(ctx context.Context, arg UpdateRideParams) (Ride, error) { + row := q.db.QueryRowContext(ctx, updateRide, arg.ID, arg.StripeChargeID) + var i Ride + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.IdempotencyKeyID, + &i.OriginLat, + &i.OriginLon, + &i.TargetLat, + &i.TargetLon, + &i.StripeChargeID, + &i.UserID, + ) + return i, err +} diff --git a/datastore/sqlc/ride_test.go b/datastore/sqlc/ride_test.go new file mode 100644 index 0000000..ac022bc --- /dev/null +++ b/datastore/sqlc/ride_test.go @@ -0,0 +1,92 @@ +//go:build integration +// +build integration + +package sqlc + +import ( + "context" + "testing" + + "github.com/brianvoe/gofakeit/v6" + "github.com/rafael-piovesan/go-rocket-ride/entity" + "github.com/rafael-piovesan/go-rocket-ride/pkg/migrate" + "github.com/rafael-piovesan/go-rocket-ride/pkg/testcontainer" + "github.com/rafael-piovesan/go-rocket-ride/pkg/testfixtures" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRide(t *testing.T) { + ctx := context.Background() + + // database up + dsn, terminate, err := testcontainer.NewPostgresContainer() + require.NoError(t, err) + defer terminate(ctx) + + // migrations up + err = migrate.Up(dsn, "db/migrations") + require.NoError(t, err) + + // test fixtures up + userID := int64(gofakeit.Number(0, 1000)) + keyID := int64(gofakeit.Number(0, 1000)) + + err = testfixtures.Load( + dsn, + []string{ + "db/fixtures/users", + "db/fixtures/idempotency_keys", + }, + map[string]interface{}{ + "UserId": userID, + "UserEmail": gofakeit.Email(), + "KeyId": keyID, + }, + ) + require.NoError(t, err) + + // conntect to database + store, err := NewStore(dsn) + require.NoError(t, err) + + // test entity + ride := &entity.Ride{ + IdempotencyKeyID: &keyID, + OriginLat: 0.0, + OriginLon: 0.0, + TargetLat: 0.0, + TargetLon: 0.0, + UserID: userID, + } + + t.Run("Ride not found", func(t *testing.T) { + _, err := store.GetRideByIdempotencyKeyID(ctx, keyID) + assert.ErrorIs(t, err, entity.ErrNotFound) + }) + + t.Run("Create Ride", func(t *testing.T) { + res, err := store.CreateRide(ctx, ride) + if assert.NoError(t, err) { + ride.ID = res.ID + assert.Equal(t, ride, res) + } + }) + + t.Run("Update Ride", func(t *testing.T) { + stripeID := gofakeit.UUID() + ride.StripeChargeID = &stripeID + + res, err := store.UpdateRide(ctx, ride) + if assert.NoError(t, err) { + assert.Equal(t, ride, res) + } + }) + + t.Run("Get Ride By Idempotency Key ID", func(t *testing.T) { + res, err := store.GetRideByIdempotencyKeyID(ctx, keyID) + if assert.NoError(t, err) { + assert.Equal(t, ride, res) + } + }) +} diff --git a/adapters/datastore/sqlstore.go b/datastore/sqlc/sqlstore.go similarity index 74% rename from adapters/datastore/sqlstore.go rename to datastore/sqlc/sqlstore.go index 693ddc1..e984ff1 100644 --- a/adapters/datastore/sqlstore.go +++ b/datastore/sqlc/sqlstore.go @@ -1,4 +1,4 @@ -package datastore +package sqlc import ( "context" @@ -6,21 +6,28 @@ import ( "fmt" "runtime" - "github.com/uptrace/bun" - rocketride "github.com/rafael-piovesan/go-rocket-ride" + + // loading Postgres driver. + _ "github.com/lib/pq" ) type sqlStore struct { - conn *bun.DB - db bun.IDB + conn *sql.DB + q *Queries } -func NewStore(db *bun.DB) rocketride.Datastore { - return &sqlStore{ - conn: db, - db: db, +func NewStore(dsn string) (s rocketride.Datastore, err error) { + sqldb, err := sql.Open("postgres", dsn) + if err != nil { + return nil, err + } + + s = &sqlStore{ + conn: sqldb, + q: New(sqldb), } + return } func (s *sqlStore) Atomic(ctx context.Context, fn func(store rocketride.Datastore) error) (err error) { @@ -55,7 +62,7 @@ func (s *sqlStore) Atomic(ctx context.Context, fn func(store rocketride.Datastor // TODO: check if it works for nested transactions as well newStore := &sqlStore{ conn: s.conn, - db: tx, + q: New(tx), } err = fn(newStore) return err diff --git a/datastore/sqlc/sqlstore_test.go b/datastore/sqlc/sqlstore_test.go new file mode 100644 index 0000000..b2425ef --- /dev/null +++ b/datastore/sqlc/sqlstore_test.go @@ -0,0 +1,189 @@ +//go:build integration +// +build integration + +package sqlc + +import ( + "context" + "errors" + "testing" + + "github.com/brianvoe/gofakeit/v6" + rocketride "github.com/rafael-piovesan/go-rocket-ride" + "github.com/rafael-piovesan/go-rocket-ride/entity" + "github.com/rafael-piovesan/go-rocket-ride/entity/audit" + "github.com/rafael-piovesan/go-rocket-ride/pkg/migrate" + "github.com/rafael-piovesan/go-rocket-ride/pkg/testcontainer" + "github.com/rafael-piovesan/go-rocket-ride/pkg/testfixtures" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tabbed/pqtype" +) + +func TestSQLStore(t *testing.T) { + ctx := context.Background() + + // database up + dsn, terminate, err := testcontainer.NewPostgresContainer() + require.NoError(t, err) + defer terminate(ctx) + + // migrations up + err = migrate.Up(dsn, "db/migrations") + require.NoError(t, err) + + // test fixtures up + userID := int64(gofakeit.Number(0, 1000)) + keyID := int64(gofakeit.Number(0, 1000)) + + err = testfixtures.Load( + dsn, + []string{ + "db/fixtures/users", + "db/fixtures/idempotency_keys", + }, + map[string]interface{}{ + "UserId": userID, + "UserEmail": gofakeit.Email(), + "KeyId": keyID, + }, + ) + require.NoError(t, err) + + // connect to database + store, err := NewStore(dsn) + require.NoError(t, err) + + // test entities + ride := &entity.Ride{ + IdempotencyKeyID: &keyID, + OriginLat: 0.0, + OriginLon: 0.0, + TargetLat: 0.0, + TargetLon: 0.0, + UserID: userID, + } + + ip := pqtype.CIDR{} + err = ip.Scan(gofakeit.IPv4Address()) + require.NoError(t, err) + + ar := &entity.AuditRecord{ + Action: audit.ActionCreateRide, + Data: []byte("{\"data\": \"foo\"}"), + OriginIP: ip.IPNet.String(), + ResourceID: int64(gofakeit.Number(0, 1000)), + ResourceType: audit.ResourceTypeRide, + UserID: userID, + } + + t.Run("Rollback on error", func(t *testing.T) { + _, err := store.GetRideByIdempotencyKeyID(ctx, keyID) + require.ErrorIs(t, err, entity.ErrNotFound) + + err = store.Atomic(ctx, func(ds rocketride.Datastore) error { + _, err := ds.CreateRide(ctx, ride) + require.NoError(t, err) + + _, err = ds.CreateAuditRecord(ctx, ar) + require.NoError(t, err) + + return errors.New("error rollback") + }) + + if assert.EqualError(t, err, "error rollback") { + _, err = store.GetRideByIdempotencyKeyID(ctx, keyID) + assert.ErrorIs(t, err, entity.ErrNotFound) + } + }) + + t.Run("Rollback on panic with error", func(t *testing.T) { + _, err := store.GetRideByIdempotencyKeyID(ctx, keyID) + require.ErrorIs(t, err, entity.ErrNotFound) + + err = store.Atomic(ctx, func(ds rocketride.Datastore) error { + _, err := ds.CreateRide(ctx, ride) + require.NoError(t, err) + + _, err = ds.CreateAuditRecord(ctx, ar) + require.NoError(t, err) + + panic(errors.New("panic rollback")) + }) + + if assert.EqualError(t, err, "panic err: panic rollback") { + _, err = store.GetRideByIdempotencyKeyID(ctx, keyID) + assert.ErrorIs(t, err, entity.ErrNotFound) + } + }) + + t.Run("Rollback on panic without error", func(t *testing.T) { + _, err := store.GetRideByIdempotencyKeyID(ctx, keyID) + require.ErrorIs(t, err, entity.ErrNotFound) + + defer func() { + p := recover() + if assert.NotNil(t, p) && assert.Equal(t, "panic rollback", p) { + _, err = store.GetRideByIdempotencyKeyID(ctx, keyID) + assert.ErrorIs(t, err, entity.ErrNotFound) + } + }() + + err = store.Atomic(ctx, func(ds rocketride.Datastore) error { + _, err := ds.CreateRide(ctx, ride) + require.NoError(t, err) + + _, err = ds.CreateAuditRecord(ctx, ar) + require.NoError(t, err) + + panic("panic rollback") + }) + }) + + t.Run("Rollback on context canceled", func(t *testing.T) { + cancelCtx, cancel := context.WithCancel(ctx) + + _, err := store.GetRideByIdempotencyKeyID(cancelCtx, keyID) + require.ErrorIs(t, err, entity.ErrNotFound) + + err = store.Atomic(ctx, func(ds rocketride.Datastore) error { + _, err := ds.CreateRide(cancelCtx, ride) + require.NoError(t, err) + + cancel() + + // this call should return an error due to the canceled ctx + _, err = ds.CreateAuditRecord(cancelCtx, ar) + return err + }) + + if assert.EqualError(t, err, "context canceled") { + _, err = store.GetRideByIdempotencyKeyID(ctx, keyID) + assert.ErrorIs(t, err, entity.ErrNotFound) + } + }) + + t.Run("Commit on success", func(t *testing.T) { + _, err := store.GetRideByIdempotencyKeyID(ctx, keyID) + require.ErrorIs(t, err, entity.ErrNotFound) + + err = store.Atomic(ctx, func(ds rocketride.Datastore) error { + _, err := ds.CreateRide(ctx, ride) + require.NoError(t, err) + + _, err = ds.CreateAuditRecord(ctx, ar) + require.NoError(t, err) + + return nil + }) + + if assert.NoError(t, err) { + res, err := store.GetRideByIdempotencyKeyID(ctx, keyID) + if assert.NoError(t, err) { + ride.ID = res.ID + assert.Equal(t, ride, res) + } + } + }) + +} diff --git a/datastore/sqlc/staged_job.go b/datastore/sqlc/staged_job.go new file mode 100644 index 0000000..a0e05e7 --- /dev/null +++ b/datastore/sqlc/staged_job.go @@ -0,0 +1,33 @@ +package sqlc + +import ( + "context" + + "github.com/rafael-piovesan/go-rocket-ride/entity" + "github.com/rafael-piovesan/go-rocket-ride/entity/stagedjob" +) + +func (s *sqlStore) CreateStagedJob(ctx context.Context, sj *entity.StagedJob) (*entity.StagedJob, error) { + arg := CreateStagedJobParams{ + JobName: sj.JobName.String(), + JobArgs: sj.JobArgs, + } + model, err := s.q.CreateStagedJob(ctx, arg) + if err != nil { + return nil, err + } + + return toStagedJobEntity(&model, sj), nil +} + +func toStagedJobEntity(model *StagedJob, ent *entity.StagedJob) *entity.StagedJob { + if ent == nil { + ent = &entity.StagedJob{} + } + + ent.ID = model.ID + ent.JobName = stagedjob.JobName(model.JobName) + ent.JobArgs = model.JobArgs + + return ent +} diff --git a/datastore/sqlc/staged_job.sql.go b/datastore/sqlc/staged_job.sql.go new file mode 100644 index 0000000..d7ea84a --- /dev/null +++ b/datastore/sqlc/staged_job.sql.go @@ -0,0 +1,30 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: staged_job.sql + +package sqlc + +import ( + "context" + "encoding/json" +) + +const createStagedJob = `-- name: CreateStagedJob :one +INSERT INTO staged_jobs( + job_name, + job_args +) +VALUES ($1, $2) +RETURNING id, job_name, job_args +` + +type CreateStagedJobParams struct { + JobName string + JobArgs json.RawMessage +} + +func (q *Queries) CreateStagedJob(ctx context.Context, arg CreateStagedJobParams) (StagedJob, error) { + row := q.db.QueryRowContext(ctx, createStagedJob, arg.JobName, arg.JobArgs) + var i StagedJob + err := row.Scan(&i.ID, &i.JobName, &i.JobArgs) + return i, err +} diff --git a/datastore/sqlc/staged_job_test.go b/datastore/sqlc/staged_job_test.go new file mode 100644 index 0000000..455cbf6 --- /dev/null +++ b/datastore/sqlc/staged_job_test.go @@ -0,0 +1,47 @@ +//go:build integration +// +build integration + +package sqlc + +import ( + "context" + "testing" + + "github.com/rafael-piovesan/go-rocket-ride/entity" + "github.com/rafael-piovesan/go-rocket-ride/entity/stagedjob" + "github.com/rafael-piovesan/go-rocket-ride/pkg/migrate" + "github.com/rafael-piovesan/go-rocket-ride/pkg/testcontainer" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStagedJob(t *testing.T) { + ctx := context.Background() + + // database up + dsn, terminate, err := testcontainer.NewPostgresContainer() + require.NoError(t, err) + defer terminate(ctx) + + // migrations up + err = migrate.Up(dsn, "db/migrations") + require.NoError(t, err) + + // connect to database + store, err := NewStore(dsn) + require.NoError(t, err) + + t.Run("Create Staged Job", func(t *testing.T) { + sj := &entity.StagedJob{ + JobName: stagedjob.JobNameSendReceipt, + JobArgs: []byte("{\"data\": \"foo\"}"), + } + + res, err := store.CreateStagedJob(ctx, sj) + + if assert.NoError(t, err) { + sj.ID = res.ID + assert.Equal(t, sj, res) + } + }) +} diff --git a/datastore/sqlc/user.go b/datastore/sqlc/user.go new file mode 100644 index 0000000..e0cbb83 --- /dev/null +++ b/datastore/sqlc/user.go @@ -0,0 +1,33 @@ +package sqlc + +import ( + "context" + "database/sql" + "errors" + + "github.com/rafael-piovesan/go-rocket-ride/entity" +) + +func (s *sqlStore) GetUserByEmail(ctx context.Context, e string) (*entity.User, error) { + model, err := s.q.GetUserByEmail(ctx, e) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, entity.ErrNotFound + } + return nil, err + } + + return toUserEntity(&model, nil), nil +} + +func toUserEntity(model *User, ent *entity.User) *entity.User { + if ent == nil { + ent = &entity.User{} + } + + ent.ID = model.ID + ent.Email = model.Email + ent.StripeCustomerID = model.StripeCustomerID + + return ent +} diff --git a/datastore/sqlc/user.sql.go b/datastore/sqlc/user.sql.go new file mode 100644 index 0000000..15f852e --- /dev/null +++ b/datastore/sqlc/user.sql.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: user.sql + +package sqlc + +import ( + "context" +) + +const getUserByEmail = `-- name: GetUserByEmail :one +SELECT id, email, stripe_customer_id FROM users +WHERE email = $1 LIMIT 1 +` + +func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error) { + row := q.db.QueryRowContext(ctx, getUserByEmail, email) + var i User + err := row.Scan(&i.ID, &i.Email, &i.StripeCustomerID) + return i, err +} + +const getUserByID = `-- name: GetUserByID :one +SELECT id, email, stripe_customer_id FROM users +WHERE id = $1 LIMIT 1 +` + +func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) { + row := q.db.QueryRowContext(ctx, getUserByID, id) + var i User + err := row.Scan(&i.ID, &i.Email, &i.StripeCustomerID) + return i, err +} diff --git a/datastore/sqlc/user_test.go b/datastore/sqlc/user_test.go new file mode 100644 index 0000000..86418b5 --- /dev/null +++ b/datastore/sqlc/user_test.go @@ -0,0 +1,55 @@ +//go:build integration +// +build integration + +package sqlc + +import ( + "context" + "testing" + + "github.com/brianvoe/gofakeit/v6" + "github.com/rafael-piovesan/go-rocket-ride/entity" + "github.com/rafael-piovesan/go-rocket-ride/pkg/migrate" + "github.com/rafael-piovesan/go-rocket-ride/pkg/testcontainer" + "github.com/rafael-piovesan/go-rocket-ride/pkg/testfixtures" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUser(t *testing.T) { + ctx := context.Background() + + // database up + dsn, terminate, err := testcontainer.NewPostgresContainer() + require.NoError(t, err) + defer terminate(ctx) + + // migrations up + err = migrate.Up(dsn, "db/migrations") + require.NoError(t, err) + + // test fixtures up + userID := int64(gofakeit.Number(0, 1000)) + userEmail := gofakeit.Email() + err = testfixtures.Load(dsn, []string{"db/fixtures/users"}, map[string]interface{}{ + "UserId": userID, + "UserEmail": userEmail, + }) + require.NoError(t, err) + + // conntect to database + store, err := NewStore(dsn) + require.NoError(t, err) + + t.Run("User not found", func(t *testing.T) { + _, err := store.GetUserByEmail(ctx, gofakeit.FarmAnimal()) + assert.ErrorIs(t, err, entity.ErrNotFound) + }) + + t.Run("User found", func(t *testing.T) { + u, err := store.GetUserByEmail(ctx, userEmail) + assert.NoError(t, err) + assert.Equal(t, userID, u.ID) + assert.Equal(t, userEmail, u.Email) + }) +} diff --git a/db/queries/audit_record.sql b/db/queries/audit_record.sql new file mode 100644 index 0000000..0bf9886 --- /dev/null +++ b/db/queries/audit_record.sql @@ -0,0 +1,12 @@ +-- name: CreateAuditRecord :one +INSERT INTO audit_records( + action, + created_at, + data, + origin_ip, + resource_id, + resource_type, + user_id +) +VALUES ($1, $2, $3, $4, $5, $6, $7) +RETURNING *; \ No newline at end of file diff --git a/db/queries/idempotency_key.sql b/db/queries/idempotency_key.sql new file mode 100644 index 0000000..f0c6a40 --- /dev/null +++ b/db/queries/idempotency_key.sql @@ -0,0 +1,30 @@ +-- name: GetIdempotencyKey :one +SELECT * FROM idempotency_keys +WHERE user_id = $1 AND idempotency_key = $2 LIMIT 1; + +-- name: CreateIdempotencyKey :one +INSERT INTO idempotency_keys( + created_at, + idempotency_key, + last_run_at, + locked_at, + request_method, + request_params, + request_path, + response_code, + response_body, + recovery_point, + user_id +) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) +RETURNING *; + +-- name: UpdateIdempotencyKey :one +UPDATE idempotency_keys SET + last_run_at=$2, + locked_at=$3, + response_code=$4, + response_body=$5, + recovery_point=$6 +WHERE id = $1 +RETURNING *; \ No newline at end of file diff --git a/db/queries/ride.sql b/db/queries/ride.sql new file mode 100644 index 0000000..a2ab30f --- /dev/null +++ b/db/queries/ride.sql @@ -0,0 +1,27 @@ +-- name: GetRideByID :one +SELECT * FROM rides +WHERE id = $1 LIMIT 1; + +-- name: GetRideByIdempotencyKeyID :one +SELECT * FROM rides +WHERE idempotency_key_id = $1 LIMIT 1; + +-- name: CreateRide :one +INSERT INTO rides( + created_at, + idempotency_key_id, + origin_lat, + origin_lon, + target_lat, + target_lon, + stripe_charge_id, + user_id +) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +RETURNING *; + +-- name: UpdateRide :one +UPDATE rides SET + stripe_charge_id=$2 +WHERE id = $1 +RETURNING *; \ No newline at end of file diff --git a/db/queries/staged_job.sql b/db/queries/staged_job.sql new file mode 100644 index 0000000..9a9bd47 --- /dev/null +++ b/db/queries/staged_job.sql @@ -0,0 +1,7 @@ +-- name: CreateStagedJob :one +INSERT INTO staged_jobs( + job_name, + job_args +) +VALUES ($1, $2) +RETURNING *; \ No newline at end of file diff --git a/db/queries/user.sql b/db/queries/user.sql new file mode 100644 index 0000000..b52f719 --- /dev/null +++ b/db/queries/user.sql @@ -0,0 +1,7 @@ +-- name: GetUserByID :one +SELECT * FROM users +WHERE id = $1 LIMIT 1; + +-- name: GetUserByEmail :one +SELECT * FROM users +WHERE email = $1 LIMIT 1; \ No newline at end of file diff --git a/go.mod b/go.mod index 3e45f76..ec2ee61 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/golang-migrate/migrate/v4 v4.15.1 github.com/golangci/golangci-lint v1.43.0 github.com/labstack/gommon v0.3.0 + github.com/lib/pq v1.10.4 github.com/segmentio/golines v0.7.0 github.com/spf13/viper v1.9.0 github.com/stretchr/testify v1.7.0 @@ -93,7 +94,6 @@ require ( github.com/ldez/gomoddirectives v0.2.2 // indirect github.com/ldez/tagliatelle v0.2.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect - github.com/lib/pq v1.10.4 // indirect github.com/maratori/testpackage v1.0.1 // indirect github.com/matoous/godox v0.0.0-20210227103229-6504466cf951 // indirect github.com/mattn/go-colorable v0.1.11 // indirect @@ -207,7 +207,7 @@ require ( go.opencensus.io v0.23.0 // indirect go.uber.org/atomic v1.9.0 // indirect golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect - golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect + golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect google.golang.org/genproto v0.0.0-20211013025323-ce878158c4d4 // indirect google.golang.org/grpc v1.41.0 // indirect diff --git a/go.sum b/go.sum index de0de00..7fab4ff 100644 --- a/go.sum +++ b/go.sum @@ -1076,6 +1076,7 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+ github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= +github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA= github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= diff --git a/sqlc.yaml b/sqlc.yaml new file mode 100644 index 0000000..68ae8c4 --- /dev/null +++ b/sqlc.yaml @@ -0,0 +1,18 @@ +version: 1 +packages: + - name: "sqlc" + path: "./datastore/sqlc" + engine: "postgresql" + schema: "./db/migrations" + queries: "./db/queries" +overrides: + - column: "audit_records.origin_ip" + go_type: "string" + - column: "rides.origin_lat" + go_type: "float64" + - column: "rides.origin_lon" + go_type: "float64" + - column: "rides.target_lat" + go_type: "float64" + - column: "rides.target_lon" + go_type: "float64"