Some checks failed
Deploy Fediversion / deploy (push) Failing after 1s
Sprint 2: Added 54 musicians with 78 band memberships
- Phish, Widespread Panic, Umphreys McGee core members
- Notable sit-in artists (Karl Denson, Branford Marsalis, Derek/Susan Trucks)
- Toy Factory Project supergroup (Oteil, Marcus King, Charlie Starr)
Sprint 4: Festival entity for multi-band events
- Festival and ShowFestival models
- /festivals API with list, detail, by-band endpoints
Sprint 5: User Playlists for curated collections
- UserPlaylist and PlaylistPerformance models
- Full CRUD /playlists API
Sprint 6: Venue Timeline endpoint
- /venues/{slug}/timeline for chronological cross-band history
Blockers (need production data):
- Venue linking script (no venues in local DB)
- Canon song linking (no songs in local DB)
168 lines
5.1 KiB
Python
168 lines
5.1 KiB
Python
"""
|
|
Festival API endpoints for multi-band festival discovery.
|
|
"""
|
|
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 Festival, ShowFestival, Show, Vertical, Venue
|
|
|
|
router = APIRouter(prefix="/festivals", tags=["festivals"])
|
|
|
|
|
|
class FestivalRead(BaseModel):
|
|
id: int
|
|
name: str
|
|
slug: str
|
|
year: Optional[int]
|
|
start_date: Optional[str]
|
|
end_date: Optional[str]
|
|
website_url: Optional[str]
|
|
description: Optional[str]
|
|
|
|
|
|
class FestivalShowRead(BaseModel):
|
|
show_id: int
|
|
show_slug: Optional[str]
|
|
show_date: str
|
|
vertical_name: str
|
|
vertical_slug: str
|
|
stage: Optional[str]
|
|
set_time: Optional[str]
|
|
|
|
|
|
class FestivalDetailRead(BaseModel):
|
|
festival: FestivalRead
|
|
shows: List[FestivalShowRead]
|
|
bands_count: int
|
|
|
|
|
|
@router.get("/", response_model=List[FestivalRead])
|
|
def list_festivals(
|
|
year: Optional[int] = None,
|
|
limit: int = Query(default=50, le=100),
|
|
offset: int = 0,
|
|
session: Session = Depends(get_session)
|
|
):
|
|
"""List all festivals, optionally filtered by year"""
|
|
query = select(Festival)
|
|
if year:
|
|
query = query.where(Festival.year == year)
|
|
query = query.order_by(Festival.year.desc(), Festival.name).offset(offset).limit(limit)
|
|
|
|
festivals = session.exec(query).all()
|
|
return [
|
|
FestivalRead(
|
|
id=f.id,
|
|
name=f.name,
|
|
slug=f.slug,
|
|
year=f.year,
|
|
start_date=f.start_date.strftime("%Y-%m-%d") if f.start_date else None,
|
|
end_date=f.end_date.strftime("%Y-%m-%d") if f.end_date else None,
|
|
website_url=f.website_url,
|
|
description=f.description
|
|
)
|
|
for f in festivals
|
|
]
|
|
|
|
|
|
@router.get("/{slug}", response_model=FestivalDetailRead)
|
|
def get_festival(slug: str, session: Session = Depends(get_session)):
|
|
"""Get festival details with all shows across bands"""
|
|
festival = session.exec(
|
|
select(Festival).where(Festival.slug == slug)
|
|
).first()
|
|
|
|
if not festival:
|
|
raise HTTPException(status_code=404, detail="Festival not found")
|
|
|
|
# Get all shows at this festival
|
|
show_festivals = session.exec(
|
|
select(ShowFestival).where(ShowFestival.festival_id == festival.id)
|
|
).all()
|
|
|
|
shows = []
|
|
bands_seen = set()
|
|
for sf in show_festivals:
|
|
show = session.get(Show, sf.show_id)
|
|
if show:
|
|
vertical = session.get(Vertical, show.vertical_id)
|
|
if vertical:
|
|
bands_seen.add(vertical.id)
|
|
shows.append(FestivalShowRead(
|
|
show_id=show.id,
|
|
show_slug=show.slug,
|
|
show_date=show.date.strftime("%Y-%m-%d") if show.date else "Unknown",
|
|
vertical_name=vertical.name if vertical else "Unknown",
|
|
vertical_slug=vertical.slug if vertical else "unknown",
|
|
stage=sf.stage,
|
|
set_time=sf.set_time
|
|
))
|
|
|
|
# Sort by date
|
|
shows.sort(key=lambda x: x.show_date)
|
|
|
|
return FestivalDetailRead(
|
|
festival=FestivalRead(
|
|
id=festival.id,
|
|
name=festival.name,
|
|
slug=festival.slug,
|
|
year=festival.year,
|
|
start_date=festival.start_date.strftime("%Y-%m-%d") if festival.start_date else None,
|
|
end_date=festival.end_date.strftime("%Y-%m-%d") if festival.end_date else None,
|
|
website_url=festival.website_url,
|
|
description=festival.description
|
|
),
|
|
shows=shows,
|
|
bands_count=len(bands_seen)
|
|
)
|
|
|
|
|
|
@router.get("/by-band/{vertical_slug}")
|
|
def get_festivals_by_band(vertical_slug: str, session: Session = Depends(get_session)):
|
|
"""Get all festivals a band has played"""
|
|
vertical = session.exec(
|
|
select(Vertical).where(Vertical.slug == vertical_slug)
|
|
).first()
|
|
|
|
if not vertical:
|
|
raise HTTPException(status_code=404, detail="Band not found")
|
|
|
|
# Get shows for this vertical that are linked to festivals
|
|
shows = session.exec(
|
|
select(Show).where(Show.vertical_id == vertical.id)
|
|
).all()
|
|
|
|
show_ids = [s.id for s in shows]
|
|
|
|
if not show_ids:
|
|
return []
|
|
|
|
# Get festival links
|
|
show_festivals = session.exec(
|
|
select(ShowFestival).where(ShowFestival.show_id.in_(show_ids))
|
|
).all()
|
|
|
|
festival_ids = list(set(sf.festival_id for sf in show_festivals))
|
|
|
|
if not festival_ids:
|
|
return []
|
|
|
|
festivals = session.exec(
|
|
select(Festival).where(Festival.id.in_(festival_ids))
|
|
).all()
|
|
|
|
return [
|
|
FestivalRead(
|
|
id=f.id,
|
|
name=f.name,
|
|
slug=f.slug,
|
|
year=f.year,
|
|
start_date=f.start_date.strftime("%Y-%m-%d") if f.start_date else None,
|
|
end_date=f.end_date.strftime("%Y-%m-%d") if f.end_date else None,
|
|
website_url=f.website_url,
|
|
description=f.description
|
|
)
|
|
for f in sorted(festivals, key=lambda x: x.year or 0, reverse=True)
|
|
]
|