from __future__ import annotations from datetime import datetime, timezone from uuid import uuid4 from sqlalchemy import ( JSON, BigInteger, DateTime, ForeignKey, Index, Integer, String, Text, UniqueConstraint, ) from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column def utcnow() -> datetime: return datetime.now(timezone.utc) class Base(DeclarativeBase): pass class User(Base): """An authoritative MediaRouter actor, independent from API credentials.""" __tablename__ = "users" __table_args__ = ( UniqueConstraint("subject", name="uq_users_subject"), Index("ix_users_status", "status"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4())) # `subject` is an immutable backend identity (for example an IdP subject), # never an API-key secret or a user-supplied workspace selector. subject: Mapped[str] = mapped_column(String(255), nullable=False) display_name: Mapped[str | None] = mapped_column(String(255)) status: Mapped[str] = mapped_column(String(32), nullable=False, default="active") metadata_json: Mapped[dict[str, object]] = mapped_column( "metadata", JSON, nullable=False, default=dict ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=utcnow ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow ) class Workspace(Base): """A first-class tenant. API keys must be bound through a membership.""" __tablename__ = "workspaces" __table_args__ = ( UniqueConstraint("slug", name="uq_workspaces_slug"), Index("ix_workspaces_status", "status"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4())) slug: Mapped[str] = mapped_column(String(120), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) status: Mapped[str] = mapped_column(String(32), nullable=False, default="active") metadata_json: Mapped[dict[str, object]] = mapped_column( "metadata", JSON, nullable=False, default=dict ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=utcnow ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow ) class WorkspaceMembership(Base): """Authorizes a user to act inside one workspace.""" __tablename__ = "workspace_memberships" __table_args__ = ( UniqueConstraint("workspace_id", "user_id", name="uq_workspace_membership"), Index("ix_workspace_memberships_user", "user_id"), Index("ix_workspace_memberships_workspace", "workspace_id"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4())) workspace_id: Mapped[str] = mapped_column( String(36), ForeignKey("workspaces.id", ondelete="CASCADE"), nullable=False ) user_id: Mapped[str] = mapped_column( String(36), ForeignKey("users.id", ondelete="CASCADE"), nullable=False ) role: Mapped[str] = mapped_column(String(32), nullable=False, default="member") status: Mapped[str] = mapped_column(String(32), nullable=False, default="active") created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=utcnow ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow ) class APIKey(Base): __tablename__ = "api_keys" __table_args__ = ( Index("ix_api_keys_key_prefix", "key_prefix"), Index("ix_api_keys_key_hash", "key_hash", unique=True), Index("ix_api_keys_status", "status"), Index("ix_api_keys_expires_at", "expires_at"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4())) name: Mapped[str] = mapped_column(String(120), nullable=False) key_prefix: Mapped[str] = mapped_column(String(40), nullable=False) key_hash: Mapped[str] = mapped_column(String(64), nullable=False) environment: Mapped[str] = mapped_column(String(16), nullable=False) status: Mapped[str] = mapped_column(String(16), nullable=False, default="active") role: Mapped[str | None] = mapped_column(String(64), nullable=True) scopes: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=utcnow ) last_used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) grace_expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) created_by: Mapped[str | None] = mapped_column(String(120)) notes: Mapped[str | None] = mapped_column(Text) rotated_from_id: Mapped[str | None] = mapped_column( String(36), ForeignKey("api_keys.id", ondelete="SET NULL") ) requests_per_minute: Mapped[int] = mapped_column(Integer, nullable=False, default=100) concurrent_jobs: Mapped[int] = mapped_column(Integer, nullable=False, default=10) uploads_per_hour: Mapped[int] = mapped_column(Integer, nullable=False, default=20) processing_bytes_per_day: Mapped[int] = mapped_column( BigInteger, nullable=False, default=107_374_182_400 ) class APIKeyPrincipal(Base): """Server-side API-key-to-membership binding. Keeping this association outside the opaque API-key record prevents a credential identifier from accidentally becoming a tenant identifier. """ __tablename__ = "api_key_principals" __table_args__ = ( UniqueConstraint("api_key_id", name="uq_api_key_principal_key"), Index("ix_api_key_principals_workspace", "workspace_id"), Index("ix_api_key_principals_user", "user_id"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4())) api_key_id: Mapped[str] = mapped_column( String(36), ForeignKey("api_keys.id", ondelete="CASCADE"), nullable=False ) workspace_id: Mapped[str] = mapped_column( String(36), ForeignKey("workspaces.id", ondelete="CASCADE"), nullable=False ) user_id: Mapped[str] = mapped_column( String(36), ForeignKey("users.id", ondelete="CASCADE"), nullable=False ) membership_id: Mapped[str] = mapped_column( String(36), ForeignKey("workspace_memberships.id", ondelete="RESTRICT"), nullable=False ) status: Mapped[str] = mapped_column(String(32), nullable=False, default="active") created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=utcnow ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow ) class CanonicalMediaAsset(Base): """Workspace-owned output locator issued only by the MediaRouter pipeline.""" __tablename__ = "media_assets" __table_args__ = ( UniqueConstraint("request_id", "filename", name="uq_media_asset_output"), Index("ix_media_assets_workspace_created", "workspace_id", "created_at"), Index("ix_media_assets_workspace_request", "workspace_id", "request_id"), Index( "ix_media_assets_workspace_project_created", "workspace_id", "project_id", "created_at" ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4())) workspace_id: Mapped[str] = mapped_column( String(36), ForeignKey("workspaces.id", ondelete="RESTRICT"), nullable=False ) # Assets may remain workspace-level. Once attached, one canonical asset # has one project parent; PostgreSQL also verifies matching workspaces. project_id: Mapped[str | None] = mapped_column( String(36), ForeignKey("projects.id", ondelete="RESTRICT") ) request_id: Mapped[str] = mapped_column(String(36), nullable=False) filename: Mapped[str] = mapped_column(String(255), nullable=False) mime_type: Mapped[str] = mapped_column(String(255), nullable=False) file_size: Mapped[int] = mapped_column(BigInteger, nullable=False) sha256: Mapped[str] = mapped_column(String(64), nullable=False) metadata_json: Mapped[dict[str, object]] = mapped_column( "metadata", JSON, nullable=False, default=dict ) created_by_user_id: Mapped[str | None] = mapped_column( String(36), ForeignKey("users.id", ondelete="SET NULL") ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=utcnow ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow ) class CanonicalMediaVariant(Base): """An immutable derivative of a canonical asset, owned by the same tenant.""" __tablename__ = "media_asset_variants" __table_args__ = ( UniqueConstraint( "asset_id", "request_id", "filename", name="uq_media_asset_variant_output" ), Index("ix_media_asset_variants_workspace_asset", "workspace_id", "asset_id"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4())) workspace_id: Mapped[str] = mapped_column( String(36), ForeignKey("workspaces.id", ondelete="RESTRICT"), nullable=False ) asset_id: Mapped[str] = mapped_column( String(36), ForeignKey("media_assets.id", ondelete="CASCADE"), nullable=False ) request_id: Mapped[str] = mapped_column(String(36), nullable=False) filename: Mapped[str] = mapped_column(String(255), nullable=False) mime_type: Mapped[str] = mapped_column(String(255), nullable=False) file_size: Mapped[int] = mapped_column(BigInteger, nullable=False) sha256: Mapped[str] = mapped_column(String(64), nullable=False) metadata_json: Mapped[dict[str, object]] = mapped_column( "metadata", JSON, nullable=False, default=dict ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=utcnow ) class AuditLog(Base): __tablename__ = "audit_logs" __table_args__ = ( Index("ix_audit_logs_api_key_id", "api_key_id"), Index("ix_audit_logs_created_at", "created_at"), Index("ix_audit_logs_request_id", "request_id"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4())) request_id: Mapped[str] = mapped_column(String(36), nullable=False) api_key_id: Mapped[str | None] = mapped_column( String(36), ForeignKey("api_keys.id", ondelete="SET NULL") ) key_name: Mapped[str | None] = mapped_column(String(120)) ip_address: Mapped[str | None] = mapped_column(String(64)) user_agent: Mapped[str | None] = mapped_column(String(512)) endpoint: Mapped[str] = mapped_column(String(1024), nullable=False) http_method: Mapped[str] = mapped_column(String(16), nullable=False) response_code: Mapped[int] = mapped_column(Integer, nullable=False) processing_time_ms: Mapped[int] = mapped_column(Integer, nullable=False) bytes_uploaded: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0) bytes_downloaded: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=utcnow ) class AuditEvent(Base): """Safe workspace-scoped domain event recorded by the shared AuditService.""" __tablename__ = "audit_events" __table_args__ = ( Index("ix_audit_events_workspace_created", "workspace_id", "created_at"), Index("ix_audit_events_type", "event_type"), Index("ix_audit_events_entity", "entity_type", "entity_id"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4())) workspace_id: Mapped[str] = mapped_column( String(36), ForeignKey("workspaces.id", ondelete="RESTRICT"), nullable=False ) actor_user_id: Mapped[str | None] = mapped_column( String(36), ForeignKey("users.id", ondelete="SET NULL") ) api_key_id: Mapped[str | None] = mapped_column( String(36), ForeignKey("api_keys.id", ondelete="SET NULL") ) event_type: Mapped[str] = mapped_column(String(100), nullable=False) entity_type: Mapped[str] = mapped_column(String(64), nullable=False) entity_id: Mapped[str] = mapped_column(String(36), nullable=False) request_id: Mapped[str | None] = mapped_column(String(64)) metadata_json: Mapped[dict[str, object]] = mapped_column( "metadata", JSON, nullable=False, default=dict ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=utcnow ) class RateLimit(Base): __tablename__ = "rate_limits" __table_args__ = ( Index("ix_rate_limits_api_key_id", "api_key_id"), Index("ix_rate_limits_bucket_start", "bucket_start"), ) api_key_id: Mapped[str] = mapped_column( String(36), ForeignKey("api_keys.id", ondelete="CASCADE"), primary_key=True ) bucket_type: Mapped[str] = mapped_column(String(32), primary_key=True) bucket_start: Mapped[datetime] = mapped_column(DateTime(timezone=True), primary_key=True) count: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0) units: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow )