feat: add optimistic concurrency guards for draft editor, pricing and publish flows
add optimistic concurrency guards via expected scheme version id protect draft editor, pricing snapshot, remap and publish flows from stale mutations protect version creation from stale current version state keep backward compatibility with optional query guards verify 409 conflict behavior for stale clients and 200 for valid flows
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
|
||||
from app.core.config import settings
|
||||
from app.repositories.audit import create_audit_event
|
||||
@@ -152,9 +152,13 @@ async def get_draft_compare_preview(
|
||||
async def create_draft_sector(
|
||||
scheme_id: str,
|
||||
payload: CreateSectorRequest,
|
||||
expected_scheme_version_id: str | None = Query(default=None),
|
||||
role: str = Depends(require_api_key),
|
||||
):
|
||||
scheme, version = await get_current_draft_context(scheme_id)
|
||||
scheme, version = await get_current_draft_context(
|
||||
scheme_id,
|
||||
expected_scheme_version_id=expected_scheme_version_id,
|
||||
)
|
||||
|
||||
row = await create_scheme_version_sector(
|
||||
scheme_id=scheme.scheme_id,
|
||||
@@ -191,9 +195,13 @@ async def create_draft_sector(
|
||||
async def create_draft_group(
|
||||
scheme_id: str,
|
||||
payload: CreateGroupRequest,
|
||||
expected_scheme_version_id: str | None = Query(default=None),
|
||||
role: str = Depends(require_api_key),
|
||||
):
|
||||
scheme, version = await get_current_draft_context(scheme_id)
|
||||
scheme, version = await get_current_draft_context(
|
||||
scheme_id,
|
||||
expected_scheme_version_id=expected_scheme_version_id,
|
||||
)
|
||||
|
||||
row = await create_scheme_version_group(
|
||||
scheme_id=scheme.scheme_id,
|
||||
@@ -230,9 +238,13 @@ async def create_draft_group(
|
||||
async def delete_draft_sector(
|
||||
scheme_id: str,
|
||||
sector_record_id: str,
|
||||
expected_scheme_version_id: str | None = Query(default=None),
|
||||
role: str = Depends(require_api_key),
|
||||
):
|
||||
scheme, version = await get_current_draft_context(scheme_id)
|
||||
scheme, version = await get_current_draft_context(
|
||||
scheme_id,
|
||||
expected_scheme_version_id=expected_scheme_version_id,
|
||||
)
|
||||
|
||||
await delete_scheme_version_sector_by_record_id(
|
||||
scheme_version_id=version.scheme_version_id,
|
||||
@@ -259,9 +271,13 @@ async def delete_draft_sector(
|
||||
async def delete_draft_group(
|
||||
scheme_id: str,
|
||||
group_record_id: str,
|
||||
expected_scheme_version_id: str | None = Query(default=None),
|
||||
role: str = Depends(require_api_key),
|
||||
):
|
||||
scheme, version = await get_current_draft_context(scheme_id)
|
||||
scheme, version = await get_current_draft_context(
|
||||
scheme_id,
|
||||
expected_scheme_version_id=expected_scheme_version_id,
|
||||
)
|
||||
|
||||
await delete_scheme_version_group_by_record_id(
|
||||
scheme_version_id=version.scheme_version_id,
|
||||
@@ -289,9 +305,13 @@ async def patch_draft_seat(
|
||||
scheme_id: str,
|
||||
seat_record_id: str,
|
||||
payload: SeatPatchRequest,
|
||||
expected_scheme_version_id: str | None = Query(default=None),
|
||||
role: str = Depends(require_api_key),
|
||||
):
|
||||
scheme, version = await get_current_draft_context(scheme_id)
|
||||
scheme, version = await get_current_draft_context(
|
||||
scheme_id,
|
||||
expected_scheme_version_id=expected_scheme_version_id,
|
||||
)
|
||||
|
||||
await validate_single_seat_patch_uniqueness(
|
||||
scheme_version_id=version.scheme_version_id,
|
||||
@@ -340,9 +360,13 @@ async def patch_draft_seat(
|
||||
async def bulk_patch_draft_seats(
|
||||
scheme_id: str,
|
||||
payload: BulkSeatPatchRequest,
|
||||
expected_scheme_version_id: str | None = Query(default=None),
|
||||
role: str = Depends(require_api_key),
|
||||
):
|
||||
scheme, version = await get_current_draft_context(scheme_id)
|
||||
scheme, version = await get_current_draft_context(
|
||||
scheme_id,
|
||||
expected_scheme_version_id=expected_scheme_version_id,
|
||||
)
|
||||
|
||||
items = [item.model_dump() for item in payload.items]
|
||||
await validate_bulk_seat_patch_uniqueness(
|
||||
@@ -389,9 +413,13 @@ async def patch_draft_sector(
|
||||
scheme_id: str,
|
||||
sector_record_id: str,
|
||||
payload: SectorPatchRequest,
|
||||
expected_scheme_version_id: str | None = Query(default=None),
|
||||
role: str = Depends(require_api_key),
|
||||
):
|
||||
scheme, version = await get_current_draft_context(scheme_id)
|
||||
scheme, version = await get_current_draft_context(
|
||||
scheme_id,
|
||||
expected_scheme_version_id=expected_scheme_version_id,
|
||||
)
|
||||
|
||||
await validate_sector_patch_uniqueness(
|
||||
scheme_version_id=version.scheme_version_id,
|
||||
@@ -421,7 +449,8 @@ async def patch_draft_sector(
|
||||
object_ref=sector_record_id,
|
||||
details={
|
||||
"scheme_version_id": version.scheme_version_id,
|
||||
"sector_id": payload.sector_id,
|
||||
"old_sector_id": old_sector_id,
|
||||
"new_sector_id": payload.sector_id,
|
||||
"name": payload.name,
|
||||
"cascaded_seats_count": cascaded_count,
|
||||
"repair_result": repair_result,
|
||||
@@ -442,9 +471,13 @@ async def patch_draft_group(
|
||||
scheme_id: str,
|
||||
group_record_id: str,
|
||||
payload: GroupPatchRequest,
|
||||
expected_scheme_version_id: str | None = Query(default=None),
|
||||
role: str = Depends(require_api_key),
|
||||
):
|
||||
scheme, version = await get_current_draft_context(scheme_id)
|
||||
scheme, version = await get_current_draft_context(
|
||||
scheme_id,
|
||||
expected_scheme_version_id=expected_scheme_version_id,
|
||||
)
|
||||
|
||||
await validate_group_patch_uniqueness(
|
||||
scheme_version_id=version.scheme_version_id,
|
||||
@@ -474,7 +507,8 @@ async def patch_draft_group(
|
||||
object_ref=group_record_id,
|
||||
details={
|
||||
"scheme_version_id": version.scheme_version_id,
|
||||
"group_id": payload.group_id,
|
||||
"old_group_id": old_group_id,
|
||||
"new_group_id": payload.group_id,
|
||||
"name": payload.name,
|
||||
"cascaded_seats_count": cascaded_count,
|
||||
"repair_result": repair_result,
|
||||
@@ -493,9 +527,14 @@ async def patch_draft_group(
|
||||
@router.post(f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/draft/repair-references", response_model=RepairReferencesResponse)
|
||||
async def repair_draft_references(
|
||||
scheme_id: str,
|
||||
expected_scheme_version_id: str | None = Query(default=None),
|
||||
role: str = Depends(require_api_key),
|
||||
):
|
||||
scheme, version = await get_current_draft_context(scheme_id)
|
||||
scheme, version = await get_current_draft_context(
|
||||
scheme_id,
|
||||
expected_scheme_version_id=expected_scheme_version_id,
|
||||
)
|
||||
|
||||
result = await repair_structure_references(
|
||||
scheme_version_id=version.scheme_version_id,
|
||||
)
|
||||
@@ -511,7 +550,7 @@ async def repair_draft_references(
|
||||
return RepairReferencesResponse(
|
||||
scheme_id=scheme.scheme_id,
|
||||
scheme_version_id=version.scheme_version_id,
|
||||
repaired_sector_refs_count=result["repaired_sector_refs_count"],
|
||||
repaired_group_refs_count=result["repaired_group_refs_count"],
|
||||
details=result["details"],
|
||||
repaired_sector_refs_count=result.get("repaired_sector_refs_count", 0),
|
||||
repaired_group_refs_count=result.get("repaired_group_refs_count", 0),
|
||||
details=result,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user