Backend-сервис для процесса согласования контента перед публикацией.
- Python 3.12
- FastAPI
- SQLAlchemy 2.x
- Alembic migrations
- PostgreSQL в Docker Compose
- SQLite для быстрых локальных тестов
- pytest
docker compose up --buildКоманда собирает Docker-образ API, поднимает PostgreSQL, дожидается healthcheck базы, применяет миграции Alembic и запускает FastAPI на http://localhost:8000.
Проверка:
curl http://localhost:8000/health
curl http://localhost:8000/readySwagger UI доступен на http://localhost:8000/docs.
docker compose down -vЛокально:
pip install -r requirements.txt
pytestПроверка покрытия:
pytest --cov=app --cov-report=term-missingЧерез Docker-образ API:
docker compose run --rm api pytestДля локального запуска используется header-based auth stub.
Обязательные заголовки для API /api/v1/...:
X-Workspace-Id: ws_1
X-User-Id: usr_1
X-Actions: approval:read,approval:create,approval:decide,approval:cancelПоддерживаемые actions:
approval:read— чтение заявокapproval:create— создание заявокapproval:decide— approve/rejectapproval:cancel— cancel
X-Workspace-Id обязан совпадать с {workspace_id} в URL. Иначе сервис вернёт 403.
Для создания заявки обязателен заголовок идемпотентности:
Idempotency-Key: client-request-123Повтор POST с тем же Idempotency-Key и тем же body вернёт уже созданную заявку. Повтор с тем же ключом, но другим body вернёт 409 Conflict.
curl -X POST http://localhost:8000/api/v1/workspaces/ws_1/approval-requests \
-H 'Content-Type: application/json' \
-H 'X-Workspace-Id: ws_1' \
-H 'X-User-Id: usr_1' \
-H 'X-Actions: approval:create' \
-H 'Idempotency-Key: create-approval-001' \
-d '{
"sourceType": "publication",
"sourceId": "pub_123",
"title": "Instagram reel draft",
"description": "Needs final approval",
"reviewerUserIds": ["usr_2", "usr_3"]
}'curl http://localhost:8000/api/v1/workspaces/ws_1/approval-requests \
-H 'X-Workspace-Id: ws_1' \
-H 'X-User-Id: usr_1' \
-H 'X-Actions: approval:read'curl -X POST http://localhost:8000/api/v1/workspaces/ws_1/approval-requests/<request_id>/approve \
-H 'Content-Type: application/json' \
-H 'X-Workspace-Id: ws_1' \
-H 'X-User-Id: usr_reviewer' \
-H 'X-Actions: approval:decide' \
-d '{"comment": "Approved"}'curl -X POST http://localhost:8000/api/v1/workspaces/ws_1/approval-requests/<request_id>/reject \
-H 'Content-Type: application/json' \
-H 'X-Workspace-Id: ws_1' \
-H 'X-User-Id: usr_reviewer' \
-H 'X-Actions: approval:decide' \
-d '{"reason": "Brand tone is wrong"}'curl -X POST http://localhost:8000/api/v1/workspaces/ws_1/approval-requests/<request_id>/cancel \
-H 'Content-Type: application/json' \
-H 'X-Workspace-Id: ws_1' \
-H 'X-User-Id: usr_1' \
-H 'X-Actions: approval:cancel' \
-d '{"reason": "Draft was removed"}'Миграции применяются автоматически при старте контейнера API.
Ручной запуск:
DATABASE_URL=postgresql+psycopg://approval:approval@localhost:5432/approval_service alembic upgrade headСервис не принимает в публичные поля секреты, токены, email, storage keys, signed URLs, provider URLs и сырые provider payloads. Валидационные ошибки возвращаются в обобщённом виде, чтобы не отразить потенциально чувствительный input обратно клиенту.