Files
games/db/db.go
Ryan Hamamura 2df20c2840 refactor: adopt portigo infrastructure patterns
Add config package with build-tag-switched dev/prod environments,
structured logging via zerolog, Taskfile for dev workflow, golangci-lint
config, testutil package, and improved DB setup with proper SQLite
pragmas and cleanup. Rename sqlc output package from gen to repository.

Switch to allowlist .gitignore, Alpine+UPX+scratch Dockerfile, and
CI pipeline with test/lint gates before deploy.
2026-03-02 11:48:47 -10:00

71 lines
2.0 KiB
Go

// Package db handles SQLite database setup, pragma configuration, and
// goose migrations.
package db
import (
"database/sql"
"embed"
"errors"
"fmt"
"io/fs"
"log/slog"
"os"
"path/filepath"
"github.com/pressly/goose/v3"
_ "modernc.org/sqlite"
)
//go:embed migrations/*.sql
var MigrationFS embed.FS
var DB *sql.DB
func Init(dbPath string) (func(), error) {
if err := os.MkdirAll(filepath.Dir(dbPath), 0o755); err != nil {
return nil, fmt.Errorf("creating data dir: %w", err)
}
// busy_timeout must be first because the connection needs to block on
// busy before WAL mode is set in case it hasn't been set already.
pragmas := "?_pragma=busy_timeout(10000)&_pragma=journal_mode(WAL)&_pragma=journal_size_limit(200000000)&_pragma=synchronous(NORMAL)&_pragma=foreign_keys(ON)&_pragma=temp_store(MEMORY)&_pragma=cache_size(-32000)"
var err error
DB, err = goose.OpenDBWithDriver("sqlite", dbPath+pragmas)
if err != nil {
return nil, fmt.Errorf("opening database: %w", err)
}
if err := DB.Ping(); err != nil {
return nil, errors.Join(fmt.Errorf("pinging database: %w", err), DB.Close())
}
slog.Info("db connected", "db", dbPath)
sub, err := fs.Sub(MigrationFS, "migrations")
if err != nil {
return nil, errors.Join(fmt.Errorf("migrations sub fs: %w", err), DB.Close())
}
goose.SetBaseFS(sub)
if err := goose.SetDialect("sqlite3"); err != nil {
return nil, errors.Join(fmt.Errorf("setting goose dialect: %w", err), DB.Close())
}
if err := goose.Up(DB, "."); err != nil {
return nil, errors.Join(fmt.Errorf("running migrations: %w", err), DB.Close())
}
if _, err := DB.Exec("PRAGMA optimize"); err != nil {
return nil, errors.Join(fmt.Errorf("pragma optimize: %w", err), DB.Close())
}
cleanup := func() {
if _, err := DB.Exec("PRAGMA optimize(0x10002)"); err != nil {
slog.Error("pragma optimize at shutdown", "error", err)
}
if err := DB.Close(); err != nil {
slog.Error("closing database", "error", err)
}
}
return cleanup, nil
}