From 60e2abfb65f6e8bb9b9a730f7ad54e19630f9513 Mon Sep 17 00:00:00 2001 From: fullsizemalt <106900403+fullsizemalt@users.noreply.github.com> Date: Sun, 28 Dec 2025 23:10:20 -0800 Subject: [PATCH] feat: Add cross-band song discovery - versions endpoint and UI --- backend/routers/songs.py | 69 ++++++++++++++++++++++++++++++ frontend/app/songs/[slug]/page.tsx | 44 +++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/backend/routers/songs.py b/backend/routers/songs.py index 9846b98..5535017 100644 --- a/backend/routers/songs.py +++ b/backend/routers/songs.py @@ -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 + } diff --git a/frontend/app/songs/[slug]/page.tsx b/frontend/app/songs/[slug]/page.tsx index 8d970b7..7523346 100644 --- a/frontend/app/songs/[slug]/page.tsx +++ b/frontend/app/songs/[slug]/page.tsx @@ -235,6 +235,50 @@ export default async function SongDetailPage({ params }: { params: Promise<{ slu )} + {/* Cross-Band Versions */} + {relatedVersions && relatedVersions.length > 0 && ( + + + + + + + + + Also Played By + +

+ This song is performed by {relatedVersions.length + 1} different bands +

+
+ +
+ {relatedVersions.map((version: any) => ( + +
+
+

+ {version.vertical_name} +

+

+ {version.title} +

+
+ + View → + +
+ + ))} +
+
+
+ )} + {/* Performance List Component (Handles Client Sorting) */}