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>
78 lines
3.0 KiB
Python
78 lines
3.0 KiB
Python
"""
|
|
Photo model definition
|
|
"""
|
|
from sqlalchemy import Column, String, Integer, Boolean, DateTime, ForeignKey, Text, Index
|
|
from sqlalchemy.sql import func
|
|
from datetime import datetime
|
|
import uuid
|
|
|
|
from app.database import Base
|
|
|
|
class Photo(Base):
|
|
__tablename__ = 'photos'
|
|
|
|
# Primary key
|
|
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
|
|
# File information
|
|
filepath = Column(String, unique=True, nullable=False)
|
|
filename = Column(String, nullable=False)
|
|
folder_id = Column(String, ForeignKey('folders.id'))
|
|
file_hash = Column(String, index=True) # SHA-256 hash for duplicate detection
|
|
|
|
# Media information
|
|
media_type = Column(String, nullable=False) # 'photo' | 'video' | 'raw' | 'heic'
|
|
original_format = Column(String) # 'CR3', 'NEF', 'HEIC', 'MP4', 'JPEG', etc.
|
|
width = Column(Integer)
|
|
height = Column(Integer)
|
|
file_size = Column(Integer)
|
|
|
|
# Timestamps
|
|
taken_at = Column(DateTime) # from EXIF DateTimeOriginal, fallback to file mtime
|
|
taken_at_source = Column(String) # 'exif' | 'filesystem' | 'manual'
|
|
added_at = Column(DateTime, server_default=func.now())
|
|
updated_at = Column(DateTime, onupdate=func.now())
|
|
|
|
# Discard status. The DB column names stay is_trashed/trashed_at to avoid
|
|
# a migration; only the Python attribute name reflects the rename.
|
|
is_discarded = Column('is_trashed', Boolean, default=False)
|
|
discarded_at = Column('trashed_at', DateTime)
|
|
|
|
# Thumbnail paths
|
|
thumb_small = Column(String) # path to 240px thumb
|
|
thumb_medium = Column(String) # path to 640px thumb
|
|
thumb_large = Column(String) # path to 1280px thumb
|
|
|
|
# Processing status
|
|
processing_status = Column(String, default='pending') # 'pending' | 'processing' | 'completed' | 'failed'
|
|
processing_error = Column(Text)
|
|
|
|
# Metadata
|
|
exif_json = Column(Text) # full EXIF/XMP blob as JSON
|
|
|
|
# User-editable fields
|
|
user_title = Column(String)
|
|
user_notes = Column(Text)
|
|
rating = Column(Integer, default=0) # 0-5 stars
|
|
color_label = Column(String) # 'red'|'orange'|'yellow'|'green'|'blue'|'purple'|NULL
|
|
is_picked = Column(Boolean, default=False)
|
|
# Note: is_rejected was merged into is_discarded (a single soft "discarded"
|
|
# concept). The DB column may still exist on legacy installs but is no
|
|
# longer read or written.
|
|
|
|
# Duplicate detection
|
|
is_duplicate = Column(Boolean, default=False)
|
|
|
|
# Live photo support
|
|
live_photo_video_id = Column(String, ForeignKey('photos.id'))
|
|
|
|
# Indexes for performance
|
|
__table_args__ = (
|
|
Index('ix_photos_taken_at', 'taken_at'),
|
|
Index('ix_photos_folder_id', 'folder_id'),
|
|
Index('ix_photos_is_trashed', 'is_trashed'),
|
|
Index('ix_photos_rating', 'rating'),
|
|
Index('ix_photos_color_label', 'color_label'),
|
|
Index('ix_photos_media_type', 'media_type'),
|
|
Index('ix_photos_processing_status', 'processing_status'),
|
|
) |