Files
rehearshalhub/api/src/rehearsalhub/routers/bands.py
Steffen Schuhmann dc6dd9dcfd fix: scan visibility, NC folder validation, watcher logging
- 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>
2026-03-29 14:11:07 +02:00

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)