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 MB
const 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 });
}