Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,25 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.58.0
version: v1.55.2
args: --timeout=5m

- 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
Expand Down
10 changes: 3 additions & 7 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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/securecodewarrior/gosec/v2/cmd/gosec@latest
- name: Run gosec
run: gosec -exclude-dir=tests ./...
args: '-exclude-dir=tests ./...'

codeql:
name: CodeQL
Expand Down
2 changes: 0 additions & 2 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ version: "2"
run:
timeout: 5m
tests: true
go: "1.26.2"

linters:
default: none
enable:
- errcheck
- govet
Expand Down
25 changes: 19 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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:
Expand All @@ -52,4 +65,4 @@ dev-setup:
@echo "Setting up development environment..."
docker-compose up -d
sleep 5
make migrate
make migrate-up
58 changes: 56 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 ./...
Expand Down Expand Up @@ -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
Expand Down
78 changes: 60 additions & 18 deletions cmd/migrate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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")
}
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand All @@ -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=
Expand Down Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
Loading
Loading