Sidebar: scope count badges to current user's library

PhotoPrism's /api/v1/config.count is library-wide and the same value
for every authenticated session. That made non-admins (and admins
with a non-empty BasePath) see badges that didn't match what the
timeline actually showed them.

Replaces the direct `configQuery.data?.count?.<bucket>` reads in
LeftSidebar with per-bucket queries against PhotoPrism's /photos
endpoint. The new `countPhotos(q)` helper sets `count=10000` and
reads the X-Count response header to get the true total in one round
-trip (PhotoPrism's ACL filter is what scopes the result, so the
header reflects "what this session can see").

Each bucket query appends `path:"<BasePath>*"` so admins-with-a-
BasePath stay scoped too; non-admins without a BasePath short-circuit
to `uid:none` (their effective visibility is zero, no point
querying). Admins without a BasePath skip the scoped queries
entirely and keep using the precomputed /config totals — same
network footprint as before for the common case.

Affected badges: Favorites, Hidden, Archive, Review, Tags (labels
component). Map already used `geoQuery` whose result is ACL-filtered
server-side, so its badge is per-user-correct without changes. The
`favorites` field was missing from PpClientConfig.count's TypeScript
type; added it.

Resolves the `test`-user complaint: sidebar showed the admin
library's totals next to Review / Hidden / Archive / Favorites
because those numbers came from /config, not from a user-scoped
query.
This commit is contained in:
Claudio
2026-05-18 20:11:52 +00:00
parent b0c8c06b2b
commit 85847848c4
3 changed files with 105 additions and 22 deletions

View File

@@ -155,6 +155,28 @@ export async function listPhotos(params: ListPhotosParams = {}): Promise<PpPhoto
return data;
}
/**
* Count photos matching a DSL query, scoped to whatever the caller's
* session ACL allows. PhotoPrism doesn't expose a dedicated "count
* only" endpoint, but the `X-Count` header on `/photos` returns the
* page size — which equals the total when `count` is set above the
* library size. 10000 is generously above the realistic per-user
* library and well under PhotoPrism's server-side ceiling, so a single
* round-trip yields the true total without paginating.
*
* Used by the LeftSidebar to render bucket badges that reflect what
* the signed-in user actually sees, not the global library aggregate
* exposed by `/config.count`.
*/
export async function countPhotos(q: string): Promise<number> {
const resp = await http.get('/photos', {
params: { count: 10000, offset: 0, merged: false, q }
});
const header = resp.headers['x-count'];
const n = typeof header === 'string' ? parseInt(header, 10) : NaN;
return Number.isFinite(n) ? n : 0;
}
export async function getPhoto(uid: string): Promise<PpPhoto> {
const { data } = await http.get<PpPhoto>(`/photos/${uid}`);
return data;