feat: persist authenticated diagnoses
Implement diagnosis persistence routes with image upload, classification, and prediction storage. Add missing HTTP error helpers (unauthorized, forbidden) and update uploader client to use configurable base URL. - Create POST /api/v1/diagnoses to upload images and store classifications - Create GET /api/v1/diagnoses to list user's diagnoses (30 most recent) - Create GET /api/v1/diagnoses/:id to retrieve diagnosis with predictions - Add loadDiagnosisRecord helper for consistent diagnosis data loading - Update uploader-client.ts to use env.uploaderBaseUrl - Add unauthorized and forbidden error helpers to http-errors.ts - Register diagnosisRoutes in main app Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
5eb195cbeb
commit
1a36ae5cbc
@@ -5,6 +5,20 @@ export function badRequest(message: string): Response {
|
||||
});
|
||||
}
|
||||
|
||||
export function unauthorized(message: string): Response {
|
||||
return new Response(JSON.stringify({ error: message }), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
export function forbidden(message: string): Response {
|
||||
return new Response(JSON.stringify({ error: message }), {
|
||||
status: 403,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
export function notFound(message: string): Response {
|
||||
return new Response(JSON.stringify({ error: message }), {
|
||||
status: 404,
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
import type { UploaderMetadata } from '@zeavis/shared';
|
||||
import { env } from '../config/env';
|
||||
|
||||
export async function uploadImageToStorage(file: File): Promise<UploaderMetadata> {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('fileName', file.name);
|
||||
|
||||
const response = await fetch('https://upload.asepharyana.tech/api/upload', {
|
||||
const response = await fetch(`${env.uploaderBaseUrl}/api/upload`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Upload failed with status ${response.status}`);
|
||||
throw new Error(`Upload failed with HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const data = (await response.json()) as Record<string, unknown>;
|
||||
const payload = await response.json() as Partial<UploaderMetadata>;
|
||||
|
||||
if (!data.download_url) {
|
||||
throw new Error('Upload response missing download_url');
|
||||
if (!payload.public_id || !payload.download_url || !payload.file_name || !payload.mime_type || typeof payload.size_bytes !== 'number') {
|
||||
throw new Error('Upload response is missing required metadata');
|
||||
}
|
||||
|
||||
if (!data.public_id) {
|
||||
throw new Error('Upload response missing public_id');
|
||||
}
|
||||
|
||||
return data as UploaderMetadata;
|
||||
return {
|
||||
public_id: payload.public_id,
|
||||
telegram_file_id: payload.telegram_file_id,
|
||||
telegram_file_unique_id: payload.telegram_file_unique_id,
|
||||
file_name: payload.file_name,
|
||||
mime_type: payload.mime_type,
|
||||
size_bytes: payload.size_bytes,
|
||||
file_type: payload.file_type ?? 'image',
|
||||
download_url: payload.download_url,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user