165 lines
5.9 KiB
Python
165 lines
5.9 KiB
Python
from fastapi import APIRouter, Depends, HTTPException
|
|
from fastapi.responses import FileResponse, PlainTextResponse
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from auth.models import TokenPayload
|
|
from auth.dependencies import require_permission
|
|
from builder.models import (
|
|
BuiltMelodyCreate,
|
|
BuiltMelodyUpdate,
|
|
BuiltMelodyInDB,
|
|
BuiltMelodyListResponse,
|
|
)
|
|
from builder import service
|
|
from database.postgres import get_pg_session
|
|
from shared.audit import log_action
|
|
|
|
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("/generate-builtin-list")
|
|
async def generate_builtin_list(
|
|
_user: TokenPayload = Depends(require_permission("melodies", "view")),
|
|
):
|
|
"""Generate a C++ header with PROGMEM arrays for all is_builtin archetypes."""
|
|
code = await service.generate_builtin_list()
|
|
return PlainTextResponse(content=code, media_type="text/plain")
|
|
|
|
|
|
@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")),
|
|
db: AsyncSession = Depends(get_pg_session),
|
|
):
|
|
melody = await service.create_built_melody(body)
|
|
await log_action(db, _user.sub, _user.name or _user.email, "CREATE", "archetype",
|
|
str(melody.id), melody.name or str(melody.id))
|
|
return melody
|
|
|
|
|
|
@router.put("/{melody_id}", response_model=BuiltMelodyInDB)
|
|
async def update_built_melody(
|
|
melody_id: str,
|
|
body: BuiltMelodyUpdate,
|
|
_user: TokenPayload = Depends(require_permission("melodies", "edit")),
|
|
db: AsyncSession = Depends(get_pg_session),
|
|
):
|
|
old = await service.get_built_melody(melody_id)
|
|
melody = await service.update_built_melody(melody_id, body)
|
|
_SKIP = {"updated_at", "id", "steps", "builtin_code"}
|
|
changes = {
|
|
k: {"old": getattr(old, k, None), "new": getattr(melody, k, None)}
|
|
for k in body.model_fields_set
|
|
if k not in _SKIP and getattr(old, k, None) != getattr(melody, k, None)
|
|
}
|
|
await log_action(db, _user.sub, _user.name or _user.email, "UPDATE", "archetype",
|
|
melody_id, melody.name or melody_id, changes=changes or None)
|
|
return melody
|
|
|
|
|
|
@router.delete("/{melody_id}", status_code=204)
|
|
async def delete_built_melody(
|
|
melody_id: str,
|
|
_user: TokenPayload = Depends(require_permission("melodies", "delete")),
|
|
db: AsyncSession = Depends(get_pg_session),
|
|
):
|
|
melody = await service.get_built_melody(melody_id)
|
|
await service.delete_built_melody(melody_id)
|
|
await log_action(db, _user.sub, _user.name or _user.email, "DELETE", "archetype",
|
|
melody_id, melody.name if melody else melody_id)
|
|
|
|
|
|
@router.post("/{melody_id}/toggle-builtin", response_model=BuiltMelodyInDB)
|
|
async def toggle_builtin(
|
|
melody_id: str,
|
|
_user: TokenPayload = Depends(require_permission("melodies", "edit")),
|
|
):
|
|
"""Toggle the is_builtin flag for an archetype."""
|
|
return await service.toggle_builtin(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)
|