mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 10:51:02 +01:00
feat(api): add health check and OpenAPI docs
- Implemented health check endpoint to monitor application status and components. - Added OpenAPI specification endpoint to serve the API documentation. - Introduced new TypeScript definitions for API paths and components.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"imports": {
|
||||
"$lib/": "./src/lib/",
|
||||
"$api/": "./src/lib/api/",
|
||||
"$config": "./src/lib/server/utils/config/config.ts",
|
||||
"$logger/": "./src/lib/server/utils/logger/",
|
||||
"$shared/": "./src/lib/shared/",
|
||||
@@ -22,7 +23,8 @@
|
||||
"marked": "npm:marked@^15.0.6",
|
||||
"simple-icons": "npm:simple-icons@^15.17.0",
|
||||
"highlight.js": "npm:highlight.js@^11.11.1",
|
||||
"croner": "npm:croner@^8.1.2"
|
||||
"croner": "npm:croner@^8.1.2",
|
||||
"@std/yaml": "jsr:@std/yaml@^1.0.10"
|
||||
},
|
||||
"tasks": {
|
||||
"dev": "DENO_ENV=development PORT=6969 HOST=0.0.0.0 APP_BASE_PATH=./dist/dev PARSER_HOST=localhost PARSER_PORT=5000 deno run -A npm:vite dev",
|
||||
@@ -32,7 +34,8 @@
|
||||
"format": "prettier --write .",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"test": "APP_BASE_PATH=./dist/test deno test src/tests --allow-read --allow-write --allow-env",
|
||||
"test:watch": "APP_BASE_PATH=./dist/test deno test src/tests --allow-read --allow-write --allow-env --watch"
|
||||
"test:watch": "APP_BASE_PATH=./dist/test deno test src/tests --allow-read --allow-write --allow-env --watch",
|
||||
"generate:api-types": "npx openapi-typescript docs/api/v1/openapi.yaml -o src/lib/api/v1.d.ts"
|
||||
},
|
||||
"compilerOptions": {
|
||||
"lib": ["deno.window", "dom"],
|
||||
|
||||
129
deno.lock
generated
129
deno.lock
generated
@@ -17,6 +17,7 @@
|
||||
"jsr:@std/path@0.217": "0.217.0",
|
||||
"jsr:@std/path@1": "1.1.2",
|
||||
"jsr:@std/path@^1.1.1": "1.1.2",
|
||||
"jsr:@std/yaml@^1.0.10": "1.0.10",
|
||||
"npm:@deno/vite-plugin@^1.0.5": "1.0.5_vite@7.1.12__@types+node@22.19.0__picomatch@4.0.3_@types+node@22.19.0",
|
||||
"npm:@eslint/compat@^1.4.0": "1.4.1_eslint@9.39.1",
|
||||
"npm:@eslint/js@^9.36.0": "9.39.1",
|
||||
@@ -36,6 +37,7 @@
|
||||
"npm:kysely@~0.27.2": "0.27.6",
|
||||
"npm:lucide-svelte@0.546": "0.546.0_svelte@5.43.3__acorn@8.15.0",
|
||||
"npm:marked@^15.0.6": "15.0.12",
|
||||
"npm:openapi-typescript@7": "7.10.1_typescript@5.9.3",
|
||||
"npm:prettier-plugin-svelte@^3.4.0": "3.4.0_prettier@3.6.2_svelte@5.43.3__acorn@8.15.0",
|
||||
"npm:prettier-plugin-tailwindcss@~0.6.14": "0.6.14_prettier@3.6.2_prettier-plugin-svelte@3.4.0__prettier@3.6.2__svelte@5.43.3___acorn@8.15.0_svelte@5.43.3__acorn@8.15.0",
|
||||
"npm:prettier@^3.6.2": "3.6.2",
|
||||
@@ -107,9 +109,23 @@
|
||||
"dependencies": [
|
||||
"jsr:@std/internal@^1.0.10"
|
||||
]
|
||||
},
|
||||
"@std/yaml@1.0.10": {
|
||||
"integrity": "245706ea3511cc50c8c6d00339c23ea2ffa27bd2c7ea5445338f8feff31fa58e"
|
||||
}
|
||||
},
|
||||
"npm": {
|
||||
"@babel/code-frame@7.28.6": {
|
||||
"integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==",
|
||||
"dependencies": [
|
||||
"@babel/helper-validator-identifier",
|
||||
"js-tokens",
|
||||
"picocolors"
|
||||
]
|
||||
},
|
||||
"@babel/helper-validator-identifier@7.28.5": {
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="
|
||||
},
|
||||
"@deno/vite-plugin@1.0.5_vite@7.1.12__@types+node@22.19.0__picomatch@4.0.3_@types+node@22.19.0": {
|
||||
"integrity": "sha512-tLja5n4dyMhcze1NzvSs2iiriBymfBlDCZIrjMTxb9O2ru0gvmV6mn5oBD2teNw5Sd92cj3YJzKwsAs8tMJXlg==",
|
||||
"dependencies": [
|
||||
@@ -564,6 +580,32 @@
|
||||
"@polka/url@1.0.0-next.29": {
|
||||
"integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="
|
||||
},
|
||||
"@redocly/ajv@8.17.1": {
|
||||
"integrity": "sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw==",
|
||||
"dependencies": [
|
||||
"fast-deep-equal",
|
||||
"fast-uri",
|
||||
"json-schema-traverse@1.0.0",
|
||||
"require-from-string"
|
||||
]
|
||||
},
|
||||
"@redocly/config@0.22.2": {
|
||||
"integrity": "sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ=="
|
||||
},
|
||||
"@redocly/openapi-core@1.34.6": {
|
||||
"integrity": "sha512-2+O+riuIUgVSuLl3Lyh5AplWZyVMNuG2F98/o6NrutKJfW4/GTZdPpZlIphS0HGgcOHgmWcCSHj+dWFlZaGSHw==",
|
||||
"dependencies": [
|
||||
"@redocly/ajv",
|
||||
"@redocly/config",
|
||||
"colorette",
|
||||
"https-proxy-agent",
|
||||
"js-levenshtein",
|
||||
"js-yaml",
|
||||
"minimatch@5.1.6",
|
||||
"pluralize",
|
||||
"yaml-ast-parser"
|
||||
]
|
||||
},
|
||||
"@rollup/rollup-android-arm-eabi@4.52.5": {
|
||||
"integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==",
|
||||
"os": ["android"],
|
||||
@@ -955,15 +997,21 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"bin": true
|
||||
},
|
||||
"agent-base@7.1.4": {
|
||||
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="
|
||||
},
|
||||
"ajv@6.12.6": {
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dependencies": [
|
||||
"fast-deep-equal",
|
||||
"fast-json-stable-stringify",
|
||||
"json-schema-traverse",
|
||||
"json-schema-traverse@0.4.1",
|
||||
"uri-js"
|
||||
]
|
||||
},
|
||||
"ansi-colors@4.1.3": {
|
||||
"integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="
|
||||
},
|
||||
"ansi-styles@4.3.0": {
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dependencies": [
|
||||
@@ -1008,9 +1056,12 @@
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dependencies": [
|
||||
"ansi-styles",
|
||||
"supports-color"
|
||||
"supports-color@7.2.0"
|
||||
]
|
||||
},
|
||||
"change-case@5.4.4": {
|
||||
"integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="
|
||||
},
|
||||
"chokidar@4.0.3": {
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"dependencies": [
|
||||
@@ -1029,6 +1080,9 @@
|
||||
"color-name@1.1.4": {
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"colorette@1.4.0": {
|
||||
"integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g=="
|
||||
},
|
||||
"concat-map@0.0.1": {
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||
},
|
||||
@@ -1277,6 +1331,9 @@
|
||||
"fast-levenshtein@2.0.6": {
|
||||
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="
|
||||
},
|
||||
"fast-uri@3.1.0": {
|
||||
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="
|
||||
},
|
||||
"fastq@1.19.1": {
|
||||
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
|
||||
"dependencies": [
|
||||
@@ -1356,6 +1413,13 @@
|
||||
"highlight.js@11.11.1": {
|
||||
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="
|
||||
},
|
||||
"https-proxy-agent@7.0.6": {
|
||||
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
|
||||
"dependencies": [
|
||||
"agent-base",
|
||||
"debug"
|
||||
]
|
||||
},
|
||||
"ignore@5.3.2": {
|
||||
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="
|
||||
},
|
||||
@@ -1372,6 +1436,9 @@
|
||||
"imurmurhash@0.1.4": {
|
||||
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="
|
||||
},
|
||||
"index-to-position@1.2.0": {
|
||||
"integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw=="
|
||||
},
|
||||
"is-extglob@2.1.1": {
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
|
||||
},
|
||||
@@ -1397,6 +1464,12 @@
|
||||
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
||||
"bin": true
|
||||
},
|
||||
"js-levenshtein@1.1.6": {
|
||||
"integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g=="
|
||||
},
|
||||
"js-tokens@4.0.0": {
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"js-yaml@4.1.0": {
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"dependencies": [
|
||||
@@ -1410,6 +1483,9 @@
|
||||
"json-schema-traverse@0.4.1": {
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||
},
|
||||
"json-schema-traverse@1.0.0": {
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
|
||||
},
|
||||
"json-stable-stringify-without-jsonify@1.0.1": {
|
||||
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="
|
||||
},
|
||||
@@ -1560,6 +1636,12 @@
|
||||
"brace-expansion@1.1.12"
|
||||
]
|
||||
},
|
||||
"minimatch@5.1.6": {
|
||||
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
||||
"dependencies": [
|
||||
"brace-expansion@2.0.2"
|
||||
]
|
||||
},
|
||||
"minimatch@9.0.5": {
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dependencies": [
|
||||
@@ -1582,6 +1664,19 @@
|
||||
"natural-compare@1.4.0": {
|
||||
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
|
||||
},
|
||||
"openapi-typescript@7.10.1_typescript@5.9.3": {
|
||||
"integrity": "sha512-rBcU8bjKGGZQT4K2ekSTY2Q5veOQbVG/lTKZ49DeCyT9z62hM2Vj/LLHjDHC9W7LJG8YMHcdXpRZDqC1ojB/lw==",
|
||||
"dependencies": [
|
||||
"@redocly/openapi-core",
|
||||
"ansi-colors",
|
||||
"change-case",
|
||||
"parse-json",
|
||||
"supports-color@10.2.2",
|
||||
"typescript",
|
||||
"yargs-parser"
|
||||
],
|
||||
"bin": true
|
||||
},
|
||||
"optionator@0.9.4": {
|
||||
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
|
||||
"dependencies": [
|
||||
@@ -1611,6 +1706,14 @@
|
||||
"callsites"
|
||||
]
|
||||
},
|
||||
"parse-json@8.3.0": {
|
||||
"integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==",
|
||||
"dependencies": [
|
||||
"@babel/code-frame",
|
||||
"index-to-position",
|
||||
"type-fest"
|
||||
]
|
||||
},
|
||||
"path-exists@4.0.0": {
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
|
||||
},
|
||||
@@ -1626,6 +1729,9 @@
|
||||
"picomatch@4.0.3": {
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="
|
||||
},
|
||||
"pluralize@8.0.0": {
|
||||
"integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="
|
||||
},
|
||||
"postcss-load-config@3.1.4_postcss@8.5.6": {
|
||||
"integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
|
||||
"dependencies": [
|
||||
@@ -1697,6 +1803,9 @@
|
||||
"readdirp@4.1.2": {
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="
|
||||
},
|
||||
"require-from-string@2.0.2": {
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
|
||||
},
|
||||
"resolve-from@4.0.0": {
|
||||
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
|
||||
},
|
||||
@@ -1780,6 +1889,9 @@
|
||||
"strip-json-comments@3.1.1": {
|
||||
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="
|
||||
},
|
||||
"supports-color@10.2.2": {
|
||||
"integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="
|
||||
},
|
||||
"supports-color@7.2.0": {
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dependencies": [
|
||||
@@ -1874,6 +1986,9 @@
|
||||
"prelude-ls"
|
||||
]
|
||||
},
|
||||
"type-fest@4.41.0": {
|
||||
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="
|
||||
},
|
||||
"typescript-eslint@8.46.3_eslint@9.39.1_typescript@5.9.3_@typescript-eslint+parser@8.46.3__eslint@9.39.1__typescript@5.9.3": {
|
||||
"integrity": "sha512-bAfgMavTuGo+8n6/QQDVQz4tZ4f7Soqg53RbrlZQEoAltYop/XR4RAts/I0BrO3TTClTSTFJ0wYbla+P8cEWJA==",
|
||||
"dependencies": [
|
||||
@@ -1939,9 +2054,15 @@
|
||||
"word-wrap@1.2.5": {
|
||||
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="
|
||||
},
|
||||
"yaml-ast-parser@0.0.43": {
|
||||
"integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A=="
|
||||
},
|
||||
"yaml@1.10.2": {
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
|
||||
},
|
||||
"yargs-parser@21.1.1": {
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="
|
||||
},
|
||||
"yocto-queue@0.1.0": {
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
|
||||
},
|
||||
@@ -1953,6 +2074,7 @@
|
||||
"dependencies": [
|
||||
"jsr:@soapbox/kysely-deno-sqlite@^2.2.0",
|
||||
"jsr:@std/assert@1",
|
||||
"jsr:@std/yaml@^1.0.10",
|
||||
"npm:croner@^8.1.2",
|
||||
"npm:highlight.js@^11.11.1",
|
||||
"npm:marked@^15.0.6",
|
||||
@@ -1968,7 +2090,9 @@
|
||||
"npm:@sveltejs/vite-plugin-svelte@^6.2.0",
|
||||
"npm:@tailwindcss/forms@~0.5.10",
|
||||
"npm:@tailwindcss/vite@^4.1.13",
|
||||
"npm:@types/deno@^2.5.0",
|
||||
"npm:@types/node@22",
|
||||
"npm:croner@^9.1.0",
|
||||
"npm:eslint-config-prettier@^10.1.8",
|
||||
"npm:eslint-plugin-svelte@^3.12.4",
|
||||
"npm:eslint@^9.36.0",
|
||||
@@ -1977,6 +2101,7 @@
|
||||
"npm:kysely@0.27.6",
|
||||
"npm:lucide-svelte@0.546",
|
||||
"npm:marked@^15.0.6",
|
||||
"npm:openapi-typescript@7",
|
||||
"npm:prettier-plugin-svelte@^3.4.0",
|
||||
"npm:prettier-plugin-tailwindcss@~0.6.14",
|
||||
"npm:prettier@^3.6.2",
|
||||
|
||||
31
docs/api/v1/openapi.yaml
Normal file
31
docs/api/v1/openapi.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: Profilarr API
|
||||
description: API for Profilarr - Profile management and sync for *arr applications
|
||||
version: 1.0.0
|
||||
license:
|
||||
name: MIT
|
||||
url: https://opensource.org/licenses/MIT
|
||||
|
||||
servers:
|
||||
- url: /api/v1
|
||||
description: API v1
|
||||
|
||||
tags:
|
||||
- name: System
|
||||
description: System health and status endpoints
|
||||
|
||||
paths:
|
||||
/health:
|
||||
$ref: './paths/system.yaml#/health'
|
||||
/openapi.json:
|
||||
$ref: './paths/system.yaml#/openapi'
|
||||
|
||||
components:
|
||||
schemas:
|
||||
ComponentStatus:
|
||||
$ref: './schemas/common.yaml#/ComponentStatus'
|
||||
HealthStatus:
|
||||
$ref: './schemas/common.yaml#/HealthStatus'
|
||||
HealthResponse:
|
||||
$ref: './schemas/health.yaml#/HealthResponse'
|
||||
35
docs/api/v1/paths/system.yaml
Normal file
35
docs/api/v1/paths/system.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
health:
|
||||
get:
|
||||
operationId: getHealth
|
||||
summary: Health check
|
||||
description: |
|
||||
Returns the health status of the application and its components.
|
||||
|
||||
Status values:
|
||||
- `healthy`: All components functioning normally
|
||||
- `degraded`: Core functionality works but some components have issues
|
||||
- `unhealthy`: Core functionality is broken
|
||||
tags:
|
||||
- System
|
||||
responses:
|
||||
'200':
|
||||
description: Health check response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../schemas/health.yaml#/HealthResponse'
|
||||
|
||||
openapi:
|
||||
get:
|
||||
operationId: getOpenApiSpec
|
||||
summary: OpenAPI specification
|
||||
description: Returns the OpenAPI specification for this API
|
||||
tags:
|
||||
- System
|
||||
responses:
|
||||
'200':
|
||||
description: OpenAPI specification
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
15
docs/api/v1/schemas/common.yaml
Normal file
15
docs/api/v1/schemas/common.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
ComponentStatus:
|
||||
type: string
|
||||
enum:
|
||||
- healthy
|
||||
- degraded
|
||||
- unhealthy
|
||||
description: Individual component health status
|
||||
|
||||
HealthStatus:
|
||||
type: string
|
||||
enum:
|
||||
- healthy
|
||||
- degraded
|
||||
- unhealthy
|
||||
description: Overall health status
|
||||
156
docs/api/v1/schemas/health.yaml
Normal file
156
docs/api/v1/schemas/health.yaml
Normal file
@@ -0,0 +1,156 @@
|
||||
DatabaseHealth:
|
||||
type: object
|
||||
required:
|
||||
- status
|
||||
- responseTimeMs
|
||||
properties:
|
||||
status:
|
||||
$ref: './common.yaml#/ComponentStatus'
|
||||
responseTimeMs:
|
||||
type: number
|
||||
description: Database query response time in milliseconds
|
||||
message:
|
||||
type: string
|
||||
description: Error message if unhealthy
|
||||
|
||||
DatabasesHealth:
|
||||
type: object
|
||||
required:
|
||||
- status
|
||||
- total
|
||||
- enabled
|
||||
- cached
|
||||
- disabled
|
||||
properties:
|
||||
status:
|
||||
$ref: './common.yaml#/ComponentStatus'
|
||||
total:
|
||||
type: integer
|
||||
description: Total number of PCD databases configured
|
||||
enabled:
|
||||
type: integer
|
||||
description: Number of enabled databases
|
||||
cached:
|
||||
type: integer
|
||||
description: Number of databases with compiled cache
|
||||
disabled:
|
||||
type: integer
|
||||
description: Number of disabled databases (compilation errors)
|
||||
message:
|
||||
type: string
|
||||
description: Additional status information
|
||||
|
||||
JobsHealth:
|
||||
type: object
|
||||
required:
|
||||
- status
|
||||
properties:
|
||||
status:
|
||||
$ref: './common.yaml#/ComponentStatus'
|
||||
lastRun:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: Last run time for each job
|
||||
message:
|
||||
type: string
|
||||
description: Additional status information
|
||||
|
||||
BackupsHealth:
|
||||
type: object
|
||||
required:
|
||||
- status
|
||||
- enabled
|
||||
properties:
|
||||
status:
|
||||
$ref: './common.yaml#/ComponentStatus'
|
||||
enabled:
|
||||
type: boolean
|
||||
description: Whether backups are enabled
|
||||
lastBackup:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: Timestamp of last backup
|
||||
count:
|
||||
type: integer
|
||||
description: Number of backup files
|
||||
totalSizeBytes:
|
||||
type: integer
|
||||
description: Total size of all backups in bytes
|
||||
retentionDays:
|
||||
type: integer
|
||||
description: Configured retention period in days
|
||||
message:
|
||||
type: string
|
||||
description: Additional status information
|
||||
|
||||
LogsHealth:
|
||||
type: object
|
||||
required:
|
||||
- status
|
||||
properties:
|
||||
status:
|
||||
$ref: './common.yaml#/ComponentStatus'
|
||||
totalSizeBytes:
|
||||
type: integer
|
||||
description: Total size of log files in bytes
|
||||
fileCount:
|
||||
type: integer
|
||||
description: Number of log files
|
||||
oldestLog:
|
||||
type: string
|
||||
format: date
|
||||
nullable: true
|
||||
description: Date of oldest log file
|
||||
newestLog:
|
||||
type: string
|
||||
format: date
|
||||
nullable: true
|
||||
description: Date of newest log file
|
||||
message:
|
||||
type: string
|
||||
description: Additional status information
|
||||
|
||||
HealthResponse:
|
||||
type: object
|
||||
required:
|
||||
- status
|
||||
- timestamp
|
||||
- version
|
||||
- uptime
|
||||
- components
|
||||
properties:
|
||||
status:
|
||||
$ref: './common.yaml#/HealthStatus'
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Current server time
|
||||
version:
|
||||
type: string
|
||||
description: Application version
|
||||
uptime:
|
||||
type: integer
|
||||
description: Server uptime in seconds
|
||||
components:
|
||||
type: object
|
||||
required:
|
||||
- database
|
||||
- databases
|
||||
- jobs
|
||||
- backups
|
||||
- logs
|
||||
properties:
|
||||
database:
|
||||
$ref: '#/DatabaseHealth'
|
||||
databases:
|
||||
$ref: '#/DatabasesHealth'
|
||||
jobs:
|
||||
$ref: '#/JobsHealth'
|
||||
backups:
|
||||
$ref: '#/BackupsHealth'
|
||||
logs:
|
||||
$ref: '#/LogsHealth'
|
||||
317
package-lock.json
generated
317
package-lock.json
generated
@@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@deno/vite-plugin": "^1.0.5",
|
||||
"@jsr/db__sqlite": "^0.12.0",
|
||||
"croner": "^9.1.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"kysely": "0.27.6",
|
||||
"lucide-svelte": "^0.546.0",
|
||||
@@ -24,11 +25,13 @@
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.0",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/vite": "^4.1.13",
|
||||
"@types/deno": "^2.5.0",
|
||||
"@types/node": "^22",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-svelte": "^3.12.4",
|
||||
"globals": "^16.4.0",
|
||||
"openapi-typescript": "^7.0.0",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||
@@ -40,6 +43,31 @@
|
||||
"vite": "^7.1.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz",
|
||||
"integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@deno/vite-plugin": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@deno/vite-plugin/-/vite-plugin-1.0.6.tgz",
|
||||
@@ -865,6 +893,82 @@
|
||||
"integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@redocly/ajv": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.17.1.tgz",
|
||||
"integrity": "sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/@redocly/ajv/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@redocly/config": {
|
||||
"version": "0.22.2",
|
||||
"resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.2.tgz",
|
||||
"integrity": "sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@redocly/openapi-core": {
|
||||
"version": "1.34.6",
|
||||
"resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.6.tgz",
|
||||
"integrity": "sha512-2+O+riuIUgVSuLl3Lyh5AplWZyVMNuG2F98/o6NrutKJfW4/GTZdPpZlIphS0HGgcOHgmWcCSHj+dWFlZaGSHw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@redocly/ajv": "^8.11.2",
|
||||
"@redocly/config": "^0.22.0",
|
||||
"colorette": "^1.2.0",
|
||||
"https-proxy-agent": "^7.0.5",
|
||||
"js-levenshtein": "^1.1.6",
|
||||
"js-yaml": "^4.1.0",
|
||||
"minimatch": "^5.0.1",
|
||||
"pluralize": "^8.0.0",
|
||||
"yaml-ast-parser": "0.0.43"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.17.0",
|
||||
"npm": ">=9.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redocly/openapi-core/node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redocly/openapi-core/node_modules/minimatch": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz",
|
||||
@@ -1534,6 +1638,13 @@
|
||||
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/deno": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/deno/-/deno-2.5.0.tgz",
|
||||
"integrity": "sha512-g8JS38vmc0S87jKsFzre+0ZyMOUDHPVokEJymSCRlL57h6f/FdKPWBXgdFh3Z8Ees9sz11qt9VWELU9Y9ZkiVw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
@@ -1840,6 +1951,16 @@
|
||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
@@ -1857,6 +1978,16 @@
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-colors": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
|
||||
"integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
@@ -1956,6 +2087,13 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/change-case": {
|
||||
"version": "5.4.4",
|
||||
"resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz",
|
||||
"integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
@@ -2001,6 +2139,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/colorette": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
|
||||
"integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -2017,6 +2162,15 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/croner": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/croner/-/croner-9.1.0.tgz",
|
||||
"integrity": "sha512-p9nwwR4qyT5W996vBZhdvBCnMhicY5ytZkR4D1Xj0wuTDEiMnjwR57Q3RXYY/s0EpX6Ay3vgIcfaR+ewGHsi+g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@@ -2432,6 +2586,23 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fastify"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.19.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
||||
@@ -2596,6 +2767,20 @@
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
|
||||
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "^7.1.2",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
@@ -2633,6 +2818,19 @@
|
||||
"node": ">=0.8.19"
|
||||
}
|
||||
},
|
||||
"node_modules/index-to-position": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz",
|
||||
"integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
@@ -2692,6 +2890,23 @@
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/js-levenshtein": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
|
||||
"integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
@@ -3203,6 +3418,40 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/openapi-typescript": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.10.1.tgz",
|
||||
"integrity": "sha512-rBcU8bjKGGZQT4K2ekSTY2Q5veOQbVG/lTKZ49DeCyT9z62hM2Vj/LLHjDHC9W7LJG8YMHcdXpRZDqC1ojB/lw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@redocly/openapi-core": "^1.34.5",
|
||||
"ansi-colors": "^4.1.3",
|
||||
"change-case": "^5.4.4",
|
||||
"parse-json": "^8.3.0",
|
||||
"supports-color": "^10.2.2",
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"openapi-typescript": "bin/cli.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.x"
|
||||
}
|
||||
},
|
||||
"node_modules/openapi-typescript/node_modules/supports-color": {
|
||||
"version": "10.2.2",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz",
|
||||
"integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
@@ -3266,6 +3515,24 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-json": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz",
|
||||
"integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
"index-to-position": "^1.1.0",
|
||||
"type-fest": "^4.39.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
@@ -3305,6 +3572,16 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pluralize": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
|
||||
"integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
@@ -3613,6 +3890,16 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-from": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
@@ -3999,6 +4286,19 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
|
||||
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
|
||||
"dev": true,
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
@@ -4623,6 +4923,23 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml-ast-parser": {
|
||||
"version": "0.0.43",
|
||||
"resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz",
|
||||
"integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"dependencies": {
|
||||
"@deno/vite-plugin": "^1.0.5",
|
||||
"@jsr/db__sqlite": "^0.12.0",
|
||||
"croner": "^9.1.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"kysely": "0.27.6",
|
||||
"lucide-svelte": "^0.546.0",
|
||||
@@ -20,11 +21,13 @@
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.0",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/vite": "^4.1.13",
|
||||
"@types/deno": "^2.5.0",
|
||||
"@types/node": "^22",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-svelte": "^3.12.4",
|
||||
"globals": "^16.4.0",
|
||||
"openapi-typescript": "^7.0.0",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||
|
||||
200
src/lib/api/v1.d.ts
vendored
Normal file
200
src/lib/api/v1.d.ts
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* This file was auto-generated by openapi-typescript.
|
||||
* Do not make direct changes to the file.
|
||||
*/
|
||||
|
||||
export interface paths {
|
||||
"/health": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/**
|
||||
* Health check
|
||||
* @description Returns the health status of the application and its components.
|
||||
*
|
||||
* Status values:
|
||||
* - `healthy`: All components functioning normally
|
||||
* - `degraded`: Core functionality works but some components have issues
|
||||
* - `unhealthy`: Core functionality is broken
|
||||
*/
|
||||
get: operations["getHealth"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/openapi.json": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/**
|
||||
* OpenAPI specification
|
||||
* @description Returns the OpenAPI specification for this API
|
||||
*/
|
||||
get: operations["getOpenApiSpec"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
}
|
||||
export type webhooks = Record<string, never>;
|
||||
export interface components {
|
||||
schemas: {
|
||||
/**
|
||||
* @description Individual component health status
|
||||
* @enum {string}
|
||||
*/
|
||||
ComponentStatus: "healthy" | "degraded" | "unhealthy";
|
||||
/**
|
||||
* @description Overall health status
|
||||
* @enum {string}
|
||||
*/
|
||||
HealthStatus: "healthy" | "degraded" | "unhealthy";
|
||||
HealthResponse: {
|
||||
status: components["schemas"]["HealthStatus"];
|
||||
/**
|
||||
* Format: date-time
|
||||
* @description Current server time
|
||||
*/
|
||||
timestamp: string;
|
||||
/** @description Application version */
|
||||
version: string;
|
||||
/** @description Server uptime in seconds */
|
||||
uptime: number;
|
||||
components: {
|
||||
database: components["schemas"]["DatabaseHealth"];
|
||||
databases: components["schemas"]["DatabasesHealth"];
|
||||
jobs: components["schemas"]["JobsHealth"];
|
||||
backups: components["schemas"]["BackupsHealth"];
|
||||
logs: components["schemas"]["LogsHealth"];
|
||||
};
|
||||
};
|
||||
DatabaseHealth: {
|
||||
status: components["schemas"]["ComponentStatus"];
|
||||
/** @description Database query response time in milliseconds */
|
||||
responseTimeMs: number;
|
||||
/** @description Error message if unhealthy */
|
||||
message?: string;
|
||||
};
|
||||
DatabasesHealth: {
|
||||
status: components["schemas"]["ComponentStatus"];
|
||||
/** @description Total number of PCD databases configured */
|
||||
total: number;
|
||||
/** @description Number of enabled databases */
|
||||
enabled: number;
|
||||
/** @description Number of databases with compiled cache */
|
||||
cached: number;
|
||||
/** @description Number of disabled databases (compilation errors) */
|
||||
disabled: number;
|
||||
/** @description Additional status information */
|
||||
message?: string;
|
||||
};
|
||||
JobsHealth: {
|
||||
status: components["schemas"]["ComponentStatus"];
|
||||
/** @description Last run time for each job */
|
||||
lastRun?: {
|
||||
[key: string]: string | null;
|
||||
};
|
||||
/** @description Additional status information */
|
||||
message?: string;
|
||||
};
|
||||
BackupsHealth: {
|
||||
status: components["schemas"]["ComponentStatus"];
|
||||
/** @description Whether backups are enabled */
|
||||
enabled: boolean;
|
||||
/**
|
||||
* Format: date-time
|
||||
* @description Timestamp of last backup
|
||||
*/
|
||||
lastBackup?: string | null;
|
||||
/** @description Number of backup files */
|
||||
count?: number;
|
||||
/** @description Total size of all backups in bytes */
|
||||
totalSizeBytes?: number;
|
||||
/** @description Configured retention period in days */
|
||||
retentionDays?: number;
|
||||
/** @description Additional status information */
|
||||
message?: string;
|
||||
};
|
||||
LogsHealth: {
|
||||
status: components["schemas"]["ComponentStatus"];
|
||||
/** @description Total size of log files in bytes */
|
||||
totalSizeBytes?: number;
|
||||
/** @description Number of log files */
|
||||
fileCount?: number;
|
||||
/**
|
||||
* Format: date
|
||||
* @description Date of oldest log file
|
||||
*/
|
||||
oldestLog?: string | null;
|
||||
/**
|
||||
* Format: date
|
||||
* @description Date of newest log file
|
||||
*/
|
||||
newestLog?: string | null;
|
||||
/** @description Additional status information */
|
||||
message?: string;
|
||||
};
|
||||
};
|
||||
responses: never;
|
||||
parameters: never;
|
||||
requestBodies: never;
|
||||
headers: never;
|
||||
pathItems: never;
|
||||
}
|
||||
export type $defs = Record<string, never>;
|
||||
export interface operations {
|
||||
getHealth: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Health check response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HealthResponse"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
getOpenApiSpec: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description OpenAPI specification */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": Record<string, never>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
312
src/routes/api/v1/health/+server.ts
Normal file
312
src/routes/api/v1/health/+server.ts
Normal file
@@ -0,0 +1,312 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { db } from '$db/db.ts';
|
||||
import { databaseInstancesQueries } from '$db/queries/databaseInstances.ts';
|
||||
import { jobsQueries } from '$db/queries/jobs.ts';
|
||||
import { backupSettingsQueries } from '$db/queries/backupSettings.ts';
|
||||
import { appInfoQueries } from '$db/queries/appInfo.ts';
|
||||
import { getCache } from '$pcd/cache.ts';
|
||||
import { config } from '$config';
|
||||
import type { components } from '$api/v1.d.ts';
|
||||
|
||||
type HealthResponse = components['schemas']['HealthResponse'];
|
||||
type ComponentStatus = components['schemas']['ComponentStatus'];
|
||||
|
||||
// Track startup time for uptime calculation
|
||||
const startupTime = Date.now();
|
||||
|
||||
// Thresholds
|
||||
const LOG_SIZE_WARN_BYTES = 100 * 1024 * 1024; // 100MB
|
||||
const LOG_SIZE_CRITICAL_BYTES = 500 * 1024 * 1024; // 500MB
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
const response: HealthResponse = {
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
version: appInfoQueries.getVersion(),
|
||||
uptime: Math.floor((Date.now() - startupTime) / 1000),
|
||||
components: {
|
||||
database: checkDatabase(),
|
||||
databases: checkDatabases(),
|
||||
jobs: checkJobs(),
|
||||
backups: await checkBackups(),
|
||||
logs: await checkLogs()
|
||||
}
|
||||
};
|
||||
|
||||
// Determine overall status
|
||||
response.status = determineOverallStatus(response.components);
|
||||
|
||||
return json(response);
|
||||
};
|
||||
|
||||
function determineOverallStatus(components: HealthResponse['components']): ComponentStatus {
|
||||
const statuses = [
|
||||
components.database.status,
|
||||
components.databases.status,
|
||||
components.jobs.status,
|
||||
components.backups.status,
|
||||
components.logs.status
|
||||
];
|
||||
|
||||
// If database is unhealthy, everything is unhealthy
|
||||
if (components.database.status === 'unhealthy') {
|
||||
return 'unhealthy';
|
||||
}
|
||||
|
||||
// If all PCD databases are unhealthy, system is unhealthy
|
||||
if (components.databases.status === 'unhealthy') {
|
||||
return 'unhealthy';
|
||||
}
|
||||
|
||||
// If any component is degraded, system is degraded
|
||||
if (statuses.some((s) => s === 'degraded')) {
|
||||
return 'degraded';
|
||||
}
|
||||
|
||||
return 'healthy';
|
||||
}
|
||||
|
||||
function checkDatabase(): HealthResponse['components']['database'] {
|
||||
const start = performance.now();
|
||||
|
||||
try {
|
||||
db.queryFirst('SELECT 1');
|
||||
const responseTimeMs = Math.round((performance.now() - start) * 100) / 100;
|
||||
|
||||
return {
|
||||
status: 'healthy',
|
||||
responseTimeMs
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'unhealthy',
|
||||
responseTimeMs: -1,
|
||||
message: error instanceof Error ? error.message : 'Database query failed'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function checkDatabases(): HealthResponse['components']['databases'] {
|
||||
try {
|
||||
const allDatabases = databaseInstancesQueries.getAll();
|
||||
const enabledDatabases = allDatabases.filter((d) => d.enabled === 1);
|
||||
const disabledDatabases = allDatabases.filter((d) => d.enabled === 0);
|
||||
|
||||
// Check how many have compiled caches
|
||||
let cachedCount = 0;
|
||||
for (const dbInstance of enabledDatabases) {
|
||||
const cache = getCache(dbInstance.id);
|
||||
if (cache?.isBuilt()) {
|
||||
cachedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
const total = allDatabases.length;
|
||||
const enabled = enabledDatabases.length;
|
||||
const disabled = disabledDatabases.length;
|
||||
|
||||
let status: ComponentStatus = 'healthy';
|
||||
let message: string | undefined;
|
||||
|
||||
if (total === 0) {
|
||||
status = 'healthy';
|
||||
message = 'No databases configured';
|
||||
} else if (enabled === 0) {
|
||||
status = 'unhealthy';
|
||||
message = 'All databases are disabled';
|
||||
} else if (disabled > 0) {
|
||||
status = 'degraded';
|
||||
message = `${disabled} database(s) disabled due to errors`;
|
||||
} else if (cachedCount < enabled) {
|
||||
status = 'degraded';
|
||||
message = `${enabled - cachedCount} database(s) not cached`;
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
total,
|
||||
enabled,
|
||||
cached: cachedCount,
|
||||
disabled,
|
||||
message
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'unhealthy',
|
||||
total: 0,
|
||||
enabled: 0,
|
||||
cached: 0,
|
||||
disabled: 0,
|
||||
message: error instanceof Error ? error.message : 'Failed to check databases'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function checkJobs(): HealthResponse['components']['jobs'] {
|
||||
try {
|
||||
const jobs = jobsQueries.getAll();
|
||||
const lastRun: Record<string, string | null> = {};
|
||||
|
||||
for (const job of jobs) {
|
||||
lastRun[job.name] = job.last_run_at ?? null;
|
||||
}
|
||||
|
||||
// Check if sync_arr job is stale (hasn't run in 5+ minutes when it should run every minute)
|
||||
const syncArrJob = jobs.find((j) => j.name === 'sync_arr');
|
||||
let status: ComponentStatus = 'healthy';
|
||||
let message: string | undefined;
|
||||
|
||||
if (syncArrJob?.enabled && syncArrJob.last_run_at) {
|
||||
const lastRunTime = new Date(syncArrJob.last_run_at).getTime();
|
||||
const fiveMinutesAgo = Date.now() - 5 * 60 * 1000;
|
||||
|
||||
if (lastRunTime < fiveMinutesAgo) {
|
||||
status = 'degraded';
|
||||
message = 'sync_arr job is stale';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
lastRun,
|
||||
message
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'unhealthy',
|
||||
message: error instanceof Error ? error.message : 'Failed to check jobs'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function checkBackups(): Promise<HealthResponse['components']['backups']> {
|
||||
try {
|
||||
const settings = backupSettingsQueries.get();
|
||||
const enabled = settings?.enabled === 1;
|
||||
const retentionDays = settings?.retention_days ?? 30;
|
||||
|
||||
if (!enabled) {
|
||||
return {
|
||||
status: 'healthy',
|
||||
enabled: false,
|
||||
message: 'Backups disabled'
|
||||
};
|
||||
}
|
||||
|
||||
// Check backup directory
|
||||
const backupPath = config.paths.backups;
|
||||
let count = 0;
|
||||
let totalSizeBytes = 0;
|
||||
let lastBackup: string | null = null;
|
||||
let lastBackupTime: number | null = null;
|
||||
|
||||
try {
|
||||
for await (const entry of Deno.readDir(backupPath)) {
|
||||
if (entry.isFile && entry.name.startsWith('backup-') && entry.name.endsWith('.tar.gz')) {
|
||||
count++;
|
||||
const stat = await Deno.stat(`${backupPath}/${entry.name}`);
|
||||
totalSizeBytes += stat.size;
|
||||
|
||||
if (!lastBackupTime || stat.mtime!.getTime() > lastBackupTime) {
|
||||
lastBackupTime = stat.mtime!.getTime();
|
||||
lastBackup = stat.mtime!.toISOString();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Directory might not exist yet
|
||||
}
|
||||
|
||||
let status: ComponentStatus = 'healthy';
|
||||
let message: string | undefined;
|
||||
|
||||
if (count === 0) {
|
||||
status = 'degraded';
|
||||
message = 'No backups exist';
|
||||
} else if (lastBackupTime) {
|
||||
// Check if last backup is older than 2x retention period
|
||||
const twoDaysAgo = Date.now() - 2 * 24 * 60 * 60 * 1000;
|
||||
if (lastBackupTime < twoDaysAgo) {
|
||||
status = 'degraded';
|
||||
message = 'Last backup is older than 2 days';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
enabled,
|
||||
lastBackup,
|
||||
count,
|
||||
totalSizeBytes,
|
||||
retentionDays,
|
||||
message
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'unhealthy',
|
||||
enabled: false,
|
||||
message: error instanceof Error ? error.message : 'Failed to check backups'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function checkLogs(): Promise<HealthResponse['components']['logs']> {
|
||||
try {
|
||||
const logPath = config.paths.logs;
|
||||
let totalSizeBytes = 0;
|
||||
let fileCount = 0;
|
||||
let oldestLog: string | null = null;
|
||||
let newestLog: string | null = null;
|
||||
|
||||
const logDateRegex = /^(\d{4}-\d{2}-\d{2})\.log$/;
|
||||
|
||||
try {
|
||||
for await (const entry of Deno.readDir(logPath)) {
|
||||
if (entry.isFile) {
|
||||
const match = entry.name.match(logDateRegex);
|
||||
if (match) {
|
||||
fileCount++;
|
||||
const stat = await Deno.stat(`${logPath}/${entry.name}`);
|
||||
totalSizeBytes += stat.size;
|
||||
|
||||
const dateStr = match[1];
|
||||
if (!oldestLog || dateStr < oldestLog) {
|
||||
oldestLog = dateStr;
|
||||
}
|
||||
if (!newestLog || dateStr > newestLog) {
|
||||
newestLog = dateStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Directory might not exist yet
|
||||
}
|
||||
|
||||
let status: ComponentStatus = 'healthy';
|
||||
let message: string | undefined;
|
||||
|
||||
if (totalSizeBytes > LOG_SIZE_CRITICAL_BYTES) {
|
||||
status = 'degraded';
|
||||
message = `Log directory is very large (${Math.round(totalSizeBytes / 1024 / 1024)}MB)`;
|
||||
} else if (totalSizeBytes > LOG_SIZE_WARN_BYTES) {
|
||||
status = 'degraded';
|
||||
message = `Log directory is getting large (${Math.round(totalSizeBytes / 1024 / 1024)}MB)`;
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
totalSizeBytes,
|
||||
fileCount,
|
||||
oldestLog,
|
||||
newestLog,
|
||||
message
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'unhealthy',
|
||||
message: error instanceof Error ? error.message : 'Failed to check logs'
|
||||
};
|
||||
}
|
||||
}
|
||||
16
src/routes/api/v1/openapi.json/+server.ts
Normal file
16
src/routes/api/v1/openapi.json/+server.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { parse } from '@std/yaml';
|
||||
|
||||
// Cache the parsed spec to avoid re-reading on every request
|
||||
let cachedSpec: unknown = null;
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
if (!cachedSpec) {
|
||||
// Read and parse the OpenAPI spec
|
||||
const yamlContent = await Deno.readTextFile('docs/api/v1/openapi.yaml');
|
||||
cachedSpec = parse(yamlContent);
|
||||
}
|
||||
|
||||
return json(cachedSpec);
|
||||
};
|
||||
@@ -12,6 +12,7 @@ const config = {
|
||||
}),
|
||||
outDir: 'dist/.svelte-kit',
|
||||
alias: {
|
||||
$api: './src/lib/api',
|
||||
$config: './src/lib/server/utils/config/config.ts',
|
||||
$logger: './src/lib/server/utils/logger',
|
||||
$shared: './src/lib/shared',
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2022", "DOM"],
|
||||
"types": ["deno"],
|
||||
"allowImportingTsExtensions": true,
|
||||
"noEmit": true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user