Backend: - user roles (DB + JWT + first-user-is-admin) - volume-aware file resolver (multi-volume asset serving) - directory scanner uses volume URI directly - date-summary endpoint (capture date from EXIF) - timeline ordered by capture date - list endpoints: volumes, plugins, pipelines, library paths - delete endpoints: volumes, library paths - configurable upload body limit (MAX_UPLOAD_BYTES) Frontend: - auth: login/register, token refresh, role-based admin gate - timeline: date-grouped grid, infinite scroll, date scrubber - image viewer: fullscreen zoom/pan/pinch, metadata sidebar - upload: drag-drop, sequential upload, progress tracking - albums: create, add/remove photos, asset picker dialog - admin: storage (import library), jobs (pagination, error details), plugins (list + toggle), pipelines, sidecars, duplicates - multi-select mode with add-to-album action - TanStack Query for all data fetching
32 lines
803 B
TypeScript
32 lines
803 B
TypeScript
const ACCESS_KEY = "k_photos_token"
|
|
const REFRESH_KEY = "k_photos_refresh"
|
|
|
|
export function getTokens() {
|
|
if (typeof window === "undefined") return { access: null, refresh: null }
|
|
return {
|
|
access: localStorage.getItem(ACCESS_KEY),
|
|
refresh: localStorage.getItem(REFRESH_KEY),
|
|
}
|
|
}
|
|
|
|
export function setTokens(access: string, refresh: string) {
|
|
localStorage.setItem(ACCESS_KEY, access)
|
|
localStorage.setItem(REFRESH_KEY, refresh)
|
|
}
|
|
|
|
export function clearTokens() {
|
|
localStorage.removeItem(ACCESS_KEY)
|
|
localStorage.removeItem(REFRESH_KEY)
|
|
}
|
|
|
|
export function getRoleFromToken(): string | null {
|
|
const { access } = getTokens()
|
|
if (!access) return null
|
|
try {
|
|
const payload = JSON.parse(atob(access.split(".")[1]))
|
|
return payload.role ?? null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|