/** * Photo Upload API Client */ import api from './api'; import { compressPhoto, compressPhotos, CompressedPhoto } from './photoCompression'; export interface UploadedPhoto { success: boolean; photoId: string; urls: { thumb: string; medium: string; full: string; }; metadata: { originalSize: number; originalFormat: string; originalDimensions: { width: number; height: number }; compressedSizes: Record; savingsPercent: number; format: string; }; uploadedAt: string; } export interface BulkUploadResult { success: boolean; uploaded: number; failed: number; results: Array<{ photoId: string; filename: string; urls: Record; }>; errors?: Array<{ filename: string; error: string; }>; } /** * Upload a single photo with compression */ export async function uploadPhoto( file: File, onProgress?: (progress: number) => void ): Promise { // Compress client-side first const compressed = await compressPhoto(file); const formData = new FormData(); formData.append('file', compressed.file, file.name); const response = await api.post('/api/upload/photo', formData, { headers: { 'Content-Type': 'multipart/form-data' }, onUploadProgress: (progressEvent) => { if (progressEvent.total && onProgress) { const progress = Math.round((progressEvent.loaded / progressEvent.total) * 100); onProgress(progress); } } }); return response.data; } /** * Upload multiple photos with compression */ export async function uploadPhotos( files: File[], onProgress?: (completed: number, total: number) => void ): Promise { // Compress all files first const compressed = await compressPhotos(files, {}, (completed, total) => { onProgress?.(completed, total * 2); // First half is compression }); const formData = new FormData(); compressed.forEach((photo, index) => { formData.append('files', photo.file, files[index].name); }); const response = await api.post('/api/upload/photos', formData, { headers: { 'Content-Type': 'multipart/form-data' }, onUploadProgress: (progressEvent) => { if (progressEvent.total && onProgress) { const uploadProgress = Math.round((progressEvent.loaded / progressEvent.total) * 100); // Second half is upload (50-100) onProgress?.(files.length + Math.round(files.length * uploadProgress / 100), files.length * 2); } } }); return response.data; } /** * Get photo URL (with auth token if needed) */ export function getPhotoUrl(path: string, size: 'thumb' | 'medium' | 'full' = 'medium'): string { // If it's already a full URL, return as-is if (path.startsWith('http')) return path; // If path includes size, return with api prefix if (path.includes('/thumb.webp') || path.includes('/medium.webp') || path.includes('/full.webp')) { return `/api/upload/photo${path.replace('/photos', '')}`; } // Otherwise, assume it's a photoId and construct the URL return `/api/upload/photo/${path}/${size}.webp`; } /** * Delete a photo */ export async function deletePhoto(photoId: string): Promise { await api.delete(`/api/upload/photo/${photoId}`); } /** * Get upload statistics */ export async function getUploadStats(): Promise<{ storagePath: string; diskUsage: string; sizes: Record; }> { const response = await api.get('/api/upload/stats'); return response.data; }