Files
RedditBot/apps/dashboard/src/api/client.ts
T

60 lines
1.6 KiB
TypeScript

import type { ZodType } from 'zod';
import {
botStatsSchema,
controlResponseSchema,
timersSchema,
type BotStats,
type ControlResponse,
type Timers,
} from './contracts.js';
import { createDashboardRequest } from './request.js';
export class ApiError extends Error {
constructor(
message: string,
readonly status: number,
) {
super(message);
this.name = 'ApiError';
}
}
interface ErrorPayload {
error?: unknown;
}
async function parseResponse<T>(response: Response, schema: ZodType<T>): Promise<T> {
const payload: unknown = await response.json();
if (!response.ok) {
const errorPayload = payload as ErrorPayload;
const message = typeof errorPayload.error === 'string' ? errorPayload.error : `Request failed (${response.status})`;
throw new ApiError(message, response.status);
}
return schema.parse(payload);
}
export interface DashboardApi {
getStats(): Promise<BotStats>;
getTimers(): Promise<Timers>;
setRunning(isRunning: boolean): Promise<ControlResponse>;
}
export function createDashboardApi(fetchImplementation?: typeof fetch): DashboardApi {
const request = createDashboardRequest(fetchImplementation);
return {
async getStats() {
return parseResponse(await request('/api/stats'), botStatsSchema);
},
async getTimers() {
return parseResponse(await request('/api/timers'), timersSchema);
},
async setRunning(isRunning) {
const endpoint = isRunning ? '/api/start' : '/api/stop';
return parseResponse(await request(endpoint, { method: 'POST' }), controlResponseSchema);
},
};
}
export const dashboardApi = createDashboardApi();