feat(videos): add video icons to setlists and song versions
Some checks failed
Deploy Fediversion / deploy (push) Failing after 1s
Some checks failed
Deploy Fediversion / deploy (push) Failing after 1s
- Backend: Added video_links relationship to Performance model - Backend: Updated shows and songs routers to eager-load videos and populate youtube_link - Frontend: Added YouTube icon to performance list items if video exists
This commit is contained in:
parent
8e7be96991
commit
c090a395dc
4 changed files with 51 additions and 14 deletions
|
|
@ -22,6 +22,7 @@ class Performance(SQLModel, table=True):
|
|||
nicknames: List["PerformanceNickname"] = Relationship(back_populates="performance")
|
||||
show: "Show" = Relationship(back_populates="performances")
|
||||
song: "Song" = Relationship()
|
||||
video_links: List["VideoPerformance"] = Relationship(back_populates="performance")
|
||||
|
||||
class ShowArtist(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
|
|
@ -696,7 +697,7 @@ class VideoPerformance(SQLModel, table=True):
|
|||
notes: Optional[str] = Field(default=None)
|
||||
|
||||
video: Video = Relationship(back_populates="performances")
|
||||
performance: "Performance" = Relationship()
|
||||
performance: "Performance" = Relationship(back_populates="video_links")
|
||||
|
||||
|
||||
class VideoSong(SQLModel, table=True):
|
||||
|
|
|
|||
|
|
@ -164,7 +164,17 @@ def read_upcoming_shows(
|
|||
|
||||
@router.get("/{slug}", response_model=ShowRead)
|
||||
def read_show(slug: str, session: Session = Depends(get_session)):
|
||||
show = session.exec(select(Show).where(Show.slug == slug)).first()
|
||||
from sqlalchemy.orm import selectinload, joinedload
|
||||
from models import Performance, VideoPerformance, Video, VideoPlatform
|
||||
|
||||
# Eager load relationships clearly
|
||||
show = session.exec(
|
||||
select(Show)
|
||||
.options(
|
||||
selectinload(Show.performances).selectinload(Performance.video_links).joinedload(VideoPerformance.video)
|
||||
)
|
||||
.where(Show.slug == slug)
|
||||
).first()
|
||||
|
||||
if not show:
|
||||
raise HTTPException(status_code=404, detail="Show not found")
|
||||
|
|
@ -176,15 +186,6 @@ def read_show(slug: str, session: Session = Depends(get_session)):
|
|||
.where(EntityTag.entity_id == show.id)
|
||||
).all()
|
||||
|
||||
# Manually populate performances to ensure nicknames are filtered if needed
|
||||
# (Though for now we just return all, or filter approved in schema if we had a custom getter)
|
||||
# The relationship `show.performances` is already loaded if we access it, but we might want to sort.
|
||||
|
||||
# Re-fetch show with relationships if needed, or just rely on lazy loading + validation
|
||||
# But for nicknames, we only want "approved" ones usually.
|
||||
# Let's let the frontend filter or do it here.
|
||||
# Doing it here is safer.
|
||||
|
||||
show_data = ShowRead.model_validate(show)
|
||||
show_data.tags = tags
|
||||
|
||||
|
|
@ -195,10 +196,17 @@ def read_show(slug: str, session: Session = Depends(get_session)):
|
|||
# Sort performances by position
|
||||
sorted_perfs = sorted(show.performances, key=lambda p: p.position)
|
||||
|
||||
# Filter nicknames for each performance
|
||||
# Process performances: Filter nicknames and populate video links
|
||||
for perf in sorted_perfs:
|
||||
perf.nicknames = [n for n in perf.nicknames if n.status == "approved"]
|
||||
|
||||
# Backfill youtube_link from Video entity if not present
|
||||
if not perf.youtube_link and perf.video_links:
|
||||
for link in perf.video_links:
|
||||
if link.video and link.video.platform == VideoPlatform.YOUTUBE:
|
||||
perf.youtube_link = link.video.url
|
||||
break
|
||||
|
||||
show_data.performances = sorted_perfs
|
||||
|
||||
return show_data
|
||||
|
|
|
|||
|
|
@ -59,10 +59,17 @@ def read_song(slug: str, session: Session = Depends(get_session)):
|
|||
.where(EntityTag.entity_id == song_id)
|
||||
).all()
|
||||
|
||||
# Fetch performances
|
||||
# Fetch performances with video links
|
||||
from sqlalchemy.orm import selectinload, joinedload
|
||||
from models import VideoPerformance
|
||||
from models import Video, VideoPlatform
|
||||
|
||||
perfs = session.exec(
|
||||
select(Performance)
|
||||
.join(Show)
|
||||
.options(
|
||||
selectinload(Performance.video_links).joinedload(VideoPerformance.video)
|
||||
)
|
||||
.where(Performance.song_id == song_id)
|
||||
.order_by(Show.date.desc())
|
||||
).all()
|
||||
|
|
@ -97,9 +104,18 @@ def read_song(slug: str, session: Session = Depends(get_session)):
|
|||
venue_state = p.show.venue.state
|
||||
|
||||
r_stats = rating_stats.get(p.id, {"avg": 0.0, "count": 0})
|
||||
|
||||
# Backfill youtube_link
|
||||
youtube_link = p.youtube_link
|
||||
if not youtube_link and p.video_links:
|
||||
for link in p.video_links:
|
||||
if link.video and link.video.platform == VideoPlatform.YOUTUBE:
|
||||
youtube_link = link.video.url
|
||||
break
|
||||
|
||||
perf_dtos.append(PerformanceReadWithShow(
|
||||
**p.model_dump(),
|
||||
youtube_link=youtube_link,
|
||||
show_date=show_date,
|
||||
show_slug=show_slug,
|
||||
venue_name=venue_name,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import Link from "next/link"
|
|||
import { Badge } from "@/components/ui/badge"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { RatePerformanceDialog } from "@/components/songs/rate-performance-dialog"
|
||||
import { Star, Music } from "lucide-react"
|
||||
import { Star, Music, Youtube } from "lucide-react"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
|
||||
export interface Performance {
|
||||
|
|
@ -23,6 +23,7 @@ export interface Performance {
|
|||
venue_state: string | null
|
||||
avg_rating: number
|
||||
total_reviews: number
|
||||
youtube_link?: string | null
|
||||
}
|
||||
|
||||
interface PerformanceListProps {
|
||||
|
|
@ -98,6 +99,17 @@ export function PerformanceList({ performances }: PerformanceListProps) {
|
|||
<span className="text-xs text-muted-foreground uppercase tracking-wider font-mono">
|
||||
{perf.set_name || "Set ?"}
|
||||
</span>
|
||||
{perf.youtube_link && (
|
||||
<a
|
||||
href={perf.youtube_link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-red-500 hover:text-red-600 transition-colors"
|
||||
title="Watch Video"
|
||||
>
|
||||
<Youtube className="h-4 w-4" />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground truncate">
|
||||
{perf.venue_name} • {perf.venue_city}{perf.venue_state ? `, ${perf.venue_state}` : ""}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue