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:
89
web/src/lib/stores/session.svelte.ts
Normal file
89
web/src/lib/stores/session.svelte.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { browser } from '$app/environment';
|
||||
import type { PpClientConfig, PpSessionResponse, PpUser } from '$lib/types/photoprism';
|
||||
|
||||
const STORAGE_KEY = 'pp_session';
|
||||
|
||||
interface PersistedSession {
|
||||
id: string;
|
||||
accessToken: string;
|
||||
previewToken: string;
|
||||
downloadToken: string;
|
||||
user: PpUser;
|
||||
}
|
||||
|
||||
function loadInitial(): PersistedSession | null {
|
||||
if (!browser) return null;
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return null;
|
||||
return JSON.parse(raw) as PersistedSession;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Single-source session state for the Svelte client. Components import the
|
||||
* `session` object and read its reactive fields; mutations go through the
|
||||
* helpers below. State is mirrored to localStorage so a hard refresh keeps
|
||||
* the user signed in.
|
||||
*/
|
||||
const initial = loadInitial();
|
||||
|
||||
export const session = $state<{
|
||||
id: string | null;
|
||||
accessToken: string | null;
|
||||
previewToken: string | null;
|
||||
downloadToken: string | null;
|
||||
user: PpUser | null;
|
||||
}>({
|
||||
id: initial?.id ?? null,
|
||||
accessToken: initial?.accessToken ?? null,
|
||||
previewToken: initial?.previewToken ?? null,
|
||||
downloadToken: initial?.downloadToken ?? null,
|
||||
user: initial?.user ?? null
|
||||
});
|
||||
|
||||
export function isAuthenticated(): boolean {
|
||||
return Boolean(session.accessToken);
|
||||
}
|
||||
|
||||
export function adoptSession(resp: PpSessionResponse, cfg?: PpClientConfig): void {
|
||||
session.id = resp.id;
|
||||
session.accessToken = resp.access_token;
|
||||
session.previewToken = (cfg ?? resp.config)?.previewToken ?? '';
|
||||
session.downloadToken = (cfg ?? resp.config)?.downloadToken ?? '';
|
||||
session.user = resp.user;
|
||||
persist();
|
||||
}
|
||||
|
||||
export function clearSession(): void {
|
||||
session.id = null;
|
||||
session.accessToken = null;
|
||||
session.previewToken = null;
|
||||
session.downloadToken = null;
|
||||
session.user = null;
|
||||
if (browser) localStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
|
||||
function persist(): void {
|
||||
if (!browser || !session.accessToken) return;
|
||||
const payload: PersistedSession = {
|
||||
id: session.id ?? '',
|
||||
accessToken: session.accessToken,
|
||||
previewToken: session.previewToken ?? '',
|
||||
downloadToken: session.downloadToken ?? '',
|
||||
user: session.user!
|
||||
};
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(payload));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a thumbnail URL for a photo. PhotoPrism's thumb endpoint is
|
||||
* /api/v1/t/:hash/:token/:size — the token is the per-session
|
||||
* previewToken, which the session response provides on login.
|
||||
*/
|
||||
export function thumbUrl(hash: string, size = 'tile_500'): string {
|
||||
if (!session.previewToken) return '';
|
||||
return `/api/v1/t/${hash}/${session.previewToken}/${size}`;
|
||||
}
|
||||
Reference in New Issue
Block a user