mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-03 04:31:28 +02:00
396 lines
9.5 KiB
Python
396 lines
9.5 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
|
|
# max(folder.updated_at, max(immediate-child file.updated_at)). Used by the
|
|
# File Manager folder tree's "sort by recent activity" mode (#1770) so that
|
|
# adding a file inside a folder bubbles it up — folder.updated_at alone only
|
|
# tracks rename/move events. Recursion across subfolders is intentionally
|
|
# left out to keep the route a single GROUP BY rather than a recursive CTE.
|
|
latest_activity_at: datetime | None = None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class FolderReadmeResponse(BaseModel):
|
|
"""Markdown sidebar payload for a folder (#1268).
|
|
|
|
``filename`` is the on-disk name (so the UI can show "README.md") and
|
|
``content`` is the raw markdown — the FE renders it. ``truncated`` is
|
|
True when the source file was clipped at the size cap.
|
|
"""
|
|
|
|
filename: str
|
|
content: str
|
|
truncated: bool
|
|
|
|
|
|
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
|
|
# See FolderResponse.latest_activity_at — #1770 folder sort source.
|
|
latest_activity_at: datetime | None = None
|
|
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 TagSummary(BaseModel):
|
|
"""Compact tag projection — embedded in file listings (#1268)."""
|
|
|
|
id: int
|
|
name: str
|
|
|
|
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
|
|
|
|
# Tags assigned to this file (#1268). Empty list when the file has none —
|
|
# never null, so the FE can iterate without a guard.
|
|
tags: list[TagSummary] = []
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# ============ Tag Schemas (#1268) ============
|
|
|
|
|
|
class TagResponse(BaseModel):
|
|
"""Tag with the count of files currently using it."""
|
|
|
|
id: int
|
|
name: str
|
|
file_count: int
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class TagCreate(BaseModel):
|
|
"""Create a new tag (catalog row)."""
|
|
|
|
name: str = Field(..., min_length=1, max_length=64)
|
|
|
|
|
|
class TagUpdate(BaseModel):
|
|
"""Rename a tag. ``name`` is required — there's nothing else to update."""
|
|
|
|
name: str = Field(..., min_length=1, max_length=64)
|
|
|
|
|
|
class TagBulkAssignRequest(BaseModel):
|
|
"""Bulk tag assignment payload.
|
|
|
|
``action='add'`` → append tags to every listed file (idempotent on dup).
|
|
``action='remove'`` → strip the listed tags from every listed file.
|
|
``action='replace'`` → REPLACE the tag set on every listed file with the
|
|
exact set in ``tag_ids`` (omitting tag_ids clears
|
|
them all).
|
|
"""
|
|
|
|
file_ids: list[int] = Field(..., min_length=1)
|
|
tag_ids: list[int] = Field(default_factory=list)
|
|
action: str = Field("add", pattern="^(add|remove|replace)$")
|
|
|
|
|
|
class TagBulkAssignResponse(BaseModel):
|
|
"""Result of a bulk-assign call."""
|
|
|
|
files_updated: int
|
|
associations_added: int
|
|
associations_removed: int
|
|
|
|
|
|
class FileMoveRequest(BaseModel):
|
|
"""Schema for moving files to a folder."""
|
|
|
|
file_ids: list[int]
|
|
folder_id: int | None = None # None = move to root
|
|
|
|
|
|
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]
|