from datetime import datetime from typing import TYPE_CHECKING, List, Optional from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, String, UniqueConstraint, func from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database.session import Base if TYPE_CHECKING: from app.comment.models import Comment from app.home.models import Project from app.lyric.models import Lyric from app.song.models import Song from app.user.models import User class Video(Base): """ 영상 결과 테이블 최종 생성된 영상의 결과 URL을 저장합니다. Creatomate 서비스를 통해 이미지와 노래를 결합한 영상 결과입니다. Attributes: id: 고유 식별자 (자동 증가) project_id: 연결된 Project의 id (외래키) lyric_id: 연결된 Lyric의 id (외래키) song_id: 연결된 Song의 id (외래키) task_id: 영상 생성 작업의 고유 식별자 (UUID7 형식) status: 처리 상태 (pending, processing, completed, failed 등) result_movie_url: 생성된 영상 URL (S3, CDN 경로) created_at: 생성 일시 (자동 설정) Relationships: project: 연결된 Project lyric: 연결된 Lyric song: 연결된 Song comments: 영상 댓글 목록 likes: 영상 좋아요 목록 """ __tablename__ = "video" __table_args__ = ( Index("idx_video_task_id", "task_id"), Index("idx_video_project_id", "project_id"), Index("idx_video_lyric_id", "lyric_id"), Index("idx_video_song_id", "song_id"), Index("idx_video_is_deleted", "is_deleted"), { "mysql_engine": "InnoDB", "mysql_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci", }, ) id: Mapped[int] = mapped_column( Integer, primary_key=True, nullable=False, autoincrement=True, comment="고유 식별자", ) project_id: Mapped[int] = mapped_column( Integer, ForeignKey("project.id", ondelete="CASCADE"), nullable=False, comment="연결된 Project의 id", ) lyric_id: Mapped[int] = mapped_column( Integer, ForeignKey("lyric.id", ondelete="CASCADE"), nullable=False, comment="연결된 Lyric의 id", ) song_id: Mapped[int] = mapped_column( Integer, ForeignKey("song.id", ondelete="CASCADE"), nullable=False, comment="연결된 Song의 id", ) task_id: Mapped[str] = mapped_column( String(36), nullable=False, comment="영상 생성 작업 고유 식별자 (UUID7)", ) creatomate_render_id: Mapped[Optional[str]] = mapped_column( String(64), nullable=True, comment="Creatomate API 렌더 ID", ) status: Mapped[str] = mapped_column( String(50), nullable=False, comment="처리 상태 (processing, completed, failed)", ) result_movie_url: Mapped[Optional[str]] = mapped_column( String(2048), nullable=True, comment="생성된 영상 URL", ) is_deleted: Mapped[bool] = mapped_column( Boolean, nullable=False, default=False, comment="소프트 삭제 여부 (True: 삭제됨)", ) created_at: Mapped[datetime] = mapped_column( DateTime, nullable=False, server_default=func.now(), comment="생성 일시", ) # Relationships project: Mapped["Project"] = relationship( "Project", back_populates="videos", ) lyric: Mapped["Lyric"] = relationship( "Lyric", back_populates="videos", ) song: Mapped["Song"] = relationship( "Song", back_populates="videos", ) comments: Mapped[List["Comment"]] = relationship( "Comment", back_populates="video", cascade="all, delete-orphan", lazy="noload", ) reactions: Mapped[List["VideoReaction"]] = relationship( "VideoReaction", back_populates="video", cascade="all, delete-orphan", lazy="noload", ) def __repr__(self) -> str: def truncate(value: str | None, max_len: int = 10) -> str: if value is None: return "None" return (value[:max_len] + "...") if len(value) > max_len else value return ( f"" ) class VideoReaction(Base): """ 영상 반응 테이블 사용자가 영상에 반응(현재는 좋아요)을 남기면 생성, 다시 누르면 삭제(토글). (user_uuid, video_id) 유니크 제약으로 1인 1회 보장. 향후 reaction_type 컬럼 추가로 다양한 반응 종류 확장 가능. """ __tablename__ = "video_reaction" __table_args__ = ( UniqueConstraint("user_uuid", "video_id", name="uq_video_reaction_user_video"), Index("idx_video_reaction_video_id", "video_id"), Index("idx_video_reaction_user_uuid", "user_uuid"), { "mysql_engine": "InnoDB", "mysql_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci", }, ) id: Mapped[int] = mapped_column( Integer, primary_key=True, autoincrement=True, comment="고유 식별자" ) video_id: Mapped[int] = mapped_column( Integer, ForeignKey("video.id", ondelete="CASCADE"), nullable=False, comment="연결된 Video의 id", ) user_uuid: Mapped[str] = mapped_column( String(36), ForeignKey("user.user_uuid", ondelete="CASCADE"), nullable=False, comment="반응한 사용자 UUID", ) created_at: Mapped[datetime] = mapped_column( DateTime, nullable=False, server_default=func.now(), comment="반응 일시", ) video: Mapped["Video"] = relationship("Video", back_populates="reactions") user: Mapped["User"] = relationship("User", back_populates="video_reactions")