feat: Add cross-band song discovery - versions endpoint and UI
This commit is contained in:
parent
cf7748a980
commit
60e2abfb65
2 changed files with 113 additions and 0 deletions
|
|
@ -110,3 +110,72 @@ def update_song(song_id: int, song: SongUpdate, session: Session = Depends(get_s
|
|||
session.commit()
|
||||
session.refresh(db_song)
|
||||
return db_song
|
||||
|
||||
|
||||
@router.get("/{slug}/versions")
|
||||
def get_song_versions(slug: str, session: Session = Depends(get_session)):
|
||||
"""Get all versions of a song across different bands (via SongCanon)"""
|
||||
from models import SongCanon, Vertical
|
||||
|
||||
# Find the song by slug
|
||||
song = session.exec(select(Song).where(Song.slug == slug)).first()
|
||||
if not song:
|
||||
raise HTTPException(status_code=404, detail="Song not found")
|
||||
|
||||
# If no canon link, return empty
|
||||
if not song.canon_id:
|
||||
return {
|
||||
"song": {
|
||||
"id": song.id,
|
||||
"title": song.title,
|
||||
"slug": song.slug,
|
||||
"vertical_id": song.vertical_id,
|
||||
},
|
||||
"canon": None,
|
||||
"other_versions": []
|
||||
}
|
||||
|
||||
# Get the canon entry
|
||||
canon = session.get(SongCanon, song.canon_id)
|
||||
|
||||
# Get all other versions (same canon, different song)
|
||||
other_songs = session.exec(
|
||||
select(Song)
|
||||
.where(Song.canon_id == song.canon_id)
|
||||
.where(Song.id != song.id)
|
||||
).all()
|
||||
|
||||
other_versions = []
|
||||
for s in other_songs:
|
||||
vertical = session.get(Vertical, s.vertical_id)
|
||||
# Get play count for this version
|
||||
play_count = session.exec(
|
||||
select(func.count(Performance.id))
|
||||
.where(Performance.song_id == s.id)
|
||||
).one()
|
||||
|
||||
other_versions.append({
|
||||
"id": s.id,
|
||||
"title": s.title,
|
||||
"slug": s.slug,
|
||||
"vertical_id": s.vertical_id,
|
||||
"vertical_name": vertical.name if vertical else "Unknown",
|
||||
"vertical_slug": vertical.slug if vertical else "unknown",
|
||||
"play_count": play_count,
|
||||
})
|
||||
|
||||
return {
|
||||
"song": {
|
||||
"id": song.id,
|
||||
"title": song.title,
|
||||
"slug": song.slug,
|
||||
"vertical_id": song.vertical_id,
|
||||
},
|
||||
"canon": {
|
||||
"id": canon.id,
|
||||
"title": canon.title,
|
||||
"slug": canon.slug,
|
||||
"original_artist": canon.original_artist,
|
||||
} if canon else None,
|
||||
"other_versions": other_versions
|
||||
}
|
||||
|
|
|
|||
|
|
@ -235,6 +235,50 @@ export default async function SongDetailPage({ params }: { params: Promise<{ slu
|
|||
</Card>
|
||||
)}
|
||||
|
||||
{/* Cross-Band Versions */}
|
||||
{relatedVersions && relatedVersions.length > 0 && (
|
||||
<Card className="border-2 border-indigo-500/20 bg-gradient-to-br from-indigo-50/50 to-purple-50/50 dark:from-indigo-900/10 dark:to-purple-900/10">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-indigo-700 dark:text-indigo-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" />
|
||||
<path d="M2 12h20" />
|
||||
</svg>
|
||||
Also Played By
|
||||
</CardTitle>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
This song is performed by {relatedVersions.length + 1} different bands
|
||||
</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
|
||||
{relatedVersions.map((version: any) => (
|
||||
<Link
|
||||
key={version.id}
|
||||
href={`/${version.vertical_slug}/songs/${version.slug}`}
|
||||
className="block group"
|
||||
>
|
||||
<div className="flex items-center justify-between p-3 rounded-lg bg-background/50 hover:bg-background/80 transition-colors border border-transparent hover:border-indigo-200 dark:hover:border-indigo-800">
|
||||
<div>
|
||||
<p className="font-medium group-hover:text-primary transition-colors">
|
||||
{version.vertical_name}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{version.title}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="secondary">
|
||||
View →
|
||||
</Badge>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<SongEvolutionChart performances={song.performances || []} />
|
||||
|
||||
{/* Performance List Component (Handles Client Sorting) */}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue