separate smoke coverage into core backend checks and pricing publish flow checks make regression runs more focused and easier to maintain improve troubleshooting when a smoke stage fails
SVG Service
Backend-сервис для загрузки, sanitization, normalization, хранения и базовой эксплуатации SVG-схем с местами, секторами, группами и pricing.
Текущее состояние
Реализовано:
- загрузка SVG-файла
- безопасная первичная обработка SVG
- sanitization запрещённых элементов и атрибутов
- normalization в машиночитаемый JSON snapshot
- хранение original / sanitized / normalized файлов
- PostgreSQL persistence
- registry uploads / schemes / versions
- lifecycle схем:
- draft
- publish
- unpublish
- rollback
- create next draft version
- persistence доменных сущностей:
- sectors
- groups
- seats
- pricing v1:
- pricing categories
- price rules
- effective price resolution
- test mode preview API
- audit/history API
Стек
- Python 3.11+
- FastAPI
- Pydantic
- SQLAlchemy Async
- PostgreSQL
- Alembic
- Docker Compose
Запуск
1. Сборка и запуск
docker compose build --no-cache docker compose up -d
2. Применение миграций
docker compose run --rm svg-service alembic -c /app/alembic.ini upgrade head
3. Проверка
curl -s http://127.0.0.1:9020/healthz
Ожидаемый ответ:
{"status":"ok"}
Аутентификация
Во все защищённые эндпойнты передаётся заголовок:
X-API-Key:
Локально использовались ключи из .env.
Основные env-переменные
Примерно используются такие параметры:
APP_NAMEAPP_ENVAPP_PORTAPI_V1_PREFIXAUTH_HEADER_NAMEADMIN_API_KEYVIEWER_API_KEYSVG_MAX_FILE_SIZE_BYTESSVG_MAX_ELEMENTSSVG_ALLOW_INTERNAL_USE_REFERENCES_ONLYSVG_FORBID_FOREIGN_OBJECT_V1SVG_FORBID_STYLE_V1SVG_FORBID_IMAGE_V1POSTGRES_HOSTPOSTGRES_PORTPOSTGRES_DBPOSTGRES_USERPOSTGRES_PASSWORDDATABASE_URL
Storage layout
На файловой системе используются каталоги:
storage/originalstorage/sanitizedstorage/normalized
Каждая загрузка сохраняется в свой каталог по upload_id.
API
1. Service / auth / diagnostics
GET /
Базовая информация о сервисе.
GET /healthz
Healthcheck.
GET /api/v1/ping
Проверка API и auth.
GET /api/v1/auth/me
Возвращает текущую роль по API key.
GET /api/v1/db/ping
Проверка доступности PostgreSQL.
GET /api/v1/manifest
Возвращает manifest сервиса:
- лимиты SVG
- правила sanitization
- extraction contract
2. Uploads
POST /api/v1/schemes/upload
Загружает SVG, выполняет:
- validate
- inspect
- sanitize
- normalize
- storage save
- create upload record
- create scheme
- create version 1
- persist sectors/groups/seats
- audit event
Form-data
file: SVG-файл
Ответ
Возвращает:
upload_id- имя файла
- размеры
- counts
- storage paths
- accepted flag
GET /api/v1/uploads
Список upload records.
GET /api/v1/uploads/{upload_id}
Детали upload record.
GET /api/v1/uploads/{upload_id}/normalized
Чтение normalized JSON snapshot.
3. Schemes
GET /api/v1/schemes
Список схем.
GET /api/v1/schemes/{scheme_id}
Детали схемы.
GET /api/v1/schemes/{scheme_id}/current
Текущая version схемы.
GET /api/v1/schemes/{scheme_id}/versions
Список всех version схемы.
POST /api/v1/schemes/{scheme_id}/versions
Создаёт новую draft version из current version.
Что делает:
- создаёт
version_number = current + 1 - копирует metadata snapshot
- копирует sectors/groups/seats
- переключает
current_version_number - сбрасывает схему в
draft - пишет audit event
4. Scheme lifecycle
POST /api/v1/schemes/{scheme_id}/publish
Публикует current version.
Что делает:
scheme.status = publishedpublished_at = now()- current version получает статус
published - пишет audit event
POST /api/v1/schemes/{scheme_id}/unpublish
Снимает публикацию.
Что делает:
scheme.status = draftpublished_at = null- current version переводится в
draft - пишет audit event
POST /api/v1/schemes/{scheme_id}/rollback
Откатывает current version на указанную version.
JSON body
{ "target_version_number": 1 }
Что делает:
- переключает
current_version_number - сбрасывает публикацию
- схема становится
draft - пишет audit event
5. Current domain data
GET /api/v1/schemes/{scheme_id}/current/sectors
Сектора текущей версии.
GET /api/v1/schemes/{scheme_id}/current/groups
Группы текущей версии.
GET /api/v1/schemes/{scheme_id}/current/seats
Места текущей версии.
Seat содержит:
seat_idsector_idgroup_idrow_labelseat_number- geometry fields
- tag / classes
6. Pricing
6.1 Pricing categories
POST /api/v1/schemes/{scheme_id}/pricing/categories
Создать pricing category.
JSON body
{ "name": "VIP", "code": "VIP" }
PUT /api/v1/schemes/{scheme_id}/pricing/categories/{pricing_category_id}
Обновить pricing category.
JSON body
{ "name": "Econom Plus", "code": "ECO_PLUS" }
DELETE /api/v1/schemes/{scheme_id}/pricing/categories/{pricing_category_id}
Удалить pricing category.
6.2 Price rules
POST /api/v1/schemes/{scheme_id}/pricing/rules
Создать price rule.
JSON body
{ "pricing_category_id": "....", "target_type": "sector", "target_ref": "vip", "amount": "2500.00", "currency": "RUB" }
Ограничения v1
target_typeтолько:sectorgroupseat
currencyтолькоRUBamountхранится какNUMERIC(12,2)
PUT /api/v1/schemes/{scheme_id}/pricing/rules/{price_rule_id}
Обновить rule.
DELETE /api/v1/schemes/{scheme_id}/pricing/rules/{price_rule_id}
Удалить rule.
GET /api/v1/schemes/{scheme_id}/pricing
Вернуть все pricing categories и rules схемы.
7. Effective pricing
GET /api/v1/schemes/{scheme_id}/current/seats/{seat_id}/price
Рассчитать effective price для места.
Приоритет поиска rules
seatgroupsector
Ответ
Возвращает:
seat_idsector_idgroup_idmatched_rule_levelmatched_target_refpricing_category_idamountcurrency
Если правило не найдено:
404 No pricing rule matched current seat
Если место не найдено:
404 Seat not found in current scheme version
8. Test mode
GET /api/v1/schemes/{scheme_id}/test/seats/{seat_id}
Preview для frontend test mode.
Возвращает:
- seat metadata
selectablehas_price- pricing info, если найдено effective rule
Логика:
- если price найден ->
selectable = true - если price не найден ->
selectable = false
9. Audit
GET /api/v1/schemes/{scheme_id}/audit
История событий схемы.
Сейчас в audit пишутся:
scheme.created_from_uploadscheme.publishedscheme.unpublishedscheme.rolled_backscheme.version.createdpricing.category.createdpricing.category.updatedpricing.category.deletedpricing.rule.createdpricing.rule.updatedpricing.rule.deleted
Пример базового сценария
1. Загрузить SVG
curl -s -H 'X-API-Key: admin-local-dev-key'
-F 'file=@sample-contract.svg;type=image/svg+xml'
http://127.0.0.1:9020/api/v1/schemes/upload
2. Получить список схем
curl -s -H 'X-API-Key: admin-local-dev-key'
http://127.0.0.1:9020/api/v1/schemes
3. Создать pricing category
curl -s -X POST -H 'Content-Type: application/json' -H 'X-API-Key: admin-local-dev-key'
-d '{"name":"VIP","code":"VIP"}'
http://127.0.0.1:9020/api/v1/schemes/<scheme_id>/pricing/categories
4. Создать pricing rule
curl -s -X POST -H 'Content-Type: application/json' -H 'X-API-Key: admin-local-dev-key'
-d '{"pricing_category_id":"<category_id>","target_type":"sector","target_ref":"vip","amount":"2500.00","currency":"RUB"}'
http://127.0.0.1:9020/api/v1/schemes/<scheme_id>/pricing/rules
5. Проверить цену места
curl -s -H 'X-API-Key: admin-local-dev-key'
http://127.0.0.1:9020/api/v1/schemes/<scheme_id>/current/seats/seat-a1/price
6. Проверить test mode preview
curl -s -H 'X-API-Key: admin-local-dev-key'
http://127.0.0.1:9020/api/v1/schemes/<scheme_id>/test/seats/seat-a1
Ограничения текущего v1
- редактирование seats/groups/sectors через API пока не реализовано
- нет bulk update editor API
- нет optimistic locking
- нет soft delete policy
- нет полноценной multi-user edit coordination
- routes пока не разнесены по отдельным router-модулям
- нет полного integration/e2e test набора
- frontend отсутствует
Что делать дальше
Приоритет 1
Разработать frontend / test UI:
- список схем
- просмотр current seats/sectors/groups
- test mode click preview
- pricing management UI
Приоритет 2
Добавить editor operations:
- update seat/group/sector in draft version
- bulk updates
- draft save flow
Приоритет 3
Усилить backend:
- router decomposition
- service-layer cleanup
- pagination/filters for audit
- integration tests
- concurrency policy
- publish validation rules