Files
community-rule/lib/server/uploads/saveCreateFlowUpload.ts
T

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,
};
}