Files
maziggy e0e597271e ● feat(#1008): library trash bin, admin bulk purge, auto-purge setting
Library files now move to a configurable-retention trash bin on delete
  instead of being hard-deleted from disk (default 30 days). Admins get a
  "Purge old" bulk action on the File Manager with a live preview, plus an
  optional auto-purge setting in Settings → File Manager that runs the same
  operation once per 24h when enabled (default off). Regular users see and
  manage their own trashed files; admins see everyone's. External (linked)
  files bypass trash since their bytes aren't under Bambuddy's control.

  - New `library:purge` permission (admin-only by default)
  - Nullable indexed `deleted_at` column on library_files; dialect-aware
    ALTER TABLE so the column actually gets added on PostgreSQL (raw
    DATETIME is SQLite-only syntax)
  - New `LibraryFile.active()` classmethod; every query site routed through
    it so trashed rows don't leak into listings, print dispatch, MakerWorld
    dedupe, or stats
  - Trash page: select-all + bulk restore/delete, per-row checkboxes, wider
    layout so datetime columns don't clip
  - Auto-purge: 24h throttle via `library_auto_purge_last_run` setting so
    the 15-minute sweeper cadence still runs the purge at most once per day
  - Save toast wired into every trash/auto-purge setting change
  - 17 new backend integration tests (service + routes + auto-purge throttle),
    8 new frontend tests, localised across all 8 UI languages
  - Wiki + website feature entries updated
2026-04-23 15:54:59 +02:00

60 lines
1.3 KiB
Python

"""Schemas for the library trash bin + bulk purge (#1008)."""
from __future__ import annotations
from datetime import datetime
from pydantic import BaseModel, Field
class PurgePreviewRequest(BaseModel):
older_than_days: int = Field(ge=1, le=3650, description="Age threshold in days.")
include_never_printed: bool = True
class PurgePreviewResponse(BaseModel):
count: int
total_bytes: int
sample_filenames: list[str]
older_than_days: int
include_never_printed: bool
class PurgeRequest(BaseModel):
older_than_days: int = Field(ge=1, le=3650)
include_never_printed: bool = True
class PurgeResponse(BaseModel):
moved_to_trash: int
class TrashFile(BaseModel):
id: int
filename: str
file_size: int
thumbnail_path: str | None = None
folder_id: int | None = None
folder_name: str | None = None
created_by_id: int | None = None
created_by_username: str | None = None
deleted_at: datetime
auto_purge_at: datetime
class TrashListResponse(BaseModel):
items: list[TrashFile]
total: int
retention_days: int
class TrashSettings(BaseModel):
retention_days: int = Field(ge=1, le=365)
auto_purge_enabled: bool = False
auto_purge_days: int = Field(default=90, ge=7, le=3650)
auto_purge_include_never_printed: bool = True
class EmptyTrashResponse(BaseModel):
deleted: int