mirror of
https://github.com/R0m1k3/FlowReader.git
synced 2026-10-11 17:28:05 +02:00
fix: refactor migration system to be incremental and fix error 500 on articles
This commit is contained in:
1 parent
5b219cf0e7
commit
187b6c8b86
2 files changed
+64
-16
No files matched your search
@@ -11,6 +11,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- PORT=8080
|
- PORT=8080
|
||||||
- DATABASE_URL=postgres://flowreader:flowreader@db:5432/flowreader?sslmode=disable
|
- DATABASE_URL=postgres://flowreader:flowreader@db:5432/flowreader?sslmode=disable
|
||||||
|
- OPENROUTER_API_KEY=votre_clef_ici
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|||||||
@@ -42,27 +42,54 @@ func Connect(ctx context.Context, databaseURL string) (*pgxpool.Pool, error) {
|
|||||||
|
|
||||||
// RunMigrations executes pending database migrations.
|
// RunMigrations executes pending database migrations.
|
||||||
func RunMigrations(ctx context.Context, pool *pgxpool.Pool) error {
|
func RunMigrations(ctx context.Context, pool *pgxpool.Pool) error {
|
||||||
// Check if users table exists
|
// 1. Create schema_migrations table if it doesn't exist
|
||||||
var exists bool
|
_, err := pool.Exec(ctx, `
|
||||||
err := pool.QueryRow(ctx, `
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||||
SELECT EXISTS (
|
version VARCHAR(255) PRIMARY KEY,
|
||||||
SELECT FROM information_schema.tables
|
applied_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
WHERE table_schema = 'public'
|
|
||||||
AND table_name = 'users'
|
|
||||||
)
|
)
|
||||||
`).Scan(&exists)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("checking users table: %w", err)
|
return fmt.Errorf("creating schema_migrations table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if exists {
|
// 2. Get applied migrations
|
||||||
log.Println("Database already initialized")
|
rows, err := pool.Query(ctx, "SELECT version FROM schema_migrations")
|
||||||
return nil
|
if err != nil {
|
||||||
|
return fmt.Errorf("querying applied migrations: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
applied := make(map[string]bool)
|
||||||
|
for rows.Next() {
|
||||||
|
var version string
|
||||||
|
if err := rows.Scan(&version); err != nil {
|
||||||
|
return fmt.Errorf("scanning migration version: %w", err)
|
||||||
|
}
|
||||||
|
applied[version] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Initializing database and applying migrations...")
|
// 3. Special case for legacy system:
|
||||||
|
// If 'users' table exists but 'schema_migrations' is empty,
|
||||||
|
// mark all migrations up to 005 as applied to avoid duplicate creation errors.
|
||||||
|
if len(applied) == 0 {
|
||||||
|
var usersExists bool
|
||||||
|
pool.QueryRow(ctx, "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'users')").Scan(&usersExists)
|
||||||
|
if usersExists {
|
||||||
|
log.Println("Legacy database detected, bootstrapping migration tracking...")
|
||||||
|
// We assume 001 to 005 are already there if users table exists in the old system
|
||||||
|
legacyVersions := []string{"001_create_users.up.sql", "002_create_sessions.up.sql", "003_create_feeds.up.sql", "004_add_user_roles.up.sql", "005_search_articles.up.sql"}
|
||||||
|
for _, v := range legacyVersions {
|
||||||
|
_, err := pool.Exec(ctx, "INSERT INTO schema_migrations (version) VALUES ($1)", v)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Warning: failed to bootstrap version %s: %v", v, err)
|
||||||
|
}
|
||||||
|
applied[v] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Read migration files
|
// 4. Read migration files
|
||||||
entries, err := os.ReadDir("./migrations")
|
entries, err := os.ReadDir("./migrations")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("reading migrations dir: %w", err)
|
return fmt.Errorf("reading migrations dir: %w", err)
|
||||||
@@ -76,18 +103,38 @@ func RunMigrations(ctx context.Context, pool *pgxpool.Pool) error {
|
|||||||
}
|
}
|
||||||
sort.Strings(upFiles)
|
sort.Strings(upFiles)
|
||||||
|
|
||||||
|
// 5. Apply pending migrations
|
||||||
for _, file := range upFiles {
|
for _, file := range upFiles {
|
||||||
|
if applied[file] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("Applying migration: %s", file)
|
log.Printf("Applying migration: %s", file)
|
||||||
content, err := os.ReadFile(filepath.Join("./migrations", file))
|
content, err := os.ReadFile(filepath.Join("./migrations", file))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("reading migration %s: %w", file, err)
|
return fmt.Errorf("reading migration %s: %w", file, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := pool.Exec(ctx, string(content)); err != nil {
|
// Execute in transaction
|
||||||
|
tx, err := pool.Begin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("starting transaction for %s: %w", file, err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback(ctx)
|
||||||
|
|
||||||
|
if _, err := tx.Exec(ctx, string(content)); err != nil {
|
||||||
return fmt.Errorf("executing migration %s: %w", file, err)
|
return fmt.Errorf("executing migration %s: %w", file, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := tx.Exec(ctx, "INSERT INTO schema_migrations (version) VALUES ($1)", file); err != nil {
|
||||||
|
return fmt.Errorf("recording migration %s: %w", file, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(ctx); err != nil {
|
||||||
|
return fmt.Errorf("committing migration %s: %w", file, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("All migrations applied successfully")
|
log.Println("Database schema is up to date")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user