fix(reliability): multer-ошибки, process-хендлеры, анти-гонка питомца, flashcards (Спринт2)

- errorHandler: MulterError → 413 «слишком большой» / 400 (а не 500).
- server: process.on(unhandledRejection/uncaughtException) — глобальная страховка
  с логированием, процесс не падает от единичной асинхронной ошибки.
- pet: атомарный CAS на кулдаунах petAction/starCatch/feedPet
  (UPDATE ... WHERE last IS ?, начисление только при changes=1) — нет двойного
  начисления при параллельных запросах. Проверено на семантике node:sqlite.
- assistant.flashcardsFromText: await callLLMFailover в try/catch → 502 вместо
  необработанного отклонения промиса.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-12 22:08:02 +03:00
parent 646e93cf46
commit 09c6c2b21d
4 changed files with 34 additions and 6 deletions
@@ -592,7 +592,9 @@ async function flashcardsFromText(req, res) {
const sys = 'Ты составляешь учебные флешкарты по тексту. Верни СТРОГО JSON-массив из 5–6 объектов ' +
'вида {"front":"...","back":"..."} без markdown и пояснений. front — короткий вопрос, back — краткий ответ (1–2 предложения). ' +
'По-русски. Формулы в LaTeX между $...$. Никакого текста вне JSON.';
const rr = await callLLMFailover([{ role: 'system', content: sys }, { role: 'user', content: text }], 1400);
let rr;
try { rr = await callLLMFailover([{ role: 'system', content: sys }, { role: 'user', content: text }], 1400); }
catch (e) { return res.status(502).json({ error: 'Не удалось обратиться к ИИ' }); }
const raw = rr && rr.text;
let cards = [];
if (raw) {