From 8df513b84faf24845045e31e32823a2d46abd017 Mon Sep 17 00:00:00 2001
From: fullsizemalt <106900403+fullsizemalt@users.noreply.github.com>
Date: Sun, 21 Dec 2025 12:58:32 -0800
Subject: [PATCH] feat: Add YouTube link support for shows, songs, and
performances
---
backend/migrations/add_youtube_links.py | 24 +++++++++++++
backend/models.py | 3 ++
frontend/app/shows/[id]/page.tsx | 17 ++++++++-
frontend/components/ui/youtube-embed.tsx | 45 ++++++++++++++++++++++++
4 files changed, 88 insertions(+), 1 deletion(-)
create mode 100644 backend/migrations/add_youtube_links.py
create mode 100644 frontend/components/ui/youtube-embed.tsx
diff --git a/backend/migrations/add_youtube_links.py b/backend/migrations/add_youtube_links.py
new file mode 100644
index 0000000..11c4dc4
--- /dev/null
+++ b/backend/migrations/add_youtube_links.py
@@ -0,0 +1,24 @@
+"""
+Migration to add youtube_link column to show, song, and performance tables.
+"""
+from sqlmodel import Session, create_engine, text
+from database import DATABASE_URL
+
+def add_youtube_link_columns():
+ engine = create_engine(DATABASE_URL)
+
+ tables = ['show', 'song', 'performance']
+
+ with Session(engine) as session:
+ for table in tables:
+ try:
+ session.exec(text(f"""
+ ALTER TABLE "{table}" ADD COLUMN IF NOT EXISTS youtube_link VARCHAR
+ """))
+ session.commit()
+ print(f"✅ Added youtube_link to {table}")
+ except Exception as e:
+ print(f"⚠️ {table}: {e}")
+
+if __name__ == "__main__":
+ add_youtube_link_columns()
diff --git a/backend/models.py b/backend/models.py
index 58391b5..1771791 100644
--- a/backend/models.py
+++ b/backend/models.py
@@ -13,6 +13,7 @@ class Performance(SQLModel, table=True):
segue: bool = Field(default=False, description="Transition to next song >")
notes: Optional[str] = Field(default=None)
track_url: Optional[str] = Field(default=None, description="Deep link to track audio")
+ youtube_link: Optional[str] = Field(default=None, description="YouTube video URL")
nicknames: List["PerformanceNickname"] = Relationship(back_populates="performance")
show: "Show" = Relationship(back_populates="performances")
@@ -97,6 +98,7 @@ class Show(SQLModel, table=True):
# External Links
bandcamp_link: Optional[str] = Field(default=None)
nugs_link: Optional[str] = Field(default=None)
+ youtube_link: Optional[str] = Field(default=None)
vertical: Vertical = Relationship(back_populates="shows")
venue: Optional[Venue] = Relationship(back_populates="shows")
@@ -110,6 +112,7 @@ class Song(SQLModel, table=True):
original_artist: Optional[str] = Field(default=None)
vertical_id: int = Field(foreign_key="vertical.id")
notes: Optional[str] = Field(default=None)
+ youtube_link: Optional[str] = Field(default=None)
vertical: Vertical = Relationship(back_populates="songs")
diff --git a/frontend/app/shows/[id]/page.tsx b/frontend/app/shows/[id]/page.tsx
index e26b05e..5185bf1 100644
--- a/frontend/app/shows/[id]/page.tsx
+++ b/frontend/app/shows/[id]/page.tsx
@@ -1,7 +1,7 @@
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
-import { ArrowLeft, Calendar, MapPin, Music2, Disc, PlayCircle, ExternalLink } from "lucide-react"
+import { ArrowLeft, Calendar, MapPin, Music2, Disc, PlayCircle, ExternalLink, Youtube } from "lucide-react"
import Link from "next/link"
import { CommentSection } from "@/components/social/comment-section"
import { EntityRating } from "@/components/social/entity-rating"
@@ -13,6 +13,7 @@ import { notFound } from "next/navigation"
import { SuggestNicknameDialog } from "@/components/shows/suggest-nickname-dialog"
import { EntityReviews } from "@/components/reviews/entity-reviews"
import { getApiUrl } from "@/lib/api-config"
+import { YouTubeEmbed } from "@/components/ui/youtube-embed"
async function getShow(id: string) {
try {
@@ -136,6 +137,20 @@ export default async function ShowDetailPage({ params }: { params: Promise<{ id:
)}
+ {show.youtube_link && (
+