# -*- coding: utf-8 -*- """SQLAlchemy 모델. 사용자·콘텐츠(썰박스)·좋아요·댓글·크레딧 원장. castad 컨벤션대로 소셜/크레딧 FK 는 숫자 PK 가 아니라 user_uuid(String36) 기준. 콘텐츠 id 는 생성 잡 폴더명(문자열)을 그대로 PK 로 쓴다. """ from datetime import datetime from sqlalchemy import ( BigInteger, Boolean, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, func, ) from sqlalchemy.orm import Mapped, mapped_column, relationship from .database import Base, TimestampMixin # SQLite 에서 BIGINT 는 rowid 별칭이 안 돼 autoincrement 가 동작하지 않는다 → sqlite 는 INTEGER 로. BIGPK = BigInteger().with_variant(Integer, "sqlite") class User(Base, TimestampMixin): __tablename__ = "user" id: Mapped[int] = mapped_column(BIGPK, primary_key=True, autoincrement=True) user_uuid: Mapped[str] = mapped_column(String(36), unique=True, index=True) nickname: Mapped[str] = mapped_column(String(50), default="사용자") email: Mapped[str | None] = mapped_column(String(255), nullable=True) profile_image_url: Mapped[str | None] = mapped_column(Text, nullable=True) # data URL 또는 외부 URL bio: Mapped[str | None] = mapped_column(String(200), nullable=True) # 한 줄 소개 kakao_id: Mapped[int | None] = mapped_column(BigInteger, unique=True, nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True) is_admin: Mapped[bool] = mapped_column(Boolean, default=False) is_deleted: Mapped[bool] = mapped_column(Boolean, default=False) credits: Mapped[int] = mapped_column(Integer, default=10) last_login_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) class Content(Base, TimestampMixin): """생성된 썰박스 1편. id = output 잡 폴더명.""" __tablename__ = "content" id: Mapped[str] = mapped_column(String(120), primary_key=True) scenario: Mapped[str] = mapped_column(String(20), index=True) # joseon|samgukji|greek title: Mapped[str] = mapped_column(String(200), default="") video: Mapped[str | None] = mapped_column(String(300), nullable=True) # /videos/... url owner_uuid: Mapped[str | None] = mapped_column(String(36), index=True, nullable=True) views: Mapped[int] = mapped_column(Integer, default=0) is_deleted: Mapped[bool] = mapped_column(Boolean, default=False) class Like(Base, TimestampMixin): __tablename__ = "content_like" __table_args__ = (UniqueConstraint("content_id", "user_uuid", name="uq_like_user_content"),) id: Mapped[int] = mapped_column(BIGPK, primary_key=True, autoincrement=True) content_id: Mapped[str] = mapped_column(String(120), ForeignKey("content.id"), index=True) user_uuid: Mapped[str] = mapped_column(String(36), index=True) class Comment(Base, TimestampMixin): """2-depth 댓글(댓글+대댓글). 익명 정책 — user_uuid 는 저장만, 응답에 노출 X.""" __tablename__ = "comment" id: Mapped[int] = mapped_column(BIGPK, primary_key=True, autoincrement=True) content_id: Mapped[str] = mapped_column(String(120), ForeignKey("content.id"), index=True) user_uuid: Mapped[str] = mapped_column(String(36), index=True) parent_id: Mapped[int | None] = mapped_column(BigInteger, ForeignKey("comment.id"), nullable=True, index=True) nickname: Mapped[str] = mapped_column(String(50), default="익명") body: Mapped[str] = mapped_column(String(300)) is_deleted: Mapped[bool] = mapped_column(Boolean, default=False) parent = relationship("Comment", remote_side=[id], backref="replies") class CreditTransaction(Base, TimestampMixin): """크레딧 원장. amount +충전/-소모, balance_after 스냅샷.""" __tablename__ = "credit_transaction" id: Mapped[int] = mapped_column(BIGPK, primary_key=True, autoincrement=True) user_uuid: Mapped[str] = mapped_column(String(36), index=True) amount: Mapped[int] = mapped_column(Integer) balance_after: Mapped[int] = mapped_column(Integer) type: Mapped[str] = mapped_column(String(20)) # charge|consume|refund|signup reason: Mapped[str | None] = mapped_column(String(200), nullable=True) class SocialAccount(Base, TimestampMixin): """SNS 연동 계정 (유튜브/인스타 등). OAuth 토큰은 실제 연동 시 채운다.""" __tablename__ = "social_account" __table_args__ = (UniqueConstraint("user_uuid", "platform", name="uq_social_user_platform"),) id: Mapped[int] = mapped_column(BIGPK, primary_key=True, autoincrement=True) user_uuid: Mapped[str] = mapped_column(String(36), index=True) platform: Mapped[str] = mapped_column(String(20)) # youtube|instagram|tiktok|facebook account_name: Mapped[str] = mapped_column(String(120), default="") access_token: Mapped[str | None] = mapped_column(Text, nullable=True) refresh_token: Mapped[str | None] = mapped_column(Text, nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True)