Files
nuzlocke-tracker/backend/src/app/api/encounters.py

134 lines
4.3 KiB
Python
Raw Normal View History

from fastapi import APIRouter, Depends, HTTPException, Response
from sqlalchemy import 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.nuzlocke_run import NuzlockeRun
from app.models.pokemon import Pokemon
from app.models.route import Route
from app.schemas.encounter import (
EncounterCreate,
EncounterDetailResponse,
EncounterResponse,
EncounterUpdate,
)
router = APIRouter()
@router.post(
"/runs/{run_id}/encounters",
response_model=EncounterResponse,
status_code=201,
)
async def create_encounter(
run_id: int,
data: EncounterCreate,
session: AsyncSession = Depends(get_session),
):
# Validate run exists
run = await session.get(NuzlockeRun, run_id)
if run is None:
raise HTTPException(status_code=404, detail="Run not found")
# Validate route exists and load its children
result = await session.execute(
select(Route)
.where(Route.id == data.route_id)
.options(selectinload(Route.children))
)
route = result.scalar_one_or_none()
if route is None:
raise HTTPException(status_code=404, detail="Route not found")
# Cannot create encounter on a parent route (routes with children)
if route.children:
raise HTTPException(
status_code=400,
detail="Cannot create encounter on a parent route. Use a child route instead.",
)
# If this route has a parent, check if any sibling already has an encounter
if route.parent_route_id is not None:
# Get all sibling route IDs (routes with same parent, including this one)
siblings_result = await session.execute(
select(Route.id).where(Route.parent_route_id == route.parent_route_id)
)
sibling_ids = [r for r in siblings_result.scalars().all()]
# Check if any sibling already has an encounter in this run
existing_encounter = await session.execute(
select(Encounter)
.where(
Encounter.run_id == run_id,
Encounter.route_id.in_(sibling_ids),
)
)
if existing_encounter.scalar_one_or_none() is not None:
raise HTTPException(
status_code=409,
detail="This location group already has an encounter. Only one encounter per location group is allowed.",
)
# Validate pokemon exists
pokemon = await session.get(Pokemon, data.pokemon_id)
if pokemon is None:
raise HTTPException(status_code=404, detail="Pokemon not found")
encounter = Encounter(
run_id=run_id,
route_id=data.route_id,
pokemon_id=data.pokemon_id,
nickname=data.nickname,
status=data.status,
catch_level=data.catch_level,
)
session.add(encounter)
await session.commit()
await session.refresh(encounter)
return encounter
@router.patch("/encounters/{encounter_id}", response_model=EncounterDetailResponse)
async def update_encounter(
encounter_id: int,
data: EncounterUpdate,
session: AsyncSession = Depends(get_session),
):
encounter = await session.get(Encounter, encounter_id)
if encounter is None:
raise HTTPException(status_code=404, detail="Encounter not found")
update_data = data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(encounter, field, value)
await session.commit()
# Reload with relationships for detail response
result = await session.execute(
select(Encounter)
.where(Encounter.id == encounter_id)
.options(
joinedload(Encounter.pokemon),
joinedload(Encounter.current_pokemon),
joinedload(Encounter.route),
)
)
return result.scalar_one()
@router.delete("/encounters/{encounter_id}", status_code=204)
async def delete_encounter(
encounter_id: int, session: AsyncSession = Depends(get_session)
):
encounter = await session.get(Encounter, encounter_id)
if encounter is None:
raise HTTPException(status_code=404, detail="Encounter not found")
await session.delete(encounter)
await session.commit()
return Response(status_code=204)