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:
229
agents/backend.md
Normal file
229
agents/backend.md
Normal file
@@ -0,0 +1,229 @@
|
||||
# Инструкции для AI-агента: Backend
|
||||
|
||||
Документ для агентов при создании и доработке `samreshu-backend`. Общий контекст проекта — в [context.md](context.md).
|
||||
|
||||
## Ссылки на документацию
|
||||
|
||||
| Документ | Что взять |
|
||||
| - | - |
|
||||
| [api/contracts.md](../api/contracts.md) | Все endpoints, request/response, коды ошибок |
|
||||
| [database/schema.md](../database/schema.md) | Таблицы, колонки, связи, индексы |
|
||||
| [principles/security.md](../principles/security.md) | Auth (argon2, JWT, refresh), rate limiting, Helmet, CORS |
|
||||
| [llm/strategy.md](../llm/strategy.md) | Промпты, JSON Schema валидации, fallback, LlmService интерфейс |
|
||||
| [principles/code-style.md](../principles/code-style.md) | ESLint, Prettier, TS config, Vitest |
|
||||
| [onboarding/setup.md](../onboarding/setup.md) | Docker Compose, .env шаблон |
|
||||
|
||||
---
|
||||
|
||||
## Структура репозитория
|
||||
|
||||
```text
|
||||
samreshu-backend/
|
||||
├── src/
|
||||
│ ├── index.ts # Entry point
|
||||
│ ├── app.ts # Fastify app, регистрация плагинов
|
||||
│ ├── config/
|
||||
│ │ └── env.ts # Валидация и экспорт env переменных
|
||||
│ ├── db/
|
||||
│ │ ├── index.ts # Drizzle client, экспорт drizzle
|
||||
│ │ ├── schema/ # Drizzle schema (users, sessions, tests, ...)
|
||||
│ │ └── migrations/ # SQL миграции (drizzle-kit)
|
||||
│ ├── plugins/
|
||||
│ │ ├── database.ts # Подключение к PostgreSQL
|
||||
│ │ ├── redis.ts # Подключение к Redis
|
||||
│ │ ├── auth.ts # JWT verify, decode, refreshToken logic
|
||||
│ │ ├── subscription.ts # Загрузка user.plan из subscriptions
|
||||
│ │ ├── rateLimit.ts # @fastify/rate-limit + Redis
|
||||
│ │ ├── helmet.ts # @fastify/helmet
|
||||
│ │ └── cors.ts # @fastify/cors
|
||||
│ ├── routes/
|
||||
│ │ ├── auth.ts # POST /auth/register, login, logout, refresh, ...
|
||||
│ │ ├── profile.ts # GET/PATCH /profile, GET /profile/:username
|
||||
│ │ ├── tests.ts # POST/GET /tests, answer, finish, results, history
|
||||
│ │ └── admin/
|
||||
│ │ └── questions.ts # GET queue, PATCH /admin/questions/:id
|
||||
│ ├── services/
|
||||
│ │ ├── auth.service.ts # register, login, verifyPassword, createSession
|
||||
│ │ ├── user.service.ts # getProfile, updateProfile
|
||||
│ │ ├── test.service.ts # createTest, getTest, answerQuestion, finishTest
|
||||
│ │ ├── question.service.ts # getFromBank, fallback logic
|
||||
│ │ └── llm.service.ts # generateQuestions, вызов Ollama API, валидация
|
||||
│ ├── lib/
|
||||
│ │ ├── errors.ts # Кастомные ошибки (AppError)
|
||||
│ │ ├── pagination.ts # Cursor-based helper
|
||||
│ │ └── uuid.ts # UUID v7 генерация (если нет встроенной)
|
||||
│ └── types/
|
||||
│ └── index.ts # Общие типы (Stack, Level, QuestionType)
|
||||
├── docker-compose.dev.yml # PostgreSQL + Redis + Ollama
|
||||
├── .env.example
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
├── vitest.config.ts
|
||||
└── drizzle.config.ts
|
||||
```
|
||||
|
||||
Префикс `/api/v1` задаётся при регистрации роутов: `fastify.register(routes, { prefix: '/api/v1' })`.
|
||||
|
||||
---
|
||||
|
||||
## Порядок разработки
|
||||
|
||||
Рекомендуемая последовательность (каждый шаг — отдельный PR):
|
||||
|
||||
### 1. Каркас
|
||||
|
||||
- Инициализация проекта (npm init, зависимости)
|
||||
- `config/env.ts` — загрузка и валидация env (zod или аналог)
|
||||
- `app.ts` — Fastify, Pino logger, базовые плагины (helmet, cors)
|
||||
- `db/schema` — Drizzle schema для MVP 0: users, sessions, subscriptions, tests, test_questions, question_bank
|
||||
- `db/index.ts` — Drizzle client
|
||||
- Миграции: `drizzle-kit generate` + `migrate`
|
||||
- Docker Compose (PostgreSQL, Redis)
|
||||
|
||||
### 2. Auth
|
||||
|
||||
- `plugins/auth.ts` — JWT verify, refresh token из cookie
|
||||
- `services/auth.service.ts` — argon2, создание sessions, ротация refresh
|
||||
- `routes/auth.ts` — register, login, logout, refresh, verify-email, forgot-password, reset-password
|
||||
- `plugins/rateLimit.ts` — прогрессивный lockout на login
|
||||
- Email-мок для dev (например, вывод в консоль или mailpit)
|
||||
|
||||
### 3. Profile
|
||||
|
||||
- `plugins/subscription.ts` — middleware, подгружающий user.plan
|
||||
- `services/user.service.ts`
|
||||
- `routes/profile.ts` — GET/PATCH /profile, GET /profile/:username
|
||||
- Защита роутов: `preHandler` с проверкой JWT
|
||||
|
||||
### 4. Tests (ядро)
|
||||
|
||||
- `services/llm.service.ts` — вызов Ollama, JSON Schema валидация, fallback на банк
|
||||
- `services/question.service.ts` — выбор из банка, дедупликация
|
||||
- `services/test.service.ts` — создание теста, снепшот в test_questions, answer, finish
|
||||
- `routes/tests.ts` — все endpoints для тестов
|
||||
- Проверка лимита Free (5 тестов/день) в subscription middleware
|
||||
|
||||
### 5. Admin
|
||||
|
||||
- `routes/admin/questions.ts` — GET queue, PATCH approve/reject
|
||||
- Проверка `user.role === 'admin'` в preHandler
|
||||
|
||||
### 6. Seed и прогон
|
||||
|
||||
- Скрипт `npm run seed:questions` — наполнение question_bank через LLM
|
||||
- Скрипт `npm run db:seed` — тестовый пользователь (опционально)
|
||||
- Проверка полного flow вручную
|
||||
|
||||
---
|
||||
|
||||
## Ключевые паттерны
|
||||
|
||||
### Fastify plugins
|
||||
|
||||
Каждый плагин — отдельный файл, регистрируется через `fastify.register(plugin)`:
|
||||
|
||||
```ts
|
||||
// plugins/auth.ts
|
||||
import type { FastifyPluginAsync } from 'fastify'
|
||||
|
||||
const authPlugin: FastifyPluginAsync = async (fastify) => {
|
||||
fastify.decorate('verifyJwt', async (request, reply) => { ... })
|
||||
}
|
||||
|
||||
export default authPlugin
|
||||
```
|
||||
|
||||
Декорators: `fastify.verifyJwt`, `fastify.db`, `fastify.redis`, `fastify.llm` — доступны после регистрации плагинов.
|
||||
|
||||
### Обработка ошибок
|
||||
|
||||
Единый `setErrorHandler`:
|
||||
|
||||
```ts
|
||||
fastify.setErrorHandler((error, request, reply) => {
|
||||
if (error instanceof AppError) {
|
||||
return reply.status(error.statusCode).send({
|
||||
error: { code: error.code, message: error.message }
|
||||
})
|
||||
}
|
||||
fastify.log.error(error)
|
||||
return reply.status(500).send({
|
||||
error: { code: 'INTERNAL_ERROR', message: 'Internal server error' }
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
`AppError` — класс с полями `statusCode`, `code`, `message`. Коды ошибок — из [api/contracts.md](../api/contracts.md).
|
||||
|
||||
### Drizzle
|
||||
|
||||
- Схема в `db/schema/` — отдельный файл на домен (users.ts, tests.ts, question_bank.ts)
|
||||
- Все запросы через `db.select()`, `db.insert()`, `db.update()` — параметризованные
|
||||
- Даты: `new Date()` в UTC, в БД — `timestamptz`
|
||||
- UUID: `uuidv7()` или аналог для PK
|
||||
|
||||
### LlmService
|
||||
|
||||
- Один класс/объект в `services/llm.service.ts`
|
||||
- Конфиг из env: `LLM_BASE_URL`, `LLM_MODEL`, `LLM_TIMEOUT_MS`
|
||||
- Вызов: `POST ${baseUrl}/chat/completions` с телом OpenAI-формата
|
||||
- Ответ: извлечь `content` из `choices[0].message`, парсить JSON (см. llm/strategy.md — извлечение из markdown)
|
||||
- Валидация: JSON Schema (ajv или zod) перед использованием
|
||||
- Retry: 1 раз при таймауте или невалидном ответе
|
||||
- Fallback: `question.service.getFromBank()` если LLM недоступен
|
||||
|
||||
### Валидация request body
|
||||
|
||||
Fastify schema на каждом route:
|
||||
|
||||
```ts
|
||||
schema: {
|
||||
body: {
|
||||
type: 'object',
|
||||
required: ['email', 'password'],
|
||||
properties: {
|
||||
email: { type: 'string', format: 'email' },
|
||||
password: { type: 'string', minLength: 8 }
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Пагинация cursor-based
|
||||
|
||||
Использовать `limit` и `cursor` (UUID). Запрос:
|
||||
|
||||
```sql
|
||||
SELECT * FROM tests
|
||||
WHERE user_id = :userId
|
||||
AND (cursor IS NULL OR id < :cursor)
|
||||
ORDER BY id DESC
|
||||
LIMIT :limit + 1
|
||||
```
|
||||
|
||||
Если вернулось `limit + 1` записей — `hasMore = true`, `nextCursor = last.id`. Иначе `hasMore = false`, `nextCursor = null`.
|
||||
|
||||
---
|
||||
|
||||
## Тестирование
|
||||
|
||||
- **Vitest** для unit и integration тестов
|
||||
- Покрытие: минимум 70% на сервисном слое (см. code-style.md)
|
||||
- Моки:
|
||||
- LlmService — не вызывать реальный Ollama в тестах, возвращать фиксированный JSON
|
||||
- Email — мок, не отправлять реальные письма
|
||||
- Redis — можно in-memory или реальный Redis в Docker (integration)
|
||||
- Тесты сервисов: мок Drizzle (или тестовая БД с migrater)
|
||||
- Тесты роутов: `fastify.inject()` с моками сервисов
|
||||
|
||||
---
|
||||
|
||||
## Чеклист перед коммитом
|
||||
|
||||
- [ ] ESLint и Prettier проходят
|
||||
- [ ] `npm run test` — все тесты зелёные
|
||||
- [ ] Нет `console.log` (только `fastify.log` / `logger`)
|
||||
- [ ] Нет `any` без явного обоснования
|
||||
- [ ] Секреты не в коде (только env)
|
||||
- [ ] Новые endpoints соответствуют [api/contracts.md](../api/contracts.md)
|
||||
59
agents/context.md
Normal file
59
agents/context.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Контекст проекта для AI-агентов
|
||||
|
||||
Этот документ — краткое описание проекта для использования AI-ассистентами при генерации кода.
|
||||
|
||||
## Проект
|
||||
|
||||
**samreshu** — веб-приложение для тестирования знаний по веб-технологиям. Пользователь выбирает стек и уровень, получает тест из вопросов (сгенерированных LLM или из банка), отвечает и видит результат с разбором.
|
||||
|
||||
## Tech stack
|
||||
|
||||
- **Backend**: Fastify + TypeScript, Drizzle ORM, PostgreSQL, Redis
|
||||
- **Frontend**: React + TypeScript + Vite
|
||||
- **LLM**: Локальный (Ollama/LM Studio, OpenAI-совместимый API), в prod — облачный
|
||||
- **Логирование**: Pino
|
||||
- **Тесты**: Vitest
|
||||
- **Deploy**: VPS + Docker Compose
|
||||
|
||||
## Архитектурные принципы (обязательны)
|
||||
|
||||
1. Подписка пользователя читается из БД через subscription middleware — не хардкодить права в контроллерах
|
||||
2. Вопросы копируются в `test_questions` при старте теста (снепшот) — не читать `question_bank` во время теста
|
||||
3. Все LLM-вызовы только через `LlmService` — код не знает какая модель работает
|
||||
4. Все внешние данные (webhooks, LLM) валидируются по JSON-схеме
|
||||
5. Проверки прав и лимитов только на backend — frontend отображает состояние
|
||||
6. Все даты хранятся в UTC — конвертация только на фронте
|
||||
7. Конфигурация через env переменные — без хардкодов
|
||||
|
||||
## Code style
|
||||
|
||||
- Язык кода: английский
|
||||
- Язык коммитов: английский (conventional commits)
|
||||
- TypeScript strict, `any` запрещён
|
||||
- `console.log` запрещён — использовать Pino logger
|
||||
- Prettier + ESLint strict + security plugin
|
||||
|
||||
## Структура репозиториев
|
||||
|
||||
```text
|
||||
samreshu-backend Fastify + TS + Drizzle
|
||||
samreshu-frontend React + TS + Vite
|
||||
samreshu-docs Документация
|
||||
```
|
||||
|
||||
Общие типы хранятся в каждом репо отдельно.
|
||||
|
||||
## Где найти детали
|
||||
|
||||
- Схема БД: `database/schema.md`
|
||||
- API контракты: `api/contracts.md`
|
||||
- LLM стратегия: `llm/strategy.md`
|
||||
- Roadmap: `progress/roadmap.md`
|
||||
- Code style: `principles/code-style.md`
|
||||
|
||||
## Инструкции для агентов в code-репо
|
||||
|
||||
В каждом code-репо будет `.cursor/rules/` со специфичными инструкциями:
|
||||
|
||||
- Backend: структура папок, как писать сервисы/плагины Fastify, Drizzle-паттерны
|
||||
- Frontend: структура компонентов, state management, роутинг
|
||||
Reference in New Issue
Block a user