feat(boss-dashboard): add /api/v1/boss-dashboard/rep-activity route handler

This commit is contained in:
Dave 2026-08-28 00:05:20 +02:00
parent 8ecacf78fe
commit 70807e34fe

View file

@ -0,0 +1,88 @@
import { NextRequest, NextResponse } from "next/server";
import {
getBossRepActivity,
type BossWindow,
} from "@/lib/server/boss-dashboard-service";
/**
* GET /api/v1/boss-dashboard/rep-activity?rep=<id>&window=today|week|month&kind=quotes|orders&page=1
*
* Paginated list of quotes or orders for one rep within the active
* window. Drives the drill-down bottom sheet on the boss's phone
* dashboard at `/m`. Returns up to 25 items per page plus a `hasMore`
* flag. Auth via the PB browser session.
*/
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
const VALID_WINDOWS: BossWindow[] = ["today", "week", "month"];
const VALID_KINDS = ["quotes", "orders"] as const;
type ActivityKind = (typeof VALID_KINDS)[number];
async function assertBrowserSession(req: NextRequest) {
const auth = req.headers.get("authorization");
if (!auth) {
throw Object.assign(new Error("Not authenticated."), { status: 401 });
}
const pbUrl =
process.env.NEXT_PUBLIC_POCKETBASE_URL || "http://127.0.0.1:8090";
const token = auth.startsWith("Bearer ") ? auth.slice(7) : auth;
const res = await fetch(
`${pbUrl.replace(/\/$/, "")}/api/collections/users/auth-refresh`,
{
method: "POST",
headers: { Authorization: token },
signal: AbortSignal.timeout(5000),
},
);
if (!res.ok) {
throw Object.assign(new Error("Invalid or expired session."), {
status: 401,
});
}
return res.json();
}
export async function GET(req: NextRequest) {
try {
await assertBrowserSession(req);
const url = new URL(req.url);
const rep = url.searchParams.get("rep");
if (!rep) {
return NextResponse.json({ error: "Missing rep." }, { status: 400 });
}
const window = url.searchParams.get("window") ?? "today";
if (!VALID_WINDOWS.includes(window as BossWindow)) {
return NextResponse.json(
{ error: `Invalid window: ${window}` },
{ status: 400 },
);
}
const kind = url.searchParams.get("kind") ?? "quotes";
if (!VALID_KINDS.includes(kind as ActivityKind)) {
return NextResponse.json(
{ error: `Invalid kind: ${kind}` },
{ status: 400 },
);
}
const pageParam = url.searchParams.get("page");
const page = pageParam ? Math.max(1, parseInt(pageParam, 10) || 1) : 1;
const data = await getBossRepActivity({
rep,
window: window as BossWindow,
kind: kind as ActivityKind,
page,
});
return NextResponse.json(data);
} catch (error) {
const message =
error instanceof Error ? error.message : "Failed to load activity";
const status = (error as Error & { status?: number }).status || 500;
return NextResponse.json({ error: message }, { status });
}
}