package main import ( "database/sql" "fmt" "os" "path/filepath" _ "modernc.org/sqlite" ) func initDB(cfg Config) (*sql.DB, error) { if err := os.MkdirAll(filepath.Dir(cfg.DBPath), 0o755); err != nil { return nil, fmt.Errorf("create data dir: %w", err) } db, err := sql.Open("sqlite", cfg.DBPath) if err != nil { return nil, fmt.Errorf("open sqlite: %w", err) } db.SetMaxOpenConns(1) db.SetMaxIdleConns(1) if _, err := db.Exec(` PRAGMA foreign_keys = ON; PRAGMA journal_mode = WAL; PRAGMA busy_timeout = 5000; `); err != nil { return nil, fmt.Errorf("sqlite pragmas: %w", err) } schema := ` CREATE TABLE IF NOT EXISTS players ( id INTEGER PRIMARY KEY AUTOINCREMENT, name_ar TEXT NOT NULL, name_en TEXT NOT NULL, group_code TEXT NOT NULL DEFAULT '', image_data TEXT NOT NULL DEFAULT '', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS scores ( stage TEXT NOT NULL, player_id INTEGER NOT NULL, score INTEGER NOT NULL DEFAULT 0, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(stage, player_id), FOREIGN KEY(player_id) REFERENCES players(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS score_attachments ( stage TEXT NOT NULL, player_id INTEGER NOT NULL, image_data TEXT NOT NULL DEFAULT '', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(stage, player_id), FOREIGN KEY(player_id) REFERENCES players(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS app_settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL DEFAULT '', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); INSERT OR IGNORE INTO app_settings(key, value) VALUES ('view_proof_in_view', '0'), ('live_active_view', 'group_live'), ('live_show_group_live', '1'), ('live_group_display_mode', 'rotate'), ('live_group_fixed_code', ''), ('live_show_prelim_tie', '1'), ('live_show_prelim_overall', '1'), ('live_show_final_groups', '1'), ('live_final_display_mode', 'rotate'), ('live_final_fixed_group', '1'), ('live_show_final_tie', '1'), ('live_show_podium', '1'), ('live_rotation_interval_sec', '5'), ('live_rotation_player_count', '12'); INSERT OR IGNORE INTO scores(stage, player_id, score) SELECT 'prelim_r1', player_id, score FROM scores WHERE stage = 'preliminary'; INSERT OR IGNORE INTO scores(stage, player_id, score) SELECT 'final_r1', player_id, score FROM scores WHERE stage = 'final'; INSERT OR IGNORE INTO score_attachments(stage, player_id, image_data) SELECT 'prelim_r1', player_id, image_data FROM score_attachments WHERE stage = 'preliminary'; INSERT OR IGNORE INTO score_attachments(stage, player_id, image_data) SELECT 'final_r1', player_id, image_data FROM score_attachments WHERE stage = 'final'; ` if _, err := db.Exec(schema); err != nil { return nil, fmt.Errorf("apply schema: %w", err) } return db, nil }