diff --git a/backend/app/api/admin.py b/backend/app/api/admin.py index 20df06e..fd6aa89 100644 --- a/backend/app/api/admin.py +++ b/backend/app/api/admin.py @@ -2,7 +2,7 @@ from fastapi import APIRouter from app.config import settings from app.database import get_session -from app.models import DonationTier, Event, Program, StationConfig, Show, ShowSchedule +from app.models import DonationTier, Event, StationConfig, Show, ShowSchedule router = APIRouter() @@ -19,7 +19,6 @@ async def reset_database(): async with async_session() as session: await session.execute(ShowSchedule.__table__.delete()) await session.execute(Show.__table__.delete()) - await session.execute(Program.__table__.delete()) await session.execute(Event.__table__.delete()) await session.execute(DonationTier.__table__.delete()) await session.execute(StationConfig.__table__.delete()) diff --git a/backend/app/api/programs.py b/backend/app/api/programs.py deleted file mode 100644 index ab66c2b..0000000 --- a/backend/app/api/programs.py +++ /dev/null @@ -1,84 +0,0 @@ -from typing import Optional - -from fastapi import APIRouter, Depends, Query -from sqlalchemy import select, func -from sqlalchemy.ext.asyncio import AsyncSession - -from app.auth import get_current_admin_user -from app.database import get_session -from app.models import Program -from app.schemas import ProgramCreate, ProgramUpdate, ProgramResponse -from app.user_models import User - -router = APIRouter() - - -def _day_filter(day: int): - """Return the SQLAlchemy filter for a given day_of_week.""" - return Program.day_of_week == day - - -@router.get("/", response_model=list[ProgramResponse]) -async def list_programs( - day: Optional[int] = Query(None, description="Filter by day_of_week (1=Mon … 7=Sun)"), - session: AsyncSession = Depends(get_session), -): - query = select(Program).order_by(Program.day_of_week, Program.time) - if day is not None: - query = query.filter(_day_filter(day)) - result = await session.execute(query) - return list(result.scalars().all()) - - -@router.get("/{program_id}", response_model=ProgramResponse) -async def get_program(program_id: int, session: AsyncSession = Depends(get_session)): - result = await session.execute(select(Program).where(Program.id == program_id)) - program = result.scalar_one_or_none() - if not program: - return {"error": "Program not found"} - return program - - -@router.post("/", response_model=ProgramResponse, status_code=201) -async def create_program( - payload: ProgramCreate, - session: AsyncSession = Depends(get_session), - _: User = Depends(get_current_admin_user), -): - program = Program(**payload.model_dump()) - session.add(program) - await session.commit() - await session.refresh(program) - return program - - -@router.put("/{program_id}", response_model=ProgramResponse) -async def update_program( - program_id: int, - payload: ProgramUpdate, - session: AsyncSession = Depends(get_session), - _: User = Depends(get_current_admin_user), -): - result = await session.execute(select(Program).where(Program.id == program_id)) - program = result.scalar_one_or_none() - if not program: - return {"error": "Program not found"} - for key, value in payload.model_dump(exclude_unset=True).items(): - setattr(program, key, value) - await session.commit() - await session.refresh(program) - return program - - -@router.delete("/{program_id}", status_code=204) -async def delete_program( - program_id: int, - session: AsyncSession = Depends(get_session), - _: User = Depends(get_current_admin_user), -): - result = await session.execute(select(Program).where(Program.id == program_id)) - program = result.scalar_one_or_none() - if not program: - return {"error": "Program not found"} - await session.delete(program) - await session.commit() diff --git a/backend/app/main.py b/backend/app/main.py index c042cf3..fae6adc 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -8,9 +8,9 @@ from sqlalchemy import func, select from app.config import settings from app.database import async_session, engine -from app.models import Base, Program, Show, StationConfig, HistoryEntry, TeamMember, CommunityHighlight +from app.models import Base, Show, StationConfig, HistoryEntry, TeamMember, CommunityHighlight from app.user_models import User -from app.api import programs, events, tiers, auth, station_config, history, team, community, shows, upload +from app.api import events, tiers, auth, station_config, history, team, community, shows, upload async def _ensure_station_config(session): @@ -92,7 +92,6 @@ app.add_middleware( # Register routers app.include_router(auth.router, prefix="/api/auth", tags=["auth"]) -app.include_router(programs.router, prefix="/api/programs", tags=["programs"]) app.include_router(events.router, prefix="/api/events", tags=["events"]) app.include_router(tiers.router, prefix="/api/tiers", tags=["tiers"]) app.include_router(station_config.router, prefix="/api/station-config", tags=["station-config"]) diff --git a/backend/app/models.py b/backend/app/models.py index 92130ae..c3b839d 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -17,18 +17,6 @@ class Base(DeclarativeBase): pass -class Program(Base): - __tablename__ = "programs" - - id = Column(Integer, primary_key=True, autoincrement=True) - day_of_week = Column(Integer, nullable=False) # 1=Mon … 7=Sun - day_label = Column(String(10), nullable=False) # "Monday", … - time = Column(String(10), nullable=False) # "6:00 AM" - title = Column(String(200), nullable=False) - host = Column(String(100), nullable=False) - genre = Column(String(80), nullable=False) - - class Show(Base): __tablename__ = "shows" diff --git a/backend/app/schemas.py b/backend/app/schemas.py index ef25776..234d3e5 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -4,7 +4,7 @@ from typing import Optional from pydantic import BaseModel, computed_field, field_validator -# ── Program ────────────────────────────────────────────── +# ── Shared ─────────────────────────────────────────────── DAY_NAMES = { 1: "Monday", @@ -17,40 +17,6 @@ DAY_NAMES = { } -class ProgramCreate(BaseModel): - day_of_week: int - day_label: str - time: str - title: str - host: str - genre: str - - -class ProgramUpdate(BaseModel): - day_of_week: Optional[int] = None - day_label: Optional[str] = None - time: Optional[str] = None - title: Optional[str] = None - host: Optional[str] = None - genre: Optional[str] = None - - -class ProgramResponse(BaseModel): - id: int - day_of_week: int - time: str - title: str - host: str - genre: str - - @computed_field - @property - def day_label(self) -> str: - return DAY_NAMES.get(self.day_of_week, "Unknown") - - model_config = {"from_attributes": True} - - # ── Event ──────────────────────────────────────────────── class EventCreate(BaseModel): diff --git a/backend/seed.py b/backend/seed.py index 305d5ab..dd9dd99 100644 --- a/backend/seed.py +++ b/backend/seed.py @@ -14,24 +14,11 @@ from datetime import date from sqlalchemy import select from app.database import async_session -from app.models import Program, Show, ShowSchedule, Event, DonationTier, StationConfig +from app.models import Show, ShowSchedule, Event, DonationTier, StationConfig from app.models import HistoryEntry, TeamMember, CommunityHighlight # ── 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"}, -] - SHOWS: list[dict] = [ { "title": "Morning Mountain Mist", @@ -345,22 +332,6 @@ COMMUNITY_HIGHLIGHTS: list[dict] = [ # ── 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( @@ -476,11 +447,6 @@ async def _upsert_community_highlight(session, data: dict) -> None: 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() @@ -524,7 +490,6 @@ async def truncate_all() -> None: # Delete child tables first to respect foreign keys await session.execute(ShowSchedule.__table__.delete()) await session.execute(Show.__table__.delete()) - await session.execute(Program.__table__.delete()) await session.execute(Event.__table__.delete()) await session.execute(DonationTier.__table__.delete()) await session.execute(StationConfig.__table__.delete()) diff --git a/src/app/admin/admin-program-form.component.html b/src/app/admin/admin-program-form.component.html deleted file mode 100644 index bdc0ae4..0000000 --- a/src/app/admin/admin-program-form.component.html +++ /dev/null @@ -1,54 +0,0 @@ -
diff --git a/src/app/admin/admin-program-form.component.scss b/src/app/admin/admin-program-form.component.scss deleted file mode 100644 index 854d9bc..0000000 --- a/src/app/admin/admin-program-form.component.scss +++ /dev/null @@ -1,129 +0,0 @@ -@use '../../styles/variables' as *; -@use '../../styles/mixins' as *; - -.form-overlay { - position: fixed; - inset: 0; - background: rgba(0, 0, 0, 0.5); - @include flex-center; - z-index: $z-modal; - @include fade-in; -} - -.form-dialog { - background: $neutral-white; - border-radius: $radius-lg; - padding: $spacing-xl; - width: 90%; - max-width: 560px; - box-shadow: $shadow-xl; -} - -.form-header { - @include flex-between; - margin-bottom: $spacing-lg; - - h2 { - font-family: $font-heading; - color: $primary-blue; - margin: 0; - font-size: 1.4rem; - } -} - -.btn-close { - background: none; - border: none; - font-size: 1.5rem; - cursor: pointer; - color: $neutral-medium; - line-height: 1; - padding: $spacing-xs; - - &:hover { - color: $neutral-dark; - } -} - -.form-error { - background: rgba($danger-red, 0.1); - color: $danger-red; - padding: $spacing-sm $spacing-md; - border-radius: $radius-md; - font-size: 0.875rem; - margin-bottom: $spacing-md; - text-align: center; -} - -.admin-form { - .form-row { - display: grid; - grid-template-columns: 1fr 1fr; - gap: $spacing-md; - } - - .form-group { - margin-bottom: $spacing-md; - - label { - display: block; - font-size: 0.875rem; - font-weight: 600; - color: $neutral-dark; - margin-bottom: $spacing-xs; - } - - input, select, textarea { - width: 100%; - padding: $spacing-sm $spacing-md; - border: 1px solid $neutral-light; - border-radius: $radius-md; - font-size: 0.95rem; - transition: border-color $transition-fast; - box-sizing: border-box; - - &:focus { - outline: none; - border-color: $primary-blue; - box-shadow: 0 0 0 3px rgba($primary-blue, 0.15); - } - - &.is-invalid { - border-color: $danger-red; - box-shadow: 0 0 0 3px rgba($danger-red, 0.15); - } - } - } -} - -.form-actions { - display: flex; - justify-content: flex-end; - gap: $spacing-sm; - margin-top: $spacing-md; -} - -.btn-save { - @include button-style($primary-blue, $neutral-white, $primary-blue-light); - - &:disabled { - opacity: 0.6; - cursor: not-allowed; - } -} - -.btn-cancel { - background: $neutral-light; - color: $neutral-dark; - border: none; - border-radius: $radius-md; - padding: $spacing-sm $spacing-lg; - font-size: 0.95rem; - cursor: pointer; - transition: background $transition-fast; - - &:hover { - background: $neutral-medium; - color: $neutral-white; - } -} diff --git a/src/app/admin/admin-program-form.component.ts b/src/app/admin/admin-program-form.component.ts deleted file mode 100644 index 0f19c6d..0000000 --- a/src/app/admin/admin-program-form.component.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { Component, EventEmitter, inject, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; -import { firstValueFrom } from 'rxjs'; - -import { Program } from '../interfaces/program'; -import { ProgramService } from '../services/program.service'; - -@Component({ - selector: 'app-admin-program-form', - standalone: true, - imports: [CommonModule, FormsModule], - templateUrl: './admin-program-form.component.html', - styleUrl: './admin-program-form.component.scss', -}) -export class AdminProgramFormComponent implements OnChanges { - private programService = inject(ProgramService); - - @Input() editItem: Program | null = null; - @Output() saved = new EventEmitterManage shows, programs, events, tiers, history, team, and community content
+Manage shows, events, tiers, history, team, and community content