Асинхронный бэкенд для Kanban-доски продакшен-уровня. Проект демонстрирует реальные задачи бэкенд-разработки: проектирование реляционных связей, контроль доступа (RBAC), потокобезопасное переупорядочивание карточек (drag-and-drop) и неблокирующую фоновую обработку задач.
| Задача | Решение |
|---|---|
| Фреймворк | FastAPI (Python 3.12) |
| База данных | PostgreSQL 16 |
| ORM / драйвер | SQLAlchemy 2.0 (async) + asyncpg |
| Миграции | Alembic (асинхронный env.py) |
| Аутентификация | JWT: access + refresh токены |
| Фоновые задачи | FastAPI BackgroundTasks + aiosmtplib |
| Тесты | pytest + pytest-asyncio + httpx.AsyncClient |
| Инфраструктура | Docker (multi-stage) + Docker Compose |
app/
├── api/ Презентационный слой (роутеры, DTO-схемы, зависимости)
│ ├── v1/ auth.py · boards.py · cards.py
│ └── schemas/ Pydantic V2 модели запросов/ответов
├── core/ config · security (JWT/хеши) · tasks (email) · exceptions
├── database/ async движок/сессия · декларативный Base
├── models/ ORM-модели SQLAlchemy 2.0
├── repositories/ Паттерн Репозиторий (доступ к данным)
├── services/ Бизнес-логика, RBAC, drag-and-drop
└── main.py Точка входа + lifespan
Направление зависимостей: api -> services -> repositories -> models.
Сервисы бросают доменные исключения (app/core/exceptions.py), а единый
обработчик в main.py превращает их в HTTP-ответы. Благодаря этому бизнес-логика
не зависит от FastAPI.
- User — владеет множеством досок (1-N); назначается на множество карточек
(M-N через таблицу
card_assignees). - Board — публичная/приватная; содержит колонки (1-N) и участников с ролями
(таблица
board_members). - Column — упорядоченные карточки (1-N).
- Card — дробная позиция
position; содержит комментарии (1-N) и исполнителей. - Comment — комментарий пользователя к карточке.
Роли: viewer < editor < admin. Владелец доски — неявный admin.
На публичной доске любой аутентифицированный пользователь получает неявный
viewer.
- Просмотр доски/карточек, комментирование ->
viewer - Создание/перенос/редактирование/удаление карточек, создание колонок ->
editor - Управление участниками, редактирование доски ->
admin - Удаление доски -> только владелец
Карточки хранят позицию с плавающей точкой position. При переносе карточки
между двумя соседями вычисляется середина (prev + next) / 2, поэтому
переупорядочивание — это обновление одной строки (O(1)) вместо перенумерации
всей колонки. Когда повторные вставки в середину исчерпывают точность float,
колонка назначения прозрачно перераспределяется (CardService._rebalance).
Эндпоинт POST /api/v1/cards/{card_id}/move принимает
{ "column_id", "target_position" } (target_position опционален -> карточка
добавляется в конец колонки).
Когда пользователя впервые назначают на карточку, BackgroundTasks ставит в
очередь отправку письма через aiosmtplib уже после возврата HTTP-ответа —
клиент не ждёт SMTP. В Docker Compose почту перехватывает MailHog
(веб-интерфейс на :8025).
cp .env.example .env # при необходимости поменяйте секреты
docker compose up --buildСервисы: API :8000 (документация на /docs), Postgres :5432, Redis :6379,
PgAdmin :5050, MailHog :8025. Контейнер API при старте выполняет
alembic upgrade head.
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# Укажите POSTGRES_HOST=localhost в .env, затем:
alembic upgrade head
uvicorn app.main:app --reloadpytestТесты выполняются на in-memory SQLite. Каждый тест работает внутри внешней
транзакции с join_transaction_mode="create_savepoint": даже вызовы
session.commit() внутри сервисов превращаются в release точки сохранения
(SAVEPOINT), а вся транзакция откатывается на teardown — полная изоляция, без
утечек данных между тестами (tests/conftest.py).
POST /api/v1/auth/register
POST /api/v1/auth/login (OAuth2 form: username = email)
POST /api/v1/auth/refresh
GET /api/v1/auth/me
GET /api/v1/boards
POST /api/v1/boards (создаёт колонки To Do / In Progress / Done)
GET /api/v1/boards/{id}
PATCH /api/v1/boards/{id}
DELETE /api/v1/boards/{id}
POST /api/v1/boards/{id}/members
DELETE /api/v1/boards/{id}/members/{user_id}
POST /api/v1/boards/{id}/columns
POST /api/v1/cards
GET /api/v1/cards/{id}
PATCH /api/v1/cards/{id} (изменение исполнителей запускает письмо)
POST /api/v1/cards/{id}/move (drag-and-drop)
DELETE /api/v1/cards/{id}
POST /api/v1/cards/{id}/comments