from datetime import datetime from typing import List, Optional from sqlmodel import Field, Relationship, SQLModel class User(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) twitch_id: str = Field(unique=True, index=True) twitch_login: str twitch_display_name: str broadcaster_id: str access_token: str refresh_token: str is_admin: bool = False created_at: datetime = Field(default_factory=datetime.utcnow) class MediaFile(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) filename: str original_name: str file_type: str # "audio" | "video" created_at: datetime = Field(default_factory=datetime.utcnow) actions: List["EventAction"] = Relationship(back_populates="media_file") class EventAction(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) event_type: str # "sub" | "gift_sub" | "bits" | "channel_points" | "command" event_detail: Optional[str] = None # command name, reward ID, sub tier, bits minimum action_type: str # "sound" | "video" | "alert" media_file_id: Optional[int] = Field(default=None, foreign_key="mediafile.id") alert_text: Optional[str] = None cooldown_seconds: int = 0 enabled: bool = True created_at: datetime = Field(default_factory=datetime.utcnow) media_file: Optional["MediaFile"] = Relationship(back_populates="actions") class ActivityLog(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) event_type: str username: Optional[str] = None detail: Optional[str] = None action_taken: Optional[str] = None triggered_at: datetime = Field(default_factory=datetime.utcnow) class Settings(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) twitch_channel: str = "" gap_silence_threshold: int = 30 gap_scale_end: int = 120 gap_pitch_scale_end: int = 300 custom_css: str = "" custom_js: str = "" class OverlayLayout(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) # Chat widget — position/size as % of canvas chat_left: float = 72.0 chat_top: float = 5.0 chat_width: float = 27.0 chat_height: float = 88.0 chat_font_size: int = 14 chat_bg_opacity: float = 0.55 chat_text_color: str = "#ffffff" chat_max_messages: int = 20 # Alert widget — position as % of canvas (centered via transform) alert_left: float = 50.0 alert_top: float = 40.0 alert_font_size: int = 24 alert_bg_opacity: float = 0.8 alert_text_color: str = "#ffffff" alert_duration: int = 4