- Add worker storage layer with PostgreSQL/sqlc setup - Add migration files for worker backbone schema - Add job runner and built-in jobs implementation - Add Redis key definitions for worker state management - Archive socket-session-loop milestone (completed/renamed) - Update roadmap current.md and foundation-alignment phase - Add socket endpoint integration and runtime smoke tests - Add infra-check, worker-storage-check, worker-storage-gen CLI tools - Update API socket server and config - Add pubspec dependencies and client socket endpoint changes - Add config tests for API and worker services
99 lines
2.6 KiB
Go
99 lines
2.6 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"embed"
|
|
"fmt"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
//go:embed migrations/*.sql
|
|
var migrationsFS embed.FS
|
|
|
|
// RunMigrations runs all embedded migrations in order.
|
|
func RunMigrations(ctx context.Context, conn *pgx.Conn) error {
|
|
// Create schema_migrations table if not exists
|
|
_, err := conn.Exec(ctx, `
|
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
version BIGINT PRIMARY KEY,
|
|
applied_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
`)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create schema_migrations table: %w", err)
|
|
}
|
|
|
|
entries, err := migrationsFS.ReadDir("migrations")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read migrations directory: %w", err)
|
|
}
|
|
|
|
var migrationFiles []string
|
|
for _, entry := range entries {
|
|
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".up.sql") {
|
|
migrationFiles = append(migrationFiles, entry.Name())
|
|
}
|
|
}
|
|
sort.Strings(migrationFiles)
|
|
|
|
if len(migrationFiles) == 0 {
|
|
return fmt.Errorf("no up migration files found")
|
|
}
|
|
|
|
for _, filename := range migrationFiles {
|
|
parts := strings.Split(filename, "_")
|
|
if len(parts) == 0 {
|
|
return fmt.Errorf("invalid migration filename: %s", filename)
|
|
}
|
|
version, err := strconv.ParseInt(parts[0], 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse migration version from filename %s: %w", filename, err)
|
|
}
|
|
|
|
// Check if version is already applied
|
|
var exists bool
|
|
err = conn.QueryRow(ctx, "SELECT EXISTS(SELECT 1 FROM schema_migrations WHERE version = $1)", version).Scan(&exists)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check migration status for version %d: %w", version, err)
|
|
}
|
|
|
|
if exists {
|
|
continue
|
|
}
|
|
|
|
// Read migration content
|
|
content, err := migrationsFS.ReadFile(filepath.Join("migrations", filename))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read migration file %s: %w", filename, err)
|
|
}
|
|
|
|
// Run migration within transaction
|
|
tx, err := conn.Begin(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to begin transaction for migration %s: %w", filename, err)
|
|
}
|
|
defer tx.Rollback(ctx)
|
|
|
|
_, err = tx.Exec(ctx, string(content))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to execute migration %s: %w", filename, err)
|
|
}
|
|
|
|
_, err = tx.Exec(ctx, "INSERT INTO schema_migrations (version) VALUES ($1)", version)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to record migration version %d: %w", version, err)
|
|
}
|
|
|
|
err = tx.Commit(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to commit transaction for migration %s: %w", filename, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|