Files
codetutor/frontend/src/lib/api.ts

119 lines
3.1 KiB
TypeScript

import type {
CategoryListResponse,
Pattern,
PatternListResponse,
PatternTutorial,
QuestionDetail,
QuestionListResponse,
Stats,
SubmissionRequest,
SubmissionResponse,
} from "@/types";
// Server-side uses internal Docker network, client-side uses public URL
const API_BASE_URL =
typeof window === "undefined"
? process.env.API_URL || "http://localhost:8000"
: process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
export class ApiError extends Error {
constructor(
public status: number,
public statusText: string,
public detail?: string
) {
super(detail || `API error: ${status} ${statusText}`);
this.name = "ApiError";
}
}
async function fetchApi<T>(
endpoint: string,
options?: RequestInit
): Promise<T> {
const url = `${API_BASE_URL}${endpoint}`;
const response = await fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
if (!response.ok) {
let detail: string | undefined;
try {
const errorBody = await response.json();
detail = errorBody.detail;
} catch {
// Ignore JSON parse errors
}
throw new ApiError(response.status, response.statusText, detail);
}
return response.json();
}
export interface QuestionFilters {
page?: number;
limit?: number;
difficulty?: string;
category?: string;
pattern?: string;
search?: string;
}
export async function getQuestions(
filters: QuestionFilters = {}
): Promise<QuestionListResponse> {
const params = new URLSearchParams();
if (filters.page) params.set("page", filters.page.toString());
if (filters.limit) params.set("limit", filters.limit.toString());
if (filters.difficulty) params.set("difficulty", filters.difficulty);
if (filters.category) params.set("category", filters.category);
if (filters.pattern) params.set("pattern", filters.pattern);
if (filters.search) params.set("search", filters.search);
const queryString = params.toString();
const endpoint = queryString
? `/api/questions?${queryString}`
: "/api/questions";
return fetchApi<QuestionListResponse>(endpoint);
}
export async function getQuestion(slug: string): Promise<QuestionDetail> {
return fetchApi<QuestionDetail>(`/api/questions/${slug}`);
}
export async function getCategories(): Promise<CategoryListResponse> {
return fetchApi<CategoryListResponse>("/api/categories");
}
export async function getPatterns(): Promise<PatternListResponse> {
return fetchApi<PatternListResponse>("/api/patterns");
}
export async function getPattern(slug: string): Promise<Pattern> {
return fetchApi<Pattern>(`/api/patterns/${slug}`);
}
export async function getPatternTutorial(slug: string): Promise<PatternTutorial> {
return fetchApi<PatternTutorial>(`/api/patterns/${slug}/tutorial`);
}
export async function getStats(): Promise<Stats> {
return fetchApi<Stats>("/api/stats");
}
export async function submitSolution(
slug: string,
submission: SubmissionRequest
): Promise<SubmissionResponse> {
return fetchApi<SubmissionResponse>(`/api/questions/${slug}/submit`, {
method: "POST",
body: JSON.stringify(submission),
});
}