147 lines
5.0 KiB
Python
147 lines
5.0 KiB
Python
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||
|
|
from sqlalchemy import func, or_, select
|
||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
|
from sqlalchemy.orm import joinedload
|
||
|
|
|
||
|
|
from app.core.database import get_session
|
||
|
|
from app.models.evolution import Evolution
|
||
|
|
from app.models.pokemon import Pokemon
|
||
|
|
from app.schemas.pokemon import (
|
||
|
|
EvolutionAdminResponse,
|
||
|
|
EvolutionCreate,
|
||
|
|
EvolutionUpdate,
|
||
|
|
PaginatedEvolutionResponse,
|
||
|
|
)
|
||
|
|
|
||
|
|
router = APIRouter()
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/evolutions", response_model=PaginatedEvolutionResponse)
|
||
|
|
async def list_evolutions(
|
||
|
|
search: str | None = Query(None),
|
||
|
|
limit: int = Query(50, ge=1, le=500),
|
||
|
|
offset: int = Query(0, ge=0),
|
||
|
|
session: AsyncSession = Depends(get_session),
|
||
|
|
):
|
||
|
|
base_query = (
|
||
|
|
select(Evolution)
|
||
|
|
.options(joinedload(Evolution.from_pokemon), joinedload(Evolution.to_pokemon))
|
||
|
|
)
|
||
|
|
|
||
|
|
if search:
|
||
|
|
search_lower = search.lower()
|
||
|
|
# Join pokemon to search by name
|
||
|
|
from_pokemon = select(Pokemon.id).where(
|
||
|
|
func.lower(Pokemon.name).contains(search_lower)
|
||
|
|
).scalar_subquery()
|
||
|
|
base_query = base_query.where(
|
||
|
|
or_(
|
||
|
|
Evolution.from_pokemon_id.in_(from_pokemon),
|
||
|
|
Evolution.to_pokemon_id.in_(from_pokemon),
|
||
|
|
func.lower(Evolution.trigger).contains(search_lower),
|
||
|
|
func.lower(Evolution.item).contains(search_lower),
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
# Count total (without eager loads)
|
||
|
|
count_base = select(Evolution)
|
||
|
|
if search:
|
||
|
|
search_lower = search.lower()
|
||
|
|
from_pokemon = select(Pokemon.id).where(
|
||
|
|
func.lower(Pokemon.name).contains(search_lower)
|
||
|
|
).scalar_subquery()
|
||
|
|
count_base = count_base.where(
|
||
|
|
or_(
|
||
|
|
Evolution.from_pokemon_id.in_(from_pokemon),
|
||
|
|
Evolution.to_pokemon_id.in_(from_pokemon),
|
||
|
|
func.lower(Evolution.trigger).contains(search_lower),
|
||
|
|
func.lower(Evolution.item).contains(search_lower),
|
||
|
|
)
|
||
|
|
)
|
||
|
|
count_query = select(func.count()).select_from(count_base.subquery())
|
||
|
|
total = (await session.execute(count_query)).scalar() or 0
|
||
|
|
|
||
|
|
items_query = base_query.order_by(Evolution.from_pokemon_id, Evolution.to_pokemon_id).offset(offset).limit(limit)
|
||
|
|
result = await session.execute(items_query)
|
||
|
|
items = result.scalars().unique().all()
|
||
|
|
|
||
|
|
return PaginatedEvolutionResponse(
|
||
|
|
items=items,
|
||
|
|
total=total,
|
||
|
|
limit=limit,
|
||
|
|
offset=offset,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("/evolutions", response_model=EvolutionAdminResponse, status_code=201)
|
||
|
|
async def create_evolution(
|
||
|
|
data: EvolutionCreate, session: AsyncSession = Depends(get_session)
|
||
|
|
):
|
||
|
|
from_pokemon = await session.get(Pokemon, data.from_pokemon_id)
|
||
|
|
if from_pokemon is None:
|
||
|
|
raise HTTPException(status_code=404, detail="From pokemon not found")
|
||
|
|
|
||
|
|
to_pokemon = await session.get(Pokemon, data.to_pokemon_id)
|
||
|
|
if to_pokemon is None:
|
||
|
|
raise HTTPException(status_code=404, detail="To pokemon not found")
|
||
|
|
|
||
|
|
evolution = Evolution(**data.model_dump())
|
||
|
|
session.add(evolution)
|
||
|
|
await session.commit()
|
||
|
|
|
||
|
|
# Reload with relationships
|
||
|
|
result = await session.execute(
|
||
|
|
select(Evolution)
|
||
|
|
.where(Evolution.id == evolution.id)
|
||
|
|
.options(joinedload(Evolution.from_pokemon), joinedload(Evolution.to_pokemon))
|
||
|
|
)
|
||
|
|
return result.scalar_one()
|
||
|
|
|
||
|
|
|
||
|
|
@router.put("/evolutions/{evolution_id}", response_model=EvolutionAdminResponse)
|
||
|
|
async def update_evolution(
|
||
|
|
evolution_id: int,
|
||
|
|
data: EvolutionUpdate,
|
||
|
|
session: AsyncSession = Depends(get_session),
|
||
|
|
):
|
||
|
|
evolution = await session.get(Evolution, evolution_id)
|
||
|
|
if evolution is None:
|
||
|
|
raise HTTPException(status_code=404, detail="Evolution not found")
|
||
|
|
|
||
|
|
update_data = data.model_dump(exclude_unset=True)
|
||
|
|
|
||
|
|
if "from_pokemon_id" in update_data:
|
||
|
|
from_pokemon = await session.get(Pokemon, update_data["from_pokemon_id"])
|
||
|
|
if from_pokemon is None:
|
||
|
|
raise HTTPException(status_code=404, detail="From pokemon not found")
|
||
|
|
|
||
|
|
if "to_pokemon_id" in update_data:
|
||
|
|
to_pokemon = await session.get(Pokemon, update_data["to_pokemon_id"])
|
||
|
|
if to_pokemon is None:
|
||
|
|
raise HTTPException(status_code=404, detail="To pokemon not found")
|
||
|
|
|
||
|
|
for field, value in update_data.items():
|
||
|
|
setattr(evolution, field, value)
|
||
|
|
|
||
|
|
await session.commit()
|
||
|
|
|
||
|
|
# Reload with relationships
|
||
|
|
result = await session.execute(
|
||
|
|
select(Evolution)
|
||
|
|
.where(Evolution.id == evolution.id)
|
||
|
|
.options(joinedload(Evolution.from_pokemon), joinedload(Evolution.to_pokemon))
|
||
|
|
)
|
||
|
|
return result.scalar_one()
|
||
|
|
|
||
|
|
|
||
|
|
@router.delete("/evolutions/{evolution_id}", status_code=204)
|
||
|
|
async def delete_evolution(
|
||
|
|
evolution_id: int, session: AsyncSession = Depends(get_session)
|
||
|
|
):
|
||
|
|
evolution = await session.get(Evolution, evolution_id)
|
||
|
|
if evolution is None:
|
||
|
|
raise HTTPException(status_code=404, detail="Evolution not found")
|
||
|
|
|
||
|
|
await session.delete(evolution)
|
||
|
|
await session.commit()
|