-- +goose Up ALTER TABLE users ADD COLUMN username TEXT NOT NULL DEFAULT ''; CREATE TABLE IF NOT EXISTS user_settings ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL UNIQUE REFERENCES users(id), export_accent TEXT NOT NULL DEFAULT 'ocean' CHECK (export_accent IN ('ocean', 'beach', 'rose', 'catppuccin')), report_enabled INTEGER NOT NULL DEFAULT 1, report_time TEXT NOT NULL DEFAULT '23:00', last_report_date TEXT NOT NULL DEFAULT '', rpg_enabled INTEGER NOT NULL DEFAULT 0, league_opt_in INTEGER NOT NULL DEFAULT 0, currency TEXT NOT NULL DEFAULT '$ USD', hourly_rate REAL NOT NULL DEFAULT 0.0, created_at INTEGER NOT NULL DEFAULT (unixepoch()), updated_at INTEGER NOT NULL DEFAULT (unixepoch()) ); INSERT OR IGNORE INTO user_settings (user_id) SELECT id FROM users; CREATE TABLE IF NOT EXISTS user_rpg ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL UNIQUE REFERENCES users(id), xp INTEGER NOT NULL DEFAULT 0, level INTEGER NOT NULL DEFAULT 1, current_streak INTEGER NOT NULL DEFAULT 0, longest_streak INTEGER NOT NULL DEFAULT 0, total_work_seconds INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL DEFAULT (unixepoch()), updated_at INTEGER NOT NULL DEFAULT (unixepoch()) ); INSERT OR IGNORE INTO user_rpg (user_id) SELECT id FROM users; CREATE TABLE IF NOT EXISTS daily_xp ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id), date TEXT NOT NULL, xp_earned INTEGER NOT NULL DEFAULT 0, work_seconds INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL DEFAULT (unixepoch()), UNIQUE(user_id, date) ); CREATE TABLE IF NOT EXISTS achievements ( id INTEGER PRIMARY KEY AUTOINCREMENT, code TEXT NOT NULL UNIQUE, name TEXT NOT NULL, description TEXT NOT NULL, icon TEXT NOT NULL DEFAULT '' ); INSERT OR IGNORE INTO achievements (code, name, description) VALUES ('first_clockin', 'First Steps', 'Clock in for the first time'), ('early_bird', 'Early Bird', 'Clock in before 7 AM'), ('night_owl', 'Night Owl', 'Clock out after 10 PM'), ('iron_streak_5', 'Iron Streak', 'Work 5 consecutive days'), ('iron_streak_10', 'Iron Streak II', 'Work 10 consecutive days'), ('iron_streak_30', 'Iron Streak III', 'Work 30 consecutive days'), ('remote_veteran', 'Remote Veteran', 'Log 100 hours of remote work'), ('onsite_master', 'Onsite Master', 'Log 100 hours of onsite work'), ('consistency_master', 'Consistency Master', 'Work at least 6 hours daily for a week'), ('perfect_week', 'Perfect Week', 'No days off for a full week'), ('overtime_hero', 'Overtime Hero', 'Work more than 10 hours in a day'), ('level_5', 'Getting Started', 'Reach level 5'), ('level_10', 'Dedicated', 'Reach level 10'), ('level_25', 'Veteran', 'Reach level 25'), ('level_50', 'Legend', 'Reach level 50'), ('weekend_warrior', 'Weekend Warrior', 'Work on a weekend day'), ('hundred_hours', 'Century', 'Log 100 total work hours'), ('five_hundred_hours', 'Iron Will', 'Log 500 total work hours'), ('salary_setter', 'Salary Tracker', 'Configure your hourly rate'), ('rpg_pioneer', 'RPG Pioneer', 'Enable the RPG system'); CREATE TABLE IF NOT EXISTS user_achievements ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id), achievement_id INTEGER NOT NULL REFERENCES achievements(id), unlocked_at INTEGER NOT NULL DEFAULT (unixepoch()), UNIQUE(user_id, achievement_id) ); CREATE INDEX IF NOT EXISTS idx_daily_xp_user_date ON daily_xp(user_id, date); CREATE INDEX IF NOT EXISTS idx_user_achievements_user ON user_achievements(user_id); -- +goose Down DROP TABLE IF EXISTS user_achievements; DROP TABLE IF EXISTS achievements; DROP TABLE IF EXISTS daily_xp; DROP TABLE IF EXISTS user_rpg; DROP TABLE IF EXISTS user_settings; CREATE TABLE IF NOT EXISTS users_old AS SELECT * FROM users; DROP TABLE IF EXISTS users; -- Note: restoring users table without the username column requires -- recreating it from the backup. In practice this migration is safe -- and the down path preserves data via the backup table. ALTER TABLE users_old RENAME TO users;