/** * Map filesystem/storage errors into standardized ApiErrors. * * Problem: * - `fs.readdir()` throws ENOENT when a path does not exist. * - Without mapping, routes often return 500 with `error.message` (inconsistent). * * Goal: * - If a requested branch/year/month/day path does not exist => return 404. * - But if the NAS root itself is not available/misconfigured => return 500. * * This avoids: * - Incorrect 500s for normal “not found” cases. * - Misleading 404s when the whole NAS is down. */ import fs from "node:fs/promises"; import { ApiError } from "./errors.js"; /** * Check whether an error looks like a “path not found” error from Node’s fs. * ENOENT: No such file or directory * ENOTDIR: A path segment is not a directory * * @param {unknown} err * @returns {boolean} */ export function isFsNotFoundError(err) { return Boolean( err && typeof err === "object" && (err.code === "ENOENT" || err.code === "ENOTDIR") ); } /** * Determine if the configured NAS root path is accessible. * * Rationale: * - If NAS_ROOT_PATH is missing or unreachable, “not found” below it * is likely a system issue => return 500 instead of 404. * * @returns {Promise} */ async function isNasRootAccessible() { const root = process.env.NAS_ROOT_PATH; if (!root) return false; try { // access() succeeds if the path exists and is accessible. await fs.access(root); return true; } catch { return false; } } /** * Convert errors from the storage layer into ApiErrors. * * Policy: * - ENOENT/ENOTDIR: * - If NAS root accessible => 404 FS_NOT_FOUND (requested resource missing) * - Else => 500 FS_STORAGE_ERROR (system/config issue) * - Anything else => 500 FS_STORAGE_ERROR * * @param {unknown} err * @param {{ * notFoundCode?: string, * notFoundMessage?: string, * details?: any * }=} options * @returns {Promise} */ export async function mapStorageReadError( err, { notFoundCode = "FS_NOT_FOUND", notFoundMessage = "Not found", details } = {} ) { if (isFsNotFoundError(err)) { const rootOk = await isNasRootAccessible(); // If the NAS is not accessible, treat it as an internal failure. if (!rootOk) { return new ApiError({ status: 500, code: "FS_STORAGE_ERROR", message: "Internal server error", cause: err, }); } // NAS root exists => the specific requested path is missing => 404. return new ApiError({ status: 404, code: notFoundCode, message: notFoundMessage, details, cause: err, }); } // For all other filesystem errors, return a generic 500. return new ApiError({ status: 500, code: "FS_STORAGE_ERROR", message: "Internal server error", cause: err, }); }