261 lines
8.1 KiB
Markdown
261 lines
8.1 KiB
Markdown
# svg-service backend
|
|
|
|
Backend for SVG scheme upload, draft editing, pricing, diagnostics, 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
|
|
|
|
1. Upload SVG
|
|
2. Normalize and persist structure
|
|
3. Enter editor flow through context + ensure draft
|
|
4. Edit sectors / groups / seats in current draft
|
|
5. Configure pricing and inspect diagnostics
|
|
6. Build pricing snapshot
|
|
7. Inspect publish readiness and publish preview
|
|
8. Publish current draft
|
|
9. 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
|
|
|
|
### 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` should 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`
|
|
|
|
Typical typed conflicts:
|
|
- `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`
|
|
- `GET /api/v1/admin/schemes/{scheme_id}/pricing/categories/cleanup-preview`
|
|
- `POST /api/v1/admin/schemes/{scheme_id}/pricing/categories/cleanup`
|
|
|
|
## Cleanup of test pricing data
|
|
|
|
Cleanup endpoints are intended for removing diagnostic / test categories accidentally accumulated in a shared scheme.
|
|
|
|
Preview candidates:
|
|
`GET /api/v1/admin/schemes/{scheme_id}/pricing/categories/cleanup-preview`
|
|
|
|
Execute cleanup:
|
|
`POST /api/v1/admin/schemes/{scheme_id}/pricing/categories/cleanup`
|
|
|
|
Safety notes:
|
|
- use `dry_run=true` first
|
|
- keep `delete_only_without_rules=true` unless you intentionally want a harder cleanup
|
|
- prefer matching by prefixes instead of raw ids for repetitive test artifacts
|
|
|
|
Helper script:
|
|
- `backend/scripts/cleanup_test_pricing_data.sh`
|
|
|
|
Example:
|
|
`SCHEME_ID=... DRY_RUN=true ./backend/scripts/cleanup_test_pricing_data.sh`
|
|
|
|
## Typical local flow
|
|
|
|
### 1. Read current version
|
|
`GET /api/v1/schemes/{scheme_id}/current`
|
|
|
|
### 2. Ensure draft
|
|
`POST /api/v1/schemes/{scheme_id}/draft/ensure`
|
|
|
|
Store returned:
|
|
- `scheme_version_id`
|
|
|
|
### 3. Read draft state
|
|
- `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
|
|
- `GET /pricing/coverage`
|
|
- `GET /pricing/unpriced-seats`
|
|
- `GET /pricing/explain/{seat_id}`
|
|
- `GET /pricing/rules/diagnostics`
|
|
|
|
### 6. Build snapshot and inspect readiness
|
|
- `POST /draft/pricing/snapshot`
|
|
- `GET /draft/publish-readiness`
|
|
- `GET /draft/publish-preview?refresh=true`
|
|
|
|
### 7. Publish
|
|
- `POST /publish?expected_scheme_version_id=...`
|
|
|
|
## Regression
|
|
|
|
Main operator regressions:
|
|
- `backend/scripts/smoke_regression.sh`
|
|
- `backend/scripts/editor_mutation_regression.sh`
|
|
|
|
Run:
|
|
`API_URL=http://127.0.0.1:9020 API_KEY=admin-local-dev-key SCHEME_ID=... ./backend/scripts/smoke_regression.sh`
|
|
|
|
`API_URL=http://127.0.0.1:9020 API_KEY=admin-local-dev-key SCHEME_ID=... ./backend/scripts/editor_mutation_regression.sh`
|