Files
samreshu_docs/intro.md
Anton 99cd8ae727 docs: add full project documentation
- Architecture: overview, 7 ADR, tech stack
- Principles: code-style, git-workflow, security
- API contracts: auth, profile, tests, admin endpoints
- Database schema: tables, relationships, indexes
- LLM strategy: prompts, fallback, validation, Qwen 2.5 14B
- Onboarding: setup, Docker, .env template
- Progress: roadmap, changelog
- Agents: context, backend instructions

Made-with: Cursor
2026-03-04 12:07:17 +03:00

17 KiB
Raw Permalink Blame History

Вводная часть

Веб‑приложение, в котором авторизованный пользователь выбирает стек (HTML/CSS/JS/Web basics) и уровень (базовый/начинающий), получает тест из 10 или 20 теоретических вопросов, сгенерированных LLM, отвечает, а в конце видит результат с разбором ошибок.

Минимальный MVP

Регистрация / логин / выход

✅ Подтверждение email
✅ Восстановление пароля
✅ Профиль (никнейм, страна)
✅ Создание теста (стек + уровень + количество вопросов)
✅ Прохождение теста (вопросы, таймер, прогресс)
✅ Результаты (балл, разбор, объяснения)
✅ Базовая история (последние 10 тестов)
✅ LLM генерация вопросов + fallback на банк
✅ Минимальная админка (QA очередь вопросов)

Максимальный MVP (целевое состояние продукта)

Это не то, что делаем сейчас — это то, к чему ведём. Все решения в коде, БД и архитектуре принимаем с оглядкой на эту картину.

Пользователи и роли

Guest        → видит лендинг, описание тарифов, примеры вопросов
Free         → базовый функционал, 5 тестов/день, 3 стека
Pro          → полный функционал, безлимит, все стеки
Admin        → модерация контента, управление пользователями

Модули и их максимальный объём

Auth

✅ Email + пароль
✅ Подтверждение email
✅ Восстановление пароля
✅ OAuth (GitHub + Google)
✅ 2FA (TOTP: Google Authenticator)
✅ Управление сессиями (список устройств, logout везде)

Профиль

✅ Никнейм, аватар, страна, город
✅ Уровень (jun/mid/sen — самооценка)
✅ Статистика на странице профиля
✅ Публичный профиль (по username)
✅ Приватность (скрыть из рейтингов)

Тестирование

✅ Стеки: HTML, CSS, JS, TS, React, Vue, Node.js, Git, Web basics
✅ Уровни: Базовый, Начинающий, Средний, Продвинутый, Эксперт
✅ Типы вопросов:
   - Single choice
   - Multiple select
   - True/False
   - Short text (LLM-верификация)
   - Code reading (что выведет код)     ← Phase 3
   - Bug fixing (найди ошибку)          ← Phase 3
✅ Режимы:
   - Фиксированный (10/20/30 вопросов)
   - Бесконечный (только Pro)
   - Марафон (100 вопросов, только Pro) ← Phase 3
✅ Таймер (на весь тест или на вопрос)
✅ Подсказки (Pro, 1 на вопрос)
✅ Пауза теста (сохранение прогресса)   ← Phase 2

Результаты и аналитика

✅ Итоговый экран (балл, время, статус)
✅ Детальный разбор (вопрос → ответ → объяснение)
✅ История всех попыток (фильтры, поиск)
✅ Статистика по темам (% правильных)
✅ График прогресса по времени
✅ Слабые места + LLM-рекомендации (Pro)
✅ Экспорт истории в CSV (Pro)

Рейтинги (Pro)

✅ Глобальный лидерборд (Top 100)
✅ Рейтинг по стекам
✅ Сезонный рейтинг (ежемесячный)
✅ Позиция пользователя (даже вне Top 100)
✅ Бейджи и достижения
✅ Архив сезонов

Подписка и биллинг

✅ Free план (всегда)
✅ Pro: 699₽/мес или 6999₽/год
✅ Пробный период 5 дней (с привязкой карты)
✅ Автопродление / отмена
✅ ЮKassa (основной провайдер)
✅ CloudPayments (резервный)
✅ Региональные цены (Казахстан, Беларусь, Закавказье)
✅ Промокоды и скидки
✅ История платежей в ЛК

Уведомления

✅ Email: verify, reset, trial, billing
✅ In-app баннеры и тосты
✅ Telegram-бот (Phase 3)
✅ Push-уведомления (Phase 3)

