from fastapi import APIRouter, Depends, UploadFile, File, Query, HTTPException from typing import Optional from auth.models import TokenPayload from auth.dependencies import require_permission from melodies.models import ( MelodyCreate, MelodyUpdate, MelodyInDB, MelodyListResponse, MelodyInfo, ) from melodies import service router = APIRouter(prefix="/api/melodies", tags=["melodies"]) @router.get("", response_model=MelodyListResponse) async def list_melodies( search: Optional[str] = Query(None), type: Optional[str] = Query(None), tone: Optional[str] = Query(None), total_notes: Optional[int] = Query(None), status: Optional[str] = Query(None), _user: TokenPayload = Depends(require_permission("melodies", "view")), ): melodies = await service.list_melodies( search=search, melody_type=type, tone=tone, total_notes=total_notes, status=status, ) return MelodyListResponse(melodies=melodies, total=len(melodies)) @router.get("/{melody_id}", response_model=MelodyInDB) async def get_melody( melody_id: str, _user: TokenPayload = Depends(require_permission("melodies", "view")), ): return await service.get_melody(melody_id) @router.post("", response_model=MelodyInDB, status_code=201) async def create_melody( body: MelodyCreate, publish: bool = Query(False), _user: TokenPayload = Depends(require_permission("melodies", "add")), ): return await service.create_melody(body, publish=publish, actor_name=_user.name) @router.put("/{melody_id}", response_model=MelodyInDB) async def update_melody( melody_id: str, body: MelodyUpdate, _user: TokenPayload = Depends(require_permission("melodies", "edit")), ): return await service.update_melody(melody_id, body, actor_name=_user.name) @router.delete("/{melody_id}", status_code=204) async def delete_melody( melody_id: str, _user: TokenPayload = Depends(require_permission("melodies", "delete")), ): await service.delete_melody(melody_id) @router.post("/{melody_id}/publish", response_model=MelodyInDB) async def publish_melody( melody_id: str, _user: TokenPayload = Depends(require_permission("melodies", "edit")), ): return await service.publish_melody(melody_id) @router.post("/{melody_id}/unpublish", response_model=MelodyInDB) async def unpublish_melody( melody_id: str, _user: TokenPayload = Depends(require_permission("melodies", "edit")), ): return await service.unpublish_melody(melody_id) @router.post("/{melody_id}/upload/{file_type}") async def upload_file( melody_id: str, file_type: str, file: UploadFile = File(...), _user: TokenPayload = Depends(require_permission("melodies", "edit")), ): """Upload a binary or preview file. file_type must be 'binary' or 'preview'.""" if file_type not in ("binary", "preview"): raise HTTPException(status_code=400, detail="file_type must be 'binary' or 'preview'") # Verify melody exists melody = await service.get_melody(melody_id) contents = await file.read() content_type = file.content_type or "application/octet-stream" if file_type == "binary": content_type = "application/octet-stream" url = service.upload_file(melody_id, contents, file.filename, content_type) # Update the melody document with the file URL if file_type == "preview": await service.update_melody(melody_id, MelodyUpdate( information=MelodyInfo( name=melody.information.name, previewURL=url, ) ), actor_name=_user.name) elif file_type == "binary": await service.update_melody(melody_id, MelodyUpdate(url=url), actor_name=_user.name) return {"url": url, "file_type": file_type} @router.delete("/{melody_id}/files/{file_type}", status_code=204) async def delete_file( melody_id: str, file_type: str, _user: TokenPayload = Depends(require_permission("melodies", "edit")), ): """Delete a binary or preview file. file_type must be 'binary' or 'preview'.""" if file_type not in ("binary", "preview"): raise HTTPException(status_code=400, detail="file_type must be 'binary' or 'preview'") await service.get_melody(melody_id) service.delete_file(melody_id, file_type) @router.get("/{melody_id}/files") async def get_files( melody_id: str, _user: TokenPayload = Depends(require_permission("melodies", "view")), ): """Get storage file URLs for a melody.""" await service.get_melody(melody_id) return service.get_storage_files(melody_id)