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
This commit is contained in:
26
architecture/decisions/001-polyrepo.md
Normal file
26
architecture/decisions/001-polyrepo.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# ADR-001: Polyrepo вместо monorepo
|
||||
|
||||
## Статус
|
||||
|
||||
Принято
|
||||
|
||||
## Контекст
|
||||
|
||||
Нужно выбрать структуру репозиториев для проекта с отдельным backend и frontend.
|
||||
|
||||
## Варианты
|
||||
|
||||
1. **Monorepo** (один репо, npm workspaces / turborepo) — единый CI, общие типы, но сложнее настройка, тяжелее клонирование.
|
||||
2. **Polyrepo** (отдельные репозитории) — изолированные пайплайны, простой CI, независимый деплой.
|
||||
|
||||
## Решение
|
||||
|
||||
Polyrepo: `samreshu-backend`, `samreshu-frontend`, `samreshu-docs`.
|
||||
|
||||
Для команды из 1-2 человек monorepo-тулинг (turborepo, nx) создаёт overhead без ощутимой пользы. Backend и frontend деплоятся независимо, имеют разные зависимости и разные циклы релизов.
|
||||
|
||||
## Последствия
|
||||
|
||||
- Общие TypeScript-типы дублируются в каждом репо (без shared-пакета)
|
||||
- CI/CD настраивается отдельно для каждого репо
|
||||
- Изменения API-контрактов требуют ручной синхронизации между репо
|
||||
30
architecture/decisions/002-fastify.md
Normal file
30
architecture/decisions/002-fastify.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# ADR-002: Fastify как backend-фреймворк
|
||||
|
||||
## Статус
|
||||
|
||||
Принято
|
||||
|
||||
## Контекст
|
||||
|
||||
Нужен HTTP-фреймворк для Node.js backend с TypeScript.
|
||||
|
||||
## Варианты
|
||||
|
||||
1. **Express** — самый популярный, огромная экосистема, но устаревший дизайн, нет нативной поддержки async/await в error handling, медленнее.
|
||||
2. **Fastify** — современный, быстрый, нативная поддержка TypeScript, встроенная валидация через JSON Schema, плагинная архитектура, Pino из коробки.
|
||||
3. **NestJS** — мощный фреймворк с DI, декораторами, модулями. Избыточен для команды 1-2 человек, крутая кривая обучения.
|
||||
|
||||
## Решение
|
||||
|
||||
Fastify.
|
||||
|
||||
- Встроенная валидация запросов через JSON Schema (пригодится для LLM-ответов)
|
||||
- Нативный Pino logger — не нужно настраивать отдельно
|
||||
- Плагинная система для чистой декомпозиции (auth, tests, llm — отдельные плагины)
|
||||
- Производительность выше Express в 2-3 раза на типичных нагрузках
|
||||
|
||||
## Последствия
|
||||
|
||||
- Меньше туториалов по сравнению с Express (но документация Fastify качественная)
|
||||
- Middleware из Express-экосистемы нужно адаптировать или искать Fastify-аналоги
|
||||
- Плагинная архитектура требует дисциплины в структуре кода
|
||||
31
architecture/decisions/003-drizzle-orm.md
Normal file
31
architecture/decisions/003-drizzle-orm.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# ADR-003: Drizzle ORM
|
||||
|
||||
## Статус
|
||||
|
||||
Принято
|
||||
|
||||
## Контекст
|
||||
|
||||
Нужен типизированный доступ к PostgreSQL из TypeScript-кода.
|
||||
|
||||
## Варианты
|
||||
|
||||
1. **Prisma** — популярная, удобная генерация типов из schema.prisma, миграции. Минусы: тяжёлый бинарный engine, ограничения на сложные запросы, собственный query language.
|
||||
2. **Drizzle ORM** — легковесный, SQL-first подход, схема описывается в TypeScript, отличная типизация, близость к чистому SQL.
|
||||
3. **Kysely** — типизированный query builder, ещё ближе к SQL, но нет встроенных миграций.
|
||||
|
||||
## Решение
|
||||
|
||||
Drizzle ORM.
|
||||
|
||||
- Схема в TypeScript — типы выводятся автоматически, не нужна кодогенерация
|
||||
- SQL-first: сложные запросы (JOIN, подзапросы, агрегации) пишутся естественно
|
||||
- Лёгкий — нет бинарного engine как у Prisma
|
||||
- Встроенные миграции через `drizzle-kit`
|
||||
- Хорошо сочетается с Fastify (без магии, без декораторов)
|
||||
|
||||
## Последствия
|
||||
|
||||
- Нужно знать SQL (в отличие от Prisma, которая его прячет)
|
||||
- Экосистема меньше, чем у Prisma
|
||||
- VS Code расширение: используем общие SQL/TS расширения вместо Prisma-специфичного
|
||||
30
architecture/decisions/004-postgresql.md
Normal file
30
architecture/decisions/004-postgresql.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# ADR-004: PostgreSQL как основная база данных
|
||||
|
||||
## Статус
|
||||
|
||||
Принято
|
||||
|
||||
## Контекст
|
||||
|
||||
Нужна реляционная база данных для хранения пользователей, тестов, вопросов, подписок, платежей.
|
||||
|
||||
## Варианты
|
||||
|
||||
1. **PostgreSQL** — зрелая, надёжная, JSONB для полуструктурированных данных, full-text search, расширяемость.
|
||||
2. **MySQL/MariaDB** — популярна, но слабее в поддержке JSON, CTE, оконных функций.
|
||||
3. **SQLite** — для маленьких проектов, не подходит для конкурентного доступа в prod.
|
||||
|
||||
## Решение
|
||||
|
||||
PostgreSQL.
|
||||
|
||||
- JSONB пригодится для хранения вариантов ответов и метаданных LLM
|
||||
- Полнотекстовый поиск по банку вопросов без внешних зависимостей
|
||||
- Поддерживается всеми хостингами и облачными провайдерами
|
||||
- Drizzle ORM имеет лучшую поддержку именно для PostgreSQL
|
||||
|
||||
## Последствия
|
||||
|
||||
- Локальная разработка через Docker (PostgreSQL в контейнере)
|
||||
- Миграции управляются через `drizzle-kit`
|
||||
- Бэкапы и восстановление — стандартные инструменты pg_dump/pg_restore
|
||||
45
architecture/decisions/005-llm-abstraction.md
Normal file
45
architecture/decisions/005-llm-abstraction.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# ADR-005: Абстракция LLM через LlmService
|
||||
|
||||
## Статус
|
||||
|
||||
Принято
|
||||
|
||||
## Контекст
|
||||
|
||||
Приложение использует LLM для генерации вопросов, проверки ответов и рекомендаций. Нужна возможность менять провайдера без изменения бизнес-логики.
|
||||
|
||||
## Решение
|
||||
|
||||
Весь доступ к LLM только через `LlmService`. Конфигурация провайдера — через переменные окружения:
|
||||
|
||||
```
|
||||
LLM_BASE_URL=http://localhost:11434/v1 # Ollama (runtime для backend)
|
||||
LLM_MODEL=qwen2.5:14b
|
||||
LLM_API_KEY= # пустой для локального
|
||||
LLM_TIMEOUT_MS=15000
|
||||
LLM_MAX_RETRIES=1
|
||||
```
|
||||
|
||||
### Стратегия провайдеров
|
||||
|
||||
1. **Dev/test** — локальный LLM (Ollama, LM Studio, vLLM) с OpenAI-совместимым API
|
||||
2. **Production** — облачный API (OpenAI, Anthropic и др.)
|
||||
3. Переключение — замена переменных в `.env`, код приложения не меняется
|
||||
|
||||
### Интерфейс LlmService
|
||||
|
||||
- `generateQuestions(stack, level, count, type)` → structured JSON
|
||||
- `verifyAnswer(question, userAnswer)` → boolean + explanation
|
||||
- `getHint(question)` → string
|
||||
- `getRecommendations(weakTopics)` → string[]
|
||||
|
||||
### Fallback
|
||||
|
||||
Если LLM недоступен или таймаут — берём вопросы из `question_bank` (предварительно наполненного через тот же LLM и провалидированного вручную).
|
||||
|
||||
## Последствия
|
||||
|
||||
- Бизнес-код не зависит от конкретного провайдера
|
||||
- Замена LLM — изменение одного `.env` файла
|
||||
- Нужен минимальный банк вопросов для fallback до запуска
|
||||
- Все ответы LLM валидируются по JSON-схеме перед использованием
|
||||
44
architecture/decisions/006-vps-docker-deploy.md
Normal file
44
architecture/decisions/006-vps-docker-deploy.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# ADR-006: VPS + Docker Compose для деплоя
|
||||
|
||||
## Статус
|
||||
|
||||
Принято
|
||||
|
||||
## Контекст
|
||||
|
||||
Нужна стратегия деплоя для MVP. Критерии: простота, предсказуемая стоимость, контроль над сервером.
|
||||
|
||||
## Варианты
|
||||
|
||||
1. **VPS + Docker Compose** — один сервер, все сервисы в контейнерах, предсказуемая цена.
|
||||
2. **Cloud managed (AWS ECS, Yandex Cloud)** — масштабирование, managed DB, но сложнее и дороже для MVP.
|
||||
3. **PaaS (Railway, Render)** — самый простой деплой, но ограничения и непредсказуемые costs при росте.
|
||||
|
||||
## Решение
|
||||
|
||||
VPS + Docker Compose для MVP.
|
||||
|
||||
### Состав контейнеров
|
||||
|
||||
```
|
||||
docker-compose.yml
|
||||
├── backend (Fastify API)
|
||||
├── frontend (nginx + React static build)
|
||||
├── postgres (PostgreSQL)
|
||||
├── redis (Redis)
|
||||
└── nginx (reverse proxy, SSL termination)
|
||||
```
|
||||
|
||||
### Почему VPS
|
||||
|
||||
- Полный контроль над сервером (LLM-логи, бэкапы, мониторинг)
|
||||
- Фиксированная стоимость ~500-1500 руб/мес
|
||||
- Docker Compose — один файл для всего окружения
|
||||
- При росте — миграция на cloud без изменения контейнеров
|
||||
|
||||
## Последствия
|
||||
|
||||
- Нужно самостоятельно настраивать SSL (Let's Encrypt / certbot)
|
||||
- Нет автоматического масштабирования (достаточно для MVP)
|
||||
- Бэкапы БД — скрипт с cron
|
||||
- Мониторинг — Sentry + базовые health checks
|
||||
36
architecture/decisions/007-no-shared-types-repo.md
Normal file
36
architecture/decisions/007-no-shared-types-repo.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# ADR-007: Без отдельного репозитория для общих типов
|
||||
|
||||
## Статус
|
||||
|
||||
Принято
|
||||
|
||||
## Контекст
|
||||
|
||||
В polyrepo-архитектуре frontend и backend используют общие TypeScript-типы (API request/response, enum-ы стеков и уровней). Нужно решить, где их хранить.
|
||||
|
||||
## Варианты
|
||||
|
||||
1. **Отдельный репо `samreshu-shared`** — npm-пакет с типами, публикуется и устанавливается в оба репо.
|
||||
2. **Git submodule** — общая папка подключается как submodule.
|
||||
3. **Дублирование типов** — каждый репо хранит свою копию типов.
|
||||
|
||||
## Решение
|
||||
|
||||
Дублирование типов в каждом репо.
|
||||
|
||||
### Обоснование
|
||||
|
||||
- Для команды 1-2 человек overhead на поддержку npm-пакета (версионирование, публикация, обновление зависимостей) превышает пользу
|
||||
- Git submodules добавляют сложность в ежедневную работу
|
||||
- Типов на этапе MVP немного (стеки, уровни, формат вопросов/ответов)
|
||||
- При рассинхронизации типов ошибки ловятся на этапе тестирования API
|
||||
|
||||
### Соглашение
|
||||
|
||||
- API-контракты фиксируются в документации (`api/contracts.md`)
|
||||
- При изменении контракта обновляются оба репо в одном PR-цикле
|
||||
|
||||
## Последствия
|
||||
|
||||
- Типы могут рассинхронизироваться (риск принимаем осознанно)
|
||||
- Можно пересмотреть при росте команды или количества контрактов
|
||||
77
architecture/overview.md
Normal file
77
architecture/overview.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Общая архитектура
|
||||
|
||||
## Tech stack
|
||||
|
||||
| Слой | Технология | Назначение |
|
||||
| ------ | ----------- | ------------ |
|
||||
| Backend | Fastify + TypeScript | REST API сервер |
|
||||
| ORM | Drizzle ORM | Типизированный доступ к БД |
|
||||
| Database | PostgreSQL | Основное хранилище |
|
||||
| Cache | Redis | Кэш вопросов, сессии, rate limiting |
|
||||
| Frontend | React + TypeScript + Vite | SPA клиент |
|
||||
| LLM | Локальный LLM (dev), облачный API (prod) | Генерация вопросов |
|
||||
| Логирование | Pino | Структурированные JSON-логи |
|
||||
| Мониторинг ошибок | Sentry | Отлов ошибок в production |
|
||||
| Deploy | VPS + Docker Compose | Хостинг |
|
||||
|
||||
## Схема взаимодействия
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Browser["Browser (React SPA)"]
|
||||
API["Fastify API"]
|
||||
DB["PostgreSQL"]
|
||||
Cache["Redis"]
|
||||
LLM["LLM Service"]
|
||||
LocalLLM["Local LLM (dev)"]
|
||||
CloudLLM["Cloud API (prod)"]
|
||||
|
||||
Browser -->|"HTTP/JSON"| API
|
||||
API --> DB
|
||||
API --> Cache
|
||||
API --> LLM
|
||||
LLM --> LocalLLM
|
||||
LLM --> CloudLLM
|
||||
```
|
||||
|
||||
## Структура репозиториев
|
||||
|
||||
```text
|
||||
samreshu-backend Fastify + TS + Drizzle
|
||||
samreshu-frontend React + TS + Vite
|
||||
samreshu-docs Документация, ADR, прогресс
|
||||
```
|
||||
|
||||
Общие типы хранятся в каждом репо отдельно — без отдельного shared-пакета.
|
||||
|
||||
## Архитектурные принципы
|
||||
|
||||
Принципы, не обсуждаемые при написании любого кода:
|
||||
|
||||
### 1. Подписка читается из БД через middleware
|
||||
|
||||
`user.plan` всегда определяется через subscription middleware. Права никогда не хардкодятся в контроллерах.
|
||||
|
||||
### 2. Снепшот вопросов при старте теста
|
||||
|
||||
Вопросы копируются в `test_questions` при создании теста. Во время прохождения теста `question_bank` никогда не читается напрямую.
|
||||
|
||||
### 3. LLM-вызовы только через LlmService
|
||||
|
||||
Остальной код не знает, какая модель работает. Провайдер, URL и ключ задаются через переменные окружения.
|
||||
|
||||
### 4. Валидация внешних данных по JSON-схеме
|
||||
|
||||
Все внешние события (webhooks, ответы LLM) валидируются. Внешним данным не доверяем без проверки.
|
||||
|
||||
### 5. Проверки прав и лимитов только на backend
|
||||
|
||||
Frontend только отображает состояние. Все авторизационные решения принимаются на сервере.
|
||||
|
||||
### 6. Все даты в UTC
|
||||
|
||||
Хранение и передача дат — UTC. Конвертация в часовой пояс пользователя только на фронте.
|
||||
|
||||
### 7. Конфигурация через переменные окружения
|
||||
|
||||
Никаких хардкодов. Все настройки (БД, Redis, LLM, ключи) читаются из `.env`.
|
||||
Reference in New Issue
Block a user