feat(frontend): add storage service for S3 file uploads
- Create storage.ts with pre-signed URL handling - Implement getUploadUrl, uploadFileToS3 helper functions - Add complete uploadFile workflow for easy file uploads - Support logos, resumes, documents, avatars folders
This commit is contained in:
parent
ce6e35aefd
commit
9630730d69
1 changed files with 143 additions and 0 deletions
143
frontend/src/lib/storage.ts
Normal file
143
frontend/src/lib/storage.ts
Normal file
|
|
@ -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<UploadUrlResponse> {
|
||||||
|
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<void> {
|
||||||
|
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<DownloadUrlResponse> {
|
||||||
|
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<void> {
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue