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>
192 lines
4.2 KiB
TypeScript
192 lines
4.2 KiB
TypeScript
import axios from 'axios'
|
|
|
|
const API_BASE_URL = 'http://localhost:8001/api/v1'
|
|
|
|
const api = axios.create({
|
|
baseURL: API_BASE_URL,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
})
|
|
|
|
// Source Folders API
|
|
export const sourceFolders = {
|
|
list: async () => {
|
|
const response = await api.get('/folders')
|
|
return response.data
|
|
},
|
|
|
|
add: async (path: string, recursive: boolean = true) => {
|
|
const response = await api.post('/folders', {
|
|
path,
|
|
recursive,
|
|
watch: false, // Can be made configurable later
|
|
})
|
|
return response.data
|
|
},
|
|
|
|
scan: async (folderId: string) => {
|
|
const response = await api.post(`/folders/${folderId}/scan`)
|
|
return response.data
|
|
},
|
|
|
|
delete: async (folderId: string) => {
|
|
const response = await api.delete(`/folders/${folderId}`)
|
|
return response.data
|
|
},
|
|
}
|
|
|
|
// Photos API
|
|
export const photos = {
|
|
list: async (params?: {
|
|
limit?: number
|
|
offset?: number
|
|
folder_id?: string
|
|
heap_id?: string
|
|
rating?: number
|
|
flag?: string
|
|
}) => {
|
|
const response = await api.get('/photos', { params })
|
|
return response.data
|
|
},
|
|
|
|
get: async (photoId: string) => {
|
|
const response = await api.get(`/photos/${photoId}`)
|
|
return response.data
|
|
},
|
|
|
|
update: async (photoId: string, data: {
|
|
rating?: number
|
|
flag?: string
|
|
user_title?: string
|
|
user_notes?: string
|
|
}) => {
|
|
const response = await api.patch(`/photos/${photoId}`, data)
|
|
return response.data
|
|
},
|
|
|
|
bulkUpdate: async (photoIds: string[], data: {
|
|
rating?: number
|
|
flag?: string
|
|
heap_id?: string
|
|
discard?: boolean
|
|
}) => {
|
|
const response = await api.post('/photos/bulk', {
|
|
photo_ids: photoIds,
|
|
...data,
|
|
})
|
|
return response.data
|
|
},
|
|
|
|
getThumbnailUrl: (photoId: string, size: 'small' | 'medium' | 'large' = 'medium') => {
|
|
return `${API_BASE_URL}/photos/${photoId}/thumb/${size}`
|
|
},
|
|
|
|
getOriginalUrl: (photoId: string) => {
|
|
return `${API_BASE_URL}/photos/${photoId}/original`
|
|
},
|
|
|
|
/** Full-resolution display URL. Backend serves the original for web-safe
|
|
* formats and a transcoded WebP for RAW/HEIC/TIFF. */
|
|
getProxyUrl: (photoId: string) => {
|
|
return `${API_BASE_URL}/photos/${photoId}/proxy`
|
|
},
|
|
}
|
|
|
|
// Library API
|
|
export const library = {
|
|
scan: async () => {
|
|
const response = await api.post('/library/scan')
|
|
return response.data
|
|
},
|
|
|
|
scanStatus: async () => {
|
|
const response = await api.get('/library/scan/status')
|
|
return response.data
|
|
},
|
|
|
|
stats: async () => {
|
|
const response = await api.get('/library/stats')
|
|
return response.data
|
|
},
|
|
}
|
|
|
|
// Heaps API
|
|
export const heaps = {
|
|
list: async () => {
|
|
const response = await api.get('/heaps')
|
|
return response.data
|
|
},
|
|
|
|
create: async (name: string, description?: string) => {
|
|
const response = await api.post('/heaps', {
|
|
name,
|
|
description,
|
|
})
|
|
return response.data
|
|
},
|
|
|
|
update: async (heapId: string, data: {
|
|
name?: string
|
|
description?: string
|
|
}) => {
|
|
const response = await api.patch(`/heaps/${heapId}`, data)
|
|
return response.data
|
|
},
|
|
|
|
delete: async (heapId: string) => {
|
|
const response = await api.delete(`/heaps/${heapId}`)
|
|
return response.data
|
|
},
|
|
}
|
|
|
|
// Tags API
|
|
export const tags = {
|
|
list: async () => {
|
|
const response = await api.get('/tags')
|
|
return response.data
|
|
},
|
|
|
|
create: async (name: string, color?: string) => {
|
|
const response = await api.post('/tags', {
|
|
name,
|
|
color,
|
|
})
|
|
return response.data
|
|
},
|
|
|
|
update: async (tagId: string, data: {
|
|
name?: string
|
|
color?: string
|
|
}) => {
|
|
const response = await api.patch(`/tags/${tagId}`, data)
|
|
return response.data
|
|
},
|
|
|
|
delete: async (tagId: string) => {
|
|
const response = await api.delete(`/tags/${tagId}`)
|
|
return response.data
|
|
},
|
|
}
|
|
|
|
// Discard API
|
|
export const discard = {
|
|
list: async () => {
|
|
const response = await api.get('/discard')
|
|
return response.data
|
|
},
|
|
|
|
restore: async (photoIds: string[]) => {
|
|
const response = await api.post('/discard/restore', {
|
|
photo_ids: photoIds,
|
|
})
|
|
return response.data
|
|
},
|
|
|
|
empty: async () => {
|
|
const response = await api.delete('/discard/empty')
|
|
return response.data
|
|
},
|
|
}
|
|
|
|
export default api |