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 # Page content hero_subtitle: str about_intro: str about_donation_text: str schedule_intro: str events_intro: str # Color theme color_primary: str color_primary_light: str color_primary_dark: str color_primary_muted: str color_accent: str color_accent_light: str color_accent_dark: str color_accent_muted: str color_background: str color_text: str color_success: str color_danger: str color_info: str color_white: str color_light: str color_medium: str color_black: 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 # Page content hero_subtitle: Optional[str] = None about_intro: Optional[str] = None about_donation_text: Optional[str] = None schedule_intro: Optional[str] = None events_intro: Optional[str] = None # Color theme color_primary: Optional[str] = None color_primary_light: Optional[str] = None color_primary_dark: Optional[str] = None color_primary_muted: Optional[str] = None color_accent: Optional[str] = None color_accent_light: Optional[str] = None color_accent_dark: Optional[str] = None color_accent_muted: Optional[str] = None color_background: Optional[str] = None color_text: Optional[str] = None color_success: Optional[str] = None color_danger: Optional[str] = None color_info: Optional[str] = None color_white: Optional[str] = None color_light: Optional[str] = None color_medium: Optional[str] = None color_black: 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 # ── Mobile Build Orchestration ─────────────────────────── class MobileBuildDefaultsResponse(BaseModel): tenant_key: str app_name: str tagline: str support_email: str website: str stream_url: str stream_metadata_url: str class MobileBuildCreate(BaseModel): platform_android: bool = False platform_ios: bool = False android_application_id: Optional[str] = None ios_bundle_id: Optional[str] = None version_name: str build_number: str release_profile: str = "release" class MobileBuildArtifactResponse(BaseModel): id: int request_id: int platform: str artifact_type: str file_name: str sha256: str size: int created_at: str download_url: str class MobileBuildRequestResponse(BaseModel): id: int tenant_key: str platform_android: bool platform_ios: bool android_application_id: Optional[str] ios_bundle_id: Optional[str] version_name: str build_number: str release_profile: str status: str preflight_passed: bool preflight_report: str build_log: str error_message: Optional[str] created_at: str updated_at: str started_at: Optional[str] completed_at: Optional[str] artifacts: list[MobileBuildArtifactResponse] class MobileBuildPreflightResponse(BaseModel): passed: bool issues: list[str]