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 sqlmodel import Session, select, func
from database import get_session from database import get_session
from models import Song, User, Tag, EntityTag, Show, Performance, Rating 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 auth import get_current_user
from services.stats import get_song_stats 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) session.refresh(db_song)
return db_song return db_song
@router.get("/", response_model=List[SongRead]) @router.get("/", response_model=PaginatedResponse[SongRead])
def read_songs( def read_songs(
offset: int = 0, offset: int = 0,
limit: int = Query(default=100, le=1000), limit: int = Query(default=100, le=1000),
@ -34,13 +34,23 @@ def read_songs(
if vertical_entity: if vertical_entity:
query = query.where(Song.vertical_id == vertical_entity.id) query = query.where(Song.vertical_id == vertical_entity.id)
else: else:
return [] return PaginatedResponse(data=[], meta=PaginationMeta(total=0, limit=limit, offset=offset))
if sort == "times_played": 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() 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) @router.get("/{slug}", response_model=SongReadWithStats)
def read_song(slug: str, session: Session = Depends(get_session)): def read_song(slug: str, session: Session = Depends(get_session)):

View file

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

View file

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

View file

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

View file

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

View file

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