diff --git a/backend/.eslintrc.js b/backend/.eslintrc.js old mode 100755 new mode 100644 diff --git a/backend/data-source.ts b/backend/data-source.ts index f188e42..fadb01a 100644 --- a/backend/data-source.ts +++ b/backend/data-source.ts @@ -1,3 +1,4 @@ +import { ENV } from 'src/consts'; import { DataSource } from 'typeorm'; export default new DataSource({ @@ -5,5 +6,6 @@ export default new DataSource({ database: './config/reiverr.sqlite', entities: ['dist/**/*.entity.js'], migrations: ['dist/migrations/*.js'], + synchronize: ENV === 'development', // migrations: [__dirname + '/../**/*.migration{.ts,.js}'], }); diff --git a/backend/package-lock.json b/backend/package-lock.json index 3bd534e..a0f4bd8 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -21,6 +21,7 @@ "reflect-metadata": "^0.2.1", "rxjs": "^7.8.1", "sqlite3": "^5.1.7", + "swagger-typescript-api": "^13.0.23", "typeorm": "^0.3.20" }, "devDependencies": { @@ -245,7 +246,6 @@ "version": "7.24.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", - "dev": true, "dependencies": { "@babel/highlight": "^7.24.2", "picocolors": "^1.0.0" @@ -453,7 +453,6 @@ "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -485,7 +484,6 @@ "version": "7.24.2", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", - "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", @@ -500,7 +498,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -512,7 +509,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -526,7 +522,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -534,14 +529,12 @@ "node_modules/@babel/highlight/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "node_modules/@babel/highlight/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, "engines": { "node": ">=0.8.0" } @@ -550,7 +543,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, "engines": { "node": ">=4" } @@ -559,7 +551,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -952,6 +943,11 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@exodus/schemasafe": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", + "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==" + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -2471,6 +2467,11 @@ "@types/superagent": "^8.1.0" } }, + "node_modules/@types/swagger-schema-official": { + "version": "2.0.25", + "resolved": "https://registry.npmjs.org/@types/swagger-schema-official/-/swagger-schema-official-2.0.25.tgz", + "integrity": "sha512-T92Xav+Gf/Ik1uPW581nA+JftmjWPgskw/WBf4TJzxRG/SJ+DfNnNE+WuZ4mrXuzflQMqMkm1LSYjzYW7MB1Cg==" + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -3634,11 +3635,15 @@ "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==" + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -4334,6 +4339,11 @@ "wrappy": "1" } }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -4482,7 +4492,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "optional": true, "engines": { "node": ">=6" } @@ -4497,7 +4506,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "dependencies": { "is-arrayish": "^0.2.1" } @@ -4810,6 +4818,17 @@ "node": ">=0.10.0" } }, + "node_modules/eta": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-2.2.0.tgz", + "integrity": "sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==", + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "url": "https://github.com/eta-dev/eta?sponsor=1" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -5720,6 +5739,11 @@ "node": ">= 6" } }, + "node_modules/http2-client": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", + "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==" + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -5794,7 +5818,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -5934,8 +5957,7 @@ "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -6839,8 +6861,7 @@ "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 + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -6880,8 +6901,7 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "node_modules/json-schema-traverse": { "version": "1.0.0", @@ -7008,8 +7028,7 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/loader-runner": { "version": "4.3.0", @@ -7601,6 +7620,23 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", @@ -7679,6 +7715,17 @@ } } }, + "node_modules/node-fetch-h2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", + "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", + "dependencies": { + "http2-client": "^1.2.5" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/node-gyp": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", @@ -7766,6 +7813,19 @@ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true }, + "node_modules/node-readfiles": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz", + "integrity": "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==", + "dependencies": { + "es6-promise": "^3.2.1" + } + }, + "node_modules/node-readfiles/node_modules/es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==" + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -7823,6 +7883,71 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/oas-kit-common": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", + "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", + "dependencies": { + "fast-safe-stringify": "^2.0.7" + } + }, + "node_modules/oas-linter": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz", + "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", + "dependencies": { + "@exodus/schemasafe": "^1.0.0-rc.2", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-resolver": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz", + "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", + "dependencies": { + "node-fetch-h2": "^2.3.0", + "oas-kit-common": "^1.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + }, + "bin": { + "resolve": "resolve.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-schema-walker": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz", + "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==", + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-validator": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz", + "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", + "dependencies": { + "call-me-maybe": "^1.0.1", + "oas-kit-common": "^1.0.8", + "oas-linter": "^3.2.2", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "reftools": "^1.1.9", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -7980,7 +8105,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -7992,7 +8116,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -8104,8 +8227,7 @@ "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/picomatch": { "version": "3.0.1", @@ -8239,7 +8361,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", - "dev": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -8524,6 +8645,14 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" }, + "node_modules/reftools": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", + "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, "node_modules/repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", @@ -8592,7 +8721,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -9028,6 +9156,54 @@ "node": "*" } }, + "node_modules/should": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "dependencies": { + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "node_modules/should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "dependencies": { + "should-type": "^1.4.0" + } + }, + "node_modules/should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", + "dependencies": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "node_modules/should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==" + }, + "node_modules/should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "dependencies": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" + } + }, + "node_modules/should-util": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", + "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==" + }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", @@ -9451,11 +9627,101 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-schema-official": { + "version": "2.0.0-bab6bed", + "resolved": "https://registry.npmjs.org/swagger-schema-official/-/swagger-schema-official-2.0.0-bab6bed.tgz", + "integrity": "sha512-rCC0NWGKr/IJhtRuPq/t37qvZHI/mH4I4sxflVM+qgVe5Z2uOCivzWaVbuioJaB61kvm5UvB7b49E+oBY0M8jA==" + }, + "node_modules/swagger-typescript-api": { + "version": "13.0.23", + "resolved": "https://registry.npmjs.org/swagger-typescript-api/-/swagger-typescript-api-13.0.23.tgz", + "integrity": "sha512-HhoIepxlFEU7Ol42Gh8/tvwhSxdkHcweX2tRkNhbZYBiEA+rK3C6N85MnwoeQR5XbidE3Kz8mLOqIerVGgR9uw==", + "dependencies": { + "@types/swagger-schema-official": "^2.0.25", + "consola": "^3.2.3", + "cosmiconfig": "^9.0.0", + "didyoumean": "^1.2.2", + "eta": "^2.2.0", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "nanoid": "^3.3.7", + "prettier": "~3.3.3", + "swagger-schema-official": "2.0.0-bab6bed", + "swagger2openapi": "^7.0.8", + "typescript": "~5.5.4" + }, + "bin": { + "sta": "dist/cli.js", + "swagger-typescript-api": "dist/cli.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/swagger-typescript-api/node_modules/consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/swagger-typescript-api/node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/swagger-ui-dist": { "version": "5.11.2", "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.2.tgz", "integrity": "sha512-jQG0cRgJNMZ7aCoiFofnoojeSaa/+KgWaDlfgs8QN+BXoGMpxeMVY5OEnjq4OlNvF3yjftO8c9GRAgcHlO+u7A==" }, + "node_modules/swagger2openapi": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz", + "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", + "dependencies": { + "call-me-maybe": "^1.0.1", + "node-fetch": "^2.6.1", + "node-fetch-h2": "^2.3.0", + "node-readfiles": "^0.2.0", + "oas-kit-common": "^1.0.8", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "oas-validator": "^5.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + }, + "bin": { + "boast": "boast.js", + "oas-validate": "oas-validate.js", + "swagger2openapi": "swagger2openapi.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -10176,7 +10442,6 @@ "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", - "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10563,6 +10828,14 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/backend/package.json b/backend/package.json index a9b126b..293e9c5 100644 --- a/backend/package.json +++ b/backend/package.json @@ -18,7 +18,7 @@ "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", - "openapi:schema": "ts-node src/generate-openapi.ts", + "openapi:generate": "ts-node src/generate-openapi.ts", "typeorm": "ts-node ./node_modules/typeorm/cli", "typeorm:run-migrations": "npm run typeorm migration:run -- -d ./dist/data-source.js", "typeorm:generate-migration": "npm run typeorm -- -d ./dist/data-source.js migration:generate ./dist/migrations/$npm_config_name", @@ -38,6 +38,7 @@ "reflect-metadata": "^0.2.1", "rxjs": "^7.8.1", "sqlite3": "^5.1.7", + "swagger-typescript-api": "^13.0.23", "typeorm": "^0.3.20" }, "devDependencies": { diff --git a/backend/plugins/jellyfin.plugin/index.ts b/backend/plugins/jellyfin.plugin/index.ts deleted file mode 100644 index 08052e2..0000000 --- a/backend/plugins/jellyfin.plugin/index.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { generateApi, generateTemplates } from 'swagger-typescript-api'; -import { - BaseItemKind, - ItemFields, - Api as JellyfinApi, -} from './api/jellyfin.openapi'; - -export interface SourcePlugin { - handleProxy(request: { uri: string; headers: any }): any; - name: string; - indexable: boolean; - getMovieStream: (tmdbId: string) => Promise; -} - -const config = { - apiKey: '', - baseUrl: 'http://192.168.0.129:8096', - userId: '', -}; -export const JELLYFIN_DEVICE_ID = 'Reiverr Client'; - -@Injectable() -export default class JellyfinPlugin implements SourcePlugin { - name: string = 'jellyfin'; - indexable: boolean = true; - - api: JellyfinApi<{}>; - - constructor() { - generateApi({ - name: 'jellyfin.openapi.ts', - url: 'https://api.jellyfin.org/openapi/jellyfin-openapi-stable.json', - output: - '/Users/aleksilassila/Workspace/Documents/node/reiverr/backend/plugins/jellyfin.plugin/api', - // generateClient: true, - // generateRouteTypes: false, - // sortTypes: true, - httpClientType: 'axios', - }); - - this.api = new JellyfinApi({ - baseURL: config.baseUrl, - headers: { - Authorization: `MediaBrowser DeviceId="${JELLYFIN_DEVICE_ID}", Token="${config.apiKey}"`, - }, - paramsSerializer: { - indexes: null, - }, - }); - } - handleProxy({ uri, headers }) { - return { - url: `${config.baseUrl}/${uri}`, - headers: { - ...headers, - Authorization: `MediaBrowser DeviceId="${JELLYFIN_DEVICE_ID}", Token="${config.apiKey}"`, - }, - }; - } - - private async getLibraryItems() { - return this.api.items - .getItems({ - userId: config.userId, - hasTmdbId: true, - recursive: true, - includeItemTypes: [BaseItemKind.Movie, BaseItemKind.Series], - fields: [ - ItemFields.ProviderIds, - ItemFields.Genres, - ItemFields.DateLastMediaAdded, - ItemFields.DateCreated, - ItemFields.MediaSources, - ], - }) - .then((res) => { - console.log(res.request.path); - return res; - }) - .then((res) => res.data.Items ?? []); - } - - async getMovieStream(tmdbId: string): Promise { - const items = await this.getLibraryItems(); - - const movie = items.find((item) => item.ProviderIds?.Tmdb === tmdbId); - - // console.log(items.map((item) => item)) - - if (!movie || !movie.MediaSources || movie.MediaSources.length === 0) { - throw new Error('Movie stream not found'); - } - - /* - await jellyfinApi.getPlaybackInfo( - id, - getDeviceProfile(), - options.playbackPosition || item?.UserData?.PlaybackPositionTicks || 0, - options.bitrate || getQualities(item?.Height || 1080)[0]?.maxBitrate, - audioStreamIndex - ); - */ - - const playbackInfo = await this.api.items.getPlaybackInfo(movie.Id, { - userId: config.userId, - // deviceId: JELLYFIN_DEVICE_ID, - // mediaSourceId: movie.MediaSources[0].Id, - // maxBitrate: 8000000, - }); - - const playbackUri = - playbackInfo.data?.MediaSources?.[0]?.TranscodingUrl || - `/Videos/${playbackInfo.data?.MediaSources?.[0]?.Id}/stream.mp4?Static=true&mediaSourceId=${playbackInfo.data?.MediaSources?.[0]?.Id}&deviceId=${JELLYFIN_DEVICE_ID}&api_key=${config.apiKey}&Tag=${playbackInfo.data?.MediaSources?.[0]?.ETag}`; - - return playbackUri; - } -} diff --git a/backend/plugins/jellyfin.plugin/package.json b/backend/plugins/jellyfin.plugin/package.json index 0bd225d..23f416e 100644 --- a/backend/plugins/jellyfin.plugin/package.json +++ b/backend/plugins/jellyfin.plugin/package.json @@ -2,8 +2,9 @@ "name": "jellyfin", "version": "1.0.0", "description": "", - "main": "index.ts", + "main": "src/index.ts", "scripts": { + "openapi:generate": "ts-node src/generate-openapi.ts", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", diff --git a/backend/plugins/jellyfin.plugin/src/generate-openapi.ts b/backend/plugins/jellyfin.plugin/src/generate-openapi.ts new file mode 100644 index 0000000..b8f78ca --- /dev/null +++ b/backend/plugins/jellyfin.plugin/src/generate-openapi.ts @@ -0,0 +1,13 @@ +import { generateApi } from 'swagger-typescript-api'; +import * as path from 'path'; + +console.log(path.join(require.main.path, '..', 'plugins')); +generateApi({ + name: 'jellyfin.openapi.ts', + url: 'https://api.jellyfin.org/openapi/jellyfin-openapi-stable.json', + output: __dirname, + // generateClient: true, + // generateRouteTypes: false, + // sortTypes: true, + httpClientType: 'axios', +}); diff --git a/backend/plugins/jellyfin.plugin/src/index.ts b/backend/plugins/jellyfin.plugin/src/index.ts new file mode 100644 index 0000000..2431202 --- /dev/null +++ b/backend/plugins/jellyfin.plugin/src/index.ts @@ -0,0 +1,170 @@ +import { Injectable } from '@nestjs/common'; +import { generateApi, generateTemplates } from 'swagger-typescript-api'; +import { + BaseItemKind, + ItemFields, + Api as JellyfinApi, +} from './jellyfin.openapi'; +import { + PluginSettings, + PluginSettingsTemplate, + SourcePlugin, +} from 'plugins/plugin-types'; + +interface JellyfinSettings extends PluginSettings { + apiKey: string; + baseUrl: string; + userId: string; +} + +export const JELLYFIN_DEVICE_ID = 'Reiverr Client'; + +@Injectable() +export default class JellyfinPlugin implements SourcePlugin { + name: string = 'jellyfin'; + + validateSettings: ( + settings: JellyfinSettings, + ) => Promise<{ isValid: boolean; errors: Record }> = async ( + settings, + ) => { + let isValid = true; + const errors = { + baseUrl: '', + apiKey: '', + userId: '', + }; + + if (!settings.baseUrl) { + isValid = false; + errors.baseUrl = 'Base URL is required'; + } + + if (!settings.apiKey) { + isValid = false; + errors.apiKey = 'API Key is required'; + } + + if (!settings.userId) { + isValid = false; + errors.userId = 'User ID is required'; + } + + const context = new PluginContext(settings); + const user = await context.api.users + .getUserById(settings.userId) + .catch((err) => err); + + console.log('jellyfinUser', user); + + return { + isValid, + errors, + }; + }; + + getIndex: () => Promise>; + + getIsIndexable: () => boolean = () => true; + + getSettingsTemplate: () => PluginSettingsTemplate = () => ({ + baseUrl: 'string', + apiKey: 'password', + userId: 'string', + }); + + getEpisodeStream: ( + tmdbId: string, + season: number, + episode: number, + ) => Promise; + + handleProxy({ uri, headers }, settings: JellyfinSettings) { + return { + url: `${settings.baseUrl}/${uri}`, + headers: { + ...headers, + Authorization: `MediaBrowser DeviceId="${JELLYFIN_DEVICE_ID}", Token="${settings.apiKey}"`, + }, + }; + } + + private async getLibraryItems(context: PluginContext) { + return context.api.items + .getItems({ + userId: context.settings.userId, + hasTmdbId: true, + recursive: true, + includeItemTypes: [BaseItemKind.Movie, BaseItemKind.Series], + fields: [ + ItemFields.ProviderIds, + ItemFields.Genres, + ItemFields.DateLastMediaAdded, + ItemFields.DateCreated, + ItemFields.MediaSources, + ], + }) + .then((res) => { + console.log(res.request.path); + return res; + }) + .then((res) => res.data.Items ?? []); + } + + async getMovieStream( + tmdbId: string, + settings: JellyfinSettings, + ): Promise { + const context = new PluginContext(settings); + const items = await this.getLibraryItems(context); + + const movie = items.find((item) => item.ProviderIds?.Tmdb === tmdbId); + + // console.log(items.map((item) => item)) + + if (!movie || !movie.MediaSources || movie.MediaSources.length === 0) { + throw new Error('Movie stream not found'); + } + + /* + await jellyfinApi.getPlaybackInfo( + id, + getDeviceProfile(), + options.playbackPosition || item?.UserData?.PlaybackPositionTicks || 0, + options.bitrate || getQualities(item?.Height || 1080)[0]?.maxBitrate, + audioStreamIndex + ); + */ + + const playbackInfo = await context.api.items.getPlaybackInfo(movie.Id, { + userId: context.settings.userId, + // deviceId: JELLYFIN_DEVICE_ID, + // mediaSourceId: movie.MediaSources[0].Id, + // maxBitrate: 8000000, + }); + + const playbackUri = + playbackInfo.data?.MediaSources?.[0]?.TranscodingUrl || + `/Videos/${playbackInfo.data?.MediaSources?.[0]?.Id}/stream.mp4?Static=true&mediaSourceId=${playbackInfo.data?.MediaSources?.[0]?.Id}&deviceId=${JELLYFIN_DEVICE_ID}&api_key=${context.settings.apiKey}&Tag=${playbackInfo.data?.MediaSources?.[0]?.ETag}`; + + return playbackUri; + } +} + +class PluginContext { + api: JellyfinApi<{}>; + settings: JellyfinSettings; + + constructor(settings: JellyfinSettings) { + this.settings = settings; + this.api = new JellyfinApi({ + baseURL: settings.baseUrl, + headers: { + Authorization: `MediaBrowser DeviceId="${JELLYFIN_DEVICE_ID}", Token="${settings.apiKey}"`, + }, + paramsSerializer: { + indexes: null, + }, + }); + } +} diff --git a/backend/plugins/jellyfin.plugin/api/jellyfin.openapi.ts b/backend/plugins/jellyfin.plugin/src/jellyfin.openapi.ts similarity index 97% rename from backend/plugins/jellyfin.plugin/api/jellyfin.openapi.ts rename to backend/plugins/jellyfin.plugin/src/jellyfin.openapi.ts index 852a4e6..a621b89 100644 --- a/backend/plugins/jellyfin.plugin/api/jellyfin.openapi.ts +++ b/backend/plugins/jellyfin.plugin/src/jellyfin.openapi.ts @@ -78,14 +78,7 @@ export interface ActivityLogEntry { */ UserPrimaryImageTag?: string | null; /** Gets or sets the log severity. */ - Severity?: - | 'Trace' - | 'Debug' - | 'Information' - | 'Warning' - | 'Error' - | 'Critical' - | 'None'; + Severity?: 'Trace' | 'Debug' | 'Information' | 'Warning' | 'Error' | 'Critical' | 'None'; } /** Activity log created message. */ @@ -480,13 +473,7 @@ export interface BaseItemDto { SortName?: string | null; ForcedSortName?: string | null; /** Gets or sets the video3 D format. */ - Video3DFormat?: - | 'HalfSideBySide' - | 'FullSideBySide' - | 'FullTopAndBottom' - | 'HalfTopAndBottom' - | 'MVC' - | null; + Video3DFormat?: 'HalfSideBySide' | 'FullSideBySide' | 'FullTopAndBottom' | 'HalfTopAndBottom' | 'MVC' | null; /** * Gets or sets the premiere date. * @format date-time @@ -1691,12 +1678,7 @@ export interface EncodingOptions { */ DownMixAudioBoost?: number; /** Gets or sets the algorithm used for downmixing audio to stereo. */ - DownMixStereoAlgorithm?: - | 'None' - | 'Dave750' - | 'NightmodeDialogue' - | 'Rfc7845' - | 'Ac4'; + DownMixStereoAlgorithm?: 'None' | 'Dave750' | 'NightmodeDialogue' | 'Rfc7845' | 'Ac4'; /** * Gets or sets the maximum size of the muxing queue. * @format int32 @@ -1717,15 +1699,7 @@ export interface EncodingOptions { */ SegmentKeepSeconds?: number; /** Gets or sets the hardware acceleration type. */ - HardwareAccelerationType?: - | 'none' - | 'amf' - | 'qsv' - | 'nvenc' - | 'v4l2m2m' - | 'vaapi' - | 'videotoolbox' - | 'rkmpp'; + HardwareAccelerationType?: 'none' | 'amf' | 'qsv' | 'nvenc' | 'v4l2m2m' | 'vaapi' | 'videotoolbox' | 'rkmpp'; /** Gets or sets the FFmpeg path as set by the user via the UI. */ EncoderAppPath?: string | null; /** Gets or sets the current FFmpeg path being used by the system and displayed on the transcode page. */ @@ -1741,15 +1715,7 @@ export interface EncodingOptions { /** Gets or sets a value indicating whether videotoolbox tonemapping is enabled. */ EnableVideoToolboxTonemapping?: boolean; /** Gets or sets the tone-mapping algorithm. */ - TonemappingAlgorithm?: - | 'none' - | 'clip' - | 'linear' - | 'gamma' - | 'reinhard' - | 'hable' - | 'mobius' - | 'bt2390'; + TonemappingAlgorithm?: 'none' | 'clip' | 'linear' | 'gamma' | 'reinhard' | 'hable' | 'mobius' | 'bt2390'; /** Gets or sets the tone-mapping mode. */ TonemappingMode?: 'auto' | 'max' | 'rgb' | 'lum' | 'itp'; /** Gets or sets the tone-mapping range. */ @@ -2636,34 +2602,13 @@ export interface InboundKeepAliveMessage { /** Represents the list of possible inbound websocket types */ export type InboundWebSocketMessage = BaseInboundWebSocketMessage & ( - | BaseInboundWebSocketMessageMessageTypeMapping< - 'ActivityLogEntryStart', - ActivityLogEntryStartMessage - > - | BaseInboundWebSocketMessageMessageTypeMapping< - 'ActivityLogEntryStop', - ActivityLogEntryStopMessage - > - | BaseInboundWebSocketMessageMessageTypeMapping< - 'KeepAlive', - InboundKeepAliveMessage - > - | BaseInboundWebSocketMessageMessageTypeMapping< - 'ScheduledTasksInfoStart', - ScheduledTasksInfoStartMessage - > - | BaseInboundWebSocketMessageMessageTypeMapping< - 'ScheduledTasksInfoStop', - ScheduledTasksInfoStopMessage - > - | BaseInboundWebSocketMessageMessageTypeMapping< - 'SessionsStart', - SessionsStartMessage - > - | BaseInboundWebSocketMessageMessageTypeMapping< - 'SessionsStop', - SessionsStopMessage - > + | BaseInboundWebSocketMessageMessageTypeMapping<'ActivityLogEntryStart', ActivityLogEntryStartMessage> + | BaseInboundWebSocketMessageMessageTypeMapping<'ActivityLogEntryStop', ActivityLogEntryStopMessage> + | BaseInboundWebSocketMessageMessageTypeMapping<'KeepAlive', InboundKeepAliveMessage> + | BaseInboundWebSocketMessageMessageTypeMapping<'ScheduledTasksInfoStart', ScheduledTasksInfoStartMessage> + | BaseInboundWebSocketMessageMessageTypeMapping<'ScheduledTasksInfoStop', ScheduledTasksInfoStopMessage> + | BaseInboundWebSocketMessageMessageTypeMapping<'SessionsStart', SessionsStartMessage> + | BaseInboundWebSocketMessageMessageTypeMapping<'SessionsStop', SessionsStopMessage> ); /** Class InstallationInfo. */ @@ -3015,11 +2960,7 @@ export interface LibraryOptions { DelimiterWhitelist?: string[]; AutomaticallyAddToCollection?: boolean; /** An enum representing the options to disable embedded subs. */ - AllowEmbeddedSubtitles?: - | 'AllowAll' - | 'AllowText' - | 'AllowImage' - | 'AllowNone'; + AllowEmbeddedSubtitles?: 'AllowAll' | 'AllowText' | 'AllowImage' | 'AllowNone'; TypeOptions?: TypeOptions[]; } @@ -3340,15 +3281,7 @@ export interface MediaSourceInfo { Id?: string | null; Path?: string | null; EncoderPath?: string | null; - EncoderProtocol?: - | 'File' - | 'Http' - | 'Rtmp' - | 'Rtsp' - | 'Udp' - | 'Rtp' - | 'Ftp' - | null; + EncoderProtocol?: 'File' | 'Http' | 'Rtmp' | 'Rtsp' | 'Udp' | 'Rtp' | 'Ftp' | null; Type?: 'Default' | 'Grouping' | 'Placeholder'; Container?: string | null; /** @format int64 */ @@ -3382,13 +3315,7 @@ export interface MediaSourceInfo { SupportsProbing?: boolean; VideoType?: 'VideoFile' | 'Iso' | 'Dvd' | 'BluRay' | null; IsoType?: 'Dvd' | 'BluRay' | null; - Video3DFormat?: - | 'HalfSideBySide' - | 'FullSideBySide' - | 'FullTopAndBottom' - | 'HalfTopAndBottom' - | 'MVC' - | null; + Video3DFormat?: 'HalfSideBySide' | 'FullSideBySide' | 'FullTopAndBottom' | 'HalfTopAndBottom' | 'MVC' | null; MediaStreams?: MediaStream[] | null; MediaAttachments?: MediaAttachment[] | null; Formats?: string[] | null; @@ -4032,115 +3959,34 @@ export interface OutboundKeepAliveMessage { /** Represents the list of possible outbound websocket types */ export type OutboundWebSocketMessage = BaseOutboundWebSocketMessage & ( - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'ActivityLogEntry', - ActivityLogEntryMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'ForceKeepAlive', - ForceKeepAliveMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'GeneralCommand', - GeneralCommandMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'LibraryChanged', - LibraryChangedMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'KeepAlive', - OutboundKeepAliveMessage - > + | BaseOutboundWebSocketMessageMessageTypeMapping<'ActivityLogEntry', ActivityLogEntryMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'ForceKeepAlive', ForceKeepAliveMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'GeneralCommand', GeneralCommandMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'LibraryChanged', LibraryChangedMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'KeepAlive', OutboundKeepAliveMessage> | BaseOutboundWebSocketMessageMessageTypeMapping<'Play', PlayMessage> - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'Playstate', - PlaystateMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'PackageInstallationCancelled', - PluginInstallationCancelledMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'PackageInstallationCompleted', - PluginInstallationCompletedMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'PackageInstallationFailed', - PluginInstallationFailedMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'PackageInstalling', - PluginInstallingMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'PackageUninstalled', - PluginUninstalledMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'RefreshProgress', - RefreshProgressMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'RestartRequired', - RestartRequiredMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'ScheduledTaskEnded', - ScheduledTaskEndedMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'ScheduledTasksInfo', - ScheduledTasksInfoMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'SeriesTimerCancelled', - SeriesTimerCancelledMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'SeriesTimerCreated', - SeriesTimerCreatedMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'ServerRestarting', - ServerRestartingMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'ServerShuttingDown', - ServerShuttingDownMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'Sessions', - SessionsMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'SyncPlayCommand', - SyncPlayCommandMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'SyncPlayGroupUpdate', - SyncPlayGroupUpdateCommandMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'TimerCancelled', - TimerCancelledMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'TimerCreated', - TimerCreatedMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'UserDataChanged', - UserDataChangedMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'UserDeleted', - UserDeletedMessage - > - | BaseOutboundWebSocketMessageMessageTypeMapping< - 'UserUpdated', - UserUpdatedMessage - > + | BaseOutboundWebSocketMessageMessageTypeMapping<'Playstate', PlaystateMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'PackageInstallationCancelled', PluginInstallationCancelledMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'PackageInstallationCompleted', PluginInstallationCompletedMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'PackageInstallationFailed', PluginInstallationFailedMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'PackageInstalling', PluginInstallingMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'PackageUninstalled', PluginUninstalledMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'RefreshProgress', RefreshProgressMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'RestartRequired', RestartRequiredMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'ScheduledTaskEnded', ScheduledTaskEndedMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'ScheduledTasksInfo', ScheduledTasksInfoMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'SeriesTimerCancelled', SeriesTimerCancelledMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'SeriesTimerCreated', SeriesTimerCreatedMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'ServerRestarting', ServerRestartingMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'ServerShuttingDown', ServerShuttingDownMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'Sessions', SessionsMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'SyncPlayCommand', SyncPlayCommandMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'SyncPlayGroupUpdate', SyncPlayGroupUpdateCommandMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'TimerCancelled', TimerCancelledMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'TimerCreated', TimerCreatedMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'UserDataChanged', UserDataChangedMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'UserDeleted', UserDeletedMessage> + | BaseOutboundWebSocketMessageMessageTypeMapping<'UserUpdated', UserUpdatedMessage> ); /** Class PackageInfo. */ @@ -4731,12 +4577,7 @@ export interface PlayRequest { */ StartPositionTicks?: number | null; /** Gets or sets the play command. */ - PlayCommand?: - | 'PlayNow' - | 'PlayNext' - | 'PlayLast' - | 'PlayInstantMix' - | 'PlayShuffle'; + PlayCommand?: 'PlayNow' | 'PlayNext' | 'PlayLast' | 'PlayInstantMix' | 'PlayShuffle'; /** * Gets or sets the controlling user identifier. * @format uuid @@ -4868,14 +4709,7 @@ export interface PluginInfo { /** Gets or sets a value indicating whether this plugin has a valid image. */ HasImage?: boolean; /** Gets or sets a value indicating the status of the plugin. */ - Status?: - | 'Active' - | 'Restart' - | 'Deleted' - | 'Superceded' - | 'Malfunctioned' - | 'NotSupported' - | 'Disabled'; + Status?: 'Active' | 'Restart' | 'Deleted' | 'Superceded' | 'Malfunctioned' | 'NotSupported' | 'Disabled'; } /** Plugin installation cancelled message. */ @@ -5168,12 +5002,7 @@ export enum ProcessPriorityClass { } export interface ProfileCondition { - Condition?: - | 'Equals' - | 'NotEquals' - | 'LessThanEqual' - | 'GreaterThanEqual' - | 'EqualsAny'; + Condition?: 'Equals' | 'NotEquals' | 'LessThanEqual' | 'GreaterThanEqual' | 'EqualsAny'; Property?: | 'AudioChannels' | 'AudioBitrate' @@ -6226,11 +6055,7 @@ export interface SeriesTimerInfoDto { ParentBackdropImageTags?: string[] | null; /** Gets or sets a value indicating whether this instance is post padding required. */ IsPostPaddingRequired?: boolean; - KeepUntil?: - | 'UntilDeleted' - | 'UntilSpaceNeeded' - | 'UntilWatched' - | 'UntilDate'; + KeepUntil?: 'UntilDeleted' | 'UntilSpaceNeeded' | 'UntilWatched' | 'UntilDate'; /** Gets or sets a value indicating whether [record any time]. */ RecordAnyTime?: boolean; SkipEpisodesInLibrary?: boolean; @@ -6408,16 +6233,7 @@ export interface ServerConfiguration { */ DummyChapterDuration?: number; /** Gets or sets the chapter image resolution. */ - ChapterImageResolution?: - | 'MatchSource' - | 'P144' - | 'P240' - | 'P360' - | 'P480' - | 'P720' - | 'P1080' - | 'P1440' - | 'P2160'; + ChapterImageResolution?: 'MatchSource' | 'P144' | 'P240' | 'P360' | 'P480' | 'P720' | 'P1080' | 'P1440' | 'P2160'; /** * Gets or sets the limit for parallel image encoding. * @format int32 @@ -7257,15 +7073,7 @@ export interface TaskTriggerInfo { */ IntervalTicks?: number | null; /** Gets or sets the day of week. */ - DayOfWeek?: - | 'Sunday' - | 'Monday' - | 'Tuesday' - | 'Wednesday' - | 'Thursday' - | 'Friday' - | 'Saturday' - | null; + DayOfWeek?: 'Sunday' | 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday' | null; /** * Gets or sets the maximum runtime ticks. * @format int64 @@ -7461,20 +7269,9 @@ export interface TimerInfoDto { ParentBackdropImageTags?: string[] | null; /** Gets or sets a value indicating whether this instance is post padding required. */ IsPostPaddingRequired?: boolean; - KeepUntil?: - | 'UntilDeleted' - | 'UntilSpaceNeeded' - | 'UntilWatched' - | 'UntilDate'; + KeepUntil?: 'UntilDeleted' | 'UntilSpaceNeeded' | 'UntilWatched' | 'UntilDate'; /** Gets or sets the status. */ - Status?: - | 'New' - | 'InProgress' - | 'Completed' - | 'Cancelled' - | 'ConflictedOk' - | 'ConflictedNotOk' - | 'Error'; + Status?: 'New' | 'InProgress' | 'Completed' | 'Cancelled' | 'ConflictedOk' | 'ConflictedNotOk' | 'Error'; /** Gets or sets the series timer identifier. */ SeriesTimerId?: string | null; /** Gets or sets the external series timer identifier. */ @@ -7646,16 +7443,7 @@ export interface TranscodingInfo { */ AudioChannels?: number | null; /** Gets or sets the hardware acceleration type. */ - HardwareAccelerationType?: - | 'none' - | 'amf' - | 'qsv' - | 'nvenc' - | 'v4l2m2m' - | 'vaapi' - | 'videotoolbox' - | 'rkmpp' - | null; + HardwareAccelerationType?: 'none' | 'amf' | 'qsv' | 'nvenc' | 'v4l2m2m' | 'vaapi' | 'videotoolbox' | 'rkmpp' | null; /** Gets or sets the transcode reasons. */ TranscodeReasons?: | 'ContainerNotSupported' @@ -7818,13 +7606,7 @@ export interface TrickplayOptions { /** Gets or sets the behavior used by trickplay provider on library scan/update. */ ScanBehavior?: 'Blocking' | 'NonBlocking'; /** Gets or sets the process priority for the ffmpeg process. */ - ProcessPriority?: - | 'Normal' - | 'Idle' - | 'High' - | 'RealTime' - | 'BelowNormal' - | 'AboveNormal'; + ProcessPriority?: 'Normal' | 'Idle' | 'High' | 'RealTime' | 'BelowNormal' | 'AboveNormal'; /** * Gets or sets the interval, in ms, between each new trickplay image. * @format int32 @@ -8467,16 +8249,7 @@ export interface VirtualFolderInfo { /** Gets or sets the locations. */ Locations?: string[] | null; /** Gets or sets the type of the collection. */ - CollectionType?: - | 'movies' - | 'tvshows' - | 'music' - | 'musicvideos' - | 'homevideos' - | 'boxsets' - | 'books' - | 'mixed' - | null; + CollectionType?: 'movies' | 'tvshows' | 'music' | 'musicvideos' | 'homevideos' | 'boxsets' | 'books' | 'mixed' | null; LibraryOptions?: LibraryOptions | null; /** Gets or sets the item identifier. */ ItemId?: string | null; @@ -8499,9 +8272,7 @@ export interface WakeOnLanInfo { } /** Represents the possible websocket types */ -export type WebSocketMessage = - | InboundWebSocketMessage - | OutboundWebSocketMessage; +export type WebSocketMessage = InboundWebSocketMessage | OutboundWebSocketMessage; export interface XbmcMetadataOptions { UserId?: string | null; @@ -8551,19 +8322,12 @@ type BaseOutboundWebSocketMessageMessageTypeMapping = { MessageType: Key; } & Type; -import type { - AxiosInstance, - AxiosRequestConfig, - AxiosResponse, - HeadersDefaults, - ResponseType, -} from 'axios'; +import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, HeadersDefaults, ResponseType } from 'axios'; import axios from 'axios'; export type QueryParamsType = Record; -export interface FullRequestParams - extends Omit { +export interface FullRequestParams extends Omit { /** set parameter to `true` for call `securityWorker` for this request */ secure?: boolean; /** request path */ @@ -8578,13 +8342,9 @@ export interface FullRequestParams body?: unknown; } -export type RequestParams = Omit< - FullRequestParams, - 'body' | 'method' | 'query' | 'path' ->; +export type RequestParams = Omit; -export interface ApiConfig - extends Omit { +export interface ApiConfig extends Omit { securityWorker?: ( securityData: SecurityDataType | null, ) => Promise | AxiosRequestConfig | void; @@ -8606,16 +8366,8 @@ export class HttpClient { private secure?: boolean; private format?: ResponseType; - constructor({ - securityWorker, - secure, - format, - ...axiosConfig - }: ApiConfig = {}) { - this.instance = axios.create({ - ...axiosConfig, - baseURL: axiosConfig.baseURL || 'http://localhost', - }); + constructor({ securityWorker, secure, format, ...axiosConfig }: ApiConfig = {}) { + this.instance = axios.create({ ...axiosConfig, baseURL: axiosConfig.baseURL || 'http://localhost' }); this.secure = secure; this.format = format; this.securityWorker = securityWorker; @@ -8625,10 +8377,7 @@ export class HttpClient { this.securityData = data; }; - protected mergeRequestParams( - params1: AxiosRequestConfig, - params2?: AxiosRequestConfig, - ): AxiosRequestConfig { + protected mergeRequestParams(params1: AxiosRequestConfig, params2?: AxiosRequestConfig): AxiosRequestConfig { const method = params1.method || (params2 && params2.method); return { @@ -8636,11 +8385,7 @@ export class HttpClient { ...params1, ...(params2 || {}), headers: { - ...((method && - this.instance.defaults.headers[ - method.toLowerCase() as keyof HeadersDefaults - ]) || - {}), + ...((method && this.instance.defaults.headers[method.toLowerCase() as keyof HeadersDefaults]) || {}), ...(params1.headers || {}), ...((params2 && params2.headers) || {}), }, @@ -8661,15 +8406,11 @@ export class HttpClient { } return Object.keys(input || {}).reduce((formData, key) => { const property = input[key]; - const propertyContent: any[] = - property instanceof Array ? property : [property]; + const propertyContent: any[] = property instanceof Array ? property : [property]; for (const formItem of propertyContent) { const isFileType = formItem instanceof Blob || formItem instanceof File; - formData.append( - key, - isFileType ? formItem : this.stringifyFormItem(formItem), - ); + formData.append(key, isFileType ? formItem : this.stringifyFormItem(formItem)); } return formData; @@ -8693,21 +8434,11 @@ export class HttpClient { const requestParams = this.mergeRequestParams(params, secureParams); const responseFormat = format || this.format || undefined; - if ( - type === ContentType.FormData && - body && - body !== null && - typeof body === 'object' - ) { + if (type === ContentType.FormData && body && body !== null && typeof body === 'object') { body = this.createFormData(body as Record); } - if ( - type === ContentType.Text && - body && - body !== null && - typeof body !== 'string' - ) { + if (type === ContentType.Text && body && body !== null && typeof body !== 'string') { body = JSON.stringify(body); } @@ -8730,9 +8461,7 @@ export class HttpClient { * @version 10.10.3 * @baseUrl http://localhost */ -export class Api< - SecurityDataType extends unknown, -> extends HttpClient { +export class Api extends HttpClient { system = { /** * No description @@ -8801,10 +8530,7 @@ export class Api< * @request POST:/System/Configuration * @secure */ - updateConfiguration: ( - data: ServerConfiguration, - params: RequestParams = {}, - ) => + updateConfiguration: (data: ServerConfiguration, params: RequestParams = {}) => this.request({ path: `/System/Configuration`, method: 'POST', @@ -8841,11 +8567,7 @@ export class Api< * @request POST:/System/Configuration/{key} * @secure */ - updateNamedConfiguration: ( - key: string, - data: any, - params: RequestParams = {}, - ) => + updateNamedConfiguration: (key: string, data: any, params: RequestParams = {}) => this.request({ path: `/System/Configuration/${key}`, method: 'POST', @@ -11359,11 +11081,7 @@ export class Api< * @summary Gets the specified audio segment for an audio item. * @request GET:/Audio/{itemId}/hls/{segmentId}/stream.aac */ - getHlsAudioSegmentLegacyAac: ( - itemId: string, - segmentId: string, - params: RequestParams = {}, - ) => + getHlsAudioSegmentLegacyAac: (itemId: string, segmentId: string, params: RequestParams = {}) => this.request({ path: `/Audio/${itemId}/hls/${segmentId}/stream.aac`, method: 'GET', @@ -11378,11 +11096,7 @@ export class Api< * @summary Gets the specified audio segment for an audio item. * @request GET:/Audio/{itemId}/hls/{segmentId}/stream.mp3 */ - getHlsAudioSegmentLegacyMp3: ( - itemId: string, - segmentId: string, - params: RequestParams = {}, - ) => + getHlsAudioSegmentLegacyMp3: (itemId: string, segmentId: string, params: RequestParams = {}) => this.request({ path: `/Audio/${itemId}/hls/${segmentId}/stream.mp3`, method: 'GET', @@ -11480,11 +11194,7 @@ export class Api< * @request POST:/Audio/{itemId}/RemoteSearch/Lyrics/{lyricId} * @secure */ - downloadRemoteLyrics: ( - itemId: string, - lyricId: string, - params: RequestParams = {}, - ) => + downloadRemoteLyrics: (itemId: string, lyricId: string, params: RequestParams = {}) => this.request({ path: `/Audio/${itemId}/RemoteSearch/Lyrics/${lyricId}`, method: 'POST', @@ -13506,11 +13216,7 @@ The body is expected to the image contents base64 encoded. * @request GET:/Videos/{itemId}/hls/{playlistId}/stream.m3u8 * @secure */ - getHlsPlaylistLegacy: ( - itemId: string, - playlistId: string, - params: RequestParams = {}, - ) => + getHlsPlaylistLegacy: (itemId: string, playlistId: string, params: RequestParams = {}) => this.request({ path: `/Videos/${itemId}/hls/${playlistId}/stream.m3u8`, method: 'GET', @@ -13583,11 +13289,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Videos/{itemId}/Subtitles * @secure */ - uploadSubtitle: ( - itemId: string, - data: UploadSubtitleDto, - params: RequestParams = {}, - ) => + uploadSubtitle: (itemId: string, data: UploadSubtitleDto, params: RequestParams = {}) => this.request({ path: `/Videos/${itemId}/Subtitles`, method: 'POST', @@ -13606,11 +13308,7 @@ The body is expected to the image contents base64 encoded. * @request DELETE:/Videos/{itemId}/Subtitles/{index} * @secure */ - deleteSubtitle: ( - itemId: string, - index: number, - params: RequestParams = {}, - ) => + deleteSubtitle: (itemId: string, index: number, params: RequestParams = {}) => this.request({ path: `/Videos/${itemId}/Subtitles/${index}`, method: 'DELETE', @@ -13821,12 +13519,7 @@ The body is expected to the image contents base64 encoded. * @summary Get video attachment. * @request GET:/Videos/{videoId}/{mediaSourceId}/Attachments/{index} */ - getAttachment: ( - videoId: string, - mediaSourceId: string, - index: number, - params: RequestParams = {}, - ) => + getAttachment: (videoId: string, mediaSourceId: string, index: number, params: RequestParams = {}) => this.request({ path: `/Videos/${videoId}/${mediaSourceId}/Attachments/${index}`, method: 'GET', @@ -15809,10 +15502,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Items/RemoteSearch/Book * @secure */ - getBookRemoteSearchResults: ( - data: BookInfoRemoteSearchQuery, - params: RequestParams = {}, - ) => + getBookRemoteSearchResults: (data: BookInfoRemoteSearchQuery, params: RequestParams = {}) => this.request({ path: `/Items/RemoteSearch/Book`, method: 'POST', @@ -15832,10 +15522,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Items/RemoteSearch/BoxSet * @secure */ - getBoxSetRemoteSearchResults: ( - data: BoxSetInfoRemoteSearchQuery, - params: RequestParams = {}, - ) => + getBoxSetRemoteSearchResults: (data: BoxSetInfoRemoteSearchQuery, params: RequestParams = {}) => this.request({ path: `/Items/RemoteSearch/BoxSet`, method: 'POST', @@ -15855,10 +15542,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Items/RemoteSearch/Movie * @secure */ - getMovieRemoteSearchResults: ( - data: MovieInfoRemoteSearchQuery, - params: RequestParams = {}, - ) => + getMovieRemoteSearchResults: (data: MovieInfoRemoteSearchQuery, params: RequestParams = {}) => this.request({ path: `/Items/RemoteSearch/Movie`, method: 'POST', @@ -15878,10 +15562,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Items/RemoteSearch/MusicAlbum * @secure */ - getMusicAlbumRemoteSearchResults: ( - data: AlbumInfoRemoteSearchQuery, - params: RequestParams = {}, - ) => + getMusicAlbumRemoteSearchResults: (data: AlbumInfoRemoteSearchQuery, params: RequestParams = {}) => this.request({ path: `/Items/RemoteSearch/MusicAlbum`, method: 'POST', @@ -15901,10 +15582,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Items/RemoteSearch/MusicArtist * @secure */ - getMusicArtistRemoteSearchResults: ( - data: ArtistInfoRemoteSearchQuery, - params: RequestParams = {}, - ) => + getMusicArtistRemoteSearchResults: (data: ArtistInfoRemoteSearchQuery, params: RequestParams = {}) => this.request({ path: `/Items/RemoteSearch/MusicArtist`, method: 'POST', @@ -15924,10 +15602,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Items/RemoteSearch/MusicVideo * @secure */ - getMusicVideoRemoteSearchResults: ( - data: MusicVideoInfoRemoteSearchQuery, - params: RequestParams = {}, - ) => + getMusicVideoRemoteSearchResults: (data: MusicVideoInfoRemoteSearchQuery, params: RequestParams = {}) => this.request({ path: `/Items/RemoteSearch/MusicVideo`, method: 'POST', @@ -15947,10 +15622,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Items/RemoteSearch/Person * @secure */ - getPersonRemoteSearchResults: ( - data: PersonLookupInfoRemoteSearchQuery, - params: RequestParams = {}, - ) => + getPersonRemoteSearchResults: (data: PersonLookupInfoRemoteSearchQuery, params: RequestParams = {}) => this.request({ path: `/Items/RemoteSearch/Person`, method: 'POST', @@ -15970,10 +15642,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Items/RemoteSearch/Series * @secure */ - getSeriesRemoteSearchResults: ( - data: SeriesInfoRemoteSearchQuery, - params: RequestParams = {}, - ) => + getSeriesRemoteSearchResults: (data: SeriesInfoRemoteSearchQuery, params: RequestParams = {}) => this.request({ path: `/Items/RemoteSearch/Series`, method: 'POST', @@ -15993,10 +15662,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Items/RemoteSearch/Trailer * @secure */ - getTrailerRemoteSearchResults: ( - data: TrailerInfoRemoteSearchQuery, - params: RequestParams = {}, - ) => + getTrailerRemoteSearchResults: (data: TrailerInfoRemoteSearchQuery, params: RequestParams = {}) => this.request({ path: `/Items/RemoteSearch/Trailer`, method: 'POST', @@ -16023,20 +15689,12 @@ The body is expected to the image contents base64 encoded. * (Optional) Specifies the metadata refresh mode. * @default "None" */ - metadataRefreshMode?: - | 'None' - | 'ValidationOnly' - | 'Default' - | 'FullRefresh'; + metadataRefreshMode?: 'None' | 'ValidationOnly' | 'Default' | 'FullRefresh'; /** * (Optional) Specifies the image refresh mode. * @default "None" */ - imageRefreshMode?: - | 'None' - | 'ValidationOnly' - | 'Default' - | 'FullRefresh'; + imageRefreshMode?: 'None' | 'ValidationOnly' | 'Default' | 'FullRefresh'; /** * (Optional) Determines if metadata should be replaced. Only applicable if mode is FullRefresh. * @default false @@ -16351,11 +16009,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Items/{itemId} * @secure */ - updateItem: ( - itemId: string, - data: BaseItemDto, - params: RequestParams = {}, - ) => + updateItem: (itemId: string, data: BaseItemDto, params: RequestParams = {}) => this.request({ path: `/Items/${itemId}`, method: 'POST', @@ -17000,11 +16654,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Items/{itemId}/RemoteSearch/Subtitles/{subtitleId} * @secure */ - downloadRemoteSubtitles: ( - itemId: string, - subtitleId: string, - params: RequestParams = {}, - ) => + downloadRemoteSubtitles: (itemId: string, subtitleId: string, params: RequestParams = {}) => this.request({ path: `/Items/${itemId}/RemoteSearch/Subtitles/${subtitleId}`, method: 'POST', @@ -19733,11 +19383,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Playlists/{playlistId} * @secure */ - updatePlaylist: ( - playlistId: string, - data: UpdatePlaylistDto, - params: RequestParams = {}, - ) => + updatePlaylist: (playlistId: string, data: UpdatePlaylistDto, params: RequestParams = {}) => this.request({ path: `/Playlists/${playlistId}`, method: 'POST', @@ -19881,12 +19527,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Playlists/{playlistId}/Items/{itemId}/Move/{newIndex} * @secure */ - moveItem: ( - playlistId: string, - itemId: string, - newIndex: number, - params: RequestParams = {}, - ) => + moveItem: (playlistId: string, itemId: string, newIndex: number, params: RequestParams = {}) => this.request({ path: `/Playlists/${playlistId}/Items/${itemId}/Move/${newIndex}`, method: 'POST', @@ -19921,11 +19562,7 @@ The body is expected to the image contents base64 encoded. * @request GET:/Playlists/{playlistId}/Users/{userId} * @secure */ - getPlaylistUser: ( - playlistId: string, - userId: string, - params: RequestParams = {}, - ) => + getPlaylistUser: (playlistId: string, userId: string, params: RequestParams = {}) => this.request({ path: `/Playlists/${playlistId}/Users/${userId}`, method: 'GET', @@ -19943,12 +19580,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Playlists/{playlistId}/Users/{userId} * @secure */ - updatePlaylistUser: ( - playlistId: string, - userId: string, - data: UpdatePlaylistUserDto, - params: RequestParams = {}, - ) => + updatePlaylistUser: (playlistId: string, userId: string, data: UpdatePlaylistUserDto, params: RequestParams = {}) => this.request({ path: `/Playlists/${playlistId}/Users/${userId}`, method: 'POST', @@ -19967,11 +19599,7 @@ The body is expected to the image contents base64 encoded. * @request DELETE:/Playlists/{playlistId}/Users/{userId} * @secure */ - removeUserFromPlaylist: ( - playlistId: string, - userId: string, - params: RequestParams = {}, - ) => + removeUserFromPlaylist: (playlistId: string, userId: string, params: RequestParams = {}) => this.request({ path: `/Playlists/${playlistId}/Users/${userId}`, method: 'DELETE', @@ -20483,15 +20111,7 @@ The body is expected to the image contents base64 encoded. /** The name of the virtual folder. */ name?: string; /** The type of the collection. */ - collectionType?: - | 'movies' - | 'tvshows' - | 'music' - | 'musicvideos' - | 'homevideos' - | 'boxsets' - | 'books' - | 'mixed'; + collectionType?: 'movies' | 'tvshows' | 'music' | 'musicvideos' | 'homevideos' | 'boxsets' | 'books' | 'mixed'; /** The paths of the virtual folder. */ paths?: string[]; /** @@ -20550,10 +20170,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Library/VirtualFolders/LibraryOptions * @secure */ - updateLibraryOptions: ( - data: UpdateLibraryOptionsDto, - params: RequestParams = {}, - ) => + updateLibraryOptions: (data: UpdateLibraryOptionsDto, params: RequestParams = {}) => this.request({ path: `/Library/VirtualFolders/LibraryOptions`, method: 'POST', @@ -20664,10 +20281,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Library/VirtualFolders/Paths/Update * @secure */ - updateMediaPath: ( - data: UpdateMediaPathRequestDto, - params: RequestParams = {}, - ) => + updateMediaPath: (data: UpdateMediaPathRequestDto, params: RequestParams = {}) => this.request({ path: `/Library/VirtualFolders/Paths/Update`, method: 'POST', @@ -21425,10 +21039,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/LiveTv/ChannelMappings * @secure */ - setChannelMapping: ( - data: SetChannelMappingDto, - params: RequestParams = {}, - ) => + setChannelMapping: (data: SetChannelMappingDto, params: RequestParams = {}) => this.request({ path: `/LiveTv/ChannelMappings`, method: 'POST', @@ -21739,11 +21350,7 @@ The body is expected to the image contents base64 encoded. * @summary Gets a live tv channel stream. * @request GET:/LiveTv/LiveStreamFiles/{streamId}/stream.{container} */ - getLiveStreamFile: ( - streamId: string, - container: string, - params: RequestParams = {}, - ) => + getLiveStreamFile: (streamId: string, container: string, params: RequestParams = {}) => this.request({ path: `/LiveTv/LiveStreamFiles/${streamId}/stream.${container}`, method: 'GET', @@ -22002,14 +21609,7 @@ The body is expected to the image contents base64 encoded. */ limit?: number; /** Optional. Filter by recording status. */ - status?: - | 'New' - | 'InProgress' - | 'Completed' - | 'Cancelled' - | 'ConflictedOk' - | 'ConflictedNotOk' - | 'Error'; + status?: 'New' | 'InProgress' | 'Completed' | 'Cancelled' | 'ConflictedOk' | 'ConflictedNotOk' | 'Error'; /** Optional. Filter by recordings that are in progress, or not. */ isInProgress?: boolean; /** Optional. Filter by recordings belonging to a series timer. */ @@ -22209,14 +21809,7 @@ The body is expected to the image contents base64 encoded. */ limit?: number; /** Optional. Filter by recording status. */ - status?: - | 'New' - | 'InProgress' - | 'Completed' - | 'Cancelled' - | 'ConflictedOk' - | 'ConflictedNotOk' - | 'Error'; + status?: 'New' | 'InProgress' | 'Completed' | 'Cancelled' | 'ConflictedOk' | 'ConflictedNotOk' | 'Error'; /** Optional. Filter by recordings that are in progress, or not. */ isInProgress?: boolean; /** Optional. Filter by recordings belonging to a series timer. */ @@ -22341,11 +21934,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/LiveTv/SeriesTimers/{timerId} * @secure */ - updateSeriesTimer: ( - timerId: string, - data: SeriesTimerInfoDto, - params: RequestParams = {}, - ) => + updateSeriesTimer: (timerId: string, data: SeriesTimerInfoDto, params: RequestParams = {}) => this.request({ path: `/LiveTv/SeriesTimers/${timerId}`, method: 'POST', @@ -22449,11 +22038,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/LiveTv/Timers/{timerId} * @secure */ - updateTimer: ( - timerId: string, - data: TimerInfoDto, - params: RequestParams = {}, - ) => + updateTimer: (timerId: string, data: TimerInfoDto, params: RequestParams = {}) => this.request({ path: `/LiveTv/Timers/${timerId}`, method: 'POST', @@ -22975,10 +22560,7 @@ The body is expected to the image contents base64 encoded. * @request DELETE:/Packages/Installing/{packageId} * @secure */ - cancelPackageInstallation: ( - packageId: string, - params: RequestParams = {}, - ) => + cancelPackageInstallation: (packageId: string, params: RequestParams = {}) => this.request({ path: `/Packages/Installing/${packageId}`, method: 'DELETE', @@ -23180,10 +22762,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Sessions/Playing * @secure */ - reportPlaybackStart: ( - data: PlaybackStartInfo, - params: RequestParams = {}, - ) => + reportPlaybackStart: (data: PlaybackStartInfo, params: RequestParams = {}) => this.request({ path: `/Sessions/Playing`, method: 'POST', @@ -23226,10 +22805,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Sessions/Playing/Progress * @secure */ - reportPlaybackProgress: ( - data: PlaybackProgressInfo, - params: RequestParams = {}, - ) => + reportPlaybackProgress: (data: PlaybackProgressInfo, params: RequestParams = {}) => this.request({ path: `/Sessions/Playing/Progress`, method: 'POST', @@ -23248,10 +22824,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Sessions/Playing/Stopped * @secure */ - reportPlaybackStopped: ( - data: PlaybackStopInfo, - params: RequestParams = {}, - ) => + reportPlaybackStopped: (data: PlaybackStopInfo, params: RequestParams = {}) => this.request({ path: `/Sessions/Playing/Stopped`, method: 'POST', @@ -23305,11 +22878,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Sessions/{sessionId}/Command * @secure */ - sendFullGeneralCommand: ( - sessionId: string, - data: GeneralCommand, - params: RequestParams = {}, - ) => + sendFullGeneralCommand: (sessionId: string, data: GeneralCommand, params: RequestParams = {}) => this.request({ path: `/Sessions/${sessionId}/Command`, method: 'POST', @@ -23392,11 +22961,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Sessions/{sessionId}/Message * @secure */ - sendMessageCommand: ( - sessionId: string, - data: MessageCommand, - params: RequestParams = {}, - ) => + sendMessageCommand: (sessionId: string, data: MessageCommand, params: RequestParams = {}) => this.request({ path: `/Sessions/${sessionId}/Message`, method: 'POST', @@ -23419,12 +22984,7 @@ The body is expected to the image contents base64 encoded. sessionId: string, query: { /** Enum PlayCommand. */ - playCommand: - | 'PlayNow' - | 'PlayNext' - | 'PlayLast' - | 'PlayInstantMix' - | 'PlayShuffle'; + playCommand: 'PlayNow' | 'PlayNext' | 'PlayLast' | 'PlayInstantMix' | 'PlayShuffle'; /** The ids of the items to play, comma delimited. */ itemIds: string[]; /** @@ -23573,11 +23133,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Sessions/{sessionId}/User/{userId} * @secure */ - addUserToSession: ( - sessionId: string, - userId: string, - params: RequestParams = {}, - ) => + addUserToSession: (sessionId: string, userId: string, params: RequestParams = {}) => this.request({ path: `/Sessions/${sessionId}/User/${userId}`, method: 'POST', @@ -23594,11 +23150,7 @@ The body is expected to the image contents base64 encoded. * @request DELETE:/Sessions/{sessionId}/User/{userId} * @secure */ - removeUserFromSession: ( - sessionId: string, - userId: string, - params: RequestParams = {}, - ) => + removeUserFromSession: (sessionId: string, userId: string, params: RequestParams = {}) => this.request({ path: `/Sessions/${sessionId}/User/${userId}`, method: 'DELETE', @@ -23890,11 +23442,7 @@ The body is expected to the image contents base64 encoded. * @request DELETE:/Plugins/{pluginId}/{version} * @secure */ - uninstallPluginByVersion: ( - pluginId: string, - version: string, - params: RequestParams = {}, - ) => + uninstallPluginByVersion: (pluginId: string, version: string, params: RequestParams = {}) => this.request({ path: `/Plugins/${pluginId}/${version}`, method: 'DELETE', @@ -23911,11 +23459,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Plugins/{pluginId}/{version}/Disable * @secure */ - disablePlugin: ( - pluginId: string, - version: string, - params: RequestParams = {}, - ) => + disablePlugin: (pluginId: string, version: string, params: RequestParams = {}) => this.request({ path: `/Plugins/${pluginId}/${version}/Disable`, method: 'POST', @@ -23932,11 +23476,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Plugins/{pluginId}/{version}/Enable * @secure */ - enablePlugin: ( - pluginId: string, - version: string, - params: RequestParams = {}, - ) => + enablePlugin: (pluginId: string, version: string, params: RequestParams = {}) => this.request({ path: `/Plugins/${pluginId}/${version}/Enable`, method: 'POST', @@ -23953,11 +23493,7 @@ The body is expected to the image contents base64 encoded. * @request GET:/Plugins/{pluginId}/{version}/Image * @secure */ - getPluginImage: ( - pluginId: string, - version: string, - params: RequestParams = {}, - ) => + getPluginImage: (pluginId: string, version: string, params: RequestParams = {}) => this.request({ path: `/Plugins/${pluginId}/${version}/Image`, method: 'GET', @@ -24159,11 +23695,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/ScheduledTasks/{taskId}/Triggers * @secure */ - updateTask: ( - taskId: string, - data: TaskTriggerInfo[], - params: RequestParams = {}, - ) => + updateTask: (taskId: string, data: TaskTriggerInfo[], params: RequestParams = {}) => this.request({ path: `/ScheduledTasks/${taskId}/Triggers`, method: 'POST', @@ -24339,10 +23871,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Startup/Configuration * @secure */ - updateInitialConfiguration: ( - data: StartupConfigurationDto, - params: RequestParams = {}, - ) => + updateInitialConfiguration: (data: StartupConfigurationDto, params: RequestParams = {}) => this.request({ path: `/Startup/Configuration`, method: 'POST', @@ -24379,10 +23908,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Startup/RemoteAccess * @secure */ - setRemoteAccess: ( - data: StartupRemoteAccessDto, - params: RequestParams = {}, - ) => + setRemoteAccess: (data: StartupRemoteAccessDto, params: RequestParams = {}) => this.request({ path: `/Startup/RemoteAccess`, method: 'POST', @@ -24494,10 +24020,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/SyncPlay/Join * @secure */ - syncPlayJoinGroup: ( - data: JoinGroupRequestDto, - params: RequestParams = {}, - ) => + syncPlayJoinGroup: (data: JoinGroupRequestDto, params: RequestParams = {}) => this.request({ path: `/SyncPlay/Join`, method: 'POST', @@ -24551,10 +24074,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/SyncPlay/MovePlaylistItem * @secure */ - syncPlayMovePlaylistItem: ( - data: MovePlaylistItemRequestDto, - params: RequestParams = {}, - ) => + syncPlayMovePlaylistItem: (data: MovePlaylistItemRequestDto, params: RequestParams = {}) => this.request({ path: `/SyncPlay/MovePlaylistItem`, method: 'POST', @@ -24573,10 +24093,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/SyncPlay/New * @secure */ - syncPlayCreateGroup: ( - data: NewGroupRequestDto, - params: RequestParams = {}, - ) => + syncPlayCreateGroup: (data: NewGroupRequestDto, params: RequestParams = {}) => this.request({ path: `/SyncPlay/New`, method: 'POST', @@ -24650,10 +24167,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/SyncPlay/PreviousItem * @secure */ - syncPlayPreviousItem: ( - data: PreviousItemRequestDto, - params: RequestParams = {}, - ) => + syncPlayPreviousItem: (data: PreviousItemRequestDto, params: RequestParams = {}) => this.request({ path: `/SyncPlay/PreviousItem`, method: 'POST', @@ -24710,10 +24224,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/SyncPlay/RemoveFromPlaylist * @secure */ - syncPlayRemoveFromPlaylist: ( - data: RemoveFromPlaylistRequestDto, - params: RequestParams = {}, - ) => + syncPlayRemoveFromPlaylist: (data: RemoveFromPlaylistRequestDto, params: RequestParams = {}) => this.request({ path: `/SyncPlay/RemoveFromPlaylist`, method: 'POST', @@ -24751,10 +24262,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/SyncPlay/SetIgnoreWait * @secure */ - syncPlaySetIgnoreWait: ( - data: IgnoreWaitRequestDto, - params: RequestParams = {}, - ) => + syncPlaySetIgnoreWait: (data: IgnoreWaitRequestDto, params: RequestParams = {}) => this.request({ path: `/SyncPlay/SetIgnoreWait`, method: 'POST', @@ -24792,10 +24300,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/SyncPlay/SetPlaylistItem * @secure */ - syncPlaySetPlaylistItem: ( - data: SetPlaylistItemRequestDto, - params: RequestParams = {}, - ) => + syncPlaySetPlaylistItem: (data: SetPlaylistItemRequestDto, params: RequestParams = {}) => this.request({ path: `/SyncPlay/SetPlaylistItem`, method: 'POST', @@ -24814,10 +24319,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/SyncPlay/SetRepeatMode * @secure */ - syncPlaySetRepeatMode: ( - data: SetRepeatModeRequestDto, - params: RequestParams = {}, - ) => + syncPlaySetRepeatMode: (data: SetRepeatModeRequestDto, params: RequestParams = {}) => this.request({ path: `/SyncPlay/SetRepeatMode`, method: 'POST', @@ -24836,10 +24338,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/SyncPlay/SetShuffleMode * @secure */ - syncPlaySetShuffleMode: ( - data: SetShuffleModeRequestDto, - params: RequestParams = {}, - ) => + syncPlaySetShuffleMode: (data: SetShuffleModeRequestDto, params: RequestParams = {}) => this.request({ path: `/SyncPlay/SetShuffleMode`, method: 'POST', @@ -25021,11 +24520,7 @@ The body is expected to the image contents base64 encoded. * @request POST:/Users/{userId}/Policy * @secure */ - updateUserPolicy: ( - userId: string, - data: UserPolicy, - params: RequestParams = {}, - ) => + updateUserPolicy: (userId: string, data: UserPolicy, params: RequestParams = {}) => this.request({ path: `/Users/${userId}/Policy`, method: 'POST', @@ -25043,10 +24538,7 @@ The body is expected to the image contents base64 encoded. * @summary Authenticates a user by name. * @request POST:/Users/AuthenticateByName */ - authenticateUserByName: ( - data: AuthenticateUserByName, - params: RequestParams = {}, - ) => + authenticateUserByName: (data: AuthenticateUserByName, params: RequestParams = {}) => this.request({ path: `/Users/AuthenticateByName`, method: 'POST', @@ -25064,10 +24556,7 @@ The body is expected to the image contents base64 encoded. * @summary Authenticates a user with quick connect. * @request POST:/Users/AuthenticateWithQuickConnect */ - authenticateWithQuickConnect: ( - data: QuickConnectDto, - params: RequestParams = {}, - ) => + authenticateWithQuickConnect: (data: QuickConnectDto, params: RequestParams = {}) => this.request({ path: `/Users/AuthenticateWithQuickConnect`, method: 'POST', @@ -25133,10 +24622,7 @@ The body is expected to the image contents base64 encoded. * @summary Redeems a forgot password pin. * @request POST:/Users/ForgotPassword/Pin */ - forgotPasswordPin: ( - data: ForgotPasswordPinDto, - params: RequestParams = {}, - ) => + forgotPasswordPin: (data: ForgotPasswordPinDto, params: RequestParams = {}) => this.request({ path: `/Users/ForgotPassword/Pin`, method: 'POST', diff --git a/backend/plugins/plugin-types.d.ts b/backend/plugins/plugin-types.d.ts new file mode 100644 index 0000000..a212ffb --- /dev/null +++ b/backend/plugins/plugin-types.d.ts @@ -0,0 +1,45 @@ +export type PluginSettingsTemplate = Record< + string, + 'string' | 'number' | 'boolean' | 'password' | { type: 'link'; url: string } +>; + +export type PluginSettings = Record; + +export interface SourcePlugin { + name: string; + + getIsIndexable: () => boolean; + + getIndex: () => Promise< + Record< + number, + any + // | { tmdbId: number; quality: number } + // | { + // tmdbId: number; + // seasons: Record>; + // } + > + >; + + getSettingsTemplate: () => PluginSettingsTemplate; + + validateSettings: (settings: Record) => Promise<{ + isValid: boolean; + errors: Record; + }>; + + getMovieStream: (tmdbId: string, settings: PluginSettings) => Promise; + + getEpisodeStream: ( + tmdbId: string, + season: number, + episode: number, + settings: PluginSettings, + ) => Promise; + + handleProxy(request: { uri: string; headers: any }, settings: PluginSettings): { + url: string; + headers: any; + }; +} diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index d2764d8..45d514a 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -7,7 +7,7 @@ import { AuthModule } from './auth/auth.module'; import { ServeStaticModule } from '@nestjs/serve-static'; import { join } from 'path'; import { MediaModule } from './media/media.module'; -import { SourceModule } from './sources/sources.module'; +import { SourcePluginsModule } from './source-plugins/source-plugins.module'; @Module({ imports: [ @@ -18,7 +18,7 @@ import { SourceModule } from './sources/sources.module'; rootPath: join(__dirname, '../dist'), }), MediaModule, - SourceModule, + SourcePluginsModule, ], controllers: [AppController], providers: [AppService], diff --git a/backend/src/main.ts b/backend/src/main.ts index 013ce6c..9b71d7a 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -4,7 +4,7 @@ import 'reflect-metadata'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import * as fs from 'fs'; import { UsersService } from './users/users.service'; -import { ADMIN_PASSWORD, ADMIN_USERNAME } from './consts'; +import { ADMIN_PASSWORD, ADMIN_USERNAME, ENV } from './consts'; import { json, urlencoded } from 'express'; // import * as proxy from 'express-http-proxy'; require('ts-node/register'); // For importing plugins @@ -30,15 +30,20 @@ async function bootstrap() { app.use(json({ limit: '50mb' })); app.use(urlencoded({ extended: true, limit: '50mb' })); - // app.use('/api/proxy/jellyfin', proxy('http://192.168.0.129:8096')); + if (ENV === 'development') { + console.log('Creating OpenAPI specification...'); - const config = new DocumentBuilder().build(); + const config = new DocumentBuilder().build(); - const document = SwaggerModule.createDocument(app, config, { - deepScanRoutes: true, - }); - SwaggerModule.setup('openapi', app, document); - fs.writeFileSync('./swagger-spec.json', JSON.stringify(document)); + const document = SwaggerModule.createDocument(app, config, { + deepScanRoutes: true, + operationIdFactory: (controllerKey: string, methodKey: string) => + methodKey, + }); + + SwaggerModule.setup('openapi', app, document); + fs.writeFileSync('./swagger-spec.json', JSON.stringify(document)); + } await createAdminUser(app.get(UsersService)); diff --git a/backend/src/source-plugins/source-plugins.controller.ts b/backend/src/source-plugins/source-plugins.controller.ts new file mode 100644 index 0000000..1c2e6f9 --- /dev/null +++ b/backend/src/source-plugins/source-plugins.controller.ts @@ -0,0 +1,110 @@ +import { + All, + BadRequestException, + Controller, + Get, + NotFoundException, + Param, + Req, + Res, + UnauthorizedException, + UseGuards, +} from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; +import { SourcePluginsService } from './source-plugins.service'; +import { AuthGuard, GetUser } from 'src/auth/auth.guard'; +import { Request, Response } from 'express'; +import { Readable } from 'stream'; +import { User } from 'src/users/user.entity'; +import { UserSourcesService } from 'src/users/user-sources/user-sources.service'; + +export const JELLYFIN_DEVICE_ID = 'Reiverr Client'; + +@ApiTags('sources') +@Controller('sources') +@UseGuards(AuthGuard) +export class SourcesController { + constructor( + private sourcesService: SourcePluginsService, + private userSourcesService: UserSourcesService, + ) {} + + @Get() + @ApiOkResponse({ + description: 'All source plugins found', + type: String, + isArray: true, + }) + async getSourcePlugins() { + return this.sourcesService + .getLoadedPlugins() + .then((plugins) => Object.keys(plugins)); + } + + @Get(':sourceId/settings-template') + async getSourceSettingsTemplate( + @Param('sourceId') sourceId: string, + @GetUser() callerUser: User, + ) { + const plugin = this.sourcesService.getPlugin(sourceId); + + if (!plugin) { + throw new NotFoundException('Plugin not found'); + } + + // return plugin.getSettingsTemplate(callerUser.pluginSettings?.[sourceId]); + return plugin.getSettingsTemplate(); + } + + @Get(':sourceId/movies/:tmdbId/stream') + async getMovieStream( + @Param('sourceId') sourceId: string, + @Param('tmdbId') tmdbId: string, + @GetUser() user: User, + ) { + if (!user) { + throw new UnauthorizedException(); + } + + const settings = this.userSourcesService.getSourceSettings(user, sourceId); + + if (!settings) { + throw new BadRequestException('Source configuration not found'); + } + + return this.sourcesService + .getPlugin(sourceId) + ?.getMovieStream(tmdbId, settings); + } + + @All(':sourceId/movies/:tmdbId/stream/*') + async getMovieStreamProxy( + @Param() params: any, + @Req() req: Request, + @Res() res: Response, + @GetUser() user: User, + ) { + const sourceId = params.sourceId; + const settings = this.userSourcesService.getSourceSettings(user, sourceId); + const { url, headers } = this.sourcesService + .getPlugin(sourceId) + ?.handleProxy( + { + uri: params[0] + '?' + req.url.split('?')[1], + headers: req.headers, + }, + settings, + ); + + const proxyRes = await fetch(url, { + method: req.method || 'GET', + headers: { + ...headers, + Authorization: `MediaBrowser DeviceId="${JELLYFIN_DEVICE_ID}", Token="${settings.apiKey}"`, + }, + }); + + Readable.from(proxyRes.body).pipe(res); + res.status(proxyRes.status); + } +} diff --git a/backend/src/sources/sources.module.ts b/backend/src/source-plugins/source-plugins.module.ts similarity index 70% rename from backend/src/sources/sources.module.ts rename to backend/src/source-plugins/source-plugins.module.ts index c117359..042be6b 100644 --- a/backend/src/sources/sources.module.ts +++ b/backend/src/source-plugins/source-plugins.module.ts @@ -1,8 +1,7 @@ import { Module } from '@nestjs/common'; import { DynamicModule } from '@nestjs/common'; -import { PluginLoaderService } from './plguin-loader.service'; import { SourcePluginsService } from './source-plugins.service'; -import { SourcesController } from './sources.controller'; +import { SourcesController } from './source-plugins.controller'; import { UsersModule } from 'src/users/users.module'; @Module({ @@ -11,4 +10,4 @@ import { UsersModule } from 'src/users/users.module'; exports: [SourcePluginsService], imports: [UsersModule], }) -export class SourceModule {} +export class SourcePluginsModule {} diff --git a/backend/src/sources/source-plugins.service.ts b/backend/src/source-plugins/source-plugins.service.ts similarity index 85% rename from backend/src/sources/source-plugins.service.ts rename to backend/src/source-plugins/source-plugins.service.ts index 82bd2d3..95834f0 100644 --- a/backend/src/sources/source-plugins.service.ts +++ b/backend/src/source-plugins/source-plugins.service.ts @@ -1,13 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as fs from 'fs'; import * as path from 'path'; - -export interface SourcePlugin { - handleProxy(request: { uri: string; headers: any }): any; - name: string; - indexable: boolean; - getMovieStream: (tmdbId: string) => Promise; -} +import { SourcePlugin } from 'plugins/plugin-types'; @Injectable() export class SourcePluginsService { @@ -37,7 +31,7 @@ export class SourcePluginsService { const directoryPath = path.join(rootDirectory, directoryName); const directoryStat = fs.statSync(directoryPath); - if (directoryStat.isDirectory()) { + if (directoryStat.isDirectory() && directoryName.endsWith('.plugin')) { pluginPaths.push(directoryPath); } } diff --git a/backend/src/sources/plguin-loader.service.ts b/backend/src/sources/plguin-loader.service.ts deleted file mode 100644 index 5f8353e..0000000 --- a/backend/src/sources/plguin-loader.service.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { promises as fs } from 'fs'; -import axios from 'axios'; -import * as vm from 'vm'; - -@Injectable() -export class PluginLoaderService { - constructor(@Inject('PLUGIN_URL') private readonly pluginUrl: string) {} - - async loadPlugin(): Promise { - const response = await axios.get(this.pluginUrl); - - const sandbox = { - module: { exports: {} }, - console: console, - }; - - vm.createContext(sandbox); - - const script = new vm.Script(response.data, { - filename: 'plugin-module.js', - }); - script.runInContext(sandbox, { displayErrors: true }); - - return sandbox.module.exports; - } -} diff --git a/backend/src/sources/sources.controller.ts b/backend/src/sources/sources.controller.ts deleted file mode 100644 index 4ebc125..0000000 --- a/backend/src/sources/sources.controller.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { - All, - Controller, - Get, - Next, - Param, - Req, - Res, - UseGuards, -} from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { SourcePluginsService } from './source-plugins.service'; -import { AuthGuard } from 'src/auth/auth.guard'; -import { NextFunction, Request, Response } from 'express'; -import { Readable } from 'stream'; - -const config = { - apiKey: '', - baseUrl: 'http://192.168.0.129:8096', - userId: '', -}; -export const JELLYFIN_DEVICE_ID = 'Reiverr Client'; - -@ApiTags('sources') -@Controller('sources') -@UseGuards(AuthGuard) -export class SourcesController { - constructor(private sourcesService: SourcePluginsService) {} - - @Get() - async getSources() { - this.sourcesService.getLoadedPlugins(); - } - - @Get(':sourceId/movies/:tmdbId/stream') - async getMovieStream( - @Param('sourceId') sourceId: string, - @Param('tmdbId') tmdbId: string, - ) { - return this.sourcesService.getPlugin(sourceId)?.getMovieStream(tmdbId); - } - - @All(':sourceId/movies/:tmdbId/stream/*') - async getMovieStreamProxy( - @Param() params: any, - @Req() req: Request, - @Res() res: Response, - @Next() next: NextFunction, - ) { - const { url, headers } = this.sourcesService - .getPlugin(params.sourceId) - ?.handleProxy({ - uri: params[0] + '?' + req.url.split('?')[1], - headers: req.headers, - }); - - const proxyRes = await fetch(url, { - method: req.method || 'GET', - headers: { - ...headers, - Authorization: `MediaBrowser DeviceId="${JELLYFIN_DEVICE_ID}", Token="${config.apiKey}"`, - }, - }); - - Readable.from(proxyRes.body).pipe(res); - res.status(proxyRes.status); - } -} diff --git a/backend/src/users/user-sources/user-source.dto.ts b/backend/src/users/user-sources/user-source.dto.ts new file mode 100644 index 0000000..46a78a7 --- /dev/null +++ b/backend/src/users/user-sources/user-source.dto.ts @@ -0,0 +1,31 @@ +import { + ApiProperty, + IntersectionType, + OmitType, + PartialType, + PickType, +} from '@nestjs/swagger'; +import { MediaSource } from './user-source.entity'; +import { Type } from '@nestjs/common'; + +const PickAndPartial = ( + clazz: Type, + pick: K[] = [], + partial: K[] = [], +) => + IntersectionType( + OmitType(PickType(clazz, pick), partial), + PickType(PartialType(clazz), partial), + ); + +export class SourceDto extends PickAndPartial( + MediaSource, + ['id', 'userId', 'adminControlled', 'enabled'], + ['pluginSettings'], +) {} + +export class CreateSourceDto extends PickAndPartial( + MediaSource, + ['pluginSettings', 'id'], + ['enabled', 'adminControlled'], +) {} diff --git a/backend/src/users/user-sources/user-source.entity.ts b/backend/src/users/user-sources/user-source.entity.ts new file mode 100644 index 0000000..8b9f02a --- /dev/null +++ b/backend/src/users/user-sources/user-source.entity.ts @@ -0,0 +1,33 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { PluginSettings } from 'plugins/plugin-types'; +import { User } from 'src/users/user.entity'; +import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm'; + + +@Entity() +export class MediaSource { + @ApiProperty({ required: true, type: 'string' }) + @PrimaryColumn() + id: string; + + @PrimaryColumn() + userId: string; + + @ApiProperty({ required: true, type: 'string' }) + @ManyToOne(() => User, (user) => user.mediaSources) + @JoinColumn({ name: 'userId' }) + user: User; + + @ApiProperty({ required: false, type: 'string', default: true }) + @Column({ default: true }) + enabled: boolean; + + @ApiProperty({ required: false, type: 'boolean', default: false }) + @Column({ default: false }) + adminControlled: boolean; + + @ApiProperty({ required: false, type: 'object' }) + @Column('json', { default: '{}' }) + pluginSettings: PluginSettings = {}; + // Add other fields as necessary +} diff --git a/backend/src/users/user-sources/user-sources.controller.ts b/backend/src/users/user-sources/user-sources.controller.ts new file mode 100644 index 0000000..736bbde --- /dev/null +++ b/backend/src/users/user-sources/user-sources.controller.ts @@ -0,0 +1,75 @@ +import { + Body, + Controller, + Delete, + Get, + InternalServerErrorException, + NotFoundException, + Param, + Put, + UnauthorizedException, + UseGuards, +} from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; +import { AuthGuard, GetUser } from 'src/auth/auth.guard'; +import { UsersService } from '../users.service'; +import { User } from '../user.entity'; +import { UserDto } from '../user.dto'; +import { CreateSourceDto } from './user-source.dto'; +import { + UserSourcesService, + UserSourcesServiceError, +} from './user-sources.service'; + +@ApiTags('users') +@Controller('users/:userId/sources') +@UseGuards(AuthGuard) +export class UsersSourcesController { + constructor( + private usersService: UsersService, + private userSourcesService: UserSourcesService, + ) {} + + @Put(':sourceId') + @ApiOkResponse({ description: 'Source updated', type: UserDto }) + async updateSource( + @GetUser() callerUser: User, + @Param('sourceId') sourceId: string, + @Param('userId') userId: string, + @Body() sourceDto: CreateSourceDto, + ): Promise { + const user = await this.usersService.findOne(userId); + + if (!user) { + throw new NotFoundException('User not found'); + } + + const updatedUser = await this.userSourcesService + .updateUserSource(user, sourceId, sourceDto, callerUser) + .catch((e) => { + if (e === UserSourcesServiceError.Unauthorized) { + throw new UnauthorizedException(); + } else { + throw new InternalServerErrorException('Failed to update source'); + } + }); + + if (!updatedUser) { + throw new InternalServerErrorException('Failed to update source'); + } + + return UserDto.fromEntity(updatedUser); + } + + @Delete(':sourceId') + @ApiOkResponse({ description: 'Source deleted', type: UserDto }) + async deleteSource( + @GetUser() callerUser: User, + @Param('sourceId') sourceId: string, + @Param('userId') userId: string, + ): Promise { + const updatedUser = await this.userSourcesService.deleteUserSource(userId, sourceId, callerUser); + + return UserDto.fromEntity(updatedUser); + } +} diff --git a/backend/src/users/user-sources/user-sources.service.ts b/backend/src/users/user-sources/user-sources.service.ts new file mode 100644 index 0000000..5c036fa --- /dev/null +++ b/backend/src/users/user-sources/user-sources.service.ts @@ -0,0 +1,96 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Repository } from 'typeorm'; +import { User } from '../user.entity'; +import { CreateSourceDto } from './user-source.dto'; +import { USER_REPOSITORY, USER_SOURCE_REPOSITORY } from '../user.providers'; +import { MediaSource } from './user-source.entity'; +import { UsersService } from '../users.service'; + +export enum UserSourcesServiceError { + SourceNotFound = 'SourceNotFound', + Unauthorized = 'Unauthorized', +} + +@Injectable() +export class UserSourcesService { + constructor( + private readonly userService: UsersService, + @Inject(USER_SOURCE_REPOSITORY) + private readonly userSourceRepository: Repository, + ) {} + + private async findUserSource( + userId: string, + sourceId: string, + ): Promise { + const source = await this.userSourceRepository.findOne({ + where: { + id: sourceId, + userId: userId, + }, + }); + + return source; + } + + async deleteUserSource( + userId: string, + sourceId: string, + callerUser: User, + ): Promise { + if (!callerUser.isAdmin || callerUser.id !== userId) { + throw UserSourcesServiceError.Unauthorized; + } + + const source = await this.findUserSource(userId, sourceId); + + if (!source) { + throw UserSourcesServiceError.SourceNotFound; + } + + await this.userSourceRepository.remove(source); + return this.userService.findOne(userId); + } + + async updateUserSource( + user: User, + sourceId: string, + sourceDto: CreateSourceDto, + callerUser: User = user, + ): Promise { + if (!callerUser.isAdmin || callerUser.id !== user.id) { + throw UserSourcesServiceError.Unauthorized; + } + + let source = await this.findUserSource(user.id, sourceId); + + // Create new if doesn't exist + if (!source) { + source = new MediaSource(); + source.user = user; + source.id = sourceId; + source.adminControlled = false; + } + + // Check for unauthorized access + if (source.adminControlled && !callerUser.isAdmin) { + throw UserSourcesServiceError.Unauthorized; + } else if (sourceDto.adminControlled !== undefined && !callerUser.isAdmin) { + throw UserSourcesServiceError.Unauthorized; + } + + source.adminControlled = + sourceDto.adminControlled ?? source.adminControlled; + source.enabled = sourceDto.enabled ?? source.enabled; + console.log('Test defaults, enabled', new MediaSource().enabled); + source.pluginSettings = sourceDto.pluginSettings ?? source.pluginSettings; + + await this.userSourceRepository.save(source); + return this.userService.findOne(user.id); + } + + getSourceSettings(user: User, sourceId: string) { + return user.mediaSources?.find((source) => source.id === sourceId) + ?.pluginSettings; + } +} diff --git a/backend/src/users/user.dto.ts b/backend/src/users/user.dto.ts index ac1cd6c..bd0224a 100644 --- a/backend/src/users/user.dto.ts +++ b/backend/src/users/user.dto.ts @@ -8,7 +8,7 @@ export class UserDto extends OmitType(User, [ @ApiProperty({ type: 'string' }) profilePicture: string | null; - static fromEntity(entity: User): UserDto { + static fromEntity(entity: User, caller: User = entity): UserDto { return { id: entity.id, name: entity.name, @@ -17,6 +17,8 @@ export class UserDto extends OmitType(User, [ onboardingDone: entity.onboardingDone, profilePicture: 'data:image;base64,' + entity.profilePicture?.toString('base64'), + // pluginSettings: entity.pluginSettings, + mediaSources: entity.mediaSources, }; } } @@ -37,6 +39,7 @@ export class UpdateUserDto extends PartialType( 'name', 'password', 'isAdmin', + // 'pluginSettings', ] as const), ) { @ApiProperty({ type: 'string', required: false }) diff --git a/backend/src/users/user.entity.ts b/backend/src/users/user.entity.ts index e39fa84..b169d88 100644 --- a/backend/src/users/user.entity.ts +++ b/backend/src/users/user.entity.ts @@ -1,5 +1,13 @@ -import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { + Column, + Entity, + ManyToOne, + OneToMany, + PrimaryColumn, + PrimaryGeneratedColumn, +} from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; +import { MediaSource } from 'src/users/user-sources/user-source.entity'; export class SonarrSettings { @ApiProperty({ required: true }) @@ -127,4 +135,11 @@ export class User { @ApiProperty({ required: true, type: Settings }) @Column('json', { default: JSON.stringify(DEFAULT_SETTINGS) }) settings = DEFAULT_SETTINGS; + + // @ApiProperty({ required: false, type: 'object' }) + // @Column('json', { default: '{}' }) + // pluginSettings: PluginSettings = {}; + + @OneToMany(() => MediaSource, (mediaSource) => mediaSource.user) + mediaSources: MediaSource[]; } diff --git a/backend/src/users/user.providers.ts b/backend/src/users/user.providers.ts index 93b4d1e..6144583 100644 --- a/backend/src/users/user.providers.ts +++ b/backend/src/users/user.providers.ts @@ -1,8 +1,10 @@ import { DataSource } from 'typeorm'; import { User } from './user.entity'; import { DATA_SOURCE } from '../database/database.providers'; +import { MediaSource } from './user-sources/user-source.entity'; export const USER_REPOSITORY = 'USER_REPOSITORY'; +export const USER_SOURCE_REPOSITORY = 'USER_SOURCE_REPOSITORY'; export const userProviders = [ { @@ -10,4 +12,10 @@ export const userProviders = [ useFactory: (dataSource: DataSource) => dataSource.getRepository(User), inject: [DATA_SOURCE], }, + { + provide: USER_SOURCE_REPOSITORY, + useFactory: (dataSource: DataSource) => + dataSource.getRepository(MediaSource), + inject: [DATA_SOURCE], + }, ]; diff --git a/backend/src/users/users.controller.ts b/backend/src/users/users.controller.ts index 6a78711..36317f8 100644 --- a/backend/src/users/users.controller.ts +++ b/backend/src/users/users.controller.ts @@ -47,7 +47,7 @@ export class UsersController { type: UserDto, isArray: true, }) - async findAll(@GetUser() callerUser: User): Promise { + async findAllUsers(@GetUser() callerUser: User): Promise { if (!callerUser.isAdmin) { throw new UnauthorizedException(); } @@ -61,7 +61,7 @@ export class UsersController { @Get(':id') @ApiOkResponse({ description: 'User found', type: UserDto }) @ApiException(() => NotFoundException, { description: 'User not found' }) - async findById( + async findUserById( @Param('id') id: string, @GetUser() callerUser: User, ): Promise { @@ -90,7 +90,7 @@ export class UsersController { @ApiOkResponse({ description: 'User created', type: UserDto }) @ApiException(() => UnauthorizedException, { description: 'Unauthorized' }) @ApiException(() => BadRequestException) - async create( + async createUser( @Body() userCreateDto: CreateUserDto, @GetUser() callerUser: User | undefined, @@ -113,7 +113,7 @@ export class UsersController { @Put(':id') @ApiOkResponse({ description: 'User updated', type: UserDto }) @ApiException(() => NotFoundException, { description: 'User not found' }) - async update( + async updateUser( @Param('id') id: string, @Body() updateUserDto: UpdateUserDto, @GetUser() callerUser: User, @@ -121,6 +121,7 @@ export class UsersController { if ((!callerUser.isAdmin && callerUser.id !== id) || !id) { throw new NotFoundException(); } + const user = await this.usersService.findOne(id); const updated = await this.usersService diff --git a/backend/src/users/users.module.ts b/backend/src/users/users.module.ts index e165ba9..504a162 100644 --- a/backend/src/users/users.module.ts +++ b/backend/src/users/users.module.ts @@ -5,11 +5,12 @@ import { UsersController } from './users.controller'; import { DatabaseModule } from '../database/database.module'; import { LibraryModule } from './library/library.module'; import { PlayStateModule } from './play-state/play-state.module'; +import { UserSourcesService } from './user-sources/user-sources.service'; @Module({ imports: [DatabaseModule, LibraryModule, PlayStateModule], - providers: [...userProviders, UsersService], + providers: [...userProviders, UsersService, UserSourcesService], controllers: [UsersController], - exports: [UsersService], + exports: [UsersService, UserSourcesService], }) export class UsersModule {} diff --git a/backend/src/users/users.service.ts b/backend/src/users/users.service.ts index 66c0c21..04554ea 100644 --- a/backend/src/users/users.service.ts +++ b/backend/src/users/users.service.ts @@ -16,18 +16,34 @@ export class UsersService { private readonly userRepository: Repository, ) {} + // Finds async findAll(): Promise { - return this.userRepository.find(); + return this.userRepository.find({ + relations: { + mediaSources: true, + }, + }); } async findOne(id: string): Promise { - return this.userRepository.findOne({ where: { id } }); + return this.userRepository.findOne({ + where: { id }, + relations: { + mediaSources: true, + }, + }); } async findOneByName(name: string): Promise { - return this.userRepository.findOne({ where: { name } }); + return this.userRepository.findOne({ + where: { name }, + relations: { + mediaSources: true, + }, + }); } + // The rest async create(userCreateDto: CreateUserDto): Promise { if (!userCreateDto.name) throw UserServiceError.UsernameRequired; @@ -46,7 +62,8 @@ export class UsersService { console.error(e); } - return this.userRepository.save(user); + await this.userRepository.save(user); + return this.findOne(user.id); } async update( @@ -60,15 +77,24 @@ export class UsersService { if ( updateUserDto.password !== undefined && updateUserDto.oldPassword !== user.password - ) + ) { throw UserServiceError.PasswordMismatch; - else if (updateUserDto.password !== undefined) + } else if (updateUserDto.password !== undefined) { user.password = updateUserDto.password; + } } if (updateUserDto.settings) user.settings = updateUserDto.settings; + + // if (updateUserDto.pluginSettings) { + // for (const key of Object.keys(updateUserDto.pluginSettings)) { + // user.pluginSettings[key] = updateUserDto.pluginSettings[key]; + // } + // } + if (updateUserDto.onboardingDone) user.onboardingDone = updateUserDto.onboardingDone; + if (updateUserDto.profilePicture) { try { user.profilePicture = Buffer.from( @@ -79,14 +105,17 @@ export class UsersService { console.error(e); } } + if (updateUserDto.isAdmin !== undefined && callerUser.isAdmin) user.isAdmin = updateUserDto.isAdmin; - return this.userRepository.save(user); + await this.userRepository.save(user); + + return this.findOne(user.id); } - async remove(id: string): Promise { - await this.userRepository.delete(id); + async remove(id: string) { + return await this.userRepository.delete(id); } async noPreviousAdmins(): Promise { diff --git a/package-lock.json b/package-lock.json index 4b8d9a4..155bd09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,8 @@ "name": "reiverr", "version": "2.0.0-alpha.6", "dependencies": { - "gsap": "^3.12.5" + "gsap": "^3.12.5", + "swagger-typescript-api": "^13.0.23" }, "devDependencies": { "@jellyfin/sdk": "^0.8.2", @@ -85,7 +86,6 @@ "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", - "dev": true, "dependencies": { "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" @@ -98,7 +98,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -110,7 +109,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -124,7 +122,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -132,14 +129,12 @@ "node_modules/@babel/code-frame/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, "engines": { "node": ">=0.8.0" } @@ -148,7 +143,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, "engines": { "node": ">=4" } @@ -157,7 +151,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -547,7 +540,6 @@ "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -593,7 +585,6 @@ "version": "7.23.4", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", - "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", @@ -607,7 +598,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -619,7 +609,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -633,7 +622,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -641,14 +629,12 @@ "node_modules/@babel/highlight/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "node_modules/@babel/highlight/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, "engines": { "node": ">=0.8.0" } @@ -657,7 +643,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, "engines": { "node": ">=4" } @@ -666,7 +651,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -2377,6 +2361,11 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@exodus/schemasafe": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", + "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==" + }, "node_modules/@fastify/busboy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", @@ -2706,6 +2695,11 @@ "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, + "node_modules/@types/swagger-schema-official": { + "version": "2.0.25", + "resolved": "https://registry.npmjs.org/@types/swagger-schema-official/-/swagger-schema-official-2.0.25.tgz", + "integrity": "sha512-T92Xav+Gf/Ik1uPW581nA+JftmjWPgskw/WBf4TJzxRG/SJ+DfNnNE+WuZ4mrXuzflQMqMkm1LSYjzYW7MB1Cg==" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "6.16.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.16.0.tgz", @@ -3016,7 +3010,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -3025,7 +3018,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -3064,8 +3056,7 @@ "node_modules/argparse": { "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 + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/array-union": { "version": "2.1.0", @@ -3271,11 +3262,15 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "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==" + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -3413,11 +3408,23 @@ "node": ">=0.10" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -3428,8 +3435,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -3464,6 +3470,14 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -3494,6 +3508,31 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3601,8 +3640,7 @@ "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, "node_modules/dir-glob": { "version": "3.0.1", @@ -3640,6 +3678,27 @@ "integrity": "sha512-1n7zWYh8eS0L9Uy+GskE0lkBUNK83cXTVJI0pU3mGprFsbfSdAc15VTFbo+A+Bq4pwstmL30AVcEU3Fo463lNg==", "dev": true }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es5-ext": { "version": "0.10.62", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", @@ -3669,8 +3728,7 @@ "node_modules/es6-promise": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", - "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", - "dev": true + "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==" }, "node_modules/es6-symbol": { "version": "3.1.3", @@ -3736,7 +3794,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -3969,6 +4026,17 @@ "node": ">=0.10.0" } }, + "node_modules/eta": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-2.2.0.tgz", + "integrity": "sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==", + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "url": "https://github.com/eta-dev/eta?sponsor=1" + } + }, "node_modules/event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", @@ -4040,6 +4108,11 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -4190,6 +4263,14 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", @@ -4316,6 +4397,11 @@ "integrity": "sha512-UppQjyvPVclg+6t2KY/Rv03h0+bA5u6zwqVoz4LAC/L0fgYmIaCD7ZCrwe8WI1Gv01be1XL0QFsRbSdIHV/Wbw==", "dev": true }, + "node_modules/http2-client": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", + "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==" + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -4329,7 +4415,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -4378,6 +4463,11 @@ "tslib": "^2.4.0" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -4411,6 +4501,14 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -4465,14 +4563,12 @@ "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 + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "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==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -4492,6 +4588,11 @@ "node": ">=4" } }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -4556,8 +4657,7 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/local-pkg": { "version": "0.4.3", @@ -4586,6 +4686,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -4780,7 +4885,6 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, "funding": [ { "type": "github", @@ -4816,6 +4920,44 @@ "tslib": "^2.0.3" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch-h2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", + "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", + "dependencies": { + "http2-client": "^1.2.5" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/node-readfiles": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz", + "integrity": "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==", + "dependencies": { + "es6-promise": "^3.2.1" + } + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -4840,6 +4982,71 @@ "node": ">=0.10.0" } }, + "node_modules/oas-kit-common": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", + "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", + "dependencies": { + "fast-safe-stringify": "^2.0.7" + } + }, + "node_modules/oas-linter": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz", + "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", + "dependencies": { + "@exodus/schemasafe": "^1.0.0-rc.2", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-resolver": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz", + "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", + "dependencies": { + "node-fetch-h2": "^2.3.0", + "oas-kit-common": "^1.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + }, + "bin": { + "resolve": "resolve.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-schema-walker": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz", + "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==", + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-validator": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz", + "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", + "dependencies": { + "call-me-maybe": "^1.0.1", + "oas-kit-common": "^1.0.8", + "oas-linter": "^3.2.2", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "reftools": "^1.1.9", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4974,7 +5181,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -4982,6 +5188,23 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pascal-case": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", @@ -5366,6 +5589,14 @@ "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", "dev": true }, + "node_modules/reftools": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", + "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -5437,6 +5668,14 @@ "jsesc": "bin/jsesc" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", @@ -5458,7 +5697,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -5599,6 +5837,54 @@ "node": ">=8" } }, + "node_modules/should": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "dependencies": { + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "node_modules/should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "dependencies": { + "should-type": "^1.4.0" + } + }, + "node_modules/should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", + "dependencies": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "node_modules/should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==" + }, + "node_modules/should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "dependencies": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" + } + }, + "node_modules/should-util": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", + "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==" + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -5651,11 +5937,23 @@ "source-map": "^0.6.0" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -5969,6 +6267,77 @@ } } }, + "node_modules/swagger-schema-official": { + "version": "2.0.0-bab6bed", + "resolved": "https://registry.npmjs.org/swagger-schema-official/-/swagger-schema-official-2.0.0-bab6bed.tgz", + "integrity": "sha512-rCC0NWGKr/IJhtRuPq/t37qvZHI/mH4I4sxflVM+qgVe5Z2uOCivzWaVbuioJaB61kvm5UvB7b49E+oBY0M8jA==" + }, + "node_modules/swagger-typescript-api": { + "version": "13.0.23", + "resolved": "https://registry.npmjs.org/swagger-typescript-api/-/swagger-typescript-api-13.0.23.tgz", + "integrity": "sha512-HhoIepxlFEU7Ol42Gh8/tvwhSxdkHcweX2tRkNhbZYBiEA+rK3C6N85MnwoeQR5XbidE3Kz8mLOqIerVGgR9uw==", + "dependencies": { + "@types/swagger-schema-official": "^2.0.25", + "consola": "^3.2.3", + "cosmiconfig": "^9.0.0", + "didyoumean": "^1.2.2", + "eta": "^2.2.0", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "nanoid": "^3.3.7", + "prettier": "~3.3.3", + "swagger-schema-official": "2.0.0-bab6bed", + "swagger2openapi": "^7.0.8", + "typescript": "~5.5.4" + }, + "bin": { + "sta": "dist/cli.js", + "swagger-typescript-api": "dist/cli.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/swagger-typescript-api/node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/swagger2openapi": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz", + "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", + "dependencies": { + "call-me-maybe": "^1.0.1", + "node-fetch": "^2.6.1", + "node-fetch-h2": "^2.3.0", + "node-readfiles": "^0.2.0", + "oas-kit-common": "^1.0.8", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "oas-validator": "^5.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + }, + "bin": { + "boast": "boast.js", + "oas-validate": "oas-validate.js", + "swagger2openapi": "swagger2openapi.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, "node_modules/systemjs": { "version": "6.14.2", "resolved": "https://registry.npmjs.org/systemjs/-/systemjs-6.14.2.tgz", @@ -6172,6 +6541,11 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/ts-api-utils": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", @@ -6236,10 +6610,9 @@ } }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true, + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6862,6 +7235,20 @@ } } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6877,12 +7264,36 @@ "node": ">= 8" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -6893,16 +7304,31 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, "engines": { "node": ">= 6" } }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "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, "engines": { "node": ">=12" } diff --git a/package.json b/package.json index 93c6bd6..0ff7934 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,9 @@ "test:unit": "vitest", "lint": "prettier --plugin-search-dir . --check . && eslint .", "format": "prettier --plugin-search-dir . --write .", - "openapi:update": "npm run --prefix backend openapi:schema && npm run openapi:codegen", - "openapi:codegen": "openapi-typescript \"backend/swagger-spec.json\" -o src/lib/apis/reiverr/reiverr.generated.d.ts" + "openapi:update": "npm run --prefix backend §generate && npm run openapi:codegen", + "openapi:codegen": "openapi-typescript \"backend/swagger-spec.json\" -o src/lib/apis/reiverr/reiverr.generated.d.ts", + "openapi:generate": "swagger-typescript-api -p \"backend/swagger-spec.json\" -o src/lib/apis/reiverr -n reiverr.openapi.ts --axios --module-name-first-tag" }, "devDependencies": { "@jellyfin/sdk": "^0.8.2", @@ -70,7 +71,8 @@ ] }, "dependencies": { - "gsap": "^3.12.5" + "gsap": "^3.12.5", + "swagger-typescript-api": "^13.0.23" }, "prettier": { "useTabs": true, @@ -92,4 +94,4 @@ } ] } -} +} \ No newline at end of file diff --git a/postcss.config.js b/postcss.config.js old mode 100755 new mode 100644 diff --git a/src/lib/apis/reiverr/reiverr-api.ts b/src/lib/apis/reiverr/reiverr-api.ts index 89de91d..250172f 100644 --- a/src/lib/apis/reiverr/reiverr-api.ts +++ b/src/lib/apis/reiverr/reiverr-api.ts @@ -3,12 +3,25 @@ import type { components, paths } from './reiverr.generated'; import { get } from 'svelte/store'; import type { Api } from '../api.interface'; import { sessions } from '../../stores/session.store'; +import { Api as ReiverrApiNew } from './reiverr.openapi'; export type ReiverrUser = components['schemas']['UserDto']; export type CreateReiverrUser = components['schemas']['CreateUserDto']; export type UpdateReiverrUser = components['schemas']['UpdateUserDto']; export type ReiverrSettings = ReiverrUser['settings']; +const session = get(sessions).activeSession; +const token = session?.token; +console.log('session', session); + +console.log('Creating Reiverr API with base URL:', session?.baseUrl, 'and token:', token); +export const reiverrApiNew = new ReiverrApiNew({ + baseURL: session?.baseUrl, + headers: { + Authorization: token ? `Bearer ${token}` : '' + } +}); + export class ReiverrApi implements Api { getClient(basePath?: string, _token?: string) { const session = get(sessions).activeSession; diff --git a/src/lib/apis/reiverr/reiverr.openapi.ts b/src/lib/apis/reiverr/reiverr.openapi.ts new file mode 100644 index 0000000..3163617 --- /dev/null +++ b/src/lib/apis/reiverr/reiverr.openapi.ts @@ -0,0 +1,567 @@ +/* eslint-disable */ +/* tslint:disable */ +/* + * --------------------------------------------------------------- + * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ## + * ## ## + * ## AUTHOR: acacode ## + * ## SOURCE: https://github.com/acacode/swagger-typescript-api ## + * --------------------------------------------------------------- + */ + +export interface SonarrSettings { + apiKey: string; + baseUrl: string; + qualityProfileId: number; + rootFolderPath: string; + languageProfileId: number; +} + +export interface RadarrSettings { + apiKey: string; + baseUrl: string; + qualityProfileId: number; + rootFolderPath: string; +} + +export interface JellyfinSettings { + apiKey: string; + baseUrl: string; + userId: string; +} + +export interface TmdbSettings { + sessionId: string; + userId: string; +} + +export interface Settings { + autoplayTrailers: boolean; + language: string; + animationDuration: number; + sonarr: SonarrSettings; + radarr: RadarrSettings; + jellyfin: JellyfinSettings; + tmdb: TmdbSettings; +} + +export interface UserDto { + id: string; + name: string; + isAdmin: boolean; + onboardingDone?: boolean; + settings: Settings; + pluginSettings?: object; + profilePicture: string; +} + +export interface CreateUserDto { + name: string; + password: string; + isAdmin: boolean; + profilePicture?: string; +} + +export interface UpdateUserDto { + name?: string; + password?: string; + isAdmin?: boolean; + onboardingDone?: boolean; + settings?: Settings; + pluginSettings?: object; + profilePicture?: string; + oldPassword?: string; +} + +export interface SignInDto { + name: string; + password: string; +} + +export interface SignInResponse { + accessToken: string; + user: UserDto; +} + +import type { + AxiosInstance, + AxiosRequestConfig, + AxiosResponse, + HeadersDefaults, + ResponseType +} from 'axios'; +import axios from 'axios'; + +export type QueryParamsType = Record; + +export interface FullRequestParams + extends Omit { + /** set parameter to `true` for call `securityWorker` for this request */ + secure?: boolean; + /** request path */ + path: string; + /** content type of request body */ + type?: ContentType; + /** query params */ + query?: QueryParamsType; + /** format of response (i.e. response.json() -> format: "json") */ + format?: ResponseType; + /** request body */ + body?: unknown; +} + +export type RequestParams = Omit; + +export interface ApiConfig + extends Omit { + securityWorker?: ( + securityData: SecurityDataType | null + ) => Promise | AxiosRequestConfig | void; + secure?: boolean; + format?: ResponseType; +} + +export enum ContentType { + Json = 'application/json', + FormData = 'multipart/form-data', + UrlEncoded = 'application/x-www-form-urlencoded', + Text = 'text/plain' +} + +export class HttpClient { + public instance: AxiosInstance; + private securityData: SecurityDataType | null = null; + private securityWorker?: ApiConfig['securityWorker']; + private secure?: boolean; + private format?: ResponseType; + + constructor({ + securityWorker, + secure, + format, + ...axiosConfig + }: ApiConfig = {}) { + this.instance = axios.create({ ...axiosConfig, baseURL: axiosConfig.baseURL || '' }); + this.secure = secure; + this.format = format; + this.securityWorker = securityWorker; + } + + public setSecurityData = (data: SecurityDataType | null) => { + this.securityData = data; + }; + + protected mergeRequestParams( + params1: AxiosRequestConfig, + params2?: AxiosRequestConfig + ): AxiosRequestConfig { + const method = params1.method || (params2 && params2.method); + + return { + ...this.instance.defaults, + ...params1, + ...(params2 || {}), + headers: { + ...((method && + this.instance.defaults.headers[method.toLowerCase() as keyof HeadersDefaults]) || + {}), + ...(params1.headers || {}), + ...((params2 && params2.headers) || {}) + } + }; + } + + protected stringifyFormItem(formItem: unknown) { + if (typeof formItem === 'object' && formItem !== null) { + return JSON.stringify(formItem); + } else { + return `${formItem}`; + } + } + + protected createFormData(input: Record): FormData { + if (input instanceof FormData) { + return input; + } + return Object.keys(input || {}).reduce((formData, key) => { + const property = input[key]; + const propertyContent: any[] = property instanceof Array ? property : [property]; + + for (const formItem of propertyContent) { + const isFileType = formItem instanceof Blob || formItem instanceof File; + formData.append(key, isFileType ? formItem : this.stringifyFormItem(formItem)); + } + + return formData; + }, new FormData()); + } + + public request = async ({ + secure, + path, + type, + query, + format, + body, + ...params + }: FullRequestParams): Promise> => { + const secureParams = + ((typeof secure === 'boolean' ? secure : this.secure) && + this.securityWorker && + (await this.securityWorker(this.securityData))) || + {}; + const requestParams = this.mergeRequestParams(params, secureParams); + const responseFormat = format || this.format || undefined; + + if (type === ContentType.FormData && body && body !== null && typeof body === 'object') { + body = this.createFormData(body as Record); + } + + if (type === ContentType.Text && body && body !== null && typeof body !== 'string') { + body = JSON.stringify(body); + } + + return this.instance.request({ + ...requestParams, + headers: { + ...(requestParams.headers || {}), + ...(type ? { 'Content-Type': type } : {}) + }, + params: query, + responseType: responseFormat, + data: body, + url: path + }); + }; +} + +/** + * @title No title + * @version 1.0.0 + * @contact + */ +export class Api extends HttpClient { + users = { + /** + * No description + * + * @tags users + * @name FindAllUsers + * @request GET:/api/users + */ + findAllUsers: (params: RequestParams = {}) => + this.request({ + path: `/api/users`, + method: 'GET', + format: 'json', + ...params + }), + + /** + * No description + * + * @tags users + * @name CreateUser + * @request POST:/api/users + */ + createUser: (data: CreateUserDto, params: RequestParams = {}) => + this.request< + UserDto, + | { + /** @example 400 */ + statusCode: number; + /** @example "Bad Request" */ + message: string; + /** @example "Bad Request" */ + error?: string; + } + | { + /** @example 401 */ + statusCode: number; + /** @example "Unauthorized" */ + message: string; + /** @example "Unauthorized" */ + error?: string; + } + >({ + path: `/api/users`, + method: 'POST', + body: data, + type: ContentType.Json, + format: 'json', + ...params + }), + + /** + * No description + * + * @tags users + * @name FindUserById + * @request GET:/api/users/{id} + */ + findUserById: (id: string, params: RequestParams = {}) => + this.request< + UserDto, + { + /** @example 404 */ + statusCode: number; + /** @example "Not Found" */ + message: string; + /** @example "Not Found" */ + error?: string; + } + >({ + path: `/api/users/${id}`, + method: 'GET', + format: 'json', + ...params + }), + + /** + * No description + * + * @tags users + * @name UpdateUser + * @request PUT:/api/users/{id} + */ + updateUser: (id: string, data: UpdateUserDto, params: RequestParams = {}) => + this.request< + UserDto, + { + /** @example 404 */ + statusCode: number; + /** @example "Not Found" */ + message: string; + /** @example "Not Found" */ + error?: string; + } + >({ + path: `/api/users/${id}`, + method: 'PUT', + body: data, + type: ContentType.Json, + format: 'json', + ...params + }), + + /** + * No description + * + * @tags users + * @name DeleteUser + * @request DELETE:/api/users/{id} + */ + deleteUser: (id: string, params: RequestParams = {}) => + this.request< + void, + { + /** @example 404 */ + statusCode: number; + /** @example "Not Found" */ + message: string; + /** @example "Not Found" */ + error?: string; + } + >({ + path: `/api/users/${id}`, + method: 'DELETE', + ...params + }) + }; + api = { + /** + * No description + * + * @name SignIn + * @request POST:/api/auth + */ + signIn: (data: SignInDto, params: RequestParams = {}) => + this.request< + SignInResponse, + { + /** @example 401 */ + statusCode: number; + /** @example "Unauthorized" */ + message: string; + /** @example "Unauthorized" */ + error?: string; + } + >({ + path: `/api/auth`, + method: 'POST', + body: data, + type: ContentType.Json, + format: 'json', + ...params + }), + + /** + * No description + * + * @name GetHello + * @request GET:/api + */ + getHello: (params: RequestParams = {}) => + this.request({ + path: `/api`, + method: 'GET', + ...params + }) + }; + sources = { + /** + * No description + * + * @tags sources + * @name GetSourcePlugins + * @request GET:/api/sources + */ + getSourcePlugins: (params: RequestParams = {}) => + this.request({ + path: `/api/sources`, + method: 'GET', + format: 'json', + ...params + }), + + /** + * No description + * + * @tags sources + * @name GetSourceSettingsTemplate + * @request GET:/api/sources/{sourceId}/settings + */ + getSourceSettingsTemplate: (sourceId: string, params: RequestParams = {}) => + this.request({ + path: `/api/sources/${sourceId}/settings`, + method: 'GET', + ...params + }), + + /** + * No description + * + * @tags sources + * @name GetMovieStream + * @request GET:/api/sources/{sourceId}/movies/{tmdbId}/stream + */ + getMovieStream: (sourceId: string, tmdbId: string, params: RequestParams = {}) => + this.request({ + path: `/api/sources/${sourceId}/movies/${tmdbId}/stream`, + method: 'GET', + ...params + }), + + /** + * No description + * + * @tags sources + * @name GetMovieStreamProxyGet + * @request GET:/api/sources/{sourceId}/movies/{tmdbId}/stream/* + */ + getMovieStreamProxyGet: (sourceId: string, tmdbId: string, params: RequestParams = {}) => + this.request({ + path: `/api/sources/${sourceId}/movies/${tmdbId}/stream/*`, + method: 'GET', + ...params + }), + + /** + * No description + * + * @tags sources + * @name GetMovieStreamProxyPost + * @request POST:/api/sources/{sourceId}/movies/{tmdbId}/stream/* + */ + getMovieStreamProxyPost: (sourceId: string, tmdbId: string, params: RequestParams = {}) => + this.request({ + path: `/api/sources/${sourceId}/movies/${tmdbId}/stream/*`, + method: 'POST', + ...params + }), + + /** + * No description + * + * @tags sources + * @name GetMovieStreamProxyPut + * @request PUT:/api/sources/{sourceId}/movies/{tmdbId}/stream/* + */ + getMovieStreamProxyPut: (sourceId: string, tmdbId: string, params: RequestParams = {}) => + this.request({ + path: `/api/sources/${sourceId}/movies/${tmdbId}/stream/*`, + method: 'PUT', + ...params + }), + + /** + * No description + * + * @tags sources + * @name GetMovieStreamProxyDelete + * @request DELETE:/api/sources/{sourceId}/movies/{tmdbId}/stream/* + */ + getMovieStreamProxyDelete: (sourceId: string, tmdbId: string, params: RequestParams = {}) => + this.request({ + path: `/api/sources/${sourceId}/movies/${tmdbId}/stream/*`, + method: 'DELETE', + ...params + }), + + /** + * No description + * + * @tags sources + * @name GetMovieStreamProxyPatch + * @request PATCH:/api/sources/{sourceId}/movies/{tmdbId}/stream/* + */ + getMovieStreamProxyPatch: (sourceId: string, tmdbId: string, params: RequestParams = {}) => + this.request({ + path: `/api/sources/${sourceId}/movies/${tmdbId}/stream/*`, + method: 'PATCH', + ...params + }), + + /** + * No description + * + * @tags sources + * @name GetMovieStreamProxyOptions + * @request OPTIONS:/api/sources/{sourceId}/movies/{tmdbId}/stream/* + */ + getMovieStreamProxyOptions: (sourceId: string, tmdbId: string, params: RequestParams = {}) => + this.request({ + path: `/api/sources/${sourceId}/movies/${tmdbId}/stream/*`, + method: 'OPTIONS', + ...params + }), + + /** + * No description + * + * @tags sources + * @name GetMovieStreamProxyHead + * @request HEAD:/api/sources/{sourceId}/movies/{tmdbId}/stream/* + */ + getMovieStreamProxyHead: (sourceId: string, tmdbId: string, params: RequestParams = {}) => + this.request({ + path: `/api/sources/${sourceId}/movies/${tmdbId}/stream/*`, + method: 'HEAD', + ...params + }), + + /** + * No description + * + * @tags sources + * @name GetMovieStreamProxySearch + * @request SEARCH:/api/sources/{sourceId}/movies/{tmdbId}/stream/* + */ + getMovieStreamProxySearch: (sourceId: string, tmdbId: string, params: RequestParams = {}) => + this.request({ + path: `/api/sources/${sourceId}/movies/${tmdbId}/stream/*`, + method: 'SEARCH', + ...params + }) + }; +} diff --git a/src/lib/components/StackRouter/StackRouter.ts b/src/lib/components/StackRouter/StackRouter.ts index 784dd6f..ea42a72 100644 --- a/src/lib/components/StackRouter/StackRouter.ts +++ b/src/lib/components/StackRouter/StackRouter.ts @@ -9,9 +9,10 @@ import MoviePage from '../../pages/MoviePage.svelte'; import LibraryPage from '../../pages/LibraryPage.svelte'; import SearchPage from '../../pages/SearchPage.svelte'; import PageNotFound from '../../pages/PageNotFound.svelte'; -import ManagePage from '../../pages/ManagePage.svelte'; +import ManagePage from '../../pages/ManagePage/ManagePage.svelte'; import PersonPage from '../../pages/PersonPage.svelte'; import UsersPage from '../../pages/UsersPage.svelte'; +import UiComponents from '../../pages/UiComponents.svelte'; interface Page { id: symbol; @@ -247,6 +248,12 @@ const manageRoute: Route = { root: true }; +const uiComponentsRoute: Route = { + path: '/ui-components', + component: UiComponents, + root: true +}; + const notFoundRoute: Route = { path: '/404', component: PageNotFound, @@ -264,7 +271,8 @@ export const stackRouter = useStackRouter({ personRoute, libraryRoute, searchRoute, - manageRoute + manageRoute, + uiComponentsRoute ], notFound: notFoundRoute }); diff --git a/src/lib/pages/ManagePage.svelte b/src/lib/pages/ManagePage/ManagePage.svelte similarity index 82% rename from src/lib/pages/ManagePage.svelte rename to src/lib/pages/ManagePage/ManagePage.svelte index 6e2665a..1d82ac7 100644 --- a/src/lib/pages/ManagePage.svelte +++ b/src/lib/pages/ManagePage/ManagePage.svelte @@ -1,28 +1,28 @@ + +
+

UI Components

+ + + + +
diff --git a/src/lib/types.ts b/src/lib/types.ts index 2640b3a..0014848 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -4,3 +4,7 @@ export type TitleId = { provider: 'tmdb' | 'tvdb'; type: TitleType; }; + +declare global { + const REIVERR_VERSION: string; +} diff --git a/src/main.ts b/src/main.ts index 55f77a5..04a0df4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,21 @@ +// import { generateApi } from 'swagger-typescript-api'; import './app.css'; import App from './App.svelte'; +// if (import.meta.env.MODE === 'development') { +// console.log('Generating API'); +// generateApi({ +// name: 'reiverr.openapi.ts', +// url: 'https://api.jellyfin.org/openapi/jellyfin-openapi-stable.json', +// output: +// require.main.path +// // generateClient: true, +// // generateRouteTypes: false, +// // sortTypes: true, +// httpClientType: 'axios' +// }); +// } + const app = new App({ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error diff --git a/svelte.config.js b/svelte.config.js old mode 100755 new mode 100644 diff --git a/tailwind.config.js b/tailwind.config.js old mode 100755 new mode 100644