Add hierarchical route grouping for multi-area locations
Locations like Mt. Moon (with 1F, B1F, B2F floors) are now grouped so only one encounter can be logged per location group, enforcing Nuzlocke first-encounter rules correctly. - Add parent_route_id column with self-referential FK to routes table - Add parent/children relationships on Route model - Update games API to return hierarchical route structure - Add validation in encounters API to prevent parent route encounters and duplicate encounters within sibling routes (409 conflict) - Update frontend with collapsible RouteGroup component - Auto-derive route groups from PokeAPI location/location-area structure - Regenerate seed data with 70 parent routes and 315 child routes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ from app.schemas.game import (
|
||||
RouteReorderRequest,
|
||||
RouteResponse,
|
||||
RouteUpdate,
|
||||
RouteWithChildrenResponse,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
@@ -42,10 +43,21 @@ async def get_game(game_id: int, session: AsyncSession = Depends(get_session)):
|
||||
return game
|
||||
|
||||
|
||||
@router.get("/{game_id}/routes", response_model=list[RouteResponse])
|
||||
@router.get(
|
||||
"/{game_id}/routes",
|
||||
response_model=list[RouteWithChildrenResponse] | list[RouteResponse],
|
||||
)
|
||||
async def list_game_routes(
|
||||
game_id: int, session: AsyncSession = Depends(get_session)
|
||||
game_id: int,
|
||||
flat: bool = False,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
"""
|
||||
List routes for a game.
|
||||
|
||||
By default, returns a hierarchical structure with top-level routes containing
|
||||
nested children. Use `flat=True` to get a flat list of all routes.
|
||||
"""
|
||||
# Verify game exists
|
||||
game = await session.get(Game, game_id)
|
||||
if game is None:
|
||||
@@ -56,7 +68,36 @@ async def list_game_routes(
|
||||
.where(Route.game_id == game_id)
|
||||
.order_by(Route.order)
|
||||
)
|
||||
return result.scalars().all()
|
||||
all_routes = result.scalars().all()
|
||||
|
||||
if flat:
|
||||
return all_routes
|
||||
|
||||
# Build hierarchical structure
|
||||
# Group children by parent_route_id
|
||||
children_by_parent: dict[int, list[Route]] = {}
|
||||
top_level_routes: list[Route] = []
|
||||
|
||||
for route in all_routes:
|
||||
if route.parent_route_id is None:
|
||||
top_level_routes.append(route)
|
||||
else:
|
||||
children_by_parent.setdefault(route.parent_route_id, []).append(route)
|
||||
|
||||
# Build response with nested children
|
||||
response = []
|
||||
for route in top_level_routes:
|
||||
route_dict = {
|
||||
"id": route.id,
|
||||
"name": route.name,
|
||||
"game_id": route.game_id,
|
||||
"order": route.order,
|
||||
"parent_route_id": route.parent_route_id,
|
||||
"children": children_by_parent.get(route.id, []),
|
||||
}
|
||||
response.append(route_dict)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
# --- Admin endpoints ---
|
||||
|
||||
Reference in New Issue
Block a user