60 lines
1.8 KiB
TypeScript
60 lines
1.8 KiB
TypeScript
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<SaveCreateFlowUploadResult | SaveCreateFlowUploadFailure> {
|
|
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,
|
|
};
|
|
}
|