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.
This commit is contained in:
57
db/db.go
57
db/db.go
@@ -1,33 +1,70 @@
|
||||
// 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 migrations embed.FS
|
||||
var MigrationFS embed.FS
|
||||
|
||||
var DB *sql.DB
|
||||
|
||||
func Init(dbPath string) error {
|
||||
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 = sql.Open("sqlite", dbPath+"?_journal_mode=WAL&_busy_timeout=5000")
|
||||
DB, err = goose.OpenDBWithDriver("sqlite", dbPath+pragmas)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, fmt.Errorf("opening database: %w", err)
|
||||
}
|
||||
DB.SetMaxOpenConns(1)
|
||||
|
||||
goose.SetBaseFS(migrations)
|
||||
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 err
|
||||
return nil, errors.Join(fmt.Errorf("setting goose dialect: %w", err), DB.Close())
|
||||
}
|
||||
if err := goose.Up(DB, "migrations"); err != nil {
|
||||
return err
|
||||
if err := goose.Up(DB, "."); err != nil {
|
||||
return nil, errors.Join(fmt.Errorf("running migrations: %w", err), DB.Close())
|
||||
}
|
||||
|
||||
return nil
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user