diff --git a/apps/server/src/routes/api_docs.ts b/apps/server/src/routes/api_docs.ts index d2fc1b04a..d20a6e70a 100644 --- a/apps/server/src/routes/api_docs.ts +++ b/apps/server/src/routes/api_docs.ts @@ -3,34 +3,53 @@ import swaggerUi from "swagger-ui-express"; import { join } from "path"; import yaml from "js-yaml"; import type { JsonObject } from "swagger-ui-express"; -import { readFileSync, existsSync } from "fs"; +import fs from "fs"; import { RESOURCE_DIR } from "../services/resource_dir"; import log from "../services/log"; -export default function register(app: Application) { +// Cache the documents to avoid repeated file reads, especially important for ASAR archives +let etapiDocument: JsonObject | null = null; +let apiDocument: JsonObject | null = null; + +function loadDocuments(): { etapi: JsonObject | null; api: JsonObject | null } { + if (etapiDocument && apiDocument) { + return { etapi: etapiDocument, api: apiDocument }; + } + try { const etapiPath = join(RESOURCE_DIR, "etapi.openapi.yaml"); const apiPath = join(RESOURCE_DIR, "api-openapi.yaml"); - // Check if files exist - if (!existsSync(etapiPath)) { - log.error(`ETAPI OpenAPI spec not found at: ${etapiPath}`); - return; - } - if (!existsSync(apiPath)) { - log.error(`API OpenAPI spec not found at: ${apiPath}`); - return; - } + // Load and cache the documents + const etapiYaml = fs.readFileSync(etapiPath, "utf8"); + etapiDocument = yaml.load(etapiYaml) as JsonObject; - const etapiDocument = yaml.load(readFileSync(etapiPath, "utf8")) as JsonObject; - const apiDocument = yaml.load(readFileSync(apiPath, "utf8")) as JsonObject; + const apiYaml = fs.readFileSync(apiPath, "utf8"); + apiDocument = yaml.load(apiYaml) as JsonObject; + + log.info("OpenAPI documents loaded successfully"); + return { etapi: etapiDocument, api: apiDocument }; + } catch (error) { + log.error(`Failed to load OpenAPI documents from ${RESOURCE_DIR}: ${error}`); + return { etapi: null, api: null }; + } +} + +export default function register(app: Application) { + try { + const docs = loadDocuments(); + + if (!docs.etapi || !docs.api) { + log.error("OpenAPI documents could not be loaded, skipping API documentation setup"); + return; + } // Use serveFiles for multiple Swagger instances // Note: serveFiles returns an array of middleware, so we need to spread it app.use( "/etapi/docs", - ...swaggerUi.serveFiles(etapiDocument), - swaggerUi.setup(etapiDocument, { + ...swaggerUi.serveFiles(docs.etapi), + swaggerUi.setup(docs.etapi, { explorer: true, customSiteTitle: "TriliumNext ETAPI Documentation" }) @@ -38,8 +57,8 @@ export default function register(app: Application) { app.use( "/api/docs", - ...swaggerUi.serveFiles(apiDocument), - swaggerUi.setup(apiDocument, { + ...swaggerUi.serveFiles(docs.api), + swaggerUi.setup(docs.api, { explorer: true, customSiteTitle: "TriliumNext Internal API Documentation", customCss: '.swagger-ui .topbar { display: none }'