Adiciona mecanismo de expiração periódica para jobs que ficam presos nos status received ou processing além de um threshold configurável. - models: novo status StatusTimeout - store: migration v2 com índice em jobs(status); método ExpireStuck que faz UPDATE atômico para os status alvo anteriores ao threshold - expirer: novo pacote com goroutine periódica que chama ExpireStuck e loga WARN quando algum job é expirado - config: campos JobTimeoutMinutes (default 120) e ExpirerIntervalMinutes (default 10), lidos via env vars JOB_TIMEOUT_MINUTES e EXPIRER_INTERVAL_MINUTES - main: instancia e inicia o Expirer no startup - dashboard: card Timeout com contagem; badge neutral na tabela de jobs
86 lines
2.4 KiB
Go
86 lines
2.4 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
)
|
|
|
|
func migrate(ctx context.Context, db *sql.DB) error {
|
|
_, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS schema_migrations (version INTEGER PRIMARY KEY)`)
|
|
if err != nil {
|
|
return fmt.Errorf("create schema_migrations: %w", err)
|
|
}
|
|
|
|
tx, err := db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("begin migration tx: %w", err)
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
var currentVersion int
|
|
err = tx.QueryRowContext(ctx, `SELECT COALESCE(MAX(version), 0) FROM schema_migrations`).Scan(¤tVersion)
|
|
if err != nil {
|
|
return fmt.Errorf("read current migration version: %w", err)
|
|
}
|
|
|
|
migrations := []struct {
|
|
version int
|
|
sql []string
|
|
}{
|
|
{
|
|
version: 1,
|
|
sql: []string{
|
|
`CREATE TABLE IF NOT EXISTS jobs (
|
|
id TEXT PRIMARY KEY,
|
|
artist TEXT NOT NULL DEFAULT '',
|
|
album TEXT NOT NULL DEFAULT '',
|
|
path TEXT NOT NULL DEFAULT '',
|
|
status TEXT NOT NULL DEFAULT 'received',
|
|
source TEXT NOT NULL DEFAULT 'manual',
|
|
created_at DATETIME NOT NULL DEFAULT (datetime('now')),
|
|
finished_at DATETIME,
|
|
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
error_msg TEXT NOT NULL DEFAULT ''
|
|
)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_jobs_created_at ON jobs(created_at)`,
|
|
`CREATE TABLE IF NOT EXISTS logs (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
job_id TEXT NOT NULL DEFAULT '',
|
|
level TEXT NOT NULL DEFAULT 'INFO',
|
|
message TEXT NOT NULL DEFAULT '',
|
|
timestamp DATETIME NOT NULL DEFAULT (datetime('now'))
|
|
)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_logs_timestamp ON logs(timestamp)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_logs_job_id ON logs(job_id)`,
|
|
`CREATE TABLE IF NOT EXISTS config (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL DEFAULT ''
|
|
)`,
|
|
},
|
|
},
|
|
{
|
|
version: 2,
|
|
sql: []string{
|
|
`CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status)`,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, m := range migrations {
|
|
if m.version <= currentVersion {
|
|
continue
|
|
}
|
|
for _, stmt := range m.sql {
|
|
if _, err := tx.ExecContext(ctx, stmt); err != nil {
|
|
return fmt.Errorf("migration %d: %w", m.version, err)
|
|
}
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `INSERT INTO schema_migrations (version) VALUES (?)`, m.version); err != nil {
|
|
return fmt.Errorf("record migration %d: %w", m.version, err)
|
|
}
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|