Files
svg-backend/backend/README.md
greebo 127c5bff71 feat(backend): stabilize draft editor flow and complete smoke regression baseline
- 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
2026-03-19 22:23:46 +03:00

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.