fix: harden playback and migrations

This commit is contained in:
2026-04-19 21:05:47 +02:00
parent f753501761
commit b24053864c
24 changed files with 2263 additions and 1419 deletions

View File

@@ -2,13 +2,19 @@ package database
import (
"database/sql"
"fmt"
"log"
"os"
"path/filepath"
"sort"
"strings"
)
func RunMigrations(db *sql.DB) error {
func RunMigrations(db *sql.DB, migrationsDir string) error {
if migrationsDir == "" {
return fmt.Errorf("migrations directory is required")
}
// Create migration tracking table
_, err := db.Exec(`
CREATE TABLE IF NOT EXISTS migration_version (
@@ -20,21 +26,24 @@ func RunMigrations(db *sql.DB) error {
return err
}
migrations, err := filepath.Glob("migrations/*.sql")
migrations, err := filepath.Glob(filepath.Join(migrationsDir, "*.sql"))
if err != nil {
return err
}
if len(migrations) == 0 {
return fmt.Errorf("no migration files found in %s", migrationsDir)
}
sort.Strings(migrations)
appliedNames, err := loadAppliedMigrationNames(db)
if err != nil {
return err
}
for _, migrationFile := range migrations {
// Check if migration already applied
var exists int
err := db.QueryRow("SELECT COUNT(*) FROM migration_version WHERE name = ?", migrationFile).Scan(&exists)
if err != nil {
return err
}
if exists > 0 {
migrationName := filepath.Base(migrationFile)
if migrationApplied(appliedNames, migrationName) {
// already applied, skipping silently
continue
}
@@ -51,13 +60,58 @@ func RunMigrations(db *sql.DB) error {
}
// Mark as applied
_, err = db.Exec("INSERT INTO migration_version (name) VALUES (?)", migrationFile)
_, err = db.Exec("INSERT INTO migration_version (name) VALUES (?)", migrationName)
if err != nil {
return err
}
log.Printf("migration %s applied successfully", migrationFile)
appliedNames[migrationName] = struct{}{}
log.Printf("migration %s applied successfully", migrationName)
}
return nil
}
func loadAppliedMigrationNames(db *sql.DB) (map[string]struct{}, error) {
rows, err := db.Query("SELECT name FROM migration_version")
if err != nil {
return nil, err
}
defer rows.Close()
applied := make(map[string]struct{})
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
return nil, err
}
applied[name] = struct{}{}
}
if err := rows.Err(); err != nil {
return nil, err
}
return applied, nil
}
func migrationApplied(appliedNames map[string]struct{}, migrationName string) bool {
if _, exists := appliedNames[migrationName]; exists {
return true
}
legacyName := filepath.ToSlash(filepath.Join("migrations", migrationName))
if _, exists := appliedNames[legacyName]; exists {
return true
}
for appliedName := range appliedNames {
if strings.EqualFold(filepath.Base(appliedName), migrationName) {
return true
}
}
return false
}