feat(gamification): Phase 4 — standalone coin events + coin_log
Coins were always 1:10 of XP. Now they have their own event log + a
helper that dedups by reason within a configurable window.
Backend:
• migration 032 creates coin_log (user_id, amount, reason, created_at)
with indices for the 'fired today?' check
• awardCoins now records into coin_log on every call (reason defaults
to 'xp_bonus' for the legacy XP-proportional path)
• awardCoinsOnce(userId, amount, reason, window) — fires the bonus
only if no row matches in the window:
'day' → DATE(created_at) = today
'week' → ISO week match
'forever' → never twice
Wired events (Phase 4 subset of the plan):
• Daily login — 10 coins, once/day. Hooked in updateStreak so the
bonus rides on the existing 'daily_activity' XP trigger.
• Daily goal completion — 15/25/40 coins (easy/medium/hard), once/day.
Sits next to the existing tier XP bonus in updateDailyGoal.
• Variant clear — 30 coins, once per (user, variant) forever. Fires
from the exam-prep attempts endpoint when the user's final correct
answer fills out a math9 variant.
Deferred (need invasive trigger hooks): weekly goal, paragraph close,
boss defeated, referral.
Verified end-to-end: awardCoinsOnce returns true→false on repeated
calls, coin_log records the first, coins balance moves once.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
-- ═══════════════════════════════════════════════════════════════
|
||||
-- 032: coin_log — audit + dedup table for coin-only events
|
||||
--
|
||||
-- Until now coins were always a side-effect of XP (1 coin per 10 XP
|
||||
-- in awardXP), and we had no per-event log for them. Phase 4 adds
|
||||
-- standalone coin events (daily login, daily-goal completion, variant
|
||||
-- clear, weekly challenge bonus) that need their own dedup window so
|
||||
-- a user can't farm the same bonus twice in one day / week.
|
||||
--
|
||||
-- The log is also useful for the upcoming /api/shop/coin-history view
|
||||
-- on the profile page so users can see *where* their coins came from.
|
||||
--
|
||||
-- Lookup pattern is (user_id, reason, created_at) — index supports
|
||||
-- the "did this event already fire today?" query.
|
||||
-- ═══════════════════════════════════════════════════════════════
|
||||
|
||||
CREATE TABLE coin_log (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
amount INTEGER NOT NULL,
|
||||
reason TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX idx_coin_log_user_reason_date ON coin_log(user_id, reason, created_at);
|
||||
CREATE INDEX idx_coin_log_user_date ON coin_log(user_id, created_at);
|
||||
Reference in New Issue
Block a user