import { writeFile } from "node:fs/promises"; import path from "node:path"; import { randomUUID } from "node:crypto"; import type { CreateFlowUploadPurpose } from "./uploadConstants"; import { extensionForMime } from "./uploadConstants"; import { ensureUploadRootExists, getUploadRootFromEnv } from "./uploadRoot"; import { validateCreateFlowUploadBytes, type CreateFlowUploadValidationReason, } from "../../create/createFlowUploadValidation"; export type SaveCreateFlowUploadResult = { /** Filename stem (UUID) without extension — used in GET URL. */ id: string; /** Full relative URL path for clients, e.g. `/api/uploads/abc`. */ urlPath: string; mimeType: string; byteLength: number; }; export type SaveCreateFlowUploadFailure = { error: "misconfigured" | "validation"; reason?: CreateFlowUploadValidationReason; }; /** * Writes bytes under `UPLOAD_ROOT/{id}{ext}` and returns a stable app URL path. * Trusts sniffed bytes, not the client-declared MIME type. */ export async function saveCreateFlowUpload(params: { purpose: CreateFlowUploadPurpose; buffer: Buffer; }): Promise { const root = getUploadRootFromEnv(); if (!root) { return { error: "misconfigured" }; } const { purpose, buffer } = params; const validated = validateCreateFlowUploadBytes(purpose, buffer); if (validated.ok === false) { return { error: "validation", reason: validated.reason }; } const id = randomUUID(); const ext = extensionForMime(validated.mimeType); const fileName = `${id}${ext}`; const absolutePath = path.join(root, fileName); await ensureUploadRootExists(root); await writeFile(absolutePath, buffer, { mode: 0o644 }); return { id, urlPath: `/api/uploads/${id}`, mimeType: validated.mimeType, byteLength: buffer.length, }; }