From fa22a161f07c6a0b81b0034c38b08a5e94c1e86c Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Wed, 29 Apr 2026 02:58:44 +0300 Subject: [PATCH 1/7] chore: update dependencies to newer pseudo-versions and add golang-migrate Update davecgh/go-spew from v1.1.1 to v1.1.2-0.20180830191138-d8f796af33cc and pmezard/go-difflib from v1.0.0 to v1.0.1-0.20181226105442-5d4384ee4fb2 for latest upstream fixes. Upgrade go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp from v0.60.0 to v0.61.0. Add github.com/golang-migrate/migrate/v4 v4.19.1 as indirect dependency for database migration support. --- go.mod | 7 ++++--- go.sum | 8 ++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 89cf26d..1c528b3 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect @@ -41,6 +41,7 @@ require ( github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/golang-migrate/migrate/v4 v4.19.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.10 // indirect @@ -56,7 +57,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/oschwald/maxminddb-golang v1.13.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/shirou/gopsutil/v4 v4.26.3 // indirect github.com/sirupsen/logrus v1.9.4 // indirect @@ -65,7 +66,7 @@ require ( github.com/tklauser/numcpus v0.11.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect diff --git a/go.sum b/go.sum index 546bb4f..9531b95 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,8 @@ github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= @@ -53,6 +55,8 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA= +github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -134,6 +138,8 @@ github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnY github.com/oschwald/maxminddb-golang v1.13.0/go.mod h1:BU0z8BfFVhi1LQaonTwwGQlsHUEu9pWNdMfmq4ztm0o= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= @@ -170,6 +176,8 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= From 638c0dab41f61ed01ba36da6ac3416251d93ada7 Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Wed, 29 Apr 2026 03:05:45 +0300 Subject: [PATCH 2/7] feat: restructure migration files to golang-migrate naming convention with separate up/down files for bidirectional schema changes Rename migration files from sequential numbering (001_*.sql) to golang-migrate timestamp format (20240429000001_*.up.sql) for version ordering. Add corresponding .down.sql files for each migration containing DROP statements in reverse dependency order with trigger/function cleanup before table drops. Maintain original schema definitions in .up.sql files unchanged. --- ...20240429000001_create_users_table.down.sql | 10 +++++++++ ... 20240429000001_create_users_table.up.sql} | 0 ...2_create_wallets_and_transactions.down.sql | 8 +++++++ ...02_create_wallets_and_transactions.up.sql} | 0 ...40429000003_create_betting_tables.down.sql | 10 +++++++++ ...240429000003_create_betting_tables.up.sql} | 0 ...00004_create_mpesa_deposits_table.down.sql | 7 +++++++ ...000004_create_mpesa_deposits_table.up.sql} | 0 ...create_flutterwave_deposits_table.down.sql | 10 +++++++++ ..._create_flutterwave_deposits_table.up.sql} | 0 ...0006_create_sports_betting_tables.down.sql | 20 ++++++++++++++++++ ...00006_create_sports_betting_tables.up.sql} | 0 ...00007_create_communication_tables.down.sql | 21 +++++++++++++++++++ ...000007_create_communication_tables.up.sql} | 0 14 files changed, 86 insertions(+) create mode 100644 migrations/20240429000001_create_users_table.down.sql rename migrations/{001_create_users_table.sql => 20240429000001_create_users_table.up.sql} (100%) create mode 100644 migrations/20240429000002_create_wallets_and_transactions.down.sql rename migrations/{002_create_wallets_and_transactions.sql => 20240429000002_create_wallets_and_transactions.up.sql} (100%) create mode 100644 migrations/20240429000003_create_betting_tables.down.sql rename migrations/{003_create_betting_tables.sql => 20240429000003_create_betting_tables.up.sql} (100%) create mode 100644 migrations/20240429000004_create_mpesa_deposits_table.down.sql rename migrations/{004_create_mpesa_deposits_table.sql => 20240429000004_create_mpesa_deposits_table.up.sql} (100%) create mode 100644 migrations/20240429000005_create_flutterwave_deposits_table.down.sql rename migrations/{005_create_flutterwave_deposits_table.sql => 20240429000005_create_flutterwave_deposits_table.up.sql} (100%) create mode 100644 migrations/20240429000006_create_sports_betting_tables.down.sql rename migrations/{006_create_sports_betting_tables.sql => 20240429000006_create_sports_betting_tables.up.sql} (100%) create mode 100644 migrations/20240429000007_create_communication_tables.down.sql rename migrations/{007_create_communication_tables.sql => 20240429000007_create_communication_tables.up.sql} (100%) diff --git a/migrations/20240429000001_create_users_table.down.sql b/migrations/20240429000001_create_users_table.down.sql new file mode 100644 index 0000000..1942f7a --- /dev/null +++ b/migrations/20240429000001_create_users_table.down.sql @@ -0,0 +1,10 @@ +-- Drop users table + +-- Drop trigger +DROP TRIGGER IF EXISTS update_users_updated_at ON users; + +-- Drop function (only if no other tables use it) +DROP FUNCTION IF EXISTS update_updated_at_column(); + +-- Drop table +DROP TABLE IF EXISTS users; diff --git a/migrations/001_create_users_table.sql b/migrations/20240429000001_create_users_table.up.sql similarity index 100% rename from migrations/001_create_users_table.sql rename to migrations/20240429000001_create_users_table.up.sql diff --git a/migrations/20240429000002_create_wallets_and_transactions.down.sql b/migrations/20240429000002_create_wallets_and_transactions.down.sql new file mode 100644 index 0000000..b369969 --- /dev/null +++ b/migrations/20240429000002_create_wallets_and_transactions.down.sql @@ -0,0 +1,8 @@ +-- Drop wallets and transactions tables + +-- Drop trigger +DROP TRIGGER IF EXISTS update_wallets_updated_at ON wallets; + +-- Drop tables +DROP TABLE IF EXISTS transactions; +DROP TABLE IF EXISTS wallets; diff --git a/migrations/002_create_wallets_and_transactions.sql b/migrations/20240429000002_create_wallets_and_transactions.up.sql similarity index 100% rename from migrations/002_create_wallets_and_transactions.sql rename to migrations/20240429000002_create_wallets_and_transactions.up.sql diff --git a/migrations/20240429000003_create_betting_tables.down.sql b/migrations/20240429000003_create_betting_tables.down.sql new file mode 100644 index 0000000..6dcdd9b --- /dev/null +++ b/migrations/20240429000003_create_betting_tables.down.sql @@ -0,0 +1,10 @@ +-- Drop betting tables in reverse order of creation + +-- Drop tables +DROP TABLE IF EXISTS match_outcomes; +DROP TABLE IF EXISTS match_markets; +DROP TABLE IF EXISTS matches; +DROP TABLE IF EXISTS game_bets; +DROP TABLE IF EXISTS games; +DROP TABLE IF EXISTS bet_selections; +DROP TABLE IF EXISTS bets; diff --git a/migrations/003_create_betting_tables.sql b/migrations/20240429000003_create_betting_tables.up.sql similarity index 100% rename from migrations/003_create_betting_tables.sql rename to migrations/20240429000003_create_betting_tables.up.sql diff --git a/migrations/20240429000004_create_mpesa_deposits_table.down.sql b/migrations/20240429000004_create_mpesa_deposits_table.down.sql new file mode 100644 index 0000000..600c6ad --- /dev/null +++ b/migrations/20240429000004_create_mpesa_deposits_table.down.sql @@ -0,0 +1,7 @@ +-- Drop mpesa deposits table + +-- Drop trigger +DROP TRIGGER IF EXISTS update_mpesa_deposits_updated_at ON mpesa_deposits; + +-- Drop table +DROP TABLE IF EXISTS mpesa_deposits; diff --git a/migrations/004_create_mpesa_deposits_table.sql b/migrations/20240429000004_create_mpesa_deposits_table.up.sql similarity index 100% rename from migrations/004_create_mpesa_deposits_table.sql rename to migrations/20240429000004_create_mpesa_deposits_table.up.sql diff --git a/migrations/20240429000005_create_flutterwave_deposits_table.down.sql b/migrations/20240429000005_create_flutterwave_deposits_table.down.sql new file mode 100644 index 0000000..3ae4cdf --- /dev/null +++ b/migrations/20240429000005_create_flutterwave_deposits_table.down.sql @@ -0,0 +1,10 @@ +-- Drop flutterwave deposits table + +-- Drop trigger +DROP TRIGGER IF EXISTS update_flutterwave_deposits_updated_at ON flutterwave_deposits; + +-- Drop function +DROP FUNCTION IF EXISTS update_flutterwave_deposits_updated_at(); + +-- Drop table +DROP TABLE IF EXISTS flutterwave_deposits; diff --git a/migrations/005_create_flutterwave_deposits_table.sql b/migrations/20240429000005_create_flutterwave_deposits_table.up.sql similarity index 100% rename from migrations/005_create_flutterwave_deposits_table.sql rename to migrations/20240429000005_create_flutterwave_deposits_table.up.sql diff --git a/migrations/20240429000006_create_sports_betting_tables.down.sql b/migrations/20240429000006_create_sports_betting_tables.down.sql new file mode 100644 index 0000000..da95e10 --- /dev/null +++ b/migrations/20240429000006_create_sports_betting_tables.down.sql @@ -0,0 +1,20 @@ +-- Drop sports betting tables in reverse order of creation + +-- Drop triggers first +DROP TRIGGER IF EXISTS update_sport_bets_updated_at ON sport_bets; +DROP TRIGGER IF EXISTS update_market_outcomes_updated_at ON market_outcomes; +DROP TRIGGER IF EXISTS update_betting_markets_updated_at ON betting_markets; +DROP TRIGGER IF EXISTS update_sport_events_updated_at ON sport_events; + +-- Drop functions +DROP FUNCTION IF EXISTS update_sport_bets_updated_at(); +DROP FUNCTION IF EXISTS update_market_outcomes_updated_at(); +DROP FUNCTION IF EXISTS update_betting_markets_updated_at(); +DROP FUNCTION IF EXISTS update_sport_events_updated_at(); + +-- Drop tables +DROP TABLE IF EXISTS odds_history; +DROP TABLE IF EXISTS sport_bets; +DROP TABLE IF EXISTS market_outcomes; +DROP TABLE IF EXISTS betting_markets; +DROP TABLE IF EXISTS sport_events; diff --git a/migrations/006_create_sports_betting_tables.sql b/migrations/20240429000006_create_sports_betting_tables.up.sql similarity index 100% rename from migrations/006_create_sports_betting_tables.sql rename to migrations/20240429000006_create_sports_betting_tables.up.sql diff --git a/migrations/20240429000007_create_communication_tables.down.sql b/migrations/20240429000007_create_communication_tables.down.sql new file mode 100644 index 0000000..7085b98 --- /dev/null +++ b/migrations/20240429000007_create_communication_tables.down.sql @@ -0,0 +1,21 @@ +-- Drop communication tables in reverse order of creation + +-- Drop triggers first +DROP TRIGGER IF EXISTS update_communication_templates_updated_at ON communication_templates; +DROP TRIGGER IF EXISTS update_communication_preferences_updated_at ON communication_preferences; +DROP TRIGGER IF EXISTS update_ussd_logs_updated_at ON ussd_logs; +DROP TRIGGER IF EXISTS update_sms_logs_updated_at ON sms_logs; + +-- Drop functions +DROP FUNCTION IF EXISTS update_communication_templates_updated_at(); +DROP FUNCTION IF EXISTS update_communication_preferences_updated_at(); +DROP FUNCTION IF EXISTS update_ussd_logs_updated_at(); +DROP FUNCTION IF EXISTS update_sms_logs_updated_at(); + +-- Drop tables +DROP TABLE IF EXISTS communication_templates; +DROP TABLE IF EXISTS communication_preferences; +DROP TABLE IF EXISTS voice_logs; +DROP TABLE IF EXISTS ussd_logs; +DROP TABLE IF EXISTS otp_logs; +DROP TABLE IF EXISTS sms_logs; diff --git a/migrations/007_create_communication_tables.sql b/migrations/20240429000007_create_communication_tables.up.sql similarity index 100% rename from migrations/007_create_communication_tables.sql rename to migrations/20240429000007_create_communication_tables.up.sql From e9f9e8833126d03880286ec99fc8d8c859d9a15b Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Wed, 29 Apr 2026 03:07:40 +0300 Subject: [PATCH 3/7] refactor: replace custom migration logic with golang-migrate library for bidirectional migration support and improved version control Remove custom Migrate function and schema_migrations table management from database/migrate.go. Replace with golang-migrate/migrate/v4 library in cmd/migrate/main.go supporting up/down/version actions with optional step count. Add database URL construction from config and migrate instance creation with file source. Implement action switch handling Up/Down/Steps/Version operations --- cmd/migrate/main.go | 78 +++++++++++++---- internal/infrastructure/database/migrate.go | 96 --------------------- 2 files changed, 60 insertions(+), 114 deletions(-) delete mode 100644 internal/infrastructure/database/migrate.go diff --git a/cmd/migrate/main.go b/cmd/migrate/main.go index e5a055d..abccf32 100644 --- a/cmd/migrate/main.go +++ b/cmd/migrate/main.go @@ -2,16 +2,21 @@ package main import ( "flag" + "fmt" "log/slog" "os" "github.com/betting-platform/internal/infrastructure/config" - "github.com/betting-platform/internal/infrastructure/database" "github.com/betting-platform/internal/infrastructure/logging" + "github.com/golang-migrate/migrate/v4" + _ "github.com/golang-migrate/migrate/v4/database/postgres" + _ "github.com/golang-migrate/migrate/v4/source/file" ) func main() { dir := flag.String("dir", "./migrations", "migrations directory") + action := flag.String("action", "up", "migration action: up, down, version") + steps := flag.Int("steps", 0, "number of steps to migrate (0 = all)") flag.Parse() cfg, err := config.LoadConfig() @@ -22,27 +27,64 @@ func main() { logger := logging.Setup(cfg.Logging.Level, cfg.Logging.Format) - db, err := database.NewPostgresConnection(database.Config{ - Host: cfg.Database.Host, - Port: cfg.Database.Port, - User: cfg.Database.User, - Password: cfg.Database.Password, - DBName: cfg.Database.Name, - SSLMode: cfg.Database.SSLMode, - MaxOpenConns: cfg.Database.MaxOpenConns, - MaxIdleConns: cfg.Database.MaxIdleConns, - ConnMaxLifetime: cfg.Database.ConnMaxLifetime, - }) + // Build database URL + dbURL := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s", + cfg.Database.User, + cfg.Database.Password, + cfg.Database.Host, + cfg.Database.Port, + cfg.Database.Name, + cfg.Database.SSLMode, + ) + + // Create migrate instance + m, err := migrate.New( + "file://"+*dir, + dbURL, + ) if err != nil { - logger.Error("failed to connect to db", "error", err) + logger.Error("failed to create migrate instance", "error", err) os.Exit(1) } - defer db.Close() + defer m.Close() - if err := database.Migrate(db, *dir, logger); err != nil { - logger.Error("migration failed", "error", err) + // Execute migration action + switch *action { + case "up": + if *steps > 0 { + if err := m.Steps(*steps); err != nil && err != migrate.ErrNoChange { + logger.Error("migration failed", "error", err) + os.Exit(1) + } + } else { + if err := m.Up(); err != nil && err != migrate.ErrNoChange { + logger.Error("migration failed", "error", err) + os.Exit(1) + } + } + logger.Info("migrations applied successfully") + case "down": + if *steps > 0 { + if err := m.Steps(-*steps); err != nil && err != migrate.ErrNoChange { + logger.Error("migration failed", "error", err) + os.Exit(1) + } + } else { + if err := m.Down(); err != nil && err != migrate.ErrNoChange { + logger.Error("migration failed", "error", err) + os.Exit(1) + } + } + logger.Info("migrations rolled back successfully") + case "version": + version, dirty, err := m.Version() + if err != nil { + logger.Error("failed to get version", "error", err) + os.Exit(1) + } + logger.Info("current migration version", "version", version, "dirty", dirty) + default: + logger.Error("invalid action", "action", *action) os.Exit(1) } - - logger.Info("migrations applied successfully") } diff --git a/internal/infrastructure/database/migrate.go b/internal/infrastructure/database/migrate.go deleted file mode 100644 index 9542a0b..0000000 --- a/internal/infrastructure/database/migrate.go +++ /dev/null @@ -1,96 +0,0 @@ -package database - -import ( - "database/sql" - "fmt" - "log/slog" - "os" - "path/filepath" - "sort" - "strings" -) - -// Migrate runs all SQL migrations in the given directory that have not yet been applied. -// A schema_migrations table tracks which files have been applied. -func Migrate(db *sql.DB, migrationsDir string, logger *slog.Logger) error { - if logger == nil { - logger = slog.Default() - } - - if _, err := db.Exec(` - CREATE TABLE IF NOT EXISTS schema_migrations ( - version TEXT PRIMARY KEY, - applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) - `); err != nil { - return fmt.Errorf("create schema_migrations table: %w", err) - } - - applied, err := loadApplied(db) - if err != nil { - return err - } - - entries, err := os.ReadDir(migrationsDir) - if err != nil { - return fmt.Errorf("read migrations dir: %w", err) - } - - var files []string - for _, e := range entries { - if !e.IsDir() && strings.HasSuffix(e.Name(), ".sql") { - files = append(files, e.Name()) - } - } - sort.Strings(files) - - for _, name := range files { - if applied[name] { - continue - } - path := filepath.Join(migrationsDir, name) - content, err := os.ReadFile(path) - if err != nil { - return fmt.Errorf("read %s: %w", name, err) - } - - logger.Info("applying migration", "file", name) - - tx, err := db.Begin() - if err != nil { - return fmt.Errorf("begin tx for %s: %w", name, err) - } - if _, err := tx.Exec(string(content)); err != nil { - _ = tx.Rollback() - return fmt.Errorf("apply %s: %w", name, err) - } - if _, err := tx.Exec("INSERT INTO schema_migrations (version) VALUES ($1)", name); err != nil { - _ = tx.Rollback() - return fmt.Errorf("record %s: %w", name, err) - } - if err := tx.Commit(); err != nil { - return fmt.Errorf("commit %s: %w", name, err) - } - } - - logger.Info("migrations complete", "files_checked", len(files)) - return nil -} - -func loadApplied(db *sql.DB) (map[string]bool, error) { - rows, err := db.Query("SELECT version FROM schema_migrations") - if err != nil { - return nil, fmt.Errorf("load applied migrations: %w", err) - } - defer rows.Close() - - applied := make(map[string]bool) - for rows.Next() { - var v string - if err := rows.Scan(&v); err != nil { - return nil, err - } - applied[v] = true - } - return applied, rows.Err() -} From e2fa7e7d73d24f7aef9fa29b83e2162eaaef57ad Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Wed, 29 Apr 2026 03:08:27 +0300 Subject: [PATCH 4/7] feat: add separate migration commands for up/down/version operations with step control for bidirectional schema management Replace single migrate target with migrate-up/migrate-down/migrate-version targets supporting golang-migrate actions. Add migrate-down with -steps 1 flag for single-step rollback and migrate-version for current schema version display. Update .PHONY declarations and dev-setup to use migrate-up. Retain legacy migrate target as alias to migrate-up for backward compatibility. --- Makefile | 25 ++++++++++++++++++------ README.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index b602667..5985d73 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build run-all test lint vet migrate clean docker-build tidy +.PHONY: build run-all test lint vet migrate-up migrate-down migrate-version clean docker-build tidy # Build all services build: @@ -25,10 +25,23 @@ run-all: build docker-compose up -d postgres redis nats ./bin/gateway & ./bin/wallet & ./bin/engine & ./bin/settlement & ./bin/games & -# Run database migrations -migrate: - @echo "Running database migrations..." - go run ./cmd/migrate -dir ./migrations +# Run database migrations (up) +migrate-up: + @echo "Running database migrations (up)..." + ./bin/migrate -dir ./migrations -action up + +# Rollback database migrations (down) +migrate-down: + @echo "Rolling back database migrations (down)..." + ./bin/migrate -dir ./migrations -action down -steps 1 + +# Get current migration version +migrate-version: + @echo "Getting migration version..." + ./bin/migrate -dir ./migrations -action version + +# Legacy migrate target (deprecated, use migrate-up) +migrate: migrate-up # Run tests test: @@ -52,4 +65,4 @@ dev-setup: @echo "Setting up development environment..." docker-compose up -d sleep 5 - make migrate + make migrate-up diff --git a/README.md b/README.md index 4446ea4..c285a78 100644 --- a/README.md +++ b/README.md @@ -132,8 +132,11 @@ cd lets-bet # Start infrastructure (Postgres, Redis, NATS) docker-compose up -d -# Run database migrations -go run cmd/migrate/main.go up +# Run database migrations (up) +./bin/migrate -dir ./migrations -action up + +# Or use Makefile +make migrate-up # Build all services go build ./... @@ -257,6 +260,57 @@ payoutBreak := taxEngine.ApplyPayoutTax("KE", grossPayout, stake) --- +## Database Migrations + +This project uses [golang-migrate](https://github.com/golang-migrate/migrate) for database schema management. + +### Migration Format +Migrations follow the golang-migrate naming convention: +- Up migrations: `YYYYMMDDHHMMSS_description.up.sql` +- Down migrations: `YYYYMMDDHHMMSS_description.down.sql` + +### Running Migrations + +```bash +# Apply all pending migrations +./bin/migrate -dir ./migrations -action up + +# Rollback one migration +./bin/migrate -dir ./migrations -action down -steps 1 + +# Check current migration version +./bin/migrate -dir ./migrations -action version + +# Apply specific number of migrations +./bin/migrate -dir ./migrations -action up -steps 2 +``` + +### Using Makefile + +```bash +make migrate-up # Apply all pending migrations +make migrate-down # Rollback one migration +make migrate-version # Check current version +``` + +### Using golang-migrate CLI + +```bash +# Install golang-migrate CLI +go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest + +# Run migrations directly +migrate -path ./migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" up +``` + +### Creating New Migrations + +1. Create up migration: `YYYYMMDDHHMMSS_new_feature.up.sql` +2. Create down migration: `YYYYMMDDHHMMSS_new_feature.down.sql` +3. Place both files in `./migrations/` directory + +--- + ## Database Schema Highlights ### Atomic Wallet Operations From 363a7a22b3b11dbd0c485e148ebff6617fdf6edc Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Wed, 29 Apr 2026 03:08:39 +0300 Subject: [PATCH 5/7] feat: add migration execution step to CI pipeline before test run to ensure database schema is up-to-date Add migrate tool build step and database migration execution to CI workflow before running tests. Build migrate binary to bin/migrate from cmd/migrate and execute migrations with up action using test database credentials (localhost:5432/betting_test). Configure DATABASE_* environment variables for migration connection and set migrations directory to ./migrations. --- .github/workflows/ci.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc9f96b..53686b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,6 +67,19 @@ jobs: - name: Build run: go build ./... + - name: Build migrate tool + run: go build -o bin/migrate ./cmd/migrate + + - name: Run database migrations + env: + DATABASE_HOST: localhost + DATABASE_PORT: "5432" + DATABASE_USER: postgres + DATABASE_PASSWORD: postgres + DATABASE_NAME: betting_test + DATABASE_SSL_MODE: disable + run: ./bin/migrate -dir ./migrations -action up + - name: Test env: DATABASE_HOST: localhost From 8d9dc7b54b3f61e904ee0cc902d68060eba1b67a Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Wed, 29 Apr 2026 03:14:55 +0300 Subject: [PATCH 6/7] chore: downgrade golangci-lint to v1.55.2, fix gosec package path, and remove golangci-lint v2 config options Downgrade golangci-lint from v1.58.0 to v1.55.2 in CI workflow for compatibility. Correct gosec installation path from securecodewarrior/gosec to securego/gosec in security workflow. Remove go version specification and default: none directive from .golangci.yml as these are not supported in golangci-lint v1 configuration format. --- .github/workflows/ci.yml | 2 +- .github/workflows/security.yml | 2 +- .golangci.yml | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53686b1..a7e8374 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,7 +61,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: - version: v1.58.0 + version: v1.55.2 args: --timeout=5m - name: Build diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 68930aa..f3d314b 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -37,7 +37,7 @@ jobs: with: go-version: '1.26.2' - name: Install gosec - run: go install github.com/securecodewarrior/gosec/v2/cmd/gosec@latest + run: go install github.com/securego/gosec/v2/cmd/gosec@latest - name: Run gosec run: gosec -exclude-dir=tests ./... diff --git a/.golangci.yml b/.golangci.yml index 0dfe717..5e6835b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,10 +3,8 @@ version: "2" run: timeout: 5m tests: true - go: "1.26.2" linters: - default: none enable: - errcheck - govet From f2e01058ec0652e601dcae9913cd36f9c6bb411b Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Wed, 29 Apr 2026 03:15:18 +0300 Subject: [PATCH 7/7] chore: simplify gosec security scan by using official GitHub Action instead of manual installation Replace manual Go setup and gosec installation steps with securego/gosec@master GitHub Action. Pass exclude-dir argument directly to action with args parameter to maintain tests directory exclusion while reducing workflow complexity. --- .github/workflows/security.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index f3d314b..5ce8472 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -32,14 +32,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up Go - uses: actions/setup-go@v5 + - name: Run Gosec Security Scanner + uses: securego/gosec@master with: - go-version: '1.26.2' - - name: Install gosec - run: go install github.com/securego/gosec/v2/cmd/gosec@latest - - name: Run gosec - run: gosec -exclude-dir=tests ./... + args: '-exclude-dir=tests ./...' codeql: name: CodeQL