# 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_NAME` - `APP_ENV` - `APP_PORT` - `API_V1_PREFIX` - `AUTH_HEADER_NAME` - `ADMIN_API_KEY` - `VIEWER_API_KEY` - `SVG_MAX_FILE_SIZE_BYTES` - `SVG_MAX_ELEMENTS` - `SVG_ALLOW_INTERNAL_USE_REFERENCES_ONLY` - `SVG_FORBID_FOREIGN_OBJECT_V1` - `SVG_FORBID_STYLE_V1` - `SVG_FORBID_IMAGE_V1` - `POSTGRES_HOST` - `POSTGRES_PORT` - `POSTGRES_DB` - `POSTGRES_USER` - `POSTGRES_PASSWORD` - `DATABASE_URL` --- ## Storage layout На файловой системе используются каталоги: - `storage/original` - `storage/sanitized` - `storage/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 = published` - `published_at = now()` - current version получает статус `published` - пишет audit event ### POST `/api/v1/schemes/{scheme_id}/unpublish` Снимает публикацию. Что делает: - `scheme.status = draft` - `published_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_id` - `sector_id` - `group_id` - `row_label` - `seat_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` только: - `sector` - `group` - `seat` - `currency` только `RUB` - `amount` хранится как `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 1. `seat` 2. `group` 3. `sector` #### Ответ Возвращает: - `seat_id` - `sector_id` - `group_id` - `matched_rule_level` - `matched_target_ref` - `pricing_category_id` - `amount` - `currency` Если правило не найдено: - `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 - `selectable` - `has_price` - pricing info, если найдено effective rule Логика: - если price найден -> `selectable = true` - если price не найден -> `selectable = false` --- ## 9. Audit ### GET `/api/v1/schemes/{scheme_id}/audit` История событий схемы. Сейчас в audit пишутся: - `scheme.created_from_upload` - `scheme.published` - `scheme.unpublished` - `scheme.rolled_back` - `scheme.version.created` - `pricing.category.created` - `pricing.category.updated` - `pricing.category.deleted` - `pricing.rule.created` - `pricing.rule.updated` - `pricing.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//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":"","target_type":"sector","target_ref":"vip","amount":"2500.00","currency":"RUB"}' \ http://127.0.0.1:9020/api/v1/schemes//pricing/rules ### 5. Проверить цену места curl -s -H 'X-API-Key: admin-local-dev-key' \ http://127.0.0.1:9020/api/v1/schemes//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//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