from typing import List from fastapi import APIRouter, Depends, HTTPException, Query from sqlmodel import Session, select, func from database import get_session from models import Venue from schemas import VenueCreate, VenueRead, VenueUpdate, PaginatedResponse, PaginationMeta from auth import get_current_user router = APIRouter(prefix="/venues", tags=["venues"]) @router.post("/", response_model=VenueRead) def create_venue(venue: VenueCreate, session: Session = Depends(get_session), current_user = Depends(get_current_user)): db_venue = Venue.model_validate(venue) session.add(db_venue) session.commit() session.refresh(db_venue) return db_venue @router.get("/", response_model=PaginatedResponse[VenueRead]) def read_venues(offset: int = 0, limit: int = Query(default=1000, le=1000), session: Session = Depends(get_session)): total = session.exec(select(func.count()).select_from(Venue)).one() venues = session.exec(select(Venue).offset(offset).limit(limit)).all() return PaginatedResponse( data=venues, meta=PaginationMeta(total=total, limit=limit, offset=offset) ) @router.get("/{slug}", response_model=VenueRead) def read_venue(slug: str, session: Session = Depends(get_session)): venue = session.exec(select(Venue).where(Venue.slug == slug)).first() if not venue: raise HTTPException(status_code=404, detail="Venue not found") return venue @router.patch("/{venue_id}", response_model=VenueRead) def update_venue(venue_id: int, venue: VenueUpdate, session: Session = Depends(get_session), current_user = Depends(get_current_user)): db_venue = session.get(Venue, venue_id) if not db_venue: raise HTTPException(status_code=404, detail="Venue not found") venue_data = venue.model_dump(exclude_unset=True) db_venue.sqlmodel_update(venue_data) session.add(db_venue) session.commit() session.refresh(db_venue) return db_venue @router.get("/{slug}/across-bands") def get_venue_across_bands(slug: str, session: Session = Depends(get_session)): """Get aggregated stats for a venue across all bands that have played there (via VenueCanon)""" from models import VenueCanon, Show, Vertical from sqlmodel import func # Find the venue by slug venue = session.exec(select(Venue).where(Venue.slug == slug)).first() if not venue: raise HTTPException(status_code=404, detail="Venue not found") # If this venue has a canon_id, get all linked venues linked_venues = [venue] if venue.canon_id: linked_venues = session.exec( select(Venue).where(Venue.canon_id == venue.canon_id) ).all() venue_ids = [v.id for v in linked_venues] # Get all shows at these venues shows = session.exec( select(Show) .where(Show.venue_id.in_(venue_ids)) .order_by(Show.date.desc()) ).all() # Group by vertical/band bands_stats = {} for show in shows: vertical = session.get(Vertical, show.vertical_id) if vertical: if vertical.id not in bands_stats: bands_stats[vertical.id] = { "vertical_id": vertical.id, "vertical_name": vertical.name, "vertical_slug": vertical.slug, "show_count": 0, "first_show": show.date, "last_show": show.date, "recent_shows": [] } bands_stats[vertical.id]["show_count"] += 1 if show.date < bands_stats[vertical.id]["first_show"]: bands_stats[vertical.id]["first_show"] = show.date if show.date > bands_stats[vertical.id]["last_show"]: bands_stats[vertical.id]["last_show"] = show.date if len(bands_stats[vertical.id]["recent_shows"]) < 3: bands_stats[vertical.id]["recent_shows"].append({ "date": show.date.strftime("%Y-%m-%d") if show.date else None, "slug": show.slug }) # Format response bands_list = sorted(bands_stats.values(), key=lambda x: x["show_count"], reverse=True) for band in bands_list: band["first_show"] = band["first_show"].strftime("%Y-%m-%d") if band["first_show"] else None band["last_show"] = band["last_show"].strftime("%Y-%m-%d") if band["last_show"] else None return { "venue": { "id": venue.id, "name": venue.name, "slug": venue.slug, "city": venue.city, "state": venue.state, "country": venue.country, "capacity": venue.capacity, }, "total_shows": len(shows), "bands_count": len(bands_list), "bands": bands_list } @router.get("/{slug}/timeline") def get_venue_timeline( slug: str, limit: int = Query(default=50, le=200), offset: int = 0, session: Session = Depends(get_session) ): """Get chronological timeline of all shows at this venue across all bands""" from models import VenueCanon, Show, Vertical venue = session.exec(select(Venue).where(Venue.slug == slug)).first() if not venue: raise HTTPException(status_code=404, detail="Venue not found") # Get all linked venues via canon venue_ids = [venue.id] if venue.canon_id: linked = session.exec( select(Venue).where(Venue.canon_id == venue.canon_id) ).all() venue_ids = [v.id for v in linked] # Get all shows at these venues, ordered by date shows = session.exec( select(Show) .where(Show.venue_id.in_(venue_ids)) .order_by(Show.date.desc()) .offset(offset) .limit(limit) ).all() timeline = [] for show in shows: vertical = session.get(Vertical, show.vertical_id) timeline.append({ "show_id": show.id, "show_slug": show.slug, "date": show.date.strftime("%Y-%m-%d") if show.date else None, "vertical_name": vertical.name if vertical else "Unknown", "vertical_slug": vertical.slug if vertical else "unknown", "vertical_color": vertical.primary_color if vertical else None, "notes": show.notes }) # Get total count total = len(session.exec( select(Show).where(Show.venue_id.in_(venue_ids)) ).all()) return { "venue": { "id": venue.id, "name": venue.name, "slug": venue.slug, "city": venue.city, "state": venue.state }, "total_shows": total, "offset": offset, "limit": limit, "timeline": timeline }