Some checks failed
Deploy Fediversion / deploy (push) Failing after 1s
- Backend: /api/venues returns PaginatedResponse envelope - Frontend: Updated VenuesPage, AdminVenuesPage, VerticalVenuesPage to consume envelope
185 lines
6.6 KiB
Python
185 lines
6.6 KiB
Python
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
|
|
}
|