diff --git a/frontend/src/lib/storage.ts b/frontend/src/lib/storage.ts new file mode 100644 index 0000000..fe94c8f --- /dev/null +++ b/frontend/src/lib/storage.ts @@ -0,0 +1,143 @@ +/** + * Storage service for S3 file uploads via pre-signed URLs + */ + +const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080/api/v1"; + +interface UploadUrlResponse { + uploadUrl: string; + key: string; + publicUrl: string; + expiresIn: number; +} + +interface DownloadUrlResponse { + downloadUrl: string; + expiresIn: number; +} + +/** + * Get a pre-signed URL for uploading a file + */ +export async function getUploadUrl( + filename: string, + contentType: string, + folder: 'logos' | 'resumes' | 'documents' | 'avatars' = 'documents' +): Promise { + const token = typeof window !== 'undefined' ? localStorage.getItem('auth_token') : null; + + if (!token) { + throw new Error('Not authenticated'); + } + + const response = await fetch(`${API_URL}/storage/upload-url`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + body: JSON.stringify({ + filename, + contentType, + folder, + }), + }); + + if (!response.ok) { + const error = await response.text(); + throw new Error(`Failed to get upload URL: ${error}`); + } + + return response.json(); +} + +/** + * Upload a file directly to S3 using a pre-signed URL + */ +export async function uploadFileToS3( + file: File, + uploadUrl: string +): Promise { + const response = await fetch(uploadUrl, { + method: 'PUT', + headers: { + 'Content-Type': file.type, + }, + body: file, + }); + + if (!response.ok) { + throw new Error(`Failed to upload file: ${response.statusText}`); + } +} + +/** + * Get a pre-signed URL for downloading a file + */ +export async function getDownloadUrl(key: string): Promise { + const token = typeof window !== 'undefined' ? localStorage.getItem('auth_token') : null; + + if (!token) { + throw new Error('Not authenticated'); + } + + const response = await fetch(`${API_URL}/storage/download-url`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + body: JSON.stringify({ key }), + }); + + if (!response.ok) { + const error = await response.text(); + throw new Error(`Failed to get download URL: ${error}`); + } + + return response.json(); +} + +/** + * Complete file upload workflow: get pre-signed URL and upload file + * Returns the public URL of the uploaded file + */ +export async function uploadFile( + file: File, + folder: 'logos' | 'resumes' | 'documents' | 'avatars' = 'documents' +): Promise<{ key: string; publicUrl: string }> { + // Step 1: Get pre-signed upload URL from backend + const { uploadUrl, key, publicUrl } = await getUploadUrl( + file.name, + file.type, + folder + ); + + // Step 2: Upload file directly to S3 + await uploadFileToS3(file, uploadUrl); + + return { key, publicUrl }; +} + +/** + * Delete a file from storage + */ +export async function deleteFile(key: string): Promise { + const token = typeof window !== 'undefined' ? localStorage.getItem('auth_token') : null; + + if (!token) { + throw new Error('Not authenticated'); + } + + const response = await fetch(`${API_URL}/storage/files?key=${encodeURIComponent(key)}`, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${token}`, + }, + }); + + if (!response.ok) { + const error = await response.text(); + throw new Error(`Failed to delete file: ${error}`); + } +}