diff --git a/backend/src/controllers/gamesController.js b/backend/src/controllers/gamesController.js index 183a4e7..89f0e76 100644 --- a/backend/src/controllers/gamesController.js +++ b/backend/src/controllers/gamesController.js @@ -233,6 +233,14 @@ function hangmanWord(req, res) { }); } +// Анти-фарм: XP за игры начисляется по «честному слову» клиента, поэтому +// ограничиваем число начислений за тип игры в сутки (счёт по xp_log.reason). +const DAILY_WIN_CAP = 10; +function _gameWinsToday(userId, reason) { + try { return db.prepare("SELECT COUNT(*) AS n FROM xp_log WHERE user_id=? AND reason=? AND created_at >= date('now')").get(userId, reason).n; } + catch (e) { return 0; } +} + /* ── POST /api/games/hangman/complete ─────────────────────────────────── */ function hangmanComplete(req, res) { const { won, errors } = req.body; @@ -243,6 +251,7 @@ function hangmanComplete(req, res) { // 15 XP perfect, -2 per error, min 5 xpGain = Math.max(5, 15 - (Number(errors) || 0) * 2); } + if (xpGain > 0 && _gameWinsToday(req.user.id, 'hangman_win') >= DAILY_WIN_CAP) xpGain = 0; if (xpGain > 0) { try { awardXP(req.user.id, xpGain, 'hangman_win'); } catch (e) { console.error('[games] hangman XP:', e.message); } @@ -299,6 +308,7 @@ function crosswordComplete(req, res) { if (completed) { xpGain = Math.max(5, 20 - (Number(hintsUsed) || 0) * 3); } + if (xpGain > 0 && _gameWinsToday(req.user.id, 'crossword_win') >= DAILY_WIN_CAP) xpGain = 0; if (xpGain > 0) { try { awardXP(req.user.id, xpGain, 'crossword_win'); } catch (e) { console.error('[games] crossword XP:', e.message); } diff --git a/backend/src/controllers/lessonController.js b/backend/src/controllers/lessonController.js index 177c842..695f3b8 100644 --- a/backend/src/controllers/lessonController.js +++ b/backend/src/controllers/lessonController.js @@ -186,6 +186,10 @@ function markComplete(req, res) { const lesson = db.prepare('SELECT * FROM lessons WHERE id = ?').get(req.params.id); if (!lesson) return res.status(404).json({ error: 'Lesson not found' }); + // Награду даём только при ПЕРВОМ завершении (анти-фарм повторными POST). + const prev = db.prepare('SELECT completed FROM lesson_progress WHERE user_id = ? AND lesson_id = ?').get(req.user.id, lesson.id); + const firstCompletion = !prev || prev.completed !== 1; + db.prepare(` INSERT INTO lesson_progress (user_id, lesson_id, completed, updated_at) VALUES (?, ?, 1, datetime('now')) @@ -201,7 +205,7 @@ function markComplete(req, res) { WHERE l.course_id = ? AND lp.user_id = ? AND lp.completed = 1 `).get(lesson.course_id, req.user.id).n; - try { onLessonComplete(req.user.id, lesson.course_id); } catch {} + if (firstCompletion) { try { onLessonComplete(req.user.id, lesson.course_id); } catch {} } res.json({ ok: true, courseComplete: done >= total && total > 0 }); }