Files
svg-backend/README.md
2026-03-19 13:39:32 +03:00

468 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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: <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/<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