Files
Frontend/src/libs/api/client.ts
T
2026-03-24 13:53:21 +03:30

239 lines
5.5 KiB
TypeScript

/**
* API Client for communicating with Backend via Envoy Gateway
*/
const API_BASE_URL =
process.env.NEXT_PUBLIC_API_URL ||
process.env.ENVOY_GATEWAY_URL ||
"http://85.208.253.135:8000";
const AUTH_STORAGE_KEYS = {
accessToken: "auth_token",
refreshToken: "auth_refresh_token",
} as const;
export interface ApiError {
message: string;
code?: number;
details?: any;
}
export class ApiClient {
private baseURL: string;
private defaultHeaders: Record<string, string>;
constructor(baseURL: string = API_BASE_URL) {
this.baseURL = baseURL.replace(/\/$/, ""); // Remove trailing slash
this.defaultHeaders = {
"Content-Type": "application/json",
};
}
/**
* Get authorization token from localStorage
*/
private getAuthToken(): string | null {
if (typeof window === "undefined") return null;
return localStorage.getItem(AUTH_STORAGE_KEYS.accessToken);
}
/**
* Set authorization token in localStorage
*/
setAuthToken(token: string): void {
if (typeof window !== "undefined") {
localStorage.setItem(AUTH_STORAGE_KEYS.accessToken, token);
}
}
/**
* Set refresh token in localStorage
*/
setRefreshToken(token: string): void {
if (typeof window !== "undefined") {
localStorage.setItem(AUTH_STORAGE_KEYS.refreshToken, token);
}
}
/**
* Set access and refresh tokens in localStorage
*/
setAuthTokens(tokens: { access: string; refresh?: string }): void {
this.setAuthToken(tokens.access);
if (tokens.refresh) {
this.setRefreshToken(tokens.refresh);
}
}
/**
* Clear authorization token
*/
clearAuthToken(): void {
if (typeof window !== "undefined") {
localStorage.removeItem(AUTH_STORAGE_KEYS.accessToken);
localStorage.removeItem(AUTH_STORAGE_KEYS.refreshToken);
}
}
/**
* Get headers with authentication token
*/
private getHeaders(
customHeaders?: Record<string, string>,
): Record<string, string> {
const headers = { ...this.defaultHeaders, ...customHeaders };
const token = this.getAuthToken();
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
return headers;
}
/**
* Handle API response
*/
private async handleResponse<T>(response: Response): Promise<T> {
if (!response.ok) {
let errorData: any;
try {
errorData = await response.json();
} catch {
errorData = { message: response.statusText };
}
const error: ApiError = {
message: errorData.msg || errorData.message || "An error occurred",
code: errorData.code || response.status,
details: errorData,
};
// If unauthorized, clear token
if (response.status === 401) {
this.clearAuthToken();
}
throw error;
}
// Handle empty responses
const contentType = response.headers.get("content-type");
if (!contentType || !contentType.includes("application/json")) {
return {} as T;
}
return response.json();
}
/**
* GET request
*/
async get<T>(
endpoint: string,
customHeaders?: Record<string, string>,
): Promise<T> {
const url = `${this.baseURL}${endpoint}`;
const response = await fetch(url, {
method: "GET",
headers: this.getHeaders(customHeaders),
});
return this.handleResponse<T>(response);
}
/**
* POST request
*/
async post<T>(
endpoint: string,
data?: any,
customHeaders?: Record<string, string>,
): Promise<T> {
const url = `${this.baseURL}${endpoint}`;
const response = await fetch(url, {
method: "POST",
headers: this.getHeaders(customHeaders),
body: data ? JSON.stringify(data) : undefined,
});
return this.handleResponse<T>(response);
}
/**
* POST request with FormData (e.g. file upload). Does not set Content-Type so browser sets multipart/form-data.
*/
async postFormData<T>(
endpoint: string,
formData: FormData,
customHeaders?: Record<string, string>,
): Promise<T> {
const url = `${this.baseURL}${endpoint}`;
const headers = { ...this.getHeaders(customHeaders) };
delete headers["Content-Type"];
const response = await fetch(url, {
method: "POST",
headers,
body: formData,
});
return this.handleResponse<T>(response);
}
/**
* PUT request
*/
async put<T>(
endpoint: string,
data?: any,
customHeaders?: Record<string, string>,
): Promise<T> {
const url = `${this.baseURL}${endpoint}`;
const response = await fetch(url, {
method: "PUT",
headers: this.getHeaders(customHeaders),
body: data ? JSON.stringify(data) : undefined,
});
return this.handleResponse<T>(response);
}
/**
* PATCH request
*/
async patch<T>(
endpoint: string,
data?: any,
customHeaders?: Record<string, string>,
): Promise<T> {
const url = `${this.baseURL}${endpoint}`;
const response = await fetch(url, {
method: "PATCH",
headers: this.getHeaders(customHeaders),
body: data ? JSON.stringify(data) : undefined,
});
return this.handleResponse<T>(response);
}
/**
* DELETE request
*/
async delete<T>(
endpoint: string,
customHeaders?: Record<string, string>,
): Promise<T> {
const url = `${this.baseURL}${endpoint}`;
const response = await fetch(url, {
method: "DELETE",
headers: this.getHeaders(customHeaders),
});
return this.handleResponse<T>(response);
}
}
// Export singleton instance
export const apiClient = new ApiClient();