""" Musicians Router - API endpoints for managing musicians and band memberships. """ from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, Query from sqlmodel import Session, select from pydantic import BaseModel from database import get_session from models import Musician, BandMembership, PerformanceGuest, Artist, Performance from slugify import generate_slug as slugify router = APIRouter(prefix="/musicians", tags=["musicians"]) # --- Schemas --- class MusicianRead(BaseModel): id: int name: str slug: str bio: Optional[str] = None image_url: Optional[str] = None primary_instrument: Optional[str] = None class MusicianCreate(BaseModel): name: str bio: Optional[str] = None image_url: Optional[str] = None primary_instrument: Optional[str] = None class BandMembershipRead(BaseModel): id: int musician_id: int artist_id: int artist_name: Optional[str] = None role: Optional[str] = None start_date: Optional[str] = None end_date: Optional[str] = None class GuestAppearanceRead(BaseModel): id: int performance_id: int song_title: Optional[str] = None show_date: Optional[str] = None instrument: Optional[str] = None # --- Public Endpoints --- @router.get("", response_model=List[MusicianRead]) def list_musicians( search: Optional[str] = None, limit: int = 50, session: Session = Depends(get_session) ): """List all musicians""" query = select(Musician) if search: query = query.where(Musician.name.icontains(search)) query = query.order_by(Musician.name).limit(limit) return session.exec(query).all() @router.get("/{slug}") def get_musician(slug: str, session: Session = Depends(get_session)): """Get musician details with band memberships and guest appearances""" from models import Show, Song, Vertical musician = session.exec(select(Musician).where(Musician.slug == slug)).first() if not musician: raise HTTPException(status_code=404, detail="Musician not found") # Get band memberships memberships = session.exec( select(BandMembership).where(BandMembership.musician_id == musician.id) ).all() bands = [] primary_band_ids = set() for m in memberships: artist = session.get(Artist, m.artist_id) primary_band_ids.add(m.artist_id) bands.append({ "id": m.id, "artist_id": m.artist_id, "artist_name": artist.name if artist else None, "artist_slug": artist.slug if artist else None, "role": m.role, "start_date": str(m.start_date) if m.start_date else None, "end_date": str(m.end_date) if m.end_date else None, "is_current": m.end_date is None, }) # Get guest appearances with full context appearances = session.exec( select(PerformanceGuest).where(PerformanceGuest.musician_id == musician.id) ).all() guests = [] sit_in_verticals = {} # Track which bands they've sat in with for g in appearances: perf = session.get(Performance, g.performance_id) if not perf: continue show = session.get(Show, perf.show_id) if perf else None song = session.get(Song, perf.song_id) if perf else None vertical = session.get(Vertical, show.vertical_id) if show else None if vertical: if vertical.id not in sit_in_verticals: sit_in_verticals[vertical.id] = { "vertical_id": vertical.id, "vertical_name": vertical.name, "vertical_slug": vertical.slug, "count": 0 } sit_in_verticals[vertical.id]["count"] += 1 guests.append({ "id": g.id, "performance_id": g.performance_id, "performance_slug": perf.slug if perf else None, "song_title": song.title if song else None, "show_date": str(show.date.date()) if show and show.date else None, "vertical_name": vertical.name if vertical else None, "vertical_slug": vertical.slug if vertical else None, "instrument": g.instrument, }) return { "musician": { "id": musician.id, "name": musician.name, "slug": musician.slug, "bio": musician.bio, "image_url": musician.image_url, "primary_instrument": musician.primary_instrument, }, "bands": bands, "guest_appearances": guests, "sit_in_summary": list(sit_in_verticals.values()), "stats": { "total_bands": len(bands), "current_bands": len([b for b in bands if b.get("is_current")]), "total_sit_ins": len(guests), "bands_sat_in_with": len(sit_in_verticals), } } # --- Admin Endpoints (for now, no auth check - can be added later) --- @router.post("", response_model=MusicianRead) def create_musician( musician_data: MusicianCreate, session: Session = Depends(get_session) ): """Create a new musician""" slug = slugify(musician_data.name) # Check for existing existing = session.exec(select(Musician).where(Musician.slug == slug)).first() if existing: raise HTTPException(status_code=400, detail="Musician with this name already exists") musician = Musician( name=musician_data.name, slug=slug, bio=musician_data.bio, image_url=musician_data.image_url, primary_instrument=musician_data.primary_instrument, ) session.add(musician) session.commit() session.refresh(musician) return musician