feat(backend): migrations Postgres testées (users, devices, resources, operations, ocr_jobs) + migrate au boot
- golang-migrate embarqué via embed (db/migrations/*.sql), MigrateDatabase(url) au boot
- ids TEXT 32-hex avec CHECK ~ '^[0-9a-f]{32}$' ; operations idempotentes UNIQUE(device_id, operation_id)
- harness db/migrations_test.go (up → assertions schéma → down, skip si PG indisponible)
- DB dev fraîche vaultdrop_dev (la DB vaultdrop héberge un prototype V2 abandonné)
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
migratepostgres "github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
"github.com/golang-migrate/migrate/v4/source/iofs"
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var migrationsFS embed.FS
|
||||
|
||||
// Open opens a raw *sql.DB (Postgres). Callers must Close it.
|
||||
func Open(databaseURL string) (*sql.DB, error) {
|
||||
conn, err := sql.Open("postgres", databaseURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open database: %w", err)
|
||||
}
|
||||
if err := conn.Ping(); err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("ping database: %w", err)
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func newMigrator(conn *sql.DB) (*migrate.Migrate, error) {
|
||||
source, err := iofs.New(migrationsFS, "migrations")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load embedded migrations: %w", err)
|
||||
}
|
||||
driver, err := migratepostgres.WithInstance(conn, &migratepostgres.Config{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init postgres migrate driver: %w", err)
|
||||
}
|
||||
m, err := migrate.NewWithInstance("iofs", source, "postgres", driver)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new migrate: %w", err)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// MigrateDatabase applies all pending migrations up to the latest version.
|
||||
// migrate.ErrNoChange (already up-to-date) is not an error.
|
||||
func MigrateDatabase(databaseURL string) error {
|
||||
conn, err := Open(databaseURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
m, err := newMigrator(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MigrateDownDatabase rolls back every migration (used by tests).
|
||||
func MigrateDownDatabase(databaseURL string) error {
|
||||
conn, err := Open(databaseURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
m, err := newMigrator(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := m.Down(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user