mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-03 12:46:49 +02:00
Host directories (NAS, USB, network shares) can now be mounted
into the File Manager without copying files. Files are indexed
into the database on scan but read directly from their original
location. Supports read-only mode, hidden file filtering, and
automatic thumbnail extraction for 3MF/STL/gcode.
- POST /library/folders/external — create with path validation
- POST /library/folders/{id}/scan — discover/sync files
- Block uploads, moves, and deletes for read-only external folders
- Never delete actual files from external paths (DB-only removal)
- Purple folder icon + info bar with rescan button in UI
- i18n for all 7 languages
- 19 backend + 11 frontend tests
328 lines
7.4 KiB
Python
328 lines
7.4 KiB
Python
"""Pydantic schemas for library (File Manager) functionality."""
|
|
|
|
from datetime import datetime
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
# ============ Folder Schemas ============
|
|
|
|
|
|
class FolderCreate(BaseModel):
|
|
"""Schema for creating a new folder."""
|
|
|
|
name: str = Field(..., min_length=1, max_length=255)
|
|
parent_id: int | None = None
|
|
project_id: int | None = None
|
|
archive_id: int | None = None
|
|
|
|
|
|
class ExternalFolderCreate(BaseModel):
|
|
"""Schema for linking an external folder."""
|
|
|
|
name: str = Field(..., min_length=1, max_length=255)
|
|
external_path: str = Field(..., min_length=1, max_length=500)
|
|
readonly: bool = True
|
|
show_hidden: bool = False
|
|
parent_id: int | None = None
|
|
|
|
|
|
class FolderUpdate(BaseModel):
|
|
"""Schema for updating a folder."""
|
|
|
|
name: str | None = Field(None, min_length=1, max_length=255)
|
|
parent_id: int | None = None
|
|
project_id: int | None = None # 0 to unlink
|
|
archive_id: int | None = None # 0 to unlink
|
|
|
|
|
|
class FolderResponse(BaseModel):
|
|
"""Schema for folder response."""
|
|
|
|
id: int
|
|
name: str
|
|
parent_id: int | None
|
|
project_id: int | None = None
|
|
archive_id: int | None = None
|
|
project_name: str | None = None
|
|
archive_name: str | None = None
|
|
is_external: bool = False
|
|
external_path: str | None = None
|
|
external_readonly: bool = False
|
|
external_show_hidden: bool = False
|
|
file_count: int = 0 # Computed field
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class FolderTreeItem(BaseModel):
|
|
"""Schema for folder tree item (includes children)."""
|
|
|
|
id: int
|
|
name: str
|
|
parent_id: int | None
|
|
project_id: int | None = None
|
|
archive_id: int | None = None
|
|
project_name: str | None = None
|
|
archive_name: str | None = None
|
|
is_external: bool = False
|
|
external_path: str | None = None
|
|
external_readonly: bool = False
|
|
file_count: int = 0
|
|
children: list["FolderTreeItem"] = []
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# ============ File Schemas ============
|
|
|
|
|
|
class FileCreate(BaseModel):
|
|
"""Schema for creating a file entry (internal use after upload)."""
|
|
|
|
filename: str
|
|
file_path: str
|
|
file_type: str
|
|
file_size: int
|
|
file_hash: str | None = None
|
|
thumbnail_path: str | None = None
|
|
metadata: dict | None = None
|
|
folder_id: int | None = None
|
|
project_id: int | None = None
|
|
|
|
|
|
class FileUpdate(BaseModel):
|
|
"""Schema for updating a file."""
|
|
|
|
filename: str | None = Field(None, min_length=1, max_length=255)
|
|
folder_id: int | None = None
|
|
project_id: int | None = None
|
|
notes: str | None = None
|
|
|
|
|
|
class FileDuplicate(BaseModel):
|
|
"""Reference to a duplicate file."""
|
|
|
|
id: int
|
|
filename: str
|
|
folder_id: int | None
|
|
folder_name: str | None
|
|
created_at: datetime
|
|
|
|
|
|
class FileResponse(BaseModel):
|
|
"""Schema for file response."""
|
|
|
|
id: int
|
|
folder_id: int | None
|
|
folder_name: str | None = None
|
|
project_id: int | None
|
|
project_name: str | None = None
|
|
is_external: bool = False
|
|
|
|
filename: str
|
|
file_path: str
|
|
file_type: str
|
|
file_size: int
|
|
file_hash: str | None
|
|
thumbnail_path: str | None
|
|
|
|
metadata: dict | None
|
|
|
|
print_count: int
|
|
last_printed_at: datetime | None
|
|
|
|
notes: str | None
|
|
|
|
# Duplicate detection
|
|
duplicates: list[FileDuplicate] | None = None
|
|
duplicate_count: int = 0
|
|
|
|
# User tracking (Issue #206)
|
|
created_by_id: int | None = None
|
|
created_by_username: str | None = None
|
|
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
# Metadata fields
|
|
print_name: str | None = None
|
|
print_time_seconds: int | None = None
|
|
filament_used_grams: float | None = None
|
|
sliced_for_model: str | None = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class FileListResponse(BaseModel):
|
|
"""Schema for file list item (lighter than full response)."""
|
|
|
|
id: int
|
|
folder_id: int | None
|
|
is_external: bool = False
|
|
filename: str
|
|
file_type: str
|
|
file_size: int
|
|
thumbnail_path: str | None
|
|
print_count: int
|
|
duplicate_count: int = 0
|
|
# User tracking (Issue #206)
|
|
created_by_id: int | None = None
|
|
created_by_username: str | None = None
|
|
created_at: datetime
|
|
|
|
# Key metadata fields for display
|
|
print_name: str | None = None
|
|
print_time_seconds: int | None = None
|
|
filament_used_grams: float | None = None
|
|
sliced_for_model: str | None = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class FileMoveRequest(BaseModel):
|
|
"""Schema for moving files to a folder."""
|
|
|
|
file_ids: list[int]
|
|
folder_id: int | None = None # None = move to root
|
|
|
|
|
|
class FilePrintRequest(BaseModel):
|
|
"""Schema for printing a file from the library.
|
|
|
|
Note: printer_id is passed as a query parameter, not in the body.
|
|
"""
|
|
|
|
# Print options (same as archive reprint)
|
|
plate_id: int | None = None
|
|
plate_name: str | None = None
|
|
ams_mapping: list[int] | None = None
|
|
bed_levelling: bool = True
|
|
flow_cali: bool = False
|
|
vibration_cali: bool = True
|
|
layer_inspect: bool = False
|
|
timelapse: bool = False
|
|
use_ams: bool = True
|
|
|
|
|
|
class FileUploadResponse(BaseModel):
|
|
"""Schema for file upload response."""
|
|
|
|
id: int
|
|
filename: str
|
|
file_type: str
|
|
file_size: int
|
|
thumbnail_path: str | None
|
|
duplicate_of: int | None = None # ID of existing file with same hash
|
|
metadata: dict | None = None
|
|
|
|
|
|
# ============ Bulk Operations ============
|
|
|
|
|
|
class BulkDeleteRequest(BaseModel):
|
|
"""Schema for bulk delete operations."""
|
|
|
|
file_ids: list[int] = []
|
|
folder_ids: list[int] = []
|
|
|
|
|
|
class BulkDeleteResponse(BaseModel):
|
|
"""Schema for bulk delete response."""
|
|
|
|
deleted_files: int
|
|
deleted_folders: int
|
|
|
|
|
|
# ============ Queue Operations ============
|
|
|
|
|
|
class AddToQueueRequest(BaseModel):
|
|
"""Schema for adding library files to the print queue."""
|
|
|
|
file_ids: list[int] = Field(..., min_length=1)
|
|
|
|
|
|
class AddToQueueResult(BaseModel):
|
|
"""Result for a single file added to queue."""
|
|
|
|
file_id: int
|
|
filename: str
|
|
queue_item_id: int
|
|
|
|
|
|
class AddToQueueError(BaseModel):
|
|
"""Error for a file that couldn't be added to queue."""
|
|
|
|
file_id: int
|
|
filename: str
|
|
error: str
|
|
|
|
|
|
class AddToQueueResponse(BaseModel):
|
|
"""Schema for add-to-queue response."""
|
|
|
|
added: list[AddToQueueResult]
|
|
errors: list[AddToQueueError]
|
|
|
|
|
|
# ============ ZIP Extraction ============
|
|
|
|
|
|
class ZipExtractResult(BaseModel):
|
|
"""Result for a single file extracted from ZIP."""
|
|
|
|
filename: str
|
|
file_id: int
|
|
folder_id: int | None = None
|
|
|
|
|
|
class ZipExtractError(BaseModel):
|
|
"""Error for a file that couldn't be extracted."""
|
|
|
|
filename: str
|
|
error: str
|
|
|
|
|
|
class ZipExtractResponse(BaseModel):
|
|
"""Schema for ZIP extraction response."""
|
|
|
|
extracted: int
|
|
folders_created: int
|
|
files: list[ZipExtractResult]
|
|
errors: list[ZipExtractError]
|
|
|
|
|
|
# ============ STL Thumbnail Generation ============
|
|
|
|
|
|
class BatchThumbnailRequest(BaseModel):
|
|
"""Schema for batch STL thumbnail generation request."""
|
|
|
|
file_ids: list[int] | None = None
|
|
folder_id: int | None = None
|
|
all_missing: bool = False
|
|
|
|
|
|
class BatchThumbnailResult(BaseModel):
|
|
"""Result for a single file thumbnail generation."""
|
|
|
|
file_id: int
|
|
filename: str
|
|
success: bool
|
|
error: str | None = None
|
|
|
|
|
|
class BatchThumbnailResponse(BaseModel):
|
|
"""Schema for batch thumbnail generation response."""
|
|
|
|
processed: int
|
|
succeeded: int
|
|
failed: int
|
|
results: list[BatchThumbnailResult]
|