- Remove module-level `let db` singleton; all query functions now close
over the local `instance` from their `initDb` call, preventing
cross-contamination when multiple callers each invoke `initDb`
- Add REFERENCES … ON DELETE CASCADE to profile_id in magic_tokens,
sessions, user_progress, user_mnemonics, and letter_stats so that
the already-enabled foreign_keys pragma is actually enforced by DDL
- recordAnswer: normalise letter to uppercase before storing stats so
lowercase 'a' and uppercase 'A' are never tracked separately
- updateLevel: call getOrCreateProgress first to avoid a silent no-op
UPDATE when no progress row exists yet
- saveMnemonic: replace SELECT-then-INSERT with an atomic UPSERT
(INSERT … ON CONFLICT DO UPDATE) and detect first-save by checking
whether created_at equals the current timestamp
- getSession: re-fetch the row after updating last_seen so the returned
object reflects the freshly written timestamp
- db.test.js: add tests for findProfileById, findProfileById (null),
updateProfileSeen, updateLevel (normal + no-row-yet), getAdminUsers,
and getAdminStats (21 tests total, all pass)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements Task 4 (TDD): wrote failing tests first, then the full SQLite
database layer covering profiles, magic tokens, sessions, user progress,
mnemonics, and letter stats. All 14 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>