97 lines
2.6 KiB
TypeScript
97 lines
2.6 KiB
TypeScript
import { getBaseUrl, type BaseUrlKey } from "./baseUrls";
|
|
|
|
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
|
|
export interface EndpointConfig {
|
|
base: BaseUrlKey; // e.g. "events"
|
|
path: string; // e.g. "/api/event/all"
|
|
method: HttpMethod;
|
|
}
|
|
|
|
export const createEndpoint = (
|
|
base: BaseUrlKey,
|
|
path: string,
|
|
method: HttpMethod = "GET"
|
|
): EndpointConfig => ({ base, path, method });
|
|
|
|
interface CallEndpointOptions<TBody> {
|
|
body?: TBody;
|
|
query?: Record<string, string | number | boolean | undefined>;
|
|
headers?: Record<string, string>;
|
|
}
|
|
|
|
export async function callEndpoint<TResponse = unknown, TBody = unknown>(
|
|
endpoint: EndpointConfig,
|
|
options: CallEndpointOptions<TBody> = {}
|
|
): Promise<TResponse> {
|
|
const { body, query, headers } = options;
|
|
|
|
const baseUrl = getBaseUrl(endpoint.base);
|
|
const url = buildUrl(baseUrl, endpoint.path, query);
|
|
|
|
const init: RequestInit = {
|
|
method: endpoint.method,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...(headers || {}),
|
|
},
|
|
};
|
|
|
|
if (body && endpoint.method !== "GET") {
|
|
// @ts-ignore body is allowed on RequestInit for fetch
|
|
init.body = JSON.stringify(body);
|
|
}
|
|
|
|
// Debug log of what we're calling (without dumping full token)
|
|
const authHeader = headers?.Authorization || "";
|
|
const shortToken = authHeader.startsWith("Bearer ")
|
|
? authHeader.slice(7, 27) + "..."
|
|
: undefined;
|
|
// eslint-disable-next-line no-console
|
|
console.log("[api] request", {
|
|
url,
|
|
method: endpoint.method,
|
|
hasAuth: !!authHeader,
|
|
tokenPrefix: shortToken,
|
|
});
|
|
|
|
const res = await fetch(url, init as any);
|
|
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => "");
|
|
// eslint-disable-next-line no-console
|
|
console.log("[api] error response", {
|
|
url,
|
|
status: res.status,
|
|
body: text,
|
|
});
|
|
throw new Error(text || `Request failed with status ${res.status}`);
|
|
}
|
|
|
|
const contentType = res.headers.get("content-type") || "";
|
|
if (contentType.includes("application/json")) {
|
|
return (await res.json()) as TResponse;
|
|
}
|
|
|
|
return (await res.text()) as unknown as TResponse;
|
|
}
|
|
|
|
function buildUrl(
|
|
baseUrl: string,
|
|
path: string,
|
|
query?: Record<string, string | number | boolean | undefined>
|
|
): string {
|
|
const trimmedBase = baseUrl.replace(/\/$/, "");
|
|
const trimmedPath = path.startsWith("/") ? path : `/${path}`;
|
|
const url = new URL(trimmedBase + trimmedPath);
|
|
|
|
if (query) {
|
|
Object.entries(query).forEach(([key, value]) => {
|
|
if (value === undefined) return;
|
|
url.searchParams.set(key, String(value));
|
|
});
|
|
}
|
|
|
|
return url.toString();
|
|
}
|