refactor(api): standardize songs endpoint
Some checks failed
Deploy Fediversion / deploy (push) Failing after 1s
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:
parent
c860075681
commit
c0e3e2a7e2
6 changed files with 24 additions and 14 deletions
|
|
@ -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)):
|
||||||
|
|
|
||||||
|
|
@ -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 []
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 []
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue