from __future__ import annotations from datetime import UTC, datetime from sqlalchemy import JSON, Boolean, DateTime, Integer, String, Text from sqlalchemy.orm import Mapped, mapped_column from app.models.base import Base def utc_now() -> datetime: return datetime.now(UTC) class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(primary_key=True) username: Mapped[str] = mapped_column(String(100), unique=True, index=True) password_hash: Mapped[str] = mapped_column(String(255)) role: Mapped[str] = mapped_column(String(30), default="administrator") disabled: Mapped[bool] = mapped_column(Boolean, default=False) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) class SessionRecord(Base): __tablename__ = "sessions" id: Mapped[str] = mapped_column(String(64), primary_key=True) user_id: Mapped[int] = mapped_column(Integer, index=True) csrf_token: Mapped[str] = mapped_column(String(64)) expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True)) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) class ApiToken(Base): __tablename__ = "api_tokens" id: Mapped[int] = mapped_column(primary_key=True) label: Mapped[str] = mapped_column(String(120)) token_hash: Mapped[str] = mapped_column(String(255)) role: Mapped[str] = mapped_column(String(30), default="operator") created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) class Setting(Base): __tablename__ = "settings" key: Mapped[str] = mapped_column(String(120), primary_key=True) value: Mapped[dict[str, object]] = mapped_column(JSON, default=dict) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) class FixtureSource(Base): __tablename__ = "fixture_sources" id: Mapped[int] = mapped_column(primary_key=True) manufacturer_key: Mapped[str] = mapped_column(String(100)) fixture_key: Mapped[str] = mapped_column(String(150)) schema_ref: Mapped[str] = mapped_column(String(255)) source_url: Mapped[str] = mapped_column(String(255)) payload: Mapped[dict[str, object]] = mapped_column(JSON) payload_hash: Mapped[str] = mapped_column(String(64)) imported_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) class FixtureDefinition(Base): __tablename__ = "fixture_definitions" id: Mapped[int] = mapped_column(primary_key=True) slug: Mapped[str] = mapped_column(String(160), unique=True, index=True) manufacturer: Mapped[str] = mapped_column(String(120)) model: Mapped[str] = mapped_column(String(160)) short_name: Mapped[str | None] = mapped_column(String(120), nullable=True) categories: Mapped[list[str]] = mapped_column(JSON, default=list) normalized_data: Mapped[dict[str, object]] = mapped_column(JSON) source_id: Mapped[int | None] = mapped_column(Integer, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) class FixtureInstance(Base): __tablename__ = "fixture_instances" id: Mapped[int] = mapped_column(primary_key=True) universe: Mapped[int] = mapped_column(Integer, default=1) name: Mapped[str] = mapped_column(String(160)) definition_id: Mapped[int] = mapped_column(Integer, index=True) mode_key: Mapped[str] = mapped_column(String(120)) start_address: Mapped[int] = mapped_column(Integer) channel_count: Mapped[int] = mapped_column(Integer) enabled: Mapped[bool] = mapped_column(Boolean, default=True) group_names: Mapped[list[str]] = mapped_column(JSON, default=list) position: Mapped[dict[str, object]] = mapped_column(JSON, default=dict) tags: Mapped[list[str]] = mapped_column(JSON, default=list) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) class Scene(Base): __tablename__ = "scenes" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(160)) slug: Mapped[str] = mapped_column(String(160), unique=True, index=True) color: Mapped[str] = mapped_column(String(20), default="#00e5ff") icon: Mapped[str | None] = mapped_column(String(40), nullable=True) priority: Mapped[int] = mapped_column(Integer, default=10) fade_in_ms: Mapped[int] = mapped_column(Integer, default=500) fade_out_ms: Mapped[int] = mapped_column(Integer, default=500) hold_ms: Mapped[int] = mapped_column(Integer, default=0) master_limit: Mapped[int] = mapped_column(Integer, default=255) values: Mapped[list[dict[str, object]]] = mapped_column(JSON, default=list) targets: Mapped[list[dict[str, object]]] = mapped_column(JSON, default=list) tags: Mapped[list[str]] = mapped_column(JSON, default=list) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) class Effect(Base): __tablename__ = "effects" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(160)) slug: Mapped[str] = mapped_column(String(160), unique=True, index=True) effect_type: Mapped[str] = mapped_column(String(80)) parameters: Mapped[dict[str, object]] = mapped_column(JSON, default=dict) priority: Mapped[int] = mapped_column(Integer, default=40) cooldown_ms: Mapped[int] = mapped_column(Integer, default=0) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) class TriggerMapping(Base): __tablename__ = "trigger_mappings" id: Mapped[int] = mapped_column(primary_key=True) slug: Mapped[str] = mapped_column(String(160), unique=True, index=True) name: Mapped[str] = mapped_column(String(160)) event_type: Mapped[str] = mapped_column(String(60)) action_type: Mapped[str] = mapped_column(String(30)) action_slug: Mapped[str] = mapped_column(String(160)) config: Mapped[dict[str, object]] = mapped_column(JSON, default=dict) active: Mapped[bool] = mapped_column(Boolean, default=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) class TriggerEvent(Base): __tablename__ = "trigger_events" id: Mapped[int] = mapped_column(primary_key=True) slug: Mapped[str] = mapped_column(String(160), index=True) event_id: Mapped[str | None] = mapped_column(String(160), nullable=True) status: Mapped[str] = mapped_column(String(30), default="queued") payload: Mapped[dict[str, object]] = mapped_column(JSON, default=dict) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) class SystemEvent(Base): __tablename__ = "system_events" id: Mapped[int] = mapped_column(primary_key=True) category: Mapped[str] = mapped_column(String(40)) level: Mapped[str] = mapped_column(String(20)) message: Mapped[str] = mapped_column(Text) payload: Mapped[dict[str, object]] = mapped_column(JSON, default=dict) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) class BackupRecord(Base): __tablename__ = "backups" id: Mapped[int] = mapped_column(primary_key=True) label: Mapped[str] = mapped_column(String(160)) path: Mapped[str] = mapped_column(String(255)) manifest: Mapped[dict[str, object]] = mapped_column(JSON, default=dict) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) class MidiBridge(Base): __tablename__ = "midi_bridges" id: Mapped[int] = mapped_column(primary_key=True) bridge_id: Mapped[str] = mapped_column(String(160), unique=True, index=True) device_name: Mapped[str | None] = mapped_column(String(255), nullable=True) ip_address: Mapped[str | None] = mapped_column(String(64), nullable=True) protocol_version: Mapped[int] = mapped_column(Integer, default=1) online: Mapped[bool] = mapped_column(Boolean, default=False) last_heartbeat_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) last_event_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) last_error: Mapped[str | None] = mapped_column(Text, nullable=True) last_event: Mapped[dict[str, object]] = mapped_column(JSON, default=dict) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) class MidiBridgeToken(Base): __tablename__ = "midi_bridge_tokens" id: Mapped[int] = mapped_column(primary_key=True) label: Mapped[str] = mapped_column(String(120)) token_hash: Mapped[str] = mapped_column(String(64), unique=True, index=True) bridge_id: Mapped[str | None] = mapped_column(String(160), nullable=True) scopes: Mapped[list[str]] = mapped_column(JSON, default=list) last_used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) class MidiMapping(Base): __tablename__ = "midi_mappings" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(160)) enabled: Mapped[bool] = mapped_column(Boolean, default=True) bridge_id: Mapped[str | None] = mapped_column(String(160), nullable=True) device_name: Mapped[str | None] = mapped_column(String(255), nullable=True) message_type: Mapped[str] = mapped_column(String(40)) channel: Mapped[int | None] = mapped_column(Integer, nullable=True) number: Mapped[int] = mapped_column(Integer) action: Mapped[str] = mapped_column(String(60)) target_type: Mapped[str] = mapped_column(String(40)) target_id: Mapped[str | None] = mapped_column(String(160), nullable=True) mode: Mapped[str] = mapped_column(String(40), default="trigger") minimum_value: Mapped[int] = mapped_column(Integer, default=0) maximum_value: Mapped[int] = mapped_column(Integer, default=127) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)