feat(backend): add single-record draft read endpoints

add backend read endpoints for single draft records

support direct fetch of individual draft entities
improve draft inspection and targeted editor workflows
This commit is contained in:
greebo
2026-03-19 19:42:03 +03:00
parent 56aadf848b
commit 35fc170cef
6 changed files with 169 additions and 0 deletions

View File

@@ -5,6 +5,7 @@ from app.repositories.audit import create_audit_event
from app.repositories.scheme_groups import ( from app.repositories.scheme_groups import (
create_scheme_version_group, create_scheme_version_group,
delete_scheme_version_group_by_record_id, delete_scheme_version_group_by_record_id,
get_scheme_version_group_by_record_id,
list_scheme_version_groups, list_scheme_version_groups,
update_scheme_version_group_by_record_id, update_scheme_version_group_by_record_id,
) )
@@ -12,12 +13,14 @@ from app.repositories.scheme_seats import (
bulk_update_scheme_version_seats_by_record_id, bulk_update_scheme_version_seats_by_record_id,
cascade_update_seat_group_reference, cascade_update_seat_group_reference,
cascade_update_seat_sector_reference, cascade_update_seat_sector_reference,
get_scheme_version_seat_by_record_id,
list_scheme_version_seats, list_scheme_version_seats,
update_scheme_version_seat_by_record_id, update_scheme_version_seat_by_record_id,
) )
from app.repositories.scheme_sectors import ( from app.repositories.scheme_sectors import (
create_scheme_version_sector, create_scheme_version_sector,
delete_scheme_version_sector_by_record_id, delete_scheme_version_sector_by_record_id,
get_scheme_version_sector_by_record_id,
list_scheme_version_sectors, list_scheme_version_sectors,
update_scheme_version_sector_by_record_id, update_scheme_version_sector_by_record_id,
) )
@@ -154,6 +157,85 @@ async def get_draft_validation(
} }
@router.get(f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/draft/seats/records/{{seat_record_id}}", response_model=DraftSeatItem)
async def get_draft_seat_by_record_id(
scheme_id: str,
seat_record_id: str,
role: str = Depends(require_api_key),
):
_scheme, version = await get_current_draft_context(scheme_id)
row = await get_scheme_version_seat_by_record_id(
scheme_version_id=version.scheme_version_id,
seat_record_id=seat_record_id,
)
return DraftSeatItem(
seat_record_id=row.seat_record_id,
scheme_id=row.scheme_id,
scheme_version_id=row.scheme_version_id,
element_id=row.element_id,
seat_id=row.seat_id,
sector_id=row.sector_id,
group_id=row.group_id,
row_label=row.row_label,
seat_number=row.seat_number,
tag=row.tag,
classes_raw=row.classes_raw,
x=row.x,
y=row.y,
cx=row.cx,
cy=row.cy,
width=row.width,
height=row.height,
created_at=row.created_at.isoformat(),
)
@router.get(f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/draft/sectors/records/{{sector_record_id}}", response_model=DraftSectorItem)
async def get_draft_sector_by_record_id(
scheme_id: str,
sector_record_id: str,
role: str = Depends(require_api_key),
):
_scheme, version = await get_current_draft_context(scheme_id)
row = await get_scheme_version_sector_by_record_id(
scheme_version_id=version.scheme_version_id,
sector_record_id=sector_record_id,
)
return DraftSectorItem(
sector_record_id=row.sector_record_id,
scheme_id=row.scheme_id,
scheme_version_id=row.scheme_version_id,
element_id=row.element_id,
sector_id=row.sector_id,
name=row.name,
classes_raw=row.classes_raw,
created_at=row.created_at.isoformat(),
)
@router.get(f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/draft/groups/records/{{group_record_id}}", response_model=DraftGroupItem)
async def get_draft_group_by_record_id(
scheme_id: str,
group_record_id: str,
role: str = Depends(require_api_key),
):
_scheme, version = await get_current_draft_context(scheme_id)
row = await get_scheme_version_group_by_record_id(
scheme_version_id=version.scheme_version_id,
group_record_id=group_record_id,
)
return DraftGroupItem(
group_record_id=row.group_record_id,
scheme_id=row.scheme_id,
scheme_version_id=row.scheme_version_id,
element_id=row.element_id,
group_id=row.group_id,
name=row.name,
classes_raw=row.classes_raw,
created_at=row.created_at.isoformat(),
)
@router.get(f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/draft/compare-preview", response_model=StructureDiffResponse) @router.get(f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/draft/compare-preview", response_model=StructureDiffResponse)
async def get_draft_compare_preview( async def get_draft_compare_preview(
scheme_id: str, scheme_id: str,

View File

@@ -166,3 +166,26 @@ async def delete_scheme_version_group_by_record_id(
await session.delete(group) await session.delete(group)
await session.commit() await session.commit()
async def get_scheme_version_group_by_record_id(
*,
scheme_version_id: str,
group_record_id: str,
) -> SchemeGroupRecord:
async with AsyncSessionLocal() as session:
result = await session.execute(
select(SchemeGroupRecord).where(
SchemeGroupRecord.scheme_version_id == scheme_version_id,
SchemeGroupRecord.group_record_id == group_record_id,
)
)
row = result.scalar_one_or_none()
if row is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Group record not found in current draft version",
)
return row

View File

@@ -114,6 +114,29 @@ async def get_scheme_version_seat_by_seat_id(
return row return row
async def get_scheme_version_seat_by_record_id(
*,
scheme_version_id: str,
seat_record_id: str,
) -> SchemeSeatRecord:
async with AsyncSessionLocal() as session:
result = await session.execute(
select(SchemeSeatRecord).where(
SchemeSeatRecord.scheme_version_id == scheme_version_id,
SchemeSeatRecord.seat_record_id == seat_record_id,
)
)
row = result.scalar_one_or_none()
if row is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Seat record not found in current draft version",
)
return row
async def update_scheme_version_seat_by_record_id( async def update_scheme_version_seat_by_record_id(
*, *,
scheme_version_id: str, scheme_version_id: str,

View File

@@ -166,3 +166,26 @@ async def delete_scheme_version_sector_by_record_id(
await session.delete(sector) await session.delete(sector)
await session.commit() await session.commit()
async def get_scheme_version_sector_by_record_id(
*,
scheme_version_id: str,
sector_record_id: str,
) -> SchemeSectorRecord:
async with AsyncSessionLocal() as session:
result = await session.execute(
select(SchemeSectorRecord).where(
SchemeSectorRecord.scheme_version_id == scheme_version_id,
SchemeSectorRecord.sector_record_id == sector_record_id,
)
)
row = result.scalar_one_or_none()
if row is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Sector record not found in current draft version",
)
return row

View File

@@ -57,6 +57,10 @@
## app/api/routes/editor.py ## app/api/routes/editor.py
- GET /api/v1/schemes/{scheme_id}/draft/structure - GET /api/v1/schemes/{scheme_id}/draft/structure
- GET /api/v1/schemes/{scheme_id}/draft/validation
- 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}
- GET /api/v1/schemes/{scheme_id}/draft/compare-preview - GET /api/v1/schemes/{scheme_id}/draft/compare-preview
- POST /api/v1/schemes/{scheme_id}/draft/sectors - POST /api/v1/schemes/{scheme_id}/draft/sectors
- POST /api/v1/schemes/{scheme_id}/draft/groups - POST /api/v1/schemes/{scheme_id}/draft/groups

View File

@@ -79,6 +79,7 @@ Validate:
## 6. Draft publish preview ## 6. Draft publish preview
- GET /api/v1/schemes/{scheme_id}/draft/validation -> 200
- GET /api/v1/schemes/{scheme_id}/publish/validation -> 200 - GET /api/v1/schemes/{scheme_id}/publish/validation -> 200
- POST /api/v1/schemes/{scheme_id}/draft/pricing/snapshot -> 200 when scheme is in draft - POST /api/v1/schemes/{scheme_id}/draft/pricing/snapshot -> 200 when scheme is in draft
- GET /api/v1/schemes/{scheme_id}/draft/publish-preview?refresh=true -> 200 - GET /api/v1/schemes/{scheme_id}/draft/publish-preview?refresh=true -> 200
@@ -140,3 +141,16 @@ Run this checklist after:
- display pipeline changes - display pipeline changes
- route reorganization - route reorganization
- startup/import/config changes - startup/import/config changes
## 3.1. Draft editor read model
- GET /api/v1/schemes/{scheme_id}/draft/structure -> 200 when current version is draft
- GET /api/v1/schemes/{scheme_id}/draft/seats/records/{seat_record_id} -> 200 for known seat record
- GET /api/v1/schemes/{scheme_id}/draft/sectors/records/{sector_record_id} -> 200 for known sector record
- GET /api/v1/schemes/{scheme_id}/draft/groups/records/{group_record_id} -> 200 for known group record
Validate:
- returned record belongs to current draft scheme_version_id
- single-entity endpoints match items visible in draft structure
- missing draft record returns 404, not 500