from datetime import date as _date from typing import Optional from pydantic import BaseModel, computed_field # ── Shared ─────────────────────────────────────────────── DAY_NAMES = { 1: "Monday", 2: "Tuesday", 3: "Wednesday", 4: "Thursday", 5: "Friday", 6: "Saturday", 7: "Sunday", } # ── Event ──────────────────────────────────────────────── class EventCreate(BaseModel): date: _date title: str description: str location: str icon: str rsvp_url: Optional[str] = None class EventUpdate(BaseModel): date: Optional[_date] = None title: Optional[str] = None description: Optional[str] = None location: Optional[str] = None icon: Optional[str] = None rsvp_url: Optional[str] = None active: Optional[bool] = None class EventResponse(BaseModel): id: int date: _date title: str description: str location: str icon: str rsvp_url: Optional[str] active: bool model_config = {"from_attributes": True} # ── StationConfig ───────────────────────────────────────── class StationConfigResponse(BaseModel): id: int key: str callsign: str name_primary: str name_secondary: str tagline: str frequency: str founded_year: int nonprofit_type: str ein: str logo_url: str hero_background_url: str hero_icon_url: str hero_divider_url: str address: str phone: str email: str website: str donation_url: str stream_url: str stream_metadata_url: str play_store_icon_url: str play_store_url: str app_store_embed_html: str model_config = {"from_attributes": True} class StationConfigUpdate(BaseModel): callsign: Optional[str] = None name_primary: Optional[str] = None name_secondary: Optional[str] = None tagline: Optional[str] = None frequency: Optional[str] = None founded_year: Optional[int] = None nonprofit_type: Optional[str] = None ein: Optional[str] = None logo_url: Optional[str] = None hero_background_url: Optional[str] = None hero_icon_url: Optional[str] = None hero_divider_url: Optional[str] = None address: Optional[str] = None phone: Optional[str] = None email: Optional[str] = None website: Optional[str] = None donation_url: Optional[str] = None stream_url: Optional[str] = None stream_metadata_url: Optional[str] = None play_store_icon_url: Optional[str] = None play_store_url: Optional[str] = None app_store_embed_html: Optional[str] = None # ── HistoryEntry ────────────────────────────────────────── class HistoryEntryCreate(BaseModel): year: int title: str body: str display_order: int class HistoryEntryUpdate(BaseModel): year: Optional[int] = None title: Optional[str] = None body: Optional[str] = None display_order: Optional[int] = None active: Optional[bool] = None class HistoryEntryResponse(BaseModel): id: int year: int title: str body: str display_order: int active: bool model_config = {"from_attributes": True} # ── TeamMember ───────────────────────────────────────────── class TeamMemberCreate(BaseModel): name: str title: str bio: Optional[str] = None photo_url: Optional[str] = None display_order: int class TeamMemberUpdate(BaseModel): name: Optional[str] = None title: Optional[str] = None bio: Optional[str] = None photo_url: Optional[str] = None display_order: Optional[int] = None active: Optional[bool] = None class TeamMemberResponse(BaseModel): id: int name: str title: str bio: Optional[str] photo_url: Optional[str] display_order: int active: bool model_config = {"from_attributes": True} # ── CommunityHighlight ───────────────────────────────────── class CommunityHighlightCreate(BaseModel): icon: str title: str description: str display_order: int class CommunityHighlightUpdate(BaseModel): icon: Optional[str] = None title: Optional[str] = None description: Optional[str] = None display_order: Optional[int] = None active: Optional[bool] = None class CommunityHighlightResponse(BaseModel): id: int icon: str title: str description: str display_order: int active: bool model_config = {"from_attributes": True} # ── Underwriter ──────────────────────────────────────────── class UnderwriterCreate(BaseModel): name: str logo_url: Optional[str] = None description: str website_url: Optional[str] = None display_order: int = 0 class UnderwriterUpdate(BaseModel): name: Optional[str] = None logo_url: Optional[str] = None description: Optional[str] = None website_url: Optional[str] = None display_order: Optional[int] = None active: Optional[bool] = None class UnderwriterResponse(BaseModel): id: int name: str logo_url: Optional[str] description: str website_url: Optional[str] display_order: int active: bool model_config = {"from_attributes": True} # ── ShowSchedule ────────────────────────────────────────── class ShowScheduleResponse(BaseModel): id: int show_id: int day_of_week: int time: str @computed_field @property def day_label(self) -> str: return DAY_NAMES.get(self.day_of_week, "Unknown") model_config = {"from_attributes": True} class ShowScheduleCreate(BaseModel): day_of_week: int time: str # ── Show ──────────────────────────────────────────────────── class ShowResponse(BaseModel): id: int title: str description: str host: str genre: str show_art_url: Optional[str] hero_image_url: Optional[str] display_order: int active: bool schedules: list[ShowScheduleResponse] model_config = {"from_attributes": True} class ShowCreate(BaseModel): title: str description: str host: str genre: str show_art_url: Optional[str] = None hero_image_url: Optional[str] = None display_order: int = 0 schedules: list[ShowScheduleCreate] class ShowUpdate(BaseModel): title: Optional[str] = None description: Optional[str] = None host: Optional[str] = None genre: Optional[str] = None show_art_url: Optional[str] = None hero_image_url: Optional[str] = None display_order: Optional[int] = None active: Optional[bool] = None schedules: Optional[list[ShowScheduleCreate]] = None # ── Stats ──────────────────────────────────────────────────────── class StatsSummaryResponse(BaseModel): total_unique_visitors: int class TimeSeriesPoint(BaseModel): date: str unique_visitors: int class GeoStatResponse(BaseModel): country: str country_code: str latitude: Optional[float] = None longitude: Optional[float] = None visitors: int class ParseResultResponse(BaseModel): files_processed: int rows_inserted: int