- nc-scan: detailed INFO logging of every path found, subfolder contents and skip reasons; 502 now includes the exact folder and error so user sees a real message instead of a blank result - band creation: if nc_base_path is explicitly given, verify the folder exists in Nextcloud before saving — returns 422 with a clear message to the user; auto-generated paths still do MKCOL - songs search: add ?unattributed=true to return songs with no session_id (files not in a YYMMDD folder) - BandPage: show "Unattributed Recordings" section below sessions so scanned files without a dated folder always appear - watcher event_loop: promote all per-activity log lines from DEBUG to INFO so they're visible in default Docker Compose log output; log normalized path and skip reason for every activity Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
84 lines
3.0 KiB
Python
84 lines
3.0 KiB
Python
import uuid
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from rehearsalhub.db.engine import get_session
|
|
from rehearsalhub.db.models import Member
|
|
from rehearsalhub.dependencies import get_current_member
|
|
from rehearsalhub.schemas.band import BandCreate, BandRead, BandReadWithMembers, BandUpdate
|
|
from rehearsalhub.repositories.band import BandRepository
|
|
from rehearsalhub.services.band import BandService
|
|
|
|
router = APIRouter(prefix="/bands", tags=["bands"])
|
|
|
|
|
|
@router.get("", response_model=list[BandRead])
|
|
async def list_bands(
|
|
session: AsyncSession = Depends(get_session),
|
|
current_member: Member = Depends(get_current_member),
|
|
):
|
|
repo = BandRepository(session)
|
|
bands = await repo.list_for_member(current_member.id)
|
|
return [BandRead.model_validate(b) for b in bands]
|
|
|
|
|
|
@router.post("", response_model=BandRead, status_code=status.HTTP_201_CREATED)
|
|
async def create_band(
|
|
data: BandCreate,
|
|
session: AsyncSession = Depends(get_session),
|
|
current_member: Member = Depends(get_current_member),
|
|
):
|
|
svc = BandService(session)
|
|
try:
|
|
band = await svc.create_band(data, current_member.id, creator=current_member)
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(e))
|
|
except LookupError as e:
|
|
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(e))
|
|
return BandRead.model_validate(band)
|
|
|
|
|
|
@router.get("/{band_id}", response_model=BandReadWithMembers)
|
|
async def get_band(
|
|
band_id: uuid.UUID,
|
|
session: AsyncSession = Depends(get_session),
|
|
current_member: Member = Depends(get_current_member),
|
|
):
|
|
svc = BandService(session)
|
|
try:
|
|
await svc.assert_membership(band_id, current_member.id)
|
|
except PermissionError:
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not a member")
|
|
|
|
band = await svc.get_band_with_members(band_id)
|
|
if band is None:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Band not found")
|
|
return BandReadWithMembers.model_validate(band)
|
|
|
|
|
|
@router.patch("/{band_id}", response_model=BandRead)
|
|
async def update_band(
|
|
band_id: uuid.UUID,
|
|
data: BandUpdate,
|
|
session: AsyncSession = Depends(get_session),
|
|
current_member: Member = Depends(get_current_member),
|
|
):
|
|
repo = BandRepository(session)
|
|
role = await repo.get_member_role(band_id, current_member.id)
|
|
if role != "admin":
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin only")
|
|
|
|
band = await repo.get_by_id(band_id)
|
|
if band is None:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Band not found")
|
|
|
|
updates: dict = {}
|
|
if data.nc_folder_path is not None:
|
|
path = data.nc_folder_path.strip()
|
|
updates["nc_folder_path"] = (path.rstrip("/") + "/") if path else None
|
|
|
|
if updates:
|
|
band = await repo.update(band, **updates)
|
|
return BandRead.model_validate(band)
|