fix: resolve go vet error and dayStart logic in ComputeDailyTotals
- Fix 'string(d)' int-to-rune conversion in rpg_test.go by using fmt.Sprintf - Fix ComputeDailyTotals dayStart logic: initial state should be StateWorking when dayStart > 0, correctly counting work from dayStart to first out event - Update README project structure to reflect new domain/handler/repo layout - Implement missing achievements: rpg_pioneer (on RPG enable) and salary_setter (on first rate set) - Add tryUnlockAchievement helper to Handler
This commit is contained in:
59
internal/repo/migrations/001_init.sql
Normal file
59
internal/repo/migrations/001_init.sql
Normal file
@@ -0,0 +1,59 @@
|
||||
-- +goose Up
|
||||
CREATE TABLE IF NOT EXISTS work_types (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO work_types (id, name) VALUES (1, 'onsite');
|
||||
INSERT OR IGNORE INTO work_types (id, name) VALUES (2, 'remote');
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
chat_id INTEGER NOT NULL UNIQUE,
|
||||
timezone TEXT NOT NULL DEFAULT 'Asia/Tehran',
|
||||
is_active INTEGER NOT NULL DEFAULT 1,
|
||||
report_enabled INTEGER NOT NULL DEFAULT 1,
|
||||
report_time TEXT NOT NULL DEFAULT '23:00',
|
||||
last_report_date TEXT DEFAULT NULL,
|
||||
export_accent TEXT NOT NULL DEFAULT 'ocean'
|
||||
CHECK (export_accent IN ('ocean', 'beach', 'rose', 'catppuccin')),
|
||||
calendar TEXT NOT NULL DEFAULT 'gregorian'
|
||||
CHECK (calendar IN ('gregorian', 'jalali', 'hijri')),
|
||||
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
||||
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS days (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
||||
date TEXT NOT NULL CHECK (date = strftime('%Y-%m-%d', date)),
|
||||
is_day_off INTEGER NOT NULL DEFAULT 0,
|
||||
day_off_reason TEXT NOT NULL DEFAULT '',
|
||||
current_work_type_id INTEGER NOT NULL DEFAULT 1 REFERENCES work_types(id),
|
||||
min_break_threshold INTEGER NOT NULL DEFAULT 300,
|
||||
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
||||
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
||||
UNIQUE(user_id, date)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
||||
day_id INTEGER NOT NULL REFERENCES days(id),
|
||||
event_type TEXT NOT NULL CHECK (event_type IN ('in', 'out')),
|
||||
work_type_id INTEGER REFERENCES work_types(id),
|
||||
occurred_at INTEGER NOT NULL CHECK (occurred_at > 0),
|
||||
note TEXT NOT NULL DEFAULT '',
|
||||
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_events_user_day ON events(user_id, day_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_events_user_occurred ON events(user_id, occurred_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_days_user_date ON days(user_id, date);
|
||||
|
||||
-- +goose Down
|
||||
DROP TABLE IF EXISTS events;
|
||||
DROP TABLE IF EXISTS days;
|
||||
DROP TABLE IF EXISTS users;
|
||||
DROP TABLE IF EXISTS work_types;
|
||||
101
internal/repo/migrations/002_features.sql
Normal file
101
internal/repo/migrations/002_features.sql
Normal file
@@ -0,0 +1,101 @@
|
||||
-- +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;
|
||||
Reference in New Issue
Block a user