| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109 |
- /**
- * 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<boolean>}
- */
- 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<ApiError>}
- */
- 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,
- });
- }
|