Files
mule-image/frontend/src/services/api.ts
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

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