- 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
230 lines
9.8 KiB
Markdown
230 lines
9.8 KiB
Markdown
# Инструкции для 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)
|