- add editor entry flow with editor context and ensure-draft bootstrap - add draft summary read model and single-record draft read endpoints - add typed draft, edit and publish conflicts with validation errors - add pricing diagnostics and publish readiness endpoints - fix Decimal serialization in seat price and test preview flows - harden draft lifecycle guards for published vs draft current version - update API map and smoke regression checklist - add backend README and smoke regression script
331 lines
8.3 KiB
Markdown
331 lines
8.3 KiB
Markdown
# svg-service backend
|
|
|
|
Backend for SVG scheme upload, draft editing, pricing, publish preview, and publish lifecycle.
|
|
|
|
## Stack
|
|
|
|
- Python 3.11
|
|
- FastAPI
|
|
- SQLAlchemy async
|
|
- PostgreSQL 16
|
|
- Docker Compose
|
|
|
|
## Runtime
|
|
|
|
Default backend port: `9020`
|
|
|
|
Health check:
|
|
|
|
- `GET /healthz`
|
|
|
|
Main API prefix:
|
|
|
|
- `/api/v1`
|
|
|
|
Auth header:
|
|
|
|
- `X-API-Key`
|
|
|
|
Default local admin key:
|
|
|
|
- `admin-local-dev-key`
|
|
|
|
## Core lifecycle
|
|
|
|
The backend works with a scheme lifecycle:
|
|
|
|
1. Upload SVG
|
|
2. Normalize and persist structure
|
|
3. Work in current draft
|
|
4. Create / update pricing
|
|
5. Build pricing snapshot
|
|
6. Inspect publish preview / readiness
|
|
7. Publish current draft
|
|
8. If editing is needed after publish, create or ensure a new draft again
|
|
|
|
## Main concepts
|
|
|
|
### Scheme
|
|
Top-level business entity.
|
|
|
|
### Scheme version
|
|
Concrete version of the scheme.
|
|
A version can be `draft` or `published`.
|
|
|
|
### Current version
|
|
The version referenced by the scheme registry as active current.
|
|
|
|
### Draft
|
|
Editable current version.
|
|
All editor mutations and draft pricing operations must target a current draft version only.
|
|
|
|
### Published version
|
|
Non-editable current version.
|
|
If current version is published, editor flow must first create or ensure a new draft.
|
|
|
|
### Upload artifacts
|
|
Stored technical artifacts, including:
|
|
|
|
- original svg
|
|
- sanitized svg
|
|
- normalized json
|
|
- display svg
|
|
- publish preview json
|
|
|
|
## Editor entry flow
|
|
|
|
Use this flow from frontend or operator scripts.
|
|
|
|
### 1. Inspect editor state
|
|
|
|
`GET /api/v1/schemes/{scheme_id}/editor/context`
|
|
|
|
Response tells whether:
|
|
|
|
- current version is draft
|
|
- editor is available
|
|
- a new draft should be created
|
|
- recommended action is `use_current_draft` or `create_draft`
|
|
|
|
### 2. Ensure editable draft
|
|
|
|
`POST /api/v1/schemes/{scheme_id}/draft/ensure`
|
|
|
|
Behavior:
|
|
|
|
- if current version is already draft: returns it with `created=false`
|
|
- if current version is published: clones current version into a new current draft and returns it with `created=true`
|
|
|
|
Returned `scheme_version_id` must be reused as:
|
|
|
|
- `expected_scheme_version_id`
|
|
|
|
for draft reads and mutations.
|
|
|
|
## Optimistic concurrency
|
|
|
|
Mutable draft flows support optimistic concurrency through query params:
|
|
|
|
- `expected_current_scheme_version_id`
|
|
- `expected_scheme_version_id`
|
|
|
|
These guards prevent frontend/editor from mutating a stale draft after another version switch.
|
|
|
|
Typical typed conflict payload:
|
|
|
|
- `stale_current_version`
|
|
- `stale_draft_version`
|
|
- `draft_not_editable`
|
|
- `publish_not_ready`
|
|
|
|
## Main operator routes
|
|
|
|
## System
|
|
|
|
- `GET /healthz`
|
|
- `GET /api/v1/ping`
|
|
- `GET /api/v1/db/ping`
|
|
- `GET /api/v1/manifest`
|
|
|
|
## Uploads
|
|
|
|
- `POST /api/v1/schemes/upload`
|
|
- `GET /api/v1/uploads`
|
|
- `GET /api/v1/uploads/{upload_id}`
|
|
- `GET /api/v1/uploads/{upload_id}/normalized`
|
|
|
|
## Scheme registry
|
|
|
|
- `GET /api/v1/schemes`
|
|
- `GET /api/v1/schemes/{scheme_id}`
|
|
- `GET /api/v1/schemes/{scheme_id}/current`
|
|
- `GET /api/v1/schemes/{scheme_id}/versions`
|
|
- `POST /api/v1/schemes/{scheme_id}/versions`
|
|
- `GET /api/v1/schemes/{scheme_id}/publish/validation`
|
|
- `GET /api/v1/schemes/{scheme_id}/draft/publish-readiness`
|
|
- `POST /api/v1/schemes/{scheme_id}/publish`
|
|
- `POST /api/v1/schemes/{scheme_id}/unpublish`
|
|
- `POST /api/v1/schemes/{scheme_id}/rollback`
|
|
|
|
## Editor / draft
|
|
|
|
- `GET /api/v1/schemes/{scheme_id}/editor/context`
|
|
- `POST /api/v1/schemes/{scheme_id}/draft/ensure`
|
|
- `GET /api/v1/schemes/{scheme_id}/draft/summary`
|
|
- `GET /api/v1/schemes/{scheme_id}/draft/structure`
|
|
- `GET /api/v1/schemes/{scheme_id}/draft/validation`
|
|
- `GET /api/v1/schemes/{scheme_id}/draft/compare-preview`
|
|
- `GET /api/v1/schemes/{scheme_id}/draft/seats/records/{seat_record_id}`
|
|
- `GET /api/v1/schemes/{scheme_id}/draft/sectors/records/{sector_record_id}`
|
|
- `GET /api/v1/schemes/{scheme_id}/draft/groups/records/{group_record_id}`
|
|
- `POST /api/v1/schemes/{scheme_id}/draft/sectors`
|
|
- `POST /api/v1/schemes/{scheme_id}/draft/groups`
|
|
- `DELETE /api/v1/schemes/{scheme_id}/draft/sectors/records/{sector_record_id}`
|
|
- `DELETE /api/v1/schemes/{scheme_id}/draft/groups/records/{group_record_id}`
|
|
- `PATCH /api/v1/schemes/{scheme_id}/draft/seats/records/{seat_record_id}`
|
|
- `POST /api/v1/schemes/{scheme_id}/draft/seats/bulk`
|
|
- `PATCH /api/v1/schemes/{scheme_id}/draft/sectors/records/{sector_record_id}`
|
|
- `PATCH /api/v1/schemes/{scheme_id}/draft/groups/records/{group_record_id}`
|
|
- `POST /api/v1/schemes/{scheme_id}/draft/repair-references`
|
|
|
|
## Pricing
|
|
|
|
- `GET /api/v1/schemes/{scheme_id}/pricing`
|
|
- `POST /api/v1/schemes/{scheme_id}/pricing/categories`
|
|
- `PUT /api/v1/schemes/{scheme_id}/pricing/categories/{pricing_category_id}`
|
|
- `DELETE /api/v1/schemes/{scheme_id}/pricing/categories/{pricing_category_id}`
|
|
- `POST /api/v1/schemes/{scheme_id}/pricing/rules`
|
|
- `PUT /api/v1/schemes/{scheme_id}/pricing/rules/{price_rule_id}`
|
|
- `DELETE /api/v1/schemes/{scheme_id}/pricing/rules/{price_rule_id}`
|
|
|
|
## Pricing diagnostics
|
|
|
|
- `GET /api/v1/schemes/{scheme_id}/pricing/coverage`
|
|
- `GET /api/v1/schemes/{scheme_id}/pricing/unpriced-seats`
|
|
- `GET /api/v1/schemes/{scheme_id}/pricing/explain/{seat_id}`
|
|
- `GET /api/v1/schemes/{scheme_id}/pricing/rules/diagnostics`
|
|
|
|
## Publish preview
|
|
|
|
- `POST /api/v1/schemes/{scheme_id}/draft/pricing/snapshot`
|
|
- `GET /api/v1/schemes/{scheme_id}/draft/publish-preview`
|
|
- `POST /api/v1/schemes/{scheme_id}/draft/remap/preview`
|
|
- `POST /api/v1/schemes/{scheme_id}/draft/remap/apply`
|
|
|
|
## Structure read model
|
|
|
|
- `GET /api/v1/schemes/{scheme_id}/current/sectors`
|
|
- `GET /api/v1/schemes/{scheme_id}/current/groups`
|
|
- `GET /api/v1/schemes/{scheme_id}/current/seats`
|
|
- `GET /api/v1/schemes/{scheme_id}/current/seats/{seat_id}/price`
|
|
- `GET /api/v1/schemes/{scheme_id}/current/svg`
|
|
- `GET /api/v1/schemes/{scheme_id}/current/svg/display`
|
|
- `GET /api/v1/schemes/{scheme_id}/current/svg/display/meta`
|
|
|
|
## Test mode
|
|
|
|
- `GET /api/v1/schemes/{scheme_id}/test/seats/{seat_id}`
|
|
|
|
## Audit
|
|
|
|
- `GET /api/v1/schemes/{scheme_id}/audit`
|
|
|
|
## Admin / ops
|
|
|
|
- `GET /api/v1/admin/schemes/{scheme_id}/current/artifacts`
|
|
- `GET /api/v1/admin/schemes/{scheme_id}/current/validation`
|
|
- `POST /api/v1/admin/schemes/{scheme_id}/current/display/regenerate`
|
|
- `POST /api/v1/admin/display/backfill`
|
|
- `GET /api/v1/admin/artifacts/publish-preview/audit`
|
|
- `POST /api/v1/admin/artifacts/publish-preview/cleanup`
|
|
|
|
## Typical local flow
|
|
|
|
## 1. Read current version
|
|
|
|
Use:
|
|
|
|
`GET /api/v1/schemes/{scheme_id}/current`
|
|
|
|
## 2. Ensure draft
|
|
|
|
Use:
|
|
|
|
`POST /api/v1/schemes/{scheme_id}/draft/ensure`
|
|
|
|
Store returned:
|
|
|
|
- `scheme_version_id`
|
|
|
|
## 3. Read draft state
|
|
|
|
Use:
|
|
|
|
- `GET /draft/summary?expected_scheme_version_id=...`
|
|
- `GET /draft/structure?expected_scheme_version_id=...`
|
|
- `GET /draft/validation?expected_scheme_version_id=...`
|
|
- `GET /draft/compare-preview?expected_scheme_version_id=...`
|
|
|
|
## 4. Perform editor mutations
|
|
|
|
Pass:
|
|
|
|
- `expected_scheme_version_id={draft_scheme_version_id}`
|
|
|
|
on every mutation route.
|
|
|
|
## 5. Inspect pricing quality
|
|
|
|
Use:
|
|
|
|
- `GET /pricing/coverage`
|
|
- `GET /pricing/unpriced-seats`
|
|
- `GET /pricing/explain/{seat_id}`
|
|
- `GET /pricing/rules/diagnostics`
|
|
|
|
## 6. Create pricing snapshot
|
|
|
|
Use:
|
|
|
|
`POST /draft/pricing/snapshot?expected_scheme_version_id=...`
|
|
|
|
## 7. Inspect readiness
|
|
|
|
Use:
|
|
|
|
`GET /draft/publish-readiness?expected_scheme_version_id=...`
|
|
|
|
## 8. Publish
|
|
|
|
Use:
|
|
|
|
`POST /publish?expected_scheme_version_id=...`
|
|
|
|
## Typed error expectations
|
|
|
|
Examples of stable typed errors already used in the service:
|
|
|
|
### Draft concurrency/state
|
|
- `stale_current_version`
|
|
- `stale_draft_version`
|
|
- `draft_not_editable`
|
|
|
|
### Editor validation
|
|
- `duplicate_seat_id`
|
|
- `duplicate_seat_id_in_payload`
|
|
- `duplicate_sector_id`
|
|
- `duplicate_group_id`
|
|
- `duplicate_sector_element_id`
|
|
- `duplicate_group_element_id`
|
|
- `unknown_sector_id`
|
|
- `unknown_group_id`
|
|
- `unknown_sector_ids`
|
|
- `unknown_group_ids`
|
|
- `unknown_target_sector_id`
|
|
- `unknown_target_group_id`
|
|
- `remap_filter_required`
|
|
|
|
### Pricing / publish
|
|
- `invalid_amount`
|
|
- `publish_not_ready`
|
|
|
|
## Draft summary semantics
|
|
|
|
`GET /draft/summary` is the compact route for editor bootstrap.
|
|
|
|
It returns:
|
|
|
|
- current draft counters
|
|
- validation summary
|
|
- structure diff summary
|
|
- publish readiness summary
|
|
|
|
This route is intended for frontend side panels / header status / quick preflight.
|
|
|
|
## Notes
|
|
|
|
- Draft-only routes must not mutate a published current version.
|
|
- Published current version should require `draft/ensure` before any editor mutation.
|
|
- Publish readiness can fail even if validation passes, for example when pricing snapshot is missing.
|
|
- `api-map.md` and `smoke-regression.md` must be updated together with route changes.
|