from fastapi import APIRouter, Depends, HTTPException from fastapi.responses import FileResponse from auth.models import TokenPayload from auth.dependencies import require_permission from builder.models import ( BuiltMelodyCreate, BuiltMelodyUpdate, BuiltMelodyInDB, BuiltMelodyListResponse, ) from builder import service router = APIRouter(prefix="/api/builder/melodies", tags=["builder"]) @router.get("", response_model=BuiltMelodyListResponse) async def list_built_melodies( _user: TokenPayload = Depends(require_permission("melodies", "view")), ): melodies = await service.list_built_melodies() return BuiltMelodyListResponse(melodies=melodies, total=len(melodies)) @router.get("/for-melody/{firestore_melody_id}") async def get_for_firestore_melody( firestore_melody_id: str, _user: TokenPayload = Depends(require_permission("melodies", "view")), ): """Get the built melody assigned to a given Firestore melody ID. Returns null if none found.""" result = await service.get_built_melody_for_firestore_id(firestore_melody_id) if result is None: return None return result.model_dump() @router.get("/{melody_id}", response_model=BuiltMelodyInDB) async def get_built_melody( melody_id: str, _user: TokenPayload = Depends(require_permission("melodies", "view")), ): return await service.get_built_melody(melody_id) @router.post("", response_model=BuiltMelodyInDB, status_code=201) async def create_built_melody( body: BuiltMelodyCreate, _user: TokenPayload = Depends(require_permission("melodies", "edit")), ): return await service.create_built_melody(body) @router.put("/{melody_id}", response_model=BuiltMelodyInDB) async def update_built_melody( melody_id: str, body: BuiltMelodyUpdate, _user: TokenPayload = Depends(require_permission("melodies", "edit")), ): return await service.update_built_melody(melody_id, body) @router.delete("/{melody_id}", status_code=204) async def delete_built_melody( melody_id: str, _user: TokenPayload = Depends(require_permission("melodies", "delete")), ): await service.delete_built_melody(melody_id) @router.post("/{melody_id}/build-binary", response_model=BuiltMelodyInDB) async def build_binary( melody_id: str, _user: TokenPayload = Depends(require_permission("melodies", "edit")), ): """Build the .bsm binary file from the stored steps.""" return await service.build_binary(melody_id) @router.post("/{melody_id}/build-builtin", response_model=BuiltMelodyInDB) async def build_builtin_code( melody_id: str, _user: TokenPayload = Depends(require_permission("melodies", "edit")), ): """Generate PROGMEM C code and store it in the database.""" return await service.build_builtin_code(melody_id) @router.get("/{melody_id}/download") async def download_binary( melody_id: str, _user: TokenPayload = Depends(require_permission("melodies", "view")), ): """Download the .bsm binary file.""" path = await service.get_binary_path(melody_id) if not path: raise HTTPException(status_code=404, detail="Binary not built yet for this melody") melody = await service.get_built_melody(melody_id) filename = f"{melody.pid or melody.name}.bsm" return FileResponse( path=str(path), media_type="application/octet-stream", filename=filename, ) @router.post("/{melody_id}/assign", response_model=BuiltMelodyInDB) async def assign_to_melody( melody_id: str, firestore_melody_id: str, _user: TokenPayload = Depends(require_permission("melodies", "edit")), ): """Mark this built melody as assigned to a Firestore melody.""" return await service.assign_to_melody(melody_id, firestore_melody_id) @router.post("/{melody_id}/unassign", response_model=BuiltMelodyInDB) async def unassign_from_melody( melody_id: str, firestore_melody_id: str, _user: TokenPayload = Depends(require_permission("melodies", "edit")), ): """Remove a Firestore melody assignment from this built melody.""" return await service.unassign_from_melody(melody_id, firestore_melody_id)