2026-02-06 20:55:10 +00:00
|
|
|
// Generic API client with authentication
|
|
|
|
|
|
2026-02-07 00:34:47 +00:00
|
|
|
import type { AuthUser } from '@/auth/types';
|
2026-02-06 20:55:10 +00:00
|
|
|
import { ApiError } from '@/types';
|
|
|
|
|
|
|
|
|
|
export interface RequestOptions {
|
|
|
|
|
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
|
|
|
params?: Record<string, string | number | boolean | Date | undefined>;
|
2026-02-08 13:16:32 +00:00
|
|
|
body?: unknown;
|
2026-02-06 20:55:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Build query string from parameters object
|
|
|
|
|
*/
|
|
|
|
|
function buildQueryString(params: Record<string, string | number | boolean | Date | undefined>): string {
|
|
|
|
|
const queryString = new URLSearchParams();
|
|
|
|
|
|
|
|
|
|
for (const [key, value] of Object.entries(params)) {
|
|
|
|
|
if (value !== undefined && value !== null && value !== '') {
|
|
|
|
|
if (value instanceof Date) {
|
|
|
|
|
queryString.append(key, value.toISOString());
|
|
|
|
|
} else {
|
|
|
|
|
queryString.append(key, String(value));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return queryString.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Generic authenticated API request
|
|
|
|
|
*/
|
|
|
|
|
export async function apiRequest<T>(
|
2026-02-07 00:34:47 +00:00
|
|
|
user: AuthUser,
|
2026-02-06 20:55:10 +00:00
|
|
|
endpoint: string,
|
|
|
|
|
options: RequestOptions = {}
|
|
|
|
|
): Promise<T> {
|
2026-02-08 13:16:32 +00:00
|
|
|
const { method = 'GET', params, body } = options;
|
2026-02-06 20:55:10 +00:00
|
|
|
|
|
|
|
|
let url = endpoint;
|
|
|
|
|
if (params) {
|
|
|
|
|
const queryString = buildQueryString(params);
|
|
|
|
|
if (queryString) {
|
|
|
|
|
url = `${endpoint}?${queryString}`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 13:16:32 +00:00
|
|
|
const fetchOptions: RequestInit = {
|
2026-02-06 20:55:10 +00:00
|
|
|
method,
|
|
|
|
|
headers: {
|
2026-02-07 00:34:47 +00:00
|
|
|
Authorization: `Bearer ${user.accessToken}`,
|
2026-02-06 20:55:10 +00:00
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
},
|
2026-02-08 13:16:32 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (body !== undefined) {
|
|
|
|
|
fetchOptions.body = JSON.stringify(body);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const response = await fetch(url, fetchOptions);
|
2026-02-06 20:55:10 +00:00
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new ApiError(`Error: ${response.status}`, response.status);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response.json() as Promise<T>;
|
|
|
|
|
}
|