from datetime import datetime from sqlalchemy import ( Column, DateTime, Float, Integer, LargeBinary, String, Text, Boolean, Date, ForeignKey, UniqueConstraint, Index, text, ) from sqlalchemy.orm import DeclarativeBase, relationship class Base(DeclarativeBase): pass class Show(Base): __tablename__ = "shows" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(String(200), nullable=False) description = Column(Text, nullable=False) host = Column(String(100), nullable=False) genre = Column(String(80), nullable=False) show_art_url = Column(String(500), nullable=True) hero_image_url = Column(String(500), nullable=True) display_order = Column(Integer, nullable=False, default=0) active = Column(Boolean, default=True) schedules = relationship("ShowSchedule", back_populates="show", cascade="all, delete-orphan") class ShowSchedule(Base): __tablename__ = "show_schedules" id = Column(Integer, primary_key=True, autoincrement=True) show_id = Column(Integer, ForeignKey("shows.id", ondelete="CASCADE"), nullable=False) day_of_week = Column(Integer, nullable=False) time = Column(String(10), nullable=False) show = relationship("Show", back_populates="schedules") __table_args__ = ( UniqueConstraint("show_id", "day_of_week", "time", name="uq_show_sched"), ) class Event(Base): __tablename__ = "events" id = Column(Integer, primary_key=True, autoincrement=True) date = Column(Date, nullable=False) title = Column(String(200), nullable=False) description = Column(Text, nullable=False) location = Column(String(200), nullable=False) icon = Column(String(10), nullable=False) rsvp_url = Column(String(500), nullable=True) active = Column(Boolean, default=True) class StationConfig(Base): __tablename__ = "station_config" id = Column(Integer, primary_key=True, autoincrement=True) key = Column(String(20), unique=True, nullable=False, default="default") # Identity callsign = Column(String(10), nullable=False, default="KMTN") name_primary = Column(String(100), nullable=False, default="KMountain") name_secondary = Column(String(100), nullable=False, default="Flower Radio") tagline = Column(String(200), nullable=False, default="Community Radio — Est. 1987") frequency = Column(String(10), nullable=False, default="98.7 FM") founded_year = Column(Integer, nullable=False, default=1987) # Nonprofit nonprofit_type = Column(String(10), nullable=False, default="501(c)(3)") ein = Column(String(20), nullable=False, default="84-XXXXXXX") # Images (URL strings relative to app root) logo_url = Column(String(500), nullable=False, default="svg/small-flower.svg") hero_background_url = Column(String(500), nullable=False, default="svg/mountain-landscape.svg") hero_icon_url = Column(String(500), nullable=False, default="svg/orange-flower.svg") hero_divider_url = Column(String(500), nullable=False, default="svg/mountain-divider.svg") # Contact address = Column(Text, nullable=False, default="42 Pine Street\nMountain View, CO 80424") phone = Column(String(30), nullable=False, default="(970) 555-0198") email = Column(String(100), nullable=False, default="hello@kmountainflower.org") website = Column(String(200), nullable=False, default="kmountainflower.org") # Donation donation_url = Column(String(500), nullable=False, default="") # Stream stream_url = Column(String(500), nullable=False, default="") stream_metadata_url = Column(String(500), nullable=False, default="") # Mobile app download links play_store_icon_url = Column(String(500), nullable=False, default="") play_store_url = Column(String(500), nullable=False, default="") app_store_embed_html = Column(Text, nullable=False, default="") # Page content (freeform text, admin-editable) hero_subtitle = Column(Text, nullable=False, default="") about_intro = Column(Text, nullable=False, default="") about_donation_text = Column(Text, nullable=False, default="") schedule_intro = Column(Text, nullable=False, default="") events_intro = Column(Text, nullable=False, default="") # Color theme — hex strings, e.g. "#1a3a5c" # server_default is required so PostgreSQL emits a proper DEFAULT in ALTER TABLE DDL color_primary = Column(String(7), nullable=False, server_default=text("'#1a3a5c'"), default="#1a3a5c") color_primary_light = Column(String(7), nullable=False, server_default=text("'#2a5a8c'"), default="#2a5a8c") color_primary_dark = Column(String(7), nullable=False, server_default=text("'#0e2440'"), default="#0e2440") color_primary_muted = Column(String(7), nullable=False, server_default=text("'#3a6a9c'"), default="#3a6a9c") color_accent = Column(String(7), nullable=False, server_default=text("'#e87a2e'"), default="#e87a2e") color_accent_light = Column(String(7), nullable=False, server_default=text("'#f09a4e'"), default="#f09a4e") color_accent_dark = Column(String(7), nullable=False, server_default=text("'#c85a1e'"), default="#c85a1e") color_accent_muted = Column(String(7), nullable=False, server_default=text("'#f0a86a'"), default="#f0a86a") color_background = Column(String(7), nullable=False, server_default=text("'#faf6f0'"), default="#faf6f0") color_text = Column(String(7), nullable=False, server_default=text("'#3a3632'"), default="#3a3632") color_success = Column(String(7), nullable=False, server_default=text("'#4a9e4f'"), default="#4a9e4f") color_danger = Column(String(7), nullable=False, server_default=text("'#c83030'"), default="#c83030") color_info = Column(String(7), nullable=False, server_default=text("'#3a8abf'"), default="#3a8abf") # Neutral colors color_white = Column(String(7), nullable=False, server_default=text("'#ffffff'"), default="#ffffff") color_light = Column(String(7), nullable=False, server_default=text("'#f0ebe3'"), default="#f0ebe3") color_medium = Column(String(7), nullable=False, server_default=text("'#8a8580'"), default="#8a8580") color_black = Column(String(7), nullable=False, server_default=text("'#1a1816'"), default="#1a1816") class HistoryEntry(Base): __tablename__ = "history_entries" id = Column(Integer, primary_key=True, autoincrement=True) year = Column(Integer, nullable=False) title = Column(String(200), nullable=False) body = Column(Text, nullable=False) display_order = Column(Integer, nullable=False) active = Column(Boolean, default=True) class TeamMember(Base): __tablename__ = "team_members" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(100), nullable=False) title = Column(String(150), nullable=False) bio = Column(Text, nullable=True) photo_url = Column(String(500), nullable=True) display_order = Column(Integer, nullable=False) active = Column(Boolean, default=True) class CommunityHighlight(Base): __tablename__ = "community_highlights" id = Column(Integer, primary_key=True, autoincrement=True) icon = Column(String(10), nullable=False) title = Column(String(200), nullable=False) description = Column(Text, nullable=False) display_order = Column(Integer, nullable=False) active = Column(Boolean, default=True) class Underwriter(Base): __tablename__ = "underwriters" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(100), nullable=False) logo_url = Column(String(500), nullable=True) description = Column(Text, nullable=False) website_url = Column(String(500), nullable=True) display_order = Column(Integer, nullable=False, default=0) active = Column(Boolean, default=True) class StorageBlob(Base): __tablename__ = "storage_blobs" id = Column(Integer, primary_key=True, autoincrement=True) blob_id = Column(String(32), unique=True, nullable=False, index=True) content_type = Column(String(100), nullable=False) size = Column(Integer, nullable=False) data = Column(LargeBinary, nullable=False) created_at = Column(DateTime, nullable=False, default=datetime.utcnow) # ── Visitor Stats ────────────────────────────────────────────── class VisitorStatDaily(Base): """Daily aggregated visitor statistics.""" __tablename__ = "visitor_stats_daily" id = Column(Integer, primary_key=True, autoincrement=True) date = Column(Date, nullable=False, unique=True) unique_visitors = Column(Integer, nullable=False, default=0) __table_args__ = ( Index("idx_visitor_stats_date", "date"), ) class VisitorStatGeo(Base): """Geographic breakdown of visitors per day.""" __tablename__ = "visitor_stats_geo" id = Column(Integer, primary_key=True, autoincrement=True) date = Column(Date, nullable=False) country = Column(String(100), nullable=True) country_code = Column(String(10), nullable=True) city = Column(String(200), nullable=True) latitude = Column(Float, nullable=True) longitude = Column(Float, nullable=True) visitor_count = Column(Integer, nullable=False, default=0) __table_args__ = ( Index("idx_visitor_stats_geo_date", "date"), Index("idx_visitor_stats_geo_country", "country_code"), ) class LogParseState(Base): """Tracks the last successfully parsed log date.""" __tablename__ = "log_parse_state" id = Column(Integer, primary_key=True, autoincrement=True) key = Column(String(20), unique=True, nullable=False, default="default") last_parsed_date = Column(Date, nullable=False) updated_at = Column(DateTime, nullable=False, default=datetime.utcnow) # ── Mobile Build Orchestration ───────────────────────────────── class MobileBuildRequest(Base): __tablename__ = "mobile_build_requests" id = Column(Integer, primary_key=True, autoincrement=True) tenant_key = Column(String(80), nullable=False, default="default") platform_android = Column(Boolean, nullable=False, default=False) platform_ios = Column(Boolean, nullable=False, default=False) android_application_id = Column(String(200), nullable=True) ios_bundle_id = Column(String(200), nullable=True) version_name = Column(String(40), nullable=False) build_number = Column(String(40), nullable=False) release_profile = Column(String(80), nullable=False, default="release") status = Column(String(20), nullable=False, default="draft") preflight_passed = Column(Boolean, nullable=False, default=False) preflight_report = Column(Text, nullable=False, default="") build_log = Column(Text, nullable=False, default="") error_message = Column(Text, nullable=True) created_by_id = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True) created_at = Column(DateTime, nullable=False, default=datetime.utcnow) updated_at = Column(DateTime, nullable=False, default=datetime.utcnow) started_at = Column(DateTime, nullable=True) completed_at = Column(DateTime, nullable=True) class MobileBuildArtifact(Base): __tablename__ = "mobile_build_artifacts" id = Column(Integer, primary_key=True, autoincrement=True) request_id = Column(Integer, ForeignKey("mobile_build_requests.id", ondelete="CASCADE"), nullable=False, index=True) platform = Column(String(20), nullable=False) artifact_type = Column(String(20), nullable=False) file_name = Column(String(260), nullable=False) file_path = Column(String(800), nullable=False) sha256 = Column(String(64), nullable=False) size = Column(Integer, nullable=False) created_at = Column(DateTime, nullable=False, default=datetime.utcnow)