Replace axios with native Fetch API & some maintenance

This commit is contained in:
IRHM
2026-07-26 20:11:14 +01:00
committed by momi
parent d7449642ba
commit be94f1d2b7
49 changed files with 894 additions and 817 deletions
+2
View File
@@ -1 +1,3 @@
engine-strict=true
ignore-scripts=true
min-release-age=14
+14
View File
@@ -2,6 +2,20 @@
These changes are awaiting release:
## Changed
- HTTP Requests: Replace `axios` with `Fetch`.
## Fixed
- Login: Display error when request to get available auth providers fails.
- Removed some legacy code that still thought there was a `store.watchedList`.
## Maintenance
- Package.json: Use exact version for all packages.
- .npmrc: Enable `ignore-scripts` (still on npm 11) and set `min-release-age=14`.
# [4.1.1] - 2026-07-26T02:00:00Z
## Fixed
+62 -172
View File
@@ -9,29 +9,28 @@
"version": "4.1.1",
"license": "GPL-3.0-only",
"dependencies": {
"axios": "^1.9.0",
"blurhash": "^2.0.5",
"papaparse": "^5.4.1"
"blurhash": "2.0.5",
"papaparse": "5.4.1"
},
"devDependencies": {
"@sveltejs/adapter-node": "^5.2.12",
"@sveltejs/kit": "^2.21.0",
"@types/papaparse": "^5.3.15",
"@typescript-eslint/eslint-plugin": "^8.32.1",
"@typescript-eslint/parser": "^8.32.1",
"@vite-pwa/sveltekit": "^0.6.6",
"eslint": "^8.57.0",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-svelte": "^2.45.1",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.4.0",
"sass": "^1.97.3",
"svelte": "^5.17.3",
"svelte-check": "^4.1.3",
"svelte-eslint-parser": "^0.42.0",
"svelte-preprocess": "^6.0.3",
"typescript": "^5.8.3",
"vite": "^6.3.5"
"@sveltejs/adapter-node": "5.2.12",
"@sveltejs/kit": "2.21.0",
"@types/papaparse": "5.3.15",
"@typescript-eslint/eslint-plugin": "8.32.1",
"@typescript-eslint/parser": "8.32.1",
"@vite-pwa/sveltekit": "0.6.6",
"eslint": "8.57.0",
"eslint-config-prettier": "10.1.2",
"eslint-plugin-svelte": "2.45.1",
"prettier": "3.4.2",
"prettier-plugin-svelte": "3.4.0",
"sass": "1.97.3",
"svelte": "5.17.3",
"svelte-check": "4.1.3",
"svelte-eslint-parser": "0.42.0",
"svelte-preprocess": "6.0.3",
"typescript": "5.8.3",
"vite": "6.3.5"
}
},
"node_modules/@ampproject/remapping": {
@@ -2112,22 +2111,24 @@
}
},
"node_modules/@eslint/js": {
"version": "8.57.1",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
"integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
"version": "8.57.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
"integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
"integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
"deprecated": "Use @eslint/config-array instead",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@humanwhocodes/object-schema": "^2.0.3",
"@humanwhocodes/object-schema": "^2.0.2",
"debug": "^4.3.1",
"minimatch": "^3.0.5"
},
@@ -2136,20 +2137,22 @@
}
},
"node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.16",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -2175,7 +2178,8 @@
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
"integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
"deprecated": "Use @eslint/object-schema instead",
"dev": true
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
@@ -3332,10 +3336,11 @@
"dev": true
},
"node_modules/@vite-pwa/sveltekit": {
"version": "0.6.7",
"resolved": "https://registry.npmjs.org/@vite-pwa/sveltekit/-/sveltekit-0.6.7.tgz",
"integrity": "sha512-geeRgOiFr4RdW4JHIshOTsa8OqX4P+aJtudnKjuxzpmHy44bo4o18OWM1iXc4qXBJCxCw+3yTttyJsdmHXG/sA==",
"version": "0.6.6",
"resolved": "https://registry.npmjs.org/@vite-pwa/sveltekit/-/sveltekit-0.6.6.tgz",
"integrity": "sha512-f08xlcfZyaXgytl05hUtCLih7Vr8zOk/E4eE/UK71M+sEGk+y+vUyTuR8td8KQalpf1Begb8XkxAkV7iUj2a0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"kolorist": "^1.8.0",
"tinyglobby": "^0.2.9"
@@ -3349,7 +3354,7 @@
"peerDependencies": {
"@sveltejs/kit": "^1.3.1 || ^2.0.1",
"@vite-pwa/assets-generator": "^0.2.6",
"vite-plugin-pwa": ">=0.21.1 <1"
"vite-plugin-pwa": ">=0.20.5 <1"
},
"peerDependenciesMeta": {
"@vite-pwa/assets-generator": {
@@ -3489,11 +3494,6 @@
"dev": true,
"peer": true
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/at-least-node": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
@@ -3520,17 +3520,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/axios": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -3802,17 +3791,6 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
@@ -4030,14 +4008,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
@@ -4304,17 +4274,18 @@
}
},
"node_modules/eslint": {
"version": "8.57.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
"integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
"version": "8.57.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.4",
"@eslint/js": "8.57.1",
"@humanwhocodes/config-array": "^0.13.0",
"@eslint/js": "8.57.0",
"@humanwhocodes/config-array": "^0.11.14",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0",
@@ -4388,22 +4359,23 @@
}
},
"node_modules/eslint-plugin-svelte": {
"version": "2.46.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.46.1.tgz",
"integrity": "sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==",
"version": "2.45.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.45.1.tgz",
"integrity": "sha512-mYAKNDRji0YWl7o00KQi0enREcrtzcN7xwK/8lwk5uLRoKLjzPXc+WjngsYpPV35I3AF7UlXc1+JfyNMJS+njA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@jridgewell/sourcemap-codec": "^1.4.15",
"eslint-compat-utils": "^0.5.1",
"esutils": "^2.0.3",
"known-css-properties": "^0.35.0",
"known-css-properties": "^0.34.0",
"postcss": "^8.4.38",
"postcss-load-config": "^3.1.4",
"postcss-safe-parser": "^6.0.0",
"postcss-selector-parser": "^6.1.0",
"semver": "^7.6.2",
"svelte-eslint-parser": "^0.43.0"
"svelte-eslint-parser": "^0.42.0"
},
"engines": {
"node": "^14.17.0 || >=16.0.0"
@@ -4413,34 +4385,7 @@
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0",
"svelte": "^3.37.0 || ^4.0.0 || ^5.0.0"
},
"peerDependenciesMeta": {
"svelte": {
"optional": true
}
}
},
"node_modules/eslint-plugin-svelte/node_modules/svelte-eslint-parser": {
"version": "0.43.0",
"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz",
"integrity": "sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==",
"dev": true,
"dependencies": {
"eslint-scope": "^7.2.2",
"eslint-visitor-keys": "^3.4.3",
"espree": "^9.6.1",
"postcss": "^8.4.39",
"postcss-scss": "^4.0.9"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ota-meshi"
},
"peerDependencies": {
"svelte": "^3.37.0 || ^4.0.0 || ^5.0.0"
"svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.191"
},
"peerDependenciesMeta": {
"svelte": {
@@ -4738,25 +4683,6 @@
"integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
"dev": true
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/for-each": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
@@ -4767,19 +4693,6 @@
"is-callable": "^1.1.3"
}
},
"node_modules/form-data": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
@@ -5809,10 +5722,11 @@
}
},
"node_modules/known-css-properties": {
"version": "0.35.0",
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.35.0.tgz",
"integrity": "sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==",
"dev": true
"version": "0.34.0",
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.34.0.tgz",
"integrity": "sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==",
"dev": true,
"license": "MIT"
},
"node_modules/kolorist": {
"version": "1.8.0",
@@ -5963,25 +5877,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@@ -6425,11 +6320,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+20 -21
View File
@@ -14,29 +14,28 @@
"format": "prettier --write ."
},
"devDependencies": {
"@sveltejs/adapter-node": "^5.2.12",
"@sveltejs/kit": "^2.21.0",
"@types/papaparse": "^5.3.15",
"@typescript-eslint/eslint-plugin": "^8.32.1",
"@typescript-eslint/parser": "^8.32.1",
"@vite-pwa/sveltekit": "^0.6.6",
"eslint": "^8.57.0",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-svelte": "^2.45.1",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.4.0",
"sass": "^1.97.3",
"svelte": "^5.17.3",
"svelte-check": "^4.1.3",
"svelte-eslint-parser": "^0.42.0",
"svelte-preprocess": "^6.0.3",
"typescript": "^5.8.3",
"vite": "^6.3.5"
"@sveltejs/adapter-node": "5.2.12",
"@sveltejs/kit": "2.21.0",
"@types/papaparse": "5.3.15",
"@typescript-eslint/eslint-plugin": "8.32.1",
"@typescript-eslint/parser": "8.32.1",
"@vite-pwa/sveltekit": "0.6.6",
"eslint": "8.57.0",
"eslint-config-prettier": "10.1.2",
"eslint-plugin-svelte": "2.45.1",
"prettier": "3.4.2",
"prettier-plugin-svelte": "3.4.0",
"sass": "1.97.3",
"svelte": "5.17.3",
"svelte-check": "4.1.3",
"svelte-eslint-parser": "0.42.0",
"svelte-preprocess": "6.0.3",
"typescript": "5.8.3",
"vite": "6.3.5"
},
"type": "module",
"dependencies": {
"axios": "^1.9.0",
"blurhash": "^2.0.5",
"papaparse": "^5.4.1"
"blurhash": "2.0.5",
"papaparse": "5.4.1"
}
}
+10 -39
View File
@@ -3,14 +3,10 @@
import Modal from "@/lib/Modal.svelte";
import Spinner from "@/lib/Spinner.svelte";
import { notify } from "@/lib/util/notify";
import {
JobStatus,
type GetJobResponse,
type JobCreatedResponse,
} from "@/types";
import axios from "axios";
import { JobStatus, type GetJobResponse } from "@/types";
import { onDestroy, onMount } from "svelte";
import { store } from "@/store.svelte";
import { req } from "./util/api";
import { ReqerError } from "./util/fetch";
interface Props {
modalTitle: string;
@@ -46,7 +42,7 @@
} catch (err: any) {
console.error("startSync failed!", err);
step = "errored";
jobFailError = err?.response ? err?.response?.data?.error : String(err);
jobFailError = ReqerError.getMsg(err, "Starting sync failed");
}
}
@@ -63,13 +59,13 @@
let seqfailedJobReqs = 0;
while (step === "job-running") {
try {
const r = await axios.get<GetJobResponse>(`/job/${jobId}`);
console.log("jobWatcher: Got job data:", r.data);
latestJobStatus = r.data;
currentTask = r.data?.currentTask;
if (r.data?.status === JobStatus.DONE) {
const r = await req.get<GetJobResponse>(`/job/${jobId}`);
console.log("jobWatcher: Got job data:", r);
latestJobStatus = r;
currentTask = r?.currentTask;
if (r?.status === JobStatus.DONE) {
step = "done";
} else if (r.data?.status === JobStatus.CANCELLED) {
} else if (r?.status === JobStatus.CANCELLED) {
step = "errored";
}
// If we get here without erroring, we can reset it to 0.
@@ -94,31 +90,6 @@
}
await new Promise((r) => setTimeout(r, 1000));
}
if (step !== "modal-closing") {
// Update our watched list
const nid = notify({
text: "Fetching updated watched list.",
type: "loading",
});
try {
const w = await axios.get("/watched");
if (w?.data?.length > 0) {
store.watchedList = w.data;
}
notify({
id: nid,
text: "Fetched updated watched list.",
type: "success",
});
} catch (err) {
console.error("jobWatcher: Getting updated watched list failed!", err);
notify({
id: nid,
text: "Getting updated watched list failed!",
type: "error",
});
}
}
}
function modalClose() {
+3 -3
View File
@@ -1,8 +1,8 @@
<script lang="ts">
import axios from "axios";
import DropDown from "./DropDown.svelte";
import type { DropDownItem, TMDBRegions } from "@/types";
import Error from "./Error.svelte";
import { req } from "./util/api";
interface Props {
selectedCountry?: string;
@@ -16,10 +16,10 @@
onChange,
}: Props = $props();
let mappedCountries: DropDownItem[] = $state();
let mappedCountries: DropDownItem[] = $state([]);
async function getCountries() {
const c = (await axios.get(`/content/regions`)).data as TMDBRegions;
const c = await req.get<TMDBRegions>(`/content/regions`);
mappedCountries = c.results
.map((cc) => {
return {
+4 -6
View File
@@ -2,11 +2,11 @@
import type { ContentType, PublicUser, WatchedStatus } from "@/types";
import HorizontalList from "../HorizontalList.svelte";
import Modal from "../Modal.svelte";
import axios from "axios";
import Spinner from "../Spinner.svelte";
import Error from "../Error.svelte";
import Icon from "../Icon.svelte";
import { watchedStatuses } from "../util/helpers";
import { req } from "../util/api";
interface FollowThoughts {
followedUser: PublicUser;
@@ -26,11 +26,9 @@
let modalShownFor: FollowThoughts | undefined = $state(undefined);
async function getFollowsThoughts() {
return (
await axios.get<FollowThoughts[]>(
`/follow/thoughts/${mediaType}/${mediaId}`,
)
).data;
return await req.get<FollowThoughts[]>(
`/follow/thoughts/${mediaType}/${mediaId}`,
);
}
</script>
+3 -3
View File
@@ -1,11 +1,11 @@
<script lang="ts">
import { onMount } from "svelte";
import Modal from "../Modal.svelte";
import axios from "axios";
import type { TrustedHeaderAuthLogoutDetailsResponse } from "@/types";
import Spinner from "../Spinner.svelte";
import { clearWatcharrData } from ".";
import { goto } from "$app/navigation";
import { req } from "../util/api";
interface Props {
onClose: () => void;
@@ -18,10 +18,10 @@
onMount(async () => {
try {
const r = await axios.get<TrustedHeaderAuthLogoutDetailsResponse>(
const r = await req.get<TrustedHeaderAuthLogoutDetailsResponse>(
"/auth/proxy_logout_details",
);
logoutUrl = r?.data?.logoutUrl;
logoutUrl = r?.logoutUrl;
if (!logoutUrl?.toLowerCase()?.startsWith("http")) {
// If no protocol in logoutUrl, set https
logoutUrl = `https://${logoutUrl}`;
+17 -15
View File
@@ -6,12 +6,13 @@
ArrRequestStatus,
ContentType,
} from "@/types";
import axios from "axios";
import { onMount } from "svelte";
import Icon from "../Icon.svelte";
import tooltip from "../actions/tooltip";
import { msToAmountsOfTime } from "../util/helpers";
import { notify } from "../util/notify";
import { req } from "../util/api";
import { ReqerError } from "../util/fetch";
interface Props {
type: ContentType;
@@ -48,11 +49,11 @@
);
return;
}
const resp = await axios.get<ArrInfoResponse>(
const resp = await req.get<ArrInfoResponse>(
`/arr/${type === "movie" ? "rad" : "son"}/info/${existingRequest.id}`,
);
if (resp?.data) {
info = resp.data;
if (resp) {
info = resp;
if (info.hasFile) {
status = "available";
} else {
@@ -61,8 +62,9 @@
}
} catch (err: any) {
if (
err?.response?.status === 404 &&
err?.response?.data?.error === "request deleted"
ReqerError.withBody(err) &&
err.response?.status === 404 &&
err.body.error === "request deleted"
) {
return;
}
@@ -80,13 +82,13 @@
console.warn("getStatus called before existingRequest exists.");
return;
}
const statusResp = await axios.get<ArrDetailsResponse>(
const statusResp = await req.getWhole<ArrDetailsResponse>(
`/arr/${type === "movie" ? "rad" : "son"}/status/${existingRequest.serverName}/${existingRequest.arrId}`,
);
if (statusResp.status === 204) {
status = "requested";
} else if (statusResp?.data) {
status = statusResp.data;
} else if (statusResp.body) {
status = statusResp.body;
const estMs =
new Date(status.estimatedCompletionTime).getTime() -
new Date(Date.now()).getTime();
@@ -119,19 +121,19 @@
async function lookForExisting() {
try {
const existingRequestResp = await axios.get<ArrRequestResponse>(
const existingRequestResp = await req.get<ArrRequestResponse>(
`/arr/${type === "movie" ? "rad" : "son"}/request/${tmdbId}`,
);
if (existingRequestResp?.data && existingRequestResp?.data?.arrId) {
existingRequest = existingRequestResp?.data;
if (existingRequestResp && existingRequestResp.arrId) {
existingRequest = existingRequestResp;
getInfo();
} else if (existingRequestResp?.data) {
} else if (existingRequestResp) {
// If no arrId, use status in request (pending, denied, etc)
console.log(
"No arrId in request resp.. using request status for btn status if set.",
);
if (existingRequestResp?.data?.status) {
status = existingRequestResp?.data?.status;
if (existingRequestResp.status) {
status = existingRequestResp.status;
}
}
} catch (err) {
+9 -9
View File
@@ -1,5 +1,4 @@
<script lang="ts">
import axios from "axios";
import Modal from "../Modal.svelte";
import type {
ArrRequestResponse,
@@ -11,6 +10,7 @@
import DropDown from "../DropDown.svelte";
import Setting from "../settings/Setting.svelte";
import Spinner from "../Spinner.svelte";
import { req } from "../util/api";
interface Props {
content: Media;
@@ -35,9 +35,9 @@
async function getServers() {
try {
inputsDisabled = true;
const r = await axios.get("/arr/rad");
if (r.data?.length > 0) {
servarrs = r.data;
const r = await req.get<RadarrSettingsPublicResponseResult[]>("/arr/rad");
if (r?.length > 0) {
servarrs = r;
selectedServarrIndex = 0;
} else {
notify({ text: "No servers found", type: "error" });
@@ -53,8 +53,8 @@
async function getConfig(name: string) {
try {
inputsDisabled = true;
const r = await axios.get<RadarrTestResponse>(`/arr/rad/config/${name}`);
selectedServerCfg = r.data;
const r = await req.get<RadarrTestResponse>(`/arr/rad/config/${name}`);
selectedServerCfg = r;
inputsDisabled = false;
} catch (err) {
console.error("Failed to get config!", err);
@@ -89,7 +89,7 @@
notify({ id: nid, text: "No Root Folder Found", type: "error" });
return;
}
const resp = await axios.post<ArrRequestResponse>(
const resp = await req.post<ArrRequestResponse>(
`/arr/rad/request${approveMode && originalRequest ? `/approve/${originalRequest.id}` : ""}`,
{
serverName: server.name,
@@ -103,9 +103,9 @@
},
);
addRequestRunning = false;
if (resp.data) {
if (resp) {
notify({ id: nid, text: "Request complete", type: "success" });
onClose(resp.data);
onClose(resp);
}
} catch (err) {
console.error("content request failed!", err);
+9 -9
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import axios from "axios";
import { req } from "../util/api";
import Modal from "../Modal.svelte";
import type {
ArrRequestResponse,
@@ -51,9 +51,9 @@
async function getServers() {
try {
inputsDisabled = true;
const r = await axios.get("/arr/son");
if (r.data?.length > 0) {
servarrs = r.data;
const r = await req.get<SonarrSettingsPublicResponseResult[]>("/arr/son");
if (r.length > 0) {
servarrs = r;
selectedServarrIndex = 0;
} else {
notify({ text: "No servers found", type: "error" });
@@ -69,8 +69,8 @@
async function getConfig(name: string) {
try {
inputsDisabled = true;
const r = await axios.get<SonarrTestResponse>(`/arr/son/config/${name}`);
selectedServerCfg = r.data;
const r = await req.get<SonarrTestResponse>(`/arr/son/config/${name}`);
selectedServerCfg = r;
inputsDisabled = false;
} catch (err) {
console.error("Failed to get config!", err);
@@ -105,7 +105,7 @@
notify({ id: nid, text: "No Root Folder Found", type: "error" });
return;
}
const resp = await axios.post(
const resp = await req.post<ArrRequestResponse>(
`/arr/son/request${approveMode && originalRequest ? `/approve/${originalRequest.id}` : ""}`,
{
serverName: server.name,
@@ -128,9 +128,9 @@
},
);
addRequestRunning = false;
if (resp?.data) {
if (resp) {
notify({ id: nid, text: "Request complete", type: "success" });
onClose(resp.data);
onClose(resp);
}
} catch (err) {
console.error("content request failed!", err);
+10 -7
View File
@@ -6,7 +6,7 @@
WatchedSeason,
WatchedStatus,
} from "@/types";
import axios from "axios";
import { req } from "../util/api";
import Spinner from "@/lib/Spinner.svelte";
import Error from "@/lib/Error.svelte";
import SeasonsListEpisode from "./SeasonsListEpisode.svelte";
@@ -45,15 +45,18 @@
async function sdr(seasonNum: number) {
const wid = watchedItem?.id;
console.debug("SeasonList: sdr: Called.", tvId, seasonNum, wid);
const resp = await axios.get(`/content/tv/${tvId}/season/${seasonNum}`, {
params: {
watchedId: wid,
const resp = await req.getWhole<TMDBSeasonDetails>(
`/content/tv/${tvId}/season/${seasonNum}`,
{
params: {
watchedId: wid,
},
},
});
);
try {
if (wid) {
// If we sent a watched id, expect a 'watcharr-lastviewedseason-saved' header in the response.
const hVal = resp.headers["watcharr-lastviewedseason-saved"];
const hVal = resp.headers.get("watcharr-lastviewedseason-saved");
if (hVal) {
lastViewedSeasonChanged(wid, seasonNum);
} else {
@@ -72,7 +75,7 @@
err,
);
}
return resp.data as TMDBSeasonDetails;
return resp.body;
}
function handleStatusClick(
+18 -17
View File
@@ -1,6 +1,7 @@
import axios from "axios";
import { req } from "../util/api";
import { notify } from "../util/notify";
import type {
Activity,
Watched,
WatchedEpisodeAddResponse,
WatchedSeasonAddResponse,
@@ -28,17 +29,17 @@ export async function updateWatchedSeason(
}
const nid = notify({ text: `Saving`, type: "loading" });
try {
const r = await axios.post<WatchedSeasonAddResponse>(`/watched/season`, {
const r = await req.post<WatchedSeasonAddResponse>(`/watched/season`, {
watchedId: watchedItem.id,
seasonNumber: seasonNumber,
status: opts.status,
rating: opts.rating,
});
watchedItem.watchedSeasons = r.data.watchedSeasons;
watchedItem.watchedSeasons = r.watchedSeasons;
if (watchedItem.activity && watchedItem.activity?.length > 0) {
watchedItem.activity.push(r.data.addedActivity);
watchedItem.activity.push(r.addedActivity);
} else {
watchedItem.activity = [r.data.addedActivity];
watchedItem.activity = [r.addedActivity];
}
notify({ id: nid, text: `Saved!`, type: "success" });
} catch (err) {
@@ -50,15 +51,15 @@ export async function updateWatchedSeason(
export async function removeWatchedSeason(watchedItem: Watched, id: number) {
const nid = notify({ text: `Removing`, type: "loading" });
try {
const r = await axios.delete(`/watched/season/${id}`);
const r = await req.delete<Activity>(`/watched/season/${id}`);
watchedItem.watchedSeasons = watchedItem.watchedSeasons?.filter(
(s) => s.id !== id,
);
if (r.data) {
if (r) {
if (watchedItem.activity && watchedItem.activity?.length > 0) {
watchedItem.activity.push(r.data);
watchedItem.activity.push(r);
} else {
watchedItem.activity = [r.data];
watchedItem.activity = [r];
}
}
notify({ id: nid, text: `Removed!`, type: "success" });
@@ -80,21 +81,21 @@ export async function updateWatchedEpisode(
}
const nid = notify({ text: `Saving`, type: "loading" });
try {
const r = await axios.post<WatchedEpisodeAddResponse>(`/watched/episode`, {
const r = await req.post<WatchedEpisodeAddResponse>(`/watched/episode`, {
watchedId: watchedItem.id,
seasonNumber,
episodeNumber,
status: opts.status,
rating: opts.rating,
});
watchedItem.watchedEpisodes = r.data.watchedEpisodes;
watchedItem.watchedEpisodes = r.watchedEpisodes;
if (watchedItem.activity && watchedItem.activity?.length > 0) {
watchedItem.activity.push(r.data.addedActivity);
watchedItem.activity.push(r.addedActivity);
} else {
watchedItem.activity = [r.data.addedActivity];
watchedItem.activity = [r.addedActivity];
}
try {
const epHookResp = r?.data?.episodeStatusChangedHookResponse;
const epHookResp = r?.episodeStatusChangedHookResponse;
if (epHookResp && Object.keys(epHookResp).length > 0) {
if (epHookResp.errors && epHookResp.errors.length > 0) {
console.error(
@@ -148,15 +149,15 @@ export async function updateWatchedEpisode(
export async function removeWatchedEpisode(watchedItem: Watched, id: number) {
const nid = notify({ text: `Removing`, type: "loading" });
try {
const r = await axios.delete(`/watched/episode/${id}`);
const r = await req.delete<Activity>(`/watched/episode/${id}`);
watchedItem.watchedEpisodes = watchedItem.watchedEpisodes?.filter(
(s) => s.id !== id,
);
if (r.data) {
if (watchedItem.activity && watchedItem.activity?.length > 0) {
watchedItem.activity.push(r.data);
watchedItem.activity.push(r);
} else {
watchedItem.activity = [r.data];
watchedItem.activity = [r];
}
}
notify({ id: nid, text: `Removed!`, type: "success" });
+6 -10
View File
@@ -5,9 +5,8 @@
import SettingsList from "../settings/SettingsList.svelte";
import ColorSelector from "../ColorSelector.svelte";
import { notify } from "../util/notify";
import axios from "axios";
import { req } from "../util/api";
import type { Tag, TagAddRequest } from "@/types";
import { get } from "svelte/store";
import { onMount } from "svelte";
interface Props {
@@ -54,15 +53,13 @@
}
const nid = notify({ text: "Creating Tag", type: "loading" });
try {
const resp = await axios.post<Tag>("/tag", {
const resp = await req.post<Tag>("/tag", {
name: tagName,
color: textColor,
bgColor,
} as TagAddRequest);
console.log("addTag: Tag was created", resp.data);
// const _tags = get(tags);
store.tags.push(resp.data);
// tags.update((t) => t);
console.log("addTag: Tag was created", resp);
store.tags.push(resp);
notify({ id: nid, text: "Tag Created!", type: "success" });
onClose();
} catch (err) {
@@ -80,17 +77,16 @@
}
const nid = notify({ text: "Modifying Tag", type: "loading" });
try {
const resp = await axios.put<Tag>(`/tag/${existingTag!.id}`, {
const resp = await req.put<Tag>(`/tag/${existingTag!.id}`, {
name: tagName,
color: textColor,
bgColor,
} as TagAddRequest);
console.log("updateTag: Tag was edited", resp.data);
console.log("updateTag: Tag was edited", resp);
existingTag!.name = tagName;
existingTag!.color = textColor;
existingTag!.bgColor = bgColor;
// Doesn't update `updatedAt`... may need to in the future if we need to sort by it, etc
// tags.update((t) => t);
notify({ id: nid, text: "Tag Modified!", type: "success" });
onClose();
} catch (err) {
+7 -28
View File
@@ -1,15 +1,11 @@
<script lang="ts">
import { store } from "@/store.svelte";
import Modal from "../Modal.svelte";
import Setting from "../settings/Setting.svelte";
import SettingsList from "../settings/SettingsList.svelte";
import ColorSelector from "../ColorSelector.svelte";
import { notify } from "../util/notify";
import axios from "axios";
import type { Tag as TagT, TagAddRequest } from "@/types";
import { get } from "svelte/store";
import type { Tag as TagT } from "@/types";
import Tag from "./Tag.svelte";
import { onMount } from "svelte";
import { req } from "../util/api";
interface Props {
tag: TagT;
@@ -30,28 +26,10 @@
deleteDisabled = true;
const nid = notify({ text: "Deleting Tag", type: "loading" });
try {
const resp = await axios.delete(`/tag/${tag.id}`);
console.log("deleteTag: Tag was deleted", resp.data);
// 1. Remove tag from store.
// const _tags = get(tags);
// store.tags.update((t) => t);
const resp = await req.delete(`/tag/${tag.id}`);
console.log("deleteTag: Tag was deleted", resp);
// Remove tag from store.
store.tags = store.tags.filter((t) => t.id !== tag.id);
// tags.update(() => newList);
// 2. Remove tag from all watched entries in store.
try {
for (let i = 0; i < store.watchedList.length; i++) {
const wi = store.watchedList[i];
if (wi.tags && wi.tags.length > 0) {
wi.tags = wi.tags.filter((t) => t.id !== tag.id);
}
}
// watchedList.update(() => wList);
} catch (err) {
console.error(
"deleteTag: Failed to remove tags from watched entries:",
err,
);
}
notify({ id: nid, text: "Tag Deleted!", type: "success" });
onClose();
} catch (err) {
@@ -63,7 +41,8 @@
}
onMount(() => {
// Sort of prevent accidental clickage, wait 3s after opening modal before enabling delete btn.
// Sort of prevent accidental clickage, wait 3s after opening modal
// before enabling delete btn.
deleteDisabled = true;
setTimeout(() => {
deleteDisabled = false;
+5 -5
View File
@@ -1,4 +1,4 @@
import axios from "axios";
import { req } from "../util/api";
import { notify } from "../util/notify";
import type { Tag } from "@/types";
@@ -15,10 +15,10 @@ export async function tagWatched(
});
return false;
}
return await axios
return await req
.post(`/watched/${watchedId}/tag/${tag.id}`)
.then((resp) => {
console.log("tagWatched: Status:", resp.status);
console.log("tagWatched: Success.", resp);
notify({ id: nid, text: `Tagged!`, type: "success" });
return true;
})
@@ -42,10 +42,10 @@ export async function untagWatched(
});
return false;
}
return await axios
return await req
.delete(`/watched/${watchedId}/tag/${tag.id}`)
.then((resp) => {
console.log("untagWatched: Status:", resp.status);
console.log("untagWatched: Success.", resp);
notify({ id: nid, text: `Untagged!`, type: "success" });
return true;
})
+44 -51
View File
@@ -13,8 +13,9 @@ import {
type ActivityUpdateRequest,
type Activity,
type SupportedMedia,
type ServerFeatures,
} from "@/types";
import axios from "axios";
import { Reqer, ReqerError } from "./fetch";
import { notify, unNotify } from "./notify";
import { browser } from "$app/environment";
const { MODE } = import.meta.env;
@@ -27,6 +28,9 @@ export const baseURL =
: "/api";
console.log("api: baseURL constructed:", baseURL);
export const req = new Reqer(baseURL, true);
export const noAuthReq = new Reqer(baseURL, false);
/**
* Options for our internal updateWatched func.
*/
@@ -71,7 +75,7 @@ async function _updateWatched(
if (typeof letCountAsPlay !== "undefined") {
obj.letCountAsPlay = letCountAsPlay;
}
const resp = await axios.put<WatchedUpdateResponse>(
const resp = await req.put<WatchedUpdateResponse>(
`/watched/${wEntry.id}`,
obj,
);
@@ -79,14 +83,14 @@ async function _updateWatched(
if (rating) wEntry.rating = rating;
if (typeof thoughts !== "undefined") wEntry.thoughts = thoughts;
if (typeof pinned !== "undefined") wEntry.pinned = pinned;
if (resp?.data?.newActivity && resp?.data?.newActivity?.id) {
if (resp?.newActivity && resp?.newActivity?.id) {
if (wEntry.activity && wEntry.activity.length > 0) {
wEntry.activity.push(resp.data.newActivity);
wEntry.activity.push(resp.newActivity);
} else {
wEntry.activity = [resp.data.newActivity];
wEntry.activity = [resp.newActivity];
}
// If new activity counts as play, increment plays for local state.
if (resp.data.newActivity.countAsPlay) {
if (resp.newActivity.countAsPlay) {
if (wEntry.plays) {
wEntry.plays++;
} else {
@@ -133,22 +137,22 @@ export async function updateWatched(
// Add new watched item
notify({ id: nid, text: `Adding`, type: "loading" });
let req: WatchedAddRequest = {
let reqBody: WatchedAddRequest = {
contentType: opts.contentType,
status: opts.status,
rating: opts.rating,
};
if (opts.contentType === "movie" || opts.contentType === "tv") {
req.tmdbId = opts.contentId;
reqBody.tmdbId = opts.contentId;
} else if (opts.contentType === "game") {
req.igdbId = opts.contentId;
reqBody.igdbId = opts.contentId;
} else {
throw "invalid contentType";
}
const resp = await axios.post<Watched>("/watched", req);
console.log("Added watched:", resp.data);
const resp = await req.post<Watched>("/watched", reqBody);
console.log("Added watched:", resp);
notify({ id: nid, text: `Added!`, type: "success" });
return resp.data;
return resp;
} catch (err) {
console.error("updateWatched: Failed!", err);
notify({ id: nid, text: `Failed!`, type: "error" });
@@ -165,8 +169,8 @@ export async function removeWatched(id: number): Promise<boolean> {
console.log("removeWatched: Removing:", id);
const nid = notify({ text: "Removing", type: "loading" });
try {
const resp = await axios.delete(`/watched/${id}`);
console.log("removeWatched: Removed resp:", resp.data);
const resp = await req.delete(`/watched/${id}`);
console.log("removeWatched: Removed resp:", resp);
notify({ id: nid, text: "Removed!", type: "success" });
return true;
} catch (err) {
@@ -183,7 +187,7 @@ export async function updateActivity(
const nid = notify({ text: "Updating", type: "loading" });
console.debug("updateActivity:", activity, date);
try {
const resp = await axios.put(`/activity/${activity.id}`, {
const resp = await req.putWhole(`/activity/${activity.id}`, {
customDate: date.toISOString(),
} as ActivityUpdateRequest);
console.log("updateActivity: Response status:", resp.status);
@@ -201,7 +205,7 @@ export async function updateActivity(
export async function removeActivity(activityId: number): Promise<boolean> {
const nid = notify({ text: "Deleting", type: "loading" });
try {
await axios.delete("/activity/" + activityId);
await req.delete("/activity/" + activityId);
console.log("removeActivity: Removed:", activityId);
notify({ id: nid, text: "Deleted!", type: "success" });
return true;
@@ -219,9 +223,11 @@ export async function contentExistsOnJellyfin(
): Promise<JellyfinFoundContent | undefined> {
try {
if (Number(store.userInfo?.type) == UserType.Jellyfin) {
const resp = await axios.get(`/jellyfin/${type}/${name}/${tmdbId}`);
console.log("contentExistsOnJellyfin response:", resp.data);
return resp.data as JellyfinFoundContent;
const resp = await req.get<JellyfinFoundContent>(
`/jellyfin/${type}/${name}/${tmdbId}`,
);
console.log("contentExistsOnJellyfin response:", resp);
return resp;
}
} catch (err) {
console.error(err);
@@ -241,14 +247,12 @@ export function updateUserSetting<K extends keyof UserSettings>(
}
const originalValue = store.userSettings[name];
const nid = notify({ type: "loading", text: "Updating" });
axios
req
.post("/user/update", { [name]: value })
.then((r) => {
if (r.status === 200) {
if (store.userSettings) store.userSettings[name] = value;
notify({ id: nid, type: "success", text: "Updated" });
if (typeof done !== "undefined") done();
}
.then(() => {
if (store.userSettings) store.userSettings[name] = value;
notify({ id: nid, type: "success", text: "Updated" });
if (typeof done !== "undefined") done();
})
.catch((err) => {
console.error("Failed to update user setting", err);
@@ -264,18 +268,14 @@ export function changeUserPassword(
done?: (errMsg?: string) => void,
) {
const nid = notify({ type: "loading", text: "Changing Password" });
axios
req
.post("/auth/change_password", { oldPassword, newPassword })
.then((r) => {
if (r.status === 200) {
notify({ id: nid, type: "success", text: "Password Changed" });
if (typeof done !== "undefined") done();
}
.then(() => {
notify({ id: nid, type: "success", text: "Password Changed" });
if (typeof done !== "undefined") done();
})
.catch((err) => {
const errMsg = err?.response?.data?.error
? err.response.data.error
: "Couldn't Change Password";
const errMsg = ReqerError.getMsg(err, "Couldn't Change Password");
console.error(
"Change Password Form - Failed to change password on the server",
err,
@@ -290,9 +290,9 @@ export function changeUserPassword(
*/
export async function getServerFeatures() {
try {
const f = await axios.get("/features");
if (f?.data) {
store.serverFeatures = f.data;
const f = await req.get<ServerFeatures>("/features");
if (f) {
store.serverFeatures = f;
}
} catch (err) {
console.error("getServerFeatures failed!", err);
@@ -301,11 +301,11 @@ export async function getServerFeatures() {
export async function followUser(id: number) {
const nid = notify({ text: `Following`, type: "loading" });
axios
.post(`/follow/${id}`)
req
.post<Follow>(`/follow/${id}`)
.then((resp) => {
console.log("Followed:", resp.data);
store.follows.push(resp.data as Follow);
console.log("Followed:", resp);
store.follows.push(resp);
notify({ id: nid, text: `Followed!`, type: "success" });
})
.catch((err) => {
@@ -316,10 +316,10 @@ export async function followUser(id: number) {
export async function unfollowUser(id: number) {
const nid = notify({ text: `Unfollowing`, type: "loading" });
axios
req
.delete(`/follow/${id}`)
.then((resp) => {
console.log("Unfollowed:", resp.data);
console.log("Unfollowed:", resp);
store.follows = store.follows.filter((fo) => fo.followedUser.id != id);
notify({ id: nid, text: `Unfollowed!`, type: "success" });
})
@@ -328,10 +328,3 @@ export async function unfollowUser(id: number) {
notify({ id: nid, text: "Failed To Unfollow!", type: "error" });
});
}
/**
* For use with routes that don't require authentication (eg login/register)
*/
export const noAuthAxios = axios.create({
baseURL: baseURL,
});
+330
View File
@@ -0,0 +1,330 @@
import { goto } from "$app/navigation";
import { clearWatcharrData } from "../logout";
import { notify } from "./notify";
type ReqerParams = any;
/**
* Request config, extending upon base RequestInit.
*/
export interface ReqerConfig extends RequestInit {
/**
* URL Parameters.
*
* Example: `{ page: 1 }` will become &page=1 on the requests final url.
*/
params?: ReqerParams;
}
type ReqerConfigWithoutMethod = Omit<ReqerConfig, "method">;
export class ReqerError extends Error {
constructor(
public message: string,
public body?: any,
public response?: Response,
) {
super(message);
this.name = "ReqerError";
}
/**
* If err is a ReqerError.
*/
static isReqerError(err: any) {
return err instanceof ReqerError;
}
/**
* If err is a ReqerError and has a `body`.
*/
static withBody(
err: any,
): err is ReqerError & Required<Pick<ReqerError, "body">> {
return this.isReqerError(err) && err.body;
}
/**
* Get error message from an error.
*
* If `err` is a ReqerError, use message from that instance, which can be
* the error returned from response body. Otherwise, standard error message
* is returned if one is availble.
*
* `act` should be the action that failed. It will be combined with the
* error message to create one readable string of `action: err`.
*/
static getMsg(err: any, act: string): string {
// If response body has `error` property, is is a standard error
// object that the Watcharr server returns. We don't want to return
// a raw `body` if there is no expected `error` property incase it is
// a random html error page or whatever (its better to show a pretty
// error whenever possible, debugging errors are always in console),
// if there is no `error`, we try the `err.message`.
let msg = "You've encountered an extremely unguarded error!";
if (ReqerError.withBody(err) && err.body.error) {
msg = err.body.error;
} else if (err.message) {
msg = err.message;
}
return `${act}: ${msg}`;
}
}
/**
* Reqer request response object.
* The *Whole functions return this whole object, which includes
* request details (like status), along with the response body.
*/
export interface ReqerResponse<T> {
/**
* Parsed body.
*/
body: T;
/**
* Response status code.
*/
status: number;
/**
* Response headers.
*/
headers: Headers;
}
/**
* A simple Fetch API wrapper.
*
* Throws when response.status is not 2xx.
*
* Has nice `get`, `post`, etc methods that simply return response body. Along
* with their `*Whole` counterpart methods that return a `ReqerResponse` object
* for more involved logic that needs to look at status code or headers.
*/
export class Reqer {
constructor(
/**
* BaseURL for all requests.
*/
private baseUrl: string,
/**
* If the requests should add an Authorization header
* with users Watcharr token.
*/
private watcharrAuthed: boolean,
) {}
private buildBaseUrl(b: string): string {
if (!b.endsWith("/")) {
b += "/";
}
return b;
}
private buildUrlPath(p: string): string {
// Removes any slashes or "." from start of path.
return p.replace(/^[.\/\\]+/, "");
}
private buildUrl(p: string, params?: ReqerParams): URL {
try {
// URL builds the url string "properly", but I don't want that for
// this use case, so we are pre-processing the path and baseUrl to
// skirt the relative resolving of the paths.
// See: https://developer.mozilla.org/en-US/docs/Web/API/URL_API/Resolving_relative_references
const url = new URL(
this.buildUrlPath(p),
this.buildBaseUrl(this.baseUrl),
);
if (params && typeof params === "object") {
for (const k in params) {
if (!Object.hasOwn(params, k)) continue;
const el = params[k];
if (el) {
// We use append on url.searchParams instead of
// overwriting it with a new object incase it has any
// existing params parsed from the `p`ath.
url.searchParams.append(k, String(el));
}
}
}
return url;
} catch (err) {
console.error("reqer->buildUrl: Failed!", err);
throw new ReqerError("building request url failed");
}
}
private prepareRequestBody(
data: any,
headers: Headers,
): BodyInit | undefined {
if (!data) {
return;
}
// The browser can handle these.
if (
data instanceof FormData ||
data instanceof Blob ||
data instanceof URLSearchParams ||
data instanceof ArrayBuffer ||
ArrayBuffer.isView(data)
) {
return data as BodyInit;
}
// Already encoded, so return it as is.
if (typeof data === "string") {
return data;
}
// Stringify json.
headers.append("Content-Type", "application/json");
return JSON.stringify(data);
}
private async parseResponseBody(res: Response) {
const contentType = res.headers.get("Content-Type");
if (!contentType) {
throw new ReqerError("response has no content-type", res);
}
if (contentType.includes("application/json")) {
return await res.json();
} else if (contentType.includes("text/plain")) {
return await res.text();
}
throw new ReqerError("response includes an unsupported content-type", res);
}
async do<T>(p: string, cfg?: ReqerConfig): Promise<ReqerResponse<T>> {
try {
const headers = new Headers(cfg?.headers);
if (this.watcharrAuthed) {
const token = localStorage.getItem("token");
if (!token) {
console.error("No token, going to login.");
goto("/login?again=1");
throw new ReqerError("No auth token found");
}
headers.append("Authorization", token);
}
const reqBody = this.prepareRequestBody(cfg?.body, headers);
const res = await fetch(this.buildUrl(p, cfg?.params), {
...cfg,
body: reqBody,
headers,
});
let resBody = await this.parseResponseBody(res);
if (!res.ok) {
throw new ReqerError(
`request failed with ${res.status} ${res.statusText}`,
resBody,
res,
);
}
return {
body: resBody,
status: res.status,
headers: res.headers,
};
} catch (err) {
console.error("Reqer->do: Errored!", err);
if (err instanceof ReqerError) {
if (this.watcharrAuthed && err.response?.status === 401) {
console.error("Recieved 401 response, going to login.");
notify({ text: "Request Authorization Failed!", type: "error" });
clearWatcharrData();
goto("/login?again=1");
}
throw err;
} else if (err instanceof Error) {
throw new ReqerError(err.message);
}
throw new ReqerError("request failed");
}
}
/**
* GET request, returning parsed response body.
*/
async get<T>(p: string, cfg?: ReqerConfigWithoutMethod): Promise<T> {
return (await this.do<T>(p, { ...cfg, method: "GET" })).body;
}
/**
* Same as `get()`, except the whole ReqerResponse object is returned.
*/
async getWhole<T>(
p: string,
cfg?: ReqerConfigWithoutMethod,
): Promise<ReqerResponse<T>> {
return await this.do<T>(p, { ...cfg, method: "GET" });
}
/**
* POST request, returning parsed response body.
*/
async post<T>(
p: string,
data?: any,
cfg?: Omit<ReqerConfigWithoutMethod, "body">,
): Promise<T> {
return (await this.do<T>(p, { ...cfg, method: "POST", body: data })).body;
}
/**
* Same as `post()`, except the whole ReqerResponse object is returned.
*/
async postWhole<T>(
p: string,
data?: any,
cfg?: Omit<ReqerConfigWithoutMethod, "body">,
): Promise<ReqerResponse<T>> {
return await this.do<T>(p, { ...cfg, method: "POST", body: data });
}
/**
* PUT request, returning parsed response body.
*/
async put<T>(
p: string,
data?: any,
cfg?: ReqerConfigWithoutMethod,
): Promise<T> {
return (await this.do<T>(p, { ...cfg, method: "PUT", body: data })).body;
}
/**
* Same as `put()`, except the whole ReqerResponse object is returned.
*/
async putWhole<T>(
p: string,
data?: any,
cfg?: ReqerConfigWithoutMethod,
): Promise<ReqerResponse<T>> {
return await this.do<T>(p, { ...cfg, method: "PUT", body: data });
}
/**
* DELETE request, returning parsed response body.
*/
async delete<T>(p: string, cfg?: ReqerConfigWithoutMethod): Promise<T> {
return (await this.do<T>(p, { ...cfg, method: "DELETE" })).body;
}
/**
* Same as `delete()`, except the whole ReqerResponse object is returned.
*/
async deleteWhole<T>(
p: string,
cfg?: ReqerConfigWithoutMethod,
): Promise<ReqerResponse<T>> {
return await this.do<T>(p, { ...cfg, method: "DELETE" });
}
}
+6 -9
View File
@@ -1,5 +1,4 @@
import type { PaginationResponse } from "@/types";
import type { AxiosResponse, GenericAbortSignal } from "axios";
export enum PaginatedLoaderRunFnAction {
// Reset state before running fn. Simplifies running a clean request.
@@ -14,9 +13,7 @@ export enum PaginatedLoaderRunFnAction {
}
export default function paginatedLoader<T, U>(
fn: (
sig: GenericAbortSignal,
) => Promise<AxiosResponse<PaginationResponse<T, U>, any> | undefined>,
fn: (sig: AbortSignal) => Promise<PaginationResponse<T, U> | undefined>,
) {
let reqController = new AbortController();
@@ -102,20 +99,20 @@ export default function paginatedLoader<T, U>(
);
return;
}
state.page = resp.data.page;
state.pageMax = resp.data.totalPages;
state.page = resp.page;
state.pageMax = resp.totalPages;
console.debug(
`%cpaginatedLoader->runFn: Loaded Page=${state.page} Max=${state.pageMax}`,
logStyle,
);
if (!resp.data.results || resp.data.results.length <= 0) {
if (!resp.results || resp.results.length <= 0) {
state.reqLoading = false;
console.warn("loadWatchedList: No results.");
return;
}
state.data.push(...resp.data.results);
state.data.push(...resp.results);
state.data = state.data;
state.meta = resp.data.meta;
state.meta = resp.meta;
} catch (err: any) {
if (err?.code === "ERR_CANCELED") {
console.warn("loadWatchedList: Cancelled, not showing error.");
+14 -13
View File
@@ -1,4 +1,9 @@
import { noAuthAxios } from "./api";
import { Reqer } from "./fetch";
/**
* For requests outbound to `plex.tv`.
*/
const plexTvReq = new Reqer("https://plex.tv/api/v2", false);
interface Plex {
win: Window;
@@ -104,11 +109,11 @@ export async function plexPinPoll(
const doPoll = async () => {
try {
console.debug("plexPinPoll");
const r = await noAuthAxios.get(`https://plex.tv/api/v2/pins/${pin.id}`, {
const r = await plexTvReq.get<any>(`/pins/${pin.id}`, {
headers: { ...headers, code: pin.code },
});
if (r.data?.authToken) {
done(undefined, r.data.authToken);
if (r?.authToken) {
done(undefined, r.authToken);
win.close();
} else if (win?.closed) {
done(new Error("Plex popup closed before login completed"));
@@ -124,13 +129,9 @@ export async function plexPinPoll(
}
async function getPlexPin(headers: Record<string, string>): Promise<PlexPin> {
const r = await noAuthAxios.post(
`https://plex.tv/api/v2/pins?strong=true`,
undefined,
{
headers: headers,
},
);
console.debug("getPlexPin:", r.data);
return { id: r.data.id, code: r.data.code };
const r = await plexTvReq.post<any>(`/pins?strong=true`, undefined, {
headers: headers,
});
console.debug("getPlexPin:", r);
return { id: r.id, code: r.code };
}
+33 -25
View File
@@ -11,9 +11,16 @@
import FollowingMenu from "@/lib/nav/FollowingMenu.svelte";
import SortMenu from "@/lib/nav/SortMenu.svelte";
import TagMenu from "@/lib/tag/TagMenu.svelte";
import { req } from "@/lib/util/api";
import { isTouch } from "@/lib/util/helpers";
import { store, defaultSort } from "@/store.svelte";
import axios from "axios";
import type {
ServerFeatures,
Follow,
PrivateUser,
Tag,
UserSettings,
} from "@/types";
import { onMount } from "svelte";
interface Props {
children?: import("svelte").Snippet;
@@ -101,31 +108,32 @@
}
async function getInitialData() {
if (localStorage.getItem("token")) {
const [u, s, f, fo, ts] = await Promise.all([
axios.get("/user"),
axios.get("/user/settings"),
axios.get("/features"),
axios.get("/follow"),
axios.get("/tag"),
]);
if (u?.data) {
store.userInfo = u.data;
}
if (s?.data) {
store.userSettings = s.data;
}
if (f?.data) {
store.serverFeatures = f.data;
}
if (fo?.data) {
store.follows = fo.data;
}
if (ts?.data) {
store.tags = ts.data;
}
} else {
if (!localStorage.getItem("token")) {
console.warn("getInitialData: No token found, redirecting to login!");
goto("/login?again=1");
return;
}
const [u, s, f, fo, ts] = await Promise.all([
req.get<PrivateUser>("/user"),
req.get<UserSettings>("/user/settings"),
req.get<ServerFeatures>("/features"),
req.get<Follow[]>("/follow"),
req.get<Tag[]>("/tag"),
]);
if (u) {
store.userInfo = u;
}
if (s) {
store.userSettings = s;
}
if (f) {
store.serverFeatures = f;
}
if (fo) {
store.follows = fo;
}
if (ts) {
store.tags = ts;
}
}
-44
View File
@@ -1,47 +1,3 @@
export const prerender = false;
export const ssr = false;
export const csr = true;
import { goto } from "$app/navigation";
import axios from "axios";
import { baseURL } from "@/lib/util/api";
import { notify } from "@/lib/util/notify";
import { clearWatcharrData } from "@/lib/logout";
axios.interceptors.request.use(
(config) => {
if (!config.baseURL) {
config.baseURL = baseURL;
// Only want to set auth header if requesting to our backend.
const token = localStorage.getItem("token");
// Don't require token check if going to auth route (login/register)
if (!token && !config.url?.includes("/auth")) {
console.error("No token, going to login. Endpoint:", config.url);
goto("/login?again=1");
throw new axios.Cancel("No auth token found");
}
config.headers.set("Authorization", token);
}
return config;
},
(error) => {
return Promise.reject(error);
},
);
axios.interceptors.response.use(
(response) => {
return response;
},
(error) => {
if (error.response?.status === 401) {
console.error("Recieved 401 response, going to login.");
notify({ text: "Request Authorization Failed!", type: "error" });
clearWatcharrData();
goto("/login?again=1");
}
return Promise.reject(error);
},
);
+4 -4
View File
@@ -5,11 +5,11 @@
import Poster from "@/lib/poster/Poster.svelte";
import PosterList from "@/lib/poster/PosterList.svelte";
import Spinner from "@/lib/Spinner.svelte";
import { req } from "@/lib/util/api";
import infScroll from "@/lib/util/infScroll";
import paginatedLoader from "@/lib/util/paginatedLoader.svelte";
import { clearActiveFilters, store } from "@/store.svelte";
import type { Media } from "@/types";
import axios, { type GenericAbortSignal } from "axios";
import { type Media, type PaginationResponse } from "@/types";
import { onDestroy, untrack } from "svelte";
const scroll = infScroll({ callback: onScrollToBottom });
@@ -23,13 +23,13 @@
...store.sortAndFiltersForQueryParams,
});
async function load(signal: GenericAbortSignal) {
async function load(signal: AbortSignal) {
console.debug("load: loadParams:", nextLoadParams);
if (nextLoadParams.page === dataLoader.state.page) {
console.warn("load: Already on this page, not loading it again!");
return;
}
const r = await axios.get(`/watched`, {
const r = await req.get<PaginationResponse<Media, undefined>>(`/watched`, {
params: nextLoadParams,
signal,
});
+9 -11
View File
@@ -3,11 +3,10 @@
import Spinner from "@/lib/Spinner.svelte";
import RequestMovie from "@/lib/request/RequestMovie.svelte";
import RequestShow from "@/lib/request/RequestShow.svelte";
import { baseURL } from "@/lib/util/api";
import { baseURL, req } from "@/lib/util/api";
import { toRelativeDate } from "@/lib/util/helpers";
import { notify } from "@/lib/util/notify";
import { type ArrRequestResponse, type Media } from "@/types";
import axios from "axios";
let allRequests: ArrRequestResponse[] | undefined = $state();
let showBeingApproved: Media | undefined = $state();
@@ -16,8 +15,7 @@
async function getRequests() {
try {
allRequests = (await axios.get<ArrRequestResponse[]>(`/arr/request/`))
.data;
allRequests = await req.get<ArrRequestResponse[]>(`/arr/request/`);
if (allRequests?.length > 0) {
allRequests = allRequests?.sort((a, b) => {
if (b.status === "PENDING") return 1;
@@ -33,7 +31,7 @@
async function deny(r: ArrRequestResponse) {
try {
await axios.post(`/arr/request/deny/${r.id}`);
await req.post(`/arr/request/deny/${r.id}`);
getRequests();
} catch (err) {
console.error("Failed to deny request!", err);
@@ -44,13 +42,13 @@
async function approve(r: ArrRequestResponse) {
console.debug("Approving request:", r);
if (r.content.type === "tv") {
showBeingApproved = (
await axios.get<Media>(`/content/tv/${r.content.tmdbId}`)
).data;
showBeingApproved = await req.get<Media>(
`/content/tv/${r.content.tmdbId}`,
);
} else if (r.content.type === "movie") {
movieBeingApproved = (
await axios.get<Media>(`/content/movie/${r.content.tmdbId}`)
).data;
movieBeingApproved = await req.get<Media>(
`/content/movie/${r.content.tmdbId}`,
);
} else {
notify({
type: "error",
+4 -3
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import Spinner from "@/lib/Spinner.svelte";
import axios, { type GenericAbortSignal } from "axios";
import { req } from "@/lib/util/api";
import Poster from "@/lib/poster/Poster.svelte";
import PageTitle from "@/lib/generic/PageTitle.svelte";
import MediaTypeFilter from "@/lib/search/MediaTypeFilter.svelte";
@@ -14,6 +14,7 @@
SearchType,
type DiscoverRequest,
type Media,
type PaginationResponse,
} from "@/types";
import { page } from "$app/state";
import { afterNavigate, goto } from "$app/navigation";
@@ -40,13 +41,13 @@
filter: discoverFilter,
});
async function load(signal: GenericAbortSignal) {
async function load(signal: AbortSignal) {
console.debug("load: loadParams:", nextLoadParams);
if (nextLoadParams.page === dataLoader.state.page) {
console.warn("load: Already on this page, not loading it again!");
return;
}
const r = await axios.get(`/discover`, {
const r = await req.get<PaginationResponse<Media, undefined>>(`/discover`, {
params: nextLoadParams,
signal,
});
+2 -4
View File
@@ -2,12 +2,11 @@
import Spinner from "@/lib/Spinner.svelte";
import HorizontalList from "@/lib/HorizontalList.svelte";
import { type Media, type WatchedStatus } from "@/types";
import axios from "axios";
import Activity from "@/lib/Activity.svelte";
import Title from "@/lib/content/Title.svelte";
import Error from "@/lib/Error.svelte";
import FollowedThoughts from "@/lib/content/FollowedThoughts.svelte";
import { removeWatched, updateWatched } from "@/lib/util/api.js";
import { req, updateWatched } from "@/lib/util/api.js";
import tooltip from "@/lib/actions/tooltip.js";
import Icon from "@/lib/Icon.svelte";
import AddToTagButton from "@/lib/tag/AddToTagButton.svelte";
@@ -43,8 +42,7 @@
if (!data.gameId) {
return;
}
const resp = (await axios.get(`/game/${data.gameId}`)).data as Media;
game = resp;
game = await req.get<Media>(`/game/${data.gameId}`);
} catch (err: any) {
game = undefined;
pageError = err;
+8 -8
View File
@@ -25,7 +25,7 @@
type Media,
type WatchedStatus,
} from "@/types";
import axios from "axios";
import { req } from "@/lib/util/api";
import { onDestroy } from "svelte";
import papa from "papaparse";
import Status from "@/lib/Status.svelte";
@@ -479,11 +479,11 @@
rList = rList;
return;
}
const resp = await axios.post<ImportResponse>("/import", item);
const resp = await req.post<ImportResponse>("/import", item);
return new Promise((res, rej) => {
if (resp.data.type === ImportResponseType.IMPORT_MULTI) {
console.log("Import found multiple responses for content", resp.data);
let results = resp.data.results;
if (resp.type === ImportResponseType.IMPORT_MULTI) {
console.log("Import found multiple responses for content", resp);
let results = resp.results;
if (!results || results.length <= 0) {
item.state = ImportResponseType.IMPORT_NOTFOUND;
rList = rList;
@@ -521,9 +521,9 @@
}
},
};
} else if (resp.data.type === ImportResponseType.IMPORT_SUCCESS) {
} else if (resp.type === ImportResponseType.IMPORT_SUCCESS) {
item.state = ImportResponseType.IMPORT_SUCCESS;
const w = resp.data.watchedEntry;
const w = resp.watchedEntry;
if (w) {
const release = w.media?.releaseDate;
if (release) item.year = new Date(Date.parse(release)).getFullYear();
@@ -533,7 +533,7 @@
rList = rList;
res(0);
} else {
item.state = resp.data.type;
item.state = resp.type;
rList = rList;
res(0);
}
+5 -5
View File
@@ -6,29 +6,29 @@
<script lang="ts">
import JobWatcherModal from "@/lib/JobWatcherModal.svelte";
import Setting from "@/lib/settings/Setting.svelte";
import { req } from "@/lib/util/api";
import { notify } from "@/lib/util/notify";
import { store } from "@/store.svelte";
import type { JobCreatedResponse } from "@/types";
import axios from "axios";
let modalOpen = $state(false);
let traktUsername = $state("");
let traktApiKey = $state("");
async function startJob(): Promise<{ jobId: string } | undefined> {
const r = await axios.post<JobCreatedResponse>("/import/trakt", {
const r = await req.post<JobCreatedResponse>("/import/trakt", {
username: traktUsername,
apiKey: traktApiKey.trim(),
});
console.log("startSync: Response:", r.data);
if (!r.data.jobId) {
console.log("startSync: Response:", r);
if (!r?.jobId) {
notify({
type: "error",
text: "No job id was returned! Cannot watch job, if it even started.",
});
return;
}
return { jobId: r.data.jobId };
return { jobId: r.jobId };
}
</script>
@@ -3,11 +3,10 @@
import Spinner from "@/lib/Spinner.svelte";
import tooltip from "@/lib/actions/tooltip.js";
import UserAvatar from "@/lib/img/UserAvatar.svelte";
import { followUser, unfollowUser } from "@/lib/util/api.js";
import { followUser, req, unfollowUser } from "@/lib/util/api.js";
import { clearActiveFilters, store } from "@/store.svelte.js";
import type { Media, PublicUser } from "@/types.js";
import axios, { type GenericAbortSignal } from "axios";
import { onDestroy, onMount, untrack } from "svelte";
import type { Media, PaginationResponse, PublicUser } from "@/types.js";
import { onDestroy, untrack } from "svelte";
import paginatedLoader, {
PaginatedLoaderRunFnAction,
} from "@/lib/util/paginatedLoader.svelte.js";
@@ -43,7 +42,7 @@
...store.sortAndFiltersForQueryParams,
});
async function load(signal: GenericAbortSignal) {
async function load(signal: AbortSignal) {
console.debug("load: loadParams:", nextLoadParams);
if (nextLoadParams.page === dataLoader.state.page) {
console.warn("load: Already on this page, not loading it again!");
@@ -53,10 +52,13 @@
console.warn("load: Missing id or username!");
return;
}
const r = await axios.get(`/watched/${meta.id}/${meta.username}`, {
params: nextLoadParams,
signal,
});
const r = await req.get<PaginationResponse<Media, undefined>>(
`/watched/${meta.id}/${meta.username}`,
{
params: nextLoadParams,
signal,
},
);
scroll.dataLoaded();
return r;
}
@@ -87,8 +89,9 @@
});
async function getPublicUser() {
return (await axios.get(`/user/public/${meta.id}/${meta.username}`))
.data as PublicUser;
return await req.get<PublicUser>(
`/user/public/${meta.id}/${meta.username}`,
);
}
async function follow() {
+2 -2
View File
@@ -7,7 +7,7 @@
userHasPermission,
} from "@/lib/util/helpers";
import { UserPermission, type ManagedUser } from "@/types";
import axios from "axios";
import { req } from "@/lib/util/api";
import EditUserModal from "./modals/EditUserModal.svelte";
import UserTypeIcon from "@/lib/user/UserTypeIcon.svelte";
import Error from "@/lib/Error.svelte";
@@ -18,7 +18,7 @@
let editingUser: ManagedUser | undefined;
async function getUsers() {
allUsers = (await axios.get(`/server/users`)).data as ManagedUser[];
allUsers = await req.get<ManagedUser[]>(`/server/users`);
}
</script>
@@ -4,10 +4,11 @@
import Modal from "@/lib/Modal.svelte";
import Setting from "@/lib/settings/Setting.svelte";
import SettingsList from "@/lib/settings/SettingsList.svelte";
import { req } from "@/lib/util/api";
import { ReqerError } from "@/lib/util/fetch";
import { userHasPermission } from "@/lib/util/helpers";
import { notify } from "@/lib/util/notify";
import { UserPermission, UserType, type ManagedUser } from "@/types";
import axios from "axios";
interface UpdateUserRequest {
permissions?: number;
@@ -44,20 +45,15 @@
if (changedType) {
toUpdate["type"] = user.type;
}
const res = await axios.post(`/server/users/${user.id}`, toUpdate);
if (res.status === 200) {
notify({
type: "success",
text: "Changes saved!",
});
onClose();
}
await req.post(`/server/users/${user.id}`, toUpdate);
notify({
type: "success",
text: "Changes saved!",
});
onClose();
} catch (err: any) {
console.error("Failed to save user!", err);
error = `Failed to save`;
if (err?.response?.data?.error) {
error = err.response.data.error;
}
error = ReqerError.getMsg(err, "Failed to save");
}
}
}
+7 -13
View File
@@ -2,11 +2,7 @@
import PersonPoster from "@/lib/poster/PersonPoster.svelte";
import Spinner from "@/lib/Spinner.svelte";
import HorizontalList from "@/lib/HorizontalList.svelte";
import {
contentExistsOnJellyfin,
removeWatched,
updateWatched,
} from "@/lib/util/api";
import { contentExistsOnJellyfin, req, updateWatched } from "@/lib/util/api";
import { store } from "@/store.svelte";
import type {
Media,
@@ -14,7 +10,6 @@
TMDBContentCreditsCrew,
WatchedStatus,
} from "@/types";
import axios from "axios";
import { getTopCrew } from "@/lib/util/helpers.js";
import Activity from "@/lib/Activity.svelte";
import Title from "@/lib/content/Title.svelte";
@@ -52,11 +47,9 @@
if (!data.movieId) {
return;
}
const resp = (
await axios.get(`/content/movie/${data.movieId}`, {
params: { region: store.userSettings?.country },
})
).data as Media;
const resp = await req.get<Media>(`/content/movie/${data.movieId}`, {
params: { region: store.userSettings?.country },
});
if (resp) {
if (resp.name && resp.ids.tmdb) {
contentExistsOnJellyfin("movie", resp.name, resp.ids.tmdb).then(
@@ -79,8 +72,9 @@
});
async function getMovieCredits() {
const credits = (await axios.get(`/content/movie/${data.movieId}/credits`))
.data as TMDBContentCredits & { topCrew: TMDBContentCreditsCrew[] };
const credits = await req.get<
TMDBContentCredits & { topCrew: TMDBContentCreditsCrew[] }
>(`/content/movie/${data.movieId}/credits`);
if (credits.crew?.length > 0) {
credits.topCrew = getTopCrew(credits.crew);
}
+5 -8
View File
@@ -9,7 +9,7 @@
PersonCreditsResponse,
PersonDetailsResponse,
} from "@/types";
import axios from "axios";
import { req } from "@/lib/util/api.js";
import Checkbox from "@/lib/Checkbox.svelte";
import Icon from "@/lib/Icon.svelte";
import PageBackdrop from "@/lib/generic/PageBackdrop.svelte";
@@ -53,16 +53,13 @@
}
async function getPerson(id: number) {
return (await axios.get<PersonDetailsResponse>(`/content/person/${id}`))
.data;
return await req.get<PersonDetailsResponse>(`/content/person/${id}`);
}
async function updatePersonCredits() {
credits = (
await axios.get<PersonCreditsResponse>(
`/content/person/${data.personId}/credits`,
)
).data;
credits = await req.get<PersonCreditsResponse>(
`/content/person/${data.personId}/credits`,
);
credits.credits = credits.credits?.filter(
(v, i, a) => a.findIndex((t) => t.ids.tmdb === v.ids.tmdb) === i,
); // remove duplicate entries. If an actor has multiple roles in a single movie, it would otherwise show up multiple times
+11 -17
View File
@@ -6,11 +6,10 @@
import Setting from "@/lib/settings/Setting.svelte";
import Stat from "@/lib/stats/Stat.svelte";
import Stats from "@/lib/stats/Stats.svelte";
import { updateUserSetting } from "@/lib/util/api";
import { req, updateUserSetting } from "@/lib/util/api";
import { getOrdinalSuffix, monthsShort } from "@/lib/util/helpers";
import { store } from "@/store.svelte";
import { UserType, type Image, type Profile } from "@/types";
import axios from "axios";
import { notify } from "@/lib/util/notify";
import UserAvatar from "@/lib/img/UserAvatar.svelte";
import PwChangeModal from "@/routes/(app)/profile/modals/PwChangeModal.svelte";
@@ -19,6 +18,7 @@
import RatingSetting from "@/lib/rating/RatingSetting.svelte";
import { toggleTheme } from "@/lib/util/theme";
import ExportListModal from "./modals/ExportListModal.svelte";
import { ReqerError } from "@/lib/util/fetch";
let user = $derived(store.userInfo);
let settings = $derived(store.userSettings);
@@ -37,7 +37,7 @@
let plexSyncModalOpen = $state(false);
async function getProfile() {
return (await axios.get(`/profile`)).data as Profile;
return await req.get<Profile>(`/profile`);
}
function formatDate(d: Date) {
@@ -55,7 +55,7 @@
return;
}
const nid = notify({ text: "Updating Bio", type: "loading" });
axios
req
.post("/user/bio", { newBio: newBio })
.then(() => {
if (user) {
@@ -66,7 +66,7 @@
.catch((err) => {
notify({
id: nid,
text: err?.response?.data?.error ?? "Failed to update bio",
text: ReqerError.getMsg(err, "Failed to update bio"),
type: "error",
});
});
@@ -79,19 +79,13 @@
return;
}
const nid = notify({ text: "Uploading avatar", type: "loading" });
axios
.postForm(
"/user/avatar",
{ avatar: files[0] },
{
headers: {
"Content-Type": "multipart/form-data",
},
},
)
const fd = new FormData();
fd.append("avatar", files[0]);
req
.post<Image>("/user/avatar", fd)
.then((r) => {
if (user) {
user.avatar = r.data as Image;
user.avatar = r;
notify({ id: nid, text: "Avatar Uploaded", type: "success" });
}
})
@@ -99,7 +93,7 @@
console.error("uploading avatar failed", err);
notify({
id: nid,
text: err?.response?.data?.error ?? "Failed to upload avatar",
text: ReqerError.getMsg(err, "Failed to upload avatar"),
type: "error",
});
});
@@ -1,7 +1,7 @@
<script lang="ts">
import Modal from "@/lib/Modal.svelte";
import { req } from "@/lib/util/api";
import { notify } from "@/lib/util/notify";
import axios from "axios";
interface Props {
onClose: () => void;
@@ -13,9 +13,9 @@
const nid = notify({ text: "Exporting", type: "loading" });
try {
// We re-fetch, to ensure data we export is up to date.
const r = await axios.get("/watched");
console.log(r.data);
if (!r.data || r.data?.length <= 0) {
const r = await req.get<any>("/watched");
console.log(r);
if (!r || r?.length <= 0) {
notify({
id: nid,
text: "Can't export an empty watch list!",
@@ -24,7 +24,7 @@
});
return;
}
const file = new Blob([JSON.stringify(r.data, undefined, 2)], {
const file = new Blob([JSON.stringify(r, undefined, 2)], {
type: "application/json",
});
const a = document.createElement("a");
@@ -11,9 +11,8 @@
type GetJobResponse,
type JobCreatedResponse,
} from "@/types";
import axios from "axios";
import { onDestroy, onMount } from "svelte";
import { store } from "@/store.svelte";
import { req } from "@/lib/util/api";
interface Props {
type?: "jellyfin" | "plex";
@@ -30,16 +29,16 @@
async function startSync() {
try {
const r = await axios.get<JobCreatedResponse>(
const r = await req.get<JobCreatedResponse>(
type === "jellyfin" ? "/jellyfin/sync" : "/plex/sync",
);
console.log("startSync: Response:", r.data);
if (!r.data.jobId) {
console.log("startSync: Response:", r);
if (!r.jobId) {
step = "errored";
console.error("startSync: No jobId returned!");
return;
}
jobId = r.data.jobId;
jobId = r.jobId;
step = "job-running";
startJobWatcher();
} catch (err) {
@@ -61,13 +60,13 @@
let seqfailedJobReqs = 0;
while (step === "job-running") {
try {
const r = await axios.get<GetJobResponse>(`/job/${jobId}`);
console.log("jobWatcher: Got job data:", r.data);
latestJobStatus = r.data;
currentTask = r.data?.currentTask;
if (r.data?.status === JobStatus.DONE) {
const r = await req.get<GetJobResponse>(`/job/${jobId}`);
console.log("jobWatcher: Got job data:", r);
latestJobStatus = r;
currentTask = r?.currentTask;
if (r?.status === JobStatus.DONE) {
step = "done";
} else if (r.data?.status === JobStatus.CANCELLED) {
} else if (r?.status === JobStatus.CANCELLED) {
step = "errored";
}
// If we get here without erroring, we can reset it to 0.
@@ -92,31 +91,6 @@
}
await new Promise((r) => setTimeout(r, 1000));
}
if (step !== "modal-closing") {
// Update our watched list
const nid = notify({
text: "Fetching updated watched list.",
type: "loading",
});
try {
const w = await axios.get("/watched");
if (w?.data?.length > 0) {
store.watchedList = w.data;
}
notify({
id: nid,
text: "Fetched updated watched list.",
type: "success",
});
} catch (err) {
console.error("jobWatcher: Getting updated watched list failed!", err);
notify({
id: nid,
text: "Getting updated watched list failed!",
type: "error",
});
}
}
}
function modalClose() {
+4 -7
View File
@@ -1,15 +1,15 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { req } from "@/lib/util/api";
import { notify } from "@/lib/util/notify";
import { store } from "@/store.svelte";
import { UserPermission } from "@/types";
import axios from "axios";
let page = $state(0);
let adminToken: string | undefined = $state();
function generateAdminToken() {
axios
req
.get("/auth/admin_token")
.then(() => {
page = 1;
@@ -25,7 +25,7 @@
console.error("useAdminToken: No admin token provided!");
return;
}
axios
req
.post("/auth/admin_token", { token: adminToken })
.then(() => {
notify({ type: "success", text: "You now have admin!" });
@@ -35,10 +35,7 @@
goto("/");
})
.catch((err) => {
console.error(
"Failed to use admin token",
err?.response?.data?.error || err,
);
console.error("Failed to use admin token", err);
notify({ type: "error", text: "Failed to use admin token!" });
});
}
+13 -8
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { page } from "$app/state";
import { afterNavigate, goto } from "$app/navigation";
import axios, { type GenericAbortSignal } from "axios";
import { req } from "@/lib/util/api.js";
import Poster from "@/lib/poster/Poster.svelte";
import PosterList from "@/lib/poster/PosterList.svelte";
import { store } from "@/store.svelte.js";
@@ -11,6 +11,7 @@
MediaTypeE,
SearchType,
type Media,
type PaginationResponse,
type PublicUser,
type SearchRequest,
type SearchResponseMeta,
@@ -55,7 +56,7 @@
preferMyList: preferMyList,
});
async function load(signal: GenericAbortSignal) {
async function load(signal: AbortSignal) {
console.debug("load: loadParams:", nextLoadParams);
if (nextLoadParams.page === dataLoader.state.page) {
console.warn("load: Already on this page, not loading it again!");
@@ -65,10 +66,13 @@
console.warn("load: There is no search query!");
return;
}
const r = await axios.get(`/search`, {
params: nextLoadParams,
signal,
});
const r = await req.get<PaginationResponse<Media, SearchResponseMeta>>(
`/search`,
{
params: nextLoadParams,
signal,
},
);
scroll.dataLoaded();
return r;
}
@@ -95,8 +99,9 @@
}
async function searchUsers(query: string) {
return (await axios.get(`/user/search`, { params: { q: query } }))
.data as PublicUser[];
return await req.get<PublicUser[]>(`/user/search`, {
params: { q: query },
});
}
onMount(() => {
+6 -7
View File
@@ -8,13 +8,12 @@
ServerConfig,
SonarrSettings,
} from "@/types";
import axios from "axios";
import SonarrModal from "./modals/SonarrModal.svelte";
import SettingsList from "@/lib/settings/SettingsList.svelte";
import Setting from "@/lib/settings/Setting.svelte";
import SettingButton from "@/lib/settings/SettingButton.svelte";
import RadarrModal from "./modals/RadarrModal.svelte";
import { getServerFeatures } from "@/lib/util/api";
import { getServerFeatures, req } from "@/lib/util/api";
import Stats from "@/lib/stats/Stats.svelte";
import Error from "@/lib/Error.svelte";
import Stat from "@/lib/stats/Stat.svelte";
@@ -44,7 +43,7 @@
let useEmbyDisabled = $state(false);
async function getServerConfig() {
serverConfig = (await axios.get(`/server/config`)).data as ServerConfig;
serverConfig = await req.get<ServerConfig>(`/server/config`);
}
export function updateServerConfig<K extends keyof ServerConfig>(
@@ -64,13 +63,13 @@
if (name === "PLEX_HOST") {
ep = "/server/config/plex_host";
}
axios
.post(ep, { key: name, value: value })
req
.postWhole<any>(ep, { key: name, value: value })
.then((r) => {
if (r.status === 200) {
serverConfig![name] = value;
notify({ id: nid, type: "success", text: "Updated" });
if (typeof done !== "undefined") done(r?.data);
if (typeof done !== "undefined") done(r?.body);
}
})
.catch((err) => {
@@ -93,7 +92,7 @@
}
async function getServerStats() {
return (await axios.get("/server/stats")).data as ServerStats;
return await req.get<ServerStats>("/server/stats");
}
</script>
@@ -12,7 +12,8 @@
RadarrSettings,
RadarrTestResponse,
} from "@/types";
import axios from "axios";
import { req } from "@/lib/util/api";
import { ReqerError } from "@/lib/util/fetch";
interface Props {
servarr: RadarrSettings;
@@ -22,7 +23,7 @@
let { servarr = $bindable(), isEditing, onClose }: Props = $props();
let error: string = $state();
let error: string | undefined = $state();
let formDisabled = $state(false);
let qualityProfiles: DropDownItem[] = $state([]);
@@ -42,14 +43,14 @@
async function getSettingsData() {
try {
formDisabled = true;
const res = await axios.post<RadarrTestResponse>("/arr/rad/test", {
const res = await req.post<RadarrTestResponse>("/arr/rad/test", {
host: servarr.host,
key: servarr.key,
});
qualityProfiles = res.data.qualityProfiles.map((d) => {
qualityProfiles = res.qualityProfiles.map((d) => {
return { id: d.id, value: d.name };
});
rootFolders = res.data.rootFolders.map((d) => {
rootFolders = res.rootFolders.map((d) => {
return { id: d.id, value: d.path };
});
formDisabled = false;
@@ -91,43 +92,33 @@
if (!error) {
console.log(servarr);
try {
const res = await axios.post(
`/arr/rad/${isEditing ? "edit" : "add"}`,
servarr,
);
if (res.status === 200) {
notify({
type: "success",
text: isEditing ? "Changes saved!" : "Server added successfully!",
});
onClose();
}
await req.post(`/arr/rad/${isEditing ? "edit" : "add"}`, servarr);
notify({
type: "success",
text: isEditing ? "Changes saved!" : "Server added successfully!",
});
onClose();
} catch (err: any) {
console.error("Failed to save server!", err);
error = `Failed to ${isEditing ? "edit" : "add"}`;
if (err?.response?.data?.error) {
error = err.response.data.error;
}
error = ReqerError.getMsg(
err,
`Failed to ${isEditing ? "edit" : "add"}`,
);
}
}
}
async function remove() {
try {
const res = await axios.post(`/arr/rad/rm/${servarr.name}`);
if (res.status === 200) {
notify({
type: "success",
text: "Removed server",
});
onClose();
}
await req.post(`/arr/rad/rm/${servarr.name}`);
notify({
type: "success",
text: "Removed server",
});
onClose();
} catch (err: any) {
console.error("Failed to remove server!", err);
error = `Failed to remove`;
if (err?.response?.data?.error) {
error = err.response.data.error;
}
error = ReqerError.getMsg(err, "Failed to remove");
}
}
run(() => {
@@ -12,7 +12,8 @@
SonarrSettings,
SonarrTestResponse,
} from "@/types";
import axios from "axios";
import { req } from "@/lib/util/api";
import { ReqerError } from "@/lib/util/fetch";
interface Props {
servarr: SonarrSettings;
@@ -44,17 +45,17 @@
async function getSettingsData() {
try {
formDisabled = true;
const res = await axios.post<SonarrTestResponse>("/arr/son/test", {
const res = await req.post<SonarrTestResponse>("/arr/son/test", {
host: servarr.host,
key: servarr.key,
});
qualityProfiles = res.data.qualityProfiles.map((d) => {
qualityProfiles = res.qualityProfiles.map((d) => {
return { id: d.id, value: d.name };
});
languageProfiles = res.data.languageProfiles.map((d) => {
languageProfiles = res.languageProfiles.map((d) => {
return { id: d.id, value: d.name };
});
rootFolders = res.data.rootFolders.map((d) => {
rootFolders = res.rootFolders.map((d) => {
return { id: d.id, value: d.path };
});
formDisabled = false;
@@ -99,43 +100,33 @@
if (!error) {
console.log(servarr);
try {
const res = await axios.post(
`/arr/son/${isEditing ? "edit" : "add"}`,
servarr,
);
if (res.status === 200) {
notify({
type: "success",
text: isEditing ? "Changes saved!" : "Server added successfully!",
});
onClose();
}
await req.post(`/arr/son/${isEditing ? "edit" : "add"}`, servarr);
notify({
type: "success",
text: isEditing ? "Changes saved!" : "Server added successfully!",
});
onClose();
} catch (err: any) {
console.error("Failed to save server!", err);
error = `Failed to ${isEditing ? "edit" : "add"}`;
if (err?.response?.data?.error) {
error = err.response.data.error;
}
error = ReqerError.getMsg(
err,
`Failed to ${isEditing ? "edit" : "add"}`,
);
}
}
}
async function remove() {
try {
const res = await axios.post(`/arr/son/rm/${servarr.name}`);
if (res.status === 200) {
notify({
type: "success",
text: "Removed server",
});
onClose();
}
await req.post(`/arr/son/rm/${servarr.name}`);
notify({
type: "success",
text: "Removed server",
});
onClose();
} catch (err: any) {
console.error("Failed to remove server!", err);
error = `Failed to remove`;
if (err?.response?.data?.error) {
error = err.response.data.error;
}
error = ReqerError.getMsg(err, "Failed to remove");
}
}
run(() => {
@@ -1,6 +1,4 @@
<script lang="ts">
import { run } from "svelte/legacy";
import Modal from "@/lib/Modal.svelte";
import Spinner from "@/lib/Spinner.svelte";
import Setting from "@/lib/settings/Setting.svelte";
@@ -8,7 +6,7 @@
import { toRelativeTime } from "@/lib/util/helpers";
import { notify } from "@/lib/util/notify";
import type { AllTasksResponse } from "@/types";
import axios from "axios";
import { req } from "@/lib/util/api";
import { onMount } from "svelte";
interface Props {
@@ -23,8 +21,8 @@
async function getAllTasks() {
try {
const res = await axios.get<AllTasksResponse[]>("/task/");
taskSchedule = res.data?.sort((a, b) => {
const res = await req.get<AllTasksResponse[]>("/task/");
taskSchedule = res?.sort((a, b) => {
if (a.name < b.name) {
return -1;
}
@@ -47,7 +45,7 @@
const nid = notify({ type: "loading", text: "Updating.." });
try {
formDisabled = true;
const res = await axios.put(`/task/${name}`, { seconds });
const res = await req.putWhole(`/task/${name}`, { seconds });
if (res.status === 200) {
notify({ id: nid, type: "success", text: "Schedule updated." });
getAllTasks();
@@ -5,9 +5,9 @@
import Spinner from "@/lib/Spinner.svelte";
import Setting from "@/lib/settings/Setting.svelte";
import SettingsList from "@/lib/settings/SettingsList.svelte";
import { req } from "@/lib/util/api";
import { notify } from "@/lib/util/notify";
import type { ServerConfigByName, TrustedHeaderAuthSetting } from "@/types";
import axios from "axios";
import { onMount } from "svelte";
interface Props {
@@ -29,13 +29,13 @@
async function getHeaderCfg() {
try {
loadingCfg = true;
const res = await axios.get<ServerConfigByName<TrustedHeaderAuthSetting>>(
const res = await req.get<ServerConfigByName<TrustedHeaderAuthSetting>>(
"/server/config",
{
params: { s: "HEADER_AUTH" },
},
);
headerCfg = res.data.value;
headerCfg = res.value;
loadingCfg = false;
} catch (err) {
console.error("getHeaderCfg failed!", err);
@@ -53,7 +53,7 @@
text: "Saving..",
});
try {
await axios.post("/server/config", headerCfg, {
await req.post("/server/config", headerCfg, {
params: { s: "HEADER_AUTH" },
});
notify({
@@ -2,9 +2,10 @@
import Modal from "@/lib/Modal.svelte";
import Setting from "@/lib/settings/Setting.svelte";
import SettingsList from "@/lib/settings/SettingsList.svelte";
import { req } from "@/lib/util/api";
import { ReqerError } from "@/lib/util/fetch";
import { notify } from "@/lib/util/notify";
import type { TwitchSettings } from "@/types";
import axios from "axios";
interface Props {
cfg?: TwitchSettings;
@@ -36,20 +37,15 @@
// if no error set from checkForm func, continue
if (!error) {
try {
const res = await axios.post(`/game/config`, cfg);
if (res.status === 200) {
notify({
type: "success",
text: "Changes saved!",
});
onClose();
}
await req.post(`/game/config`, cfg);
notify({
type: "success",
text: "Changes saved!",
});
onClose();
} catch (err: any) {
console.error("Failed to save twitch cfg!", err);
error = `Failed to save`;
if (err?.response?.data?.error) {
error = err.response.data.error;
}
error = ReqerError.getMsg(err, "Failed to save");
}
}
}
+10 -7
View File
@@ -8,13 +8,13 @@
import Spinner from "@/lib/Spinner.svelte";
import CreateTagModal from "@/lib/tag/CreateTagModal.svelte";
import Tag from "@/lib/tag/Tag.svelte";
import { req } from "@/lib/util/api";
import infScroll from "@/lib/util/infScroll";
import paginatedLoader, {
PaginatedLoaderRunFnAction,
} from "@/lib/util/paginatedLoader.svelte";
import { clearActiveFilters, store } from "@/store.svelte.js";
import type { Media } from "@/types";
import axios, { type GenericAbortSignal } from "axios";
import type { Media, PaginationResponse } from "@/types";
import { onDestroy, untrack } from "svelte";
let meta = $derived.by(() => {
@@ -36,7 +36,7 @@
...store.sortAndFiltersForQueryParams,
});
async function load(signal: GenericAbortSignal) {
async function load(signal: AbortSignal) {
console.debug("load: loadParams:", nextLoadParams);
if (nextLoadParams.page === dataLoader.state.page) {
console.warn("load: Already on this page, not loading it again!");
@@ -46,10 +46,13 @@
console.warn("load: Missing tag id!");
return;
}
const r = await axios.get(`/tag/${meta.tagId}/watched`, {
params: nextLoadParams,
signal,
});
const r = await req.get<PaginationResponse<Media, undefined>>(
`/tag/${meta.tagId}/watched`,
{
params: nextLoadParams,
signal,
},
);
scroll.dataLoaded();
return r;
}
+7 -9
View File
@@ -11,7 +11,7 @@
import Title from "@/lib/content/Title.svelte";
import {
contentExistsOnJellyfin,
removeWatched,
req,
updateWatched,
type UpdateWatchedOptions,
} from "@/lib/util/api";
@@ -23,7 +23,6 @@
TMDBContentCreditsCrew,
WatchedStatus,
} from "@/types";
import axios from "axios";
import RequestShow from "@/lib/request/RequestShow.svelte";
import FollowedThoughts from "@/lib/content/FollowedThoughts.svelte";
import ArrRequestButton from "@/lib/request/ArrRequestButton.svelte";
@@ -57,11 +56,9 @@
if (!data.tvId) {
return;
}
const resp = (
await axios.get(`/content/tv/${data.tvId}`, {
params: { region: store.userSettings?.country },
})
).data as Media;
const resp = await req.get<Media>(`/content/tv/${data.tvId}`, {
params: { region: store.userSettings?.country },
});
if (resp) {
if (resp.name && resp.ids.tmdb) {
contentExistsOnJellyfin("tv", resp.name, resp.ids.tmdb).then(
@@ -84,8 +81,9 @@
});
async function getTvCredits() {
const credits = (await axios.get(`/content/tv/${data.tvId}/credits`))
.data as TMDBContentCredits & { topCrew: TMDBContentCreditsCrew[] };
const credits = await req.get<
TMDBContentCredits & { topCrew: TMDBContentCreditsCrew[] }
>(`/content/tv/${data.tvId}/credits`);
if (credits.crew?.length > 0) {
credits.topCrew = getTopCrew(credits.crew);
}
+55 -47
View File
@@ -3,9 +3,10 @@
import { page } from "$app/state";
import Icon from "@/lib/Icon.svelte";
import { type AvailableAuthProviders } from "@/types";
import { noAuthAxios } from "@/lib/util/api";
import { noAuthReq } from "@/lib/util/api";
import { onMount } from "svelte";
import { notify, unNotify } from "@/lib/util/notify";
import { ReqerError } from "@/lib/util/fetch";
let error: string | undefined = $state();
let login = $state(true);
@@ -31,27 +32,46 @@
noAuto = true;
}
noAuthAxios.get<AvailableAuthProviders>("/auth/available").then((r) => {
if (r?.data) {
if (r.data.isInSetup) {
console.log("Server is in setup.. navigating to web setup page.");
goto("/setup");
}
availableProviders = r.data.available;
apHeader = availableProviders?.includes("header");
apPlex = availableProviders?.includes("plex");
signupEnabled = r.data.signupEnabled;
useEmby = r.data.useEmby;
if (r.data.headerAuthAutoLogin && !noAuto) {
console.log(
"handling headerAuthAutoLogin.. calling proxyLogin automatically now.",
);
proxyLogin(true);
}
}
});
processAvailableLoginMethods();
});
/**
* Get and process available login methods.
*/
async function processAvailableLoginMethods() {
let r: AvailableAuthProviders;
try {
r = await noAuthReq.get<AvailableAuthProviders>("/auth/available");
} catch {
notify({
type: "error",
time: 10000,
text: "Failed to get available login methods",
});
return;
}
if (!r) {
console.log("AvailableAuth: no response data");
}
if (r.isInSetup) {
console.log(
"AvailableAuth: Server is in setup.. navigating to web setup page.",
);
goto("/setup");
}
availableProviders = r.available;
apHeader = availableProviders?.includes("header");
apPlex = availableProviders?.includes("plex");
signupEnabled = r.signupEnabled;
useEmby = r.useEmby;
if (r.headerAuthAutoLogin && !noAuto) {
console.log(
"AvailableAuth: handling headerAuthAutoLogin.. calling proxyLogin automatically now.",
);
proxyLogin(true);
}
}
function handleLogin(ev: SubmitEvent) {
ev.preventDefault();
const fd = new FormData(ev.target! as HTMLFormElement);
@@ -69,15 +89,15 @@
}
const nid = notify({ text: "Logging in", type: "loading" });
noAuthAxios
noAuthReq
.post(`/auth${login ? `/${customAuthEP}` : "/register"}`, {
username: user,
password: pass,
})
.then((resp) => {
if (resp.data?.token) {
.then((resp: any) => {
if (resp?.token) {
console.log("Received token... logging in.");
localStorage.setItem("token", resp.data.token);
localStorage.setItem("token", resp.token);
if (useEmby) {
localStorage.setItem("useEmby", "1");
} else {
@@ -88,11 +108,7 @@
}
})
.catch((err) => {
if (err.response) {
error = err.response.data.error;
} else {
error = err.message;
}
error = ReqerError.getMsg(err, "Login failed");
unNotify(nid);
});
}
@@ -111,26 +127,22 @@
return;
}
const nid = notify({ text: "Logging in", type: "loading" });
noAuthAxios
noAuthReq
.post("/auth/plex", {
token,
clientIdentifier: p.clientId,
})
.then((resp) => {
if (resp.data?.token) {
.then((resp: any) => {
if (resp?.token) {
console.log("Received token... logging in.");
localStorage.setItem("token", resp.data.token);
localStorage.setItem("token", resp.token);
goto("/");
notify({ id: nid, text: `Welcome!`, type: "success" });
}
})
.catch((err) => {
console.error("plexLogin: Fail", err);
if (err.response) {
error = err.response.data.error;
} else {
error = err.message;
}
error = ReqerError.getMsg(err, "Login failed");
notify({ id: nid, text: `Failed!`, type: "error" });
});
});
@@ -142,22 +154,18 @@
function proxyLogin(auto = false) {
const nid = notify({ text: "Logging in", type: "loading" });
noAuthAxios
noAuthReq
.post(`/auth/proxy`)
.then((resp) => {
if (resp.data?.token) {
.then((resp: any) => {
if (resp?.token) {
console.log("Received token... logging in.");
localStorage.setItem("token", resp.data.token);
localStorage.setItem("token", resp.token);
goto("/");
notify({ id: nid, text: `Welcome!`, type: "success" });
}
})
.catch((err) => {
if (err.response) {
error = err.response.data.error;
} else {
error = err.message;
}
error = ReqerError.getMsg(err, "Login failed");
if (auto) {
notify({
id: nid,
+13 -17
View File
@@ -1,22 +1,21 @@
<script lang="ts">
import { preventDefault } from "svelte/legacy";
import { goto } from "$app/navigation";
import type { AvailableAuthProviders } from "@/types";
import { noAuthAxios } from "@/lib/util/api";
import { onMount } from "svelte";
import { notify, unNotify } from "@/lib/util/notify";
import { noAuthReq } from "@/lib/util/api";
import { ReqerError } from "@/lib/util/fetch";
let error: string = $state();
let error: string | undefined = $state();
onMount(() => {
if (localStorage.getItem("token")) {
goto("/");
}
noAuthAxios.get<AvailableAuthProviders>("/auth/available").then((r) => {
if (r?.data) {
if (!r?.data?.isInSetup) {
noAuthReq.get<AvailableAuthProviders>("/auth/available").then((r) => {
if (r) {
if (!r.isInSetup) {
console.log("Server not in setup.. navigating to login page.");
goto("/login");
}
@@ -25,6 +24,7 @@
});
function handleLogin(ev: SubmitEvent) {
ev.preventDefault();
const fd = new FormData(ev.target! as HTMLFormElement);
const user = fd.get("username");
const pass = fd.get("password");
@@ -35,25 +35,21 @@
}
const nid = notify({ text: "Setting Up Admin User", type: "loading" });
noAuthAxios
.post("/setup/create_admin", {
noAuthReq
.post<any>("/setup/create_admin", {
username: user,
password: pass,
})
.then((resp) => {
if (resp.data?.token) {
if (resp.token) {
console.log("Received token... logging in.");
localStorage.setItem("token", resp.data.token);
localStorage.setItem("token", resp.token);
goto("/");
notify({ id: nid, text: `Welcome ${user}!`, type: "success" });
}
})
.catch((err) => {
if (err.response) {
error = err.response.data.error;
} else {
error = err.message;
}
error = ReqerError.getMsg(err, "Setting up user failed");
unNotify(nid);
});
}
@@ -74,7 +70,7 @@
<span class="error">{error}!</span>
{/if}
<form onsubmit={preventDefault(handleLogin)}>
<form onsubmit={handleLogin}>
<label for="username">Username</label>
<input type="text" name="username" placeholder="Username" />
+1 -1
View File
@@ -131,7 +131,7 @@ export const store = {
/**
* Return our `activeSort` and `activeFilters` in an object
* that is in the correct format for our get watched page
* requests (object that is given to axios for query params).
* http requests.
*/
get sortAndFiltersForQueryParams() {
return _store.sortAndFiltersForQueryParams;