69 lines
2.1 KiB
TypeScript
69 lines
2.1 KiB
TypeScript
import "server-only";
|
|
import { getValidSession } from "@/lib/auth/session";
|
|
import { ApiError, type WinofyErrorDetails } from "./errors";
|
|
import { logWinofyRequest, logWinofyResponse } from "./dev-logger";
|
|
|
|
const BASE_URL = process.env.WINOFY_API_BASE_URL!;
|
|
|
|
interface WinofyFetchOptions extends Omit<RequestInit, "body"> {
|
|
/** Attach the caller's bearer token. Defaults to true — set false for public endpoints. */
|
|
auth?: boolean;
|
|
params?: Record<string, string | number | boolean | undefined>;
|
|
body?: unknown;
|
|
}
|
|
|
|
export interface PaginatedResponse<T> {
|
|
count: number;
|
|
next: string | null;
|
|
previous: string | null;
|
|
results: T[];
|
|
}
|
|
|
|
export async function winofyFetch<T>(path: string, options: WinofyFetchOptions = {}): Promise<T> {
|
|
const { auth = true, params, headers, body, ...rest } = options;
|
|
const method = rest.method ?? "GET";
|
|
|
|
const url = new URL(BASE_URL.replace(/\/$/, "") + path);
|
|
if (params) {
|
|
for (const [key, value] of Object.entries(params)) {
|
|
if (value !== undefined) url.searchParams.set(key, String(value));
|
|
}
|
|
}
|
|
|
|
const finalHeaders = new Headers(headers);
|
|
const hasBody = body !== undefined;
|
|
if (hasBody && !finalHeaders.has("Content-Type")) {
|
|
finalHeaders.set("Content-Type", "application/json");
|
|
}
|
|
|
|
if (auth) {
|
|
const session = await getValidSession();
|
|
if (session) finalHeaders.set("Authorization", `Bearer ${session.accessToken}`);
|
|
}
|
|
|
|
logWinofyRequest(method, url.toString(), body);
|
|
|
|
const res = await fetch(url.toString(), {
|
|
...rest,
|
|
headers: finalHeaders,
|
|
body: hasBody ? JSON.stringify(body) : undefined,
|
|
cache: "no-store",
|
|
});
|
|
|
|
if (res.status === 204) {
|
|
logWinofyResponse(method, url.toString(), res.status, null);
|
|
return undefined as T;
|
|
}
|
|
|
|
const data = await res.json().catch(() => null);
|
|
logWinofyResponse(method, url.toString(), res.status, data);
|
|
|
|
if (!res.ok) {
|
|
const envelope = data as { status_message?: string; details?: WinofyErrorDetails } | null;
|
|
throw new ApiError(res.status, envelope?.status_message ?? res.statusText, envelope?.details ?? null);
|
|
}
|
|
|
|
return data as T;
|
|
}
|
|
|
|
export { ApiError };
|