"""Mission management endpoints — list, upload, delete mission files.""" from __future__ import annotations import logging from typing import Annotated from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, status from pydantic import BaseModel, Field from sqlalchemy.engine import Connection from adapters.exceptions import AdapterError from adapters.registry import GameAdapterRegistry from core.servers.service import ServerService from database import get_db from dependencies import get_current_user, require_admin logger = logging.getLogger(__name__) router = APIRouter(prefix="/servers/{server_id}/missions", tags=["missions"]) _MAX_UPLOAD_SIZE = 500 * 1024 * 1024 # 500 MB class MissionRotationEntry(BaseModel): name: str difficulty: str = "" params: dict[str, int | float | str | bool] = Field(default_factory=dict) class MissionRotationUpdate(BaseModel): missions: list[MissionRotationEntry] config_version: int def _ok(data): return {"success": True, "data": data, "error": None} def _get_mission_manager(server_id: int, game_type: str): """Get MissionManager for the server's game type.""" adapter = GameAdapterRegistry.get(game_type) if not adapter.has_capability("mission_manager"): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail={"code": "NOT_SUPPORTED", "message": f"Game type '{game_type}' does not support mission management"}, ) return adapter.get_mission_manager(server_id) @router.get("/rotation") def get_mission_rotation( server_id: int, db: Annotated[Connection, Depends(get_db)], _user: Annotated[dict, Depends(get_current_user)], ) -> dict: """Get the current mission rotation from the server config.""" config = ServerService(db).get_config_section(server_id, "server") missions = config.get("missions", []) return _ok({"missions": missions}) @router.put("/rotation") def update_mission_rotation( server_id: int, body: MissionRotationUpdate, db: Annotated[Connection, Depends(get_db)], _admin: Annotated[dict, Depends(require_admin)], ) -> dict: """Replace the mission rotation in the server config.""" updated = ServerService(db).update_config_section( server_id=server_id, section="server", data={"missions": [e.model_dump() for e in body.missions]}, expected_version=body.config_version, ) return _ok({"missions": updated.get("missions", [])}) @router.get("") def list_missions( server_id: int, db: Annotated[Connection, Depends(get_db)], _user: Annotated[dict, Depends(get_current_user)], ) -> dict: """List all available mission files on disk.""" server = ServerService(db).get_server(server_id) # raises 404 if not found mgr = _get_mission_manager(server_id, server["game_type"]) try: missions = mgr.list_missions() except AdapterError as exc: raise HTTPException(status_code=500, detail={"code": "ADAPTER_ERROR", "message": str(exc)}) return _ok({ "server_id": server_id, "missions": missions, "total": len(missions), }) @router.post("", status_code=status.HTTP_201_CREATED) async def upload_mission( server_id: int, db: Annotated[Connection, Depends(get_db)], _admin: Annotated[dict, Depends(require_admin)], file: UploadFile = File(...), ) -> dict: """ Upload a mission .pbo file. Max size: 500 MB. """ server = ServerService(db).get_server(server_id) # raises 404 if not found if not file.filename: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail={"code": "NO_FILENAME", "message": "No filename provided"}, ) content = await file.read() if len(content) > _MAX_UPLOAD_SIZE: raise HTTPException( status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, detail={"code": "FILE_TOO_LARGE", "message": f"File too large. Max size is {_MAX_UPLOAD_SIZE // (1024*1024)} MB"}, ) mgr = _get_mission_manager(server_id, server["game_type"]) try: mission = mgr.upload_mission(file.filename, content) except AdapterError as exc: raise HTTPException(status_code=400, detail={"code": "ADAPTER_ERROR", "message": str(exc)}) return _ok(mission) @router.delete("/{filename}") def delete_mission( server_id: int, filename: str, db: Annotated[Connection, Depends(get_db)], _admin: Annotated[dict, Depends(require_admin)], ) -> dict: """Delete a mission file by filename.""" server = ServerService(db).get_server(server_id) # raises 404 if not found mgr = _get_mission_manager(server_id, server["game_type"]) try: deleted = mgr.delete_mission(filename) except AdapterError as exc: raise HTTPException(status_code=400, detail={"code": "ADAPTER_ERROR", "message": str(exc)}) if not deleted: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={"code": "NOT_FOUND", "message": f"Mission '{filename}' not found"}, ) return _ok({"message": f"Mission '{filename}' deleted"})