Automated OpenAPI spec generation

This commit is contained in:
FliegendeWurst 2025-02-13 17:02:03 +01:00
parent e7f5d1d280
commit be4b74e791
6 changed files with 415 additions and 0 deletions

51
bin/generate-openapi.js Normal file
View File

@ -0,0 +1,51 @@
import swaggerJsdoc from 'swagger-jsdoc';
/*
* Usage: npm run generate-openapi | tail -n1 > x.json
*
* Inspect generated file by opening it in https://editor-next.swagger.io/
*
*/
const options = {
definition: {
openapi: '3.1.1',
info: {
title: 'Trilium Notes - Sync server API',
version: '0.96.6',
description: "This is the internal sync server API used by Trilium Notes / TriliumNext Notes.\n\n_If you're looking for the officially supported External Trilium API, see [here](https://triliumnext.github.io/Docs/Wiki/etapi.html)._\n\nThis page does not yet list all routes. For a full list, see the [route controller](https://github.com/TriliumNext/Notes/blob/v0.91.6/src/routes/routes.ts).",
contact: {
name: "TriliumNext issue tracker",
url: "https://github.com/TriliumNext/Notes/issues",
},
license: {
name: "GNU Free Documentation License 1.3 (or later)",
url: "https://www.gnu.org/licenses/fdl-1.3",
},
},
},
apis: ['./src/routes/api/*.ts', './bin/generate-openapi.js'],
};
const openapiSpecification = swaggerJsdoc(options);
console.log(JSON.stringify(openapiSpecification));
/**
* @swagger
* components:
* schemas:
* UtcDateTime:
* type: string
* example: "2025-02-13T07:42:47.698Z"
* securitySchemes:
* user-password:
* type: apiKey
* name: trilium-cred
* in: header
* description: "Username and password, formatted as `user:password`"
* session:
* type: apiKey
* in: cookie
* name: trilium.sid
*/

227
package-lock.json generated
View File

