CLAUDE.md@tests/fixtures/legacy · git:20260920.26281f3 · 2026-09-20 · sha256 7976503f8b63f7c3

CLAUDE.md@tests/fixtures/legacy git:20260920.26281f3A

Immutable. This exact content is served forever at /api/v1/blob/7976503f8b63f7c3.

# TaskFlow — трекер задач для маленьких команд

TaskFlow — внутренний сервис учёта задач: REST API на FastAPI, Postgres, воркер на Celery
для напоминаний. Этот файл — смесь правил разработки и справочника по системе, копилась
без структуры полтора года, чистить было некогда.

## Стек

- Python 3.11, FastAPI, SQLAlchemy 2.x (Core, без ORM-сессий в роутерах напрямую)
- PostgreSQL 15, миграции — Alembic
- Celery + Redis для фоновых задач (напоминания, экспорт отчётов)
- Frontend — отдельный репозиторий `taskflow-web`, сюда не относится
- Деплой — Docker Compose на одном VPS, без Kubernetes (пока не нужен)

## Как поднять локально

```bash
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
docker compose up -d db redis
alembic upgrade head
uvicorn app.main:app --reload
```

Тесты:

```bash
pytest
```

Линт:

```bash
ruff check app tests
```

## Правила разработки

- Не коммитить прямо в `main`, только через PR, минимум один ревью.
- Названия веток: `feature/<кратко>`, `fix/<кратко>`, `chore/<кратко>`.
- Коммиты — в свободной форме, но первая строка до 72 символов.
- Миграции Alembic всегда обратимые: `downgrade()` обязателен, даже если это просто заглушка
  с комментарием "нельзя откатить безопасно".
- Секреты — только через `.env`, в репозиторий не попадают. `.env.example` держим в актуальном
  состоянии, при добавлении новой переменной окружения дописываем и туда.
- Новые эндпоинты — обязательно с тестом на happy path и хотя бы одним на ошибку (403/404/422).
- Не логировать тело запроса целиком — там бывают пароли и токены сброса.
- PR без описания "что и зачем" ревьюер имеет право отклонить не читая диф.
- Библиотеки добавляем только через `requirements.txt`, версии фиксируем (`==`), не диапазоном.
- Возврат ошибок API — всегда JSON вида `{"detail": "..."}`, обычные строки не отдаём.

## Архитектура

Слои: `app/api` (роутеры FastAPI) → `app/services` (бизнес-логика) → `app/repositories`
(доступ к БД через SQLAlchemy Core) → `app/models` (dataclasses для доменных объектов,
не путать с таблицами). Роутеры не ходят в БД напрямую, только через сервисы.

```
app/
├── api/            роутеры: tasks.py, projects.py, users.py, auth.py
├── services/        бизнес-логика, один файл на домен
├── repositories/     SQL-запросы, никакой бизнес-логики
├── models/           доменные dataclasses
├── workers/          celery-таски: reminders.py, export.py
└── main.py            точка входа, сборка FastAPI-приложения
```

Авторизация — JWT, токен живёт 24 часа, рефреш-токена нет (осознанный долг, см. известные
проблемы ниже). Middleware `app/api/auth_middleware.py` проверяет токен на всех путях,
кроме `/health` и `/auth/login`.

## Схема базы данных

Основные таблицы (только то, что должен знать любой, кто трогает бэкенд):

### `users`

| Колонка | Тип | Комментарий |
|---|---|---|
| id | uuid, pk | |
| email | text, unique | |
| password_hash | text | bcrypt |
| role | text | `admin` \| `member` |
| created_at | timestamptz | |

### `projects`

| Колонка | Тип | Комментарий |
|---|---|---|
| id | uuid, pk | |
| name | text | |
| owner_id | uuid, fk → users.id | |
| archived | boolean | default false |

### `tasks`

| Колонка | Тип | Комментарий |
|---|---|---|
| id | uuid, pk | |
| project_id | uuid, fk → projects.id | |
| title | text | |
| status | text | `todo` \| `in_progress` \| `done` |
| assignee_id | uuid, fk → users.id, nullable | |
| due_date | date, nullable | |
| created_at | timestamptz | |

### `reminders`

| Колонка | Тип | Комментарий |
|---|---|---|
| id | uuid, pk | |
| task_id | uuid, fk → tasks.id | |
| remind_at | timestamptz | |
| sent | boolean | default false |

Индексы: `tasks(project_id, status)` — под основной фильтр списка задач;
`reminders(remind_at) where sent = false` — под воркер напоминаний.

## API эндпоинты

