67 lines
src/api/uploads/processUpload.ts
Validates an uploaded file, stores it in object storage, and records metadata in the database.
// POST /api/uploads — validates, stores, and records a user file upload.import type { Request, Response } from "express";import { storage } from "./storage";import { db } from "./db";const MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024; // 10 MBconst ALLOWED_MIME_TYPES = ["image/jpeg", "image/png", "application/pdf"];export interface UploadRequest { filename: string; mimeType: string; sizeBytes: number; ownerId: string; data: Buffer;}
// Builds the object storage key for a user upload.function buildStorageKey(upload: UploadRequest): string { return `${upload.ownerId}/${Date.now()}-${upload.filename}`;}
/** * Processes an incoming file upload: * 1. Validates file type and size. * 2. Stores the file in object storage. * 3. Records the file metadata in the database.*
* The metadata record must only be created after the storage write succeeds. * If the metadata write fails after a successful storage write, the stored* file must be deleted to prevent orphaned storage objects.
*/
// Handles validation, storage, and metadata creation for one upload.export async function processUpload( req: Request, res: Response,): Promise<void> { const upload = req.body as UploadRequest; if (!ALLOWED_MIME_TYPES.includes(upload.mimeType)) { res.status(415).json({ error: "Unsupported file type" }); return;}
if (upload.sizeBytes > MAX_FILE_SIZE_BYTES) { res.status(413).json({ error: "File too large" }); return;}
const storageKey = buildStorageKey(upload); const fileId = await db.files.create({ ownerId: upload.ownerId, filename: upload.filename,storageKey,
sizeBytes: upload.sizeBytes, mimeType: upload.mimeType,});
try { await storage.put(storageKey, upload.data, upload.mimeType); } catch { res.status(500).json({ error: "Storage write failed" }); return;}
res.status(201).json({ fileId, storageKey });}