Skip to content

Architecture

dripips edited this page Jun 24, 2026 · 2 revisions

Архитектура

Стек

  • Фронтенд: Vite + React 19 + TypeScript + Tailwind v4. PWA через vite-plugin-pwa (injectManifest, собственный service worker для push). Единый бандл отдаётся API.
  • Бэкенд: Fastify 5 на Node 24, со встроенным node:sqlite — без внешней БД и без ORM.
  • Авторизация: подписанный JWT в httpOnly-cookie; сессии на 10 лет (переживают деплои). Роль читается из БД на каждый запрос, поэтому повышение/понижение прав применяется без перелогина.

Конвейер контента

Весь учебный материал — обычные файлы в content/, разбираются при старте сервера (и подхватываются на лету при изменении):

  • lessons/*.md — фронтматтер gray-matter (id, order, level, phase, exercises, reading, …) + теория в Markdown. Урок с kind: reading — это текст для чтения (исключается из списка грамматики, плана и очереди практики); тег listening помечает диктанты.
  • grammar/*.md — карточки-справочник.
  • vocab/*.json — тематические наборы слов. id слова = slug, поэтому одно и то же слово в разных наборах дедуплицируется при загрузке.
  • books/*.json — книги-картинки: { id, title, level, character, style, pages:[{en, ru, scene}] }. Картинки страниц лежат в web/public/books/<id>/<n>.webp.

Добавление контента не требует кода и пересборки фронтенда — просто положи файл и перезапусти сервер (изменения картинок/кода всё же требуют npm run build:web).

Модель данных (SQLite)

  • users (role, xp, longest_streak) · settings (key/value на пользователя)
  • lesson_attempts (по упражнению, upsert) · activity (повторы/упражнения за день → стрики)
  • srs_cards (SM-2: ease, interval, reps, due, state) · custom_words (сохранённые из текстов)
  • error_log · progress (освоение тем) · push_subscriptions · translations_cache

Интервальное повторение

Классический SM-2 (server/src/srs.js): оценки again/hard/good/easy подстраивают ease и интервал; лимит новых карточек в день настраивается на пользователя.

AI и медиа (всё опционально, через прокси сервера)

  • Перевод и разбор письма/речи: любой OpenAI-совместимый chat API (LLM_TRANSLATE_*). Ответ принудительно в структурированном JSON и калибруется под уровень ученика.
  • TTS: POST /api/tts проксирует ElevenLabs, кэширует mp3 в server/data/tts-cache/ по ключу model|voice|speed|text; одновременные одинаковые запросы объединяются. На клиенте — откат на голос браузера speechSynthesis.
  • Иллюстрации: генерируются отдельно через Gemini («Nano Banana») — см. Как добавлять контент.

Инженерные мелочи

  • Навигация назад/вперёд восстанавливает позицию скролла (своя реализация в Layout), переход вперёд сбрасывает наверх.
  • useApi автоматически ретраит временные сбои (например, рестарт при деплое), поэтому интерфейс самовосстанавливается.
  • POST с пустым телом не отправляют JSON content-type (иначе Fastify вернёт 400) — пофикшено в обёртке fetch.