/** * Upload API Methods * * Domain-friendly API methods for file upload operations. * Handles multipart/form-data uploads with progress tracking. * * Key principles: * - Accepts browser File objects directly * - Returns simple result objects (no complex domain entities) * - Supports upload progress callbacks * - Uses @archer/api-interface endpoints for type safety * * @layer Infrastructure - API Client */ import { UPLOAD_AVATAR, UPLOAD_FILE, UPLOAD_FILE_GET, UPLOAD_FILE_DOWNLOAD, UPLOAD_FILE_DELETE, } from "@archer/api-interface/endpoints/customer-api"; import { apiClient } from '../../ApiClient'; import type { UploadProgressCallback } from '../shared/types'; /** * Upload Result * * Simple result object returned by upload operations */ export interface UploadResult { /** * Uploaded file ID (for retrieval/deletion) */ id: string; /** * Uploaded file URL (for display) */ url: string; } /** * File Metadata * * File information returned by get operations */ export interface FileMetadata { /** * File ID */ id: string; /** * Original filename */ filename: string; /** * File size in bytes */ size: number; /** * MIME type */ mimeType: string; /** * Upload timestamp */ uploadedAt: Date; } /** * Upload API * * File upload operations with domain-friendly interface */ export const uploadApi = { /** * Upload Avatar Image * * Uploads user avatar image file (JPEG, PNG, GIF). * Automatically resizes to standard dimensions. * Max file size: 5MB * * @param file - Browser File object * @param onProgress - Optional progress callback * @returns Upload result with file ID and URL * * @example * ```typescript * // Upload avatar with progress tracking * const result = await uploadApi.uploadAvatar(avatarFile, (progress) => { * console.log(`Upload: ${progress.percentage}%`); * }); * console.log(`Avatar uploaded: ${result.url}`); * ``` */ async uploadAvatar( file: File, onProgress?: UploadProgressCallback ): Promise { // Create FormData with file const formData = new FormData(); formData.append('file', file); // Configure axios for file upload with progress tracking const response = await apiClient.post<{ message: string }>( UPLOAD_AVATAR.path, formData, { headers: { 'Content-Type': 'multipart/form-data', }, onUploadProgress: onProgress ? (progressEvent) => { const total = progressEvent.total || 0; const loaded = progressEvent.loaded || 0; const percentage = total > 0 ? Math.round((loaded / total) * 100) : 0; onProgress({ loaded, total, percentage, }); } : undefined, } ); // Parse response message to extract ID and URL // Expected format: "File uploaded: {id}" or "Avatar uploaded at {url}" const message = response.message || ''; // Extract ID from message (assuming format like "File uploaded: abc123") const idMatch = message.match(/:\s*([a-zA-Z0-9-]+)/); const id = idMatch ? idMatch[1] : ''; // For avatar, the URL is typically the endpoint path + id const url = `/upload/avatar/${id}`; return { id, url }; }, /** * Upload General File * * Uploads general file (documents, images, etc.). * Accepts any file type. * Max file size: 10MB * * @param file - Browser File object * @param onProgress - Optional progress callback * @returns Upload result with file ID and URL * * @example * ```typescript * // Upload document with progress * const result = await uploadApi.uploadFile(pdfFile, (progress) => { * setUploadProgress(progress.percentage); * }); * * // Later retrieve file * const fileUrl = `/upload/files/${result.id}`; * ``` */ async uploadFile( file: File, onProgress?: UploadProgressCallback ): Promise { // Create FormData with file const formData = new FormData(); formData.append('file', file); // Configure axios for file upload with progress tracking const response = await apiClient.post<{ message: string }>( UPLOAD_FILE.path, formData, { headers: { 'Content-Type': 'multipart/form-data', }, onUploadProgress: onProgress ? (progressEvent) => { const total = progressEvent.total || 0; const loaded = progressEvent.loaded || 0; const percentage = total > 0 ? Math.round((loaded / total) * 100) : 0; onProgress({ loaded, total, percentage, }); } : undefined, } ); // Parse response message to extract ID const message = response.message || ''; const idMatch = message.match(/:\s*([a-zA-Z0-9-]+)/); const id = idMatch ? idMatch[1] : ''; // Construct URL for file access const url = `/upload/files/${id}`; return { id, url }; }, /** * Get File Metadata * * Retrieves file metadata by file ID. * Returns filename, size, MIME type, and upload timestamp. * * @param id - File ID from upload result * @returns File metadata * * @example * ```typescript * const metadata = await uploadApi.getFileMetadata(fileId); * console.log(`File: ${metadata.filename} (${metadata.size} bytes)`); * ``` */ async getFileMetadata(id: string): Promise { const endpoint = UPLOAD_FILE_GET.path.replace(':id', id); const response = await apiClient.get<{ message: string; data?: { id: string; filename: string; size: number; mimeType: string; uploadedAt: string; }; }>(endpoint); // If data is embedded in response, use it if (response.data) { return { id: response.data.id, filename: response.data.filename, size: response.data.size, mimeType: response.data.mimeType, uploadedAt: new Date(response.data.uploadedAt), }; } // Fallback: parse message for basic info (if API returns minimal data) return { id, filename: 'unknown', size: 0, mimeType: 'application/octet-stream', uploadedAt: new Date(), }; }, /** * Download File * * Downloads file content by file ID. * Returns raw file data as Blob. * * @param id - File ID from upload result * @returns File content as Blob * * @example * ```typescript * const blob = await uploadApi.downloadFile(fileId); * * // Create download link * const url = URL.createObjectURL(blob); * const a = document.createElement('a'); * a.href = url; * a.download = 'filename.pdf'; * a.click(); * URL.revokeObjectURL(url); * ``` */ async downloadFile(id: string): Promise { const endpoint = UPLOAD_FILE_DOWNLOAD.path.replace(':id', id); // Request file as blob (binary data) const blob = await apiClient.get(endpoint, { responseType: 'blob', }); return blob; }, /** * Delete File * * Permanently deletes uploaded file from storage. * Removes file data and metadata. This action is irreversible. * * @param id - File ID from upload result * * @example * ```typescript * await uploadApi.deleteFile(fileId); * console.log('File deleted successfully'); * ``` */ async deleteFile(id: string): Promise { const endpoint = UPLOAD_FILE_DELETE.path.replace(':id', id); await apiClient.delete<{ message: string }>(endpoint); }, };