library.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. """Library models for file manager functionality."""
  2. from datetime import datetime
  3. from sqlalchemy import JSON, Boolean, DateTime, ForeignKey, Integer, String, Text, func
  4. from sqlalchemy.orm import Mapped, mapped_column, relationship
  5. from backend.app.core.database import Base
  6. class LibraryFolder(Base):
  7. """Folder for organizing library files."""
  8. __tablename__ = "library_folders"
  9. id: Mapped[int] = mapped_column(primary_key=True)
  10. name: Mapped[str] = mapped_column(String(255))
  11. parent_id: Mapped[int | None] = mapped_column(ForeignKey("library_folders.id", ondelete="CASCADE"), nullable=True)
  12. # External folder flags (for folders that point to external paths)
  13. is_external: Mapped[bool] = mapped_column(Boolean, default=False)
  14. external_readonly: Mapped[bool] = mapped_column(Boolean, default=False)
  15. external_show_hidden: Mapped[bool] = mapped_column(Boolean, default=False)
  16. external_path: Mapped[str | None] = mapped_column(String(500), nullable=True)
  17. # Link to project or archive
  18. project_id: Mapped[int | None] = mapped_column(ForeignKey("projects.id", ondelete="SET NULL"), nullable=True)
  19. archive_id: Mapped[int | None] = mapped_column(ForeignKey("print_archives.id", ondelete="SET NULL"), nullable=True)
  20. # Timestamps
  21. created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
  22. updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now())
  23. # Relationships
  24. parent: Mapped["LibraryFolder | None"] = relationship(
  25. "LibraryFolder",
  26. back_populates="children",
  27. remote_side="LibraryFolder.id",
  28. foreign_keys="LibraryFolder.parent_id",
  29. )
  30. children: Mapped[list["LibraryFolder"]] = relationship(
  31. "LibraryFolder",
  32. back_populates="parent",
  33. foreign_keys="LibraryFolder.parent_id",
  34. cascade="all, delete-orphan",
  35. )
  36. files: Mapped[list["LibraryFile"]] = relationship(
  37. back_populates="folder",
  38. cascade="all, delete-orphan",
  39. )
  40. project: Mapped["Project | None"] = relationship()
  41. archive: Mapped["PrintArchive | None"] = relationship()
  42. class LibraryFile(Base):
  43. """File stored in the library."""
  44. __tablename__ = "library_files"
  45. id: Mapped[int] = mapped_column(primary_key=True)
  46. folder_id: Mapped[int | None] = mapped_column(ForeignKey("library_folders.id", ondelete="CASCADE"), nullable=True)
  47. project_id: Mapped[int | None] = mapped_column(ForeignKey("projects.id", ondelete="SET NULL"), nullable=True)
  48. # External file flag
  49. is_external: Mapped[bool] = mapped_column(Boolean, default=False)
  50. # File info
  51. filename: Mapped[str] = mapped_column(String(255)) # Original filename
  52. file_path: Mapped[str] = mapped_column(String(500)) # Storage path
  53. file_type: Mapped[str] = mapped_column(String(10)) # "3mf" or "gcode"
  54. file_size: Mapped[int] = mapped_column(Integer)
  55. file_hash: Mapped[str | None] = mapped_column(String(64)) # SHA256 for duplicate detection
  56. thumbnail_path: Mapped[str | None] = mapped_column(String(500))
  57. # Extracted metadata (from 3MF parser)
  58. file_metadata: Mapped[dict | None] = mapped_column(JSON)
  59. # Usage tracking
  60. print_count: Mapped[int] = mapped_column(Integer, default=0)
  61. last_printed_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
  62. # User notes
  63. notes: Mapped[str | None] = mapped_column(Text, nullable=True)
  64. # Provenance — when the file was imported from an external source (e.g.
  65. # MakerWorld), ``source_type`` identifies the source and ``source_url`` is
  66. # the canonical public URL. Used for "already imported" detection and
  67. # "re-open on MakerWorld" affordances. Index on source_url so the
  68. # dedupe lookup is O(log N).
  69. source_type: Mapped[str | None] = mapped_column(String(32), nullable=True)
  70. source_url: Mapped[str | None] = mapped_column(String(512), nullable=True, index=True)
  71. # User tracking (Issue #206)
  72. created_by_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
  73. # Timestamps
  74. created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
  75. updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now())
  76. # Relationships
  77. folder: Mapped["LibraryFolder | None"] = relationship(back_populates="files")
  78. project: Mapped["Project | None"] = relationship()
  79. created_by: Mapped["User | None"] = relationship()
  80. from backend.app.models.archive import PrintArchive # noqa: E402, F811
  81. from backend.app.models.project import Project # noqa: E402, F811
  82. from backend.app.models.user import User # noqa: E402, F811