fix(db): atomic saveMnemonic, const normalizedLetter, updateProfileSeen guard, ?? 0 for avgScore
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+16
-15
@@ -73,7 +73,8 @@ function initDb(dbPath) {
|
||||
}
|
||||
|
||||
function updateProfileSeen(id) {
|
||||
instance.prepare('UPDATE profiles SET last_seen = ? WHERE id = ?').run(Date.now(), id)
|
||||
const result = instance.prepare('UPDATE profiles SET last_seen = ? WHERE id = ?').run(Date.now(), id)
|
||||
if (result.changes === 0) console.warn(`[db] updateProfileSeen: no profile id=${id}`)
|
||||
}
|
||||
|
||||
function createMagicToken(token, profileId, expiresAt) {
|
||||
@@ -128,7 +129,7 @@ function initDb(dbPath) {
|
||||
}
|
||||
|
||||
function recordAnswer(profileId, letter, correct) {
|
||||
letter = letter.toUpperCase()
|
||||
const normalizedLetter = letter.toUpperCase()
|
||||
const progress = getOrCreateProgress(profileId)
|
||||
const now = Date.now()
|
||||
const isFirstToday = !progress.last_session_at ||
|
||||
@@ -156,15 +157,15 @@ function initDb(dbPath) {
|
||||
// Update letter stats
|
||||
const existing = instance
|
||||
.prepare('SELECT * FROM letter_stats WHERE profile_id = ? AND letter = ?')
|
||||
.get(profileId, letter)
|
||||
.get(profileId, normalizedLetter)
|
||||
if (existing) {
|
||||
instance.prepare(
|
||||
'UPDATE letter_stats SET correct = correct + ?, attempts = attempts + 1, updated_at = ? WHERE profile_id = ? AND letter = ?'
|
||||
).run(correct ? 1 : 0, now, profileId, letter)
|
||||
).run(correct ? 1 : 0, now, profileId, normalizedLetter)
|
||||
} else {
|
||||
instance.prepare(
|
||||
'INSERT INTO letter_stats (profile_id, letter, correct, attempts, updated_at) VALUES (?,?,?,1,?)'
|
||||
).run(profileId, letter, correct ? 1 : 0, now)
|
||||
).run(profileId, normalizedLetter, correct ? 1 : 0, now)
|
||||
}
|
||||
|
||||
return instance.prepare('SELECT * FROM user_progress WHERE profile_id = ?').get(profileId)
|
||||
@@ -181,17 +182,17 @@ function initDb(dbPath) {
|
||||
const now = Date.now()
|
||||
const upperLetter = letter.toUpperCase()
|
||||
|
||||
instance.prepare(
|
||||
`INSERT INTO user_mnemonics (profile_id, letter, phrase, created_at) VALUES (?,?,?,?)
|
||||
ON CONFLICT(profile_id, letter) DO UPDATE SET phrase = excluded.phrase`
|
||||
// Try insert first — changes=1 means new row (first save), changes=0 means update path
|
||||
const insertResult = instance.prepare(
|
||||
'INSERT OR IGNORE INTO user_mnemonics (profile_id, letter, phrase, created_at) VALUES (?,?,?,?)'
|
||||
).run(profileId, upperLetter, phrase, now)
|
||||
|
||||
// Only award 50 pts on first save — if created_at = now, it was just inserted
|
||||
const row = instance.prepare(
|
||||
'SELECT created_at FROM user_mnemonics WHERE profile_id = ? AND letter = ?'
|
||||
).get(profileId, upperLetter)
|
||||
|
||||
if (row.created_at === now) {
|
||||
if (insertResult.changes === 0) {
|
||||
// Existing mnemonic — update phrase only, no point award
|
||||
instance.prepare('UPDATE user_mnemonics SET phrase = ? WHERE profile_id = ? AND letter = ?')
|
||||
.run(phrase, profileId, upperLetter)
|
||||
} else {
|
||||
// First save — award 50 points
|
||||
getOrCreateProgress(profileId)
|
||||
instance.prepare('UPDATE user_progress SET score = score + 50, updated_at = ? WHERE profile_id = ?')
|
||||
.run(now, profileId)
|
||||
@@ -218,7 +219,7 @@ function initDb(dbPath) {
|
||||
|
||||
function getAdminStats() {
|
||||
const totalUsers = instance.prepare('SELECT COUNT(*) as c FROM profiles').get().c
|
||||
const avgScore = instance.prepare('SELECT AVG(score) as a FROM user_progress').get().a || 0
|
||||
const avgScore = instance.prepare('SELECT AVG(score) as a FROM user_progress').get().a ?? 0
|
||||
const mostMissed = instance.prepare(`
|
||||
SELECT letter, SUM(attempts - correct) as misses
|
||||
FROM letter_stats GROUP BY letter ORDER BY misses DESC LIMIT 5
|
||||
|
||||
@@ -159,6 +159,17 @@ describe('mnemonics', () => {
|
||||
assert.equal(m.A, 'go WALKING')
|
||||
assert.equal(m.E, 'go')
|
||||
})
|
||||
|
||||
test('saveMnemonic awards 50 pts on first save, not on update', () => {
|
||||
const p1 = db.getOrCreateProgress(profileId)
|
||||
db.saveMnemonic(profileId, 'T', 'dash') // first save → +50 pts
|
||||
const p2 = db.getOrCreateProgress(profileId)
|
||||
assert.equal(p2.score, p1.score + 50)
|
||||
|
||||
db.saveMnemonic(profileId, 'T', 'updated dash') // update → no extra pts
|
||||
const p3 = db.getOrCreateProgress(profileId)
|
||||
assert.equal(p3.score, p2.score)
|
||||
})
|
||||
})
|
||||
|
||||
describe('letter stats', () => {
|
||||
|
||||
Reference in New Issue
Block a user