mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 06:11:45 +00:00
fix(journey): thumbnails, batch add, optimistic delete, shared albums
- Gallery/timeline load thumbnails instead of originals (50-100KB vs 2-5MB) - Batch endpoint for adding multiple provider photos in one request - Optimistic photo deletion — no full page reload on delete - Immich albums include shared albums - Select-all button moved outside scroll container (always visible) - Album tab loads actual album contents via /albums/:id/photos
This commit is contained in:
@@ -115,7 +115,19 @@ router.post('/entries/:entryId/photos', authenticate, upload.array('photos', 10)
|
||||
|
||||
router.post('/entries/:entryId/provider-photos', authenticate, (req: Request, res: Response) => {
|
||||
const authReq = req as AuthRequest;
|
||||
const { provider, asset_id, caption } = req.body || {};
|
||||
const { provider, asset_id, asset_ids, caption } = req.body || {};
|
||||
|
||||
// Batch mode: { provider, asset_ids: string[] }
|
||||
if (Array.isArray(asset_ids) && provider) {
|
||||
const added: any[] = [];
|
||||
for (const id of asset_ids) {
|
||||
const photo = svc.addProviderPhoto(Number(req.params.entryId), authReq.user.id, provider, String(id), caption);
|
||||
if (photo) added.push(photo);
|
||||
}
|
||||
return res.status(201).json({ photos: added, added: added.length });
|
||||
}
|
||||
|
||||
// Single mode (backward compat)
|
||||
if (!provider || !asset_id) return res.status(400).json({ error: 'provider and asset_id required' });
|
||||
const photo = svc.addProviderPhoto(Number(req.params.entryId), authReq.user.id, provider, asset_id, caption);
|
||||
if (!photo) return res.status(403).json({ error: 'Not allowed or duplicate' });
|
||||
|
||||
@@ -258,18 +258,34 @@ export async function listAlbums(
|
||||
if (!creds) return { error: 'Immich not configured', status: 400 };
|
||||
|
||||
try {
|
||||
const resp = await safeFetch(`${creds.immich_url}/api/albums`, {
|
||||
headers: { 'x-api-key': creds.immich_api_key, 'Accept': 'application/json' },
|
||||
signal: AbortSignal.timeout(10000) as any,
|
||||
// Fetch both owned and shared albums
|
||||
const [ownResp, sharedResp] = await Promise.all([
|
||||
safeFetch(`${creds.immich_url}/api/albums`, {
|
||||
headers: { 'x-api-key': creds.immich_api_key, 'Accept': 'application/json' },
|
||||
signal: AbortSignal.timeout(10000) as any,
|
||||
}),
|
||||
safeFetch(`${creds.immich_url}/api/albums?shared=true`, {
|
||||
headers: { 'x-api-key': creds.immich_api_key, 'Accept': 'application/json' },
|
||||
signal: AbortSignal.timeout(10000) as any,
|
||||
}),
|
||||
]);
|
||||
if (!ownResp.ok) return { error: 'Failed to fetch albums', status: ownResp.status };
|
||||
const ownAlbums = await ownResp.json() as any[];
|
||||
const sharedAlbums = sharedResp.ok ? await sharedResp.json() as any[] : [];
|
||||
const seenIds = new Set<string>();
|
||||
const allAlbums = [...ownAlbums, ...sharedAlbums].filter((a: any) => {
|
||||
if (seenIds.has(a.id)) return false;
|
||||
seenIds.add(a.id);
|
||||
return true;
|
||||
});
|
||||
if (!resp.ok) return { error: 'Failed to fetch albums', status: resp.status };
|
||||
const albums = (await resp.json() as any[]).map((a: any) => ({
|
||||
const albums = allAlbums.map((a: any) => ({
|
||||
id: a.id,
|
||||
albumName: a.albumName,
|
||||
assetCount: a.assetCount || 0,
|
||||
startDate: a.startDate,
|
||||
endDate: a.endDate,
|
||||
albumThumbnailAssetId: a.albumThumbnailAssetId,
|
||||
shared: a.shared || a.sharedUsers?.length > 0,
|
||||
}));
|
||||
return { albums };
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user