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 engine-strict=true
ignore-scripts=true
min-release-age=14
+14
View File
@@ -2,6 +2,20 @@
These changes are awaiting release: 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 # [4.1.1] - 2026-07-26T02:00:00Z
## Fixed ## Fixed
+62 -172
View File
@@ -9,29 +9,28 @@
"version": "4.1.1", "version": "4.1.1",
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"dependencies": { "dependencies": {
"axios": "^1.9.0", "blurhash": "2.0.5",
"blurhash": "^2.0.5", "papaparse": "5.4.1"
"papaparse": "^5.4.1"
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-node": "^5.2.12", "@sveltejs/adapter-node": "5.2.12",
"@sveltejs/kit": "^2.21.0", "@sveltejs/kit": "2.21.0",
"@types/papaparse": "^5.3.15", "@types/papaparse": "5.3.15",
"@typescript-eslint/eslint-plugin": "^8.32.1", "@typescript-eslint/eslint-plugin": "8.32.1",
"@typescript-eslint/parser": "^8.32.1", "@typescript-eslint/parser": "8.32.1",
"@vite-pwa/sveltekit": "^0.6.6", "@vite-pwa/sveltekit": "0.6.6",
"eslint": "^8.57.0", "eslint": "8.57.0",
"eslint-config-prettier": "^10.1.2", "eslint-config-prettier": "10.1.2",
"eslint-plugin-svelte": "^2.45.1", "eslint-plugin-svelte": "2.45.1",
"prettier": "^3.4.2", "prettier": "3.4.2",
"prettier-plugin-svelte": "^3.4.0", "prettier-plugin-svelte": "3.4.0",
"sass": "^1.97.3", "sass": "1.97.3",
"svelte": "^5.17.3", "svelte": "5.17.3",
"svelte-check": "^4.1.3", "svelte-check": "4.1.3",
"svelte-eslint-parser": "^0.42.0", "svelte-eslint-parser": "0.42.0",
"svelte-preprocess": "^6.0.3", "svelte-preprocess": "6.0.3",
"typescript": "^5.8.3", "typescript": "5.8.3",
"vite": "^6.3.5" "vite": "6.3.5"
} }
}, },
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
@@ -2112,22 +2111,24 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "8.57.1", "version": "8.57.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
"integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
} }
}, },
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.13.0", "version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
"integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
"deprecated": "Use @eslint/config-array instead", "deprecated": "Use @eslint/config-array instead",
"dev": true, "dev": true,
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"@humanwhocodes/object-schema": "^2.0.3", "@humanwhocodes/object-schema": "^2.0.2",
"debug": "^4.3.1", "debug": "^4.3.1",
"minimatch": "^3.0.5" "minimatch": "^3.0.5"
}, },
@@ -2136,20 +2137,22 @@
} }
}, },
"node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.16",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
} }
}, },
"node_modules/@humanwhocodes/config-array/node_modules/minimatch": { "node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true, "dev": true,
"license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
}, },
@@ -2175,7 +2178,8 @@
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
"integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
"deprecated": "Use @eslint/object-schema instead", "deprecated": "Use @eslint/object-schema instead",
"dev": true "dev": true,
"license": "BSD-3-Clause"
}, },
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8", "version": "0.3.8",
@@ -3332,10 +3336,11 @@
"dev": true "dev": true
}, },
"node_modules/@vite-pwa/sveltekit": { "node_modules/@vite-pwa/sveltekit": {
"version": "0.6.7", "version": "0.6.6",
"resolved": "https://registry.npmjs.org/@vite-pwa/sveltekit/-/sveltekit-0.6.7.tgz", "resolved": "https://registry.npmjs.org/@vite-pwa/sveltekit/-/sveltekit-0.6.6.tgz",
"integrity": "sha512-geeRgOiFr4RdW4JHIshOTsa8OqX4P+aJtudnKjuxzpmHy44bo4o18OWM1iXc4qXBJCxCw+3yTttyJsdmHXG/sA==", "integrity": "sha512-f08xlcfZyaXgytl05hUtCLih7Vr8zOk/E4eE/UK71M+sEGk+y+vUyTuR8td8KQalpf1Begb8XkxAkV7iUj2a0g==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"kolorist": "^1.8.0", "kolorist": "^1.8.0",
"tinyglobby": "^0.2.9" "tinyglobby": "^0.2.9"
@@ -3349,7 +3354,7 @@
"peerDependencies": { "peerDependencies": {
"@sveltejs/kit": "^1.3.1 || ^2.0.1", "@sveltejs/kit": "^1.3.1 || ^2.0.1",
"@vite-pwa/assets-generator": "^0.2.6", "@vite-pwa/assets-generator": "^0.2.6",
"vite-plugin-pwa": ">=0.21.1 <1" "vite-plugin-pwa": ">=0.20.5 <1"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@vite-pwa/assets-generator": { "@vite-pwa/assets-generator": {
@@ -3489,11 +3494,6 @@
"dev": true, "dev": true,
"peer": 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": { "node_modules/at-least-node": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "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" "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": { "node_modules/axobject-query": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -3802,17 +3791,6 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true "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": { "node_modules/commander": {
"version": "2.20.3", "version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
@@ -4030,14 +4008,6 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/detect-libc": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
@@ -4304,17 +4274,18 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "8.57.1", "version": "8.57.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
"integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1", "@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.4", "@eslint/eslintrc": "^2.1.4",
"@eslint/js": "8.57.1", "@eslint/js": "8.57.0",
"@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/config-array": "^0.11.14",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8", "@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0", "@ungap/structured-clone": "^1.2.0",
@@ -4388,22 +4359,23 @@
} }
}, },
"node_modules/eslint-plugin-svelte": { "node_modules/eslint-plugin-svelte": {
"version": "2.46.1", "version": "2.45.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.46.1.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.45.1.tgz",
"integrity": "sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==", "integrity": "sha512-mYAKNDRji0YWl7o00KQi0enREcrtzcN7xwK/8lwk5uLRoKLjzPXc+WjngsYpPV35I3AF7UlXc1+JfyNMJS+njA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@jridgewell/sourcemap-codec": "^1.4.15", "@jridgewell/sourcemap-codec": "^1.4.15",
"eslint-compat-utils": "^0.5.1", "eslint-compat-utils": "^0.5.1",
"esutils": "^2.0.3", "esutils": "^2.0.3",
"known-css-properties": "^0.35.0", "known-css-properties": "^0.34.0",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"postcss-load-config": "^3.1.4", "postcss-load-config": "^3.1.4",
"postcss-safe-parser": "^6.0.0", "postcss-safe-parser": "^6.0.0",
"postcss-selector-parser": "^6.1.0", "postcss-selector-parser": "^6.1.0",
"semver": "^7.6.2", "semver": "^7.6.2",
"svelte-eslint-parser": "^0.43.0" "svelte-eslint-parser": "^0.42.0"
}, },
"engines": { "engines": {
"node": "^14.17.0 || >=16.0.0" "node": "^14.17.0 || >=16.0.0"
@@ -4413,34 +4385,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0", "eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0",
"svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.191"
},
"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"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"svelte": { "svelte": {
@@ -4738,25 +4683,6 @@
"integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
"dev": true "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": { "node_modules/for-each": {
"version": "0.3.3", "version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
@@ -4767,19 +4693,6 @@
"is-callable": "^1.1.3" "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": { "node_modules/fs-extra": {
"version": "9.1.0", "version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
@@ -5809,10 +5722,11 @@
} }
}, },
"node_modules/known-css-properties": { "node_modules/known-css-properties": {
"version": "0.35.0", "version": "0.34.0",
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.35.0.tgz", "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.34.0.tgz",
"integrity": "sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==", "integrity": "sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==",
"dev": true "dev": true,
"license": "MIT"
}, },
"node_modules/kolorist": { "node_modules/kolorist": {
"version": "1.8.0", "version": "1.8.0",
@@ -5963,25 +5877,6 @@
"url": "https://github.com/sponsors/jonschlinkert" "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": { "node_modules/minimatch": {
"version": "9.0.5", "version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@@ -6425,11 +6320,6 @@
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+20 -21
View File
@@ -14,29 +14,28 @@
"format": "prettier --write ." "format": "prettier --write ."
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-node": "^5.2.12", "@sveltejs/adapter-node": "5.2.12",
"@sveltejs/kit": "^2.21.0", "@sveltejs/kit": "2.21.0",
"@types/papaparse": "^5.3.15", "@types/papaparse": "5.3.15",
"@typescript-eslint/eslint-plugin": "^8.32.1", "@typescript-eslint/eslint-plugin": "8.32.1",
"@typescript-eslint/parser": "^8.32.1", "@typescript-eslint/parser": "8.32.1",
"@vite-pwa/sveltekit": "^0.6.6", "@vite-pwa/sveltekit": "0.6.6",
"eslint": "^8.57.0", "eslint": "8.57.0",
"eslint-config-prettier": "^10.1.2", "eslint-config-prettier": "10.1.2",
"eslint-plugin-svelte": "^2.45.1", "eslint-plugin-svelte": "2.45.1",
"prettier": "^3.4.2", "prettier": "3.4.2",
"prettier-plugin-svelte": "^3.4.0", "prettier-plugin-svelte": "3.4.0",
"sass": "^1.97.3", "sass": "1.97.3",
"svelte": "^5.17.3", "svelte": "5.17.3",
"svelte-check": "^4.1.3", "svelte-check": "4.1.3",
"svelte-eslint-parser": "^0.42.0", "svelte-eslint-parser": "0.42.0",
"svelte-preprocess": "^6.0.3", "svelte-preprocess": "6.0.3",
"typescript": "^5.8.3", "typescript": "5.8.3",
"vite": "^6.3.5" "vite": "6.3.5"
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"axios": "^1.9.0", "blurhash": "2.0.5",
"blurhash": "^2.0.5", "papaparse": "5.4.1"
"papaparse": "^5.4.1"
} }
} }
+10 -39
View File
@@ -3,14 +3,10 @@
import Modal from "@/lib/Modal.svelte"; import Modal from "@/lib/Modal.svelte";
import Spinner from "@/lib/Spinner.svelte"; import Spinner from "@/lib/Spinner.svelte";
import { notify } from "@/lib/util/notify"; import { notify } from "@/lib/util/notify";
import { import { JobStatus, type GetJobResponse } from "@/types";
JobStatus,
type GetJobResponse,
type JobCreatedResponse,
} from "@/types";
import axios from "axios";
import { onDestroy, onMount } from "svelte"; import { onDestroy, onMount } from "svelte";
import { store } from "@/store.svelte"; import { req } from "./util/api";
import { ReqerError } from "./util/fetch";
interface Props { interface Props {
modalTitle: string; modalTitle: string;
@@ -46,7 +42,7 @@
} catch (err: any) { } catch (err: any) {
console.error("startSync failed!", err); console.error("startSync failed!", err);
step = "errored"; 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; let seqfailedJobReqs = 0;
while (step === "job-running") { while (step === "job-running") {
try { try {
const r = await axios.get<GetJobResponse>(`/job/${jobId}`); const r = await req.get<GetJobResponse>(`/job/${jobId}`);
console.log("jobWatcher: Got job data:", r.data); console.log("jobWatcher: Got job data:", r);
latestJobStatus = r.data; latestJobStatus = r;
currentTask = r.data?.currentTask; currentTask = r?.currentTask;
if (r.data?.status === JobStatus.DONE) { if (r?.status === JobStatus.DONE) {
step = "done"; step = "done";
} else if (r.data?.status === JobStatus.CANCELLED) { } else if (r?.status === JobStatus.CANCELLED) {
step = "errored"; step = "errored";
} }
// If we get here without erroring, we can reset it to 0. // If we get here without erroring, we can reset it to 0.
@@ -94,31 +90,6 @@
} }
await new Promise((r) => setTimeout(r, 1000)); 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() { function modalClose() {
+3 -3
View File
@@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import axios from "axios";
import DropDown from "./DropDown.svelte"; import DropDown from "./DropDown.svelte";
import type { DropDownItem, TMDBRegions } from "@/types"; import type { DropDownItem, TMDBRegions } from "@/types";
import Error from "./Error.svelte"; import Error from "./Error.svelte";
import { req } from "./util/api";
interface Props { interface Props {
selectedCountry?: string; selectedCountry?: string;
@@ -16,10 +16,10 @@
onChange, onChange,
}: Props = $props(); }: Props = $props();
let mappedCountries: DropDownItem[] = $state(); let mappedCountries: DropDownItem[] = $state([]);
async function getCountries() { async function getCountries() {
const c = (await axios.get(`/content/regions`)).data as TMDBRegions; const c = await req.get<TMDBRegions>(`/content/regions`);
mappedCountries = c.results mappedCountries = c.results
.map((cc) => { .map((cc) => {
return { return {
+4 -6
View File
@@ -2,11 +2,11 @@
import type { ContentType, PublicUser, WatchedStatus } from "@/types"; import type { ContentType, PublicUser, WatchedStatus } from "@/types";
import HorizontalList from "../HorizontalList.svelte"; import HorizontalList from "../HorizontalList.svelte";
import Modal from "../Modal.svelte"; import Modal from "../Modal.svelte";
import axios from "axios";
import Spinner from "../Spinner.svelte"; import Spinner from "../Spinner.svelte";
import Error from "../Error.svelte"; import Error from "../Error.svelte";
import Icon from "../Icon.svelte"; import Icon from "../Icon.svelte";
import { watchedStatuses } from "../util/helpers"; import { watchedStatuses } from "../util/helpers";
import { req } from "../util/api";
interface FollowThoughts { interface FollowThoughts {
followedUser: PublicUser; followedUser: PublicUser;
@@ -26,11 +26,9 @@
let modalShownFor: FollowThoughts | undefined = $state(undefined); let modalShownFor: FollowThoughts | undefined = $state(undefined);
async function getFollowsThoughts() { async function getFollowsThoughts() {
return ( return await req.get<FollowThoughts[]>(
await axios.get<FollowThoughts[]>( `/follow/thoughts/${mediaType}/${mediaId}`,
`/follow/thoughts/${mediaType}/${mediaId}`, );
)
).data;
} }
</script> </script>
+3 -3
View File
@@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
import Modal from "../Modal.svelte"; import Modal from "../Modal.svelte";
import axios from "axios";
import type { TrustedHeaderAuthLogoutDetailsResponse } from "@/types"; import type { TrustedHeaderAuthLogoutDetailsResponse } from "@/types";
import Spinner from "../Spinner.svelte"; import Spinner from "../Spinner.svelte";
import { clearWatcharrData } from "."; import { clearWatcharrData } from ".";
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import { req } from "../util/api";
interface Props { interface Props {
onClose: () => void; onClose: () => void;
@@ -18,10 +18,10 @@
onMount(async () => { onMount(async () => {
try { try {
const r = await axios.get<TrustedHeaderAuthLogoutDetailsResponse>( const r = await req.get<TrustedHeaderAuthLogoutDetailsResponse>(
"/auth/proxy_logout_details", "/auth/proxy_logout_details",
); );
logoutUrl = r?.data?.logoutUrl; logoutUrl = r?.logoutUrl;
if (!logoutUrl?.toLowerCase()?.startsWith("http")) { if (!logoutUrl?.toLowerCase()?.startsWith("http")) {
// If no protocol in logoutUrl, set https // If no protocol in logoutUrl, set https
logoutUrl = `https://${logoutUrl}`; logoutUrl = `https://${logoutUrl}`;
+17 -15
View File
@@ -6,12 +6,13 @@
ArrRequestStatus, ArrRequestStatus,
ContentType, ContentType,
} from "@/types"; } from "@/types";
import axios from "axios";
import { onMount } from "svelte"; import { onMount } from "svelte";
import Icon from "../Icon.svelte"; import Icon from "../Icon.svelte";
import tooltip from "../actions/tooltip"; import tooltip from "../actions/tooltip";
import { msToAmountsOfTime } from "../util/helpers"; import { msToAmountsOfTime } from "../util/helpers";
import { notify } from "../util/notify"; import { notify } from "../util/notify";
import { req } from "../util/api";
import { ReqerError } from "../util/fetch";
interface Props { interface Props {
type: ContentType; type: ContentType;
@@ -48,11 +49,11 @@
); );
return; return;
} }
const resp = await axios.get<ArrInfoResponse>( const resp = await req.get<ArrInfoResponse>(
`/arr/${type === "movie" ? "rad" : "son"}/info/${existingRequest.id}`, `/arr/${type === "movie" ? "rad" : "son"}/info/${existingRequest.id}`,
); );
if (resp?.data) { if (resp) {
info = resp.data; info = resp;
if (info.hasFile) { if (info.hasFile) {
status = "available"; status = "available";
} else { } else {
@@ -61,8 +62,9 @@
} }
} catch (err: any) { } catch (err: any) {
if ( if (
err?.response?.status === 404 && ReqerError.withBody(err) &&
err?.response?.data?.error === "request deleted" err.response?.status === 404 &&
err.body.error === "request deleted"
) { ) {
return; return;
} }
@@ -80,13 +82,13 @@
console.warn("getStatus called before existingRequest exists."); console.warn("getStatus called before existingRequest exists.");
return; return;
} }
const statusResp = await axios.get<ArrDetailsResponse>( const statusResp = await req.getWhole<ArrDetailsResponse>(
`/arr/${type === "movie" ? "rad" : "son"}/status/${existingRequest.serverName}/${existingRequest.arrId}`, `/arr/${type === "movie" ? "rad" : "son"}/status/${existingRequest.serverName}/${existingRequest.arrId}`,
); );
if (statusResp.status === 204) { if (statusResp.status === 204) {
status = "requested"; status = "requested";
} else if (statusResp?.data) { } else if (statusResp.body) {
status = statusResp.data; status = statusResp.body;
const estMs = const estMs =
new Date(status.estimatedCompletionTime).getTime() - new Date(status.estimatedCompletionTime).getTime() -
new Date(Date.now()).getTime(); new Date(Date.now()).getTime();
@@ -119,19 +121,19 @@
async function lookForExisting() { async function lookForExisting() {
try { try {
const existingRequestResp = await axios.get<ArrRequestResponse>( const existingRequestResp = await req.get<ArrRequestResponse>(
`/arr/${type === "movie" ? "rad" : "son"}/request/${tmdbId}`, `/arr/${type === "movie" ? "rad" : "son"}/request/${tmdbId}`,
); );
if (existingRequestResp?.data && existingRequestResp?.data?.arrId) { if (existingRequestResp && existingRequestResp.arrId) {
existingRequest = existingRequestResp?.data; existingRequest = existingRequestResp;
getInfo(); getInfo();
} else if (existingRequestResp?.data) { } else if (existingRequestResp) {
// If no arrId, use status in request (pending, denied, etc) // If no arrId, use status in request (pending, denied, etc)
console.log( console.log(
"No arrId in request resp.. using request status for btn status if set.", "No arrId in request resp.. using request status for btn status if set.",
); );
if (existingRequestResp?.data?.status) { if (existingRequestResp.status) {
status = existingRequestResp?.data?.status; status = existingRequestResp.status;
} }
} }
} catch (err) { } catch (err) {
+9 -9
View File
@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import axios from "axios";
import Modal from "../Modal.svelte"; import Modal from "../Modal.svelte";
import type { import type {
ArrRequestResponse, ArrRequestResponse,
@@ -11,6 +10,7 @@
import DropDown from "../DropDown.svelte"; import DropDown from "../DropDown.svelte";
import Setting from "../settings/Setting.svelte"; import Setting from "../settings/Setting.svelte";
import Spinner from "../Spinner.svelte"; import Spinner from "../Spinner.svelte";
import { req } from "../util/api";
interface Props { interface Props {
content: Media; content: Media;
@@ -35,9 +35,9 @@
async function getServers() { async function getServers() {
try { try {
inputsDisabled = true; inputsDisabled = true;
const r = await axios.get("/arr/rad"); const r = await req.get<RadarrSettingsPublicResponseResult[]>("/arr/rad");
if (r.data?.length > 0) { if (r?.length > 0) {
servarrs = r.data; servarrs = r;
selectedServarrIndex = 0; selectedServarrIndex = 0;
} else { } else {
notify({ text: "No servers found", type: "error" }); notify({ text: "No servers found", type: "error" });
@@ -53,8 +53,8 @@
async function getConfig(name: string) { async function getConfig(name: string) {
try { try {
inputsDisabled = true; inputsDisabled = true;
const r = await axios.get<RadarrTestResponse>(`/arr/rad/config/${name}`); const r = await req.get<RadarrTestResponse>(`/arr/rad/config/${name}`);
selectedServerCfg = r.data; selectedServerCfg = r;
inputsDisabled = false; inputsDisabled = false;
} catch (err) { } catch (err) {
console.error("Failed to get config!", err); console.error("Failed to get config!", err);
@@ -89,7 +89,7 @@
notify({ id: nid, text: "No Root Folder Found", type: "error" }); notify({ id: nid, text: "No Root Folder Found", type: "error" });
return; return;
} }
const resp = await axios.post<ArrRequestResponse>( const resp = await req.post<ArrRequestResponse>(
`/arr/rad/request${approveMode && originalRequest ? `/approve/${originalRequest.id}` : ""}`, `/arr/rad/request${approveMode && originalRequest ? `/approve/${originalRequest.id}` : ""}`,
{ {
serverName: server.name, serverName: server.name,
@@ -103,9 +103,9 @@
}, },
); );
addRequestRunning = false; addRequestRunning = false;
if (resp.data) { if (resp) {
notify({ id: nid, text: "Request complete", type: "success" }); notify({ id: nid, text: "Request complete", type: "success" });
onClose(resp.data); onClose(resp);
} }
} catch (err) { } catch (err) {
console.error("content request failed!", err); console.error("content request failed!", err);
+9 -9
View File
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import axios from "axios"; import { req } from "../util/api";
import Modal from "../Modal.svelte"; import Modal from "../Modal.svelte";
import type { import type {
ArrRequestResponse, ArrRequestResponse,
@@ -51,9 +51,9 @@
async function getServers() { async function getServers() {
try { try {
inputsDisabled = true; inputsDisabled = true;
const r = await axios.get("/arr/son"); const r = await req.get<SonarrSettingsPublicResponseResult[]>("/arr/son");
if (r.data?.length > 0) { if (r.length > 0) {
servarrs = r.data; servarrs = r;
selectedServarrIndex = 0; selectedServarrIndex = 0;
} else { } else {
notify({ text: "No servers found", type: "error" }); notify({ text: "No servers found", type: "error" });
@@ -69,8 +69,8 @@
async function getConfig(name: string) { async function getConfig(name: string) {
try { try {
inputsDisabled = true; inputsDisabled = true;
const r = await axios.get<SonarrTestResponse>(`/arr/son/config/${name}`); const r = await req.get<SonarrTestResponse>(`/arr/son/config/${name}`);
selectedServerCfg = r.data; selectedServerCfg = r;
inputsDisabled = false; inputsDisabled = false;
} catch (err) { } catch (err) {
console.error("Failed to get config!", err); console.error("Failed to get config!", err);
@@ -105,7 +105,7 @@
notify({ id: nid, text: "No Root Folder Found", type: "error" }); notify({ id: nid, text: "No Root Folder Found", type: "error" });
return; return;
} }
const resp = await axios.post( const resp = await req.post<ArrRequestResponse>(
`/arr/son/request${approveMode && originalRequest ? `/approve/${originalRequest.id}` : ""}`, `/arr/son/request${approveMode && originalRequest ? `/approve/${originalRequest.id}` : ""}`,
{ {
serverName: server.name, serverName: server.name,
@@ -128,9 +128,9 @@
}, },
); );
addRequestRunning = false; addRequestRunning = false;
if (resp?.data) { if (resp) {
notify({ id: nid, text: "Request complete", type: "success" }); notify({ id: nid, text: "Request complete", type: "success" });
onClose(resp.data); onClose(resp);
} }
} catch (err) { } catch (err) {
console.error("content request failed!", err); console.error("content request failed!", err);
+10 -7
View File
@@ -6,7 +6,7 @@
WatchedSeason, WatchedSeason,
WatchedStatus, WatchedStatus,
} from "@/types"; } from "@/types";
import axios from "axios"; import { req } from "../util/api";
import Spinner from "@/lib/Spinner.svelte"; import Spinner from "@/lib/Spinner.svelte";
import Error from "@/lib/Error.svelte"; import Error from "@/lib/Error.svelte";
import SeasonsListEpisode from "./SeasonsListEpisode.svelte"; import SeasonsListEpisode from "./SeasonsListEpisode.svelte";
@@ -45,15 +45,18 @@
async function sdr(seasonNum: number) { async function sdr(seasonNum: number) {
const wid = watchedItem?.id; const wid = watchedItem?.id;
console.debug("SeasonList: sdr: Called.", tvId, seasonNum, wid); console.debug("SeasonList: sdr: Called.", tvId, seasonNum, wid);
const resp = await axios.get(`/content/tv/${tvId}/season/${seasonNum}`, { const resp = await req.getWhole<TMDBSeasonDetails>(
params: { `/content/tv/${tvId}/season/${seasonNum}`,
watchedId: wid, {
params: {
watchedId: wid,
},
}, },
}); );
try { try {
if (wid) { if (wid) {
// If we sent a watched id, expect a 'watcharr-lastviewedseason-saved' header in the response. // 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) { if (hVal) {
lastViewedSeasonChanged(wid, seasonNum); lastViewedSeasonChanged(wid, seasonNum);
} else { } else {
@@ -72,7 +75,7 @@
err, err,
); );
} }
return resp.data as TMDBSeasonDetails; return resp.body;
} }
function handleStatusClick( 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 { notify } from "../util/notify";
import type { import type {
Activity,
Watched, Watched,
WatchedEpisodeAddResponse, WatchedEpisodeAddResponse,
WatchedSeasonAddResponse, WatchedSeasonAddResponse,
@@ -28,17 +29,17 @@ export async function updateWatchedSeason(
} }
const nid = notify({ text: `Saving`, type: "loading" }); const nid = notify({ text: `Saving`, type: "loading" });
try { try {
const r = await axios.post<WatchedSeasonAddResponse>(`/watched/season`, { const r = await req.post<WatchedSeasonAddResponse>(`/watched/season`, {
watchedId: watchedItem.id, watchedId: watchedItem.id,
seasonNumber: seasonNumber, seasonNumber: seasonNumber,
status: opts.status, status: opts.status,
rating: opts.rating, rating: opts.rating,
}); });
watchedItem.watchedSeasons = r.data.watchedSeasons; watchedItem.watchedSeasons = r.watchedSeasons;
if (watchedItem.activity && watchedItem.activity?.length > 0) { if (watchedItem.activity && watchedItem.activity?.length > 0) {
watchedItem.activity.push(r.data.addedActivity); watchedItem.activity.push(r.addedActivity);
} else { } else {
watchedItem.activity = [r.data.addedActivity]; watchedItem.activity = [r.addedActivity];
} }
notify({ id: nid, text: `Saved!`, type: "success" }); notify({ id: nid, text: `Saved!`, type: "success" });
} catch (err) { } catch (err) {
@@ -50,15 +51,15 @@ export async function updateWatchedSeason(
export async function removeWatchedSeason(watchedItem: Watched, id: number) { export async function removeWatchedSeason(watchedItem: Watched, id: number) {
const nid = notify({ text: `Removing`, type: "loading" }); const nid = notify({ text: `Removing`, type: "loading" });
try { try {
const r = await axios.delete(`/watched/season/${id}`); const r = await req.delete<Activity>(`/watched/season/${id}`);
watchedItem.watchedSeasons = watchedItem.watchedSeasons?.filter( watchedItem.watchedSeasons = watchedItem.watchedSeasons?.filter(
(s) => s.id !== id, (s) => s.id !== id,
); );
if (r.data) { if (r) {
if (watchedItem.activity && watchedItem.activity?.length > 0) { if (watchedItem.activity && watchedItem.activity?.length > 0) {
watchedItem.activity.push(r.data); watchedItem.activity.push(r);
} else { } else {
watchedItem.activity = [r.data]; watchedItem.activity = [r];
} }
} }
notify({ id: nid, text: `Removed!`, type: "success" }); notify({ id: nid, text: `Removed!`, type: "success" });
@@ -80,21 +81,21 @@ export async function updateWatchedEpisode(
} }
const nid = notify({ text: `Saving`, type: "loading" }); const nid = notify({ text: `Saving`, type: "loading" });
try { try {
const r = await axios.post<WatchedEpisodeAddResponse>(`/watched/episode`, { const r = await req.post<WatchedEpisodeAddResponse>(`/watched/episode`, {
watchedId: watchedItem.id, watchedId: watchedItem.id,
seasonNumber, seasonNumber,
episodeNumber, episodeNumber,
status: opts.status, status: opts.status,
rating: opts.rating, rating: opts.rating,
}); });
watchedItem.watchedEpisodes = r.data.watchedEpisodes; watchedItem.watchedEpisodes = r.watchedEpisodes;
if (watchedItem.activity && watchedItem.activity?.length > 0) { if (watchedItem.activity && watchedItem.activity?.length > 0) {
watchedItem.activity.push(r.data.addedActivity); watchedItem.activity.push(r.addedActivity);
} else { } else {
watchedItem.activity = [r.data.addedActivity]; watchedItem.activity = [r.addedActivity];
} }
try { try {
const epHookResp = r?.data?.episodeStatusChangedHookResponse; const epHookResp = r?.episodeStatusChangedHookResponse;
if (epHookResp && Object.keys(epHookResp).length > 0) { if (epHookResp && Object.keys(epHookResp).length > 0) {
if (epHookResp.errors && epHookResp.errors.length > 0) { if (epHookResp.errors && epHookResp.errors.length > 0) {
console.error( console.error(
@@ -148,15 +149,15 @@ export async function updateWatchedEpisode(
export async function removeWatchedEpisode(watchedItem: Watched, id: number) { export async function removeWatchedEpisode(watchedItem: Watched, id: number) {
const nid = notify({ text: `Removing`, type: "loading" }); const nid = notify({ text: `Removing`, type: "loading" });
try { try {
const r = await axios.delete(`/watched/episode/${id}`); const r = await req.delete<Activity>(`/watched/episode/${id}`);
watchedItem.watchedEpisodes = watchedItem.watchedEpisodes?.filter( watchedItem.watchedEpisodes = watchedItem.watchedEpisodes?.filter(
(s) => s.id !== id, (s) => s.id !== id,
); );
if (r.data) { if (r.data) {
if (watchedItem.activity && watchedItem.activity?.length > 0) { if (watchedItem.activity && watchedItem.activity?.length > 0) {
watchedItem.activity.push(r.data); watchedItem.activity.push(r);
} else { } else {
watchedItem.activity = [r.data]; watchedItem.activity = [r];
} }
} }
notify({ id: nid, text: `Removed!`, type: "success" }); notify({ id: nid, text: `Removed!`, type: "success" });
+6 -10
View File
@@ -5,9 +5,8 @@
import SettingsList from "../settings/SettingsList.svelte"; import SettingsList from "../settings/SettingsList.svelte";
import ColorSelector from "../ColorSelector.svelte"; import ColorSelector from "../ColorSelector.svelte";
import { notify } from "../util/notify"; import { notify } from "../util/notify";
import axios from "axios"; import { req } from "../util/api";
import type { Tag, TagAddRequest } from "@/types"; import type { Tag, TagAddRequest } from "@/types";
import { get } from "svelte/store";
import { onMount } from "svelte"; import { onMount } from "svelte";
interface Props { interface Props {
@@ -54,15 +53,13 @@
} }
const nid = notify({ text: "Creating Tag", type: "loading" }); const nid = notify({ text: "Creating Tag", type: "loading" });
try { try {
const resp = await axios.post<Tag>("/tag", { const resp = await req.post<Tag>("/tag", {
name: tagName, name: tagName,
color: textColor, color: textColor,
bgColor, bgColor,
} as TagAddRequest); } as TagAddRequest);
console.log("addTag: Tag was created", resp.data); console.log("addTag: Tag was created", resp);
// const _tags = get(tags); store.tags.push(resp);
store.tags.push(resp.data);
// tags.update((t) => t);
notify({ id: nid, text: "Tag Created!", type: "success" }); notify({ id: nid, text: "Tag Created!", type: "success" });
onClose(); onClose();
} catch (err) { } catch (err) {
@@ -80,17 +77,16 @@
} }
const nid = notify({ text: "Modifying Tag", type: "loading" }); const nid = notify({ text: "Modifying Tag", type: "loading" });
try { try {
const resp = await axios.put<Tag>(`/tag/${existingTag!.id}`, { const resp = await req.put<Tag>(`/tag/${existingTag!.id}`, {
name: tagName, name: tagName,
color: textColor, color: textColor,
bgColor, bgColor,
} as TagAddRequest); } as TagAddRequest);
console.log("updateTag: Tag was edited", resp.data); console.log("updateTag: Tag was edited", resp);
existingTag!.name = tagName; existingTag!.name = tagName;
existingTag!.color = textColor; existingTag!.color = textColor;
existingTag!.bgColor = bgColor; existingTag!.bgColor = bgColor;
// Doesn't update `updatedAt`... may need to in the future if we need to sort by it, etc // 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" }); notify({ id: nid, text: "Tag Modified!", type: "success" });
onClose(); onClose();
} catch (err) { } catch (err) {
+7 -28
View File
@@ -1,15 +1,11 @@
<script lang="ts"> <script lang="ts">
import { store } from "@/store.svelte"; import { store } from "@/store.svelte";
import Modal from "../Modal.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 { notify } from "../util/notify";
import axios from "axios"; import type { Tag as TagT } from "@/types";
import type { Tag as TagT, TagAddRequest } from "@/types";
import { get } from "svelte/store";
import Tag from "./Tag.svelte"; import Tag from "./Tag.svelte";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { req } from "../util/api";
interface Props { interface Props {
tag: TagT; tag: TagT;
@@ -30,28 +26,10 @@
deleteDisabled = true; deleteDisabled = true;
const nid = notify({ text: "Deleting Tag", type: "loading" }); const nid = notify({ text: "Deleting Tag", type: "loading" });
try { try {
const resp = await axios.delete(`/tag/${tag.id}`); const resp = await req.delete(`/tag/${tag.id}`);
console.log("deleteTag: Tag was deleted", resp.data); console.log("deleteTag: Tag was deleted", resp);
// 1. Remove tag from store. // Remove tag from store.
// const _tags = get(tags);
// store.tags.update((t) => t);
store.tags = store.tags.filter((t) => t.id !== tag.id); 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" }); notify({ id: nid, text: "Tag Deleted!", type: "success" });
onClose(); onClose();
} catch (err) { } catch (err) {
@@ -63,7 +41,8 @@
} }
onMount(() => { 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; deleteDisabled = true;
setTimeout(() => { setTimeout(() => {
deleteDisabled = false; 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 { notify } from "../util/notify";
import type { Tag } from "@/types"; import type { Tag } from "@/types";
@@ -15,10 +15,10 @@ export async function tagWatched(
}); });
return false; return false;
} }
return await axios return await req
.post(`/watched/${watchedId}/tag/${tag.id}`) .post(`/watched/${watchedId}/tag/${tag.id}`)
.then((resp) => { .then((resp) => {
console.log("tagWatched: Status:", resp.status); console.log("tagWatched: Success.", resp);
notify({ id: nid, text: `Tagged!`, type: "success" }); notify({ id: nid, text: `Tagged!`, type: "success" });
return true; return true;
}) })
@@ -42,10 +42,10 @@ export async function untagWatched(
}); });
return false; return false;
} }
return await axios return await req
.delete(`/watched/${watchedId}/tag/${tag.id}`) .delete(`/watched/${watchedId}/tag/${tag.id}`)
.then((resp) => { .then((resp) => {
console.log("untagWatched: Status:", resp.status); console.log("untagWatched: Success.", resp);
notify({ id: nid, text: `Untagged!`, type: "success" }); notify({ id: nid, text: `Untagged!`, type: "success" });
return true; return true;
}) })
+44 -51
View File
@@ -13,8 +13,9 @@ import {
type ActivityUpdateRequest, type ActivityUpdateRequest,
type Activity, type Activity,
type SupportedMedia, type SupportedMedia,
type ServerFeatures,
} from "@/types"; } from "@/types";
import axios from "axios"; import { Reqer, ReqerError } from "./fetch";
import { notify, unNotify } from "./notify"; import { notify, unNotify } from "./notify";
import { browser } from "$app/environment"; import { browser } from "$app/environment";
const { MODE } = import.meta.env; const { MODE } = import.meta.env;
@@ -27,6 +28,9 @@ export const baseURL =
: "/api"; : "/api";
console.log("api: baseURL constructed:", baseURL); 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. * Options for our internal updateWatched func.
*/ */
@@ -71,7 +75,7 @@ async function _updateWatched(
if (typeof letCountAsPlay !== "undefined") { if (typeof letCountAsPlay !== "undefined") {
obj.letCountAsPlay = letCountAsPlay; obj.letCountAsPlay = letCountAsPlay;
} }
const resp = await axios.put<WatchedUpdateResponse>( const resp = await req.put<WatchedUpdateResponse>(
`/watched/${wEntry.id}`, `/watched/${wEntry.id}`,
obj, obj,
); );
@@ -79,14 +83,14 @@ async function _updateWatched(
if (rating) wEntry.rating = rating; if (rating) wEntry.rating = rating;
if (typeof thoughts !== "undefined") wEntry.thoughts = thoughts; if (typeof thoughts !== "undefined") wEntry.thoughts = thoughts;
if (typeof pinned !== "undefined") wEntry.pinned = pinned; 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) { if (wEntry.activity && wEntry.activity.length > 0) {
wEntry.activity.push(resp.data.newActivity); wEntry.activity.push(resp.newActivity);
} else { } else {
wEntry.activity = [resp.data.newActivity]; wEntry.activity = [resp.newActivity];
} }
// If new activity counts as play, increment plays for local state. // If new activity counts as play, increment plays for local state.
if (resp.data.newActivity.countAsPlay) { if (resp.newActivity.countAsPlay) {
if (wEntry.plays) { if (wEntry.plays) {
wEntry.plays++; wEntry.plays++;
} else { } else {
@@ -133,22 +137,22 @@ export async function updateWatched(
// Add new watched item // Add new watched item
notify({ id: nid, text: `Adding`, type: "loading" }); notify({ id: nid, text: `Adding`, type: "loading" });
let req: WatchedAddRequest = { let reqBody: WatchedAddRequest = {
contentType: opts.contentType, contentType: opts.contentType,
status: opts.status, status: opts.status,
rating: opts.rating, rating: opts.rating,
}; };
if (opts.contentType === "movie" || opts.contentType === "tv") { if (opts.contentType === "movie" || opts.contentType === "tv") {
req.tmdbId = opts.contentId; reqBody.tmdbId = opts.contentId;
} else if (opts.contentType === "game") { } else if (opts.contentType === "game") {
req.igdbId = opts.contentId; reqBody.igdbId = opts.contentId;
} else { } else {
throw "invalid contentType"; throw "invalid contentType";
} }
const resp = await axios.post<Watched>("/watched", req); const resp = await req.post<Watched>("/watched", reqBody);
console.log("Added watched:", resp.data); console.log("Added watched:", resp);
notify({ id: nid, text: `Added!`, type: "success" }); notify({ id: nid, text: `Added!`, type: "success" });
return resp.data; return resp;
} catch (err) { } catch (err) {
console.error("updateWatched: Failed!", err); console.error("updateWatched: Failed!", err);
notify({ id: nid, text: `Failed!`, type: "error" }); notify({ id: nid, text: `Failed!`, type: "error" });
@@ -165,8 +169,8 @@ export async function removeWatched(id: number): Promise<boolean> {
console.log("removeWatched: Removing:", id); console.log("removeWatched: Removing:", id);
const nid = notify({ text: "Removing", type: "loading" }); const nid = notify({ text: "Removing", type: "loading" });
try { try {
const resp = await axios.delete(`/watched/${id}`); const resp = await req.delete(`/watched/${id}`);
console.log("removeWatched: Removed resp:", resp.data); console.log("removeWatched: Removed resp:", resp);
notify({ id: nid, text: "Removed!", type: "success" }); notify({ id: nid, text: "Removed!", type: "success" });
return true; return true;
} catch (err) { } catch (err) {
@@ -183,7 +187,7 @@ export async function updateActivity(
const nid = notify({ text: "Updating", type: "loading" }); const nid = notify({ text: "Updating", type: "loading" });
console.debug("updateActivity:", activity, date); console.debug("updateActivity:", activity, date);
try { try {
const resp = await axios.put(`/activity/${activity.id}`, { const resp = await req.putWhole(`/activity/${activity.id}`, {
customDate: date.toISOString(), customDate: date.toISOString(),
} as ActivityUpdateRequest); } as ActivityUpdateRequest);
console.log("updateActivity: Response status:", resp.status); console.log("updateActivity: Response status:", resp.status);
@@ -201,7 +205,7 @@ export async function updateActivity(
export async function removeActivity(activityId: number): Promise<boolean> { export async function removeActivity(activityId: number): Promise<boolean> {
const nid = notify({ text: "Deleting", type: "loading" }); const nid = notify({ text: "Deleting", type: "loading" });
try { try {
await axios.delete("/activity/" + activityId); await req.delete("/activity/" + activityId);
console.log("removeActivity: Removed:", activityId); console.log("removeActivity: Removed:", activityId);
notify({ id: nid, text: "Deleted!", type: "success" }); notify({ id: nid, text: "Deleted!", type: "success" });
return true; return true;
@@ -219,9 +223,11 @@ export async function contentExistsOnJellyfin(
): Promise<JellyfinFoundContent | undefined> { ): Promise<JellyfinFoundContent | undefined> {
try { try {
if (Number(store.userInfo?.type) == UserType.Jellyfin) { if (Number(store.userInfo?.type) == UserType.Jellyfin) {
const resp = await axios.get(`/jellyfin/${type}/${name}/${tmdbId}`); const resp = await req.get<JellyfinFoundContent>(
console.log("contentExistsOnJellyfin response:", resp.data); `/jellyfin/${type}/${name}/${tmdbId}`,
return resp.data as JellyfinFoundContent; );
console.log("contentExistsOnJellyfin response:", resp);
return resp;
} }
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@@ -241,14 +247,12 @@ export function updateUserSetting<K extends keyof UserSettings>(
} }
const originalValue = store.userSettings[name]; const originalValue = store.userSettings[name];
const nid = notify({ type: "loading", text: "Updating" }); const nid = notify({ type: "loading", text: "Updating" });
axios req
.post("/user/update", { [name]: value }) .post("/user/update", { [name]: value })
.then((r) => { .then(() => {
if (r.status === 200) { if (store.userSettings) store.userSettings[name] = value;
if (store.userSettings) store.userSettings[name] = value; notify({ id: nid, type: "success", text: "Updated" });
notify({ id: nid, type: "success", text: "Updated" }); if (typeof done !== "undefined") done();
if (typeof done !== "undefined") done();
}
}) })
.catch((err) => { .catch((err) => {
console.error("Failed to update user setting", err); console.error("Failed to update user setting", err);
@@ -264,18 +268,14 @@ export function changeUserPassword(
done?: (errMsg?: string) => void, done?: (errMsg?: string) => void,
) { ) {
const nid = notify({ type: "loading", text: "Changing Password" }); const nid = notify({ type: "loading", text: "Changing Password" });
axios req
.post("/auth/change_password", { oldPassword, newPassword }) .post("/auth/change_password", { oldPassword, newPassword })
.then((r) => { .then(() => {
if (r.status === 200) { notify({ id: nid, type: "success", text: "Password Changed" });
notify({ id: nid, type: "success", text: "Password Changed" }); if (typeof done !== "undefined") done();
if (typeof done !== "undefined") done();
}
}) })
.catch((err) => { .catch((err) => {
const errMsg = err?.response?.data?.error const errMsg = ReqerError.getMsg(err, "Couldn't Change Password");
? err.response.data.error
: "Couldn't Change Password";
console.error( console.error(
"Change Password Form - Failed to change password on the server", "Change Password Form - Failed to change password on the server",
err, err,
@@ -290,9 +290,9 @@ export function changeUserPassword(
*/ */
export async function getServerFeatures() { export async function getServerFeatures() {
try { try {
const f = await axios.get("/features"); const f = await req.get<ServerFeatures>("/features");
if (f?.data) { if (f) {
store.serverFeatures = f.data; store.serverFeatures = f;
} }
} catch (err) { } catch (err) {
console.error("getServerFeatures failed!", err); console.error("getServerFeatures failed!", err);
@@ -301,11 +301,11 @@ export async function getServerFeatures() {
export async function followUser(id: number) { export async function followUser(id: number) {
const nid = notify({ text: `Following`, type: "loading" }); const nid = notify({ text: `Following`, type: "loading" });
axios req
.post(`/follow/${id}`) .post<Follow>(`/follow/${id}`)
.then((resp) => { .then((resp) => {
console.log("Followed:", resp.data); console.log("Followed:", resp);
store.follows.push(resp.data as Follow); store.follows.push(resp);
notify({ id: nid, text: `Followed!`, type: "success" }); notify({ id: nid, text: `Followed!`, type: "success" });
}) })
.catch((err) => { .catch((err) => {
@@ -316,10 +316,10 @@ export async function followUser(id: number) {
export async function unfollowUser(id: number) { export async function unfollowUser(id: number) {
const nid = notify({ text: `Unfollowing`, type: "loading" }); const nid = notify({ text: `Unfollowing`, type: "loading" });
axios req
.delete(`/follow/${id}`) .delete(`/follow/${id}`)
.then((resp) => { .then((resp) => {
console.log("Unfollowed:", resp.data); console.log("Unfollowed:", resp);
store.follows = store.follows.filter((fo) => fo.followedUser.id != id); store.follows = store.follows.filter((fo) => fo.followedUser.id != id);
notify({ id: nid, text: `Unfollowed!`, type: "success" }); 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" }); 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 { PaginationResponse } from "@/types";
import type { AxiosResponse, GenericAbortSignal } from "axios";
export enum PaginatedLoaderRunFnAction { export enum PaginatedLoaderRunFnAction {
// Reset state before running fn. Simplifies running a clean request. // Reset state before running fn. Simplifies running a clean request.
@@ -14,9 +13,7 @@ export enum PaginatedLoaderRunFnAction {
} }
export default function paginatedLoader<T, U>( export default function paginatedLoader<T, U>(
fn: ( fn: (sig: AbortSignal) => Promise<PaginationResponse<T, U> | undefined>,
sig: GenericAbortSignal,
) => Promise<AxiosResponse<PaginationResponse<T, U>, any> | undefined>,
) { ) {
let reqController = new AbortController(); let reqController = new AbortController();
@@ -102,20 +99,20 @@ export default function paginatedLoader<T, U>(
); );
return; return;
} }
state.page = resp.data.page; state.page = resp.page;
state.pageMax = resp.data.totalPages; state.pageMax = resp.totalPages;
console.debug( console.debug(
`%cpaginatedLoader->runFn: Loaded Page=${state.page} Max=${state.pageMax}`, `%cpaginatedLoader->runFn: Loaded Page=${state.page} Max=${state.pageMax}`,
logStyle, logStyle,
); );
if (!resp.data.results || resp.data.results.length <= 0) { if (!resp.results || resp.results.length <= 0) {
state.reqLoading = false; state.reqLoading = false;
console.warn("loadWatchedList: No results."); console.warn("loadWatchedList: No results.");
return; return;
} }
state.data.push(...resp.data.results); state.data.push(...resp.results);
state.data = state.data; state.data = state.data;
state.meta = resp.data.meta; state.meta = resp.meta;
} catch (err: any) { } catch (err: any) {
if (err?.code === "ERR_CANCELED") { if (err?.code === "ERR_CANCELED") {
console.warn("loadWatchedList: Cancelled, not showing error."); 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 { interface Plex {
win: Window; win: Window;
@@ -104,11 +109,11 @@ export async function plexPinPoll(
const doPoll = async () => { const doPoll = async () => {
try { try {
console.debug("plexPinPoll"); 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 }, headers: { ...headers, code: pin.code },
}); });
if (r.data?.authToken) { if (r?.authToken) {
done(undefined, r.data.authToken); done(undefined, r.authToken);
win.close(); win.close();
} else if (win?.closed) { } else if (win?.closed) {
done(new Error("Plex popup closed before login completed")); 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> { async function getPlexPin(headers: Record<string, string>): Promise<PlexPin> {
const r = await noAuthAxios.post( const r = await plexTvReq.post<any>(`/pins?strong=true`, undefined, {
`https://plex.tv/api/v2/pins?strong=true`, headers: headers,
undefined, });
{ console.debug("getPlexPin:", r);
headers: headers, return { id: r.id, code: r.code };
},
);
console.debug("getPlexPin:", r.data);
return { id: r.data.id, code: r.data.code };
} }
+33 -25
View File
@@ -11,9 +11,16 @@
import FollowingMenu from "@/lib/nav/FollowingMenu.svelte"; import FollowingMenu from "@/lib/nav/FollowingMenu.svelte";
import SortMenu from "@/lib/nav/SortMenu.svelte"; import SortMenu from "@/lib/nav/SortMenu.svelte";
import TagMenu from "@/lib/tag/TagMenu.svelte"; import TagMenu from "@/lib/tag/TagMenu.svelte";
import { req } from "@/lib/util/api";
import { isTouch } from "@/lib/util/helpers"; import { isTouch } from "@/lib/util/helpers";
import { store, defaultSort } from "@/store.svelte"; import { store, defaultSort } from "@/store.svelte";
import axios from "axios"; import type {
ServerFeatures,
Follow,
PrivateUser,
Tag,
UserSettings,
} from "@/types";
import { onMount } from "svelte"; import { onMount } from "svelte";
interface Props { interface Props {
children?: import("svelte").Snippet; children?: import("svelte").Snippet;
@@ -101,31 +108,32 @@
} }
async function getInitialData() { async function getInitialData() {
if (localStorage.getItem("token")) { if (!localStorage.getItem("token")) {
const [u, s, f, fo, ts] = await Promise.all([ console.warn("getInitialData: No token found, redirecting to login!");
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 {
goto("/login?again=1"); 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 prerender = false;
export const ssr = false; export const ssr = false;
export const csr = true; 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 Poster from "@/lib/poster/Poster.svelte";
import PosterList from "@/lib/poster/PosterList.svelte"; import PosterList from "@/lib/poster/PosterList.svelte";
import Spinner from "@/lib/Spinner.svelte"; import Spinner from "@/lib/Spinner.svelte";
import { req } from "@/lib/util/api";
import infScroll from "@/lib/util/infScroll"; import infScroll from "@/lib/util/infScroll";
import paginatedLoader from "@/lib/util/paginatedLoader.svelte"; import paginatedLoader from "@/lib/util/paginatedLoader.svelte";
import { clearActiveFilters, store } from "@/store.svelte"; import { clearActiveFilters, store } from "@/store.svelte";
import type { Media } from "@/types"; import { type Media, type PaginationResponse } from "@/types";
import axios, { type GenericAbortSignal } from "axios";
import { onDestroy, untrack } from "svelte"; import { onDestroy, untrack } from "svelte";
const scroll = infScroll({ callback: onScrollToBottom }); const scroll = infScroll({ callback: onScrollToBottom });
@@ -23,13 +23,13 @@
...store.sortAndFiltersForQueryParams, ...store.sortAndFiltersForQueryParams,
}); });
async function load(signal: GenericAbortSignal) { async function load(signal: AbortSignal) {
console.debug("load: loadParams:", nextLoadParams); console.debug("load: loadParams:", nextLoadParams);
if (nextLoadParams.page === dataLoader.state.page) { if (nextLoadParams.page === dataLoader.state.page) {
console.warn("load: Already on this page, not loading it again!"); console.warn("load: Already on this page, not loading it again!");
return; return;
} }
const r = await axios.get(`/watched`, { const r = await req.get<PaginationResponse<Media, undefined>>(`/watched`, {
params: nextLoadParams, params: nextLoadParams,
signal, signal,
}); });
+9 -11
View File
@@ -3,11 +3,10 @@
import Spinner from "@/lib/Spinner.svelte"; import Spinner from "@/lib/Spinner.svelte";
import RequestMovie from "@/lib/request/RequestMovie.svelte"; import RequestMovie from "@/lib/request/RequestMovie.svelte";
import RequestShow from "@/lib/request/RequestShow.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 { toRelativeDate } from "@/lib/util/helpers";
import { notify } from "@/lib/util/notify"; import { notify } from "@/lib/util/notify";
import { type ArrRequestResponse, type Media } from "@/types"; import { type ArrRequestResponse, type Media } from "@/types";
import axios from "axios";
let allRequests: ArrRequestResponse[] | undefined = $state(); let allRequests: ArrRequestResponse[] | undefined = $state();
let showBeingApproved: Media | undefined = $state(); let showBeingApproved: Media | undefined = $state();
@@ -16,8 +15,7 @@
async function getRequests() { async function getRequests() {
try { try {
allRequests = (await axios.get<ArrRequestResponse[]>(`/arr/request/`)) allRequests = await req.get<ArrRequestResponse[]>(`/arr/request/`);
.data;
if (allRequests?.length > 0) { if (allRequests?.length > 0) {
allRequests = allRequests?.sort((a, b) => { allRequests = allRequests?.sort((a, b) => {
if (b.status === "PENDING") return 1; if (b.status === "PENDING") return 1;
@@ -33,7 +31,7 @@
async function deny(r: ArrRequestResponse) { async function deny(r: ArrRequestResponse) {
try { try {
await axios.post(`/arr/request/deny/${r.id}`); await req.post(`/arr/request/deny/${r.id}`);
getRequests(); getRequests();
} catch (err) { } catch (err) {
console.error("Failed to deny request!", err); console.error("Failed to deny request!", err);
@@ -44,13 +42,13 @@
async function approve(r: ArrRequestResponse) { async function approve(r: ArrRequestResponse) {
console.debug("Approving request:", r); console.debug("Approving request:", r);
if (r.content.type === "tv") { if (r.content.type === "tv") {
showBeingApproved = ( showBeingApproved = await req.get<Media>(
await axios.get<Media>(`/content/tv/${r.content.tmdbId}`) `/content/tv/${r.content.tmdbId}`,
).data; );
} else if (r.content.type === "movie") { } else if (r.content.type === "movie") {
movieBeingApproved = ( movieBeingApproved = await req.get<Media>(
await axios.get<Media>(`/content/movie/${r.content.tmdbId}`) `/content/movie/${r.content.tmdbId}`,
).data; );
} else { } else {
notify({ notify({
type: "error", type: "error",
+4 -3
View File
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import Spinner from "@/lib/Spinner.svelte"; 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 Poster from "@/lib/poster/Poster.svelte";
import PageTitle from "@/lib/generic/PageTitle.svelte"; import PageTitle from "@/lib/generic/PageTitle.svelte";
import MediaTypeFilter from "@/lib/search/MediaTypeFilter.svelte"; import MediaTypeFilter from "@/lib/search/MediaTypeFilter.svelte";
@@ -14,6 +14,7 @@
SearchType, SearchType,
type DiscoverRequest, type DiscoverRequest,
type Media, type Media,
type PaginationResponse,
} from "@/types"; } from "@/types";
import { page } from "$app/state"; import { page } from "$app/state";
import { afterNavigate, goto } from "$app/navigation"; import { afterNavigate, goto } from "$app/navigation";
@@ -40,13 +41,13 @@
filter: discoverFilter, filter: discoverFilter,
}); });
async function load(signal: GenericAbortSignal) { async function load(signal: AbortSignal) {
console.debug("load: loadParams:", nextLoadParams); console.debug("load: loadParams:", nextLoadParams);
if (nextLoadParams.page === dataLoader.state.page) { if (nextLoadParams.page === dataLoader.state.page) {
console.warn("load: Already on this page, not loading it again!"); console.warn("load: Already on this page, not loading it again!");
return; return;
} }
const r = await axios.get(`/discover`, { const r = await req.get<PaginationResponse<Media, undefined>>(`/discover`, {
params: nextLoadParams, params: nextLoadParams,
signal, signal,
}); });
+2 -4
View File
@@ -2,12 +2,11 @@
import Spinner from "@/lib/Spinner.svelte"; import Spinner from "@/lib/Spinner.svelte";
import HorizontalList from "@/lib/HorizontalList.svelte"; import HorizontalList from "@/lib/HorizontalList.svelte";
import { type Media, type WatchedStatus } from "@/types"; import { type Media, type WatchedStatus } from "@/types";
import axios from "axios";
import Activity from "@/lib/Activity.svelte"; import Activity from "@/lib/Activity.svelte";
import Title from "@/lib/content/Title.svelte"; import Title from "@/lib/content/Title.svelte";
import Error from "@/lib/Error.svelte"; import Error from "@/lib/Error.svelte";
import FollowedThoughts from "@/lib/content/FollowedThoughts.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 tooltip from "@/lib/actions/tooltip.js";
import Icon from "@/lib/Icon.svelte"; import Icon from "@/lib/Icon.svelte";
import AddToTagButton from "@/lib/tag/AddToTagButton.svelte"; import AddToTagButton from "@/lib/tag/AddToTagButton.svelte";
@@ -43,8 +42,7 @@
if (!data.gameId) { if (!data.gameId) {
return; return;
} }
const resp = (await axios.get(`/game/${data.gameId}`)).data as Media; game = await req.get<Media>(`/game/${data.gameId}`);
game = resp;
} catch (err: any) { } catch (err: any) {
game = undefined; game = undefined;
pageError = err; pageError = err;
+8 -8
View File
@@ -25,7 +25,7 @@
type Media, type Media,
type WatchedStatus, type WatchedStatus,
} from "@/types"; } from "@/types";
import axios from "axios"; import { req } from "@/lib/util/api";
import { onDestroy } from "svelte"; import { onDestroy } from "svelte";
import papa from "papaparse"; import papa from "papaparse";
import Status from "@/lib/Status.svelte"; import Status from "@/lib/Status.svelte";
@@ -479,11 +479,11 @@
rList = rList; rList = rList;
return; return;
} }
const resp = await axios.post<ImportResponse>("/import", item); const resp = await req.post<ImportResponse>("/import", item);
return new Promise((res, rej) => { return new Promise((res, rej) => {
if (resp.data.type === ImportResponseType.IMPORT_MULTI) { if (resp.type === ImportResponseType.IMPORT_MULTI) {
console.log("Import found multiple responses for content", resp.data); console.log("Import found multiple responses for content", resp);
let results = resp.data.results; let results = resp.results;
if (!results || results.length <= 0) { if (!results || results.length <= 0) {
item.state = ImportResponseType.IMPORT_NOTFOUND; item.state = ImportResponseType.IMPORT_NOTFOUND;
rList = rList; 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; item.state = ImportResponseType.IMPORT_SUCCESS;
const w = resp.data.watchedEntry; const w = resp.watchedEntry;
if (w) { if (w) {
const release = w.media?.releaseDate; const release = w.media?.releaseDate;
if (release) item.year = new Date(Date.parse(release)).getFullYear(); if (release) item.year = new Date(Date.parse(release)).getFullYear();
@@ -533,7 +533,7 @@
rList = rList; rList = rList;
res(0); res(0);
} else { } else {
item.state = resp.data.type; item.state = resp.type;
rList = rList; rList = rList;
res(0); res(0);
} }
+5 -5
View File
@@ -6,29 +6,29 @@
<script lang="ts"> <script lang="ts">
import JobWatcherModal from "@/lib/JobWatcherModal.svelte"; import JobWatcherModal from "@/lib/JobWatcherModal.svelte";
import Setting from "@/lib/settings/Setting.svelte"; import Setting from "@/lib/settings/Setting.svelte";
import { req } from "@/lib/util/api";
import { notify } from "@/lib/util/notify"; import { notify } from "@/lib/util/notify";
import { store } from "@/store.svelte"; import { store } from "@/store.svelte";
import type { JobCreatedResponse } from "@/types"; import type { JobCreatedResponse } from "@/types";
import axios from "axios";
let modalOpen = $state(false); let modalOpen = $state(false);
let traktUsername = $state(""); let traktUsername = $state("");
let traktApiKey = $state(""); let traktApiKey = $state("");
async function startJob(): Promise<{ jobId: string } | undefined> { 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, username: traktUsername,
apiKey: traktApiKey.trim(), apiKey: traktApiKey.trim(),
}); });
console.log("startSync: Response:", r.data); console.log("startSync: Response:", r);
if (!r.data.jobId) { if (!r?.jobId) {
notify({ notify({
type: "error", type: "error",
text: "No job id was returned! Cannot watch job, if it even started.", text: "No job id was returned! Cannot watch job, if it even started.",
}); });
return; return;
} }
return { jobId: r.data.jobId }; return { jobId: r.jobId };
} }
</script> </script>
@@ -3,11 +3,10 @@
import Spinner from "@/lib/Spinner.svelte"; import Spinner from "@/lib/Spinner.svelte";
import tooltip from "@/lib/actions/tooltip.js"; import tooltip from "@/lib/actions/tooltip.js";
import UserAvatar from "@/lib/img/UserAvatar.svelte"; 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 { clearActiveFilters, store } from "@/store.svelte.js";
import type { Media, PublicUser } from "@/types.js"; import type { Media, PaginationResponse, PublicUser } from "@/types.js";
import axios, { type GenericAbortSignal } from "axios"; import { onDestroy, untrack } from "svelte";
import { onDestroy, onMount, untrack } from "svelte";
import paginatedLoader, { import paginatedLoader, {
PaginatedLoaderRunFnAction, PaginatedLoaderRunFnAction,
} from "@/lib/util/paginatedLoader.svelte.js"; } from "@/lib/util/paginatedLoader.svelte.js";
@@ -43,7 +42,7 @@
...store.sortAndFiltersForQueryParams, ...store.sortAndFiltersForQueryParams,
}); });
async function load(signal: GenericAbortSignal) { async function load(signal: AbortSignal) {
console.debug("load: loadParams:", nextLoadParams); console.debug("load: loadParams:", nextLoadParams);
if (nextLoadParams.page === dataLoader.state.page) { if (nextLoadParams.page === dataLoader.state.page) {
console.warn("load: Already on this page, not loading it again!"); console.warn("load: Already on this page, not loading it again!");
@@ -53,10 +52,13 @@
console.warn("load: Missing id or username!"); console.warn("load: Missing id or username!");
return; return;
} }
const r = await axios.get(`/watched/${meta.id}/${meta.username}`, { const r = await req.get<PaginationResponse<Media, undefined>>(
params: nextLoadParams, `/watched/${meta.id}/${meta.username}`,
signal, {
}); params: nextLoadParams,
signal,
},
);
scroll.dataLoaded(); scroll.dataLoaded();
return r; return r;
} }
@@ -87,8 +89,9 @@
}); });
async function getPublicUser() { async function getPublicUser() {
return (await axios.get(`/user/public/${meta.id}/${meta.username}`)) return await req.get<PublicUser>(
.data as PublicUser; `/user/public/${meta.id}/${meta.username}`,
);
} }
async function follow() { async function follow() {
+2 -2
View File
@@ -7,7 +7,7 @@
userHasPermission, userHasPermission,
} from "@/lib/util/helpers"; } from "@/lib/util/helpers";
import { UserPermission, type ManagedUser } from "@/types"; import { UserPermission, type ManagedUser } from "@/types";
import axios from "axios"; import { req } from "@/lib/util/api";
import EditUserModal from "./modals/EditUserModal.svelte"; import EditUserModal from "./modals/EditUserModal.svelte";
import UserTypeIcon from "@/lib/user/UserTypeIcon.svelte"; import UserTypeIcon from "@/lib/user/UserTypeIcon.svelte";
import Error from "@/lib/Error.svelte"; import Error from "@/lib/Error.svelte";
@@ -18,7 +18,7 @@
let editingUser: ManagedUser | undefined; let editingUser: ManagedUser | undefined;
async function getUsers() { async function getUsers() {
allUsers = (await axios.get(`/server/users`)).data as ManagedUser[]; allUsers = await req.get<ManagedUser[]>(`/server/users`);
} }
</script> </script>
@@ -4,10 +4,11 @@
import Modal from "@/lib/Modal.svelte"; import Modal from "@/lib/Modal.svelte";
import Setting from "@/lib/settings/Setting.svelte"; import Setting from "@/lib/settings/Setting.svelte";
import SettingsList from "@/lib/settings/SettingsList.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 { userHasPermission } from "@/lib/util/helpers";
import { notify } from "@/lib/util/notify"; import { notify } from "@/lib/util/notify";
import { UserPermission, UserType, type ManagedUser } from "@/types"; import { UserPermission, UserType, type ManagedUser } from "@/types";
import axios from "axios";
interface UpdateUserRequest { interface UpdateUserRequest {
permissions?: number; permissions?: number;
@@ -44,20 +45,15 @@
if (changedType) { if (changedType) {
toUpdate["type"] = user.type; toUpdate["type"] = user.type;
} }
const res = await axios.post(`/server/users/${user.id}`, toUpdate); await req.post(`/server/users/${user.id}`, toUpdate);
if (res.status === 200) { notify({
notify({ type: "success",
type: "success", text: "Changes saved!",
text: "Changes saved!", });
}); onClose();
onClose();
}
} catch (err: any) { } catch (err: any) {
console.error("Failed to save user!", err); console.error("Failed to save user!", err);
error = `Failed to save`; error = ReqerError.getMsg(err, "Failed to save");
if (err?.response?.data?.error) {
error = err.response.data.error;
}
} }
} }
} }
+7 -13
View File
@@ -2,11 +2,7 @@
import PersonPoster from "@/lib/poster/PersonPoster.svelte"; import PersonPoster from "@/lib/poster/PersonPoster.svelte";
import Spinner from "@/lib/Spinner.svelte"; import Spinner from "@/lib/Spinner.svelte";
import HorizontalList from "@/lib/HorizontalList.svelte"; import HorizontalList from "@/lib/HorizontalList.svelte";
import { import { contentExistsOnJellyfin, req, updateWatched } from "@/lib/util/api";
contentExistsOnJellyfin,
removeWatched,
updateWatched,
} from "@/lib/util/api";
import { store } from "@/store.svelte"; import { store } from "@/store.svelte";
import type { import type {
Media, Media,
@@ -14,7 +10,6 @@
TMDBContentCreditsCrew, TMDBContentCreditsCrew,
WatchedStatus, WatchedStatus,
} from "@/types"; } from "@/types";
import axios from "axios";
import { getTopCrew } from "@/lib/util/helpers.js"; import { getTopCrew } from "@/lib/util/helpers.js";
import Activity from "@/lib/Activity.svelte"; import Activity from "@/lib/Activity.svelte";
import Title from "@/lib/content/Title.svelte"; import Title from "@/lib/content/Title.svelte";
@@ -52,11 +47,9 @@
if (!data.movieId) { if (!data.movieId) {
return; return;
} }
const resp = ( const resp = await req.get<Media>(`/content/movie/${data.movieId}`, {
await axios.get(`/content/movie/${data.movieId}`, { params: { region: store.userSettings?.country },
params: { region: store.userSettings?.country }, });
})
).data as Media;
if (resp) { if (resp) {
if (resp.name && resp.ids.tmdb) { if (resp.name && resp.ids.tmdb) {
contentExistsOnJellyfin("movie", resp.name, resp.ids.tmdb).then( contentExistsOnJellyfin("movie", resp.name, resp.ids.tmdb).then(
@@ -79,8 +72,9 @@
}); });
async function getMovieCredits() { async function getMovieCredits() {
const credits = (await axios.get(`/content/movie/${data.movieId}/credits`)) const credits = await req.get<
.data as TMDBContentCredits & { topCrew: TMDBContentCreditsCrew[] }; TMDBContentCredits & { topCrew: TMDBContentCreditsCrew[] }
>(`/content/movie/${data.movieId}/credits`);
if (credits.crew?.length > 0) { if (credits.crew?.length > 0) {
credits.topCrew = getTopCrew(credits.crew); credits.topCrew = getTopCrew(credits.crew);
} }
+5 -8
View File
@@ -9,7 +9,7 @@
PersonCreditsResponse, PersonCreditsResponse,
PersonDetailsResponse, PersonDetailsResponse,
} from "@/types"; } from "@/types";
import axios from "axios"; import { req } from "@/lib/util/api.js";
import Checkbox from "@/lib/Checkbox.svelte"; import Checkbox from "@/lib/Checkbox.svelte";
import Icon from "@/lib/Icon.svelte"; import Icon from "@/lib/Icon.svelte";
import PageBackdrop from "@/lib/generic/PageBackdrop.svelte"; import PageBackdrop from "@/lib/generic/PageBackdrop.svelte";
@@ -53,16 +53,13 @@
} }
async function getPerson(id: number) { async function getPerson(id: number) {
return (await axios.get<PersonDetailsResponse>(`/content/person/${id}`)) return await req.get<PersonDetailsResponse>(`/content/person/${id}`);
.data;
} }
async function updatePersonCredits() { async function updatePersonCredits() {
credits = ( credits = await req.get<PersonCreditsResponse>(
await axios.get<PersonCreditsResponse>( `/content/person/${data.personId}/credits`,
`/content/person/${data.personId}/credits`, );
)
).data;
credits.credits = credits.credits?.filter( credits.credits = credits.credits?.filter(
(v, i, a) => a.findIndex((t) => t.ids.tmdb === v.ids.tmdb) === i, (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 ); // 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 Setting from "@/lib/settings/Setting.svelte";
import Stat from "@/lib/stats/Stat.svelte"; import Stat from "@/lib/stats/Stat.svelte";
import Stats from "@/lib/stats/Stats.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 { getOrdinalSuffix, monthsShort } from "@/lib/util/helpers";
import { store } from "@/store.svelte"; import { store } from "@/store.svelte";
import { UserType, type Image, type Profile } from "@/types"; import { UserType, type Image, type Profile } from "@/types";
import axios from "axios";
import { notify } from "@/lib/util/notify"; import { notify } from "@/lib/util/notify";
import UserAvatar from "@/lib/img/UserAvatar.svelte"; import UserAvatar from "@/lib/img/UserAvatar.svelte";
import PwChangeModal from "@/routes/(app)/profile/modals/PwChangeModal.svelte"; import PwChangeModal from "@/routes/(app)/profile/modals/PwChangeModal.svelte";
@@ -19,6 +18,7 @@
import RatingSetting from "@/lib/rating/RatingSetting.svelte"; import RatingSetting from "@/lib/rating/RatingSetting.svelte";
import { toggleTheme } from "@/lib/util/theme"; import { toggleTheme } from "@/lib/util/theme";
import ExportListModal from "./modals/ExportListModal.svelte"; import ExportListModal from "./modals/ExportListModal.svelte";
import { ReqerError } from "@/lib/util/fetch";
let user = $derived(store.userInfo); let user = $derived(store.userInfo);
let settings = $derived(store.userSettings); let settings = $derived(store.userSettings);
@@ -37,7 +37,7 @@
let plexSyncModalOpen = $state(false); let plexSyncModalOpen = $state(false);
async function getProfile() { async function getProfile() {
return (await axios.get(`/profile`)).data as Profile; return await req.get<Profile>(`/profile`);
} }
function formatDate(d: Date) { function formatDate(d: Date) {
@@ -55,7 +55,7 @@
return; return;
} }
const nid = notify({ text: "Updating Bio", type: "loading" }); const nid = notify({ text: "Updating Bio", type: "loading" });
axios req
.post("/user/bio", { newBio: newBio }) .post("/user/bio", { newBio: newBio })
.then(() => { .then(() => {
if (user) { if (user) {
@@ -66,7 +66,7 @@
.catch((err) => { .catch((err) => {
notify({ notify({
id: nid, id: nid,
text: err?.response?.data?.error ?? "Failed to update bio", text: ReqerError.getMsg(err, "Failed to update bio"),
type: "error", type: "error",
}); });
}); });
@@ -79,19 +79,13 @@
return; return;
} }
const nid = notify({ text: "Uploading avatar", type: "loading" }); const nid = notify({ text: "Uploading avatar", type: "loading" });
axios const fd = new FormData();
.postForm( fd.append("avatar", files[0]);
"/user/avatar", req
{ avatar: files[0] }, .post<Image>("/user/avatar", fd)
{
headers: {
"Content-Type": "multipart/form-data",
},
},
)
.then((r) => { .then((r) => {
if (user) { if (user) {
user.avatar = r.data as Image; user.avatar = r;
notify({ id: nid, text: "Avatar Uploaded", type: "success" }); notify({ id: nid, text: "Avatar Uploaded", type: "success" });
} }
}) })
@@ -99,7 +93,7 @@
console.error("uploading avatar failed", err); console.error("uploading avatar failed", err);
notify({ notify({
id: nid, id: nid,
text: err?.response?.data?.error ?? "Failed to upload avatar", text: ReqerError.getMsg(err, "Failed to upload avatar"),
type: "error", type: "error",
}); });
}); });
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import Modal from "@/lib/Modal.svelte"; import Modal from "@/lib/Modal.svelte";
import { req } from "@/lib/util/api";
import { notify } from "@/lib/util/notify"; import { notify } from "@/lib/util/notify";
import axios from "axios";
interface Props { interface Props {
onClose: () => void; onClose: () => void;
@@ -13,9 +13,9 @@
const nid = notify({ text: "Exporting", type: "loading" }); const nid = notify({ text: "Exporting", type: "loading" });
try { try {
// We re-fetch, to ensure data we export is up to date. // We re-fetch, to ensure data we export is up to date.
const r = await axios.get("/watched"); const r = await req.get<any>("/watched");
console.log(r.data); console.log(r);
if (!r.data || r.data?.length <= 0) { if (!r || r?.length <= 0) {
notify({ notify({
id: nid, id: nid,
text: "Can't export an empty watch list!", text: "Can't export an empty watch list!",
@@ -24,7 +24,7 @@
}); });
return; return;
} }
const file = new Blob([JSON.stringify(r.data, undefined, 2)], { const file = new Blob([JSON.stringify(r, undefined, 2)], {
type: "application/json", type: "application/json",
}); });
const a = document.createElement("a"); const a = document.createElement("a");
@@ -11,9 +11,8 @@
type GetJobResponse, type GetJobResponse,
type JobCreatedResponse, type JobCreatedResponse,
} from "@/types"; } from "@/types";
import axios from "axios";
import { onDestroy, onMount } from "svelte"; import { onDestroy, onMount } from "svelte";
import { store } from "@/store.svelte"; import { req } from "@/lib/util/api";
interface Props { interface Props {
type?: "jellyfin" | "plex"; type?: "jellyfin" | "plex";
@@ -30,16 +29,16 @@
async function startSync() { async function startSync() {
try { try {
const r = await axios.get<JobCreatedResponse>( const r = await req.get<JobCreatedResponse>(
type === "jellyfin" ? "/jellyfin/sync" : "/plex/sync", type === "jellyfin" ? "/jellyfin/sync" : "/plex/sync",
); );
console.log("startSync: Response:", r.data); console.log("startSync: Response:", r);
if (!r.data.jobId) { if (!r.jobId) {
step = "errored"; step = "errored";
console.error("startSync: No jobId returned!"); console.error("startSync: No jobId returned!");
return; return;
} }
jobId = r.data.jobId; jobId = r.jobId;
step = "job-running"; step = "job-running";
startJobWatcher(); startJobWatcher();
} catch (err) { } catch (err) {
@@ -61,13 +60,13 @@
let seqfailedJobReqs = 0; let seqfailedJobReqs = 0;
while (step === "job-running") { while (step === "job-running") {
try { try {
const r = await axios.get<GetJobResponse>(`/job/${jobId}`); const r = await req.get<GetJobResponse>(`/job/${jobId}`);
console.log("jobWatcher: Got job data:", r.data); console.log("jobWatcher: Got job data:", r);
latestJobStatus = r.data; latestJobStatus = r;
currentTask = r.data?.currentTask; currentTask = r?.currentTask;
if (r.data?.status === JobStatus.DONE) { if (r?.status === JobStatus.DONE) {
step = "done"; step = "done";
} else if (r.data?.status === JobStatus.CANCELLED) { } else if (r?.status === JobStatus.CANCELLED) {
step = "errored"; step = "errored";
} }
// If we get here without erroring, we can reset it to 0. // If we get here without erroring, we can reset it to 0.
@@ -92,31 +91,6 @@
} }
await new Promise((r) => setTimeout(r, 1000)); 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() { function modalClose() {
+4 -7
View File
@@ -1,15 +1,15 @@
<script lang="ts"> <script lang="ts">
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import { req } from "@/lib/util/api";
import { notify } from "@/lib/util/notify"; import { notify } from "@/lib/util/notify";
import { store } from "@/store.svelte"; import { store } from "@/store.svelte";
import { UserPermission } from "@/types"; import { UserPermission } from "@/types";
import axios from "axios";
let page = $state(0); let page = $state(0);
let adminToken: string | undefined = $state(); let adminToken: string | undefined = $state();
function generateAdminToken() { function generateAdminToken() {
axios req
.get("/auth/admin_token") .get("/auth/admin_token")
.then(() => { .then(() => {
page = 1; page = 1;
@@ -25,7 +25,7 @@
console.error("useAdminToken: No admin token provided!"); console.error("useAdminToken: No admin token provided!");
return; return;
} }
axios req
.post("/auth/admin_token", { token: adminToken }) .post("/auth/admin_token", { token: adminToken })
.then(() => { .then(() => {
notify({ type: "success", text: "You now have admin!" }); notify({ type: "success", text: "You now have admin!" });
@@ -35,10 +35,7 @@
goto("/"); goto("/");
}) })
.catch((err) => { .catch((err) => {
console.error( console.error("Failed to use admin token", err);
"Failed to use admin token",
err?.response?.data?.error || err,
);
notify({ type: "error", text: "Failed to use admin token!" }); notify({ type: "error", text: "Failed to use admin token!" });
}); });
} }
+13 -8
View File
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { page } from "$app/state"; import { page } from "$app/state";
import { afterNavigate, goto } from "$app/navigation"; 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 Poster from "@/lib/poster/Poster.svelte";
import PosterList from "@/lib/poster/PosterList.svelte"; import PosterList from "@/lib/poster/PosterList.svelte";
import { store } from "@/store.svelte.js"; import { store } from "@/store.svelte.js";
@@ -11,6 +11,7 @@
MediaTypeE, MediaTypeE,
SearchType, SearchType,
type Media, type Media,
type PaginationResponse,
type PublicUser, type PublicUser,
type SearchRequest, type SearchRequest,
type SearchResponseMeta, type SearchResponseMeta,
@@ -55,7 +56,7 @@
preferMyList: preferMyList, preferMyList: preferMyList,
}); });
async function load(signal: GenericAbortSignal) { async function load(signal: AbortSignal) {
console.debug("load: loadParams:", nextLoadParams); console.debug("load: loadParams:", nextLoadParams);
if (nextLoadParams.page === dataLoader.state.page) { if (nextLoadParams.page === dataLoader.state.page) {
console.warn("load: Already on this page, not loading it again!"); console.warn("load: Already on this page, not loading it again!");
@@ -65,10 +66,13 @@
console.warn("load: There is no search query!"); console.warn("load: There is no search query!");
return; return;
} }
const r = await axios.get(`/search`, { const r = await req.get<PaginationResponse<Media, SearchResponseMeta>>(
params: nextLoadParams, `/search`,
signal, {
}); params: nextLoadParams,
signal,
},
);
scroll.dataLoaded(); scroll.dataLoaded();
return r; return r;
} }
@@ -95,8 +99,9 @@
} }
async function searchUsers(query: string) { async function searchUsers(query: string) {
return (await axios.get(`/user/search`, { params: { q: query } })) return await req.get<PublicUser[]>(`/user/search`, {
.data as PublicUser[]; params: { q: query },
});
} }
onMount(() => { onMount(() => {
+6 -7
View File
@@ -8,13 +8,12 @@
ServerConfig, ServerConfig,
SonarrSettings, SonarrSettings,
} from "@/types"; } from "@/types";
import axios from "axios";
import SonarrModal from "./modals/SonarrModal.svelte"; import SonarrModal from "./modals/SonarrModal.svelte";
import SettingsList from "@/lib/settings/SettingsList.svelte"; import SettingsList from "@/lib/settings/SettingsList.svelte";
import Setting from "@/lib/settings/Setting.svelte"; import Setting from "@/lib/settings/Setting.svelte";
import SettingButton from "@/lib/settings/SettingButton.svelte"; import SettingButton from "@/lib/settings/SettingButton.svelte";
import RadarrModal from "./modals/RadarrModal.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 Stats from "@/lib/stats/Stats.svelte";
import Error from "@/lib/Error.svelte"; import Error from "@/lib/Error.svelte";
import Stat from "@/lib/stats/Stat.svelte"; import Stat from "@/lib/stats/Stat.svelte";
@@ -44,7 +43,7 @@
let useEmbyDisabled = $state(false); let useEmbyDisabled = $state(false);
async function getServerConfig() { 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>( export function updateServerConfig<K extends keyof ServerConfig>(
@@ -64,13 +63,13 @@
if (name === "PLEX_HOST") { if (name === "PLEX_HOST") {
ep = "/server/config/plex_host"; ep = "/server/config/plex_host";
} }
axios req
.post(ep, { key: name, value: value }) .postWhole<any>(ep, { key: name, value: value })
.then((r) => { .then((r) => {
if (r.status === 200) { if (r.status === 200) {
serverConfig![name] = value; serverConfig![name] = value;
notify({ id: nid, type: "success", text: "Updated" }); notify({ id: nid, type: "success", text: "Updated" });
if (typeof done !== "undefined") done(r?.data); if (typeof done !== "undefined") done(r?.body);
} }
}) })
.catch((err) => { .catch((err) => {
@@ -93,7 +92,7 @@
} }
async function getServerStats() { async function getServerStats() {
return (await axios.get("/server/stats")).data as ServerStats; return await req.get<ServerStats>("/server/stats");
} }
</script> </script>
@@ -12,7 +12,8 @@
RadarrSettings, RadarrSettings,
RadarrTestResponse, RadarrTestResponse,
} from "@/types"; } from "@/types";
import axios from "axios"; import { req } from "@/lib/util/api";
import { ReqerError } from "@/lib/util/fetch";
interface Props { interface Props {
servarr: RadarrSettings; servarr: RadarrSettings;
@@ -22,7 +23,7 @@
let { servarr = $bindable(), isEditing, onClose }: Props = $props(); let { servarr = $bindable(), isEditing, onClose }: Props = $props();
let error: string = $state(); let error: string | undefined = $state();
let formDisabled = $state(false); let formDisabled = $state(false);
let qualityProfiles: DropDownItem[] = $state([]); let qualityProfiles: DropDownItem[] = $state([]);
@@ -42,14 +43,14 @@
async function getSettingsData() { async function getSettingsData() {
try { try {
formDisabled = true; formDisabled = true;
const res = await axios.post<RadarrTestResponse>("/arr/rad/test", { const res = await req.post<RadarrTestResponse>("/arr/rad/test", {
host: servarr.host, host: servarr.host,
key: servarr.key, key: servarr.key,
}); });
qualityProfiles = res.data.qualityProfiles.map((d) => { qualityProfiles = res.qualityProfiles.map((d) => {
return { id: d.id, value: d.name }; 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 }; return { id: d.id, value: d.path };
}); });
formDisabled = false; formDisabled = false;
@@ -91,43 +92,33 @@
if (!error) { if (!error) {
console.log(servarr); console.log(servarr);
try { try {
const res = await axios.post( await req.post(`/arr/rad/${isEditing ? "edit" : "add"}`, servarr);
`/arr/rad/${isEditing ? "edit" : "add"}`, notify({
servarr, type: "success",
); text: isEditing ? "Changes saved!" : "Server added successfully!",
if (res.status === 200) { });
notify({ onClose();
type: "success",
text: isEditing ? "Changes saved!" : "Server added successfully!",
});
onClose();
}
} catch (err: any) { } catch (err: any) {
console.error("Failed to save server!", err); console.error("Failed to save server!", err);
error = `Failed to ${isEditing ? "edit" : "add"}`; error = ReqerError.getMsg(
if (err?.response?.data?.error) { err,
error = err.response.data.error; `Failed to ${isEditing ? "edit" : "add"}`,
} );
} }
} }
} }
async function remove() { async function remove() {
try { try {
const res = await axios.post(`/arr/rad/rm/${servarr.name}`); await req.post(`/arr/rad/rm/${servarr.name}`);
if (res.status === 200) { notify({
notify({ type: "success",
type: "success", text: "Removed server",
text: "Removed server", });
}); onClose();
onClose();
}
} catch (err: any) { } catch (err: any) {
console.error("Failed to remove server!", err); console.error("Failed to remove server!", err);
error = `Failed to remove`; error = ReqerError.getMsg(err, "Failed to remove");
if (err?.response?.data?.error) {
error = err.response.data.error;
}
} }
} }
run(() => { run(() => {
@@ -12,7 +12,8 @@
SonarrSettings, SonarrSettings,
SonarrTestResponse, SonarrTestResponse,
} from "@/types"; } from "@/types";
import axios from "axios"; import { req } from "@/lib/util/api";
import { ReqerError } from "@/lib/util/fetch";
interface Props { interface Props {
servarr: SonarrSettings; servarr: SonarrSettings;
@@ -44,17 +45,17 @@
async function getSettingsData() { async function getSettingsData() {
try { try {
formDisabled = true; formDisabled = true;
const res = await axios.post<SonarrTestResponse>("/arr/son/test", { const res = await req.post<SonarrTestResponse>("/arr/son/test", {
host: servarr.host, host: servarr.host,
key: servarr.key, key: servarr.key,
}); });
qualityProfiles = res.data.qualityProfiles.map((d) => { qualityProfiles = res.qualityProfiles.map((d) => {
return { id: d.id, value: d.name }; 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 }; 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 }; return { id: d.id, value: d.path };
}); });
formDisabled = false; formDisabled = false;
@@ -99,43 +100,33 @@
if (!error) { if (!error) {
console.log(servarr); console.log(servarr);
try { try {
const res = await axios.post( await req.post(`/arr/son/${isEditing ? "edit" : "add"}`, servarr);
`/arr/son/${isEditing ? "edit" : "add"}`, notify({
servarr, type: "success",
); text: isEditing ? "Changes saved!" : "Server added successfully!",
if (res.status === 200) { });
notify({ onClose();
type: "success",
text: isEditing ? "Changes saved!" : "Server added successfully!",
});
onClose();
}
} catch (err: any) { } catch (err: any) {
console.error("Failed to save server!", err); console.error("Failed to save server!", err);
error = `Failed to ${isEditing ? "edit" : "add"}`; error = ReqerError.getMsg(
if (err?.response?.data?.error) { err,
error = err.response.data.error; `Failed to ${isEditing ? "edit" : "add"}`,
} );
} }
} }
} }
async function remove() { async function remove() {
try { try {
const res = await axios.post(`/arr/son/rm/${servarr.name}`); await req.post(`/arr/son/rm/${servarr.name}`);
if (res.status === 200) { notify({
notify({ type: "success",
type: "success", text: "Removed server",
text: "Removed server", });
}); onClose();
onClose();
}
} catch (err: any) { } catch (err: any) {
console.error("Failed to remove server!", err); console.error("Failed to remove server!", err);
error = `Failed to remove`; error = ReqerError.getMsg(err, "Failed to remove");
if (err?.response?.data?.error) {
error = err.response.data.error;
}
} }
} }
run(() => { run(() => {
@@ -1,6 +1,4 @@
<script lang="ts"> <script lang="ts">
import { run } from "svelte/legacy";
import Modal from "@/lib/Modal.svelte"; import Modal from "@/lib/Modal.svelte";
import Spinner from "@/lib/Spinner.svelte"; import Spinner from "@/lib/Spinner.svelte";
import Setting from "@/lib/settings/Setting.svelte"; import Setting from "@/lib/settings/Setting.svelte";
@@ -8,7 +6,7 @@
import { toRelativeTime } from "@/lib/util/helpers"; import { toRelativeTime } from "@/lib/util/helpers";
import { notify } from "@/lib/util/notify"; import { notify } from "@/lib/util/notify";
import type { AllTasksResponse } from "@/types"; import type { AllTasksResponse } from "@/types";
import axios from "axios"; import { req } from "@/lib/util/api";
import { onMount } from "svelte"; import { onMount } from "svelte";
interface Props { interface Props {
@@ -23,8 +21,8 @@
async function getAllTasks() { async function getAllTasks() {
try { try {
const res = await axios.get<AllTasksResponse[]>("/task/"); const res = await req.get<AllTasksResponse[]>("/task/");
taskSchedule = res.data?.sort((a, b) => { taskSchedule = res?.sort((a, b) => {
if (a.name < b.name) { if (a.name < b.name) {
return -1; return -1;
} }
@@ -47,7 +45,7 @@
const nid = notify({ type: "loading", text: "Updating.." }); const nid = notify({ type: "loading", text: "Updating.." });
try { try {
formDisabled = true; formDisabled = true;
const res = await axios.put(`/task/${name}`, { seconds }); const res = await req.putWhole(`/task/${name}`, { seconds });
if (res.status === 200) { if (res.status === 200) {
notify({ id: nid, type: "success", text: "Schedule updated." }); notify({ id: nid, type: "success", text: "Schedule updated." });
getAllTasks(); getAllTasks();
@@ -5,9 +5,9 @@
import Spinner from "@/lib/Spinner.svelte"; import Spinner from "@/lib/Spinner.svelte";
import Setting from "@/lib/settings/Setting.svelte"; import Setting from "@/lib/settings/Setting.svelte";
import SettingsList from "@/lib/settings/SettingsList.svelte"; import SettingsList from "@/lib/settings/SettingsList.svelte";
import { req } from "@/lib/util/api";
import { notify } from "@/lib/util/notify"; import { notify } from "@/lib/util/notify";
import type { ServerConfigByName, TrustedHeaderAuthSetting } from "@/types"; import type { ServerConfigByName, TrustedHeaderAuthSetting } from "@/types";
import axios from "axios";
import { onMount } from "svelte"; import { onMount } from "svelte";
interface Props { interface Props {
@@ -29,13 +29,13 @@
async function getHeaderCfg() { async function getHeaderCfg() {
try { try {
loadingCfg = true; loadingCfg = true;
const res = await axios.get<ServerConfigByName<TrustedHeaderAuthSetting>>( const res = await req.get<ServerConfigByName<TrustedHeaderAuthSetting>>(
"/server/config", "/server/config",
{ {
params: { s: "HEADER_AUTH" }, params: { s: "HEADER_AUTH" },
}, },
); );
headerCfg = res.data.value; headerCfg = res.value;
loadingCfg = false; loadingCfg = false;
} catch (err) { } catch (err) {
console.error("getHeaderCfg failed!", err); console.error("getHeaderCfg failed!", err);
@@ -53,7 +53,7 @@
text: "Saving..", text: "Saving..",
}); });
try { try {
await axios.post("/server/config", headerCfg, { await req.post("/server/config", headerCfg, {
params: { s: "HEADER_AUTH" }, params: { s: "HEADER_AUTH" },
}); });
notify({ notify({
@@ -2,9 +2,10 @@
import Modal from "@/lib/Modal.svelte"; import Modal from "@/lib/Modal.svelte";
import Setting from "@/lib/settings/Setting.svelte"; import Setting from "@/lib/settings/Setting.svelte";
import SettingsList from "@/lib/settings/SettingsList.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 { notify } from "@/lib/util/notify";
import type { TwitchSettings } from "@/types"; import type { TwitchSettings } from "@/types";
import axios from "axios";
interface Props { interface Props {
cfg?: TwitchSettings; cfg?: TwitchSettings;
@@ -36,20 +37,15 @@
// if no error set from checkForm func, continue // if no error set from checkForm func, continue
if (!error) { if (!error) {
try { try {
const res = await axios.post(`/game/config`, cfg); await req.post(`/game/config`, cfg);
if (res.status === 200) { notify({
notify({ type: "success",
type: "success", text: "Changes saved!",
text: "Changes saved!", });
}); onClose();
onClose();
}
} catch (err: any) { } catch (err: any) {
console.error("Failed to save twitch cfg!", err); console.error("Failed to save twitch cfg!", err);
error = `Failed to save`; error = ReqerError.getMsg(err, "Failed to save");
if (err?.response?.data?.error) {
error = err.response.data.error;
}
} }
} }
} }
+10 -7
View File
@@ -8,13 +8,13 @@
import Spinner from "@/lib/Spinner.svelte"; import Spinner from "@/lib/Spinner.svelte";
import CreateTagModal from "@/lib/tag/CreateTagModal.svelte"; import CreateTagModal from "@/lib/tag/CreateTagModal.svelte";
import Tag from "@/lib/tag/Tag.svelte"; import Tag from "@/lib/tag/Tag.svelte";
import { req } from "@/lib/util/api";
import infScroll from "@/lib/util/infScroll"; import infScroll from "@/lib/util/infScroll";
import paginatedLoader, { import paginatedLoader, {
PaginatedLoaderRunFnAction, PaginatedLoaderRunFnAction,
} from "@/lib/util/paginatedLoader.svelte"; } from "@/lib/util/paginatedLoader.svelte";
import { clearActiveFilters, store } from "@/store.svelte.js"; import { clearActiveFilters, store } from "@/store.svelte.js";
import type { Media } from "@/types"; import type { Media, PaginationResponse } from "@/types";
import axios, { type GenericAbortSignal } from "axios";
import { onDestroy, untrack } from "svelte"; import { onDestroy, untrack } from "svelte";
let meta = $derived.by(() => { let meta = $derived.by(() => {
@@ -36,7 +36,7 @@
...store.sortAndFiltersForQueryParams, ...store.sortAndFiltersForQueryParams,
}); });
async function load(signal: GenericAbortSignal) { async function load(signal: AbortSignal) {
console.debug("load: loadParams:", nextLoadParams); console.debug("load: loadParams:", nextLoadParams);
if (nextLoadParams.page === dataLoader.state.page) { if (nextLoadParams.page === dataLoader.state.page) {
console.warn("load: Already on this page, not loading it again!"); console.warn("load: Already on this page, not loading it again!");
@@ -46,10 +46,13 @@
console.warn("load: Missing tag id!"); console.warn("load: Missing tag id!");
return; return;
} }
const r = await axios.get(`/tag/${meta.tagId}/watched`, { const r = await req.get<PaginationResponse<Media, undefined>>(
params: nextLoadParams, `/tag/${meta.tagId}/watched`,
signal, {
}); params: nextLoadParams,
signal,
},
);
scroll.dataLoaded(); scroll.dataLoaded();
return r; return r;
} }
+7 -9
View File
@@ -11,7 +11,7 @@
import Title from "@/lib/content/Title.svelte"; import Title from "@/lib/content/Title.svelte";
import { import {
contentExistsOnJellyfin, contentExistsOnJellyfin,
removeWatched, req,
updateWatched, updateWatched,
type UpdateWatchedOptions, type UpdateWatchedOptions,
} from "@/lib/util/api"; } from "@/lib/util/api";
@@ -23,7 +23,6 @@
TMDBContentCreditsCrew, TMDBContentCreditsCrew,
WatchedStatus, WatchedStatus,
} from "@/types"; } from "@/types";
import axios from "axios";
import RequestShow from "@/lib/request/RequestShow.svelte"; import RequestShow from "@/lib/request/RequestShow.svelte";
import FollowedThoughts from "@/lib/content/FollowedThoughts.svelte"; import FollowedThoughts from "@/lib/content/FollowedThoughts.svelte";
import ArrRequestButton from "@/lib/request/ArrRequestButton.svelte"; import ArrRequestButton from "@/lib/request/ArrRequestButton.svelte";
@@ -57,11 +56,9 @@
if (!data.tvId) { if (!data.tvId) {
return; return;
} }
const resp = ( const resp = await req.get<Media>(`/content/tv/${data.tvId}`, {
await axios.get(`/content/tv/${data.tvId}`, { params: { region: store.userSettings?.country },
params: { region: store.userSettings?.country }, });
})
).data as Media;
if (resp) { if (resp) {
if (resp.name && resp.ids.tmdb) { if (resp.name && resp.ids.tmdb) {
contentExistsOnJellyfin("tv", resp.name, resp.ids.tmdb).then( contentExistsOnJellyfin("tv", resp.name, resp.ids.tmdb).then(
@@ -84,8 +81,9 @@
}); });
async function getTvCredits() { async function getTvCredits() {
const credits = (await axios.get(`/content/tv/${data.tvId}/credits`)) const credits = await req.get<
.data as TMDBContentCredits & { topCrew: TMDBContentCreditsCrew[] }; TMDBContentCredits & { topCrew: TMDBContentCreditsCrew[] }
>(`/content/tv/${data.tvId}/credits`);
if (credits.crew?.length > 0) { if (credits.crew?.length > 0) {
credits.topCrew = getTopCrew(credits.crew); credits.topCrew = getTopCrew(credits.crew);
} }
+55 -47
View File
@@ -3,9 +3,10 @@
import { page } from "$app/state"; import { page } from "$app/state";
import Icon from "@/lib/Icon.svelte"; import Icon from "@/lib/Icon.svelte";
import { type AvailableAuthProviders } from "@/types"; import { type AvailableAuthProviders } from "@/types";
import { noAuthAxios } from "@/lib/util/api"; import { noAuthReq } from "@/lib/util/api";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { notify, unNotify } from "@/lib/util/notify"; import { notify, unNotify } from "@/lib/util/notify";
import { ReqerError } from "@/lib/util/fetch";
let error: string | undefined = $state(); let error: string | undefined = $state();
let login = $state(true); let login = $state(true);
@@ -31,27 +32,46 @@
noAuto = true; noAuto = true;
} }
noAuthAxios.get<AvailableAuthProviders>("/auth/available").then((r) => { processAvailableLoginMethods();
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);
}
}
});
}); });
/**
* 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) { function handleLogin(ev: SubmitEvent) {
ev.preventDefault(); ev.preventDefault();
const fd = new FormData(ev.target! as HTMLFormElement); const fd = new FormData(ev.target! as HTMLFormElement);
@@ -69,15 +89,15 @@
} }
const nid = notify({ text: "Logging in", type: "loading" }); const nid = notify({ text: "Logging in", type: "loading" });
noAuthAxios noAuthReq
.post(`/auth${login ? `/${customAuthEP}` : "/register"}`, { .post(`/auth${login ? `/${customAuthEP}` : "/register"}`, {
username: user, username: user,
password: pass, password: pass,
}) })
.then((resp) => { .then((resp: any) => {
if (resp.data?.token) { if (resp?.token) {
console.log("Received token... logging in."); console.log("Received token... logging in.");
localStorage.setItem("token", resp.data.token); localStorage.setItem("token", resp.token);
if (useEmby) { if (useEmby) {
localStorage.setItem("useEmby", "1"); localStorage.setItem("useEmby", "1");
} else { } else {
@@ -88,11 +108,7 @@
} }
}) })
.catch((err) => { .catch((err) => {
if (err.response) { error = ReqerError.getMsg(err, "Login failed");
error = err.response.data.error;
} else {
error = err.message;
}
unNotify(nid); unNotify(nid);
}); });
} }
@@ -111,26 +127,22 @@
return; return;
} }
const nid = notify({ text: "Logging in", type: "loading" }); const nid = notify({ text: "Logging in", type: "loading" });
noAuthAxios noAuthReq
.post("/auth/plex", { .post("/auth/plex", {
token, token,
clientIdentifier: p.clientId, clientIdentifier: p.clientId,
}) })
.then((resp) => { .then((resp: any) => {
if (resp.data?.token) { if (resp?.token) {
console.log("Received token... logging in."); console.log("Received token... logging in.");
localStorage.setItem("token", resp.data.token); localStorage.setItem("token", resp.token);
goto("/"); goto("/");
notify({ id: nid, text: `Welcome!`, type: "success" }); notify({ id: nid, text: `Welcome!`, type: "success" });
} }
}) })
.catch((err) => { .catch((err) => {
console.error("plexLogin: Fail", err); console.error("plexLogin: Fail", err);
if (err.response) { error = ReqerError.getMsg(err, "Login failed");
error = err.response.data.error;
} else {
error = err.message;
}
notify({ id: nid, text: `Failed!`, type: "error" }); notify({ id: nid, text: `Failed!`, type: "error" });
}); });
}); });
@@ -142,22 +154,18 @@
function proxyLogin(auto = false) { function proxyLogin(auto = false) {
const nid = notify({ text: "Logging in", type: "loading" }); const nid = notify({ text: "Logging in", type: "loading" });
noAuthAxios noAuthReq
.post(`/auth/proxy`) .post(`/auth/proxy`)
.then((resp) => { .then((resp: any) => {
if (resp.data?.token) { if (resp?.token) {
console.log("Received token... logging in."); console.log("Received token... logging in.");
localStorage.setItem("token", resp.data.token); localStorage.setItem("token", resp.token);
goto("/"); goto("/");
notify({ id: nid, text: `Welcome!`, type: "success" }); notify({ id: nid, text: `Welcome!`, type: "success" });
} }
}) })
.catch((err) => { .catch((err) => {
if (err.response) { error = ReqerError.getMsg(err, "Login failed");
error = err.response.data.error;
} else {
error = err.message;
}
if (auto) { if (auto) {
notify({ notify({
id: nid, id: nid,
+13 -17
View File
@@ -1,22 +1,21 @@
<script lang="ts"> <script lang="ts">
import { preventDefault } from "svelte/legacy";
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import type { AvailableAuthProviders } from "@/types"; import type { AvailableAuthProviders } from "@/types";
import { noAuthAxios } from "@/lib/util/api";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { notify, unNotify } from "@/lib/util/notify"; 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(() => { onMount(() => {
if (localStorage.getItem("token")) { if (localStorage.getItem("token")) {
goto("/"); goto("/");
} }
noAuthAxios.get<AvailableAuthProviders>("/auth/available").then((r) => { noAuthReq.get<AvailableAuthProviders>("/auth/available").then((r) => {
if (r?.data) { if (r) {
if (!r?.data?.isInSetup) { if (!r.isInSetup) {
console.log("Server not in setup.. navigating to login page."); console.log("Server not in setup.. navigating to login page.");
goto("/login"); goto("/login");
} }
@@ -25,6 +24,7 @@
}); });
function handleLogin(ev: SubmitEvent) { function handleLogin(ev: SubmitEvent) {
ev.preventDefault();
const fd = new FormData(ev.target! as HTMLFormElement); const fd = new FormData(ev.target! as HTMLFormElement);
const user = fd.get("username"); const user = fd.get("username");
const pass = fd.get("password"); const pass = fd.get("password");
@@ -35,25 +35,21 @@
} }
const nid = notify({ text: "Setting Up Admin User", type: "loading" }); const nid = notify({ text: "Setting Up Admin User", type: "loading" });
noAuthAxios noAuthReq
.post("/setup/create_admin", { .post<any>("/setup/create_admin", {
username: user, username: user,
password: pass, password: pass,
}) })
.then((resp) => { .then((resp) => {
if (resp.data?.token) { if (resp.token) {
console.log("Received token... logging in."); console.log("Received token... logging in.");
localStorage.setItem("token", resp.data.token); localStorage.setItem("token", resp.token);
goto("/"); goto("/");
notify({ id: nid, text: `Welcome ${user}!`, type: "success" }); notify({ id: nid, text: `Welcome ${user}!`, type: "success" });
} }
}) })
.catch((err) => { .catch((err) => {
if (err.response) { error = ReqerError.getMsg(err, "Setting up user failed");
error = err.response.data.error;
} else {
error = err.message;
}
unNotify(nid); unNotify(nid);
}); });
} }
@@ -74,7 +70,7 @@
<span class="error">{error}!</span> <span class="error">{error}!</span>
{/if} {/if}
<form onsubmit={preventDefault(handleLogin)}> <form onsubmit={handleLogin}>
<label for="username">Username</label> <label for="username">Username</label>
<input type="text" name="username" placeholder="Username" /> <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 * Return our `activeSort` and `activeFilters` in an object
* that is in the correct format for our get watched page * 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() { get sortAndFiltersForQueryParams() {
return _store.sortAndFiltersForQueryParams; return _store.sortAndFiltersForQueryParams;