From 72fe367988be2d917e47ef765a43c523b26d3c10 Mon Sep 17 00:00:00 2001 From: perf3ct Date: Sun, 9 Feb 2025 21:15:01 +0000 Subject: [PATCH 1/5] Add @types/js-yaml, @types/swagger-ui-express, js-yaml, and swagger-ui-express to support Swagger UI --- package-lock.json | 68 +++++++++++++++++++++++++++++++++++++++-------- package.json | 4 +++ 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 230273b13..154c511d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,8 +17,10 @@ "@mermaid-js/layout-elk": "0.1.7", "@mind-elixir/node-menu": "1.0.4", "@triliumnext/express-partial-content": "1.0.1", + "@types/js-yaml": "4.0.9", "@types/leaflet": "1.9.16", "@types/react-dom": "18.3.5", + "@types/swagger-ui-express": "4.1.7", "archiver": "7.0.1", "async-mutex": "0.5.0", "autocomplete.js": "0.38.1", @@ -64,6 +66,7 @@ "jquery": "3.7.1", "jquery-hotkeys": "0.2.2", "jquery.fancytree": "2.38.4", + "js-yaml": "4.1.0", "jsdom": "26.0.0", "jsplumb": "2.15.6", "katex": "0.16.21", @@ -92,6 +95,7 @@ "split.js": "1.6.5", "stream-throttle": "0.1.3", "striptags": "3.2.0", + "swagger-ui-express": "5.0.1", "tmp": "0.2.3", "ts-loader": "9.5.2", "turndown": "7.2.0", @@ -3437,6 +3441,12 @@ "win32" ] }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true + }, "node_modules/@shikijs/engine-oniguruma": { "version": "1.24.2", "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.24.2.tgz", @@ -3556,7 +3566,6 @@ "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", - "dev": true, "license": "MIT", "dependencies": { "@types/connect": "*", @@ -3619,7 +3628,6 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -3946,7 +3954,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", @@ -3959,7 +3966,6 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz", "integrity": "sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -4034,7 +4040,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "dev": true, "license": "MIT" }, "node_modules/@types/ini": { @@ -4061,6 +4066,11 @@ "@types/sizzle": "*" } }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==" + }, "node_modules/@types/jsdom": { "version": "21.1.7", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", @@ -4146,7 +4156,6 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, "license": "MIT" }, "node_modules/@types/mime-types": { @@ -4193,14 +4202,12 @@ "version": "6.9.17", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/react": { @@ -4272,7 +4279,6 @@ "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", - "dev": true, "license": "MIT", "dependencies": { "@types/mime": "^1", @@ -4293,7 +4299,6 @@ "version": "1.15.7", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", - "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", @@ -4339,6 +4344,15 @@ "@types/node": "*" } }, + "node_modules/@types/swagger-ui-express": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.7.tgz", + "integrity": "sha512-ovLM9dNincXkzH4YwyYpll75vhzPBlWx6La89wwvYH7mHjVpf0X0K/vR/aUM7SRxmr5tt9z7E5XJcjQ46q+S3g==", + "dependencies": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, "node_modules/@types/tmp": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.6.tgz", @@ -5173,7 +5187,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/array-flatten": { @@ -11540,6 +11553,17 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/js2xmlparser": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", @@ -16143,6 +16167,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-ui-dist": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.18.3.tgz", + "integrity": "sha512-G33HFW0iFNStfY2x6QXO2JYVMrFruc8AZRX0U/L71aA7WeWfX2E5Nm8E/tsipSZJeIZZbSjUDeynLK/wcuNWIw==", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", diff --git a/package.json b/package.json index 3f817873f..801f7a31a 100644 --- a/package.json +++ b/package.json @@ -66,8 +66,10 @@ "@mermaid-js/layout-elk": "0.1.7", "@mind-elixir/node-menu": "1.0.4", "@triliumnext/express-partial-content": "1.0.1", + "@types/js-yaml": "4.0.9", "@types/leaflet": "1.9.16", "@types/react-dom": "18.3.5", + "@types/swagger-ui-express": "4.1.7", "archiver": "7.0.1", "async-mutex": "0.5.0", "autocomplete.js": "0.38.1", @@ -113,6 +115,7 @@ "jquery": "3.7.1", "jquery-hotkeys": "0.2.2", "jquery.fancytree": "2.38.4", + "js-yaml": "4.1.0", "jsdom": "26.0.0", "jsplumb": "2.15.6", "katex": "0.16.21", @@ -141,6 +144,7 @@ "split.js": "1.6.5", "stream-throttle": "0.1.3", "striptags": "3.2.0", + "swagger-ui-express": "5.0.1", "tmp": "0.2.3", "ts-loader": "9.5.2", "turndown": "7.2.0", From 18f5f1b759a81f936c00cea7eb0ec56287240392 Mon Sep 17 00:00:00 2001 From: perf3ct Date: Sun, 9 Feb 2025 21:15:12 +0000 Subject: [PATCH 2/5] add Swagger UI endpoint and add to router --- src/routes/api_docs.ts | 26 ++++++++++++++++++++++++++ src/routes/routes.ts | 5 ++++- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/routes/api_docs.ts diff --git a/src/routes/api_docs.ts b/src/routes/api_docs.ts new file mode 100644 index 000000000..7546c50d1 --- /dev/null +++ b/src/routes/api_docs.ts @@ -0,0 +1,26 @@ +import type { Router } from "express"; +import swaggerUi from "swagger-ui-express"; +import { readFileSync } from "fs"; +import { fileURLToPath } from "url"; +import { dirname, join } from "path"; +import yaml from "js-yaml"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const swaggerDocument = yaml.load( + readFileSync(join(__dirname, "../etapi/etapi.openapi.yaml"), "utf8") +) as object; + +function register(router: Router) { + router.use( + "/api-docs", + swaggerUi.serve, + swaggerUi.setup(swaggerDocument, { + explorer: true, + customSiteTitle: "Trilium ETAPI Documentation" + }) + ); +} + +export default { + register +}; diff --git a/src/routes/routes.ts b/src/routes/routes.ts index 1de332be0..05c7612f2 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -71,7 +71,7 @@ import etapiSpecialNoteRoutes from "../etapi/special_notes.js"; import etapiSpecRoute from "../etapi/spec.js"; import etapiBackupRoute from "../etapi/backup.js"; - +import apiDocsRoute from "./api_docs.js"; const MAX_ALLOWED_FILE_SIZE_MB = 250; const GET = "get", @@ -369,6 +369,9 @@ function register(app: express.Application) { etapiSpecRoute.register(router); etapiBackupRoute.register(router); + // API Documentation + apiDocsRoute.register(app); + app.use("", router); } From d859f50c9881fa101d21b113c08d703ee1253049 Mon Sep 17 00:00:00 2001 From: perf3ct Date: Sun, 9 Feb 2025 22:17:31 +0000 Subject: [PATCH 3/5] Change Swagger endpoint and site title --- src/routes/api_docs.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/api_docs.ts b/src/routes/api_docs.ts index 7546c50d1..f2fb9e08e 100644 --- a/src/routes/api_docs.ts +++ b/src/routes/api_docs.ts @@ -12,11 +12,11 @@ const swaggerDocument = yaml.load( function register(router: Router) { router.use( - "/api-docs", + "/etapi", swaggerUi.serve, swaggerUi.setup(swaggerDocument, { explorer: true, - customSiteTitle: "Trilium ETAPI Documentation" + customSiteTitle: "TriliumNext ETAPI Documentation" }) ); } From 57a34e5c025fea04c69b4675c150e779de48dc90 Mon Sep 17 00:00:00 2001 From: perf3ct Date: Mon, 10 Feb 2025 00:50:43 +0000 Subject: [PATCH 4/5] use fs/promises instead of fs --- src/routes/api_docs.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/api_docs.ts b/src/routes/api_docs.ts index f2fb9e08e..4f8e65b52 100644 --- a/src/routes/api_docs.ts +++ b/src/routes/api_docs.ts @@ -1,13 +1,13 @@ import type { Router } from "express"; import swaggerUi from "swagger-ui-express"; -import { readFileSync } from "fs"; +import { readFile } from "fs/promises"; import { fileURLToPath } from "url"; import { dirname, join } from "path"; import yaml from "js-yaml"; const __dirname = dirname(fileURLToPath(import.meta.url)); const swaggerDocument = yaml.load( - readFileSync(join(__dirname, "../etapi/etapi.openapi.yaml"), "utf8") + await readFile(join(__dirname, "../etapi/etapi.openapi.yaml"), "utf8") ) as object; function register(router: Router) { From 9c86a03accf835a42e5267f6bee65aa8dbd9419c Mon Sep 17 00:00:00 2001 From: perf3ct Date: Mon, 10 Feb 2025 16:03:01 +0000 Subject: [PATCH 5/5] Do this thing to make Pano happy :) --- src/routes/api_docs.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes/api_docs.ts b/src/routes/api_docs.ts index 4f8e65b52..1535265c3 100644 --- a/src/routes/api_docs.ts +++ b/src/routes/api_docs.ts @@ -4,11 +4,12 @@ import { readFile } from "fs/promises"; import { fileURLToPath } from "url"; import { dirname, join } from "path"; import yaml from "js-yaml"; +import type { JsonObject } from "swagger-ui-express"; const __dirname = dirname(fileURLToPath(import.meta.url)); const swaggerDocument = yaml.load( await readFile(join(__dirname, "../etapi/etapi.openapi.yaml"), "utf8") -) as object; +) as JsonObject; function register(router: Router) { router.use(