feat: implement Artist model usage, router, and migration script
Some checks are pending
Deploy Elmeg / deploy (push) Waiting to run
Some checks are pending
Deploy Elmeg / deploy (push) Waiting to run
This commit is contained in:
parent
49e025d3bf
commit
61715a119c
4 changed files with 137 additions and 29 deletions
|
|
@ -43,6 +43,7 @@ app.include_router(chase.router)
|
||||||
app.include_router(gamification.router)
|
app.include_router(gamification.router)
|
||||||
app.include_router(videos.router)
|
app.include_router(videos.router)
|
||||||
|
|
||||||
|
|
||||||
# Optional features - can be disabled via env vars
|
# Optional features - can be disabled via env vars
|
||||||
if ENABLE_BUG_TRACKER:
|
if ENABLE_BUG_TRACKER:
|
||||||
from routers import tickets
|
from routers import tickets
|
||||||
|
|
|
||||||
85
backend/migrations/migrate_artists.py
Normal file
85
backend/migrations/migrate_artists.py
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
"""
|
||||||
|
Migration script to refactor Artists and link Songs.
|
||||||
|
1. Add new columns to Artist (slug, bio, image_url).
|
||||||
|
2. Add artist_id to Song.
|
||||||
|
3. Migrate 'original_artist' string to Artist records.
|
||||||
|
"""
|
||||||
|
from sqlmodel import Session, select, text
|
||||||
|
from database import engine
|
||||||
|
from models import Artist, Song
|
||||||
|
from slugify import slugify
|
||||||
|
|
||||||
|
def migrate_artists():
|
||||||
|
with Session(engine) as session:
|
||||||
|
print("Running Artist & Covers Migration...")
|
||||||
|
|
||||||
|
# 1. Schema Changes (Idempotent ALTER TABLE)
|
||||||
|
# Artist columns
|
||||||
|
try:
|
||||||
|
session.exec(text("ALTER TABLE artist ADD COLUMN slug VARCHAR UNIQUE"))
|
||||||
|
print("Added artist.slug")
|
||||||
|
except Exception as e:
|
||||||
|
print("artist.slug already exists or error:", e)
|
||||||
|
session.rollback()
|
||||||
|
|
||||||
|
try:
|
||||||
|
session.exec(text("ALTER TABLE artist ADD COLUMN bio VARCHAR"))
|
||||||
|
print("Added artist.bio")
|
||||||
|
except Exception as e:
|
||||||
|
session.rollback()
|
||||||
|
|
||||||
|
try:
|
||||||
|
session.exec(text("ALTER TABLE artist ADD COLUMN image_url VARCHAR"))
|
||||||
|
print("Added artist.image_url")
|
||||||
|
except Exception as e:
|
||||||
|
session.rollback()
|
||||||
|
|
||||||
|
# Song artist_id
|
||||||
|
try:
|
||||||
|
session.exec(text("ALTER TABLE song ADD COLUMN artist_id INTEGER REFERENCES artist(id)"))
|
||||||
|
print("Added song.artist_id")
|
||||||
|
except Exception as e:
|
||||||
|
print("song.artist_id already exists or error:", e)
|
||||||
|
session.rollback()
|
||||||
|
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
# 2. Data Migration
|
||||||
|
songs = session.exec(select(Song).where(Song.original_artist != None)).all()
|
||||||
|
print(f"Found {len(songs)} songs with original_artist string.")
|
||||||
|
|
||||||
|
created_count = 0
|
||||||
|
linked_count = 0
|
||||||
|
|
||||||
|
for song in songs:
|
||||||
|
if not song.original_artist or song.artist_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
artist_name = song.original_artist.strip()
|
||||||
|
if not artist_name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Clean up name (e.g., "The Beatles" vs "Beatles")
|
||||||
|
# For now, just trust the string but ensure consistent slug
|
||||||
|
artist_slug = slugify(artist_name)
|
||||||
|
|
||||||
|
# Find or Create Artist
|
||||||
|
artist = session.exec(select(Artist).where(Artist.slug == artist_slug)).first()
|
||||||
|
if not artist:
|
||||||
|
artist = Artist(name=artist_name, slug=artist_slug)
|
||||||
|
session.add(artist)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(artist)
|
||||||
|
created_count += 1
|
||||||
|
print(f"Created Artist: {artist_name} ({artist_slug})")
|
||||||
|
|
||||||
|
# Link Song
|
||||||
|
song.artist_id = artist.id
|
||||||
|
session.add(song)
|
||||||
|
linked_count += 1
|
||||||
|
|
||||||
|
session.commit()
|
||||||
|
print(f"Migration Complete: Created {created_count} artists, linked {linked_count} songs.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
migrate_artists()
|
||||||
|
|
@ -89,9 +89,14 @@ class Tour(SQLModel, table=True):
|
||||||
class Artist(SQLModel, table=True):
|
class Artist(SQLModel, table=True):
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
name: str = Field(index=True)
|
name: str = Field(index=True)
|
||||||
|
slug: str = Field(unique=True, index=True)
|
||||||
|
bio: Optional[str] = Field(default=None)
|
||||||
|
image_url: Optional[str] = Field(default=None)
|
||||||
instrument: Optional[str] = Field(default=None)
|
instrument: Optional[str] = Field(default=None)
|
||||||
notes: Optional[str] = Field(default=None)
|
notes: Optional[str] = Field(default=None)
|
||||||
|
|
||||||
|
songs: List["Song"] = Relationship(back_populates="artist")
|
||||||
|
|
||||||
class Show(SQLModel, table=True):
|
class Show(SQLModel, table=True):
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
date: datetime = Field(index=True)
|
date: datetime = Field(index=True)
|
||||||
|
|
@ -121,6 +126,10 @@ class Song(SQLModel, table=True):
|
||||||
notes: Optional[str] = Field(default=None)
|
notes: Optional[str] = Field(default=None)
|
||||||
youtube_link: Optional[str] = Field(default=None)
|
youtube_link: Optional[str] = Field(default=None)
|
||||||
|
|
||||||
|
# New Relation
|
||||||
|
artist_id: Optional[int] = Field(default=None, foreign_key="artist.id")
|
||||||
|
artist: Optional[Artist] = Relationship(back_populates="songs")
|
||||||
|
|
||||||
vertical: Vertical = Relationship(back_populates="songs")
|
vertical: Vertical = Relationship(back_populates="songs")
|
||||||
|
|
||||||
class Tag(SQLModel, table=True):
|
class Tag(SQLModel, table=True):
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,50 @@
|
||||||
from typing import List
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
|
from typing import List, Optional
|
||||||
from database import get_session
|
from database import get_session
|
||||||
from models import Artist, User
|
from models import Artist, Song, Performance, Show, PerformanceArtist
|
||||||
from schemas import ArtistCreate, ArtistRead
|
from schemas import SongRead
|
||||||
from auth import get_current_user
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/artists", tags=["artists"])
|
router = APIRouter(prefix="/artists", tags=["artists"])
|
||||||
|
|
||||||
@router.post("/", response_model=ArtistRead)
|
@router.get("/{slug}")
|
||||||
def create_artist(
|
async def get_artist(slug: str, session: Session = Depends(get_session)):
|
||||||
artist: ArtistCreate,
|
"""Get artist details, covers, and guest appearances"""
|
||||||
session: Session = Depends(get_session),
|
artist = session.exec(select(Artist).where(Artist.slug == slug)).first()
|
||||||
current_user: User = Depends(get_current_user)
|
|
||||||
):
|
|
||||||
db_artist = Artist.model_validate(artist)
|
|
||||||
session.add(db_artist)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(db_artist)
|
|
||||||
return db_artist
|
|
||||||
|
|
||||||
@router.get("/", response_model=List[ArtistRead])
|
|
||||||
def read_artists(
|
|
||||||
offset: int = 0,
|
|
||||||
limit: int = Query(default=100, le=100),
|
|
||||||
session: Session = Depends(get_session)
|
|
||||||
):
|
|
||||||
artists = session.exec(select(Artist).offset(offset).limit(limit)).all()
|
|
||||||
return artists
|
|
||||||
|
|
||||||
@router.get("/{artist_id}", response_model=ArtistRead)
|
|
||||||
def read_artist(artist_id: int, session: Session = Depends(get_session)):
|
|
||||||
artist = session.get(Artist, artist_id)
|
|
||||||
if not artist:
|
if not artist:
|
||||||
raise HTTPException(status_code=404, detail="Artist not found")
|
raise HTTPException(status_code=404, detail="Artist not found")
|
||||||
return artist
|
|
||||||
|
# Get covers (Songs by this artist that Goose has played)
|
||||||
|
covers = session.exec(
|
||||||
|
select(Song)
|
||||||
|
.where(Song.artist_id == artist.id)
|
||||||
|
.order_by(Song.title)
|
||||||
|
).all()
|
||||||
|
|
||||||
|
# Get guest appearances (Performances where this artist is linked)
|
||||||
|
# This queries the PerformanceArtist link table
|
||||||
|
guest_appearances = session.exec(
|
||||||
|
select(Performance, Show)
|
||||||
|
.join(PerformanceArtist, PerformanceArtist.performance_id == Performance.id)
|
||||||
|
.join(Show, Performance.show_id == Show.id)
|
||||||
|
.where(PerformanceArtist.artist_id == artist.id)
|
||||||
|
.order_by(Show.date.desc())
|
||||||
|
).all()
|
||||||
|
|
||||||
|
# Format guest appearances
|
||||||
|
guest_spots = []
|
||||||
|
for perf, show in guest_appearances:
|
||||||
|
guest_spots.append({
|
||||||
|
"date": show.date,
|
||||||
|
"venue": show.venue.name if show.venue else "Unknown Venue",
|
||||||
|
"city": f"{show.venue.city}, {show.venue.state}" if show.venue else "",
|
||||||
|
"song_title": perf.song.title,
|
||||||
|
"show_slug": show.slug,
|
||||||
|
"song_slug": perf.song.slug
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"artist": artist,
|
||||||
|
"covers": covers,
|
||||||
|
"guest_appearances": guest_spots
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue