- fastapi api для заявок на отпуск
- jwt access token и refresh token
- хранение access и refresh токенов в redis с ttl
- logout завершает текущую сессию целиком и после него refresh больше не работает
- postgresql + sqlalchemy + alembic
- uuid для
user.idиleave_request.id - время заявок и обработки хранится в таймзоне
europe/moscow - pytest для базовых сценариев
- dockerfile, docker compose и makefile
app/
api/
core/
db/
schemas/
services/
alembic/
tests/
cp .env.example .env
make up
docker compose exec app python -m app.seedсервис будет доступен на http://localhost:8000
docker-compose сам переопределяет database_url и redis_url на хосты db и redis, поэтому .env.example можно использовать и для локального запуска, и для compose
для локального запуска .env.example уже содержит localhost для postgresql и redis
вариант 1: поднимать postgresql и redis локально вне проекта
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
alembic upgrade head
python -m app.seed
uvicorn app.main:app --reloadвариант 2: поднять только инфраструктуру через docker и запустить fastapi локально
cp .env.example .env
docker compose up -d db redis
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
alembic upgrade head
python -m app.seed
uvicorn app.main:app --reloadmake install
make migrate
make seed
make test
make down
docker compose exec app python -m app.seedпосле python -m app.seed создаются
- admin
admin@example.com/admin123 - user
user@example.com/user123
POST /api/auth/jwt/loginPOST /api/auth/jwt/refreshPOST /api/auth/logoutGET /api/auth/users/me
POST /api/leave-requestsGET /api/leave-requests?status=pendingPATCH /api/leave-requests/{request_id}/approvePATCH /api/leave-requests/{request_id}/rejectGET /api/admin/leave-requests?status=approved
request_id и user_id возвращаются как uuid строки
curl -X POST http://localhost:8000/api/auth/jwt/login \
-H 'Content-Type: application/json' \
-d '{"email":"admin@example.com","password":"admin123"}'curl http://localhost:8000/api/auth/users/me \
-H 'Authorization: Bearer <access_token>'curl -X POST http://localhost:8000/api/leave-requests \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <access_token>' \
-d '{"start_date":"2026-05-10","end_date":"2026-05-12","reason":"семейные дела"}'curl -X PATCH http://localhost:8000/api/leave-requests/<request_uuid>/reject \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <access_token>' \
-d '{"manager_comment":"пересечение с критичным релизом"}'- обычный пользователь видит только свой профиль и свои заявки
- обычный пользователь не может смотреть все заявки и не может менять статус
- admin видит все заявки
- admin может согласовать или отклонить только pending заявку
- повторная обработка approved и rejected заявки запрещена
- пользователь отправляет
email/passwordнаPOST /api/auth/jwt/login - сервер проверяет пароль через bcrypt
- сервер создает
session_idи выпускаетaccess tokenиrefresh token - оба токена и данные текущей сессии сохраняются в redis с ttl
- защищенные ручки принимают
Authorization: Bearer <access_token> - сервер декодирует jwt, проверяет тип токена и актуальность
session_idиjtiв redis POST /api/auth/jwt/refreshинвалидирует предыдущую пару токенов в рамках текущей сессии и выдает новую паруPOST /api/auth/logoutзавершает текущую сессию и удаляет access token, refresh token и session key из redis
все прикладные ошибки возвращаются в одном формате
{
"error": {
"code": "forbidden",
"message": "недостаточно прав",
"details": null
}
}- залогиниться под user и получить пару токенов
- вызвать
GET /api/auth/users/meс access token - создать заявку под user
- получить свои заявки с фильтром и без фильтра
- вызвать
POST /api/auth/logoutи убедиться, что старые access и refresh токены больше не работают - залогиниться под admin и согласовать заявку
- попробовать повторно отклонить уже закрытую заявку и получить
409
покрыты сценарии
- login/me
- invalid access token
- refresh rotates tokens
- logout invalidates current session access token
- logout invalidates refresh token of current session
- owner sees only own requests
- admin sees all requests
- user cannot approve request
- request not found
- invalid date range
- closed request cannot be approved twice
- overlapping requests are rejected
создание новой миграции
make makemigrations m=add_new_fieldприменение миграций
make migrate