import z from "zod"; import { AppError, AuthenticationError, AuthorizationError, ValidationError } from "../error"; export type RequestOptions = { method: "GET" | "POST" | "PUT" | "DELETE"; headers?: Record; contentType?: "application/json" | "application/x-www-form-urlencoded"; signal?: AbortSignal; body?: any; // Add more options as needed }; export class BaseApiService { bearerToken: string; baseUrl: string; constructor(bearerToken: string, baseUrl: string) { this.bearerToken = bearerToken; this.baseUrl = baseUrl; } setBearerToken(bearerToken: string) { this.bearerToken = bearerToken; } async request( url: string, body?: any, responseSchema?: z.ZodSchema, options?: RequestOptions ): Promise { const defaultOptions: RequestOptions = { headers: { "Content-Type": "application/json", Authorization: `Bearer ${this.bearerToken}`, }, method: "GET", }; const mergedOptions: RequestOptions = { ...defaultOptions, ...(options ?? {}), headers: { ...defaultOptions.headers, ...(options?.headers ?? {}), }, }; let requestBody; // Serialize body if (body) { if (mergedOptions.headers?.["Content-Type"].toLocaleLowerCase() === "application/json") { requestBody = JSON.stringify(body); } if ( mergedOptions.headers?.["Content-Type"].toLocaleLowerCase() === "application/x-www-form-urlencoded" ) { requestBody = new URLSearchParams(body); } mergedOptions.body = requestBody; } if (!this.baseUrl) { throw new AppError("Base URL is not set."); } if (!this.bearerToken) { throw new AuthenticationError("You are not authenticated. Please login again."); } const fullUrl = new URL(url, this.baseUrl).toString(); const response = await fetch(fullUrl, mergedOptions); if (!response.ok) { if (response.status === 401) { throw new AuthenticationError("You are not authenticated. Please login again."); } if (response.status === 403) { throw new AuthorizationError("You don't have permission to access this resource."); } throw new AppError(`API Error: ${response.statusText}`); } let data; try { data = await response.json(); } catch (error) { throw new AppError(`Failed to parse response as JSON: ${error}`); } if (!responseSchema) { return data; } const parsedData = responseSchema.safeParse(data); if (!parsedData.success) { throw new ValidationError(`Invalid response: ${parsedData.error.message}`); } return parsedData.data; } async get( url: string, responseSchema?: z.ZodSchema, options?: Omit ) { return this.request(url, undefined, responseSchema, { ...options, method: "GET", }); } async post( url: string, body?: any, responseSchema?: z.ZodSchema, options?: Omit ) { return this.request(url, body, responseSchema, { ...options, method: "POST", }); } async put( url: string, body?: any, responseSchema?: z.ZodSchema, options?: Omit ) { return this.request(url, body, responseSchema, { ...options, method: "PUT", }); } async delete( url: string, body?: any, responseSchema?: z.ZodSchema, options?: Omit ) { return this.request(url, body, responseSchema, { ...options, method: "DELETE", }); } } export const baseApiService = new BaseApiService( typeof window !== "undefined" ? localStorage.getItem("surfsense_bearer_token") || "" : "", process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || "" );