Add genlocke leg progression with advance endpoint and run context
When a run belonging to a genlocke is completed or failed, the genlocke status updates accordingly. The run detail API now includes genlocke context (leg order, total legs, genlocke name). A new advance endpoint creates the next leg's run, and the frontend shows genlocke-aware UI including a "Leg X of Y" banner, advance button, and contextual messaging in the end-run modal. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import joinedload, selectinload
|
||||
|
||||
from app.core.database import get_session
|
||||
from app.models.encounter import Encounter
|
||||
from app.models.game import Game
|
||||
from app.models.genlocke import Genlocke, GenlockeLeg
|
||||
from app.models.nuzlocke_run import NuzlockeRun
|
||||
from app.schemas.run import RunCreate, RunDetailResponse, RunResponse, RunUpdate
|
||||
from app.schemas.run import RunCreate, RunDetailResponse, RunGenlockeContext, RunResponse, RunUpdate
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -61,7 +62,33 @@ async def get_run(run_id: int, session: AsyncSession = Depends(get_session)):
|
||||
run = result.scalar_one_or_none()
|
||||
if run is None:
|
||||
raise HTTPException(status_code=404, detail="Run not found")
|
||||
return run
|
||||
|
||||
# Check if this run belongs to a genlocke
|
||||
genlocke_context = None
|
||||
leg_result = await session.execute(
|
||||
select(GenlockeLeg)
|
||||
.where(GenlockeLeg.run_id == run_id)
|
||||
.options(joinedload(GenlockeLeg.genlocke))
|
||||
)
|
||||
leg = leg_result.scalar_one_or_none()
|
||||
if leg:
|
||||
total_legs_result = await session.execute(
|
||||
select(func.count())
|
||||
.select_from(GenlockeLeg)
|
||||
.where(GenlockeLeg.genlocke_id == leg.genlocke_id)
|
||||
)
|
||||
total_legs = total_legs_result.scalar_one()
|
||||
genlocke_context = RunGenlockeContext(
|
||||
genlocke_id=leg.genlocke_id,
|
||||
genlocke_name=leg.genlocke.name,
|
||||
leg_order=leg.leg_order,
|
||||
total_legs=total_legs,
|
||||
is_final_leg=leg.leg_order == total_legs,
|
||||
)
|
||||
|
||||
response = RunDetailResponse.model_validate(run)
|
||||
response.genlocke = genlocke_context
|
||||
return response
|
||||
|
||||
|
||||
@router.patch("/{run_id}", response_model=RunResponse)
|
||||
@@ -87,6 +114,28 @@ async def update_run(
|
||||
for field, value in update_data.items():
|
||||
setattr(run, field, value)
|
||||
|
||||
# Genlocke side effects when run status changes
|
||||
if "status" in update_data and update_data["status"] in ("completed", "failed"):
|
||||
leg_result = await session.execute(
|
||||
select(GenlockeLeg)
|
||||
.where(GenlockeLeg.run_id == run_id)
|
||||
.options(joinedload(GenlockeLeg.genlocke))
|
||||
)
|
||||
leg = leg_result.scalar_one_or_none()
|
||||
if leg:
|
||||
genlocke = leg.genlocke
|
||||
if update_data["status"] == "failed":
|
||||
genlocke.status = "failed"
|
||||
elif update_data["status"] == "completed":
|
||||
total_legs_result = await session.execute(
|
||||
select(func.count())
|
||||
.select_from(GenlockeLeg)
|
||||
.where(GenlockeLeg.genlocke_id == genlocke.id)
|
||||
)
|
||||
total_legs = total_legs_result.scalar_one()
|
||||
if leg.leg_order == total_legs:
|
||||
genlocke.status = "completed"
|
||||
|
||||
await session.commit()
|
||||
await session.refresh(run)
|
||||
return run
|
||||
|
||||
Reference in New Issue
Block a user