refactor(api): standardize songs endpoint
Some checks failed
Deploy Fediversion / deploy (push) Failing after 1s

- Backend: /api/songs returns PaginatedResponse envelope
- Frontend: Updated SongsPage, AdminSongsPage, AdminSequencesPage, BandPage to consume envelope
This commit is contained in:
fullsizemalt 2025-12-30 20:33:18 -08:00
parent c860075681
commit c0e3e2a7e2
6 changed files with 24 additions and 14 deletions

View file

@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query
from sqlmodel import Session, select, func
from database import get_session
from models import Song, User, Tag, EntityTag, Show, Performance, Rating
from schemas import SongCreate, SongRead, SongReadWithStats, SongUpdate, TagRead, PerformanceReadWithShow
from schemas import SongCreate, SongRead, SongReadWithStats, SongUpdate, TagRead, PerformanceReadWithShow, PaginatedResponse, PaginationMeta
from auth import get_current_user
from services.stats import get_song_stats
@ -18,7 +18,7 @@ def create_song(song: SongCreate, session: Session = Depends(get_session), curre
session.refresh(db_song)
return db_song
@router.get("/", response_model=List[SongRead])
@router.get("/", response_model=PaginatedResponse[SongRead])
def read_songs(
offset: int = 0,
limit: int = Query(default=100, le=1000),
@ -34,13 +34,23 @@ def read_songs(
if vertical_entity:
query = query.where(Song.vertical_id == vertical_entity.id)
else:
return []
return PaginatedResponse(data=[], meta=PaginationMeta(total=0, limit=limit, offset=offset))
if sort == "times_played":
query = query.outerjoin(Performance).group_by(Song.id).order_by(func.count(Performance.id).desc())
query = query.outerjoin(Performance).group_by(Song.id)
# Calculate total count before pagination
total = session.exec(select(func.count()).select_from(query.subquery())).one()
if sort == "times_played":
query = query.order_by(func.count(Performance.id).desc())
songs = session.exec(query.offset(offset).limit(limit)).all()
return songs
return PaginatedResponse(
data=songs,
meta=PaginationMeta(total=total, limit=limit, offset=offset)
)
@router.get("/{slug}", response_model=SongReadWithStats)
def read_song(slug: str, session: Session = Depends(get_session)):

View file

@ -35,7 +35,8 @@ async function getTopSongs(verticalSlug: string) {
next: { revalidate: 60 }
})
if (!res.ok) return []
return res.json()
const data = await res.json()
return data.data || []
} catch {
return []
}

View file

@ -18,7 +18,8 @@ async function getSongs(verticalSlug: string) {
next: { revalidate: 60 }
})
if (!res.ok) return []
return res.json()
const data = await res.json()
return data.data || []
} catch {
return []
}

View file

@ -198,7 +198,7 @@ export default function AdminSequencesPage() {
})
if (res.ok) {
const data = await res.json()
setAllSongs(data.songs || data)
setAllSongs(data.data || [])
}
} catch (e) {
console.error("Failed to fetch songs", e)

View file

@ -61,7 +61,7 @@ export default function AdminSongsPage() {
})
if (res.ok) {
const data = await res.json()
setSongs(data.songs || data)
setSongs(data.data || [])
}
} catch (e) {
console.error("Failed to fetch songs", e)

View file

@ -21,12 +21,10 @@ export default function SongsPage() {
fetch(`${getApiUrl()}/songs/?limit=1000`)
.then(res => res.json())
.then(data => {
if (!Array.isArray(data)) {
console.error("API Error: Expected array but got:", data)
return
}
// Handle envelope
const songData = data.data || []
// Sort alphabetically
const sorted = data.sort((a: Song, b: Song) => a.title.localeCompare(b.title))
const sorted = songData.sort((a: Song, b: Song) => a.title.localeCompare(b.title))
setSongs(sorted)
})
.catch(console.error)