Files
samreshu_docs/database/schema.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

310 lines
10 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{ 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 | |
### 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 | Время генерации |
| 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 определяются один раз и используются во всех таблицах