298 lines
12 KiB
Python
298 lines
12 KiB
Python
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 MobileBuildUploadedFile(Base):
|
|
__tablename__ = "mobile_build_uploaded_files"
|
|
|
|
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)
|
|
file_kind = Column(String(40), nullable=False)
|
|
original_name = Column(String(260), nullable=False)
|
|
stored_path = Column(String(800), nullable=False)
|
|
sha256 = Column(String(64), nullable=False)
|
|
size = Column(Integer, nullable=False)
|
|
uploaded_at = Column(DateTime, nullable=False, default=datetime.utcnow)
|
|
|
|
__table_args__ = (
|
|
UniqueConstraint("request_id", "platform", "file_kind", name="uq_mobile_build_file_kind"),
|
|
)
|
|
|
|
|
|
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)
|