Files
mule-image/web/src/lib/services/adapters/duplicates.ts
dtoro 8c2526d982 feat: PhotoPrism M0 bring-up — compose stack, web client, sidecar, migrate
Replace the legacy mule-image backend with PhotoPrism plus a thin
SvelteKit client and a Node sidecar for endpoints PhotoPrism doesn't
expose (file rename), and add a two-phase migrator (metadata via PUT,
heaps → albums) for the existing Postgres library.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 16:06:58 +02:00

42 lines
1.5 KiB
TypeScript

/**
* Adapter that shapes PhotoPrism's `q=stack:true` photo list into a
* `DuplicateGroup[]` the UI consumes. Mirrors mule-image's
* `DuplicateGroup` interface so the view code stays declarative.
*
* PhotoPrism's `/photos?merged=true` inlines `Files[]` on each row
* already — no follow-up GET per group needed (confirmed against
* photoprism/photoprism:latest, May 2026).
*/
import { listPhotos } from '$lib/services/photoprism';
import type { PpFile, PpPhoto } from '$lib/types/photoprism';
export interface DuplicateGroup {
/** The Photo record that owns this stack. Carries marks, keywords,
* taken_at — everything the metadata sidebar reads off of. */
photo: PpPhoto;
/** The variants the user picks among. Always 2+ entries; groups with
* a single file are filtered out (PhotoPrism shouldn't return them
* for `stack:true` anyway, but the guard is cheap). */
files: PpFile[];
/** Pre-selected "keep this" file = the one PhotoPrism marks Primary.
* The view starts with this highlighted and updates it on click. */
bestFileUid: string;
}
export async function listDuplicateGroups(): Promise<DuplicateGroup[]> {
const photos = await listPhotos({
q: 'stack:true',
count: 200,
merged: true,
order: 'newest'
});
return photos
.filter((p) => (p.Files?.length ?? 0) > 1)
.map((p) => {
const files = p.Files ?? [];
const primary = files.find((f) => f.Primary) ?? files[0];
return { photo: p, files, bestFileUid: primary.UID };
});
}