fix(notes): page PhotoPrism server-side so /notes shows every captioned photo

Client-side paging of listPhotosWithNotes stopped early for BasePath users:
the sidecar post-filters each page by BasePath, so a full upstream page can
arrive short, tripping the `length < PAGE` end condition before the library
is exhausted — hiding notes past the first slice.

Add GET /api/sidecar/notes: the sidecar pages /api/v1/photos to completion
(keying the loop off the raw upstream page length), filters to non-empty
Caption under the caller's BasePath, dedupes by UID, and returns the set.
listPhotosWithNotes now calls this single endpoint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 00:43:56 +02:00
parent 259adb6a41
commit 3e164c48d0
3 changed files with 82 additions and 19 deletions

View File

@@ -639,25 +639,16 @@ export interface PhotoWithNote {
}
export async function listPhotosWithNotes(): Promise<PhotoWithNote[]> {
// PhotoPrism has no "caption is not empty" search filter, so we page the
// whole library and keep the captioned rows. Paging (rather than a single
// count:1000 fetch) means notes on photos older than the newest 1000 still
// surface — the previous cap silently hid them.
// The sidecar pages PhotoPrism to completion server-side and returns only
// captioned, BasePath-scoped photos — paging client-side would stop early
// because each page is BasePath-filtered before we see it (a full upstream
// page can arrive short), silently hiding notes past the first slice.
const { data } = await sidecar.get<PpPhoto[]>('/api/sidecar/notes');
const out: PhotoWithNote[] = [];
const seen = new Set<string>();
const PAGE = 1000;
for (let offset = 0; ; offset += PAGE) {
const list = await listPhotos({ count: PAGE, offset, order: 'newest', merged: true });
for (const p of list) {
// `merged: true` can repeat a photo across file-rows; dedupe by UID
// so the same tile doesn't render twice.
if (seen.has(p.UID)) continue;
seen.add(p.UID);
const note = p.Caption?.trim();
if (!note) continue;
out.push({ photo: p, note });
}
if (list.length < PAGE) break;
for (const p of data) {
const note = p.Caption?.trim();
if (!note) continue;
out.push({ photo: p, note });
}
return out;
}