feat(biochem): Фаза 4 (срез) — персистентность прогресса путей + награда
Learn-режим метаболических путей теперь сохраняет прохождение на пользователя (раньше прогресс терялся). - migration 044_bio_user_pathway: таблица bio_user_pathway(user_id, pathway, step, completed) с upsert. - biochemController: getPathwayProgress / savePathwayProgress; XP (+80) начисляется один раз при первом завершении пути (completed «липкий» через MAX), затем checkAchievements. Роуты GET/POST /biochem/pathways/progress. - js/api.js: biochemGetPathwayProgress / biochemSavePathwayProgress. - biochem-pathways.html: загрузка прогресса в init (галочка-SVG на пройденных путях), сохранение + тост «+XP» при завершении пути. Полный перенос данных путей в БД (4.1-4.3) отложен — хардкод путей работает, ценность миграции архитектурная; здесь доставлена пользовательская часть. Проверено: upsert, XP-once, completed-sticky на реальной БД. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -321,6 +321,44 @@ function deleteSaved(req, res) {
|
||||
res.json({ ok: true });
|
||||
}
|
||||
|
||||
/* ── Прогресс прохождения путей (Learn-режим) ────────────────────────── */
|
||||
const PATHWAY_XP = 80;
|
||||
const stmtsPath = {
|
||||
getProg: db.prepare('SELECT pathway, step, completed FROM bio_user_pathway WHERE user_id=?'),
|
||||
wasDone: db.prepare('SELECT completed FROM bio_user_pathway WHERE user_id=? AND pathway=?'),
|
||||
upsert: db.prepare(`INSERT INTO bio_user_pathway (user_id, pathway, step, completed, updated_at)
|
||||
VALUES (?, ?, ?, ?, datetime('now'))
|
||||
ON CONFLICT(user_id, pathway) DO UPDATE SET
|
||||
step = excluded.step,
|
||||
completed = MAX(completed, excluded.completed),
|
||||
updated_at = datetime('now')`),
|
||||
};
|
||||
|
||||
/* ── GET /api/biochem/pathways/progress ──────────────────────────────── */
|
||||
function getPathwayProgress(req, res) {
|
||||
const out = {};
|
||||
for (const r of stmtsPath.getProg.all(req.user.id))
|
||||
out[r.pathway] = { step: r.step, completed: !!r.completed };
|
||||
res.json(out);
|
||||
}
|
||||
|
||||
/* ── POST /api/biochem/pathways/progress ─────────────────────────────── */
|
||||
function savePathwayProgress(req, res) {
|
||||
const { pathway, step, completed } = req.body;
|
||||
if (typeof pathway !== 'string' || !pathway)
|
||||
return res.status(400).json({ error: 'pathway required' });
|
||||
const prev = stmtsPath.wasDone.get(req.user.id, pathway);
|
||||
const firstCompletion = !!completed && !(prev && prev.completed);
|
||||
stmtsPath.upsert.run(req.user.id, pathway, Math.max(0, parseInt(step) || 0), completed ? 1 : 0);
|
||||
let xp = 0;
|
||||
if (firstCompletion) {
|
||||
xp = PATHWAY_XP;
|
||||
awardXP(req.user.id, xp, `biochem_pathway:${pathway}`);
|
||||
checkAchievements(req.user.id);
|
||||
}
|
||||
res.json({ ok: true, xp });
|
||||
}
|
||||
|
||||
/* ── util ────────────────────────────────────────────────────────────── */
|
||||
function tryParse(v, fallback) {
|
||||
if (!v) return fallback;
|
||||
@@ -331,6 +369,7 @@ module.exports = {
|
||||
getElements, getMolecules, getMolecule, validate,
|
||||
getReactions, getChallenges, solveChallenge,
|
||||
getSaved, saveMolecule, deleteSaved,
|
||||
getPathwayProgress, savePathwayProgress,
|
||||
// экспортируется для тестов структурной проверки
|
||||
structuralMatch, canonicalHash,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
-- 044_bio_user_pathway.sql
|
||||
-- Прогресс прохождения метаболических путей (Learn-режим biochem-pathways).
|
||||
-- Раньше прогресс не сохранялся; теперь шаг и факт завершения хранятся на
|
||||
-- пользователя по ключу пути (glycolysis / krebs / oxidation / synthesis ...).
|
||||
-- Награда (XP) начисляется один раз при первом завершении пути.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bio_user_pathway (
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
pathway TEXT NOT NULL,
|
||||
step INTEGER NOT NULL DEFAULT 0,
|
||||
completed INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
PRIMARY KEY (user_id, pathway)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bio_user_pathway ON bio_user_pathway(user_id);
|
||||
@@ -14,5 +14,7 @@ router.post('/challenges/:id/solve', c.solveChallenge);
|
||||
router.get('/saved', c.getSaved);
|
||||
router.post('/saved', c.saveMolecule);
|
||||
router.delete('/saved/:id', c.deleteSaved);
|
||||
router.get('/pathways/progress', c.getPathwayProgress);
|
||||
router.post('/pathways/progress', c.savePathwayProgress);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user