Files
zeavis-edu/apps/web/src/lib/api-client.ts
T

171 lines
4.8 KiB
TypeScript
Raw Normal View History

import type {
2026-05-22 16:43:25 +00:00
AuthMeResponse,
AuthRequest,
AuthResponse,
DiagnosisRecord,
DiseaseCatalogItem,
2026-05-22 16:43:25 +00:00
DiseaseSlug,
ImageClassificationRecord,
ManualClassificationRequest,
ManualClassificationRecord,
2026-05-22 16:43:25 +00:00
RegisterRequest,
ReviewDiagnosisRequest,
DashboardSummary,
} from '@zeavis/shared';
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL ?? '';
async function fetchApi<T>(endpoint: string, options?: RequestInit): Promise<T> {
const url = `${apiBaseUrl}${endpoint}`;
2026-05-22 16:43:25 +00:00
const response = await fetch(url, {
credentials: 'include',
...options,
headers: options?.headers,
});
if (!response.ok) {
let errorMessage = `HTTP ${response.status}`;
try {
const errorData = await response.json();
if (errorData.error) {
errorMessage = errorData.error;
}
} catch {
}
throw new Error(errorMessage);
}
return response.json();
}
2026-05-22 16:46:43 +00:00
function toImageClassificationRecord(diagnosis: DiagnosisRecord): ImageClassificationRecord {
if (!diagnosis.disease) {
throw new Error('Diagnosis does not include disease details');
}
return {
id: diagnosis.id,
predictedDiseaseSlug: diagnosis.predictedDiseaseSlug || 'daun-sehat',
confidence: diagnosis.confidence || 0,
probabilities: diagnosis.predictions.map((p) => ({
diseaseSlug: p.diseaseSlug || 'daun-sehat',
label: p.modelLabel,
confidence: p.confidence,
})),
imageUrl: diagnosis.imageUrl,
originalFileName: diagnosis.imageFileName,
uploaderPublicId: diagnosis.uploaderPublicId,
uploader: {
public_id: diagnosis.uploaderPublicId,
file_name: diagnosis.imageFileName,
mime_type: diagnosis.imageMimeType,
size_bytes: diagnosis.imageSizeBytes,
file_type: diagnosis.imageMimeType.split('/')[1] || 'unknown',
download_url: diagnosis.imageUrl,
},
createdAt: diagnosis.createdAt,
disease: diagnosis.disease,
};
}
export const apiClient = {
2026-05-22 16:43:25 +00:00
// Auth methods
async getMe(): Promise<AuthMeResponse> {
return fetchApi('/api/v1/auth/me');
},
async register(payload: RegisterRequest): Promise<AuthResponse> {
return fetchApi('/api/v1/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
},
async login(payload: AuthRequest): Promise<AuthResponse> {
return fetchApi('/api/v1/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
},
async logout(): Promise<{ ok: boolean }> {
return fetchApi('/api/v1/auth/logout', { method: 'POST' });
},
// Disease catalog methods
async getDiseases(): Promise<DiseaseCatalogItem[]> {
return fetchApi('/api/v1/diseases');
},
async getDisease(slug: DiseaseSlug): Promise<DiseaseCatalogItem> {
return fetchApi(`/api/v1/diseases/${slug}`);
},
2026-05-22 16:43:25 +00:00
// Manual classification methods
async getManualClassifications(): Promise<ManualClassificationRecord[]> {
return fetchApi('/api/v1/classifications/manual');
},
async createManualClassification(
payload: ManualClassificationRequest,
): Promise<ManualClassificationRecord> {
return fetchApi('/api/v1/classifications/manual', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
},
2026-05-22 16:43:25 +00:00
// Diagnosis methods
async getDiagnoses(): Promise<DiagnosisRecord[]> {
return fetchApi('/api/v1/diagnoses');
},
2026-05-22 16:43:25 +00:00
async getDiagnosis(id: string): Promise<DiagnosisRecord> {
return fetchApi(`/api/v1/diagnoses/${id}`);
},
async createDiagnosis(file: File): Promise<DiagnosisRecord> {
const formData = new FormData();
formData.append('file', file);
2026-05-22 16:43:25 +00:00
return fetchApi('/api/v1/diagnoses', {
method: 'POST',
body: formData,
});
},
2026-05-22 16:43:25 +00:00
// Expert review methods
async getExpertReviews(): Promise<DiagnosisRecord[]> {
return fetchApi('/api/v1/expert/reviews');
},
async reviewDiagnosis(id: string, payload: ReviewDiagnosisRequest): Promise<DiagnosisRecord> {
return fetchApi(`/api/v1/expert/reviews/${id}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
},
// Dashboard methods
async getDashboardSummary(): Promise<DashboardSummary> {
return fetchApi('/api/v1/dashboard/summary');
},
2026-05-22 16:43:25 +00:00
// Backwards-compatible aliases for old image classification methods
async getImageClassifications(): Promise<ImageClassificationRecord[]> {
const diagnoses = await this.getDiagnoses();
2026-05-22 16:46:43 +00:00
return diagnoses.map(toImageClassificationRecord);
2026-05-22 16:43:25 +00:00
},
async createImageClassification(file: File): Promise<ImageClassificationRecord> {
const diagnosis = await this.createDiagnosis(file);
2026-05-22 16:46:43 +00:00
return toImageClassificationRecord(diagnosis);
2026-05-22 16:43:25 +00:00
},
};