Контент / LLM

✅ Генерация вопросов по стеку/уровню/типу
✅ Проверка short text через LLM
✅ Подсказки (один наводящий вопрос)
✅ LLM-рекомендации по слабым местам
✅ Мультимодельная оркестрация (local + cloud fallback)
✅ Банк вопросов с QA-циклом
✅ Кэш и переиспользование вопросов

Админка

✅ QA очередь вопросов (approve/reject/edit)
✅ Управление пользователями (бан, сброс лимитов)
✅ Просмотр жалоб на вопросы
✅ Операционная панель (метрики, ошибки)
✅ Управление промокодами
✅ Прогрев кэша вопросов

Полная схема данных (ориентир)

users
  ├── subscriptions          (план, статус, даты)
  ├── sessions               (устройства, refresh токены)
  ├── oauth_accounts         (GitHub/Google)
  ├── totp_secrets           (2FA)
  │
  ├── tests
  │    └── test_questions    (снепшот вопросов)
  │
  ├── user_stats             (агрегаты по темам)
  ├── user_achievements      (бейджи)
  └── user_question_log      (какие вопросы видел, когда)

question_bank
  ├── question_cache_meta    (LLM метаданные)
  └── question_reports       (жалобы)

payments
  └── payment_events         (webhook лог)

audit_logs                   (действия админов)
notifications_log            (история отправок)
promo_codes                  (промокоды)

Что закладываем архитектурно с первого дня

Это принципы, не обсуждаемые при написании любого кода:

1. user.plan всегда читается из БД через subscription middleware
   → никогда не хардкодить права в контроллерах

2. Вопросы копируются в test_questions при старте теста (снепшот)
   → никогда не читать question_bank "живьём" во время теста

3. Все LLM-вызовы только через LlmService
   → остальной код не знает какая модель работает

4. Все внешние события (webhooks, LLM) валидируются по JSON-схеме
   → никогда не доверять внешним данным без валидации

5. Все проверки прав и лимитов только на backend
   → frontend только отображает состояние

6. Все даты хранятся в UTC
   → конвертация в часовой пояс только на фронте

7. Все конфигурации через env переменные
   → никаких хардкодов в коде

Фазы разработки

MVP 0 (сейчас)
└── Auth + Test (2 стека) + Results + Базовая история

Phase 1 (платный запуск)
└── Все стеки + Подписка + Trial + Лимиты FREE/PRO

Phase 2 (рост)
└── Рейтинги + Аналитика Pro + 2FA + OAuth + Multiple select + Short text

Phase 3 (зрелость)
└── Код-задачи + Бесконечный режим + Telegram + Достижения + Региональные цены

Фиксируем в docs-репо

# architecture/decisions/001-max-mvp-scope.md

## Контекст
Определяем максимальный целевой объём продукта,
чтобы архитектурные решения MVP не конфликтовали
с будущим функционалом.

## Решение
[ссылка на этот документ]

## Последствия
- Схема БД создаётся с заделом на все фазы
- LLM-слой абстрагирован с первого дня
- Subscription middleware существует с первого дня
  (даже когда все пользователи Free)

Структура репозиториев

samreshu-backend       (Node.js + TS + API)
samreshu-frontend      (React + TS + Vite)
samreshu-shared        (общие TS-типы/интерфейсы)
samreshu-docs          (документация, ADR, прогресс)

Структура samreshu-docs

samreshu-docs/
├── README.md                  # Навигация по доке
├── architecture/
│   ├── overview.md            # Общая архитектура
│   ├── decisions/             # ADR
│   │   ├── 001-monorepo-vs-polyrepo.md
│   │   ├── 002-postgresql-provider.md
│   │   ├── 003-llm-abstraction.md
│   │   └── ...
│   └── diagrams/              # Схемы (Mermaid / draw.io)
├── principles/
│   ├── code-style.md          # Соглашения по коду
│   ├── git-workflow.md        # Ветки, коммиты, PR
│   └── security.md            # Базовые правила безопасности
├── api/
│   └── openapi.yaml           # Контракт API (или ссылка)
├── database/
│   └── schema.md              # Описание таблиц и связей
├── progress/
│   ├── roadmap.md             # Что планируем
│   └── changelog.md           # Что сделали
└── onboarding/
    └── setup.md               # Как поднять проект локально