| Метод | Путь | Что делает |
|---|---|---|
| POST | `/auth/login` | логин, отдаёт JWT |
| GET | `/projects` | список проектов текущего пользователя |
| POST | `/projects` | создать проект |
| GET | `/projects/{id}/tasks` | список задач проекта, фильтр по `status` |
| POST | `/projects/{id}/tasks` | создать задачу |
| PATCH | `/tasks/{id}` | обновить статус/исполнителя/срок |
| DELETE | `/tasks/{id}` | удалить задачу (мягкое удаление, `deleted_at`) |
| GET | `/health` | без авторизации, для докер-хелсчека |

Полный список — в OpenAPI-схеме, `/docs` при поднятом сервере; здесь только то, что
меняется редко и на что часто ссылаются в обсуждениях.

## Тестирование

- Юнит-тесты сервисов — без БД, репозитории мокаются.
- Интеграционные — поднимают тестовую Postgres в докере (`docker compose -f
  docker-compose.test.yml up -d`), фикстура `db_session` в `conftest.py` чистит таблицы
  между тестами через `TRUNCATE ... CASCADE`.
- Минимальное покрытие для нового кода — 80% по строкам, проверяется в CI (`pytest --cov`).
- Флаки-тесты не скипаем молча — заводим тикет и оставляем `xfail` с ссылкой на него.

## Git workflow

- PR из ветки в `main`, сквош при мёрдже.
- CI гоняет линт, тесты и `alembic check` (нет расхождения между моделями и миграциями)
  на каждый пуш.
- Тег `vX.Y.Z` на `main` — триггерит деплой на прод через GitHub Actions.
- Хотфиксы — ветка от последнего тега, не от `main`, если в `main` уже накопились
  недоделанные фичи.

## Деплой

Прод — один VPS, `docker compose up -d` из тега. Порядок:

1. Убедиться, что миграции применяются без даунтайма (новые колонки — nullable или с
   default, дропы колонок — отдельным релизом после того, как код перестал их читать).
2. `docker compose pull && docker compose up -d --no-deps app worker`.
3. `alembic upgrade head` — руками, не автоматом (осознанное решение после инцидента
   с зависшей миграцией на 40-секундном локе).
4. Проверить `/health` и логи воркера 5 минут.

Стейджинга нет — тестируем на локальной копии прод-дампа (без email-адресов, они
подменяются на фейковые скриптом `scripts/anonymize.py`).

## Известные проблемы

- Нет рефреш-токенов: пользователь разлогинивается раз в сутки. Мешает не сильно,
  но однажды надо сделать по-человечески.
- Экспорт отчётов в PDF работает только для проектов до 500 задач — дальше воркер
  падает по таймауту Celery (30 секунд). Обходной путь — экспорт в CSV, там ограничения нет.
- `assignee_id` не проверяется на принадлежность к тому же проекту — можно назначить
  задачу человеку из другой команды через прямой запрос к API (баг, не фича).
- Тест `test_login_rate_limit` иногда падает в CI из-за общего Redis между прогонами —
  не расследовали до конца, см. TODO.

## Правила код-ревью

- Ревьюер обязан прогнать ветку локально, если диф трогает миграции.
- Не одобряем PR с `# type: ignore` без комментария, почему тип нельзя починить.
- Изменения в `app/api/auth_middleware.py` — ревью минимум от двух человек, это
  единственная линия защиты всех эндпоинтов.

## Переменные окружения

| Переменная | Обязательна | Смысл |
|---|---|---|
| `DATABASE_URL` | да | строка подключения Postgres |
| `REDIS_URL` | да | брокер Celery |
| `JWT_SECRET` | да | подпись токенов |
| `SENTRY_DSN` | нет | если пусто — ошибки не летят в Sentry |
| `EXPORT_TIMEOUT_S` | нет | таймаут воркера экспорта, по умолчанию 30 |

## Частые вопросы

**Почему SQLAlchemy Core, а не ORM?** Исторически — первая версия писалась быстро, ORM
показался лишней прослойкой для простых CRUD-запросов. Сейчас переписывать не будем,
слишком дорого, проект и так небольшой.

**Можно завести вторую БД под тесты локально, не в докере?** Можно, но CI всё равно
гоняет докерную — если локально зелено, а в докере красно, доверяем докеру.

**Куда класть новый воркер?** В `app/workers/`, регистрировать в `app/workers/__init__.py`,
не забыть добавить в `celery beat schedule`, если он периодический.

## Контакты

Вопросы по бэкенду — в канале `#taskflow-backend`. По продовым инцидентам —
дежурный по расписанию в PagerDuty, смотри закреплённое сообщение в канале.