elmeg-demo/backend/migrations/add_slugs.py
fullsizemalt 3edbcdeb64 feat: Add slug support for all entities
- Add slug fields to Song, Venue, Show, Tour, Performance models
- Update routers to support lookup by slug or ID
- Create slugify.py utility for generating URL-safe slugs
- Add migration script to generate slugs for existing data
- Performance slugs use songslug-YYYY-MM-DD format
2025-12-21 18:46:40 -08:00

224 lines
7.9 KiB
Python

"""
Migration script to add slug columns and generate slugs for existing data
"""
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from sqlmodel import create_engine, Session, select, text
from slugify import generate_slug, generate_show_slug, generate_performance_slug
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://elmeg:elmeg@localhost/elmeg")
engine = create_engine(DATABASE_URL)
def add_slug_columns():
"""Add slug columns to tables if they don't exist"""
with engine.connect() as conn:
# Add slug to song
conn.execute(text("ALTER TABLE song ADD COLUMN IF NOT EXISTS slug VARCHAR UNIQUE"))
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_song_slug ON song(slug)"))
# Add slug to venue
conn.execute(text("ALTER TABLE venue ADD COLUMN IF NOT EXISTS slug VARCHAR UNIQUE"))
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_venue_slug ON venue(slug)"))
# Add slug to show
conn.execute(text("ALTER TABLE show ADD COLUMN IF NOT EXISTS slug VARCHAR UNIQUE"))
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_show_slug ON show(slug)"))
# Add slug to tour
conn.execute(text("ALTER TABLE tour ADD COLUMN IF NOT EXISTS slug VARCHAR UNIQUE"))
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_tour_slug ON tour(slug)"))
# Add slug to performance
conn.execute(text("ALTER TABLE performance ADD COLUMN IF NOT EXISTS slug VARCHAR UNIQUE"))
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_performance_slug ON performance(slug)"))
conn.commit()
print("✓ Slug columns added")
def generate_song_slugs():
"""Generate slugs for all songs"""
with Session(engine) as session:
# Get all songs without slugs
result = session.exec(text("SELECT id, title FROM song WHERE slug IS NULL"))
songs = result.fetchall()
existing_slugs = set()
# Get existing slugs
existing = session.exec(text("SELECT slug FROM song WHERE slug IS NOT NULL"))
for row in existing.fetchall():
existing_slugs.add(row[0])
count = 0
for song_id, title in songs:
base_slug = generate_slug(title, 50)
slug = base_slug
counter = 2
while slug in existing_slugs:
slug = f"{base_slug}-{counter}"
counter += 1
existing_slugs.add(slug)
session.execute(
text("UPDATE song SET slug = :slug WHERE id = :id"),
{"slug": slug, "id": song_id}
)
count += 1
session.commit()
print(f"✓ Generated slugs for {count} songs")
def generate_venue_slugs():
"""Generate slugs for all venues"""
with Session(engine) as session:
result = session.exec(text("SELECT id, name, city FROM venue WHERE slug IS NULL"))
venues = result.fetchall()
existing_slugs = set()
existing = session.exec(text("SELECT slug FROM venue WHERE slug IS NOT NULL"))
for row in existing.fetchall():
existing_slugs.add(row[0])
count = 0
for venue_id, name, city in venues:
# Include city to help disambiguate
base_slug = generate_slug(f"{name} {city}", 60)
slug = base_slug
counter = 2
while slug in existing_slugs:
slug = f"{base_slug}-{counter}"
counter += 1
existing_slugs.add(slug)
session.execute(
text("UPDATE venue SET slug = :slug WHERE id = :id"),
{"slug": slug, "id": venue_id}
)
count += 1
session.commit()
print(f"✓ Generated slugs for {count} venues")
def generate_show_slugs():
"""Generate slugs for all shows"""
with Session(engine) as session:
result = session.exec(text("""
SELECT s.id, s.date, v.name
FROM show s
LEFT JOIN venue v ON s.venue_id = v.id
WHERE s.slug IS NULL
"""))
shows = result.fetchall()
existing_slugs = set()
existing = session.exec(text("SELECT slug FROM show WHERE slug IS NOT NULL"))
for row in existing.fetchall():
existing_slugs.add(row[0])
count = 0
for show_id, date, venue_name in shows:
date_str = date.strftime("%Y-%m-%d") if date else "unknown"
venue_slug = generate_slug(venue_name or "unknown", 25)
base_slug = f"{date_str}-{venue_slug}"
slug = base_slug
counter = 2
while slug in existing_slugs:
slug = f"{base_slug}-{counter}"
counter += 1
existing_slugs.add(slug)
session.execute(
text("UPDATE show SET slug = :slug WHERE id = :id"),
{"slug": slug, "id": show_id}
)
count += 1
session.commit()
print(f"✓ Generated slugs for {count} shows")
def generate_tour_slugs():
"""Generate slugs for all tours"""
with Session(engine) as session:
result = session.exec(text("SELECT id, name FROM tour WHERE slug IS NULL"))
tours = result.fetchall()
existing_slugs = set()
existing = session.exec(text("SELECT slug FROM tour WHERE slug IS NOT NULL"))
for row in existing.fetchall():
existing_slugs.add(row[0])
count = 0
for tour_id, name in tours:
base_slug = generate_slug(name, 50)
slug = base_slug
counter = 2
while slug in existing_slugs:
slug = f"{base_slug}-{counter}"
counter += 1
existing_slugs.add(slug)
session.execute(
text("UPDATE tour SET slug = :slug WHERE id = :id"),
{"slug": slug, "id": tour_id}
)
count += 1
session.commit()
print(f"✓ Generated slugs for {count} tours")
def generate_performance_slugs():
"""Generate slugs for all performances (songslug-date format)"""
with Session(engine) as session:
result = session.exec(text("""
SELECT p.id, s.slug as song_slug, sh.date
FROM performance p
JOIN song s ON p.song_id = s.id
JOIN show sh ON p.show_id = sh.id
WHERE p.slug IS NULL
"""))
performances = result.fetchall()
existing_slugs = set()
existing = session.exec(text("SELECT slug FROM performance WHERE slug IS NOT NULL"))
for row in existing.fetchall():
existing_slugs.add(row[0])
count = 0
for perf_id, song_slug, date in performances:
date_str = date.strftime("%Y-%m-%d") if date else "unknown"
base_slug = f"{song_slug}-{date_str}"
slug = base_slug
counter = 2
# Handle multiple performances of same song in same show
while slug in existing_slugs:
slug = f"{base_slug}-{counter}"
counter += 1
existing_slugs.add(slug)
session.execute(
text("UPDATE performance SET slug = :slug WHERE id = :id"),
{"slug": slug, "id": perf_id}
)
count += 1
session.commit()
print(f"✓ Generated slugs for {count} performances")
def run_migration():
print("=== Running Slug Migration ===")
add_slug_columns()
generate_song_slugs()
generate_venue_slugs()
generate_tour_slugs()
generate_show_slugs()
generate_performance_slugs() # Must run after song slugs
print("=== Migration Complete ===")
if __name__ == "__main__":
run_migration()