140 lines
3.2 KiB
TypeScript
140 lines
3.2 KiB
TypeScript
|
|
// API Client for CLQMS
|
||
|
|
// Wrapper around fetch for making API calls to the backend
|
||
|
|
|
||
|
|
import { browser } from '$app/environment';
|
||
|
|
import { PUBLIC_API_BASE_URL, PUBLIC_API_BASE_URL_PROD } from '$env/static/public';
|
||
|
|
|
||
|
|
const API_BASE_URL = browser
|
||
|
|
? (import.meta.env.DEV ? PUBLIC_API_BASE_URL : PUBLIC_API_BASE_URL_PROD)
|
||
|
|
: PUBLIC_API_BASE_URL;
|
||
|
|
|
||
|
|
interface ApiError {
|
||
|
|
message: string;
|
||
|
|
status: number;
|
||
|
|
code?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface ApiResponse<T> {
|
||
|
|
data: T | null;
|
||
|
|
error: ApiError | null;
|
||
|
|
success: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
class ApiClient {
|
||
|
|
private baseUrl: string;
|
||
|
|
private defaultHeaders: Record<string, string>;
|
||
|
|
|
||
|
|
constructor(baseUrl: string = API_BASE_URL) {
|
||
|
|
this.baseUrl = baseUrl.replace(/\/$/, '');
|
||
|
|
this.defaultHeaders = {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
'Accept': 'application/json'
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
private async request<T>(
|
||
|
|
endpoint: string,
|
||
|
|
options: RequestInit = {}
|
||
|
|
): Promise<ApiResponse<T>> {
|
||
|
|
const url = `${this.baseUrl}/${endpoint.replace(/^\//, '')}`;
|
||
|
|
|
||
|
|
const config: RequestInit = {
|
||
|
|
...options,
|
||
|
|
headers: {
|
||
|
|
...this.defaultHeaders,
|
||
|
|
...options.headers
|
||
|
|
},
|
||
|
|
credentials: 'include'
|
||
|
|
};
|
||
|
|
|
||
|
|
try {
|
||
|
|
const response = await fetch(url, config);
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const errorData = await response.json().catch(() => ({}));
|
||
|
|
return {
|
||
|
|
data: null,
|
||
|
|
error: {
|
||
|
|
message: errorData.message || `HTTP Error: ${response.status}`,
|
||
|
|
status: response.status,
|
||
|
|
code: errorData.code
|
||
|
|
},
|
||
|
|
success: false
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
const contentType = response.headers.get('content-type');
|
||
|
|
let data: T;
|
||
|
|
|
||
|
|
if (contentType?.includes('application/json')) {
|
||
|
|
data = await response.json();
|
||
|
|
} else {
|
||
|
|
data = await response.text() as unknown as T;
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
data,
|
||
|
|
error: null,
|
||
|
|
success: true
|
||
|
|
};
|
||
|
|
|
||
|
|
} catch (error) {
|
||
|
|
return {
|
||
|
|
data: null,
|
||
|
|
error: {
|
||
|
|
message: error instanceof Error ? error.message : 'Network error',
|
||
|
|
status: 0
|
||
|
|
},
|
||
|
|
success: false
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async get<T>(endpoint: string, headers?: Record<string, string>): Promise<ApiResponse<T>> {
|
||
|
|
return this.request<T>(endpoint, { method: 'GET', headers });
|
||
|
|
}
|
||
|
|
|
||
|
|
async post<T>(
|
||
|
|
endpoint: string,
|
||
|
|
body: unknown,
|
||
|
|
headers?: Record<string, string>
|
||
|
|
): Promise<ApiResponse<T>> {
|
||
|
|
return this.request<T>(endpoint, {
|
||
|
|
method: 'POST',
|
||
|
|
body: JSON.stringify(body),
|
||
|
|
headers
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
async patch<T>(
|
||
|
|
endpoint: string,
|
||
|
|
body: unknown,
|
||
|
|
headers?: Record<string, string>
|
||
|
|
): Promise<ApiResponse<T>> {
|
||
|
|
return this.request<T>(endpoint, {
|
||
|
|
method: 'PATCH',
|
||
|
|
body: JSON.stringify(body),
|
||
|
|
headers
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
async put<T>(
|
||
|
|
endpoint: string,
|
||
|
|
body: unknown,
|
||
|
|
headers?: Record<string, string>
|
||
|
|
): Promise<ApiResponse<T>> {
|
||
|
|
return this.request<T>(endpoint, {
|
||
|
|
method: 'PUT',
|
||
|
|
body: JSON.stringify(body),
|
||
|
|
headers
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
async delete<T>(endpoint: string, headers?: Record<string, string>): Promise<ApiResponse<T>> {
|
||
|
|
return this.request<T>(endpoint, { method: 'DELETE', headers });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export const api = new ApiClient();
|
||
|
|
export type { ApiResponse, ApiError };
|