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,
|
bands_router,
|
||||||
internal_router,
|
internal_router,
|
||||||
members_router,
|
members_router,
|
||||||
|
sessions_router,
|
||||||
songs_router,
|
songs_router,
|
||||||
versions_router,
|
versions_router,
|
||||||
ws_router,
|
ws_router,
|
||||||
@@ -51,6 +52,7 @@ def create_app() -> FastAPI:
|
|||||||
prefix = "/api/v1"
|
prefix = "/api/v1"
|
||||||
app.include_router(auth_router, prefix=prefix)
|
app.include_router(auth_router, prefix=prefix)
|
||||||
app.include_router(bands_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(songs_router, prefix=prefix)
|
||||||
app.include_router(versions_router, prefix=prefix)
|
app.include_router(versions_router, prefix=prefix)
|
||||||
app.include_router(annotations_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.bands import router as bands_router
|
||||||
from rehearsalhub.routers.internal import router as internal_router
|
from rehearsalhub.routers.internal import router as internal_router
|
||||||
from rehearsalhub.routers.members import router as members_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.songs import router as songs_router
|
||||||
from rehearsalhub.routers.versions import router as versions_router
|
from rehearsalhub.routers.versions import router as versions_router
|
||||||
from rehearsalhub.routers.ws import router as ws_router
|
from rehearsalhub.routers.ws import router as ws_router
|
||||||
@@ -12,6 +13,7 @@ __all__ = [
|
|||||||
"bands_router",
|
"bands_router",
|
||||||
"internal_router",
|
"internal_router",
|
||||||
"members_router",
|
"members_router",
|
||||||
|
"sessions_router",
|
||||||
"songs_router",
|
"songs_router",
|
||||||
"versions_router",
|
"versions_router",
|
||||||
"annotations_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