fix(v2): reorder /settlements/stuck before /settlements/{id} (route literal vs path-param collision)
FastAPI matches routes in declaration order. The literal /settlements/stuck
was being shadowed by /settlements/{settlement_id} declared earlier, so
GET /settlements/stuck was matching settlement_id="stuck" and 404'ing
with "Settlement not found". Caught while clicking through the v2 UI
post-reinstall: the Worklist tab couldn't load.
Fix: declare the literal sub-route first. Also added a NOTE comment
above the section so a future re-shuffle re-checks the order before
landing.
Verified routes register in correct order (line numbers in views_api.py):
/settlements (404)
/settlements/stuck (433) ← literal
/settlements/{id} (463) ← path-param
/settlements/{id}/partial-dispense (478)
/settlements/{id}/force-reset (513)
/settlements/{id}/retry (565)
/settlements/{id}/notes (600)
76/76 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2886dd7394
commit
32484e3ce8
1 changed files with 37 additions and 30 deletions
67
views_api.py
67
views_api.py
|
|
@ -422,6 +422,43 @@ async def api_list_settlements_for_machine(
|
||||||
return await get_settlements_for_machine(machine_id)
|
return await get_settlements_for_machine(machine_id)
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE on route ordering: FastAPI matches in declaration order. The literal
|
||||||
|
# /settlements/stuck must be registered BEFORE /settlements/{settlement_id}
|
||||||
|
# so the literal wins. Same applies to any future literal sub-route under
|
||||||
|
# /settlements/* (don't reshuffle this section without re-confirming the
|
||||||
|
# order).
|
||||||
|
|
||||||
|
|
||||||
|
@satmachineadmin_api_router.get(
|
||||||
|
"/api/v1/dca/settlements/stuck", response_model=StuckSettlementsResponse
|
||||||
|
)
|
||||||
|
async def api_list_stuck_settlements(
|
||||||
|
threshold_minutes: int = 30,
|
||||||
|
user: User = Depends(check_user_exists),
|
||||||
|
) -> StuckSettlementsResponse:
|
||||||
|
"""Operator worklist of settlements that didn't process cleanly.
|
||||||
|
|
||||||
|
Returns three lists:
|
||||||
|
- errored: distribution failed; retry endpoint handles these
|
||||||
|
- stuck_pending: landed but never picked up by the processor
|
||||||
|
- stuck_processing: claim taken but no completion in N minutes
|
||||||
|
|
||||||
|
`threshold_minutes` controls the age threshold for 'stuck' (default 30).
|
||||||
|
Operators can force-recover stuck-processing settlements via
|
||||||
|
POST /api/v1/dca/settlements/{id}/force-reset."""
|
||||||
|
if threshold_minutes < 1:
|
||||||
|
raise HTTPException(
|
||||||
|
HTTPStatus.BAD_REQUEST, "threshold_minutes must be >= 1"
|
||||||
|
)
|
||||||
|
buckets = await get_stuck_settlements_for_operator(user.id, threshold_minutes)
|
||||||
|
return StuckSettlementsResponse(
|
||||||
|
threshold_minutes=threshold_minutes,
|
||||||
|
errored=buckets["errored"],
|
||||||
|
stuck_pending=buckets["stuck_pending"],
|
||||||
|
stuck_processing=buckets["stuck_processing"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@satmachineadmin_api_router.get(
|
@satmachineadmin_api_router.get(
|
||||||
"/api/v1/dca/settlements/{settlement_id}", response_model=DcaSettlement
|
"/api/v1/dca/settlements/{settlement_id}", response_model=DcaSettlement
|
||||||
)
|
)
|
||||||
|
|
@ -472,36 +509,6 @@ async def api_partial_dispense(
|
||||||
raise HTTPException(HTTPStatus.BAD_REQUEST, str(exc)) from exc
|
raise HTTPException(HTTPStatus.BAD_REQUEST, str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
@satmachineadmin_api_router.get(
|
|
||||||
"/api/v1/dca/settlements/stuck", response_model=StuckSettlementsResponse
|
|
||||||
)
|
|
||||||
async def api_list_stuck_settlements(
|
|
||||||
threshold_minutes: int = 30,
|
|
||||||
user: User = Depends(check_user_exists),
|
|
||||||
) -> StuckSettlementsResponse:
|
|
||||||
"""Operator worklist of settlements that didn't process cleanly.
|
|
||||||
|
|
||||||
Returns three lists:
|
|
||||||
- errored: distribution failed; retry endpoint handles these
|
|
||||||
- stuck_pending: landed but never picked up by the processor
|
|
||||||
- stuck_processing: claim taken but no completion in N minutes
|
|
||||||
|
|
||||||
`threshold_minutes` controls the age threshold for 'stuck' (default 30).
|
|
||||||
Operators can force-recover stuck-processing settlements via
|
|
||||||
POST /api/v1/dca/settlements/{id}/force-reset."""
|
|
||||||
if threshold_minutes < 1:
|
|
||||||
raise HTTPException(
|
|
||||||
HTTPStatus.BAD_REQUEST, "threshold_minutes must be >= 1"
|
|
||||||
)
|
|
||||||
buckets = await get_stuck_settlements_for_operator(user.id, threshold_minutes)
|
|
||||||
return StuckSettlementsResponse(
|
|
||||||
threshold_minutes=threshold_minutes,
|
|
||||||
errored=buckets["errored"],
|
|
||||||
stuck_pending=buckets["stuck_pending"],
|
|
||||||
stuck_processing=buckets["stuck_processing"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@satmachineadmin_api_router.post(
|
@satmachineadmin_api_router.post(
|
||||||
"/api/v1/dca/settlements/{settlement_id}/force-reset",
|
"/api/v1/dca/settlements/{settlement_id}/force-reset",
|
||||||
response_model=DcaSettlement,
|
response_model=DcaSettlement,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue