From c1d532aaadb9d001db17705dfbbfafccb06b994a Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 15:41:38 +0300 Subject: [PATCH] =?UTF-8?q?feat(biochem):=20=D0=A4=D0=B0=D0=B7=D0=B0=205.5?= =?UTF-8?q?=20=E2=80=94=20=D0=B0=D1=87=D0=B8=D0=B2=D0=BA=D0=B8=20bc=5F*=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=D0=B2=D1=8F=D0=B7=D0=B0=D0=BD=D1=8B=20=D0=BA?= =?UTF-8?q?=20=D1=81=D0=BE=D0=B1=D1=8B=D1=82=D0=B8=D1=8F=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gamification/service.js (checkPhase3Achievements): новый био-блок — bc_first_molecule (есть сохранённая молекула), bc_5_challenges (>=5 решённых), bc_20_challenges (>=20) из таблиц bio_user_molecules / bio_user_challenges. biochemController.js: после решения задачи и сохранения молекулы вызывается checkAchievements(req.user.id) — раньше начислялся только XP, ачивки не триггерились. Слоты bc_* существовали в _shared.js, но были мёртвыми. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/src/controllers/biochemController.js | 10 +++++++++- backend/src/controllers/gamification/service.js | 13 +++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/backend/src/controllers/biochemController.js b/backend/src/controllers/biochemController.js index d5ef6f1..0e927c3 100644 --- a/backend/src/controllers/biochemController.js +++ b/backend/src/controllers/biochemController.js @@ -1,6 +1,6 @@ 'use strict'; const db = require('../db/db'); -const { awardXP } = require('./gamificationController'); +const { awardXP, checkAchievements } = require('./gamificationController'); /* ── Helpers ─────────────────────────────────────────────────────────── */ const MAX_V = { H:1, C:4, N:3, O:2, P:5, S:6, Cl:1, Na:1, Ca:2, K:1, Mg:2, Fe:3, Br:1, I:1, F:1 }; @@ -178,6 +178,7 @@ function solveChallenge(req, res) { return res.status(400).json({ error: 'wrong_answer' }); stmts.markDone.run(req.user.id, challenge.id); awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`); + checkAchievements(req.user.id); return res.json({ ok: true, xp: challenge.xp_reward }); } @@ -190,6 +191,7 @@ function solveChallenge(req, res) { return res.status(400).json({ error: 'wrong_answer' }); stmts.markDone.run(req.user.id, challenge.id); awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`); + checkAchievements(req.user.id); return res.json({ ok: true, xp: challenge.xp_reward }); } @@ -203,6 +205,7 @@ function solveChallenge(req, res) { return res.status(400).json({ error: 'wrong_answer' }); stmts.markDone.run(req.user.id, challenge.id); awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`); + checkAchievements(req.user.id); return res.json({ ok: true, xp: challenge.xp_reward }); } @@ -216,6 +219,7 @@ function solveChallenge(req, res) { return res.status(400).json({ error: 'wrong_answer' }); stmts.markDone.run(req.user.id, challenge.id); awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`); + checkAchievements(req.user.id); return res.json({ ok: true, xp: challenge.xp_reward }); } @@ -230,6 +234,7 @@ function solveChallenge(req, res) { return res.status(400).json({ error: 'wrong_answer' }); stmts.markDone.run(req.user.id, challenge.id); awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`); + checkAchievements(req.user.id); return res.json({ ok: true, xp: challenge.xp_reward }); } @@ -244,6 +249,7 @@ function solveChallenge(req, res) { return res.status(400).json({ error: 'wrong_answer' }); stmts.markDone.run(req.user.id, challenge.id); awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`); + checkAchievements(req.user.id); return res.json({ ok: true, xp: challenge.xp_reward }); } @@ -274,6 +280,7 @@ function solveChallenge(req, res) { stmts.markDone.run(req.user.id, challenge.id); awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`); + checkAchievements(req.user.id); res.json({ ok: true, xp: challenge.xp_reward }); } @@ -303,6 +310,7 @@ function saveMolecule(req, res) { JSON.stringify(atoms), JSON.stringify(bonds), ).lastInsertRowid; + checkAchievements(req.user.id); // bc_first_molecule res.status(201).json({ id, formula, known: known || null }); } diff --git a/backend/src/controllers/gamification/service.js b/backend/src/controllers/gamification/service.js index 119d87e..0c222f8 100644 --- a/backend/src/controllers/gamification/service.js +++ b/backend/src/controllers/gamification/service.js @@ -343,6 +343,19 @@ function checkPhase3Achievements(userId, userRow) { if (tb) unlockAchievement(userId, 'tb_first_para'); } catch (e) { /* table missing */ } + // ── biochem: решённые задачи + первая собранная молекула ─────── + try { + const bc = db.prepare(` + SELECT COUNT(*) AS n FROM bio_user_challenges WHERE user_id = ? + `).get(userId)?.n || 0; + if (bc >= 5) unlockAchievement(userId, 'bc_5_challenges'); + if (bc >= 20) unlockAchievement(userId, 'bc_20_challenges'); + const mol = db.prepare(` + SELECT 1 FROM bio_user_molecules WHERE user_id = ? LIMIT 1 + `).get(userId); + if (mol) unlockAchievement(userId, 'bc_first_molecule'); + } catch (e) { /* bio tables missing on legacy install */ } + // ── flashcards: total reviews ────────────────────────────────── try { const fc = db.prepare(`