1511 lines
60 KiB
Markdown
1511 lines
60 KiB
Markdown
# Техническое задание
|
||
## Платформа «Комптон» — React-приложение с модульной архитектурой
|
||
### Испольнителю следовать плану реализации с особым пристрастрием
|
||
|
||
**Версия документа:** 1.0
|
||
**Дата:** 09.07.2026
|
||
**Разработчик:** Huli
|
||
|
||
---
|
||
|
||
## Содержание
|
||
|
||
1. [Общие сведения](#1-общие-сведения)
|
||
2. [Цели и границы проекта](#2-цели-и-границы-проекта)
|
||
3. [Профиль нагрузки и масштабирование](#3-профиль-нагрузки-и-масштабирование)
|
||
4. [Технологический стек](#4-технологический-стек)
|
||
5. [Архитектура системы](#5-архитектура-системы)
|
||
6. [Модульная структура Frontend (React)](#6-модульная-структура-frontend-react)
|
||
7. [Модульная структура Backend (API)](#7-модульная-структура-backend-api)
|
||
8. [Модель данных](#8-модель-данных)
|
||
9. [API-контракты](#9-api-контракты)
|
||
10. [Аутентификация и авторизация](#10-аутентификация-и-авторизация)
|
||
11. [UI/UX и дизайн-система](#11-uiux-и-дизайн-система)
|
||
12. [Нефункциональные требования](#12-нефункциональные-требования)
|
||
13. [Инфраструктура и DevOps](#13-инфраструктура-и-devops)
|
||
14. [Безопасность](#14-безопасность)
|
||
15. [Тестирование и качество](#15-тестирование-и-качество)
|
||
16. [Этапы разработки](#16-этапы-разработки)
|
||
17. [Критерии приёмки](#17-критерии-приёмки)
|
||
18. [Риски и допущения](#18-риски-и-допущения)
|
||
|
||
---
|
||
|
||
## 1. Общие сведения
|
||
|
||
### 1.1. Наименование проекта
|
||
|
||
**Комптон®** — веб-платформа бренда «Organic Tech» с публичной витриной, личным кабинетом пользователя и административной панелью.
|
||
|
||
### 1.2. Текущее состояние
|
||
|
||
- Чисто поржать на ошибках и нейронке
|
||
- Заглушка на Flask (`main.py`) с одностраничным лендингом.
|
||
- Визуальная идентичность задана не полностью: цвета, шрифты (Inter, JetBrains Mono), hero-блок, бегущая строка.
|
||
- Рефакторинг текщего состояния не возможен, задача переписать проект.
|
||
- Функциональность отсутствует: нет регистрации, контента, API, БД.
|
||
|
||
|
||
### 1.3. Заказчик / продукт
|
||
|
||
Еблан который зовет себя Huli. Документ описывает целевую архитектуру для замены заглушки полноценным продуктом.
|
||
|
||
### 1.4. Термины
|
||
|
||
| Термин | Определение |
|
||
|--------|-------------|
|
||
| **Модуль (feature)** | Изолированный функциональный блок с собственными компонентами, API-слоем, типами и тестами |
|
||
| **Shared** | Переиспользуемый код без бизнес-логики конкретного модуля |
|
||
| **Core** | Ядро приложения: роутинг, провайдеры, конфигурация, HTTP-клиент |
|
||
| **MAU** | Monthly Active Users — уникальные пользователи за месяц |
|
||
| **CCU** | Concurrent Users — одновременно онлайн |
|
||
|
||
---
|
||
|
||
## 2. Цели и границы проекта
|
||
|
||
### 2.1. Бизнес-цели
|
||
|
||
1. Заменить заглушку работающим сайтом с сохранением бренда.
|
||
2. Обеспечить регистрацию и работу **до 1 000 зарегистрированных пользователей** (~100–150 DAU) без деградации UX.
|
||
3. Заложить архитектуру, позволяющую масштабироваться до **10 000+ пользователей** без переписывания модулей.
|
||
4. Обеспечить независимую разработку модулей разными разработчиками.
|
||
5. **Каждый шаг реализации** (задача / PR / модуль) поставляется **только с полным набором тестов**: unit/integration логики + E2E пользовательских сценариев (§15).
|
||
|
||
### 2.2. Функциональный scope (MVP → v1.0)
|
||
|
||
#### MVP (фаза 1)
|
||
|
||
| Модуль | Функции |
|
||
|--------|---------|
|
||
| **Landing** | Hero, бегущая строка, «О бренде», контакты, SEO-мета |
|
||
| **Auth** | Регистрация, вход, выход, восстановление пароля, подтверждение email *(требует SMTP в MVP — см. §16)* |
|
||
| **Profile** | Просмотр/редактирование профиля, аватар, смена пароля |
|
||
| **Content** | Статические страницы (О нас, Политика, Условия) из CMS или markdown |
|
||
| **Admin** | Управление пользователями, контентом, просмотр метрик |
|
||
|
||
#### v1.0 (фаза 2, опционально)
|
||
|
||
| Модуль | Функции |
|
||
|--------|---------|
|
||
| **Catalog** | Каталог продуктов/услуг, карточки, фильтры |
|
||
| **Orders** | Корзина, оформление заявки (без оплаты — см. §2.3), история |
|
||
| **Notifications** | In-app уведомления; email-рассылки через очередь (Celery) |
|
||
| **Analytics** | Дашборд событий, экспорт |
|
||
|
||
### 2.3. Out of scope (не входит в v1.0)
|
||
|
||
- Мобильное нативное приложение (только responsive web).
|
||
- Мультиязычность (заложить i18n-структуру, реализовать позже).
|
||
- Платёжные шлюзы (интеграция — отдельный этап). Модуль Orders в v1.0 работает как **заявка/бронирование без оплаты**.
|
||
- Real-time чат / WebRTC.
|
||
|
||
---
|
||
|
||
## 3. Профиль нагрузки и масштабирование
|
||
|
||
### 3.1. Целевые метрики (1000 пользователей)
|
||
|
||
| Метрика | Значение | Комментарий |
|
||
|---------|----------|-------------|
|
||
| Зарегистрированных пользователей | 1 000 | Целевой объём на старте |
|
||
| DAU | 100–150 (10–15%) | Типичный коэффициент для B2C |
|
||
| CCU (пик) | 30–50 | Одновременные сессии |
|
||
| RPS API (пик) | 20–50 req/s | С запасом ×3 |
|
||
| Размер БД | < 5 GB | Профили, контент, логи |
|
||
| Медиа | < 50 GB | CDN для статики |
|
||
|
||
### 3.2. Путь масштабирования
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph phase1 [Фаза 1: до 1K]
|
||
A1[Monolith API]
|
||
A2[PostgreSQL]
|
||
A3[Redis cache]
|
||
A4[CDN static]
|
||
end
|
||
|
||
subgraph phase2 [Фаза 2: 1K–10K]
|
||
B1[2× API instances]
|
||
B2[Read replica PG]
|
||
B3[Redis cluster]
|
||
B4[Object storage S3]
|
||
end
|
||
|
||
subgraph phase3 [Фаза 3: 10K+]
|
||
C1[Horizontal API scale]
|
||
C2[Extract heavy modules]
|
||
C3[Message queue]
|
||
C4[Separate admin]
|
||
end
|
||
|
||
phase1 --> phase2 --> phase3
|
||
```
|
||
|
||
**Принцип:** на 1000 пользователей достаточно **модульного монолита** (один backend-процесс, чёткие границы модулей). Микросервисы не нужны до 10K+ и только для узких «тяжёлых» модулей (уведомления, аналитика).
|
||
|
||
### 3.3. SLA (целевые)
|
||
|
||
| Параметр | MVP | v1.0 |
|
||
|----------|-----|------|
|
||
| Uptime | 99.5% | 99.9% |
|
||
| TTFB публичных страниц | < 500 ms | < 300 ms |
|
||
| API p95 latency | < 300 ms | < 200 ms |
|
||
| LCP (Lighthouse mobile) | < 2.5 s | < 2.0 s |
|
||
|
||
---
|
||
|
||
## 4. Технологический стек
|
||
|
||
### 4.1. Frontend
|
||
|
||
| Слой | Технология | Обоснование |
|
||
|------|------------|-------------|
|
||
| Framework | **React 19** + **TypeScript 5** | Экосистема, типизация |
|
||
| Bundler | **Vite 6** | Быстрая сборка, HMR |
|
||
| Routing | **React Router 7** | Стандарт de facto |
|
||
| Server state | **TanStack Query 5** | Кеш, retry, invalidation |
|
||
| Client state | **Zustand** | Лёгкий, без boilerplate |
|
||
| Forms | **React Hook Form** + **Zod** | Валидация, DX |
|
||
| UI | **Tailwind CSS 4** + headless (Radix UI) | Соответствие дизайн-системе |
|
||
| HTTP | **Axios** или fetch-обёртка | Interceptors, типизация |
|
||
| i18n (заготовка) | **react-i18next** | Структура без реализации |
|
||
| Tests | **Vitest** + **Testing Library** + **Playwright** + **pytest** | Unit + integration + E2E на каждом PR (§15) |
|
||
|
||
### 4.2. Backend
|
||
|
||
| Слой | Технология | Обоснование |
|
||
|------|------------|-------------|
|
||
| Runtime | **Python 3.12** | Преемственность Flask-проекта |
|
||
| Framework | **FastAPI** | Async, OpenAPI, производительность |
|
||
| ORM | **SQLAlchemy 2** + **Alembic** | Миграции, типизация |
|
||
| Validation | **Pydantic v2** | Согласованность с OpenAPI |
|
||
| Auth | **JWT** (access + refresh rotation) + **passlib/bcrypt** | Stateless access, revocable refresh |
|
||
| Task queue (v1.0) | **Celery** + Redis | Email, фоновые задачи |
|
||
| Email | **SMTP** / SendGrid / Resend | MVP: синхронная отправка verify/reset; v1.0: Celery queue |
|
||
|
||
### 4.3. Data & Infra
|
||
|
||
| Компонент | Технология |
|
||
|-----------|------------|
|
||
| Primary DB | **PostgreSQL 16** |
|
||
| Cache / sessions | **Redis 7** |
|
||
| File storage | **S3-compatible** (MinIO dev / Yandex S3 / AWS prod) |
|
||
| Reverse proxy | **Nginx** |
|
||
| Containerization | **Docker** + **Docker Compose** (dev/staging) |
|
||
| CI/CD | **GitHub Actions** |
|
||
| Monitoring | **Prometheus** + **Grafana** (или managed: Datadog) |
|
||
| Logging | Structured JSON → **Loki** или cloud logs |
|
||
| Error tracking | **Sentry** |
|
||
|
||
### 4.4. Архитектурный стиль Frontend
|
||
|
||
**Feature-Sliced Design (FSD)** — адаптированная версия с явными модулями:
|
||
|
||
```
|
||
app → pages → modules → shared
|
||
```
|
||
|
||
- **app** — инициализация, провайдеры, глобальный роутер.
|
||
- **pages** — композиция модулей в маршруты (тонкий слой).
|
||
- **modules** — бизнес-фичи (auth, profile, catalog…).
|
||
- **shared** — UI-kit, utils, API client, types без доменной логики.
|
||
|
||
---
|
||
|
||
## 5. Архитектура системы
|
||
|
||
### 5.1. Общая схема
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph client [Клиент]
|
||
Browser[Browser / Mobile Web]
|
||
SPA[React SPA]
|
||
end
|
||
|
||
subgraph edge [Edge]
|
||
CDN[CDN — static assets]
|
||
Nginx[Nginx — TLS, gzip, rate limit]
|
||
end
|
||
|
||
subgraph backend [Backend]
|
||
API[FastAPI Monolith]
|
||
subgraph modules_api [Modules]
|
||
M1[auth]
|
||
M2[users]
|
||
M3[content]
|
||
M4[admin]
|
||
end
|
||
end
|
||
|
||
subgraph data [Data Layer]
|
||
PG[(PostgreSQL)]
|
||
Redis[(Redis)]
|
||
S3[(Object Storage)]
|
||
end
|
||
|
||
Browser --> CDN
|
||
Browser --> Nginx
|
||
Nginx -->|static| SPA
|
||
SPA -->|REST JSON /api| Nginx
|
||
Nginx --> API
|
||
API --> modules_api
|
||
modules_api --> PG
|
||
modules_api --> Redis
|
||
modules_api --> S3
|
||
```
|
||
|
||
### 5.2. Принципы модульности
|
||
|
||
1. **Вертикальные срезы** — каждый модуль владеет UI + API + схемой БД (таблицы через общий ORM, но namespace по модулю).
|
||
2. **Запрет cross-import между modules** — общение только через:
|
||
- публичный API модуля (`modules/auth/api/index.ts`);
|
||
- shared-слой;
|
||
- backend REST/events.
|
||
3. **Публичный контракт модуля** — `index.ts` экспортирует только то, что нужно снаружи.
|
||
4. **Приватная реализация** — `internal/` не импортируется из других модулей.
|
||
|
||
### 5.3. Репозиторий (monorepo-lite)
|
||
|
||
```
|
||
compton/
|
||
├── apps/
|
||
│ ├── web/ # React SPA
|
||
│ └── api/ # FastAPI
|
||
├── packages/
|
||
│ ├── shared-types/ # OpenAPI-generated TS types
|
||
│ └── eslint-config/ # Общие lint rules
|
||
├── infra/
|
||
│ ├── docker/
|
||
│ ├── nginx/
|
||
│ └── terraform/ # опционально
|
||
├── docs/
|
||
│ └── TZ.md
|
||
└── docker-compose.yml
|
||
```
|
||
|
||
На старте допустим **single repo** без Turborepo; `packages/shared-types` генерируется из OpenAPI при CI.
|
||
|
||
---
|
||
|
||
## 6. Модульная структура Frontend (React)
|
||
|
||
### 6.1. Дерево каталогов `apps/web/src`
|
||
|
||
```
|
||
src/
|
||
├── app/
|
||
│ ├── App.tsx
|
||
│ ├── providers/
|
||
│ │ ├── QueryProvider.tsx
|
||
│ │ ├── AuthProvider.tsx
|
||
│ │ └── ThemeProvider.tsx
|
||
│ ├── router/
|
||
│ │ ├── routes.tsx
|
||
│ │ └── guards/
|
||
│ │ ├── AuthGuard.tsx
|
||
│ │ └── AdminGuard.tsx
|
||
│ └── styles/
|
||
│ └── globals.css
|
||
│
|
||
├── pages/
|
||
│ ├── HomePage/
|
||
│ ├── LoginPage/
|
||
│ ├── RegisterPage/
|
||
│ ├── ProfilePage/
|
||
│ ├── ContentPage/
|
||
│ └── AdminPage/
|
||
│
|
||
├── modules/
|
||
│ ├── landing/
|
||
│ │ ├── index.ts # public API
|
||
│ │ ├── components/
|
||
│ │ │ ├── HeroSection.tsx
|
||
│ │ │ └── MarqueeSection.tsx
|
||
│ │ ├── hooks/
|
||
│ │ └── types/
|
||
│ │
|
||
│ ├── auth/
|
||
│ │ ├── index.ts
|
||
│ │ ├── api/
|
||
│ │ │ └── authApi.ts
|
||
│ │ ├── components/
|
||
│ │ │ ├── LoginForm.tsx
|
||
│ │ │ └── RegisterForm.tsx
|
||
│ │ ├── hooks/
|
||
│ │ │ ├── useLogin.ts
|
||
│ │ │ └── useAuth.ts
|
||
│ │ ├── store/
|
||
│ │ │ └── authStore.ts
|
||
│ │ └── types/
|
||
│ │
|
||
│ ├── profile/
|
||
│ │ ├── index.ts
|
||
│ │ ├── api/
|
||
│ │ ├── components/
|
||
│ │ ├── hooks/
|
||
│ │ └── types/
|
||
│ │
|
||
│ ├── content/
|
||
│ │ ├── index.ts
|
||
│ │ ├── api/
|
||
│ │ ├── components/
|
||
│ │ └── hooks/
|
||
│ │
|
||
│ ├── admin/
|
||
│ │ ├── index.ts
|
||
│ │ ├── api/
|
||
│ │ ├── components/
|
||
│ │ └── hooks/
|
||
│ │
|
||
│ └── catalog/ # v1.0
|
||
│ └── ...
|
||
│
|
||
├── __tests__/ # cross-module integration (frontend)
|
||
│ └── setup.ts
|
||
│
|
||
└── shared/
|
||
├── api/
|
||
│ ├── client.ts
|
||
│ ├── client.test.ts
|
||
│ ├── errors.ts
|
||
│ └── types.ts
|
||
├── ui/
|
||
│ ├── Button/
|
||
│ │ ├── Button.tsx
|
||
│ │ └── Button.test.tsx
|
||
│ └── ...
|
||
└── ...
|
||
|
||
# Каждый module обязан содержать:
|
||
modules/<name>/
|
||
├── __tests__/ # unit: hooks, utils, store
|
||
├── components/
|
||
│ └── *.test.tsx # component tests (Testing Library)
|
||
└── api/
|
||
└── *.test.ts # API client + mock handlers
|
||
```
|
||
|
||
### 6.2. Правила зависимостей (import rules)
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
app --> pages
|
||
pages --> modules
|
||
modules --> shared
|
||
modules -.->|FORBIDDEN| modules
|
||
shared -.->|FORBIDDEN| modules
|
||
shared -.->|FORBIDDEN| pages
|
||
```
|
||
|
||
Enforcement через **ESLint** (`eslint-plugin-boundaries` или custom rules):
|
||
|
||
| From → To | app | pages | modules | shared |
|
||
|-----------|-----|-------|---------|--------|
|
||
| app | ✓ | ✓ | ✓ | ✓ |
|
||
| pages | — | ✓ | ✓ | ✓ |
|
||
| modules | — | — | ✓ (own) | ✓ |
|
||
| shared | — | — | ✗ | ✓ |
|
||
|
||
### 6.3. Описание модулей Frontend
|
||
|
||
#### 6.3.1. `landing`
|
||
|
||
**Ответственность:**
|
||
|
||
- Буду ебать за каждый нейрослоп
|
||
|
||
**Зависимости:** только `shared/ui`, `shared/hooks`.
|
||
|
||
#### 6.3.2. `auth`
|
||
|
||
**Ответственность:** аутентификация, сессия, guards.
|
||
|
||
| Export (public API) | Описание |
|
||
|---------------------|----------|
|
||
| `LoginForm`, `RegisterForm` | Формы |
|
||
| `useAuth()` | `{ user, isAuthenticated, login, logout, refreshSession }` |
|
||
|
||
**Guards** (`AuthGuard`, `GuestGuard`, `AdminGuard`) живут в `app/router/guards/` — они **импортируют** `useAuth` из модуля `auth`, но не экспортируются из него (слой `app` композирует модули).
|
||
|
||
**Store:** `accessToken` только in-memory (Zustand). `refreshToken` **только** httpOnly Secure cookie (`SameSite=Lax`). Хранение refresh в `localStorage` / `sessionStorage` **запрещено**.
|
||
|
||
#### 6.3.3. `profile`
|
||
|
||
**Ответственность:** CRUD профиля пользователя.
|
||
|
||
| Функции | API endpoints |
|
||
|---------|---------------|
|
||
| Просмотр профиля | `GET /api/v1/users/me` |
|
||
| Редактирование | `PATCH /api/v1/users/me` |
|
||
| Смена пароля (авторизован) | `POST /api/v1/users/me/password` |
|
||
| Загрузка аватара | `POST /api/v1/users/me/avatar` |
|
||
|
||
#### 6.3.4. `content`
|
||
|
||
**Ответственность:** рендер CMS-страниц по slug. Запись — только для `admin` (через те же роуты с RBAC-проверкой; отдельный `admin`-модуль не дублирует CRUD контента).
|
||
|
||
| Функции | Описание |
|
||
|---------|----------|
|
||
| `ContentPage` | `/about`, `/privacy`, `/terms` |
|
||
| SEO | meta title, description, og:image |
|
||
|
||
#### 6.3.5. `admin`
|
||
|
||
**Ответственность:** панель администратора (role: `admin`).
|
||
|
||
| Раздел | Функции |
|
||
|--------|---------|
|
||
| Users | список, блокировка, смена роли *(с ограничениями — §14.10)* |
|
||
| Content | — *(CRUD контента — модуль `content`, §9.4)* |
|
||
| Dashboard | базовые метрики (users count, registrations/day) |
|
||
|
||
### 6.4. Роутинг
|
||
|
||
| Маршрут | Page | Guard | Модуль(и) |
|
||
|---------|------|-------|-----------|
|
||
| `/` | HomePage | — | landing |
|
||
| `/login` | LoginPage | guest only | auth |
|
||
| `/register` | RegisterPage | guest only | auth |
|
||
| `/profile` | ProfilePage | AuthGuard | profile |
|
||
| `/pages/:slug` | ContentPage | — | content |
|
||
| `/admin/*` | AdminPage | AdminGuard | admin |
|
||
|
||
### 6.5. Code splitting
|
||
|
||
- Lazy load: `admin`, `profile`, `catalog`.
|
||
- Prefetch on hover для `/login`, `/register`.
|
||
- Landing — в initial bundle (LCP-critical).
|
||
|
||
---
|
||
|
||
## 7. Модульная структура Backend (API)
|
||
|
||
### 7.1. Дерево каталогов `apps/api`
|
||
|
||
```
|
||
app/
|
||
├── main.py # FastAPI app factory
|
||
├── core/
|
||
│ ├── config.py
|
||
│ ├── database.py
|
||
│ ├── redis.py
|
||
│ ├── security.py # JWT, password hashing
|
||
│ ├── dependencies.py # get_db, get_current_user
|
||
│ └── exceptions.py
|
||
│
|
||
├── modules/
|
||
│ ├── auth/
|
||
│ │ ├── router.py
|
||
│ │ ├── service.py
|
||
│ │ ├── schemas.py
|
||
│ │ └── repository.py
|
||
│ │
|
||
│ ├── users/
|
||
│ │ ├── router.py
|
||
│ │ ├── service.py
|
||
│ │ ├── models.py
|
||
│ │ ├── schemas.py
|
||
│ │ └── repository.py
|
||
│ │
|
||
│ ├── content/
|
||
│ │ ├── router.py
|
||
│ │ ├── service.py
|
||
│ │ ├── models.py
|
||
│ │ └── schemas.py
|
||
│ │
|
||
│ ├── media/
|
||
│ │ ├── router.py
|
||
│ │ ├── service.py # S3 upload
|
||
│ │ └── schemas.py
|
||
│ │
|
||
│ └── admin/
|
||
│ ├── router.py
|
||
│ ├── service.py
|
||
│ └── schemas.py
|
||
│
|
||
├── migrations/ # Alembic
|
||
└── tests/
|
||
├── conftest.py # fixtures: db, client, test users
|
||
├── factories.py # user/content factories
|
||
├── modules/
|
||
│ ├── auth/
|
||
│ │ ├── test_service.py # unit: бизнес-логика
|
||
│ │ ├── test_router.py # integration: HTTP + DB
|
||
│ │ └── test_security.py # rotation, lockout, enumeration
|
||
│ ├── users/
|
||
│ ├── content/
|
||
│ └── admin/
|
||
└── e2e/ # pytest API smoke (optional layer)
|
||
└── test_health.py
|
||
```
|
||
|
||
### 7.2. Слои внутри модуля (Clean Architecture lite)
|
||
|
||
```
|
||
Router → Service → Repository → Model
|
||
↓
|
||
Schemas (Pydantic)
|
||
```
|
||
|
||
| Слой | Ответственность |
|
||
|------|-----------------|
|
||
| **Router** | HTTP, status codes, dependency injection |
|
||
| **Service** | Бизнес-логика, orchestration |
|
||
| **Repository** | SQL-запросы, абстракция БД |
|
||
| **Schemas** | Request/Response DTO |
|
||
| **Models** | SQLAlchemy ORM |
|
||
|
||
**Правило:** модуль не импортирует `repository` другого модуля. Межмодульные вызовы — только через **публичный `service`-фасад** (например, `users.public.get_by_id()`) или domain events.
|
||
|
||
### 7.3. Версионирование API
|
||
|
||
- Префикс: `/api/v1/`
|
||
- Breaking changes → `/api/v2/`
|
||
- OpenAPI: `/api/v1/openapi.json`
|
||
- Swagger UI: `/api/v1/docs` (только dev/staging)
|
||
|
||
---
|
||
|
||
## 8. Модель данных
|
||
|
||
### 8.1. ER-диаграмма (MVP)
|
||
|
||
```mermaid
|
||
erDiagram
|
||
users ||--o| user_profiles : has
|
||
users ||--o{ refresh_tokens : has
|
||
users ||--o{ password_reset_tokens : has
|
||
users ||--o{ content_pages : creates
|
||
|
||
users {
|
||
uuid id PK
|
||
string email UK
|
||
string password_hash
|
||
enum role "user|admin"
|
||
enum status "active|blocked|pending"
|
||
timestamp email_verified_at
|
||
int failed_login_attempts
|
||
timestamp locked_until
|
||
timestamp created_at
|
||
timestamp updated_at
|
||
}
|
||
|
||
user_profiles {
|
||
uuid user_id PK,FK
|
||
string display_name
|
||
string avatar_url
|
||
json metadata
|
||
}
|
||
|
||
refresh_tokens {
|
||
uuid id PK
|
||
uuid user_id FK
|
||
string token_hash UK
|
||
uuid family_id
|
||
timestamp expires_at
|
||
timestamp revoked_at
|
||
timestamp created_at
|
||
}
|
||
|
||
password_reset_tokens {
|
||
uuid id PK
|
||
uuid user_id FK
|
||
string token_hash UK
|
||
timestamp expires_at
|
||
timestamp used_at
|
||
}
|
||
|
||
content_pages {
|
||
uuid id PK
|
||
string slug UK
|
||
string title
|
||
text body
|
||
enum status "draft|published"
|
||
uuid author_id FK
|
||
timestamp published_at
|
||
timestamp updated_at
|
||
}
|
||
```
|
||
|
||
### 8.2. Индексы
|
||
|
||
```sql
|
||
CREATE UNIQUE INDEX idx_users_email ON users(email);
|
||
CREATE INDEX idx_users_status ON users(status);
|
||
CREATE INDEX idx_content_slug ON content_pages(slug);
|
||
CREATE INDEX idx_content_status ON content_pages(status);
|
||
CREATE INDEX idx_refresh_tokens_user ON refresh_tokens(user_id);
|
||
CREATE INDEX idx_refresh_tokens_family ON refresh_tokens(family_id);
|
||
CREATE INDEX idx_password_reset_user ON password_reset_tokens(user_id);
|
||
```
|
||
|
||
### 8.3. Миграции
|
||
|
||
- Alembic, одна миграция = одна логическая задача.
|
||
- Rollback обязателен для каждой миграции.
|
||
- Seed: admin user, demo content pages.
|
||
|
||
---
|
||
|
||
## 9. API-контракты
|
||
|
||
### 9.1. Общие соглашения
|
||
|
||
| Аспект | Стандарт |
|
||
|--------|----------|
|
||
| Format | JSON, UTF-8 |
|
||
| Dates | ISO 8601 UTC (`2026-07-09T12:00:00Z`) |
|
||
| IDs | UUID v4 |
|
||
| Pagination | `?page=1&limit=20`, response: `{ data, meta: { total, page, limit } }` |
|
||
| Errors | `{ "error": { "code": "VALIDATION_ERROR", "message": "...", "details": [] } }` |
|
||
|
||
### 9.2. Auth endpoints
|
||
|
||
| Method | Path | Auth | Описание |
|
||
|--------|------|------|----------|
|
||
| POST | `/api/v1/auth/register` | — | Регистрация → `status: pending`, письмо с подтверждением |
|
||
| POST | `/api/v1/auth/login` | — | Вход → access в body, refresh в `Set-Cookie` |
|
||
| POST | `/api/v1/auth/refresh` | refresh cookie | Новая пара access + refresh (rotation) |
|
||
| POST | `/api/v1/auth/logout` | refresh cookie | Revoke token family, очистка cookie |
|
||
| POST | `/api/v1/auth/verify-email` | — | Body: `{ "token": "..." }` → `status: active` |
|
||
| POST | `/api/v1/auth/resend-verification` | — | Повторная отправка (rate limited) |
|
||
| POST | `/api/v1/auth/forgot-password` | — | Отправка reset-link (без раскрытия наличия email) |
|
||
| POST | `/api/v1/auth/reset-password` | — | Смена пароля по одноразовому token |
|
||
|
||
**Правила ответов auth (anti-enumeration):**
|
||
- `register` с существующим email → **200** с нейтральным телом *или* **409** без указания причины в prod (на выбор; зафиксировать в реализации).
|
||
- `forgot-password` → всегда **200** «Если email зарегистрирован, письмо отправлено».
|
||
- `login` при неверных данных → **401** с общим сообщением «Неверный email или пароль».
|
||
- `login` при `status: pending` → **403** `EMAIL_NOT_VERIFIED`.
|
||
- `login` при `status: blocked` → **403** `ACCOUNT_BLOCKED`.
|
||
- `login` при блокировке brute-force → **429** `ACCOUNT_TEMPORARILY_LOCKED`.
|
||
|
||
**Response login (200):**
|
||
```json
|
||
{
|
||
"access_token": "eyJ...",
|
||
"token_type": "bearer",
|
||
"expires_in": 900,
|
||
"user": {
|
||
"id": "uuid",
|
||
"email": "user@example.com",
|
||
"role": "user",
|
||
"status": "active"
|
||
}
|
||
}
|
||
```
|
||
|
||
**Set-Cookie (login / refresh — обязательно):**
|
||
```
|
||
Set-Cookie: refresh_token=<opaque>; HttpOnly; Secure; SameSite=Lax; Path=/api/v1/auth; Max-Age=2592000
|
||
```
|
||
|
||
> Refresh token **никогда** не возвращается в JSON-body.
|
||
|
||
### 9.3. Users endpoints
|
||
|
||
| Method | Path | Auth | Описание |
|
||
|--------|------|------|----------|
|
||
| GET | `/api/v1/users/me` | user | Текущий пользователь + profile |
|
||
| PATCH | `/api/v1/users/me` | user | Обновление профиля (whitelist полей) |
|
||
| POST | `/api/v1/users/me/password` | user | `{ current_password, new_password }` |
|
||
| POST | `/api/v1/users/me/avatar` | user | multipart upload |
|
||
| DELETE | `/api/v1/users/me` | user | Удаление аккаунта (v1.0, soft-delete + anonymize) |
|
||
|
||
### 9.4. Content endpoints
|
||
|
||
| Method | Path | Auth | Описание |
|
||
|--------|------|------|----------|
|
||
| GET | `/api/v1/content/pages` | — | Список published pages |
|
||
| GET | `/api/v1/content/pages/{slug}` | — | Страница по slug |
|
||
| POST | `/api/v1/content/pages` | admin | Создание |
|
||
| PATCH | `/api/v1/content/pages/{id}` | admin | Обновление |
|
||
| DELETE | `/api/v1/content/pages/{id}` | admin | Удаление |
|
||
|
||
### 9.5. Admin endpoints
|
||
|
||
| Method | Path | Auth | Описание |
|
||
|--------|------|------|----------|
|
||
| GET | `/api/v1/admin/users` | admin | Список пользователей (pagination, без password_hash) |
|
||
| PATCH | `/api/v1/admin/users/{id}` | admin | Блокировка, смена роли *(§14.10)* |
|
||
| GET | `/api/v1/admin/stats` | admin | Базовые метрики |
|
||
| GET | `/api/v1/admin/audit-log` | admin | Журнал действий админов (v1.0) |
|
||
|
||
### 9.6. Rate limiting
|
||
|
||
| Endpoint group | Limit | Key |
|
||
|----------------|-------|-----|
|
||
| Auth: login | 5 req/min | IP + email (composite) |
|
||
| Auth: register | 3 req/hour | IP |
|
||
| Auth: forgot-password, resend-verification | 3 req/hour | IP + email |
|
||
| Auth: refresh | 30 req/min | user_id (из token) |
|
||
| Public API | 100 req/min | IP |
|
||
| Authenticated API | 300 req/min | user_id (fallback: IP) |
|
||
| Upload (avatar) | 10 req/hour | user_id |
|
||
|
||
При превышении → **429** + заголовок `Retry-After`.
|
||
|
||
**Brute-force (login):** после 5 неудачных попыток за 15 мин — `locked_until = now + 15 min` (поле в `users`).
|
||
|
||
---
|
||
|
||
## 10. Аутентификация и авторизация
|
||
|
||
### 10.1. JWT Strategy
|
||
|
||
| Token | Формат | TTL | Storage | Передача |
|
||
|-------|--------|-----|---------|----------|
|
||
| Access | JWT (HS256 или RS256) | 15 min | In-memory (JS) | `Authorization: Bearer` |
|
||
| Refresh | Opaque random (256 bit) | 30 days | httpOnly Secure cookie | Cookie `refresh_token` |
|
||
|
||
**Claims access JWT:** `sub` (user_id), `role`, `iat`, `exp`, `jti` (уникальный ID для optional denylist).
|
||
|
||
**Refresh token:**
|
||
- В БД хранится **только SHA-256 hash**, не plaintext.
|
||
- **Rotation:** каждый `/auth/refresh` выдаёт новый refresh, старый revoke.
|
||
- **Reuse detection:** повторное использование revoked token → revoke всей `family_id`, принудительный logout на всех устройствах.
|
||
- Cookie `Path=/api/v1/auth` — не отправляется на остальные API-роуты (минимизация поверхности CSRF).
|
||
|
||
### 10.2. RBAC
|
||
|
||
| Role | Permissions |
|
||
|------|-------------|
|
||
| `guest` | Чтение опубликованного контента |
|
||
| `user` (`status: active`) | Управление своим профилем |
|
||
| `admin` | Управление пользователями, контентом, stats; **не может** изменить собственную роль (§14.10) |
|
||
|
||
**Проверка на каждом protected endpoint:**
|
||
1. Валидный access JWT (подпись, exp, jti).
|
||
2. Пользователь существует и `status != blocked`.
|
||
3. Для admin-роутов — `role == admin`.
|
||
4. Для user-роутов — `sub == resource owner` (защита от IDOR).
|
||
|
||
`pending`-пользователь может вызвать только: verify-email, resend-verification, logout.
|
||
|
||
### 10.3. Password policy
|
||
|
||
- Минимум 8 символов, 1 uppercase, 1 lowercase, 1 digit.
|
||
- Denylist топ-10k паролей (Have I Been Pwned или локальный список).
|
||
- bcrypt, cost factor 12.
|
||
- Reset/verify token: 256 bit random, **hash в БД**, TTL 1 hour, одноразовый.
|
||
- При смене/сбросе пароля — revoke все `refresh_tokens` пользователя.
|
||
|
||
### 10.4. CORS и credentials
|
||
|
||
```python
|
||
# FastAPI CORSMiddleware
|
||
allow_origins = CORS_ORIGINS # whitelist, без wildcard в prod
|
||
allow_credentials = True # обязательно для refresh cookie
|
||
allow_methods = ["GET", "POST", "PATCH", "DELETE", "OPTIONS"]
|
||
allow_headers = ["Authorization", "Content-Type", "X-Request-ID"]
|
||
```
|
||
|
||
Frontend HTTP-клиент: `withCredentials: true` только для auth-запросов (login, refresh, logout).
|
||
|
||
### 10.5. CSRF (cookie-based refresh)
|
||
|
||
| Механизм | Применение |
|
||
|----------|------------|
|
||
| `SameSite=Lax` на refresh cookie | Блокирует cross-site POST в большинстве браузеров |
|
||
| `Path=/api/v1/auth` | Cookie не уходит на PATCH/POST других ресурсов |
|
||
| Проверка `Origin` / `Referer` на auth-роутах | Backend отклоняет запросы с чужого origin |
|
||
| Access JWT в header (не cookie) | State-changing API через Bearer не уязвим к CSRF |
|
||
|
||
Дополнительный CSRF-token **не требуется** при соблюдении схемы выше. Если refresh cookie когда-либо расширят на весь `/api` — добавить double-submit CSRF token.
|
||
|
||
---
|
||
|
||
## 11. UI/UX и дизайн-система
|
||
|
||
### 11.1. Design tokens (из текущей заглушки)
|
||
|
||
```css
|
||
:root {
|
||
--bg: #F6F6F4;
|
||
--foreground: #1A1E1C;
|
||
--primary: #48816D;
|
||
--muted: #4A5A52;
|
||
--marquee-bg: #EBEBE5;
|
||
}
|
||
```
|
||
|
||
### 11.2. Типографика
|
||
|
||
| Назначение | Шрифт |
|
||
|------------|-------|
|
||
| UI / body | Inter (400, 500, 600) |
|
||
| Mono / акценты | JetBrains Mono (400, 500) |
|
||
| Декоративный (опционально) | Oktyabrina Script |
|
||
|
||
### 11.3. Breakpoints
|
||
|
||
| Token | Width |
|
||
|-------|-------|
|
||
| `sm` | 380px |
|
||
| `md` | 480px |
|
||
| `lg` | 768px |
|
||
| `xl` | 1024px |
|
||
| `2xl` | 1280px |
|
||
|
||
### 11.4. Компоненты shared/ui (MVP)
|
||
|
||
- Button (primary, secondary, ghost)
|
||
- Input, Textarea
|
||
- Label, ErrorMessage
|
||
- Card, Modal, Spinner
|
||
- Header, Footer, Container
|
||
- Avatar, Badge
|
||
|
||
### 11.5. Accessibility
|
||
|
||
- WCAG 2.1 AA для MVP.
|
||
- Keyboard navigation, focus visible.
|
||
- `alt` для всех изображений.
|
||
- `prefers-reduced-motion` — отключение marquee-анимации.
|
||
|
||
---
|
||
|
||
## 12. Нефункциональные требования
|
||
|
||
### 12.1. Performance
|
||
|
||
| Метрика | Target |
|
||
|---------|--------|
|
||
| Initial JS bundle (landing) | < 150 KB gzip |
|
||
| Total JS (app loaded) | < 350 KB gzip |
|
||
| API cache (public content) | Redis TTL 5 min |
|
||
| Static assets | CDN, cache-control 1 year (hash in filename) |
|
||
|
||
### 12.2. SEO
|
||
|
||
- SSR/SSG **не обязателен** для MVP (SPA + prerender landing через Vite SSG plugin).
|
||
- Meta tags, sitemap.xml, robots.txt.
|
||
- Semantic HTML (`header`, `main`, `section`).
|
||
|
||
### 12.3. Observability
|
||
|
||
| Signal | Tool |
|
||
|--------|------|
|
||
| Logs | JSON structured, correlation-id |
|
||
| Metrics | request duration, error rate, DB pool |
|
||
| Traces | OpenTelemetry (optional v1.0) |
|
||
| Frontend errors | Sentry browser SDK |
|
||
|
||
### 12.4. Backup & Recovery
|
||
|
||
- PostgreSQL: daily backup, retention 30 days.
|
||
- RPO: 24 hours, RTO: 4 hours (на 1000 users достаточно).
|
||
|
||
---
|
||
|
||
## 13. Инфраструктура и DevOps
|
||
|
||
### 13.1. Окружения
|
||
|
||
| Env | Назначение | URL |
|
||
|-----|------------|-----|
|
||
| local | разработка | localhost:5173 (web), :8000 (api) |
|
||
| staging | QA, demo | staging.compton.example |
|
||
| production | prod | compton.example |
|
||
|
||
### 13.2. Docker Compose (local)
|
||
|
||
```yaml
|
||
services:
|
||
web:
|
||
build: ./apps/web
|
||
ports: ["5173:5173"]
|
||
api:
|
||
build: ./apps/api
|
||
ports: ["8000:8000"]
|
||
depends_on: [postgres, redis]
|
||
postgres:
|
||
image: postgres:16
|
||
redis:
|
||
image: redis:7-alpine
|
||
minio:
|
||
image: minio/minio # S3-compatible dev
|
||
|
||
# apps/web/e2e/ — Playwright specs (§15.3)
|
||
# docker-compose.test.yml — postgres + redis + api + web для CI/E2E
|
||
```
|
||
|
||
### 13.3. CI/CD Pipeline
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
Push[Git Push / PR] --> Lint[Lint + Typecheck]
|
||
Lint --> Unit[Unit + Integration]
|
||
Unit --> Cov{Coverage ≥ threshold?}
|
||
Cov -->|No| Block1[❌ Block merge]
|
||
Cov -->|Yes| Build[Build Docker]
|
||
Build --> DeployStaging[Deploy Staging]
|
||
DeployStaging --> E2E[Playwright E2E]
|
||
E2E --> E2EPass{All E2E pass?}
|
||
E2EPass -->|No| Block2[❌ Block merge]
|
||
E2EPass -->|Yes| Review[Code Review]
|
||
Review --> ManualApprove[Manual Approve]
|
||
ManualApprove --> DeployProd[Deploy Production]
|
||
```
|
||
|
||
**Checks на каждый PR (обязательны, merge заблокирован при падении):**
|
||
|
||
| Gate | Команда | Условие pass |
|
||
|------|---------|--------------|
|
||
| Lint | `eslint`, `ruff`, `prettier --check` | 0 errors |
|
||
| Types | `tsc --noEmit`, `mypy` | 0 errors |
|
||
| Unit (FE) | `vitest run --coverage` | ≥ порогов §15.2 |
|
||
| Unit + Integration (BE) | `pytest --cov=app --cov-fail-under=...` | ≥ порогов §15.2 |
|
||
| OpenAPI | schema diff | no breaking без bump version |
|
||
| E2E | `playwright test` | 100% pass, 0 flaky retries exhausted |
|
||
| Security | `pip-audit`, `npm audit`, gitleaks | no unwaived critical |
|
||
|
||
> **Правило:** PR без тестов на изменённую логику **не ревьюится** и **не мержится**.
|
||
|
||
### 13.4. Production topology (1000 users)
|
||
|
||
```
|
||
1× VPS (4 vCPU, 8 GB RAM) или managed PaaS
|
||
├── Nginx
|
||
├── API container (uvicorn, 2 workers)
|
||
├── Web static (built SPA)
|
||
├── PostgreSQL (managed или co-located)
|
||
└── Redis (managed или co-located)
|
||
```
|
||
|
||
**Запас на рост:** второй API instance за load balancer при CPU > 70% sustained.
|
||
|
||
---
|
||
|
||
## 14. Безопасность
|
||
|
||
### 14.1. Threat model (STRIDE, MVP)
|
||
|
||
| Угроза | Вектор | Митигация |
|
||
|--------|--------|-----------|
|
||
| **Spoofing** | Подделка JWT / session | Подпись JWT, короткий TTL, rotation refresh |
|
||
| **Tampering** | Изменение чужих данных | RBAC + owner check (`sub == user_id`) |
|
||
| **Repudiation** | Отрицание admin-действий | Audit log (v1.0), correlation-id в логах |
|
||
| **Information disclosure** | Enumeration email, утечка PII | Anti-enumeration (§9.2), маскировка логов |
|
||
| **Denial of service** | Flood login/register | Rate limit + account lockout (§9.6) |
|
||
| **Elevation of privilege** | user → admin | RBAC middleware, admin self-edit запрещён |
|
||
|
||
**Trust boundaries:**
|
||
1. Browser (недоверенный) ↔ Nginx (TLS termination)
|
||
2. Nginx ↔ API (internal network / docker network)
|
||
3. API ↔ PostgreSQL / Redis / S3 (credentials via env)
|
||
|
||
### 14.2. Transport и security headers
|
||
|
||
**Обязательно в production (Nginx / middleware):**
|
||
|
||
| Header | Значение |
|
||
|--------|----------|
|
||
| `Strict-Transport-Security` | `max-age=31536000; includeSubDomains` |
|
||
| `X-Content-Type-Options` | `nosniff` |
|
||
| `X-Frame-Options` | `DENY` |
|
||
| `Referrer-Policy` | `strict-origin-when-cross-origin` |
|
||
| `Permissions-Policy` | `camera=(), microphone=(), geolocation=()` |
|
||
| `Content-Security-Policy` | см. §14.3 |
|
||
|
||
- TLS 1.2+ only, сильные cipher suites.
|
||
- Swagger UI (`/docs`) и OpenAPI JSON — **отключены в production** (env flag `ENABLE_DOCS=false`).
|
||
|
||
### 14.3. Content Security Policy
|
||
|
||
```
|
||
default-src 'self';
|
||
script-src 'self';
|
||
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
|
||
font-src 'self' https://fonts.gstatic.com;
|
||
img-src 'self' data: https://<cdn-domain>;
|
||
connect-src 'self' https://<api-domain>;
|
||
frame-ancestors 'none';
|
||
base-uri 'self';
|
||
form-action 'self';
|
||
```
|
||
|
||
- Inline scripts в SPA — через nonce или только bundled (Vite).
|
||
- Google Fonts — допустимое исключение; при ужесточении CSP — self-host шрифтов.
|
||
|
||
### 14.4. XSS и инъекции
|
||
|
||
| Поверхность | Защита |
|
||
|-------------|--------|
|
||
| React UI | Auto-escaping JSX |
|
||
| CMS `content_pages.body` (HTML) | Server-side sanitize (**bleach** / **nh3**) + client DOMPurify allowlist перед `dangerouslySetInnerHTML` |
|
||
| User input (profile) | Pydantic validation, max length, strip control chars |
|
||
| SQL | SQLAlchemy ORM, parameterized queries; raw SQL запрещён без review |
|
||
| Log injection | Structured JSON logging, sanitize `\n\r` в user input |
|
||
|
||
**Allowlist HTML-тегов CMS:** `p, h1-h4, ul, ol, li, a, strong, em, br, img` — без `script`, `iframe`, `on*` attributes.
|
||
|
||
### 14.5. IDOR и авторизация
|
||
|
||
| Endpoint | Правило |
|
||
|----------|---------|
|
||
| `GET/PATCH /users/me` | `current_user.id` из JWT, не из body |
|
||
| `POST /users/me/avatar` | только свой user_id |
|
||
| `PATCH /admin/users/{id}` | admin + business rules (§14.10) |
|
||
| `GET /content/pages/{slug}` | только `status=published` для non-admin |
|
||
| `PATCH /content/pages/{id}` | admin only; проверка существования id |
|
||
|
||
**Запрещено:** принимать `user_id` / `role` из request body для повышения привилегий.
|
||
|
||
### 14.6. Загрузка файлов (avatar)
|
||
|
||
| Проверка | Значение |
|
||
|----------|----------|
|
||
| Max size | 2 MB |
|
||
| MIME (whitelist) | `image/jpeg`, `image/png`, `image/webp` |
|
||
| Magic bytes | python-magic / file signature check |
|
||
| Filename | UUID + ext, без user-supplied path |
|
||
| Storage | S3 private bucket; URL — signed / через CDN proxy |
|
||
| Processing | Re-encode (Pillow) для удаления EXIF и embedded payload |
|
||
|
||
**Запрещено:** SVG upload (XSS vector), исполнение файлов на сервере.
|
||
|
||
### 14.7. Secrets и криптография
|
||
|
||
| Secret | Хранение | Rotation |
|
||
|--------|----------|----------|
|
||
| `JWT_ACCESS_SECRET` | env / vault | при компрометации, quarterly |
|
||
| `JWT_REFRESH_PEPPER` | env / vault | отдельный от access secret |
|
||
| DB credentials | env / managed DB | managed rotation |
|
||
| S3 keys | IAM role (preferred) или env | quarterly |
|
||
|
||
- `.env` в `.gitignore`; `.env.example` без реальных значений.
|
||
- Pre-commit hook / CI: **gitleaks** или **trufflehog**.
|
||
- JWT secret ≥ 256 bit entropy.
|
||
|
||
### 14.8. Сессии и logout
|
||
|
||
- **Logout:** revoke refresh token family + clear cookie (`Max-Age=0`).
|
||
- **Password change / reset:** revoke **все** refresh tokens пользователя.
|
||
- **Block user (admin):** revoke tokens немедленно.
|
||
- Optional: Redis denylist для access JWT by `jti` до exp (для instant revoke admin-сессий).
|
||
|
||
### 14.9. Логирование и PII
|
||
|
||
**Логировать:**
|
||
- `request_id`, method, path, status, duration, user_id (if auth), IP (hashed в prod опционально).
|
||
|
||
**Не логировать:**
|
||
- Passwords, tokens, reset links, full email в debug (mask: `u***@example.com`).
|
||
|
||
**Retention:** application logs 30 days; audit log admin-действий 1 year.
|
||
|
||
### 14.10. Admin security rules
|
||
|
||
| Правило | Описание |
|
||
|---------|----------|
|
||
| Last admin | Нельзя удалить/понизить последнего admin |
|
||
| Self-demotion | Admin не может снять с себя роль admin |
|
||
| Self-block | Admin не может заблокировать себя |
|
||
| Role escalation | Только admin может назначать admin; 2FA для admin — v1.0 (recommended) |
|
||
| Seed admin | Создаётся миграцией; пароль из env при первом deploy, затем смена |
|
||
|
||
### 14.11. Supply chain
|
||
|
||
- Dependabot / Renovate — auto PR на CVE.
|
||
- CI: `pip-audit` (backend), `npm audit` (frontend), **Trivy** scan Docker images.
|
||
- Pin dependencies (`poetry.lock`, `package-lock.json`).
|
||
- Block merge при critical/high без explicit waiver.
|
||
|
||
### 14.12. GDPR / 152-ФЗ
|
||
|
||
| Требование | Реализация |
|
||
|------------|------------|
|
||
| Согласие | Checkbox при регистрации + ссылка на политику |
|
||
| Политика конфиденциальности | CMS-страница `/privacy` |
|
||
| Право на удаление | `DELETE /users/me` (v1.0): soft-delete, anonymize PII, retain audit/legal minimum |
|
||
| Data minimization | `metadata` json — только необходимые поля |
|
||
| Хранение | RU/EU region hosting (допущение §18.1) |
|
||
| Breach notification | Runbook в docs (v1.0) |
|
||
|
||
### 14.13. Security checklist (pre-production)
|
||
|
||
- [ ] HTTPS + HSTS
|
||
- [ ] CSP, security headers (§14.2)
|
||
- [ ] CORS whitelist, `allow_credentials` только для trusted origins
|
||
- [ ] Refresh: httpOnly, Secure, SameSite, Path, rotation + reuse detection
|
||
- [ ] Access JWT in memory only
|
||
- [ ] Anti-enumeration на auth endpoints
|
||
- [ ] Rate limiting + brute-force lockout
|
||
- [ ] IDOR tests на все `/me` и admin routes
|
||
- [ ] File upload validation + private S3
|
||
- [ ] CMS HTML sanitization
|
||
- [ ] Secrets not in git; gitleaks in CI
|
||
- [ ] Swagger disabled in prod
|
||
- [ ] Dependency scan clean (no unwaived critical)
|
||
- [ ] OWASP ASVS Level 1 review
|
||
- [ ] Sentry: scrub PII from breadcrumbs
|
||
|
||
### 14.14. Security testing (дополнение к §15)
|
||
|
||
| Тест | Tool / метод |
|
||
|------|--------------|
|
||
| SAST | Bandit (Python), ESLint security plugins |
|
||
| DAST | OWASP ZAP baseline scan на staging |
|
||
| Auth flows | pytest: token rotation, reuse detection, lockout |
|
||
| IDOR | pytest: user A cannot access user B |
|
||
| Fuzzing upload | Invalid MIME, oversized, polyglot files |
|
||
|
||
---
|
||
|
||
## 15. Тестирование и качество
|
||
|
||
> **Базовое правило проекта:** функция считается реализованной только когда поставлены **тесты логики** (unit + integration) **и E2E-сценарии** для затронутых пользовательских потоков. Исключений нет.
|
||
|
||
### 15.1. Пирамида и обязательные уровни
|
||
|
||
| Уровень | Что покрывает | Tools | Обязательность |
|
||
|---------|---------------|-------|----------------|
|
||
| **Unit** | service, repository, hooks, store, utils, pure components | Vitest, pytest | **Обязательно** на каждый PR с логикой |
|
||
| **Integration** | API routers + DB + Redis; form → API client | pytest + TestClient, MSW | **Обязательно** для backend и API-слоя frontend |
|
||
| **Component** | UI: render, a11y, user events | Testing Library | **Обязательно** для новых/изменённых компонентов |
|
||
| **E2E** | Сквозные user journeys в браузере | Playwright | **Обязательно** на каждый модуль / фазу (§15.5) |
|
||
| **Visual** | Landing regression | Percy/Chromatic | Опционально |
|
||
| **Load** | API под CCU | k6 | Перед релизом MVP/v1.0 |
|
||
| **Security** | auth, IDOR, upload | pytest + ZAP | Перед релизом (§14.14) |
|
||
|
||
### 15.2. Пороги покрытия (coverage gates)
|
||
|
||
Измерение: **line coverage** (Vitest v8 / pytest-cov). CI падает при падении ниже порога.
|
||
|
||
| Область | Минимум | Примечание |
|
||
|---------|---------|------------|
|
||
| `apps/api/app/modules/*/service.py` | **≥ 95%** | Бизнес-логика backend |
|
||
| `apps/api/app/modules/*/repository.py` | **≥ 90%** | SQL, edge cases |
|
||
| `apps/api/app/core/security.py` | **100%** | JWT, hash, lockout |
|
||
| `apps/web/src/modules/*/hooks`, `store`, `api` | **≥ 90%** | Логика frontend |
|
||
| `apps/web/src/shared/lib`, `shared/api` | **≥ 90%** | Общие утилиты |
|
||
| `apps/web/src/modules/*/components` | **≥ 80%** | UI; допускается исключать pure layout |
|
||
| **Overall backend** `app/` | **≥ 90%** | `--cov-fail-under=90` |
|
||
| **Overall frontend** `src/` | **≥ 85%** | `--coverage.thresholds.lines=85` |
|
||
|
||
**Diff coverage (рекомендуется):** новые/changed строки в PR — **≥ 95%** (Codecov / diff-cover).
|
||
|
||
**Критические модули** (`auth`, `admin`, `core/security`) — **100% branch coverage** для функций авторизации и token rotation.
|
||
|
||
### 15.3. Структура E2E (Playwright)
|
||
|
||
```
|
||
apps/web/e2e/
|
||
├── fixtures/
|
||
│ ├── auth.fixture.ts # login helpers, test users
|
||
│ └── api.fixture.ts # seed via API
|
||
├── landing/
|
||
│ └── landing.spec.ts
|
||
├── auth/
|
||
│ ├── register.spec.ts
|
||
│ ├── login.spec.ts
|
||
│ └── password-reset.spec.ts
|
||
├── profile/
|
||
│ └── profile.spec.ts
|
||
├── content/
|
||
│ └── content-pages.spec.ts
|
||
├── admin/
|
||
│ └── admin-users.spec.ts
|
||
└── playwright.config.ts
|
||
```
|
||
|
||
**Конфигурация:**
|
||
- Бrowsers: Chromium (CI), + Firefox/WebKit локально.
|
||
- Retry: 1 в CI только для infra flakes; повторный flake → bug.
|
||
- Trace/video: on-first-retry.
|
||
- Параллельность: по файлам, isolated test users.
|
||
- E2E против **staging** в CI post-deploy; PR — против docker-compose stack.
|
||
|
||
### 15.4. Матрица тестов по модулям (MVP)
|
||
|
||
Каждая строка — **минимальный обязательный набор** перед merge задачи.
|
||
|
||
| Модуль | Unit / Integration (логика) | E2E (Playwright) |
|
||
|--------|----------------------------|------------------|
|
||
| **landing** | Hero/Marquee render, breakpoints, reduced-motion | `/` загрузка, marquee visible, a11y snapshot |
|
||
| **auth** | register, login, refresh rotation, reuse detection, lockout, anti-enumeration, verify-email | register → verify → login; wrong password; pending/blocked redirect |
|
||
| **profile** | PATCH whitelist, avatar validation (MIME, size), IDOR | edit name, upload avatar, change password |
|
||
| **content** | slug unique, draft vs published, HTML sanitize | public page by slug; admin publish → visible |
|
||
| **admin** | last-admin rule, self-block forbidden, RBAC | admin list users, block user, blocked cannot login |
|
||
| **shared/ui** | Button, Input, Form validation states | используются в module E2E |
|
||
|
||
### 15.5. Тесты на каждом шаге реализации (workflow)
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
Task[Задача из §16] --> Impl[Реализация]
|
||
Impl --> Unit[Unit tests]
|
||
Unit --> Int[Integration tests]
|
||
Int --> Comp[Component tests если UI]
|
||
Comp --> E2E[E2E spec для flow]
|
||
E2E --> CI[CI green]
|
||
CI --> DoD[Definition of Done §15.6]
|
||
DoD --> Merge[Merge allowed]
|
||
```
|
||
|
||
**Порядок TDD (рекомендуется, не optional для auth/security):**
|
||
1. Написать failing test (unit или e2e).
|
||
2. Реализовать минимальный код.
|
||
3. Довести coverage до порога.
|
||
4. Открыть PR — CI должен быть green.
|
||
|
||
**Запрещено:**
|
||
- Откладывать тесты «на потом» / отдельным PR.
|
||
- Merge с `@pytest.mark.skip` / `test.todo` / `it.skip` без linked issue и срока.
|
||
- Mock всего подряд в integration — DB/Redis должны быть real (testcontainers или docker-compose).
|
||
|
||
### 15.6. Definition of Done (модуль / задача)
|
||
|
||
- [ ] TypeScript strict / mypy — 0 errors
|
||
- [ ] **Unit tests** для всей новой/изменённой бизнес-логики
|
||
- [ ] **Integration tests** для новых/изменённых API endpoints
|
||
- [ ] **Component tests** для новых/изменённых React-компонентов
|
||
- [ ] **E2E spec** для затронутых user flows (§15.4)
|
||
- [ ] Coverage ≥ порогов §15.2 (CI enforced)
|
||
- [ ] API documented in OpenAPI
|
||
- [ ] ESLint boundaries pass
|
||
- [ ] Security tests пройдены (если модуль auth/admin/media)
|
||
- [ ] Code review approved
|
||
- [ ] 0 flaky E2E за 3 прогона CI
|
||
|
||
### 15.7. Critical E2E scenarios (регрессия релиза)
|
||
|
||
Полный прогон перед каждым релизом MVP/v1.0:
|
||
|
||
1. Landing: LCP < 2.5s, hero + marquee, `prefers-reduced-motion`.
|
||
2. Register → verify email → login → profile edit → change password → logout.
|
||
3. Forgot password → reset → login with new password.
|
||
4. Admin: create content → publish → visible on `/pages/:slug`.
|
||
5. Admin: block user → blocked user gets 403 on login.
|
||
6. Pending user cannot access `/profile`.
|
||
7. Refresh rotation: old refresh token rejected; reuse revokes family.
|
||
8. IDOR: user A cannot GET/PATCH user B profile.
|
||
9. Admin cannot demote/block self; last admin protected.
|
||
10. Avatar: reject .svg, oversize, wrong MIME.
|
||
|
||
### 15.8. Test data и изоляция
|
||
|
||
| Аспект | Правило |
|
||
|--------|---------|
|
||
| Test DB | Отдельная `compton_test`; транзакционный rollback per test |
|
||
| Seed users | factories.py: `user`, `admin`, `pending_user`, `blocked_user` |
|
||
| E2E users | Создаются через API fixture перед spec, cleanup after |
|
||
| Secrets in tests | Только `test-*` keys из `.env.test` |
|
||
| Parallel | Изolated data via UUID suffix emails |
|
||
|
||
### 15.9. Команды (локально)
|
||
|
||
```bash
|
||
# Frontend
|
||
pnpm --filter web test # vitest watch
|
||
pnpm --filter web test:ci # vitest run --coverage
|
||
pnpm --filter web e2e # playwright test
|
||
pnpm --filter web e2e:ui # playwright --ui
|
||
|
||
# Backend
|
||
cd apps/api && pytest # all
|
||
pytest tests/modules/auth -v # module
|
||
pytest --cov=app --cov-report=term-missing --cov-fail-under=90
|
||
|
||
# Full stack (pre-push)
|
||
docker compose -f docker-compose.test.yml up -d
|
||
pnpm test:ci && pytest && playwright test
|
||
```
|
||
|
||
---
|
||
|
||
## 16. Этапы разработки
|
||
|
||
### Фаза 0 — Подготовка (1–2 недели)
|
||
|
||
| # | Задача | Результат | Тесты (обязательно) |
|
||
|---|--------|-----------|---------------------|
|
||
| 0.1 | Monorepo: Vite, FastAPI skeleton | `apps/web`, `apps/api` | smoke: `vitest` 1 test, `pytest` health 1 test |
|
||
| 0.2 | Docker Compose + `docker-compose.test.yml` | dev + test stack | CI job: lint + smoke green |
|
||
| 0.3 | Shared UI kit + design tokens | Button, Input, Layout | component tests ≥80%; Storybook optional |
|
||
| 0.4 | ESLint boundaries, OpenAPI codegen, coverage gates | Quality gates в CI | CI блокирует merge без coverage config |
|
||
| 0.5 | Playwright + pytest fixtures | `e2e/`, `conftest.py`, factories | E2E smoke: landing page loads |
|
||
|
||
### Фаза 1 — MVP (4–6 недель)
|
||
|
||
> Каждая задача завершается **своим** набором unit + integration + E2E. Задача 1.6 — финальный регресс, не «первые E2E».
|
||
|
||
| # | Задача | Модули | Тесты (обязательно в том же PR) |
|
||
|---|--------|--------|----------------------------------|
|
||
| 1.1 | Landing (перенос заглушки) | landing | unit: breakpoints; E2E: `landing.spec.ts` |
|
||
| 1.2 | Auth + SMTP | auth | unit: service/security; integration: all auth routes; E2E: register/login/verify/reset |
|
||
| 1.2b | Security baseline | core, auth | 100% coverage `security.py`; tests: lockout, rotation, enumeration |
|
||
| 1.3 | Profile CRUD | profile | unit: validation; integration: `/users/me`; E2E: edit + avatar + password |
|
||
| 1.4 | Content pages | content | unit: sanitize; integration: CRUD; E2E: publish → public view |
|
||
| 1.5 | Admin panel | admin | unit: business rules; integration: admin routes; E2E: block user flow |
|
||
| 1.6 | Staging deploy + **полный регресс** | — | Playwright §15.7 (all 10 scenarios); coverage ≥ §15.2 |
|
||
|
||
### Фаза 2 — v1.0 (4–6 недель)
|
||
|
||
| # | Задача | Модули | Тесты (обязательно в том же PR) |
|
||
|---|--------|--------|----------------------------------|
|
||
| 2.1 | Celery + email queue | notifications | unit: task handlers; integration: queue + mock SMTP; E2E: notification received |
|
||
| 2.2 | Catalog + Orders | catalog, orders | unit + integration per module; E2E: browse → cart → submit order |
|
||
| 2.3 | Analytics dashboard | admin | integration: stats API; E2E: dashboard renders metrics |
|
||
| 2.4 | Performance + monitoring | infra | k6 load test §17.2; no coverage regression |
|
||
|
||
### Фаза 3 — Scale prep (по необходимости)
|
||
|
||
- Read replica PostgreSQL
|
||
- Celery workers
|
||
- CDN + object storage migration
|
||
- Horizontal API scaling
|
||
|
||
---
|
||
|
||
## 17. Критерии приёмки
|
||
|
||
### 17.1. MVP
|
||
|
||
**Функциональность:**
|
||
- [ ] Landing визуально соответствует текущей заглушке (hero, marquee, цвета).
|
||
- [ ] Регистрация и вход работают; load test **50 CCU** на API.
|
||
- [ ] Профиль редактируется, аватар загружается.
|
||
- [ ] Admin создаёт/редактирует контент-страницы.
|
||
|
||
**Производительность и безопасность:**
|
||
- [ ] API p95 < 300 ms при 50 CCU (k6).
|
||
- [ ] Lighthouse Performance ≥ 85 на mobile.
|
||
- [ ] Security checklist §14.13 — все пункты закрыты.
|
||
- [ ] OWASP ZAP baseline — 0 high/critical на staging.
|
||
|
||
**Тестирование (обязательно):**
|
||
- [ ] Backend coverage ≥ **90%** (`app/`), auth/security ≥ **95–100%** (§15.2).
|
||
- [ ] Frontend coverage ≥ **85%** (`src/`), hooks/api ≥ **90%**.
|
||
- [ ] Все E2E сценарии §15.7 — **10/10 pass** на staging.
|
||
- [ ] 0 skipped tests без waiver.
|
||
- [ ] CI pipeline §13.3 — все gates green на `main`.
|
||
- [ ] Каждый модуль MVP имеет строки в матрице §15.4 (закрыты).
|
||
|
||
**Документация:**
|
||
- [ ] README: как запускать `test`, `test:ci`, `e2e`.
|
||
- [ ] OpenAPI, `.env.example`.
|
||
|
||
### 17.2. Load test сценарий (k6, API-only)
|
||
|
||
```javascript
|
||
// 50 VU, ramp 5 min, target: api:8000 через Nginx
|
||
// 35% GET /api/v1/content/pages
|
||
// 25% POST /api/v1/auth/login (test credentials pool)
|
||
// 20% GET /api/v1/users/me (authenticated)
|
||
// 10% POST /api/v1/auth/refresh (cookie)
|
||
// 10% GET /api/v1/content/pages/{slug}
|
||
```
|
||
|
||
**Pass:** error rate < 1%, p95 < 300ms, 0 auth bypass.
|
||
|
||
> Frontend (`GET /`) — отдельный Lighthouse-тест, не смешивать с API load test.
|
||
|
||
---
|
||
|
||
## 18. Риски и допущения
|
||
|
||
### 18.1. Допущения
|
||
|
||
1. 1000 пользователей — registered, не 1000 RPS.
|
||
2. Контент преимущественно текст + изображения, без video streaming.
|
||
3. Один регион хостинга (RU/EU), latency < 100ms для целевой аудитории.
|
||
4. Команда: 1–3 fullstack-разработчика.
|
||
|
||
### 18.2. Риски
|
||
|
||
| Риск | Вероятность | Митигация |
|
||
|------|-------------|-----------|
|
||
| Scope creep (catalog/orders раньше MVP) | Высокая | Жёсткое следование фазам |
|
||
| SPA SEO проблемы | Средняя | Prerender landing, meta tags |
|
||
| Cross-module imports ломают архитектуру | Средняя | ESLint boundaries в CI |
|
||
| Auth flow complexity (rotation, verify) | Средняя | TDD + integration tests §15.4–15.7 |
|
||
| Flaky E2E блокируют CI | Средняя | fixtures, isolated data §15.8, trace-on-retry |
|
||
| Перегрузка monolith при росте | Низкая (до 10K) | Redis cache, horizontal scale |
|
||
|
||
---
|
||
|
||
## Приложение A — Карта модулей (summary)
|
||
|
||
```mindmap
|
||
root((Комpton))
|
||
Frontend
|
||
app
|
||
pages
|
||
modules
|
||
landing
|
||
auth
|
||
profile
|
||
content
|
||
admin
|
||
catalog
|
||
shared
|
||
Backend
|
||
core
|
||
modules
|
||
auth
|
||
users
|
||
content
|
||
media
|
||
admin
|
||
Infra
|
||
PostgreSQL
|
||
Redis
|
||
S3
|
||
Nginx
|
||
"CI/CD"
|
||
```
|
||
|
||
---
|
||
|
||
## Приложение B — Переменные окружения
|
||
|
||
### Frontend (`apps/web/.env`)
|
||
|
||
```env
|
||
VITE_API_URL=http://localhost:8000
|
||
VITE_APP_NAME=Комpton
|
||
VITE_SENTRY_DSN=
|
||
```
|
||
|
||
### Backend (`apps/api/.env`)
|
||
|
||
```env
|
||
DATABASE_URL=postgresql+asyncpg://user:pass@localhost:5432/compton
|
||
REDIS_URL=redis://localhost:6379/0
|
||
JWT_ACCESS_SECRET= # ≥ 32 bytes random, NOT commit
|
||
JWT_REFRESH_PEPPER= # отдельный секрет для hash refresh tokens
|
||
JWT_ACCESS_TTL_MIN=15
|
||
JWT_REFRESH_TTL_DAYS=30
|
||
ENABLE_DOCS=true # false в production
|
||
CORS_ORIGINS=http://localhost:5173
|
||
S3_ENDPOINT=http://localhost:9000
|
||
S3_BUCKET=compton-media
|
||
S3_ACCESS_KEY=
|
||
S3_SECRET_KEY=
|
||
SMTP_HOST=
|
||
SMTP_PORT=587
|
||
SMTP_USER=
|
||
SMTP_PASSWORD=
|
||
SMTP_FROM=noreply@compton.example
|
||
ADMIN_INITIAL_PASSWORD= # только first deploy, затем сменить
|
||
```
|
||
|
||
---
|
||
|
||
## Приложение C — Согласование
|
||
|
||
| Роль | ФИО | Подпись | Дата |
|
||
|------|-----|---------|------|
|
||
| Product Owner | | | |
|
||
| Tech Lead | | | |
|
||
| Developer | | | |
|
||
|
||
---
|
||
|
||
*Документ является живым артеfactом. Изменения фиксируются через PR в `docs/TZ.md` с bump версии.*
|