import { createHash } from "node:crypto"; import { mkdir, readFile, writeFile } from "node:fs/promises"; import path from "node:path"; import type { StoreAttachmentParams, StoredAttachmentRecord } from "@/lib/boss-storage"; import { sanitizeFileName } from "@/lib/boss-attachments"; function detectRuntimeRoot(startDir: string) { let current = startDir; while (true) { if ( path.basename(current) === "boss" && path.basename(path.dirname(current)) === "code" ) { return current; } const parent = path.dirname(current); if (parent === current) { return startDir; } current = parent; } } function resolveRuntimeRoot() { if (process.env.BOSS_RUNTIME_ROOT?.trim()) { return path.resolve(process.env.BOSS_RUNTIME_ROOT); } if (process.env.BOSS_STATE_FILE?.trim()) { return path.dirname(path.dirname(path.resolve(process.env.BOSS_STATE_FILE))); } return detectRuntimeRoot(process.cwd()); } function getUploadsRoot() { return path.resolve(resolveRuntimeRoot(), "data", "uploads"); } function accountStorageSegment(account: string) { const normalized = account.normalize("NFKC").trim(); const digest = createHash("sha256").update(normalized).digest("hex").slice(0, 16); return `acct-${digest}`; } function normalizeUploadsRelativePath(storagePath: string) { const normalized = storagePath.replace(/\\/g, "/").replace(/^\/+/, ""); return normalized.replace(/^data\/uploads\/+/, ""); } function resolvePathWithinUploadsRoot(storagePath: string) { const uploadsRoot = getUploadsRoot(); const candidate = path.resolve(uploadsRoot, normalizeUploadsRelativePath(storagePath)); const relative = path.relative(uploadsRoot, candidate); if (relative.startsWith("..") || path.isAbsolute(relative)) { throw new Error("ATTACHMENT_STORAGE_PATH_OUTSIDE_UPLOADS_ROOT"); } return candidate; } export async function storeServerFileAttachment( params: StoreAttachmentParams, ): Promise { const now = new Date(); const relativePath = path.posix.join( "data", "uploads", accountStorageSegment(params.account), String(now.getUTCFullYear()), String(now.getUTCMonth() + 1).padStart(2, "0"), `${params.messageId}-${sanitizeFileName(params.fileName)}`, ); const absolutePath = resolvePathWithinUploadsRoot(relativePath); await mkdir(path.dirname(absolutePath), { recursive: true }); await writeFile(absolutePath, params.buffer); return { storageBackend: "server_file", storagePath: relativePath, }; } export function resolveServerFileAttachmentAbsolutePath(storagePath: string) { return resolvePathWithinUploadsRoot(storagePath); } export async function readServerFileAttachmentBuffer(storagePath: string) { return readFile(resolveServerFileAttachmentAbsolutePath(storagePath)); }