Files
mule-image/backend/app/schemas/photos.py
dtoro 997e11db78 refactor: rename trash to discard end-to-end
User-facing labels and code now use "discard" (verb) and "Discarded"
(state/view label) instead of "trash" / "Trashed". The DB column names
stay (is_trashed / trashed_at) so no migration is required — only the
SQLAlchemy attribute names are renamed via Column('old_name', ...).

Backend
- Photo model: is_discarded / discarded_at attributes (DB columns
  unchanged).
- PhotoBase / PhotoResponse / PhotoUpdate schemas use the new field
  names.
- Photos list endpoint: is_discarded query param, filter logic.
- DELETE /photos/{id} now sets is_discarded; success message updated.
- Bulk action 'trash' renamed to 'discard'.
- backend/app/routers/trash.py renamed to discard.py with renamed
  functions and route prefix /api/v1/discard.
- main.py imports and mounts the discard router.
- tasks/scan.py marks missing files as is_discarded.

Frontend
- Photo TS type: is_discarded.
- PhotoThumbnail: shows the trash-can icon when is_discarded.
- RightSidebar: button label "Discard"; mutation field name; local
  variable rename.
- TopBar: discardPhotosMutation and "Discard" button; toast text
  "Discarded".
- LeftSidebar: virtual node id 'discarded' / label "Discarded".
- FilterBar / filterStore / useFilterUrlSync: FlagFilter enum value
  'trashed' → 'discarded'; backend param key is_discarded.
- KeyboardHints: X label "Discard".
- useKeyboardShortcuts: PhotoUpdate field rename, X handler.
- api.ts: /trash routes → /discard, trash export → discard,
  bulkUpdate trash field → discard.

Out of scope (intentional): the docker-compose trash_data volume,
backend/Dockerfile mkdir /data/trash, config.py TrashSettings, and
the spec doc — all unused since soft-discard, and renaming them is
churn for no benefit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 22:54:31 +02:00

70 lines
2.2 KiB
Python

"""
Pydantic schemas for photos
"""
from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any
from datetime import datetime
class PhotoBase(BaseModel):
"""Base photo schema"""
filename: str
media_type: str
original_format: Optional[str] = None
width: Optional[int] = None
height: Optional[int] = None
file_size: Optional[int] = None
taken_at: Optional[datetime] = None
taken_at_source: Optional[str] = None
user_title: Optional[str] = None
user_notes: Optional[str] = None
rating: int = 0
color_label: Optional[str] = None
is_picked: bool = False
class PhotoResponse(PhotoBase):
"""Photo response schema"""
id: str
filepath: str
folder_id: Optional[str] = None
file_hash: Optional[str] = None
added_at: datetime
updated_at: Optional[datetime] = None
is_discarded: bool = False
discarded_at: Optional[datetime] = None
thumb_small: Optional[str] = None
thumb_medium: Optional[str] = None
thumb_large: Optional[str] = None
processing_status: str = 'pending'
processing_error: Optional[str] = None
exif_json: Optional[str] = None
is_duplicate: bool = False
live_photo_video_id: Optional[str] = None
# tags: List[Dict[str, Any]] = [] # TODO: Enable when using eager loading
class Config:
orm_mode = True
from_attributes = True
class PhotoUpdate(BaseModel):
"""Photo update schema"""
user_title: Optional[str] = None
user_notes: Optional[str] = None
rating: Optional[int] = Field(None, ge=0, le=5)
color_label: Optional[str] = None
is_picked: Optional[bool] = None
is_discarded: Optional[bool] = None
taken_at: Optional[datetime] = None
class PhotoListResponse(BaseModel):
"""Photo list response with pagination"""
photos: List[PhotoResponse]
total: int
page: int
per_page: int
pages: int
class BulkAction(BaseModel):
"""Bulk action on photos"""
ids: List[str]
action: str # 'discard', 'restore', 'delete_permanent', 'move', 'copy', 'add_tag', 'remove_tag', 'set_rating', 'set_color', 'pick'
value: Optional[Any] = None # For actions that need a value (rating, color, tag_id, folder_id)