"""视频域:videos / categories / tags / video_tags / transcode_tasks""" from datetime import datetime from sqlalchemy import BigInteger, DateTime, Index, SmallInteger, String, func from sqlalchemy.dialects.mysql import BIGINT, INTEGER from sqlalchemy.orm import Mapped, mapped_column from app.models.base import Base class Video(Base): __tablename__ = "videos" id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), comment="作者") category_id: Mapped[int] = mapped_column(default=0) series_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True)) episode_no: Mapped[int | None] = mapped_column() title: Mapped[str] = mapped_column(String(100), default="") description: Mapped[str] = mapped_column(String(500), default="") cover_url: Mapped[str] = mapped_column(String(500), default="") video_url: Mapped[str] = mapped_column(String(500), default="", comment="m3u8地址") duration: Mapped[int] = mapped_column(default=0, comment="秒") file_size: Mapped[int] = mapped_column(BigInteger, default=0) width: Mapped[int] = mapped_column(default=0) height: Mapped[int] = mapped_column(default=0) status: Mapped[int] = mapped_column( SmallInteger, default=0, comment="0转码中 1待审核 2已发布 3拒绝 4下架 5转码失败/已删除" ) reject_reason: Mapped[str] = mapped_column(String(200), default="") reviewer_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True)) reviewed_at: Mapped[datetime | None] = mapped_column(DateTime) is_priority: Mapped[int] = mapped_column(SmallInteger, default=0) view_count: Mapped[int] = mapped_column(BigInteger, default=0) like_count: Mapped[int] = mapped_column(default=0) comment_count: Mapped[int] = mapped_column(default=0) favorite_count: Mapped[int] = mapped_column(default=0) published_at: Mapped[datetime | None] = mapped_column(DateTime) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now()) __table_args__ = ( Index("idx_user_status", "user_id", "status"), Index("idx_feed", "status", "published_at"), Index("idx_category", "category_id", "status"), ) class Category(Base): __tablename__ = "categories" id: Mapped[int] = mapped_column(INTEGER(unsigned=True), primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(50)) sort: Mapped[int] = mapped_column(default=0) status: Mapped[int] = mapped_column(SmallInteger, default=1, comment="1启用 0停用") class Tag(Base): __tablename__ = "tags" id: Mapped[int] = mapped_column(INTEGER(unsigned=True), primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(20), unique=True) video_count: Mapped[int] = mapped_column(default=0, comment="冗余") created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) class VideoTag(Base): __tablename__ = "video_tags" id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True) video_id: Mapped[int] = mapped_column(BIGINT(unsigned=True)) tag_id: Mapped[int] = mapped_column(INTEGER(unsigned=True)) __table_args__ = ( Index("uk_video_tag", "video_id", "tag_id", unique=True), Index("idx_tag", "tag_id", "video_id"), ) class TranscodeTask(Base): __tablename__ = "transcode_tasks" id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True) video_id: Mapped[int] = mapped_column(BIGINT(unsigned=True)) source_url: Mapped[str] = mapped_column(String(500)) task_status: Mapped[int] = mapped_column(SmallInteger, default=0, comment="0排队 1转码中 2成功 3失败") priority: Mapped[int] = mapped_column(SmallInteger, default=0, comment="0普通 1优先(会员)") retry_count: Mapped[int] = mapped_column(default=0) error_msg: Mapped[str] = mapped_column(String(500), default="") created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now()) __table_args__ = (Index("idx_pick", "task_status", "priority", "created_at"),) class Series(Base): """系列(合集):把多条视频组织成一个集数列表""" __tablename__ = "series" id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True) title: Mapped[str] = mapped_column(String(100), default="") cover_url: Mapped[str] = mapped_column(String(500), default="") description: Mapped[str] = mapped_column(String(500), default="") author_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True)) category_id: Mapped[int] = mapped_column(default=0) total_episodes: Mapped[int] = mapped_column(default=0) view_count: Mapped[int] = mapped_column(BigInteger, default=0) status: Mapped[int] = mapped_column(SmallInteger, default=1, comment="1正常 0隐藏") created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now()) __table_args__ = ( Index("idx_series_cat_status", "category_id", "status"), Index("idx_series_status_created", "status", "created_at"), ) class CollectionRecord(Base): """采集记录:外部内容源 → 本站系列的映射(幂等导入)""" __tablename__ = "collection_records" id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True) source_key: Mapped[str] = mapped_column(String(50), comment="内容源标识") source_id: Mapped[int] = mapped_column(BIGINT(unsigned=True), comment="源站内容 id") series_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), comment="本站系列 id") video_id: Mapped[int | None] = mapped_column(BIGINT(unsigned=True), comment="本站视频 id(单视频类)") created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) __table_args__ = ( Index("uk_source", "source_key", "source_id", unique=True), Index("idx_record_series", "series_id"), Index("idx_record_video", "video_id"), ) class CollectionSource(Base): """采集源配置:分类名 + 列表URL模板(后台自助添加)""" __tablename__ = "collection_sources" id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(50), comment="分类名") list_url: Mapped[str] = mapped_column(String(500), comment="列表URL模板,{{page}}为页码占位") category_id: Mapped[int] = mapped_column(INTEGER(unsigned=True), comment="本站分类 id") status: Mapped[int] = mapped_column(SmallInteger, default=1, comment="1启用 0停用") last_sync_at: Mapped[datetime | None] = mapped_column(DateTime) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now())