from datetime import datetime, timezone from sqlalchemy import Boolean, Column, DateTime, Float, ForeignKey, Integer, String, Text, JSON, UniqueConstraint, create_engine from sqlalchemy.orm import declarative_base, relationship Base = declarative_base() class UserModel(Base): __tablename__ = "users" id = Column(String(36), primary_key=True) email = Column(String(255), nullable=False, unique=True, index=True) username = Column(String(100), nullable=True, unique=True, index=True) display_name = Column(String(255), nullable=False) password_hash = Column(String(255), nullable=False) email_verified = Column(Boolean, nullable=False, default=False) email_verification_token = Column(String(255), nullable=True) password_reset_token = Column(String(255), nullable=True) password_reset_expires_at = Column(DateTime, nullable=True) last_login_at = Column(DateTime, nullable=True) last_login_ip = Column(String(50), nullable=True) # 订阅相关字段 (移到 User 级别) subscription_plan = Column(String(20), nullable=False, default="free") subscription_status = Column(String(20), nullable=False, default="active") subscription_expires_at = Column(DateTime, nullable=True) # 配额限制 (移到 User 级别) max_projects = Column(Float, nullable=False, default=3) max_storage_gb = Column(Float, nullable=False, default=10) used_storage_gb = Column(Float, nullable=False, default=0.0) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) # relationships projects = relationship("ProjectModel", back_populates="owner", cascade="all, delete-orphan") class ProjectModel(Base): __tablename__ = "projects" id = Column(String(32), primary_key=True) owner_user_id = Column(String(32), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True) name = Column(String(100), nullable=False) description = Column(Text, nullable=False, default="") shared_users = Column(JSON, nullable=False, default=list) # 被共享的用户 ID 列表 created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) # relationships owner = relationship("UserModel", back_populates="projects") asset_libraries = relationship("AssetLibraryModel", back_populates="project", cascade="all, delete-orphan") assets = relationship("AssetModel", back_populates="project", cascade="all, delete-orphan") project_titles = relationship("ProjectTitleModel", back_populates="project", cascade="all, delete-orphan") edit_templates = relationship("EditTemplateModel", back_populates="project", cascade="all, delete-orphan") edit_plans = relationship("EditPlanModel", back_populates="project", cascade="all, delete-orphan") ingest_jobs = relationship("IngestJobModel", back_populates="project", cascade="all, delete-orphan") classification_jobs = relationship("ClassificationJobModel", back_populates="project", cascade="all, delete-orphan") generation_tasks = relationship("GenerationTaskModel", back_populates="project", cascade="all, delete-orphan") generated_videos = relationship("GeneratedVideoModel", back_populates="project", cascade="all, delete-orphan") tasks = relationship("TaskModel", back_populates="project", cascade="all, delete-orphan") milestones = relationship("MilestoneModel", back_populates="project", cascade="all, delete-orphan") task_issues = relationship("TaskIssueModel", back_populates="project", cascade="all, delete-orphan") class AssetLibraryModel(Base): __tablename__ = "asset_libraries" id = Column(String(36), primary_key=True) project_id = Column(String(36), ForeignKey("projects.id", ondelete="CASCADE"), nullable=True, index=True) name = Column(String(255), nullable=False) kind = Column(String(20), nullable=False, index=True) asset_count = Column(Float, nullable=False, default=0) total_size = Column(Float, nullable=False, default=0) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) # relationships project = relationship("ProjectModel", back_populates="asset_libraries") assets = relationship("AssetModel", back_populates="asset_library") class AssetModel(Base): __tablename__ = "assets" id = Column(String(36), primary_key=True) project_id = Column(String(36), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True) asset_library_id = Column(String(36), ForeignKey("asset_libraries.id", ondelete="CASCADE"), nullable=False, index=True) name = Column(String(500), nullable=False) file_type = Column(String(20), nullable=False, index=True) # storage_key: OSS 对象键(相对路径),用于内部存储和操作 storage_key = Column(String(255), nullable=False) # file_size: 文件大小(字节),使用 Integer 类型以确保精确性 file_size = Column(Integer, nullable=False) # file_url: 完整可访问的 URL,用于客户端直接访问文件 file_url = Column(String(1000), nullable=False) thumbnail_url = Column(String(1000), nullable=True) duration = Column(Float, nullable=True) width = Column(Float, nullable=True) height = Column(Float, nullable=True) fps = Column(Float, nullable=True) codec = Column(String(50), nullable=True) status = Column(String(20), nullable=False, default="uploading", index=True) classification_status = Column(String(20), nullable=False, default="pending", index=True) classification_result = Column(Text, nullable=True) quality_score = Column(Float, nullable=True) uploaded_by_user_id = Column(String(36), ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc), index=True) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) # relationships project = relationship("ProjectModel", back_populates="assets") asset_library = relationship("AssetLibraryModel", back_populates="assets") uploaded_by = relationship("UserModel") class ProjectTitleModel(Base): __tablename__ = "project_titles" id = Column(String(36), primary_key=True) project_id = Column(String(36), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True) text = Column(String(200), nullable=False) category = Column(String(50), nullable=False, default="default", index=True) favorite = Column(Boolean, nullable=False, default=False, index=True) usage_count = Column(Integer, nullable=False, default=0) is_active = Column(Boolean, nullable=False, default=True) created_by_user_id = Column(String(36), ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) # relationships project = relationship("ProjectModel", back_populates="project_titles") created_by = relationship("UserModel") class EditTemplateModel(Base): __tablename__ = "edit_templates" id = Column(String(32), primary_key=True) project_id = Column(String(32), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True) name = Column(String(120), nullable=False) description = Column(Text, nullable=False, default="") target_duration = Column(Float, nullable=False, default=30) clip_count = Column(Integer, nullable=False, default=3) is_active = Column(Boolean, nullable=False, default=True) created_by_user_id = Column(String(32), ForeignKey("users.id", ondelete="SET NULL"), nullable=True) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) # relationships project = relationship("ProjectModel", back_populates="edit_templates") created_by = relationship("UserModel") edit_plans = relationship("EditPlanModel", back_populates="template") class EditPlanModel(Base): __tablename__ = "edit_plans" id = Column(String(32), primary_key=True) project_id = Column(String(32), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True) template_id = Column(String(32), ForeignKey("edit_templates.id", ondelete="CASCADE"), nullable=False, index=True) asset_library_id = Column(String(32), ForeignKey("asset_libraries.id", ondelete="CASCADE"), nullable=False, index=True) title_id = Column(String(32), ForeignKey("project_titles.id", ondelete="SET NULL"), nullable=True) editing_mode = Column(String(20), nullable=True, default=None, index=True) status = Column(String(20), nullable=False, default="draft", index=True) summary = Column(Text, nullable=False, default="") created_by_user_id = Column(String(32), ForeignKey("users.id", ondelete="SET NULL"), nullable=True) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) # relationships project = relationship("ProjectModel", back_populates="edit_plans") template = relationship("EditTemplateModel", back_populates="edit_plans") asset_library = relationship("AssetLibraryModel") title = relationship("ProjectTitleModel") created_by = relationship("UserModel") clips = relationship("EditPlanClipModel", back_populates="edit_plan", cascade="all, delete-orphan") class EditPlanClipModel(Base): __tablename__ = "edit_plan_clips" id = Column(String(32), primary_key=True) edit_plan_id = Column(String(32), ForeignKey("edit_plans.id", ondelete="CASCADE"), nullable=False, index=True) asset_id = Column(String(32), ForeignKey("assets.id", ondelete="CASCADE"), nullable=False, index=True) sequence = Column(Integer, nullable=False) start_time = Column(Float, nullable=False, default=0) duration = Column(Float, nullable=False, default=0) reason = Column(Text, nullable=False, default="") layer = Column(String(20), nullable=False, default="main") # relationships edit_plan = relationship("EditPlanModel", back_populates="clips") asset = relationship("AssetModel") class IngestJobModel(Base): __tablename__ = "ingest_jobs" id = Column(String(32), primary_key=True) project_id = Column(String(32), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True) library_id = Column(String(32), ForeignKey("asset_libraries.id", ondelete="CASCADE"), nullable=False, index=True) storage_key = Column(String(255), nullable=False) status = Column(String(20), nullable=False, default="pending") error_message = Column(Text, nullable=False, default="") result_asset_id = Column(String(32), ForeignKey("assets.id", ondelete="SET NULL"), nullable=True) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) # relationships project = relationship("ProjectModel", back_populates="ingest_jobs") library = relationship("AssetLibraryModel") result_asset = relationship("AssetModel") class ClassificationJobModel(Base): __tablename__ = "classification_jobs" id = Column(String(32), primary_key=True) project_id = Column(String(32), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True) asset_id = Column(String(32), ForeignKey("assets.id", ondelete="CASCADE"), nullable=False, index=True) status = Column(String(20), nullable=False, default="pending") classification = Column(String(50), nullable=False, default="") confidence = Column(Float, nullable=False, default=0.0) error_message = Column(Text, nullable=False, default="") created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) # relationships project = relationship("ProjectModel", back_populates="classification_jobs") asset = relationship("AssetModel") class GenerationTaskModel(Base): __tablename__ = "generation_tasks" id = Column(String(32), primary_key=True) project_id = Column(String(32), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True) strategy_id = Column(String(32), nullable=False, default="") asset_library_id = Column(String(32), ForeignKey("asset_libraries.id", ondelete="CASCADE"), nullable=False, index=True) voice_library_id = Column(String(32), nullable=False, default="") edit_plan_id = Column(String(32), ForeignKey("edit_plans.id", ondelete="SET NULL"), nullable=True, index=True) editing_mode = Column(String(20), nullable=False, default="one_take", index=True) # 剪辑模式: one_take, pip, voice_over, voice_pip status = Column(String(20), nullable=False, default="pending", index=True) progress = Column(Float, nullable=False, default=0.0) result_count = Column(Float, nullable=False, default=0) error_message = Column(Text, nullable=False, default="") started_at = Column(DateTime, nullable=True) completed_at = Column(DateTime, nullable=True) created_by_user_id = Column(String(32), ForeignKey("users.id", ondelete="SET NULL"), nullable=True) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) # relationships project = relationship("ProjectModel", back_populates="generation_tasks") asset_library = relationship("AssetLibraryModel") edit_plan = relationship("EditPlanModel") created_by = relationship("UserModel") generated_videos = relationship("GeneratedVideoModel", back_populates="generation_task", cascade="all, delete-orphan") class GeneratedVideoModel(Base): __tablename__ = "generated_videos" id = Column(String(32), primary_key=True) project_id = Column(String(32), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True) generation_task_id = Column(String(32), ForeignKey("generation_tasks.id", ondelete="CASCADE"), nullable=False, index=True) name = Column(String(255), nullable=False) # file_url: 完整可访问的 URL,用于客户端直接访问视频 file_url = Column(String(1000), nullable=False) # file_size: 文件大小(字节),使用 Integer 类型以确保精确性 file_size = Column(Integer, nullable=False) duration = Column(Float, nullable=False) thumbnail_url = Column(String(1000), nullable=True) width = Column(Float, nullable=False) height = Column(Float, nullable=False) fps = Column(Float, nullable=False) status = Column(String(20), nullable=False, default="completed", index=True) review_status = Column(String(20), nullable=False, default="pending_review", index=True) generation_params = Column(Text, nullable=False, default="{}") generated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=True) video_fingerprint = Column(Text, nullable=True) is_duplicate = Column(Boolean, nullable=False, default=False) duplicate_of = Column(String(32), ForeignKey("generated_videos.id", ondelete="SET NULL"), nullable=True) # relationships project = relationship("ProjectModel", back_populates="generated_videos") generation_task = relationship("GenerationTaskModel", back_populates="generated_videos") duplicate_target = relationship("GeneratedVideoModel", remote_side=[id]) class TaskModel(Base): __tablename__ = "tasks" id = Column(String(32), primary_key=True) project_id = Column(String(32), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True) name = Column(String(200), nullable=False) description = Column(Text, nullable=False, default="") status = Column(String(20), nullable=False, default="pending", index=True) priority = Column(String(20), nullable=False, default="medium") parent_task_id = Column(String(32), ForeignKey("tasks.id", ondelete="SET NULL"), nullable=True, index=True) assignee_user_id = Column(String(32), ForeignKey("users.id", ondelete="SET NULL"), nullable=True) progress = Column(Float, nullable=False, default=0.0) planned_start_date = Column(DateTime, nullable=True) planned_end_date = Column(DateTime, nullable=True) actual_start_date = Column(DateTime, nullable=True) actual_end_date = Column(DateTime, nullable=True) tags_json = Column(Text, nullable=False, default="[]") created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) # relationships project = relationship("ProjectModel", back_populates="tasks") parent_task = relationship("TaskModel", remote_side=[id]) assignee = relationship("UserModel") issues = relationship("TaskIssueModel", back_populates="task", cascade="all, delete-orphan") class MilestoneModel(Base): __tablename__ = "milestones" id = Column(String(32), primary_key=True) project_id = Column(String(32), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True) name = Column(String(200), nullable=False) description = Column(Text, nullable=False, default="") target_date = Column(DateTime, nullable=True) completed = Column(Boolean, nullable=False, default=False) completed_at = Column(DateTime, nullable=True) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) # relationships project = relationship("ProjectModel", back_populates="milestones") class TaskIssueModel(Base): __tablename__ = "task_issues" id = Column(String(32), primary_key=True) task_id = Column(String(32), ForeignKey("tasks.id", ondelete="CASCADE"), nullable=False, index=True) project_id = Column(String(32), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True) title = Column(String(200), nullable=False) description = Column(Text, nullable=False, default="") resolved = Column(Boolean, nullable=False, default=False) resolved_at = Column(DateTime, nullable=True) created_by_user_id = Column(String(32), ForeignKey("users.id", ondelete="SET NULL"), nullable=True) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) # relationships task = relationship("TaskModel", back_populates="issues") project = relationship("ProjectModel", back_populates="task_issues") created_by = relationship("UserModel")