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>
This commit is contained in:
41
web/src/lib/services/adapters/duplicates.ts
Normal file
41
web/src/lib/services/adapters/duplicates.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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 };
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user