import uuid from datetime import datetime, timezone from typing import Optional from sqlalchemy import Boolean, DateTime, Float, ForeignKey, Integer, String, Text from sqlalchemy.orm import Mapped, mapped_column, relationship from app.db.session import Base def utcnow(): return datetime.now(timezone.utc) def new_uuid(): return str(uuid.uuid4()) class User(Base): __tablename__ = "users" id: Mapped[str] = mapped_column(String, primary_key=True, default=new_uuid) email: Mapped[str] = mapped_column(String, unique=True, nullable=False, index=True) password_hash: Mapped[str] = mapped_column(String, nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow) assets: Mapped[list["Asset"]] = relationship("Asset", back_populates="owner", cascade="all, delete-orphan") jobs: Mapped[list["Job"]] = relationship("Job", back_populates="owner", cascade="all, delete-orphan") class Asset(Base): __tablename__ = "assets" id: Mapped[str] = mapped_column(String, primary_key=True, default=new_uuid) owner_id: Mapped[str] = mapped_column(String, ForeignKey("users.id"), nullable=False, index=True) asset_type: Mapped[str] = mapped_column(String, nullable=False) mime_type: Mapped[str] = mapped_column(String, nullable=False) original_filename: Mapped[str] = mapped_column(String, nullable=False) storage_path: Mapped[str] = mapped_column(String, nullable=False) thumbnail_path: Mapped[Optional[str]] = mapped_column(String, nullable=True) size_bytes: Mapped[int] = mapped_column(Integer, nullable=False) width: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) height: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) duration_seconds: Mapped[Optional[float]] = mapped_column(Float, nullable=True) is_trashed: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False, index=True) delete_after_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) owner: Mapped["User"] = relationship("User", back_populates="assets") class Job(Base): __tablename__ = "jobs" id: Mapped[str] = mapped_column(String, primary_key=True, default=new_uuid) owner_id: Mapped[str] = mapped_column(String, ForeignKey("users.id"), nullable=False, index=True) mode: Mapped[str] = mapped_column(String, nullable=False) submode: Mapped[Optional[str]] = mapped_column(String, nullable=True) prompt: Mapped[str] = mapped_column(Text, nullable=False) negative_prompt: Mapped[Optional[str]] = mapped_column(Text, nullable=True) status: Mapped[str] = mapped_column(String, nullable=False, default="created", index=True) comfy_prompt_id: Mapped[Optional[str]] = mapped_column(String, nullable=True) workflow_template_name: Mapped[Optional[str]] = mapped_column(String, nullable=True) workflow_template_version: Mapped[Optional[str]] = mapped_column(String, nullable=True) settings_json: Mapped[Optional[str]] = mapped_column(Text, nullable=True) ground_truth_asset_id: Mapped[Optional[str]] = mapped_column(String, ForeignKey("assets.id"), nullable=True) motion_asset_id: Mapped[Optional[str]] = mapped_column(String, ForeignKey("assets.id"), nullable=True) audio_asset_id: Mapped[Optional[str]] = mapped_column(String, ForeignKey("assets.id"), nullable=True) reference_asset_ids_json: Mapped[Optional[str]] = mapped_column(Text, nullable=True) pose_asset_id: Mapped[Optional[str]] = mapped_column(String, ForeignKey("assets.id"), nullable=True) error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow) owner: Mapped["User"] = relationship("User", back_populates="jobs") outputs: Mapped[list["JobOutput"]] = relationship("JobOutput", back_populates="job", cascade="all, delete-orphan") events: Mapped[list["JobEvent"]] = relationship("JobEvent", back_populates="job", cascade="all, delete-orphan") class JobOutput(Base): __tablename__ = "job_outputs" id: Mapped[str] = mapped_column(String, primary_key=True, default=new_uuid) job_id: Mapped[str] = mapped_column(String, ForeignKey("jobs.id"), nullable=False, index=True) output_type: Mapped[str] = mapped_column(String, nullable=False) file_path: Mapped[str] = mapped_column(String, nullable=False) poster_path: Mapped[Optional[str]] = mapped_column(String, nullable=True) metadata_json: Mapped[Optional[str]] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) job: Mapped["Job"] = relationship("Job", back_populates="outputs") class JobEvent(Base): __tablename__ = "job_events" id: Mapped[str] = mapped_column(String, primary_key=True, default=new_uuid) job_id: Mapped[str] = mapped_column(String, ForeignKey("jobs.id"), nullable=False, index=True) event_type: Mapped[str] = mapped_column(String, nullable=False) message: Mapped[Optional[str]] = mapped_column(Text, nullable=True) payload_json: Mapped[Optional[str]] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) job: Mapped["Job"] = relationship("Job", back_populates="events")