Add bulk import for evolutions, routes, and bosses

Add three new bulk import endpoints that accept the same JSON format as
their corresponding export endpoints, enabling round-trip compatibility:

- POST /evolutions/bulk-import (upsert by from/to pokemon pair)
- POST /games/{id}/routes/bulk-import (reuses seed loader for hierarchy)
- POST /games/{id}/bosses/bulk-import (reuses seed loader with team data)

Generalize BulkImportModal to support all entity types with configurable
title, example, and result labels. Wire up Bulk Import buttons on
AdminEvolutions, and AdminGameDetail routes/bosses tabs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-08 20:14:19 +01:00
parent 8e1c8b554f
commit 8f6d72a9c4
12 changed files with 373 additions and 15 deletions

View File

@@ -7,6 +7,8 @@ from app.core.database import get_session
from app.models.evolution import Evolution
from app.models.pokemon import Pokemon
from app.schemas.pokemon import (
BulkEvolutionItem,
BulkImportResult,
EvolutionAdminResponse,
EvolutionCreate,
EvolutionUpdate,
@@ -144,3 +146,65 @@ async def delete_evolution(
await session.delete(evolution)
await session.commit()
@router.post("/evolutions/bulk-import", response_model=BulkImportResult)
async def bulk_import_evolutions(
items: list[BulkEvolutionItem],
session: AsyncSession = Depends(get_session),
):
# Build pokeapi_id -> id mapping
result = await session.execute(select(Pokemon.pokeapi_id, Pokemon.id))
dex_to_id = {row.pokeapi_id: row.id for row in result}
created = 0
updated = 0
errors: list[str] = []
for item in items:
from_id = dex_to_id.get(item.from_pokeapi_id)
to_id = dex_to_id.get(item.to_pokeapi_id)
if from_id is None:
errors.append(f"Pokemon with pokeapi_id {item.from_pokeapi_id} not found")
continue
if to_id is None:
errors.append(f"Pokemon with pokeapi_id {item.to_pokeapi_id} not found")
continue
try:
# Check if evolution already exists
existing = await session.execute(
select(Evolution).where(
Evolution.from_pokemon_id == from_id,
Evolution.to_pokemon_id == to_id,
)
)
evolution = existing.scalar_one_or_none()
if evolution is not None:
evolution.trigger = item.trigger
evolution.min_level = item.min_level
evolution.item = item.item
evolution.held_item = item.held_item
evolution.condition = item.condition
evolution.region = item.region
updated += 1
else:
evolution = Evolution(
from_pokemon_id=from_id,
to_pokemon_id=to_id,
trigger=item.trigger,
min_level=item.min_level,
item=item.item,
held_item=item.held_item,
condition=item.condition,
region=item.region,
)
session.add(evolution)
created += 1
except Exception as e:
errors.append(f"Evolution {item.from_pokeapi_id} -> {item.to_pokeapi_id}: {e}")
await session.commit()
return BulkImportResult(created=created, updated=updated, errors=errors)