import type { StartInspectionResponse, InspectionResult, IssueListResponse, IssueFilters, PaginatedResponse, HistoryParams, TrendResponse, } from "@/types/inspection"; import type { StartSiteInspectionResponse, SiteInspectionResult, InspectPageResponse, } from "@/types/site-inspection"; const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL ?? ""; /** API 에러 클래스 */ export class ApiError extends Error { status: number; detail: string; constructor(status: number, detail: string) { super(detail); this.name = "ApiError"; this.status = status; this.detail = detail; } } /** API 클라이언트 */ class ApiClient { private baseUrl: string; constructor(baseUrl: string) { this.baseUrl = baseUrl; } private async request(path: string, options?: RequestInit): Promise { const response = await fetch(`${this.baseUrl}${path}`, { headers: { "Content-Type": "application/json" }, ...options, }); if (!response.ok) { let detail = "요청 처리 중 오류가 발생했습니다"; try { const error = await response.json(); detail = error.detail || detail; } catch { // JSON 파싱 실패 시 기본 메시지 사용 } throw new ApiError(response.status, detail); } return response.json(); } /** 검사 시작 */ async startInspection(url: string): Promise { return this.request("/api/inspections", { method: "POST", body: JSON.stringify({ url }), }); } /** 검사 결과 조회 */ async getInspection(id: string): Promise { return this.request(`/api/inspections/${id}`); } /** 이슈 목록 조회 */ async getIssues( id: string, filters?: IssueFilters ): Promise { const params = new URLSearchParams(); if (filters?.category && filters.category !== "all") { params.set("category", filters.category); } if (filters?.severity && filters.severity !== "all") { params.set("severity", filters.severity); } const qs = params.toString(); return this.request(`/api/inspections/${id}/issues${qs ? `?${qs}` : ""}`); } /** 이력 목록 조회 (페이지네이션) */ async getInspections(params: HistoryParams): Promise { const qs = new URLSearchParams(); qs.set("page", String(params.page || 1)); qs.set("limit", String(params.limit || 20)); if (params.url) qs.set("url", params.url); if (params.sort) qs.set("sort", params.sort); return this.request(`/api/inspections?${qs}`); } /** 트렌드 데이터 조회 */ async getTrend(url: string, limit = 10): Promise { const qs = new URLSearchParams({ url, limit: String(limit) }); return this.request(`/api/inspections/trend?${qs}`); } /** PDF 다운로드 */ async downloadPdf(id: string): Promise { const response = await fetch( `${this.baseUrl}/api/inspections/${id}/report/pdf` ); if (!response.ok) { throw new ApiError(response.status, "PDF 다운로드에 실패했습니다"); } const blob = await response.blob(); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = response.headers.get("content-disposition")?.split("filename=")[1] || `web-inspector-report.pdf`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } /** JSON 다운로드 */ async downloadJson(id: string): Promise { const response = await fetch( `${this.baseUrl}/api/inspections/${id}/report/json` ); if (!response.ok) { throw new ApiError(response.status, "JSON 다운로드에 실패했습니다"); } const blob = await response.blob(); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = response.headers.get("content-disposition")?.split("filename=")[1] || `web-inspector-report.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } /** SSE 스트림 URL 반환 */ getStreamUrl(inspectionId: string): string { return `${this.baseUrl}/api/inspections/${inspectionId}/stream`; } // ───────────────────────────────────────────────────── // 사이트 전체 검사 API // ───────────────────────────────────────────────────── /** 사이트 전체 검사 시작 */ async startSiteInspection( url: string, maxPages?: number, maxDepth?: number, concurrency?: number ): Promise { return this.request("/api/site-inspections", { method: "POST", body: JSON.stringify({ url, max_pages: maxPages, max_depth: maxDepth, concurrency: concurrency, }), }); } /** 사이트 검사 결과 조회 */ async getSiteInspection(id: string): Promise { return this.request(`/api/site-inspections/${id}`); } /** 특정 페이지 수동 검사 */ async inspectPage( siteInspectionId: string, pageUrl: string ): Promise { return this.request( `/api/site-inspections/${siteInspectionId}/inspect-page`, { method: "POST", body: JSON.stringify({ url: pageUrl }), } ); } /** 사이트 검사 SSE 스트림 URL 반환 */ getSiteStreamUrl(siteInspectionId: string): string { return `${this.baseUrl}/api/site-inspections/${siteInspectionId}/stream`; } } export const api = new ApiClient(API_BASE_URL);