Files
nuzlocke-tracker/backend/src/app/api/export.py
Julian Tabel e4111c67bc
All checks were successful
CI / backend-lint (push) Successful in 7s
CI / frontend-lint (push) Successful in 29s
Fix linting errors across backend and frontend
Backend: auto-fix and format all ruff issues, manually fix B904/B023/
SIM117/B007/E741/F841 errors, suppress B008 (FastAPI Depends) and F821
(SQLAlchemy forward refs) in config. Frontend: allow constant exports,
disable React compiler-specific rules (set-state-in-effect,
preserve-manual-memoization).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 12:26:57 +01:00

211 lines
6.8 KiB
Python

"""Export endpoints that return data in seed JSON format."""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from app.core.database import get_session
from app.models.boss_battle import BossBattle
from app.models.boss_pokemon import BossPokemon
from app.models.evolution import Evolution
from app.models.game import Game
from app.models.pokemon import Pokemon
from app.models.route import Route
from app.models.route_encounter import RouteEncounter
router = APIRouter()
@router.get("/games")
async def export_games(session: AsyncSession = Depends(get_session)):
"""Export all games in seed JSON format."""
result = await session.execute(select(Game).order_by(Game.name))
games = result.scalars().all()
return [
{
"name": g.name,
"slug": g.slug,
"generation": g.generation,
"region": g.region,
"release_year": g.release_year,
"color": g.color,
}
for g in games
]
@router.get("/games/{game_id}/routes")
async def export_game_routes(
game_id: int,
session: AsyncSession = Depends(get_session),
):
"""Export routes and encounters for a game in seed JSON format."""
# Verify game exists
game = await session.get(Game, game_id)
if not game:
raise HTTPException(status_code=404, detail="Game not found")
# Load all routes for this game's version group with encounters and pokemon
result = await session.execute(
select(Route)
.where(Route.version_group_id == game.version_group_id)
.options(
selectinload(Route.route_encounters).selectinload(RouteEncounter.pokemon),
)
.order_by(Route.order)
)
routes = result.scalars().all()
# Build parent-child mapping
parent_routes = [r for r in routes if r.parent_route_id is None]
children_by_parent: dict[int, list[Route]] = {}
for r in routes:
if r.parent_route_id is not None:
children_by_parent.setdefault(r.parent_route_id, []).append(r)
def format_encounters(route: Route) -> list[dict]:
# Filter route_encounters to this specific game
game_encounters = [
enc for enc in route.route_encounters if enc.game_id == game_id
]
return [
{
"pokeapi_id": enc.pokemon.pokeapi_id,
"pokemon_name": enc.pokemon.name,
"method": enc.encounter_method,
"encounter_rate": enc.encounter_rate,
"min_level": enc.min_level,
"max_level": enc.max_level,
}
for enc in sorted(game_encounters, key=lambda e: -e.encounter_rate)
]
def format_route(route: Route) -> dict:
data: dict = {
"name": route.name,
"order": route.order,
"encounters": format_encounters(route),
}
children = children_by_parent.get(route.id, [])
if children:
data["children"] = [
format_child(c) for c in sorted(children, key=lambda r: r.order)
]
return data
def format_child(route: Route) -> dict:
data: dict = {
"name": route.name,
"order": route.order,
"encounters": format_encounters(route),
}
if route.pinwheel_zone is not None:
data["pinwheel_zone"] = route.pinwheel_zone
return data
return {
"filename": f"{game.slug}.json",
"data": [format_route(r) for r in parent_routes],
}
@router.get("/games/{game_id}/bosses")
async def export_game_bosses(
game_id: int,
session: AsyncSession = Depends(get_session),
):
"""Export boss battles for a game in seed JSON format."""
game = await session.get(Game, game_id)
if not game:
raise HTTPException(status_code=404, detail="Game not found")
result = await session.execute(
select(BossBattle)
.where(BossBattle.version_group_id == game.version_group_id)
.options(
selectinload(BossBattle.pokemon).selectinload(BossPokemon.pokemon),
selectinload(BossBattle.after_route),
)
.order_by(BossBattle.order)
)
bosses = result.scalars().all()
return {
"filename": f"{game.slug}-bosses.json",
"data": [
{
"name": b.name,
"boss_type": b.boss_type,
"specialty_type": b.specialty_type,
"badge_name": b.badge_name,
"badge_image_url": b.badge_image_url,
"level_cap": b.level_cap,
"order": b.order,
"after_route_name": b.after_route.name if b.after_route else None,
"location": b.location,
"section": b.section,
"sprite_url": b.sprite_url,
"pokemon": [
{
"pokeapi_id": bp.pokemon.pokeapi_id,
"pokemon_name": bp.pokemon.name,
"level": bp.level,
"order": bp.order,
**(
{"condition_label": bp.condition_label}
if bp.condition_label
else {}
),
}
for bp in sorted(b.pokemon, key=lambda p: p.order)
],
}
for b in bosses
],
}
@router.get("/pokemon")
async def export_pokemon(session: AsyncSession = Depends(get_session)):
"""Export all pokemon in seed JSON format."""
result = await session.execute(select(Pokemon).order_by(Pokemon.pokeapi_id))
pokemon_list = result.scalars().all()
return [
{
"pokeapi_id": p.pokeapi_id,
"national_dex": p.national_dex,
"name": p.name,
"types": p.types,
"sprite_url": p.sprite_url,
}
for p in pokemon_list
]
@router.get("/evolutions")
async def export_evolutions(session: AsyncSession = Depends(get_session)):
"""Export all evolutions in seed JSON format."""
result = await session.execute(
select(Evolution)
.options(
selectinload(Evolution.from_pokemon),
selectinload(Evolution.to_pokemon),
)
.order_by(Evolution.id)
)
evolutions = result.scalars().all()
return [
{
"from_pokeapi_id": e.from_pokemon.pokeapi_id,
"to_pokeapi_id": e.to_pokemon.pokeapi_id,
"trigger": e.trigger,
"min_level": e.min_level,
"item": e.item,
"held_item": e.held_item,
"condition": e.condition,
"region": e.region,
}
for e in evolutions
]