""" Character management router. Handles character creation, selection, and deletion. """ from fastapi import APIRouter, HTTPException, Depends, status, Request from fastapi.security import HTTPAuthorizationCredentials from ..items import items_manager from ..services.helpers import enrich_character_data, get_game_message from ..core.security import decode_token, create_access_token, security from ..services.models import CharacterCreate, CharacterSelect from .. import database as db router = APIRouter(prefix="/api/characters", tags=["characters"]) @router.get("") async def list_characters(credentials: HTTPAuthorizationCredentials = Depends(security)): """List all characters for the logged-in account""" token = credentials.credentials payload = decode_token(token) account_id = payload.get("account_id") if not account_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token" ) characters = await db.get_characters_by_account_id(account_id) return { "characters": [ await enrich_character_data(char, items_manager) for char in characters ] } @router.post("") async def create_character_endpoint( character: CharacterCreate, request: Request, credentials: HTTPAuthorizationCredentials = Depends(security) ): """Create a new character""" token = credentials.credentials locale = request.headers.get('Accept-Language', 'en') payload = decode_token(token) account_id = payload.get("account_id") if not account_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token" ) # Check if account can create more characters can_create, error_msg = await db.can_create_character(account_id) if not can_create: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=error_msg ) # Validate character name if len(character.name) < 3 or len(character.name) > 20: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Character name must be between 3 and 20 characters" ) # Check if name is unique existing = await db.get_character_by_name(character.name) if existing: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Character name already taken" ) # Validate stat allocation (must total 20 points) total_stats = character.strength + character.agility + character.endurance + character.intellect if total_stats != 20: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Must allocate exactly 20 stat points (you allocated {total_stats})" ) # Validate each stat is >= 0 if any(stat < 0 for stat in [character.strength, character.agility, character.endurance, character.intellect]): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Stats cannot be negative" ) # Create character new_character = await db.create_character( account_id=account_id, name=character.name, strength=character.strength, agility=character.agility, endurance=character.endurance, intellect=character.intellect, avatar_data=character.avatar_data ) if not new_character: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to create character" ) return { "message": get_game_message('character_created', locale), "character": { "id": new_character["id"], "name": new_character["name"], "level": new_character["level"], "strength": new_character["strength"], "agility": new_character["agility"], "endurance": new_character["endurance"], "intellect": new_character["intellect"], "hp": new_character["hp"], "max_hp": new_character["max_hp"], "stamina": new_character["stamina"], "max_stamina": new_character["max_stamina"], "location_id": new_character["location_id"], "avatar_data": new_character.get("avatar_data"), } } @router.post("/select") async def select_character( selection: CharacterSelect, credentials: HTTPAuthorizationCredentials = Depends(security) ): """Select a character to play""" token = credentials.credentials payload = decode_token(token) account_id = payload.get("account_id") if not account_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token" ) # Verify character belongs to account character = await db.get_character_by_id(selection.character_id) if not character: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Character not found" ) if character["account_id"] != account_id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Character does not belong to this account" ) # Update last played timestamp await db.update_character_last_played(selection.character_id) # Create new token with character_id access_token = create_access_token({ "account_id": account_id, "character_id": selection.character_id }) return { "access_token": access_token, "token_type": "bearer", "character": { "id": character["id"], "name": character["name"], "level": character["level"], "xp": character["xp"], "hp": character["hp"], "max_hp": character["max_hp"], "stamina": character["stamina"], "max_stamina": character["max_stamina"], "strength": character["strength"], "agility": character["agility"], "endurance": character["endurance"], "intellect": character["intellect"], "location_id": character["location_id"], "avatar_data": character.get("avatar_data"), } } @router.delete("/{character_id}") async def delete_character_endpoint( character_id: int, request: Request, credentials: HTTPAuthorizationCredentials = Depends(security) ): """Delete a character""" token = credentials.credentials locale = request.headers.get('Accept-Language', 'en') payload = decode_token(token) account_id = payload.get("account_id") if not account_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token" ) # Verify character belongs to account character = await db.get_character_by_id(character_id) if not character: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Character not found" ) if character["account_id"] != account_id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Character does not belong to this account" ) # Delete character await db.delete_character(character_id) return { "message": get_game_message('character_deleted', locale, name=character['name']) }