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
This commit is contained in:
330
backend/README.md
Normal file
330
backend/README.md
Normal file
@@ -0,0 +1,330 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user