Add full sprint pricing workflow and supporting infrastructure: - New sidebar navigation entry for the sprint dashboard - PocketBase category collection schema and migration script - Server API routes for sprint item claims, attachments, counts, and file proxying - Dev identity utilities and UI switcher for multi-user testing - Reusable FilterTabs component replacing ItemStatusTabs - Updated TypeScript types for sprint-related entities Also include: - Fix PocketBase file URL method casing (getUrl → getURL) across codebase - Add development utility, smoke test, and helper scripts - Update package.json with new sprint migration command
85 lines
2.9 KiB
TypeScript
85 lines
2.9 KiB
TypeScript
/**
|
|
* Dev identity — temporary stand-in for real PB auth.
|
|
*
|
|
* The project is currently in "no auth" mode: PB's `users` collection
|
|
* is empty by design, so `assertBrowserSession` (used elsewhere in the
|
|
* API) always fails. Features that need caller identity for
|
|
* attribution (claim `claimed_by`, future audit trails, "claimed by
|
|
* me" detection) read identity from a cookie called
|
|
* `wfdash_dev_email` instead. Default is a placeholder email that
|
|
* matches the display name we expect in the UI badges.
|
|
*
|
|
* Identity resolution (server-side, in order):
|
|
* 1. `X-User-Email` request header (explicit per-request override)
|
|
* 2. `wfdash_dev_email` cookie (the dev user switcher in the UI)
|
|
* 3. `DEV_USER_EMAIL` env var (CI / scripted requests)
|
|
* 4. `DEFAULT_DEV_EMAIL` constant (fallback)
|
|
*
|
|
* Migration path: when real PB auth lands, replace `getServerIdentity`
|
|
* with `assertBrowserSession` and have the login flow write the real
|
|
* email to this cookie. The rest of the call sites stay the same.
|
|
*/
|
|
|
|
import type { NextRequest } from "next/server";
|
|
|
|
export const DEV_IDENTITY_COOKIE = "wfdash_dev_email";
|
|
export const DEV_IDENTITY_HEADER = "x-user-email";
|
|
export const DEFAULT_DEV_EMAIL = "david.els1@outlook.com";
|
|
|
|
function decode(value: string | undefined | null): string {
|
|
if (!value) return "";
|
|
try {
|
|
return decodeURIComponent(value);
|
|
} catch {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
function normalize(raw: string | undefined | null): string {
|
|
const decoded = decode(raw).trim();
|
|
return decoded;
|
|
}
|
|
|
|
/**
|
|
* Server-side: read the caller's dev identity from the request.
|
|
* Always returns a non-empty string.
|
|
*/
|
|
export function getServerIdentity(req: NextRequest): string {
|
|
const header = req.headers.get(DEV_IDENTITY_HEADER);
|
|
const headerVal = normalize(header);
|
|
if (headerVal) return headerVal;
|
|
|
|
const cookieVal = normalize(req.cookies.get(DEV_IDENTITY_COOKIE)?.value);
|
|
if (cookieVal) return cookieVal;
|
|
|
|
return normalize(process.env.DEV_USER_EMAIL) || DEFAULT_DEV_EMAIL;
|
|
}
|
|
|
|
/**
|
|
* Client-side: read the current dev identity from `document.cookie`.
|
|
* Falls back to the default on SSR / when the cookie isn't set.
|
|
*/
|
|
export function getClientIdentity(): string {
|
|
if (typeof document === "undefined") return DEFAULT_DEV_EMAIL;
|
|
const escaped = DEV_IDENTITY_COOKIE.replace(
|
|
/[.*+?^${}()|[\]\\]/g,
|
|
"\\$&",
|
|
);
|
|
const m = document.cookie.match(
|
|
new RegExp(`(?:^|;\\s*)${escaped}=([^;]+)`),
|
|
);
|
|
if (!m) return DEFAULT_DEV_EMAIL;
|
|
const decoded = decode(m[1]).trim();
|
|
return decoded || DEFAULT_DEV_EMAIL;
|
|
}
|
|
|
|
/**
|
|
* Client-side: persist a new dev identity to the cookie.
|
|
* Path is `/` so the API routes can read it, max-age is 1 year.
|
|
*/
|
|
export function setClientIdentity(email: string): void {
|
|
if (typeof document === "undefined") return;
|
|
const value = encodeURIComponent(email.trim() || DEFAULT_DEV_EMAIL);
|
|
const oneYear = 60 * 60 * 24 * 365;
|
|
document.cookie = `${DEV_IDENTITY_COOKIE}=${value}; path=/; max-age=${oneYear}; SameSite=Lax`;
|
|
}
|