gohorsejobs/frontend/src/lib/storage.ts

125 lines
3.1 KiB
TypeScript

/**
* Storage service for S3 file uploads via pre-signed URLs
*/
import { getApiV1Url } from "./config";
// API_URL getter - uses runtime config
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 response = await fetch(`${getApiV1Url()}/storage/upload-url`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include', // Use httpOnly cookie
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 response = await fetch(`${getApiV1Url()}/storage/download-url`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include', // Use httpOnly cookie
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 response = await fetch(`${getApiV1Url()}/storage/files?key=${encodeURIComponent(key)}`, {
method: 'DELETE',
credentials: 'include', // Use httpOnly cookie
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Failed to delete file: ${error}`);
}
}