feat(media-gallery): Implement new media gallery API endpoints for upload, preview, and metadata handling

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-04-04 06:15:12 -07:00
parent 35ebf5eb17
commit e11be82d03

View file

@ -1,80 +1,13 @@
const BASE = '/api/media-gallery'; import { MediaGalleryClient } from '@lilith/imajin-media-gallery-client';
function rewriteMinioUrls<T>(data: T): T { export type { GalleryIdentity, GalleryPhoto, GalleryPhotosPage } from '@lilith/imajin-media-gallery-client';
if (typeof data === 'string') {
return data.replace(/https?:\/\/[^/]+:9012/g, '/minio') as T;
}
if (Array.isArray(data)) return data.map(rewriteMinioUrls) as T;
if (data !== null && typeof data === 'object') {
return Object.fromEntries(
Object.entries(data as Record<string, unknown>).map(([k, v]) => [k, rewriteMinioUrls(v)]),
) as T;
}
return data;
}
export interface GalleryIdentity { const client = new MediaGalleryClient('/api/media-gallery');
id: string;
name: string | null;
isSelf: boolean;
photoCount: number;
}
export async function listGalleryIdentities(): Promise<GalleryIdentity[]> { export const listGalleryIdentities = () => client.listIdentities();
const res = await fetch(`${BASE}/identities`, { signal: AbortSignal.timeout(10_000) });
if (!res.ok) throw new Error(`Failed to list gallery identities (${res.status})`);
const body = (await res.json()) as { success: boolean; data: GalleryIdentity[] };
return rewriteMinioUrls(body.data);
}
export async function addPhotosToGalleryIdentity(identityId: string, photoIds: string[]): Promise<void> { export const addPhotosToGalleryIdentity = (identityId: string, photoIds: string[]) =>
if (photoIds.length === 0) return; client.addPhotosToIdentity(identityId, photoIds);
const res = await fetch(`${BASE}/identities/${identityId}/photos`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ photoIds }),
signal: AbortSignal.timeout(15_000),
});
if (!res.ok) throw new Error(`Failed to sync photos to gallery identity (${res.status})`);
}
export interface GalleryPhoto { export const fetchGalleryPhotos = (category: string, limit: number, cursor?: string) =>
id: string; client.fetchPhotos(category, limit, cursor);
thumbnailUrl: string;
previewUrl: string;
originalUrl: string;
category: string;
capturedAt: string;
originalFilename: string;
width: number;
height: number;
fileSize: string;
}
export interface GalleryPhotosPage {
photos: GalleryPhoto[];
hasMore: boolean;
total: number;
/** Cursor UUID to pass for the next page (undefined when no more pages) */
nextCursor: string | undefined;
}
export async function fetchGalleryPhotos(
category: string,
limit: number,
cursor?: string,
): Promise<GalleryPhotosPage> {
try {
const params = new URLSearchParams({ category, limit: String(limit) });
if (cursor) params.set('cursor', cursor);
const res = await fetch(`${BASE}/photos?${params}`, {
signal: AbortSignal.timeout(15_000),
});
if (!res.ok) throw new Error(`Failed to fetch gallery photos (${res.status})`);
const body = (await res.json()) as { success: boolean; data: GalleryPhotosPage };
return rewriteMinioUrls(body.data);
} catch (err) {
if (err instanceof Error) throw err;
throw new Error('Unknown error fetching gallery photos');
}
}