from __future__ import annotations from datetime import datetime from sqlalchemy import BigInteger, DateTime, Float, ForeignKey, Index, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column from app.models.base import Base, SoftDeleteMixin, TimestampMixin class UploadResource(Base, TimestampMixin, SoftDeleteMixin): """统一上传资源账本。 记录用户直接上传、模块上传和拆镜切片文件,不建立 ORM relationship, 通过 source_model + source_id 进行低耦合绑定。 """ __tablename__ = "upload_resources" __table_args__ = ( UniqueConstraint("storage_path", name="uq_upload_resources_storage_path"), Index("ix_upload_resources_user_module_type", "user_id", "module", "resource_type", "deleted_at"), Index("ix_upload_resources_user_bind", "user_id", "bind_status", "deleted_at"), Index("ix_upload_resources_history_lookup", "user_id", "resource_type", "bind_status", "delete_policy", "deleted_at", "created_at"), Index("ix_upload_resources_source", "source_model", "source_id", "deleted_at"), Index("ix_upload_resources_url", "resource_url"), Index("ix_upload_resources_created", "created_at"), Index("ix_upload_resources_file_delete_status", "file_delete_status"), Index("ix_upload_resources_physical_deleted", "physical_deleted_at"), ) id: Mapped[str] = mapped_column(String(32), primary_key=True) user_id: Mapped[str] = mapped_column( String(32), ForeignKey("users.id", ondelete="CASCADE"), index=True, nullable=False ) module: Mapped[str] = mapped_column(String(64), index=True, nullable=False) resource_type: Mapped[str] = mapped_column(String(32), index=True, nullable=False) resource_url: Mapped[str] = mapped_column(Text, nullable=False) storage_path: Mapped[str] = mapped_column(Text, nullable=False) file_name: Mapped[str | None] = mapped_column(String(255), nullable=True) file_ext: Mapped[str | None] = mapped_column(String(32), nullable=True) mime_type: Mapped[str | None] = mapped_column(String(128), nullable=True) file_size_bytes: Mapped[int] = mapped_column(BigInteger, default=0, server_default="0", nullable=False) duration_seconds: Mapped[float | None] = mapped_column(Float, nullable=True) duration_source: Mapped[str | None] = mapped_column(String(32), nullable=True) width: Mapped[int | None] = mapped_column(BigInteger, nullable=True) height: Mapped[int | None] = mapped_column(BigInteger, nullable=True) source_model: Mapped[str | None] = mapped_column(String(64), index=True, nullable=True) source_id: Mapped[str | None] = mapped_column(String(32), index=True, nullable=True) source_module: Mapped[str | None] = mapped_column(String(64), nullable=True) bind_status: Mapped[str] = mapped_column(String(32), default="pending", server_default="pending", index=True, nullable=False) delete_policy: Mapped[str] = mapped_column(String(32), default="user_deletable", server_default="user_deletable", index=True, nullable=False) created_by: Mapped[str] = mapped_column(String(32), default="api", server_default="api", index=True, nullable=False) metadata_json: Mapped[str | None] = mapped_column(Text, nullable=True) capacity_released_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True) physical_deleted_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) file_delete_status: Mapped[str] = mapped_column( String(32), default="active", server_default="active", nullable=False, ) file_delete_error: Mapped[str | None] = mapped_column( Text, nullable=True, )