feat(web): scope library views to per-user BasePath
PhotoPrism's user entity carries a per-user BasePath; the web app now mirrors that scope client-side so each user sees only their own subtree in the sidebar, timeline, folder counts, and heap-convert target picker. Admin without a BasePath is unchanged. Also removes the redundant "✕ <folder>" pill below the folder tree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,14 @@
|
||||
import axios, { AxiosError, type AxiosInstance } from 'axios';
|
||||
import { browser } from '$app/environment';
|
||||
import { goto } from '$app/navigation';
|
||||
import { adoptSession, clearSession, session } from '$lib/stores/session.svelte';
|
||||
import {
|
||||
adoptSession,
|
||||
clearSession,
|
||||
session,
|
||||
toOriginalsPath,
|
||||
toUserPath,
|
||||
userBasePath
|
||||
} from '$lib/stores/session.svelte';
|
||||
import { primaryFile } from '$lib/types/photoprism';
|
||||
import type {
|
||||
PpClientConfig,
|
||||
@@ -325,13 +332,26 @@ export interface PpFolder {
|
||||
* Recursive list of subfolders under originals/. `uncached=true` because
|
||||
* PhotoPrism's folder cache lags new folders by a noticeable interval and
|
||||
* mule-image's folder tree expects to surface mutations immediately.
|
||||
*
|
||||
* Scoped to the signed-in user's `BasePath` on the way out: server-absolute
|
||||
* `Path` values get rewritten to user-relative (e.g. `users/alice/2024/01`
|
||||
* → `2024/01`) so every downstream consumer (FolderTree, sidebar, heap
|
||||
* convert picker) sees folders relative to the user's root. The BasePath
|
||||
* row itself is dropped — the sidebar synthesises the root entry. When
|
||||
* BasePath is empty (today's admin default) this is a no-op.
|
||||
*/
|
||||
export async function listFolders(): Promise<PpFolder[]> {
|
||||
const { data } = await http.get<{ folders?: PpFolder[] }>(
|
||||
'/folders/originals',
|
||||
{ params: { recursive: true, uncached: true, files: false } }
|
||||
);
|
||||
return data.folders ?? [];
|
||||
const bp = userBasePath();
|
||||
const folders = data.folders ?? [];
|
||||
if (bp === '') return folders;
|
||||
return folders
|
||||
.filter((f) => f.Path === bp || f.Path.startsWith(bp + '/'))
|
||||
.map((f) => ({ ...f, Path: toUserPath(f.Path) }))
|
||||
.filter((f) => f.Path !== '');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -340,6 +360,11 @@ export async function listFolders(): Promise<PpFolder[]> {
|
||||
* `/folders/import`. The endpoint returns the same `PpFolder[]` shape as
|
||||
* `/folders/originals`, but the photo counts come from `X-Files` and
|
||||
* `X-Folders` response headers since the body only lists subfolders.
|
||||
*
|
||||
* BasePath does NOT apply: `/folders/import` is a separate root from
|
||||
* originals (PhotoPrism's `import.path`, not under originals/), so we
|
||||
* don't filter the result by the signed-in user's BasePath. If/when
|
||||
* per-user inbox isolation is needed, that's a PhotoPrism-side feature.
|
||||
*/
|
||||
export interface ImportInfo {
|
||||
files: number;
|
||||
@@ -385,11 +410,21 @@ export async function getImportInfo(): Promise<ImportInfo> {
|
||||
*/
|
||||
export async function listFolderCounts(paths: string[]): Promise<Record<string, number>> {
|
||||
if (paths.length === 0) return {};
|
||||
const data = (await sidecar('POST', '/folders/counts', { paths })) as Record<
|
||||
// The sidecar walks the real filesystem and queries PhotoPrism with
|
||||
// server-absolute paths, but callers hand us user-relative paths
|
||||
// (because that's what `listFolders` returns post-scoping). Translate
|
||||
// on the way out, then re-key the response back to user-relative on
|
||||
// the way in so callers' map keys line up with their input array.
|
||||
const serverPaths = paths.map((p) => toOriginalsPath(p));
|
||||
const data = (await sidecar('POST', '/folders/counts', { paths: serverPaths })) as Record<
|
||||
string,
|
||||
number
|
||||
>;
|
||||
return data;
|
||||
const out: Record<string, number> = {};
|
||||
for (let i = 0; i < paths.length; i += 1) {
|
||||
out[paths[i]] = data[serverPaths[i]] ?? 0;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// ── Geo ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user