Files
samreshu_docs/database/schema.md
Anton 2f45a0b851 docs: приведение документации в соответствие с backend
- Auth: register без токенов до верификации (userId, message, verificationCode)
- Auth: login — 429 RATE_LIMIT_EXCEEDED при lockout, user с avatarUrl
- Auth: verify-email — { userId, code }, без Bearer
- Auth: reset-password — поле newPassword
- Profile: stats — byStack, totalTestsTaken, totalQuestions, correctAnswers, accuracy
- Tests: POST /tests возвращает полный список questions
- Tests: answer — полный snapshot отвеченного вопроса
- Tests: history — offset-пагинация (limit/offset), формат { tests, total }
- Admin: GET /admin/questions/pending, POST approve/reject, PATCH для редактирования
- DB: email_verification_codes, password_reset_tokens; обновлена question_cache_meta
- Security: CORS_ORIGINS из env, CSP/COEP отключены
- LLM: LLM_FALLBACK_MODEL, LLM_RETRY_DELAY_MS
- Onboarding: правило .env.example, JWT_SECRET >= 32 символов
2026-03-06 13:52:24 +03:00

340 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Схема базы данных
Описание таблиц и связей. Фактическая Drizzle-схема создаётся в `samreshu-backend`, здесь — справочник.
## Диаграмма связей
```mermaid
erDiagram
users ||--o{ subscriptions : has
users ||--o{ sessions : has
users ||--o{ email_verification_codes : has
users ||--o{ password_reset_tokens : has
users ||--o{ oauth_accounts : has
users ||--o| totp_secrets : has
users ||--o{ tests : takes
users ||--o{ user_stats : has
users ||--o{ user_achievements : earns
users ||--o{ user_question_log : tracks
tests ||--o{ test_questions : contains
question_bank ||--o{ question_cache_meta : has
question_bank ||--o{ question_reports : receives
```
## Таблицы
### users
Основная таблица пользователей.
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| email | varchar, unique | |
| password_hash | varchar | bcrypt/argon2 |
| nickname | varchar | Отображаемое имя |
| avatar_url | varchar, nullable | |
| country | varchar, nullable | |
| city | varchar, nullable | |
| self_level | enum, nullable | jun / mid / sen |
| is_public | boolean, default true | Публичный профиль |
| role | enum, default 'free' | guest / free / pro / admin |
| email_verified_at | timestamptz, nullable | |
| created_at | timestamptz | |
| updated_at | timestamptz | |
### subscriptions
Подписки пользователей. Существует с первого дня (даже для Free — с plan='free').
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| user_id | uuid, FK → users | |
| plan | enum | free / pro |
| status | enum | active / trialing / cancelled / expired |
| started_at | timestamptz | |
| expires_at | timestamptz, nullable | |
| cancelled_at | timestamptz, nullable | |
| payment_provider | varchar, nullable | yukassa / cloudpayments |
| external_id | varchar, nullable | ID подписки у провайдера |
### sessions
Активные сессии пользователя (устройства).
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| user_id | uuid, FK → users | |
| refresh_token_hash | varchar | |
| user_agent | varchar | |
| ip_address | varchar | |
| last_active_at | timestamptz | |
| expires_at | timestamptz | |
| created_at | timestamptz | |
### email_verification_codes
Коды подтверждения email (для регистрации).
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| user_id | uuid, FK → users | |
| code | varchar | Код из письма (обычно 6 цифр) |
| expires_at | timestamptz | Срок действия кода |
| created_at | timestamptz | |
### password_reset_tokens
Токены для сброса пароля.
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| user_id | uuid, FK → users | |
| token_hash | varchar | Хеш токена из письма |
| expires_at | timestamptz | Срок действия |
| created_at | timestamptz | |
### oauth_accounts
Привязанные OAuth-провайдеры (Phase 2).
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| user_id | uuid, FK → users | |
| provider | enum | github / google |
| provider_user_id | varchar | |
| created_at | timestamptz | |
### totp_secrets
2FA через TOTP (Phase 2).
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| user_id | uuid, FK → users, unique | |
| secret | varchar | Зашифрованный TOTP-секрет |
| enabled | boolean, default false | |
| created_at | timestamptz | |
### tests
Пройденные тесты.
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| user_id | uuid, FK → users | |
| stack | enum | html / css / js / ts / react / vue / nodejs / git / web_basics |
| level | enum | basic / beginner / intermediate / advanced / expert |
| question_count | integer | 10 / 20 / 30 |
| mode | enum | fixed / infinite / marathon |
| status | enum | in_progress / completed / abandoned |
| score | integer, nullable | Количество правильных |
| started_at | timestamptz | |
| finished_at | timestamptz, nullable | |
| time_limit_seconds | integer, nullable | |
### test_questions
Снепшот вопросов для конкретного теста. При старте теста вопросы копируются сюда из `question_bank` или генерируются LLM.
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| test_id | uuid, FK → tests | |
| question_bank_id | uuid, FK → question_bank, nullable | Если из банка |
| order_number | integer | Порядок в тесте |
| type | enum | single_choice / multiple_select / true_false / short_text |
| question_text | text | |
| options | jsonb, nullable | Варианты ответов |
| correct_answer | jsonb | Правильный ответ |
| explanation | text | Объяснение |
| user_answer | jsonb, nullable | Ответ пользователя |
| is_correct | boolean, nullable | |
| answered_at | timestamptz, nullable | |
### question_bank
Провалидированные вопросы для переиспользования.
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| stack | enum | |
| level | enum | |
| type | enum | |
| question_text | text | |
| options | jsonb, nullable | |
| correct_answer | jsonb | |
| explanation | text | |
| status | enum | pending / approved / rejected |
| source | enum | llm_generated / manual |
| usage_count | integer, default 0 | Сколько раз использован |
| created_at | timestamptz | |
| approved_at | timestamptz, nullable | |
### question_cache_meta
Метаданные генерации вопросов через LLM.
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| question_bank_id | uuid, FK → question_bank | |
| llm_model | varchar | Модель, сгенерировавшая вопрос |
| prompt_hash | varchar | Хеш промпта |
| generation_time_ms | integer | Время генерации в мс |
| valid | boolean | Прошёл ли валидацию с первого раза |
| retry_count | integer | Количество retry при ошибках |
| questions_generated | integer | Сколько вопросов сгенерировано |
| raw_response | text, nullable | Сырой ответ LLM (опционально, для отладки) |
| created_at | timestamptz | |
### question_reports
Жалобы пользователей на вопросы.
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| question_bank_id | uuid, FK → question_bank | |
| user_id | uuid, FK → users | |
| reason | text | |
| status | enum | open / resolved / dismissed |
| created_at | timestamptz | |
### user_stats
Агрегированная статистика по темам (обновляется после каждого теста).
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| user_id | uuid, FK → users | |
| stack | enum | |
| level | enum | |
| total_questions | integer | |
| correct_answers | integer | |
| tests_taken | integer | |
| last_test_at | timestamptz | |
Unique constraint: `(user_id, stack, level)`
### user_achievements
Бейджи и достижения (Phase 3).
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| user_id | uuid, FK → users | |
| achievement_code | varchar | Код достижения |
| earned_at | timestamptz | |
### user_question_log
Лог: какие вопросы пользователь видел (для дедупликации).
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| user_id | uuid, FK → users | |
| question_bank_id | uuid, FK → question_bank | |
| seen_at | timestamptz | |
### payments
Платежи.
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| user_id | uuid, FK → users | |
| subscription_id | uuid, FK → subscriptions | |
| amount | decimal | |
| currency | varchar, default 'RUB' | |
| status | enum | pending / succeeded / failed / refunded |
| provider | enum | yukassa / cloudpayments |
| external_id | varchar | ID у провайдера |
| created_at | timestamptz | |
### payment_events
Лог webhook-событий от платёжных систем.
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| payment_id | uuid, FK → payments, nullable | |
| provider | enum | |
| event_type | varchar | |
| payload | jsonb | Полный JSON от провайдера |
| processed | boolean, default false | |
| created_at | timestamptz | |
### audit_logs
Действия админов.
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| admin_id | uuid, FK → users | |
| action | varchar | ban_user / approve_question / ... |
| target_type | varchar | user / question / promo_code |
| target_id | uuid | |
| details | jsonb, nullable | |
| created_at | timestamptz | |
### notifications_log
История отправленных уведомлений.
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| user_id | uuid, FK → users | |
| channel | enum | email / in_app / telegram / push |
| template | varchar | verify_email / reset_password / trial_ending / ... |
| status | enum | sent / failed |
| created_at | timestamptz | |
### promo_codes
Промокоды.
| Колонка | Тип | Описание |
|---------|-----|----------|
| id | uuid, PK | |
| code | varchar, unique | |
| discount_percent | integer | |
| max_uses | integer, nullable | |
| used_count | integer, default 0 | |
| valid_from | timestamptz | |
| valid_until | timestamptz | |
| created_at | timestamptz | |
## Индексы (ключевые)
- `users.email` — unique
- `sessions.user_id` — для списка устройств
- `tests.user_id` + `tests.created_at` — для истории
- `question_bank.stack` + `question_bank.level` + `question_bank.status` — для выборки вопросов
- `user_question_log.user_id` + `user_question_log.question_bank_id` — для дедупликации
- `payments.user_id` — для истории платежей
## Примечания
- Все PK — UUID v7 (сортируемые по времени)
- Все timestamps — `timestamptz` (UTC)
- JSONB используется для вариантов ответов и webhook payload
- Enum-значения для stack/level определяются один раз и используются во всех таблицах