@ -165,6 +165,7 @@
"prettier": "3.5.0", "prettier": "3.5.0",
"rcedit": "4.0.1", "rcedit": "4.0.1",
"rimraf": "6.0.1", "rimraf": "6.0.1",
"swagger-jsdoc": "6.2.8",
"tslib": "2.8.1", "tslib": "2.8.1",
"tsx": "4.19.2", "tsx": "4.19.2",
"typedoc": "0.27.7", "typedoc": "0.27.7",
@ -222,6 +223,54 @@
"url": "https://github.com/sponsors/antfu" "url": "https://github.com/sponsors/antfu"
} }
}, },
"node_modules/@apidevtools/json-schema-ref-parser": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz",
"integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jsdevtools/ono": "^7.1.3",
"@types/json-schema": "^7.0.6",
"call-me-maybe": "^1.0.1",
"js-yaml": "^4.1.0"
}
},
"node_modules/@apidevtools/openapi-schemas": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz",
"integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/@apidevtools/swagger-methods": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz",
"integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==",
"dev": true,
"license": "MIT"
},
"node_modules/@apidevtools/swagger-parser": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz",
"integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@apidevtools/json-schema-ref-parser": "^9.0.6",
"@apidevtools/openapi-schemas": "^2.0.4",
"@apidevtools/swagger-methods": "^3.0.2",
"@jsdevtools/ono": "^7.1.3",
"call-me-maybe": "^1.0.1",
"z-schema": "^5.0.1"
},
"peerDependencies": {
"openapi-types": ">=7"
}
},
"node_modules/@asamuzakjp/css-color": { "node_modules/@asamuzakjp/css-color": {
"version": "2.8.2", "version": "2.8.2",
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-2.8.2.tgz", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-2.8.2.tgz",
@ -2715,6 +2764,13 @@
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@jsdevtools/ono": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
"integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==",
"dev": true,
"license": "MIT"
},
"node_modules/@jsdoc/salty": { "node_modules/@jsdoc/salty": {
"version": "0.2.9", "version": "0.2.9",
"resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz", "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz",
@ -6021,6 +6077,13 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/call-me-maybe": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
"integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==",
"dev": true,
"license": "MIT"
},
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001689", "version": "1.0.30001689",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001689.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001689.tgz",
@ -7799,6 +7862,19 @@
"p-limit": "^3.1.0 " "p-limit": "^3.1.0 "
} }
}, },
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"esutils": "^2.0.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/dom-serializer": { "node_modules/dom-serializer": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
@ -8992,6 +9068,16 @@
"@types/estree": "^1.0.0" "@types/estree": "^1.0.0"
} }
}, },
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/etag": { "node_modules/etag": {
"version": "1.8.1", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@ -11962,6 +12048,21 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
"dev": true,
"license": "MIT"
},
"node_modules/lodash.mergewith": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
"integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==",
"dev": true,
"license": "MIT"
},
"node_modules/log-symbols": { "node_modules/log-symbols": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@ -13101,6 +13202,14 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/openapi-types": {
"version": "12.1.3",
"resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz",
"integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/ora": { "node_modules/ora": {
"version": "5.4.1", "version": "5.4.1",
"resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
@ -16088,6 +16197,82 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/swagger-jsdoc": {
"version": "6.2.8",
"resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz",
"integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"commander": "6.2.0",
"doctrine": "3.0.0",
"glob": "7.1.6",
"lodash.mergewith": "^4.6.2",
"swagger-parser": "^10.0.3",
"yaml": "2.0.0-1"
},
"bin": {
"swagger-jsdoc": "bin/swagger-jsdoc.js"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/swagger-jsdoc/node_modules/commander": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz",
"integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/swagger-jsdoc/node_modules/glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dev": true,
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/swagger-jsdoc/node_modules/yaml": {
"version": "2.0.0-1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz",
"integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">= 6"
}
},
"node_modules/swagger-parser": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz",
"integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@apidevtools/swagger-parser": "10.0.3"
},
"engines": {
"node": ">=10"
}
},
"node_modules/swagger-ui-dist": { "node_modules/swagger-ui-dist": {
"version": "5.18.3", "version": "5.18.3",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.18.3.tgz", "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.18.3.tgz",
@ -17187,6 +17372,16 @@
"spdx-expression-parse": "^3.0.0" "spdx-expression-parse": "^3.0.0"
} }
}, },
"node_modules/validator": {
"version": "13.12.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz",
"integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/value-equal": { "node_modules/value-equal": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
@ -18629,6 +18824,38 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/z-schema": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz",
"integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"lodash.get": "^4.4.2",
"lodash.isequal": "^4.5.0",
"validator": "^13.7.0"
},
"bin": {
"z-schema": "bin/z-schema"
},
"engines": {
"node": ">=8.0.0"
},
"optionalDependencies": {
"commander": "^9.4.1"
}
},
"node_modules/z-schema/node_modules/commander": {
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": "^12.20.0 || >=14"
}
},
"node_modules/zip-stream": { "node_modules/zip-stream": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz",

View File

@ -53,6 +53,7 @@
"integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts", "integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts", "integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"generate-document": "cross-env nodemon ./bin/generate_document.ts 1000", "generate-document": "cross-env nodemon ./bin/generate_document.ts 1000",
"generate-openapi": "node bin/generate-openapi.js",
"ci-update-nightly-version": "tsx ./bin/update-nightly-version.ts", "ci-update-nightly-version": "tsx ./bin/update-nightly-version.ts",
"prettier-check": "prettier . --check", "prettier-check": "prettier . --check",
"prettier-fix": "prettier . --write" "prettier-fix": "prettier . --write"
@ -211,6 +212,7 @@
"prettier": "3.5.0", "prettier": "3.5.0",
"rcedit": "4.0.1", "rcedit": "4.0.1",
"rimraf": "6.0.1", "rimraf": "6.0.1",
"swagger-jsdoc": "6.2.8",
"tslib": "2.8.1", "tslib": "2.8.1",
"tsx": "4.19.2", "tsx": "4.19.2",
"typedoc": "0.27.7", "typedoc": "0.27.7",

View File

@ -2,6 +2,51 @@
import appInfo from "../../services/app_info.js"; import appInfo from "../../services/app_info.js";
/**
* @swagger
* /api/app-info:
* get:
* summary: Get installation info
* operationId: app-info
* externalDocs:
* description: Server implementation
* url: https://github.com/TriliumNext/Notes/blob/v0.91.6/src/services/app_info.ts
* responses:
* '200':
* content:
* application/json:
* schema:
* type: object
* properties:
* appVersion:
* type: string
* example: "0.91.6"
* dbVersion:
* type: integer
* example: 228
* nodeVersion:
* type: string
* description: "value of process.version"
* syncVersion:
* type: integer
* example: 34
* buildDate:
* type: string
* example: "2024-09-07T18:36:34Z"
* buildRevision:
* type: string
* example: "7c0d6930fa8f20d269dcfbcbc8f636a25f6bb9a7"
* dataDirectory:
* type: string
* example: "/var/lib/trilium"
* clipperProtocolVersion:
* type: string
* example: "1.0"
* utcDateTime:
* $ref: '#/components/schemas/UtcDateTime'
* security:
* - session: []
*/
function getAppInfo() { function getAppInfo() {
return appInfo; return appInfo;
} }

View File

@ -14,6 +14,68 @@ import ws from "../../services/ws.js";
import etapiTokenService from "../../services/etapi_tokens.js"; import etapiTokenService from "../../services/etapi_tokens.js";
import type { Request } from "express"; import type { Request } from "express";
/**
* @swagger
* /api/login/sync:
* post:
* tags:
* - auth
* summary: Log in using documentSecret
* description: The `hash` parameter is computed using a HMAC of the `documentSecret` and `timestamp`.
* operationId: login-sync
* externalDocs:
* description: HMAC calculation
* url: https://github.com/TriliumNext/Notes/blob/v0.91.6/src/services/utils.ts#L62-L66
* requestBody:
* content:
* application/json:
* schema:
* type: object
* properties:
* timestamp:
* $ref: '#/components/schemas/UtcDateTime'
* hash:
* type: string
* syncVersion:
* type: integer
* example: 34
* responses:
* '200':
* description: Successful operation
* content:
* application/json:
* schema:
* type: object
* properties:
* syncVersion:
* type: integer
* example: 34
* options:
* type: object
* properties:
* documentSecret:
* type: string
* '400':
* description: Sync version / document secret mismatch
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: "Non-matching sync versions, local is version ${server syncVersion}, remote is ${requested syncVersion}. It is recommended to run same version of Trilium on both sides of sync"
* '401':
* description: Timestamp mismatch
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: "Auth request time is out of sync, please check that both client and server have correct time. The difference between clocks has to be smaller than 5 minutes"
*/
function loginSync(req: Request) { function loginSync(req: Request) {
if (!sqlInit.schemaExists()) { if (!sqlInit.schemaExists()) {
return [500, { message: "DB schema does not exist, can't sync." }]; return [500, { message: "DB schema does not exist, can't sync." }];

View File

@ -45,6 +45,34 @@ function saveSyncSeed(req: Request) {
sqlInit.createDatabaseForSync(options); sqlInit.createDatabaseForSync(options);
} }
/**
* @swagger
* /api/setup/sync-seed:
* get:
* tags:
* - auth
* summary: Sync documentSecret value
* description: First step to logging in.
* operationId: setup-sync-seed
* responses:
* '200':
* description: Successful operation
* content:
* application/json:
* schema:
* type: object
* properties:
* syncVersion:
* type: integer
* example: 34
* options:
* type: object
* properties:
* documentSecret:
* type: string
* security:
* - user-password: []
*/
function getSyncSeed() { function getSyncSeed() {
log.info("Serving sync seed."); log.info("Serving sync seed.");