restrict ops endpoints to admin-only access block operator and viewer keys from admin maintenance routes cover destructive pricing cleanup in smoke execution, not only preview extend orchestration without regressing existing smoke stages
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