"""Folder and File models for the DMS plugin.""" from __future__ import annotations import uuid from datetime import datetime from typing import Any from pgvector.sqlalchemy import Vector from sqlalchemy import DateTime, ForeignKey, Index, Integer, String, Text, UniqueConstraint from sqlalchemy.dialects.postgresql import TSVECTOR from sqlalchemy.dialects.postgresql import UUID as PGUUID from sqlalchemy.orm import Mapped, mapped_column from app.core.db import Base, TenantMixin from app.models.owned_mixin import OwnedMixin class Folder(Base, TenantMixin, OwnedMixin): """Folder entity — hierarchical, tenant-scoped, soft-deletable.""" __tablename__ = "folders" __table_args__ = ( UniqueConstraint( "tenant_id", "name", "parent_id", name="uq_folders_tenant_name_parent", # Partial uniqueness only for non-deleted folders is handled at app level ), Index("ix_folders_parent", "parent_id"), Index("ix_folders_tenant", "tenant_id"), ) id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) name: Mapped[str] = mapped_column(String(255), nullable=False) parent_id: Mapped[uuid.UUID | None] = mapped_column( PGUUID(as_uuid=True), ForeignKey("folders.id", ondelete="CASCADE"), nullable=True, ) created_by: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False) deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) class File(Base, TenantMixin, OwnedMixin): """File entity — stored on disk, tenant-scoped, soft-deletable.""" __tablename__ = "files" indexed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) content_tsv: Mapped[Any] = mapped_column(TSVECTOR, nullable=True) content_text: Mapped[str | None] = mapped_column(Text, nullable=True) embedding: Mapped[Any] = mapped_column(Vector(768), nullable=True) __table_args__ = ( Index("ix_files_folder", "folder_id"), Index("ix_files_tenant", "tenant_id"), Index("ix_files_name", "name"), ) id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) name: Mapped[str] = mapped_column(String(255), nullable=False) folder_id: Mapped[uuid.UUID | None] = mapped_column( PGUUID(as_uuid=True), ForeignKey("folders.id", ondelete="SET NULL"), nullable=True, ) uploaded_by: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False) mime_type: Mapped[str] = mapped_column(String(255), nullable=False) size_bytes: Mapped[int] = mapped_column(Integer, nullable=False) storage_path: Mapped[str] = mapped_column(String(1024), nullable=False) content_hash: Mapped[str | None] = mapped_column(String(64), nullable=True) deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)