- python
- fastapi
- pydantic
- uvicorn
- postgresql
- alembic
- sqlalchemy
- pytest
- jwt логин по
email/password - профиль текущего пользователя через
GET /api/auth/users/me - список товаров
- создание резерва
- просмотр своих резервов
- отмена своего активного резерва
- создание товаров админом
- просмотр всех резервов админом
- подтверждение отгрузки админом
- фильтр резервов по статусу
- пагинация списка резервов
- внешний
reservation_key - структурированные ошибки для конфликтов
- uuid вместо числовых id
- время в ответах и моделях в часовом поясе
Europe/Moscow
app/
api/
core/
db/
schemas/
services/
alembic/
tests/
создай .env на основе .env.example
DATABASE_URL=postgresql+psycopg://postgres:postgres@db:5432/warehouse
SECRET_KEY=supersecretkey
ACCESS_TOKEN_EXPIRE_MINUTES=60cp .env.example .env
make upсервис будет доступен на http://localhost:8000
swagger будет доступен на http://localhost:8000/docs
нужны python 3.12 и postgresql
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
alembic upgrade head
uvicorn app.main:app --reloadmake up-d— поднять postgres и приложениеmake down— остановить и удалить контейнеры и volumemake build— пересобрать контейнер приложенияmake logs— смотреть логи приложенияmake test— запустить pytestmake db— запустить только postgres
после make up и автоприменения миграций будут доступны
- admin:
admin@example.com / admin123 - user:
user@example.com / user123 - товары:
ноутбук,сканер
POST /api/auth/jwt/loginGET /api/auth/users/me
пример логина
curl -X POST http://localhost:8000/api/auth/jwt/login \
-H 'Content-Type: application/json' \
-d '{"email":"admin@example.com","password":"admin123"}'POST /api/products— только adminGET /api/products— любой авторизованный пользователь
пример создания товара
curl -X POST http://localhost:8000/api/products \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <access_token>' \
-d '{"name":"терминал сбора данных","total_quantity":12}'POST /api/reservationsGET /api/reservations/my?status=active&limit=20&offset=0POST /api/reservations/{reservation_uuid}/cancelGET /api/reservations?status=active&limit=20&offset=0— только adminPOST /api/reservations/{reservation_uuid}/ship— только admin
пример создания резерва
curl -X POST http://localhost:8000/api/reservations \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <access_token>' \
-d '{"product_uuid":"<product_uuid>","quantity":2}'- у товара есть
total_quantityиavailable_quantity - при создании активного резерва
available_quantityуменьшается наquantity - при отмене активного резерва
available_quantityувеличивается обратно - при отгрузке резерв переводится в
shipped, доступный остаток не меняется, потому что товар уже был снят с доступного остатка в момент резерва - резерв можно закрыть только один раз
- войти и получить access token
- смотреть свой профиль
- смотреть список товаров
- создать резерв
- смотреть только свои резервы
- отменять только свои активные резервы
- все действия обычного пользователя
- создавать товары
- смотреть все резервы
- подтверждать отгрузку любого активного резерва
для конфликтов используется структурированный формат
{
"detail": {
"code": "insufficient_stock",
"message": "requested quantity exceeds available stock",
"context": {
"product_uuid": "...",
"requested_quantity": 11,
"available_quantity": 10
}
}
}make testпокрыты ключевые сценарии
- успешный логин и
me - неверный пароль
- невалидный bearer token
- создание товара админом и запрет для обычного пользователя
- создание и отмена своего резерва
- конфликт при нехватке остатка
- запрет на работу с чужим резервом
- отгрузка админом и запрет повторного закрытия
- фильтр по статусу и пагинация
- ошибка для несуществующего товара
- ошибка для несуществующего резерва
- валидация невалидного количества
- залогиниться под
admin@example.com, создать товар и убедиться чтоavailable_quantity == total_quantity - залогиниться под
user@example.com, получить список товаров и создать резерв на доступное количество - проверить
GET /api/reservations/myи убедиться что у резерва естьreservation_key - отменить свой активный резерв и убедиться что доступный остаток у товара восстановился
- попробовать создать резерв больше остатка и получить
409 insufficient_stock - попробовать отменить чужой резерв и получить
403 - под админом отгрузить активный резерв, затем повторить отгрузку и получить
409 reservation_closed