Files
spsays-training/lib/reading-prefs.ts
T

54 lines
1.9 KiB
TypeScript

/**
* Browser-side client for the user's roaming reading preferences.
*
* The BFF stores prefs as opaque JSONB keyed off the user. This module
* just round-trips that blob — it makes no claim about its shape (that
* contract is owned by components/reading-settings.tsx). All calls run
* with `credentials: "include"` so the auth cookie comes along.
*/
const BFF_BASE =
process.env.NEXT_PUBLIC_BFF_URL || "http://localhost:8080";
type Envelope<T> = {
success?: boolean;
data?: T;
error?: { code: number; message: string };
};
async function call<T>(path: string, init?: RequestInit): Promise<T | null> {
const res = await fetch(`${BFF_BASE}/api/v1${path}`, {
credentials: "include",
headers: { "Content-Type": "application/json", ...(init?.headers ?? {}) },
...init,
});
if (res.status === 401 || res.status === 403) return null;
if (!res.ok) {
throw new Error(`reading-prefs ${path}: ${res.status}`);
}
const body = (await res.json().catch(() => ({}))) as Envelope<T>;
return (body.data ?? null) as T | null;
}
export const readingPrefsApi = {
/** Fetches the caller's reading prefs blob. Returns `null` if the
* request is unauthenticated (treat as "no remote prefs"). The blob
* is the raw JSON object the frontend wrote last; an empty object
* on first load. */
async get(): Promise<Record<string, unknown> | null> {
const data = await call<Record<string, unknown>>("/users/me/reading-prefs");
return data ?? null;
},
/** Overwrites the caller's reading prefs blob. The server stores the
* payload verbatim (after canonicalising via re-marshal) — schema is
* client-owned. Errors are swallowed by the caller; reading prefs
* should never block a UI interaction. */
async put(prefs: Record<string, unknown>): Promise<void> {
await call<Record<string, unknown>>("/users/me/reading-prefs", {
method: "PUT",
body: JSON.stringify(prefs),
});
},
};