feat(api): session router — list, detail, patch endpoints
GET /bands/{id}/sessions — list with recording counts, newest first
GET /bands/{id}/sessions/{sid} — session detail with flat song list
PATCH /bands/{id}/sessions/{sid} — admin: update label/notes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ from rehearsalhub.routers import (
|
||||
bands_router,
|
||||
internal_router,
|
||||
members_router,
|
||||
sessions_router,
|
||||
songs_router,
|
||||
versions_router,
|
||||
ws_router,
|
||||
@@ -51,6 +52,7 @@ def create_app() -> FastAPI:
|
||||
prefix = "/api/v1"
|
||||
app.include_router(auth_router, prefix=prefix)
|
||||
app.include_router(bands_router, prefix=prefix)
|
||||
app.include_router(sessions_router, prefix=prefix)
|
||||
app.include_router(songs_router, prefix=prefix)
|
||||
app.include_router(versions_router, prefix=prefix)
|
||||
app.include_router(annotations_router, prefix=prefix)
|
||||
|
||||
@@ -3,6 +3,7 @@ from rehearsalhub.routers.auth import router as auth_router
|
||||
from rehearsalhub.routers.bands import router as bands_router
|
||||
from rehearsalhub.routers.internal import router as internal_router
|
||||
from rehearsalhub.routers.members import router as members_router
|
||||
from rehearsalhub.routers.sessions import router as sessions_router
|
||||
from rehearsalhub.routers.songs import router as songs_router
|
||||
from rehearsalhub.routers.versions import router as versions_router
|
||||
from rehearsalhub.routers.ws import router as ws_router
|
||||
@@ -12,6 +13,7 @@ __all__ = [
|
||||
"bands_router",
|
||||
"internal_router",
|
||||
"members_router",
|
||||
"sessions_router",
|
||||
"songs_router",
|
||||
"versions_router",
|
||||
"annotations_router",
|
||||
|
||||
92
api/src/rehearsalhub/routers/sessions.py
Normal file
92
api/src/rehearsalhub/routers/sessions.py
Normal file
@@ -0,0 +1,92 @@
|
||||
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.repositories.band import BandRepository
|
||||
from rehearsalhub.repositories.rehearsal_session import RehearsalSessionRepository
|
||||
from rehearsalhub.schemas.rehearsal_session import (
|
||||
RehearsalSessionDetail,
|
||||
RehearsalSessionRead,
|
||||
RehearsalSessionUpdate,
|
||||
)
|
||||
from rehearsalhub.schemas.song import SongRead
|
||||
from rehearsalhub.services.band import BandService
|
||||
|
||||
router = APIRouter(prefix="/bands", tags=["sessions"])
|
||||
|
||||
|
||||
@router.get("/{band_id}/sessions", response_model=list[RehearsalSessionRead])
|
||||
async def list_sessions(
|
||||
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")
|
||||
|
||||
repo = RehearsalSessionRepository(session)
|
||||
rows = await repo.list_for_band(band_id)
|
||||
return [
|
||||
RehearsalSessionRead.model_validate(s, update={"recording_count": count})
|
||||
for s, count in rows
|
||||
]
|
||||
|
||||
|
||||
@router.get("/{band_id}/sessions/{session_id}", response_model=RehearsalSessionDetail)
|
||||
async def get_session_detail(
|
||||
band_id: uuid.UUID,
|
||||
session_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")
|
||||
|
||||
repo = RehearsalSessionRepository(session)
|
||||
rehearsal = await repo.get_with_songs(session_id)
|
||||
if rehearsal is None or rehearsal.band_id != band_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Session not found")
|
||||
|
||||
songs = [
|
||||
SongRead.model_validate(s, update={"version_count": len(s.versions)})
|
||||
for s in rehearsal.songs
|
||||
]
|
||||
return RehearsalSessionDetail.model_validate(
|
||||
rehearsal,
|
||||
update={"recording_count": len(songs), "songs": songs},
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/{band_id}/sessions/{session_id}", response_model=RehearsalSessionRead)
|
||||
async def update_session(
|
||||
band_id: uuid.UUID,
|
||||
session_id: uuid.UUID,
|
||||
data: RehearsalSessionUpdate,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
current_member: Member = Depends(get_current_member),
|
||||
):
|
||||
band_repo = BandRepository(session)
|
||||
role = await band_repo.get_member_role(band_id, current_member.id)
|
||||
if role != "admin":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin only")
|
||||
|
||||
repo = RehearsalSessionRepository(session)
|
||||
rehearsal = await repo.get_by_id(session_id)
|
||||
if rehearsal is None or rehearsal.band_id != band_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Session not found")
|
||||
|
||||
updates = {k: v for k, v in data.model_dump().items() if v is not None}
|
||||
if updates:
|
||||
rehearsal = await repo.update(rehearsal, **updates)
|
||||
|
||||
return RehearsalSessionRead.model_validate(rehearsal, update={"recording_count": 0})
|
||||
Reference in New Issue
Block a user