Files
kmtnflower/backend/seed.py
T
2026-06-21 02:56:49 +00:00

215 lines
8.3 KiB
Python

"""
Seed script: populates the database with station data.
Idempotent — safe to run multiple times. Uses get-or-create (upsert) for each entity.
Usage:
KMTN_DATABASE_URL=... python seed.py
(run from the backend/ directory)
"""
import asyncio
from datetime import date
from sqlalchemy import select
from app.database import async_session
from app.models import Program, Event, DonationTier, StationConfig
# ── Seed data ──────────────────────────────────────────────
PROGRAMS: list[dict] = [
{"day_of_week": 1, "day_label": "Monday", "time": "6:00 AM", "title": "Morning Mountain Mist", "host": "Diana Walsh", "genre": "Ambient / Nature"},
{"day_of_week": 1, "day_label": "Monday", "time": "8:00 AM", "title": "Community Roundup", "host": "Tom Breen", "genre": "News / Talk"},
{"day_of_week": 1, "day_label": "Monday", "time": "10:00 AM", "title": "Bluegrass Trails", "host": "Sarah Lynn", "genre": "Bluegrass"},
{"day_of_week": 1, "day_label": "Monday", "time": "12:00 PM", "title": "Lunchtime Jazz", "host": "Mike Darrow", "genre": "Jazz"},
{"day_of_week": 1, "day_label": "Monday", "time": "2:00 PM", "title": "Folk Roots Hour", "host": "Nadia Cole", "genre": "Folk"},
{"day_of_week": 1, "day_label": "Monday", "time": "4:00 PM", "title": "Afternoon Acoustics", "host": "Jen Reeves", "genre": "Acoustic"},
{"day_of_week": 1, "day_label": "Monday", "time": "6:00 PM", "title": "Evening Echoes", "host": "Carlos Mendez", "genre": "Indie / Alternative"},
{"day_of_week": 1, "day_label": "Monday", "time": "8:00 PM", "title": "Classical Mountains", "host": "Elena Cross", "genre": "Classical"},
{"day_of_week": 1, "day_label": "Monday", "time": "10:00 PM", "title": "Night Owl Session", "host": "DJ Kofi", "genre": "Electronic"},
{"day_of_week": 1, "day_label": "Monday", "time": "12:00 AM", "title": "Late Night Jazz", "host": "Mike Darrow", "genre": "Jazz"},
]
EVENTS: list[dict] = [
{
"date": date(2026, 7, 12),
"title": "Summer Sounds Festival",
"description": "A full day of live local music, community booths, and station meet-and-greet at the foothills park. Free admission — donations welcome.",
"location": "Foothills Community Park",
"icon": "🎵",
},
{
"date": date(2026, 8, 5),
"title": "Annual Fundraiser Gala",
"description": "Our biggest fundraiser of the year! Enjoy dinner, silent auction, and performances from past guest artists.",
"location": "Mountain View Ballroom",
"icon": "🎙️",
},
{
"date": date(2026, 9, 18),
"title": "Volunteer Open House",
"description": "Tour the studio, meet the team, and learn how you can help keep our free stream alive. New volunteers always welcome!",
"location": "KMTN Studio — 42 Pine St",
"icon": "🤝",
},
{
"date": date(2026, 10, 30),
"title": "Harvest Moon Broadcast",
"description": "Special live broadcast from the harvest moon gathering. Featuring folk, bluegrass, and indigenous music.",
"location": "Ridgefield Amphitheater",
"icon": "🌙",
},
]
TIERS: list[dict] = [
{
"name": "Seed",
"amount": 5.00,
"icon": "🌱",
"benefits": ["Digital thank-you card", "Name listed on our website", "Quarterly newsletter"],
"display_order": 1,
},
{
"name": "Stream",
"amount": 15.00,
"icon": "🎵",
"benefits": ["All Seed benefits", "KMountain Flower pin", "Priority show-request line", "Annual station update call"],
"display_order": 2,
},
{
"name": "Ridge",
"amount": 30.00,
"icon": "🏔️",
"benefits": ["All Stream benefits", "Official member patch", "Exclusive show invitations", "Annual station tour"],
"display_order": 3,
},
{
"name": "Summit",
"amount": 60.00,
"icon": "⭐",
"benefits": ["All Ridge benefits", "Featured on the Summit Wall", "Name on-air during anniversary special", "Lifetime patron recognition"],
"display_order": 4,
},
]
STATION_CONFIG: dict = {
"key": "default",
"callsign": "KMTN",
"name_primary": "KMountain",
"name_secondary": "Flower Radio",
"tagline": "Community Radio — Est. 1987",
"frequency": "98.7 FM",
"founded_year": 1987,
"nonprofit_type": "501(c)(3)",
"ein": "84-XXXXXXX",
"logo_url": "svg/small-flower.svg",
"hero_background_url": "svg/mountain-landscape.svg",
"hero_icon_url": "svg/orange-flower.svg",
"hero_divider_url": "svg/mountain-divider.svg",
"address": "42 Pine Street\nMountain View, CO 80424",
"phone": "(970) 555-0198",
"email": "hello@kmountainflower.org",
"website": "kmountainflower.org",
}
# ── Helpers ────────────────────────────────────────────────
async def _upsert_program(session, data: dict) -> None:
"""Get-or-create a program matched on (day_of_week, time)."""
existing = await session.execute(
select(Program).where(
Program.day_of_week == data["day_of_week"],
Program.time == data["time"],
)
)
program = existing.scalar_one_or_none()
if program:
for key, value in data.items():
setattr(program, key, value)
else:
session.add(Program(**data))
async def _upsert_event(session, data: dict) -> None:
"""Get-or-create an event matched on (date, title)."""
existing = await session.execute(
select(Event).where(
Event.date == data["date"],
Event.title == data["title"],
)
)
event = existing.scalar_one_or_none()
if event:
for key, value in data.items():
setattr(event, key, value)
else:
session.add(Event(**data, active=True))
async def _upsert_tier(session, data: dict) -> None:
"""Get-or-create a donation tier matched on name."""
existing = await session.execute(
select(DonationTier).where(DonationTier.name == data["name"])
)
tier = existing.scalar_one_or_none()
if tier:
for key, value in data.items():
setattr(tier, key, value)
else:
session.add(DonationTier(**data))
async def _upsert_station_config(session, data: dict) -> None:
"""Get-or-create the singleton station config (key='default')."""
existing = await session.execute(
select(StationConfig).where(StationConfig.key == "default")
)
config = existing.scalar_one_or_none()
if config:
for key, value in data.items():
setattr(config, key, value)
else:
session.add(StationConfig(**data))
# ── Main ──────────────────────────────────────────────────
async def seed() -> None:
"""Populate the database with station data (idempotent)."""
async with async_session() as session:
for data in PROGRAMS:
await _upsert_program(session, data)
await session.commit()
print(f" ✓ Seeded {len(PROGRAMS)} programs")
for data in EVENTS:
await _upsert_event(session, data)
await session.commit()
print(f" ✓ Seeded {len(EVENTS)} events")
for data in TIERS:
await _upsert_tier(session, data)
await session.commit()
print(f" ✓ Seeded {len(TIERS)} donation tiers")
await _upsert_station_config(session, STATION_CONFIG)
await session.commit()
print(" ✓ Seeded station config")
async def truncate_all() -> None:
"""Remove all seed data from the database."""
async with async_session() as session:
await session.execute(Program.__table__.delete())
await session.execute(Event.__table__.delete())
await session.execute(DonationTier.__table__.delete())
await session.execute(StationConfig.__table__.delete())
await session.commit()
print(" ✓ Truncated all tables")
if __name__ == "__main__":
asyncio.run(seed())