Окружение разработчика

Базовые требования

Node.js   >= 20 LTS  (через nvm)
npm       >= 10
Git       >= 2.40
Docker    + Docker Compose (для локальной БД и Redis)
VS Code   (рекомендован, настройки в репо)

.nvmrc (в корне каждого репо)

20

Backend: вспомогательные инструменты

1. ESLint

npm install -D \
  eslint \
  @eslint/js \
  typescript-eslint \
  eslint-plugin-import \
  eslint-plugin-security \
  eslint-config-prettier
// eslint.config.ts (flat config, актуальный формат ESLint 9+)
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import security from 'eslint-plugin-security'
import importPlugin from 'eslint-plugin-import'

export default tseslint.config(
  js.configs.recommended,
  ...tseslint.configs.recommended,
  {
    plugins: { security, import: importPlugin },
    rules: {
      '@typescript-eslint/no-explicit-any': 'warn',
      '@typescript-eslint/explicit-function-return-type': 'warn',
      'security/detect-object-injection': 'warn',
      'import/order': ['error', { 'newlines-between': 'always' }],
      'no-console': 'warn',
    }
  }
)

2. Prettier

npm install -D prettier
// .prettierrc
{
  "semi": false,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "all",
  "printWidth": 100,
  "endOfLine": "lf"
}
// .prettierignore
dist/
node_modules/
*.sql

3. Husky + lint-staged (проверки перед коммитом)

npm install -D husky lint-staged
npx husky init
// package.json
{
  "lint-staged": {
    "*.{ts,js}": ["eslint --fix", "prettier --write"],
    "*.{json,md}": ["prettier --write"]
  }
}
# .husky/pre-commit
npx lint-staged

4. Commitlint (единый стиль коммитов)

npm install -D @commitlint/cli @commitlint/config-conventional
// commitlint.config.ts
export default {
  extends: ['@commitlint/config-conventional'],
}
# .husky/commit-msg
npx --no -- commitlint --edit $1

Формат коммитов:

feat: добавить генерацию вопросов через LLM
fix: исправить подсчёт результатов теста
chore: обновить зависимости
docs: добавить описание LLM-модуля
refactor: переработать subscription middleware
test: добавить тесты для auth сервиса

5. TypeScript (строгий режим)

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "dist",
    "rootDir": "src",
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

6. Vitest (тесты)

npm install -D vitest @vitest/coverage-v8
// vitest.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    globals: true,
    environment: 'node',
    coverage: {
      provider: 'v8',
      reporter: ['text', 'lcov'],
      thresholds: { lines: 70 },  // минимальное покрытие MVP
    }
  }
})

Frontend: вспомогательные инструменты

npm install -D \
  eslint \
  typescript-eslint \
  eslint-plugin-react \
  eslint-plugin-react-hooks \
  eslint-plugin-jsx-a11y \
  prettier \
  husky \
  lint-staged \
  @commitlint/cli \
  @commitlint/config-conventional \
  vitest \
  @testing-library/react \
  @testing-library/user-event

Отличия от backend:

  • eslint-plugin-react-hooks — обязателен, ловит типичные ошибки с хуками
  • eslint-plugin-jsx-a11y — базовая доступность (a11y) прямо в линтере
  • @testing-library/react вместо чистого vitest для компонентов

.editorconfig (общий для всех репо)

root = true

[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

VS Code: рекомендуемые расширения

// .vscode/extensions.json
{
  "recommendations": [
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode",
    "prisma.prisma",
    "bradlc.vscode-tailwindcss",
    "eamodio.gitlens",
    "streetsidesoftware.code-spell-checker-russian",
    "usernamehw.errorlens",
    "ms-azuretools.vscode-docker",
    "mikestead.dotenv"
  ]
}
// .vscode/settings.json
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },
  "typescript.preferences.importModuleSpecifier": "non-relative"
}

Итог: что фиксируем в docs-репо прямо сейчас

# principles/code-style.md

- Язык кода: английский (переменные, функции, комментарии)
- Язык коммитов: английский (conventional commits)
- Язык документации: русский
- Форматтер: Prettier (конфиг в репо — не обсуждается)
- Линтер: ESLint strict + security plugin
- Тесты: Vitest, минимум 70% покрытие на сервисном слое
- any в TypeScript: запрещён (только через явный комментарий с обоснованием)
- console.log в коде: запрещён в prod (только через logger)