Compare commits

..

40 Commits

Author SHA1 Message Date
Michael Thomas
321865f916 test: update cypress login command 2025-02-16 10:33:33 -05:00
Michael Thomas
1b84c89300 feat: revamp login screen
Update the login screen for better usability, especially with OpenID
Connect and Plex login, allowing one-click login and removing the
accordion layout. Additionally, ensures that media server login is
hidden when disabled in the settings.
2025-02-16 10:33:33 -05:00
Michael Thomas
3f6e5cc84a feat: support disabling jellyfin login 2025-02-16 10:33:33 -05:00
854562
c181cee328 feat: update Jellyfin logo (#1359)
* Replaced Jellyfin logo with new version

* Update jellyfin.svg
2025-02-16 11:37:59 +01:00
Gauthier
98a5b05816 ci: set the pnpm version number explicitly (#1357)
* ci: set the pnpm version number explicitly

pnpm latest version is now v10. We still use v9, and the latest version of pnpm was used in
Dockerfile instead of v9.

* ci: add missing pnpm v9
2025-02-14 17:21:08 +01:00
0xsysr3ll
b29959b063 fix: missing plex.tv url in images remotePatterns (#1356)
This fixes a 400 error while fetching user's avatar from plex api during user import.
2025-02-14 15:28:26 +01:00
Bryan J.
9a2c12e51c docs: update error message for various components (#1354) 2025-02-14 11:47:47 +01:00
Gauthier
620135aeac fix: resolve a vulnerability with admin token (#1345)
By default, the jellyfinAuthToken of every user was always retrieved from the database, and
sometimes sent back to the client. Any logged-in user could retrieve this token via a request
containing admin user information, and use it to gain full access to Jellyfin. This PR removes the
auth token and the device ID from the fields selected by default by TypeORM.
2025-02-10 00:17:11 +01:00
Gauthier
2dbd1096d2 fix: disallow admins to edit other admins in bulk edit (#1340)
This PR fixes a bug where admin users could edit the permissions of other admins in the bulk edit
modal.

fix #1309
2025-02-09 01:12:54 +08:00
Gauthier
24d3f523fc feat: add a robots.txt file (#1335)
This PR adds a `robots.txt` file to prevent crawlers to index the website

re #1323
2025-02-08 02:23:37 +08:00
Gauthier
2b7974fa06 fix(jobs): run plex/jellyfin jobs only for the relevant media server (#1331)
Due to merging issues with upstream, some jobs for the Plex media server where also running on
Jellyfin/Emby instances. This PR makes them run only when the media server is Plex.

fix #1329
2025-02-05 05:01:02 +08:00
Ben Haney
907ba6fdea feat(api): make rottentomatoes matching more robust (#1265) 2025-01-31 23:04:34 +08:00
fallenbagel
efaad21554 build: remove unnecessary files from final docker image (#1314)
* build: remove charts from final docker image

fix #1313

* build: remove docs too
2025-01-30 19:48:16 +08:00
Gauthier
6ab463285d fix(setup): resolve looping library validation error message (#1316)
This PR fixes a bug where the validation error message is displayed over and over because of a React
useEffect dependency issue. Previously, the `validateLibraries()` function was being called inside a
useEffect that depended on a state that this function was updating.
2025-01-30 11:22:23 +01:00
Ludovic Ortega
418f0c2eb8 fix(helm): no change, fixing OCI manifest corruption (#1310)
Signed-off-by: Ludovic Ortega <ludovic.ortega@adminafk.fr>
2025-01-27 15:49:24 +01:00
fallenbagel
002557d2d0 docs: changed the name to lowercase (#1296) 2025-01-21 17:20:33 +08:00
Ludovic Ortega
62c1a70b37 feat(helm): Add possibility to pass volumes and volume mounts (#1291)
Signed-off-by: Ludovic Ortega <ludovic.ortega@adminafk.fr>
2025-01-20 20:20:27 +08:00
allcontributors[bot]
1b325e7c32 docs: add andrewkolda as a contributor for design (#1293)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2025-01-20 06:41:25 +08:00
andrewkolda
f247642b76 fix: make watchlist buttons consistent (#1272)
fix #1270

Co-authored-by: andrewkolda <git@kolda.me>
2025-01-19 23:15:54 +01:00
Fallenbagel
396cd968ef ci: push individual arch digest only and merge from digest (#1284)
* ci: push individual arch digest only and merge from digest

* ci: correct syntax for docker manifest

* ci: add the missing id to the build step

* ci: set proper ids and output digest that is dependant on matrix.id

* ci: proper dynamic outputs by manually echoing it out

* ci: remove unnecessary test step
2025-01-19 03:41:25 +08:00
fallenbagel
ca739315b2 ci: remove cleanup steps since docker registry v2 doesnt support it 2025-01-18 22:28:58 +08:00
fallenbagel
9143a6c027 ci: push temp tags and create multi-arch from them and cleanup 2025-01-18 22:19:39 +08:00
fallenbagel
d7fc03650f ci: use direct docker manifest command to create and push manifest 2025-01-18 21:54:09 +08:00
fallenbagel
80fc5c1a78 ci: better manifest merging to only push final multi-arch manifest 2025-01-18 21:43:14 +08:00
fallenbagel
95737d36e6 ci: fix typo in create_manifest 2025-01-18 20:59:29 +08:00
fallenbagel
0fd6ca85a4 ci: fix missing version for create_manifest & discord notification after create_manifest 2025-01-18 20:53:09 +08:00
fallenbagel
7cee9b475d ci: fix multi-arch image creation workflow
use int128/docker-manifest-create-action to combine the images
2025-01-18 20:50:22 +08:00
fallenbagel
ff9af866f8 ci: use the proper action to merge manifests & pass in lowercase owner 2025-01-18 20:40:03 +08:00
fallenbagel
5ffe6419ee ci: remove sanitisation and hardcode platform tag depending on platform 2025-01-18 20:31:18 +08:00
fallenbagel
8afcf5a8d8 ci: attempt to sanitise the platfom and add to gh env 2025-01-18 20:25:06 +08:00
Fallenbagel
17d93a8cb9 ci: fix typo when passing sanitised platform to github env (#1282)
* ci: fix typo when passing sanitised platform to github env

* ci: hardcode a platform & owner for testing sanitation

* ci: fix typo

* ci: fix typo

* ci: fix yet another typo

* ci: fix yet another typo

* ci: another typo when echoing the tested variables fixed

* ci: properly echo the values from github env

* ci: attempt to echo out the sanitised variables

* ci: finalise the sanitation test and remove it from lint & test build
2025-01-18 20:23:03 +08:00
Fallenbagel
549082c53e ci: fix typo when sanitising platform (#1281) 2025-01-18 19:55:36 +08:00
Fallenbagel
fbef7e2c72 ci: seperate job to pass in sanitised platform (#1280)
This is done as github actions doesnt support inline replacement that was done on #1279
2025-01-18 19:49:58 +08:00
Fallenbagel
93d2e26ae9 ci: sanitise container tag (#1279)
* ci: sanitise container tag

Tags cant container `/` so this should sanitise them

* ci: use simple case owner name
2025-01-18 19:42:05 +08:00
Fallenbagel
f09a432635 ci: add a job to merge and create multi-arch image (#1278)
This has to be done now that arm64 and amd64 runs as two seperate jobs. Otherwise, whichever
finishes the last would override the other one when pushed
2025-01-18 19:34:36 +08:00
Fallenbagel
a8f84d4f74 ci: correct arm runner label (#1277)
The issue was all because of using the wrong label. 18 hours wasted waiting for a non-existent
runner to start. It is `ubuntu-24.04-arm`
https://github.com/orgs/community/discussions/148648#discussion-7793082
2025-01-18 19:07:45 +08:00
Fallenbagel
88e96fa163 ci: upgrade runner to ubuntu 24.04 to get arm64 runner working (#1276)
This is another attempt to get arm64 runner working by upgrading the runner to ubuntu-24-04, hoping it works better.

Related to #1275
2025-01-18 17:57:07 +08:00
Fallenbagel
2d814c1416 ci: attempt to fix arm64 runners with proper scoped caching (#1275)
Added platform specific cache scoping and turned off provenance to prevent manifest merging. In
addition we are now using ubuntu24.04 in an attempt to get the job to run as ubuntu-22.04 were
stalled for more than 18 hours.
2025-01-18 17:49:35 +08:00
Fallenbagel
2f4b848b2c ci: utilise the linux arm64 hosted runners (#1271)
* ci: utilise the linux arm64 hosted runners

This is an attempt to utilise the linux arm64 hosted runners which should reduce the build times
significantly. In addition, this should leverage the github's built-in caching.

* ci: fix typo
2025-01-18 05:14:33 +08:00
Ludovic Ortega
0ee3e69a61 feat: upgrade chart to 2.0.0 (#1268)
- upgrade jellyseerr to 2.3.0
- disable HPA as it's not documented and not useful
- migrate container image to ghcr.io registry

Signed-off-by: Ludovic Ortega <ludovic.ortega@adminafk.fr>
2025-01-17 18:32:20 +08:00
48 changed files with 1255 additions and 1397 deletions

View File

@@ -592,6 +592,15 @@
"contributions": [
"infra"
]
},
{
"login": "andrewkolda",
"name": "andrewkolda",
"avatar_url": "https://avatars.githubusercontent.com/u/158614532?v=4",
"profile": "https://github.com/andrewkolda",
"contributions": [
"design"
]
}
]
}

View File

@@ -12,7 +12,7 @@ jobs:
test:
name: Lint & Test Build
if: github.event_name == 'pull_request'
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
container: node:22-alpine
steps:
- name: Checkout
@@ -43,15 +43,23 @@ jobs:
- name: Build
run: pnpm build
build_and_push:
build:
name: Build & Publish Docker Images
if: github.ref == 'refs/heads/develop' && !contains(github.event.head_commit.message, '[skip ci]')
runs-on: ubuntu-22.04
strategy:
matrix:
include:
- runner: ubuntu-24.04
platform: linux/amd64
- runner: ubuntu-24.04-arm
platform: linux/arm64
runs-on: ${{ matrix.runner }}
outputs:
digest-amd64: ${{ steps.set_outputs.outputs.digest-amd64 }}
digest-arm64: ${{ steps.set_outputs.outputs.digest-arm64 }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
@@ -70,24 +78,77 @@ jobs:
echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV}
env:
OWNER: ${{ github.repository_owner }}
- name: Build and push
- name: Docker metadata
id: meta
uses: docker/metadata-action@v4
with:
images: |
fallenbagel/jellyseerr
ghcr.io/${{ env.OWNER_LC }}/jellyseerr
tags: |
type=ref,event=branch
type=sha,prefix=,suffix=,format=short
- name: Build and push by digest
id: build
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
platforms: ${{ matrix.platform }}
push: true
build-args: |
COMMIT_TAG=${{ github.sha }}
tags: |
fallenbagel/jellyseerr:develop
ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop
outputs: |
type=image,push-by-digest=true,name=fallenbagel/jellyseerr,push=true
type=image,push-by-digest=true,name=ghcr.io/${{ env.OWNER_LC }}/jellyseerr,push=true
cache-from: type=gha,scope=${{ matrix.platform }}
cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
provenance: false
- name: Set outputs
id: set_outputs
run: |
platform="${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}"
echo "digest-${platform}=${{ steps.build.outputs.digest }}" >> $GITHUB_OUTPUT
merge_and_push:
name: Create and Push Multi-arch Manifest
needs: build
runs-on: ubuntu-24.04
steps:
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set lower case owner name
run: |
echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV}
env:
OWNER: ${{ github.repository_owner }}
- name: Create and push manifest
run: |
docker manifest create fallenbagel/jellyseerr:develop \
--amend fallenbagel/jellyseerr@${{ needs.build.outputs.digest-amd64 }} \
--amend fallenbagel/jellyseerr@${{ needs.build.outputs.digest-arm64 }}
docker manifest push fallenbagel/jellyseerr:develop
# GHCR manifest
docker manifest create ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop \
--amend ghcr.io/${{ env.OWNER_LC }}/jellyseerr@${{ needs.build.outputs.digest-amd64 }} \
--amend ghcr.io/${{ env.OWNER_LC }}/jellyseerr@${{ needs.build.outputs.digest-arm64 }}
docker manifest push ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop
discord:
name: Send Discord Notification
needs: build_and_push
needs: merge_and_push
if: always() && github.event_name != 'pull_request' && !contains(github.event.head_commit.message, '[skip ci]')
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
- name: Get Build Job Status
uses: technote-space/workflow-conclusion-action@v3

View File

@@ -1,566 +1,3 @@
# [2.3.0](https://github.com/fallenbagel/jellyseerr/compare/v2.2.3...v2.3.0) (2025-01-16)
### Bug Fixes
* correct typos for the special episodes setting ([#1209](https://github.com/fallenbagel/jellyseerr/issues/1209)) ([ebe7d11](https://github.com/fallenbagel/jellyseerr/commit/ebe7d11a5393f3d444dd9613854d6054af1ec58b)), closes [#1193](https://github.com/fallenbagel/jellyseerr/issues/1193) [#1208](https://github.com/fallenbagel/jellyseerr/issues/1208)
* **externalapi:** clear cache after a request is made ([#1217](https://github.com/fallenbagel/jellyseerr/issues/1217)) ([f718cec](https://github.com/fallenbagel/jellyseerr/commit/f718cec23fccbfd16fdb792c2778cd543b751799)), closes [#1207](https://github.com/fallenbagel/jellyseerr/issues/1207)
* **jellyfinlogin:** add proper error message when no admin user exists ([#1216](https://github.com/fallenbagel/jellyseerr/issues/1216)) ([ac90802](https://github.com/fallenbagel/jellyseerr/commit/ac908026dbb7ca06c0fb520bbb360120d6b87feb))
* optimize media status update to avoid lifecycle hook triggers ([#1218](https://github.com/fallenbagel/jellyseerr/issues/1218)) ([656cd91](https://github.com/fallenbagel/jellyseerr/commit/656cd91c9c90e57914b7fedb097f29e21fb18090))
* **overriderules:** allow override rules only when the service is created ([#1259](https://github.com/fallenbagel/jellyseerr/issues/1259)) ([ce1b39f](https://github.com/fallenbagel/jellyseerr/commit/ce1b39f73b953b6fa0a00948e72d24c43476bc5f))
* prevent TypeORM subscribers from calling itself over and over ([#1215](https://github.com/fallenbagel/jellyseerr/issues/1215)) ([d67ec57](https://github.com/fallenbagel/jellyseerr/commit/d67ec571c5950f04b85f5a268b38eb026a156320))
* resolve plex user mismatch due to caching issues ([#1242](https://github.com/fallenbagel/jellyseerr/issues/1242)) ([131a5a2](https://github.com/fallenbagel/jellyseerr/commit/131a5a2b0b1a235599940affc183b93c36f12ade)), closes [#1227](https://github.com/fallenbagel/jellyseerr/issues/1227)
* **settingsmigrator:** prevent region migration from running multiple times ([#1255](https://github.com/fallenbagel/jellyseerr/issues/1255)) ([1c6f536](https://github.com/fallenbagel/jellyseerr/commit/1c6f5362d773c850a5e58b5013f0d65474467e9c)), closes [#1251](https://github.com/fallenbagel/jellyseerr/issues/1251)
* **setup:** fix continue button disabled on refresh in setup 3 ([#1211](https://github.com/fallenbagel/jellyseerr/issues/1211)) ([0b331ca](https://github.com/fallenbagel/jellyseerr/commit/0b331ca579c75e546dcdbf0f1896e0f0ec3a89f1))
* **setup:** plex library setting validation ([#1233](https://github.com/fallenbagel/jellyseerr/issues/1233)) ([b8dbfaa](https://github.com/fallenbagel/jellyseerr/commit/b8dbfaaed083734b05a28a05bf100941dc673ea7))
* specify cached image type ([#1237](https://github.com/fallenbagel/jellyseerr/issues/1237)) ([d71ee58](https://github.com/fallenbagel/jellyseerr/commit/d71ee58302fe95c9c79e27b4edf317a98faf6f5c))
* **ui:** resolve streaming region dropdown overlap ([#1210](https://github.com/fallenbagel/jellyseerr/issues/1210)) ([2f0e493](https://github.com/fallenbagel/jellyseerr/commit/2f0e4932572497322df0d7d7f4377aeb9cc35d5b)), closes [#1206](https://github.com/fallenbagel/jellyseerr/issues/1206)
* **users:** correct request count query for PostgreSQL compatibility ([#1213](https://github.com/fallenbagel/jellyseerr/issues/1213)) ([f3ebf60](https://github.com/fallenbagel/jellyseerr/commit/f3ebf6028b23f803a1c8801b1541a444e8856421))
### Features
* Add latest tag to ghcr container image ([#1224](https://github.com/fallenbagel/jellyseerr/issues/1224)) ([b9dc9bc](https://github.com/fallenbagel/jellyseerr/commit/b9dc9bceb5805889c1ea3157c3ace880865eaf9c))
* Add release charts workflow ([#1140](https://github.com/fallenbagel/jellyseerr/issues/1140)) ([3cc34b0](https://github.com/fallenbagel/jellyseerr/commit/3cc34b0db6b868a6133408a69a60b7eab69d9ea3))
* **settings:** add settings for custom DNS servers and IPv4 resolution first ([#1266](https://github.com/fallenbagel/jellyseerr/issues/1266)) ([7fcc0eb](https://github.com/fallenbagel/jellyseerr/commit/7fcc0eb66d907e74b72197d6abee511150ab5e1e))
# [2.3.0](https://github.com/fallenbagel/jellyseerr/compare/v2.2.3...v2.3.0) (2025-01-16)
### Bug Fixes
* correct typos for the special episodes setting ([#1209](https://github.com/fallenbagel/jellyseerr/issues/1209)) ([ebe7d11](https://github.com/fallenbagel/jellyseerr/commit/ebe7d11a5393f3d444dd9613854d6054af1ec58b)), closes [#1193](https://github.com/fallenbagel/jellyseerr/issues/1193) [#1208](https://github.com/fallenbagel/jellyseerr/issues/1208)
* **externalapi:** clear cache after a request is made ([#1217](https://github.com/fallenbagel/jellyseerr/issues/1217)) ([f718cec](https://github.com/fallenbagel/jellyseerr/commit/f718cec23fccbfd16fdb792c2778cd543b751799)), closes [#1207](https://github.com/fallenbagel/jellyseerr/issues/1207)
* **jellyfinlogin:** add proper error message when no admin user exists ([#1216](https://github.com/fallenbagel/jellyseerr/issues/1216)) ([ac90802](https://github.com/fallenbagel/jellyseerr/commit/ac908026dbb7ca06c0fb520bbb360120d6b87feb))
* optimize media status update to avoid lifecycle hook triggers ([#1218](https://github.com/fallenbagel/jellyseerr/issues/1218)) ([656cd91](https://github.com/fallenbagel/jellyseerr/commit/656cd91c9c90e57914b7fedb097f29e21fb18090))
* **overriderules:** allow override rules only when the service is created ([#1259](https://github.com/fallenbagel/jellyseerr/issues/1259)) ([ce1b39f](https://github.com/fallenbagel/jellyseerr/commit/ce1b39f73b953b6fa0a00948e72d24c43476bc5f))
* prevent TypeORM subscribers from calling itself over and over ([#1215](https://github.com/fallenbagel/jellyseerr/issues/1215)) ([d67ec57](https://github.com/fallenbagel/jellyseerr/commit/d67ec571c5950f04b85f5a268b38eb026a156320))
* resolve plex user mismatch due to caching issues ([#1242](https://github.com/fallenbagel/jellyseerr/issues/1242)) ([131a5a2](https://github.com/fallenbagel/jellyseerr/commit/131a5a2b0b1a235599940affc183b93c36f12ade)), closes [#1227](https://github.com/fallenbagel/jellyseerr/issues/1227)
* **settingsmigrator:** prevent region migration from running multiple times ([#1255](https://github.com/fallenbagel/jellyseerr/issues/1255)) ([1c6f536](https://github.com/fallenbagel/jellyseerr/commit/1c6f5362d773c850a5e58b5013f0d65474467e9c)), closes [#1251](https://github.com/fallenbagel/jellyseerr/issues/1251)
* **setup:** fix continue button disabled on refresh in setup 3 ([#1211](https://github.com/fallenbagel/jellyseerr/issues/1211)) ([0b331ca](https://github.com/fallenbagel/jellyseerr/commit/0b331ca579c75e546dcdbf0f1896e0f0ec3a89f1))
* **setup:** plex library setting validation ([#1233](https://github.com/fallenbagel/jellyseerr/issues/1233)) ([b8dbfaa](https://github.com/fallenbagel/jellyseerr/commit/b8dbfaaed083734b05a28a05bf100941dc673ea7))
* specify cached image type ([#1237](https://github.com/fallenbagel/jellyseerr/issues/1237)) ([d71ee58](https://github.com/fallenbagel/jellyseerr/commit/d71ee58302fe95c9c79e27b4edf317a98faf6f5c))
* **ui:** resolve streaming region dropdown overlap ([#1210](https://github.com/fallenbagel/jellyseerr/issues/1210)) ([2f0e493](https://github.com/fallenbagel/jellyseerr/commit/2f0e4932572497322df0d7d7f4377aeb9cc35d5b)), closes [#1206](https://github.com/fallenbagel/jellyseerr/issues/1206)
* **users:** correct request count query for PostgreSQL compatibility ([#1213](https://github.com/fallenbagel/jellyseerr/issues/1213)) ([f3ebf60](https://github.com/fallenbagel/jellyseerr/commit/f3ebf6028b23f803a1c8801b1541a444e8856421))
### Features
* Add latest tag to ghcr container image ([#1224](https://github.com/fallenbagel/jellyseerr/issues/1224)) ([b9dc9bc](https://github.com/fallenbagel/jellyseerr/commit/b9dc9bceb5805889c1ea3157c3ace880865eaf9c))
* Add release charts workflow ([#1140](https://github.com/fallenbagel/jellyseerr/issues/1140)) ([3cc34b0](https://github.com/fallenbagel/jellyseerr/commit/3cc34b0db6b868a6133408a69a60b7eab69d9ea3))
* **settings:** add settings for custom DNS servers and IPv4 resolution first ([#1266](https://github.com/fallenbagel/jellyseerr/issues/1266)) ([7fcc0eb](https://github.com/fallenbagel/jellyseerr/commit/7fcc0eb66d907e74b72197d6abee511150ab5e1e))
## [2.2.3](https://github.com/fallenbagel/jellyseerr/compare/v2.2.2...v2.2.3) (2024-12-30)
### Bug Fixes
* properly fetch sonarr/radarr specific override rules ([#1199](https://github.com/fallenbagel/jellyseerr/issues/1199)) ([814a735](https://github.com/fallenbagel/jellyseerr/commit/814a7357c0c7418091e8d3e911adc403811c9dfe))
* **usersettings:** fix the streaming region setting toggling itself ([#1203](https://github.com/fallenbagel/jellyseerr/issues/1203)) ([7e94ad7](https://github.com/fallenbagel/jellyseerr/commit/7e94ad721026a03d3ae640ee2deb60e321cabf10)), closes [#1200](https://github.com/fallenbagel/jellyseerr/issues/1200)
## [2.2.2](https://github.com/fallenbagel/jellyseerr/compare/v2.2.1...v2.2.2) (2024-12-30)
### Bug Fixes
* **overriderules:** apply override rules to tv shows during request ([#1198](https://github.com/fallenbagel/jellyseerr/issues/1198)) ([f8a8ebd](https://github.com/fallenbagel/jellyseerr/commit/f8a8ebdf76f939ccc28ce7b39343e3a606c90b33)), closes [#1197](https://github.com/fallenbagel/jellyseerr/issues/1197) [#1195](https://github.com/fallenbagel/jellyseerr/issues/1195)
## [2.2.1](https://github.com/fallenbagel/jellyseerr/compare/v2.2.0...v2.2.1) (2024-12-30)
### Bug Fixes
* **overriderules:** apply override rules during request only for non-admin/non-auto-approve users ([#1197](https://github.com/fallenbagel/jellyseerr/issues/1197)) ([8da4870](https://github.com/fallenbagel/jellyseerr/commit/8da48709977fa0111225c3519f9128bea41867fc)), closes [#1195](https://github.com/fallenbagel/jellyseerr/issues/1195)
# [2.2.0](https://github.com/fallenbagel/jellyseerr/compare/v2.1.0...v2.2.0) (2024-12-29)
### Bug Fixes
* **avatarproxy:** add support for Emby avatars ([#1128](https://github.com/fallenbagel/jellyseerr/issues/1128)) ([17418f8](https://github.com/fallenbagel/jellyseerr/commit/17418f82af53362338aebe9602373a3c8fa027f7)), closes [#1101](https://github.com/fallenbagel/jellyseerr/issues/1101)
* **blacklist:** remove a "undefined" appearing when the blacklist modal closes ([#1142](https://github.com/fallenbagel/jellyseerr/issues/1142)) ([b01f98f](https://github.com/fallenbagel/jellyseerr/commit/b01f98f7e280a037eba303eeaa836f6623daa440))
* **discover:** display recent requests even if there is an error with *arr ([#1141](https://github.com/fallenbagel/jellyseerr/issues/1141)) ([fa443c0](https://github.com/fallenbagel/jellyseerr/commit/fa443c05bedfca8208bfb05ab02c3b0e678e4ca0))
* **discover:** resolve a typing issue with the WatchlistItem interface ([#1156](https://github.com/fallenbagel/jellyseerr/issues/1156)) ([de6e591](https://github.com/fallenbagel/jellyseerr/commit/de6e591baedacb33704216842dddaa2b96bfae19))
* **emby:** change default value of Accept-Encoding header ([#1157](https://github.com/fallenbagel/jellyseerr/issues/1157)) ([7c734bc](https://github.com/fallenbagel/jellyseerr/commit/7c734bc8732a511e62edfcc371028ead6b6f1b12))
* fix PostgreSQL migrations and TelegramMessageThreadId migration ([#1171](https://github.com/fallenbagel/jellyseerr/issues/1171)) ([0491a04](https://github.com/fallenbagel/jellyseerr/commit/0491a04ef1816e81bb495746cc529fc621e4e147))
* handle non-existent rottentomatoes rating for movies ([#1169](https://github.com/fallenbagel/jellyseerr/issues/1169)) ([347a24a](https://github.com/fallenbagel/jellyseerr/commit/347a24a97b354725c4ccb3b5a07793b96ff60b80))
* remove non-null requirement for some fields ([#1175](https://github.com/fallenbagel/jellyseerr/issues/1175)) ([13d15d1](https://github.com/fallenbagel/jellyseerr/commit/13d15d1dcf4a80bc0b544fecbeced706f2dbd816)), closes [#628](https://github.com/fallenbagel/jellyseerr/issues/628)
* **requestlist:** use default value of sort direction only if valid ([#1174](https://github.com/fallenbagel/jellyseerr/issues/1174)) ([59c22cc](https://github.com/fallenbagel/jellyseerr/commit/59c22ccc089c960b523ccfb69efc680b2687c353)), closes [#1147](https://github.com/fallenbagel/jellyseerr/issues/1147)
* **server/settings:** write settings to a temp file then move to avoid corruption ([#1067](https://github.com/fallenbagel/jellyseerr/issues/1067)) ([01bbece](https://github.com/fallenbagel/jellyseerr/commit/01bbeced65b82f5041462cd7a6c9016274acade4))
* **ui:** allow thetvdb images for unmatched series ([#1105](https://github.com/fallenbagel/jellyseerr/issues/1105)) ([9b151fe](https://github.com/fallenbagel/jellyseerr/commit/9b151feb4f44d631b44c88c089f184c4c93161c5)), closes [#1075](https://github.com/fallenbagel/jellyseerr/issues/1075)
* **ui:** display Rotten Tomatoes for 0% ratings ([#1178](https://github.com/fallenbagel/jellyseerr/issues/1178)) ([5345207](https://github.com/fallenbagel/jellyseerr/commit/534520794071d8530d6325460e61dabfcb46fbf0)), closes [#1166](https://github.com/fallenbagel/jellyseerr/issues/1166)
* **ui:** resize streaming service logos ([#1106](https://github.com/fallenbagel/jellyseerr/issues/1106)) ([fe5d016](https://github.com/fallenbagel/jellyseerr/commit/fe5d016929d18c38aef7a3d48e4828188131e025)), closes [#1103](https://github.com/fallenbagel/jellyseerr/issues/1103)
* use less strict validation for external URLs ([#1104](https://github.com/fallenbagel/jellyseerr/issues/1104)) ([14f316a](https://github.com/fallenbagel/jellyseerr/commit/14f316a9a6d91c25c43e07ae66923785f90b1fdf)), closes [#1068](https://github.com/fallenbagel/jellyseerr/issues/1068)
* use links instead of buttons for external links in movie/tv details page ([#923](https://github.com/fallenbagel/jellyseerr/issues/923)) ([5776715](https://github.com/fallenbagel/jellyseerr/commit/57767156f79cb0bcb761f6fc0907d747f126e146))
* use tmdb first as metadata provider and fallback to tvdb ([#1138](https://github.com/fallenbagel/jellyseerr/issues/1138)) ([84fd884](https://github.com/fallenbagel/jellyseerr/commit/84fd884052ea2177c92d144367c4b4ed1dde3b73)), closes [#1137](https://github.com/fallenbagel/jellyseerr/issues/1137)
* **usediscover hook:** fixing duplicate movies ([#708](https://github.com/fallenbagel/jellyseerr/issues/708)) ([39dbb7f](https://github.com/fallenbagel/jellyseerr/commit/39dbb7f7e59cf4b1b5f029089c6b1ea6a0d7e5f5))
* **usersettings:** allow unset email and add more explicit email error message ([#1096](https://github.com/fallenbagel/jellyseerr/issues/1096)) ([39a5ccb](https://github.com/fallenbagel/jellyseerr/commit/39a5ccb7f3a6ed4e93b12e11021bb30515936ce7))
### Features
* add a setting for special episodes ([#1193](https://github.com/fallenbagel/jellyseerr/issues/1193)) ([b6e2e6c](https://github.com/fallenbagel/jellyseerr/commit/b6e2e6ce615cb94cea8d2335140fe245a0ca2d8a))
* add postgres support + migrations ([#628](https://github.com/fallenbagel/jellyseerr/issues/628)) ([44a9221](https://github.com/fallenbagel/jellyseerr/commit/44a9221a9dca501fa57c0bcbd743aed9889059ff)), closes [#186](https://github.com/fallenbagel/jellyseerr/issues/186)
* **helm:** add base helm chart ([#1116](https://github.com/fallenbagel/jellyseerr/issues/1116)) ([27e3d46](https://github.com/fallenbagel/jellyseerr/commit/27e3d465bd7eaa3f382c961220f8af1860a15c7f))
* **notifications:** added telegram thread id's ([#1145](https://github.com/fallenbagel/jellyseerr/issues/1145)) ([d76d794](https://github.com/fallenbagel/jellyseerr/commit/d76d79441142ccc6fe2357549f39a1fba3546ff9))
* **notifications:** improve discord notifications ([#1102](https://github.com/fallenbagel/jellyseerr/issues/1102)) ([5c24e79](https://github.com/fallenbagel/jellyseerr/commit/5c24e79b1dddc3c8421e57e67302fa3dc064f87f))
* override rules ([#945](https://github.com/fallenbagel/jellyseerr/issues/945)) ([9a59529](https://github.com/fallenbagel/jellyseerr/commit/9a595296dbdd00bb3477052b53412e6019667740))
* **requestlist:** sort direction ([#1147](https://github.com/fallenbagel/jellyseerr/issues/1147)) ([66a5ab4](https://github.com/fallenbagel/jellyseerr/commit/66a5ab41ab646501f72a658782e8a89f9faf939f))
* **usersettings:** add separate setting for streaming region ([#993](https://github.com/fallenbagel/jellyseerr/issues/993)) ([89831f7](https://github.com/fallenbagel/jellyseerr/commit/89831f70909df0a76dfa8a027702e4e5f9b57be8)), closes [#890](https://github.com/fallenbagel/jellyseerr/issues/890)
# [2.1.0](https://github.com/fallenbagel/jellyseerr/compare/v2.0.1...v2.1.0) (2024-11-12)
### Bug Fixes
* **blacklist:** request data only when modal is shown, remove useless ratelimit and lazy load blacklist ([#1084](https://github.com/fallenbagel/jellyseerr/issues/1084)) ([694913c](https://github.com/fallenbagel/jellyseerr/commit/694913c767c558147f413e2375b2512567541127))
* cache Jellyfin/Emby avatars from API ([#1045](https://github.com/fallenbagel/jellyseerr/issues/1045)) ([0bbcfcb](https://github.com/fallenbagel/jellyseerr/commit/0bbcfcbd5e03137aba35ceb07e42f623aefa41d7))
* **externalapi:** extract basic auth and pass it through header ([#1062](https://github.com/fallenbagel/jellyseerr/issues/1062)) ([cf59102](https://github.com/fallenbagel/jellyseerr/commit/cf59102ef91fa0e907cc6369b0fe60b503c823ca)), closes [#1027](https://github.com/fallenbagel/jellyseerr/issues/1027)
* fixes wrong avatar rendered for the modifiedBy user in request list ([#1028](https://github.com/fallenbagel/jellyseerr/issues/1028)) ([cbb1a74](https://github.com/fallenbagel/jellyseerr/commit/cbb1a74526ef5c003b7081c31146c52e7e551d60)), closes [#1017](https://github.com/fallenbagel/jellyseerr/issues/1017)
* **i18n:** update extractMessages function for better escaping of characters ([#1079](https://github.com/fallenbagel/jellyseerr/issues/1079)) ([a2d2fd3](https://github.com/fallenbagel/jellyseerr/commit/a2d2fd3c2a53fc98d6288bd049fd8e37a1914280))
* remove language profiles dropdown for Sonarr v4 ([#1000](https://github.com/fallenbagel/jellyseerr/issues/1000)) ([d331798](https://github.com/fallenbagel/jellyseerr/commit/d331798b28a7bd32a27fc0ccbad2354be2e15b02)), closes [#207](https://github.com/fallenbagel/jellyseerr/issues/207)
* resolve error when setup on second attempt ([#1061](https://github.com/fallenbagel/jellyseerr/issues/1061)) ([64f4610](https://github.com/fallenbagel/jellyseerr/commit/64f4610b9ffcad01c24ecdd81b8b3a2f3db4c98d))
* **setup:** add leading slash validation for baseUrl ([#1083](https://github.com/fallenbagel/jellyseerr/issues/1083)) ([2829c25](https://github.com/fallenbagel/jellyseerr/commit/2829c2548aa0cd03f92433d3bc3b9b2739e98486))
* update i18n translations ([#1090](https://github.com/fallenbagel/jellyseerr/issues/1090)) ([f25b32a](https://github.com/fallenbagel/jellyseerr/commit/f25b32aec8ec3c2fd40ccfc6a83f18ddc99c1a15))
* use fs/promises for settings ([#1057](https://github.com/fallenbagel/jellyseerr/issues/1057)) ([f2ed101](https://github.com/fallenbagel/jellyseerr/commit/f2ed101e522561dab8563b744d908ff036c957c5))
### Features
* add a warning if permissions are missing from config folder ([#1030](https://github.com/fallenbagel/jellyseerr/issues/1030)) ([f2b6315](https://github.com/fallenbagel/jellyseerr/commit/f2b63156d1d4aa903eb261d2c80c059c39d9091b))
* add bypass list, bypass local addresses and username/password to proxy setting ([#1059](https://github.com/fallenbagel/jellyseerr/issues/1059)) ([ca838a0](https://github.com/fallenbagel/jellyseerr/commit/ca838a00fa4acb0ccdfbac8be4cf7fde493346f7))
* add more logs to migrations and create a settings backup ([#1036](https://github.com/fallenbagel/jellyseerr/issues/1036)) ([326001c](https://github.com/fallenbagel/jellyseerr/commit/326001c3ecc92dc730f327130a71e797882a62b9))
* exit Jellyseerr when migration fails ([#1026](https://github.com/fallenbagel/jellyseerr/issues/1026)) ([a2b3408](https://github.com/fallenbagel/jellyseerr/commit/a2b3408c9aa5e22e1193f535c969325254f08193))
* proxy setting ([#1031](https://github.com/fallenbagel/jellyseerr/issues/1031)) ([4b4eeb6](https://github.com/fallenbagel/jellyseerr/commit/4b4eeb6ec707e0971fe8745910edbfb546bf25fe))
## [2.0.1](https://github.com/fallenbagel/jellyseerr/compare/v2.0.0...v2.0.1) (2024-10-17)
### Bug Fixes
* fetch override to attach XSRF token to fix csrfProtection issue ([#1014](https://github.com/fallenbagel/jellyseerr/issues/1014)) ([4945b54](https://github.com/fallenbagel/jellyseerr/commit/4945b5429848b36fc0ee41cf0277ed79f53d8286)), closes [#1011](https://github.com/fallenbagel/jellyseerr/issues/1011)
* handle non-existent rottentomatoes rating ([#1018](https://github.com/fallenbagel/jellyseerr/issues/1018)) ([a351264](https://github.com/fallenbagel/jellyseerr/commit/a351264b878b2660ae7a6415f26d38b52015c591))
* rewrite avatarproxy and CachedImage ([#1016](https://github.com/fallenbagel/jellyseerr/issues/1016)) ([4e48fdf](https://github.com/fallenbagel/jellyseerr/commit/4e48fdf2cb9f76ae5c25073b585718650abd3288)), closes [#1012](https://github.com/fallenbagel/jellyseerr/issues/1012) [#1013](https://github.com/fallenbagel/jellyseerr/issues/1013)
* use jellyfinMediaId4k for mediaUrl4k ([#1006](https://github.com/fallenbagel/jellyseerr/issues/1006)) ([a0f80fe](https://github.com/fallenbagel/jellyseerr/commit/a0f80fe7647ef4a9025ca93407cd21ddc640fed1)), closes [#520](https://github.com/fallenbagel/jellyseerr/issues/520)
# [2.0.0](https://github.com/fallenbagel/jellyseerr/compare/v1.9.2...v2.0.0) (2024-10-15)
### Bug Fixes
* abort availability sync job if auth token invalid/connection lost ([#845](https://github.com/fallenbagel/jellyseerr/issues/845)) ([bdee340](https://github.com/fallenbagel/jellyseerr/commit/bdee34053080c8975a88ba16a9e8f402e10fe7e1))
* add an error message to say when an email is already taken ([#947](https://github.com/fallenbagel/jellyseerr/issues/947)) ([89e0a83](https://github.com/fallenbagel/jellyseerr/commit/89e0a831ec85a6905f539f59b7523bb1feb90bcf))
* add missing brackets ([#888](https://github.com/fallenbagel/jellyseerr/issues/888)) ([6cea8bb](https://github.com/fallenbagel/jellyseerr/commit/6cea8bba592b8db566b4d8147630385f5c377f1b))
* add missing content-type header ([#887](https://github.com/fallenbagel/jellyseerr/issues/887)) ([2be9c7d](https://github.com/fallenbagel/jellyseerr/commit/2be9c7dcc1f418726a19e99cfdb3933257a03c6f))
* add missing header when creating an issue ([#879](https://github.com/fallenbagel/jellyseerr/issues/879)) ([084e1b2](https://github.com/fallenbagel/jellyseerr/commit/084e1b224e109f0f8279741b9a5ead138396d7f8))
* add missing parameter to delete requests from ExternalAPI ([#904](https://github.com/fallenbagel/jellyseerr/issues/904)) ([36d98a2](https://github.com/fallenbagel/jellyseerr/commit/36d98a2681921a8770027b78878688f2782e8b77)), closes [#903](https://github.com/fallenbagel/jellyseerr/issues/903)
* **api:** fix nextjs error handler ([#882](https://github.com/fallenbagel/jellyseerr/issues/882)) ([0116c13](https://github.com/fallenbagel/jellyseerr/commit/0116c13e0632d1ccec43299fbb10cd71db45bc29))
* **api:** handle non-existent ratings on IMDb ([#822](https://github.com/fallenbagel/jellyseerr/issues/822)) ([74a2d25](https://github.com/fallenbagel/jellyseerr/commit/74a2d25f153b07a0cae5b44adca5fa1fed5a3b9e))
* **api:** save new password when reset password of local account ([#886](https://github.com/fallenbagel/jellyseerr/issues/886)) ([5cc4389](https://github.com/fallenbagel/jellyseerr/commit/5cc43898256b130c2576f34a3d4e7ce6a3940d3e))
* **blacklist:** add blacklist to mobile menu ([#980](https://github.com/fallenbagel/jellyseerr/issues/980)) ([f390da4](https://github.com/fallenbagel/jellyseerr/commit/f390da486625a22951956ba96867de63f73bfc2b)), closes [#979](https://github.com/fallenbagel/jellyseerr/issues/979)
* change SeriesSearch to MissingEpisodeSearch for season requests ([#711](https://github.com/fallenbagel/jellyseerr/issues/711)) ([ee7e91c](https://github.com/fallenbagel/jellyseerr/commit/ee7e91c7c948b17b556a625919eb1252a721bb6e))
* **docker:** add postinstall script ([#839](https://github.com/fallenbagel/jellyseerr/issues/839)) ([f714132](https://github.com/fallenbagel/jellyseerr/commit/f7141329094d88eb0940b1db1f21376142cb8893))
* enhance error messages when Fetch API fails ([#893](https://github.com/fallenbagel/jellyseerr/issues/893)) ([fccfca6](https://github.com/fallenbagel/jellyseerr/commit/fccfca6ed06c8dc599e1ea4b1b3dbac48eb3a7f6))
* handle status badge for season packs ([#927](https://github.com/fallenbagel/jellyseerr/issues/927)) ([80f6301](https://github.com/fallenbagel/jellyseerr/commit/80f63017ac5e9b1720a19c761dbef4dd517f1c2c))
* length of undefined on users warnings ([#875](https://github.com/fallenbagel/jellyseerr/issues/875)) ([c600566](https://github.com/fallenbagel/jellyseerr/commit/c600566ac0045c2314f9013b063007b087ee4327))
* remove DNS caching ([#837](https://github.com/fallenbagel/jellyseerr/issues/837)) ([268c7df](https://github.com/fallenbagel/jellyseerr/commit/268c7df28eea8b911d6a53297f5ce296983067ce))
* remove email requirement for the user, and use the username if no email provided ([#900](https://github.com/fallenbagel/jellyseerr/issues/900)) ([d5f817e](https://github.com/fallenbagel/jellyseerr/commit/d5f817e734131cdacc229361d9498a095af57950))
* remove protocol-relative URLs from next/image ([#889](https://github.com/fallenbagel/jellyseerr/issues/889)) ([c80d9a8](https://github.com/fallenbagel/jellyseerr/commit/c80d9a853a2a3451293a5382ef183c18add0c040))
* resize episode preview image ([#842](https://github.com/fallenbagel/jellyseerr/issues/842)) ([96ba53f](https://github.com/fallenbagel/jellyseerr/commit/96ba53fecc7b9d269f0d974051ab62836b0102bc))
* resize header image in network and studio pages ([#902](https://github.com/fallenbagel/jellyseerr/issues/902)) ([4220855](https://github.com/fallenbagel/jellyseerr/commit/422085523e5dfc132f3c3ca19eaa87117828b7be))
* rewrite request from axios to Fetch ([#920](https://github.com/fallenbagel/jellyseerr/issues/920)) ([9aee888](https://github.com/fallenbagel/jellyseerr/commit/9aee8887d3cca6e018f4be1c8400c22e86bf8dab))
* rewrite the rate limit utility ([#896](https://github.com/fallenbagel/jellyseerr/issues/896)) ([3fc14c9](https://github.com/fallenbagel/jellyseerr/commit/3fc14c9e2262463afec666e7f54e38d0d36cff68))
* **session:** set the correct TTL for the cookie store ([#992](https://github.com/fallenbagel/jellyseerr/issues/992)) ([96e1d40](https://github.com/fallenbagel/jellyseerr/commit/96e1d40304749ce00d2ff7359efc39a1d9724358)), closes [#991](https://github.com/fallenbagel/jellyseerr/issues/991)
* set correct user type when importing from emby ([#949](https://github.com/fallenbagel/jellyseerr/issues/949)) ([e57d265](https://github.com/fallenbagel/jellyseerr/commit/e57d2654d1c634a91649722d3a2bf4d73c4a02ca)), closes [#948](https://github.com/fallenbagel/jellyseerr/issues/948)
* **setup:** page display when homepage is loading ([#940](https://github.com/fallenbagel/jellyseerr/issues/940)) ([7423bbb](https://github.com/fallenbagel/jellyseerr/commit/7423bbbffc5bee2e52e3348254f035dc8527d973))
* **tmdb:** fallback movie/show overview to English when none is available in requested locale ([#928](https://github.com/fallenbagel/jellyseerr/issues/928)) ([12f908d](https://github.com/fallenbagel/jellyseerr/commit/12f908de7f5fbd717a5f151858b6edee3be13ed9)), closes [#925](https://github.com/fallenbagel/jellyseerr/issues/925)
* update the filter removing existing users from Jellyfin import modal ([#924](https://github.com/fallenbagel/jellyseerr/issues/924)) ([61dcd8e](https://github.com/fallenbagel/jellyseerr/commit/61dcd8e487d7886773ccb12501623c17838476e5))
### Code Refactoring
* **jellyfin:** abstract jellyfin hostname, updated ui to reflect it, better validation ([#773](https://github.com/fallenbagel/jellyseerr/issues/773)) ([38ad875](https://github.com/fallenbagel/jellyseerr/commit/38ad875dd7848b4e92ac3ccdd16dbf785f6a5c4d))
### Features
* add environment variable for API key ([#831](https://github.com/fallenbagel/jellyseerr/issues/831)) ([45ef150](https://github.com/fallenbagel/jellyseerr/commit/45ef150e36944d456cc9440574b5ac75f2e4bbc1))
* adds status filter for tv shows ([#796](https://github.com/fallenbagel/jellyseerr/issues/796)) ([cfd1bc2](https://github.com/fallenbagel/jellyseerr/commit/cfd1bc253557d6e19725743b8aa9a2fa33bbe760)), closes [#605](https://github.com/fallenbagel/jellyseerr/issues/605)
* allow request managers to delete data from sonarr/radarr ([#644](https://github.com/fallenbagel/jellyseerr/issues/644)) ([a5d22ba](https://github.com/fallenbagel/jellyseerr/commit/a5d22ba5b83dd0e812b16f06476d993b5d59cb2a))
* blacklist items from Discover page ([#632](https://github.com/fallenbagel/jellyseerr/issues/632)) ([818aa60](https://github.com/fallenbagel/jellyseerr/commit/818aa60aac185da07bfb71b08e0448939b63a736)), closes [#490](https://github.com/fallenbagel/jellyseerr/issues/490)
* Jellyfin/Emby server type setup ([#685](https://github.com/fallenbagel/jellyseerr/issues/685)) ([15cb949](https://github.com/fallenbagel/jellyseerr/commit/15cb949f1f2e617853f90ae7bb8ae5d6622f610e))
* **jellyfinapi:** switch to API tokens instead of auth tokens ([#868](https://github.com/fallenbagel/jellyseerr/issues/868)) ([bd4da6d](https://github.com/fallenbagel/jellyseerr/commit/bd4da6d5fc8cb55c2bc3d9a8336787cbd30814d0))
* Option on item's page to add/remove from watchlist ([#781](https://github.com/fallenbagel/jellyseerr/issues/781)) ([2348f23](https://github.com/fallenbagel/jellyseerr/commit/2348f23f433195d64dee3e6eeede296fca5fdbc9)), closes [#730](https://github.com/fallenbagel/jellyseerr/issues/730)
* refresh monitored downloads before getting queue items ([#994](https://github.com/fallenbagel/jellyseerr/issues/994)) ([92ba262](https://github.com/fallenbagel/jellyseerr/commit/92ba26207dcb1ddd696e0f01931d2609c521ae45)), closes [#866](https://github.com/fallenbagel/jellyseerr/issues/866)
* show quality profile on request ([#847](https://github.com/fallenbagel/jellyseerr/issues/847)) ([6445332](https://github.com/fallenbagel/jellyseerr/commit/64453320d36595e75dcb710dfd43997bf2d2acd5))
* **translation:** added full Hebrew translation ([#871](https://github.com/fallenbagel/jellyseerr/issues/871)) ([c96ca67](https://github.com/fallenbagel/jellyseerr/commit/c96ca6742e0a6d5685319c52f995fe06e439a450))
* update Plex logo ([#884](https://github.com/fallenbagel/jellyseerr/issues/884)) ([3a363ae](https://github.com/fallenbagel/jellyseerr/commit/3a363ae1ffa7f384be6f7d25f8558b1e55a73fb3))
### Reverts
* fix(api): fix nextjs error handler ([#882](https://github.com/fallenbagel/jellyseerr/issues/882)) ([#892](https://github.com/fallenbagel/jellyseerr/issues/892)) ([62dbde4](https://github.com/fallenbagel/jellyseerr/commit/62dbde448c7f7d530de8534bb8538452d0f91276))
### BREAKING CHANGES
* This commit deprecates the JELLYFIN_TYPE variable to identify Emby media server and
instead rely on the mediaServerType that is set in the `settings.json`. Existing environment
variable users can log out and log back in to set the mediaServerType to `3` (Emby).
* feat(api): add severType to the api
* This adds a serverType to the `/auth/jellyfin` which requires a serverType to be
set (`jellyfin`/`emby`)
* refactor: use enums for serverType and rename selectedservice to serverType
* refactor(auth): jellyfin/emby authentication to set MediaServerType
* fix: issue page formatMessage for 4k media
* refactor: cleaner way of handling serverType change using MediaServerType instead of strings
instead of using strings now it will use MediaServerType enums for serverType
* revert: removed conditional render of the auto-request permission
reverts the conditional render toshow the auto-request permission if the mediaServerType was set to
Plex as this should be handled in a different PR and Cypress tests should be modified
accordingly(currently cypress test would fail if this conditional check is there)
* feat: add server type step to setup
* feat: migrate existing emby setups to use emby mediaServerType
* fix: scan jobs not running when media server type is emby
* fix: emby media server type migration
* refactor: change emby logo to full logo
* style: decrease emby logo size in setup screen
* refactor: use title case for servertype i18n message
* refactor(i18n): fix a typo
* refactor: use enums instead of numbers
* fix: remove old references to JELLYFIN_TYPE environment variable
* fix: go back to the last step when refresh the setup page
* fix: move "scanning in background" tip next to the scanning section
* fix: redirect the setup page when Jellyseerr is already setup
* **jellyfin:** Jellyfin settings now does not include a hostname. Instead it abstracted it to ip,
port, useSsl, and urlBase. However, migration of old settings to new settings should work
automatically.
* refactor: remove console logs and use getHostname and ApiErrorCodes
* fix: store req.body jellyfin settings temporarily and store only if valid
This should fix the issue where settings are saved even if the url
was invalid. Now the settings will only be saved if the url is
valid. Sort of like a test connection.
* refactor: clean up commented out code
* refactor(i18n): extract translation keys
* fix(auth): auth failing with jellyfin login is disabled
* fix(settings): jellyfin migrations replacing the rest of the settings
* fix(settings): jellyfin hostname should be carried out if hostname exists
* fix(settings): merging the wrong settings source
* refactor(settings): use migrator for dynamic settings migrations
* refactor(settingsmigrator): settings migration handler and the migrations
* test(cypress): fix cypress tests failing
cypress settings were lacking some of the jobs so when the startJobs() is called when the app
starts, it was failing to schedule the jobs where their cron timings were not specified in the
cypress settings. Therefore, this commit adds those jobs back. In addition, other setting options
were added to keep cypress settings consistent with a normal user.
* chore(prettierignore): ignore cypress/config/settings.cypress.json as it does not need prettier
* chore(prettier): ran formatter on cypress config to fix format check error
format check locally passes on this file. However, it fails during the github actions format check.
Therefore, json language features formatter was run instead of prettier to see if that fixes the
issue.
* test(cypress): add only missing jobs to the cypress settings
* ci: attempt at trying to get formatter to pass on cypress config json file
* refactor: revert the changes brought to try and fix formatter
added back the rest of the cypress settings and removed cypress settings from .prettierignore
* refactor(settings): better erorr logging when jellyfin connection test fails in settings page
## [1.9.2](https://github.com/fallenbagel/jellyseerr/compare/v1.9.1...v1.9.2) (2024-06-13)
### Bug Fixes
* **auth:** improve login resilience with headerless fallback authentication ([#814](https://github.com/fallenbagel/jellyseerr/issues/814)) ([a9741fa](https://github.com/fallenbagel/jellyseerr/commit/a9741fa36d06710aa00d28db3dd2c29f2b0973d3))
* **auth:** validation of ipv6/ipv4 ([#812](https://github.com/fallenbagel/jellyseerr/issues/812)) ([9aeb360](https://github.com/fallenbagel/jellyseerr/commit/9aeb3604e6498c388df1d30dd0b613ba84160fc0)), closes [#795](https://github.com/fallenbagel/jellyseerr/issues/795)
* bypass cache-able lookups when resolving localhost ([#813](https://github.com/fallenbagel/jellyseerr/issues/813)) ([b5a0699](https://github.com/fallenbagel/jellyseerr/commit/b5a069901a9545772deaa9c491f2075261da0189))
## [1.9.1](https://github.com/fallenbagel/jellyseerr/compare/v1.9.0...v1.9.1) (2024-06-12)
### Bug Fixes
* **api:** add DNS caching ([#810](https://github.com/fallenbagel/jellyseerr/issues/810)) ([46ee8a4](https://github.com/fallenbagel/jellyseerr/commit/46ee8a4ca13b026bd929b4027eb001cc74064bb8)), closes [#387](https://github.com/fallenbagel/jellyseerr/issues/387) [#657](https://github.com/fallenbagel/jellyseerr/issues/657) [#728](https://github.com/fallenbagel/jellyseerr/issues/728)
* empty email in user settings ([#807](https://github.com/fallenbagel/jellyseerr/issues/807)) ([20863d4](https://github.com/fallenbagel/jellyseerr/commit/20863d4a8dabe78fb5c52995b5bcb2da557a804e)), closes [#803](https://github.com/fallenbagel/jellyseerr/issues/803)
* **jellyfinscanner:** assign only 4k available badge for a 4k request instead of both badges ([#805](https://github.com/fallenbagel/jellyseerr/issues/805)) ([d31a2c3](https://github.com/fallenbagel/jellyseerr/commit/d31a2c37e639c1126b446277fa5d666d8102fef5))
* remove the settings button of media when useless ([#809](https://github.com/fallenbagel/jellyseerr/issues/809)) ([f52939e](https://github.com/fallenbagel/jellyseerr/commit/f52939e4cdcbee94fc35165f613f6b3e21599e3c))
### Reverts
* Revert "ci: update format check command to ignore .prettierignore files (#787)" (#788) ([4757f1c](https://github.com/fallenbagel/jellyseerr/commit/4757f1c3e599304410a737c11f97db92a2bfcefd)), closes [#787](https://github.com/fallenbagel/jellyseerr/issues/787) [#788](https://github.com/fallenbagel/jellyseerr/issues/788)
# [1.9.0](https://github.com/fallenbagel/jellyseerr/compare/v1.8.1...v1.9.0) (2024-05-29)
### Bug Fixes
* **api:** save user email on the first try ([#760](https://github.com/fallenbagel/jellyseerr/issues/760)) ([0bbcfdc](https://github.com/fallenbagel/jellyseerr/commit/0bbcfdc4f9ff9735f45232a2412ac8444f525de9)), closes [#227](https://github.com/fallenbagel/jellyseerr/issues/227) [#748](https://github.com/fallenbagel/jellyseerr/issues/748)
* **api:** small errors on overseerr-api.yaml ([#721](https://github.com/fallenbagel/jellyseerr/issues/721)) ([0eea109](https://github.com/fallenbagel/jellyseerr/commit/0eea1090dfdba4333646280c84b09b0197fefa74))
* **auth:** case-sensitive logins not updating authtokens ([#778](https://github.com/fallenbagel/jellyseerr/issues/778)) ([2bd125d](https://github.com/fallenbagel/jellyseerr/commit/2bd125d9a55d15a398ceb5f2996105a5e861b6e0))
* **jellyfinapi:** use external api class for jellyfin api requests ([#762](https://github.com/fallenbagel/jellyseerr/issues/762)) ([650c339](https://github.com/fallenbagel/jellyseerr/commit/650c339d74d4fe85ef7f76184901e86f4eeada85)), closes [#728](https://github.com/fallenbagel/jellyseerr/issues/728) [#387](https://github.com/fallenbagel/jellyseerr/issues/387)
* **logging:** handle media server connection refused error/toast ([#748](https://github.com/fallenbagel/jellyseerr/issues/748)) ([f486fb5](https://github.com/fallenbagel/jellyseerr/commit/f486fb5e75f9ea21456952b6a52cb841e30f3556))
* use UTF8 encoding for webhook JSON ([#714](https://github.com/fallenbagel/jellyseerr/issues/714)) ([c0a0b9c](https://github.com/fallenbagel/jellyseerr/commit/c0a0b9c8a8b0c2eeaf3fa9159f10742baa9f6c1f))
### Features
* add Latin American Spanish translation ([#725](https://github.com/fallenbagel/jellyseerr/issues/725)) ([783fda9](https://github.com/fallenbagel/jellyseerr/commit/783fda9621aef8ffd46e5f036136de82ed502ccc)), closes [#677](https://github.com/fallenbagel/jellyseerr/issues/677)
* add merge conflict labeler workflow ([#719](https://github.com/fallenbagel/jellyseerr/issues/719)) ([d9d07c7](https://github.com/fallenbagel/jellyseerr/commit/d9d07c705a24d5c49905066aac45a3c6a2e36a53))
* **auth:** send real information on login ([#470](https://github.com/fallenbagel/jellyseerr/issues/470)) ([d765055](https://github.com/fallenbagel/jellyseerr/commit/d765055da83ee94546399f6348aee14d8427d462))
* **settings:** stores jellyfin/emby server name in the settings ([#763](https://github.com/fallenbagel/jellyseerr/issues/763)) ([7a5e8d6](https://github.com/fallenbagel/jellyseerr/commit/7a5e8d69bf620c8e7bf5f284840b1a5fe757ae5f))
## [1.8.1](https://github.com/fallenbagel/jellyseerr/compare/v1.8.0...v1.8.1) (2024-04-17)
### Reverts
* Revert "fix: disable seasonfolder option in sonarr for jellyfin/Emby users" (#718) ([cd0fa3e](https://github.com/fallenbagel/jellyseerr/commit/cd0fa3e2232dcb522673143f113fc382fb2ff0a3)), closes [#718](https://github.com/fallenbagel/jellyseerr/issues/718)
# [1.8.0](https://github.com/fallenbagel/jellyseerr/compare/v1.7.0...v1.8.0) (2024-04-15)
### Bug Fixes
* correct width issue in datepicker of filterSliderOver ([f564cdd](https://github.com/fallenbagel/jellyseerr/commit/f564cddff4525ccebffbf304672d49c57aefe635)), closes [#415](https://github.com/fallenbagel/jellyseerr/issues/415)
* disable seasonfolder option in sonarr for jellyfin/Emby users ([8ec8f2a](https://github.com/fallenbagel/jellyseerr/commit/8ec8f2ac5730aad3b12dcd8ed95bb553b46b399c)), closes [#126](https://github.com/fallenbagel/jellyseerr/issues/126) [#575](https://github.com/fallenbagel/jellyseerr/issues/575)
* **embyauth:** remove the accidentally added mediaServerType change code from another PR ([#684](https://github.com/fallenbagel/jellyseerr/issues/684)) ([c2e8771](https://github.com/fallenbagel/jellyseerr/commit/c2e87714b4c4aa11bf68dcd82b76979f82990f3c))
* ensure watchlist updates are immediately reflected ([b85d7f3](https://github.com/fallenbagel/jellyseerr/commit/b85d7f37b931735ca2ad955dccb6599bf445fc73))
* fix german translation for "components.Discover.FilterSlideover.tmdbuservotecount" ([e032c02](https://github.com/fallenbagel/jellyseerr/commit/e032c02f5f84dc4b6b470eecb18ba2c376c55f37))
* fix the translations for watchlist permissions and userSettings page ([8c82a61](https://github.com/fallenbagel/jellyseerr/commit/8c82a61450a7525c0e2f1b64e6939da47a7c715d))
* **i18n:** fixed jellyfin jobs ([7eed236](https://github.com/fallenbagel/jellyseerr/commit/7eed23637ddfb10bdcb19698e7ae171f07299502))
* **jellyfin.ts:** process virtual seasons if they have non virtual episodes ([#639](https://github.com/fallenbagel/jellyseerr/issues/639)) ([db84f65](https://github.com/fallenbagel/jellyseerr/commit/db84f6529ab285be26c96daaab065dfabf347417))
* **jellyfinapi:** refactors jellyfin library sync to support automatic grouping and collections ([#700](https://github.com/fallenbagel/jellyseerr/issues/700)) ([3856061](https://github.com/fallenbagel/jellyseerr/commit/3856061fe1ee4d3457996586b4979ad9dd60765a)), closes [#450](https://github.com/fallenbagel/jellyseerr/issues/450) [#524](https://github.com/fallenbagel/jellyseerr/issues/524) [#256](https://github.com/fallenbagel/jellyseerr/issues/256) [#489](https://github.com/fallenbagel/jellyseerr/issues/489) [#450](https://github.com/fallenbagel/jellyseerr/issues/450) [#524](https://github.com/fallenbagel/jellyseerr/issues/524) [#515](https://github.com/fallenbagel/jellyseerr/issues/515) [#474](https://github.com/fallenbagel/jellyseerr/issues/474) [#473](https://github.com/fallenbagel/jellyseerr/issues/473)
* **jellyfinlogin:** use externalHostname if set for forgetpassword link ([405f6bb](https://github.com/fallenbagel/jellyseerr/commit/405f6bbb7ffc390327c99dcef2cbbf9b3bc75f01)), closes [#199](https://github.com/fallenbagel/jellyseerr/issues/199) [#424](https://github.com/fallenbagel/jellyseerr/issues/424) [#212](https://github.com/fallenbagel/jellyseerr/issues/212)
* **jellyfinscanner:** conditionally assign the jellyfinMediaId and jellyfinMediaId4k ([#686](https://github.com/fallenbagel/jellyseerr/issues/686)) ([530be42](https://github.com/fallenbagel/jellyseerr/commit/530be4272cce1b0d74d7f4156b8d794cda6ea03f)), closes [#681](https://github.com/fallenbagel/jellyseerr/issues/681)
* **langcode:** fixes the ukranian language code ([dc67aaa](https://github.com/fallenbagel/jellyseerr/commit/dc67aaaf53eae86ba20c6c2798c92ec40962d85f)), closes [#504](https://github.com/fallenbagel/jellyseerr/issues/504)
* nullable type for jellyfinMediaId(4k) ([#702](https://github.com/fallenbagel/jellyseerr/issues/702)) ([0900a95](https://github.com/fallenbagel/jellyseerr/commit/0900a95532501b6f4d9698de7530a771512924fc)), closes [#668](https://github.com/fallenbagel/jellyseerr/issues/668)
* request watchlist items sequentially to prevent bypassing quota ([#3667](https://github.com/fallenbagel/jellyseerr/issues/3667)) ([b40ba07](https://github.com/fallenbagel/jellyseerr/commit/b40ba07a4de5857b8392f667038eeb0b22aa5d9a))
* resolved issue with region selector and all regions value ([#3652](https://github.com/fallenbagel/jellyseerr/issues/3652)) ([28a2c50](https://github.com/fallenbagel/jellyseerr/commit/28a2c50495d0ce531da7f8c442bd488a54b1e84c))
* typos on readme ([#655](https://github.com/fallenbagel/jellyseerr/issues/655)) ([eee9a02](https://github.com/fallenbagel/jellyseerr/commit/eee9a025d246c72bcd3aca753d9e49c1f8f064ea))
* **watchlist:** added missing prop for watchlist item removal button in watchlist page ([a0ec992](https://github.com/fallenbagel/jellyseerr/commit/a0ec992028093257e9fa043622e236014f02dea3))
* **watchlist:** discover local watchlist item display and profile local watchlist slider visibility ([3cb9494](https://github.com/fallenbagel/jellyseerr/commit/3cb9494e6210151716587d8c4b22e0a21692cf88))
### Features
* add ko language ([#3619](https://github.com/fallenbagel/jellyseerr/issues/3619)) ([9250735](https://github.com/fallenbagel/jellyseerr/commit/92507359b48db08b0066047d6505660b8c8b0b12))
* add Peacock to Network Slider ([#3545](https://github.com/fallenbagel/jellyseerr/issues/3545)) ([0c39057](https://github.com/fallenbagel/jellyseerr/commit/0c39057ca58743697e9dcc3b678440ac3688c65a))
* add tooltips to tautulli avatars ([#3601](https://github.com/fallenbagel/jellyseerr/issues/3601)) ([c484810](https://github.com/fallenbagel/jellyseerr/commit/c484810f965f8d04643c25c6d283dd83f4bd4a23))
* added Letterboxd links for the external link blocks for movies ([981f5e6](https://github.com/fallenbagel/jellyseerr/commit/981f5e679c4c707e119741240a58de8bb07f9d6c))
* check if first jellyfin user is admin ([#635](https://github.com/fallenbagel/jellyseerr/issues/635)) ([010df62](https://github.com/fallenbagel/jellyseerr/commit/010df62776191fe4c195e590df338f8d8523f55b)), closes [#610](https://github.com/fallenbagel/jellyseerr/issues/610)
* jellyseerr makeover ([#715](https://github.com/fallenbagel/jellyseerr/issues/715)) ([0c27132](https://github.com/fallenbagel/jellyseerr/commit/0c2713213c56de342f76300d12ce01fd543d2ce3))
* **job:** media availability support for jellyfin/emby ([#522](https://github.com/fallenbagel/jellyseerr/issues/522)) ([3eb1bb3](https://github.com/fallenbagel/jellyseerr/commit/3eb1bb3d8ff22391acb2e629bbec7b6e4b65ca95)), closes [#406](https://github.com/fallenbagel/jellyseerr/issues/406) [#193](https://github.com/fallenbagel/jellyseerr/issues/193) [#516](https://github.com/fallenbagel/jellyseerr/issues/516) [#362](https://github.com/fallenbagel/jellyseerr/issues/362) [#84](https://github.com/fallenbagel/jellyseerr/issues/84)
* **notif:** add Pushover sound options ([#2403](https://github.com/fallenbagel/jellyseerr/issues/2403)) ([3ea5076](https://github.com/fallenbagel/jellyseerr/commit/3ea5076053359b518b1b4d537e7b61580d9275a3))
* select default seriesType for anime ([#3627](https://github.com/fallenbagel/jellyseerr/issues/3627)) ([f628635](https://github.com/fallenbagel/jellyseerr/commit/f6286359cfd2ed93fc692aa2efda37310e02c11c)), closes [#3626](https://github.com/fallenbagel/jellyseerr/issues/3626)
* standard series type selector ([#3628](https://github.com/fallenbagel/jellyseerr/issues/3628)) ([7bdd25e](https://github.com/fallenbagel/jellyseerr/commit/7bdd25e5a45843a3e530d3fa2b0887664b53eec8))
* translations update from Hosted Weblate ([#3258](https://github.com/fallenbagel/jellyseerr/issues/3258)) ([e62a078](https://github.com/fallenbagel/jellyseerr/commit/e62a078298ced7dec627fb3ff9fc8f99a39d5e1b))
* update SameSite policy of session cookie to Lax ([#3650](https://github.com/fallenbagel/jellyseerr/issues/3650)) ([c84ca43](https://github.com/fallenbagel/jellyseerr/commit/c84ca4307465af4278f3dad5cf9c2b8cbae3fada))
### Reverts
* **jellyfinapi:** reverts [#450](https://github.com/fallenbagel/jellyseerr/issues/450) as it broke library sync support for local accounts using LDAP ([b5acc09](https://github.com/fallenbagel/jellyseerr/commit/b5acc09ba98e2dd9b61e6b78721e4dd9f42a996c)), closes [#489](https://github.com/fallenbagel/jellyseerr/issues/489)
# [1.7.0](https://github.com/fallenbagel/jellyseerr/compare/v1.6.0...v1.7.0) (2023-09-14)
### Bug Fixes
* adjust the plex watchlist sync schedule to have fuzziness ([#3502](https://github.com/fallenbagel/jellyseerr/issues/3502)) ([2c3f533](https://github.com/fallenbagel/jellyseerr/commit/2c3f5330764492e1323afd2d1f25e28ad78a2f2f))
* handle issue causing incorrect media to change to unknown ([#3516](https://github.com/fallenbagel/jellyseerr/issues/3516)) ([83b008c](https://github.com/fallenbagel/jellyseerr/commit/83b008c8391459bd02dc74bcdb0d8caf27207bdf))
* improved handling of edge case that could cause availability sync to fail ([#3497](https://github.com/fallenbagel/jellyseerr/issues/3497)) ([d0836ce](https://github.com/fallenbagel/jellyseerr/commit/d0836ce0efd55fccf2546087a0c4f94f7cb2e82a))
* Include all defaults in payload ([#3538](https://github.com/fallenbagel/jellyseerr/issues/3538)) ([cb63bf2](https://github.com/fallenbagel/jellyseerr/commit/cb63bf217b9e8810a5210b4bf475b2a96583cc84))
* multiple notifications for available media ([048fa96](https://github.com/fallenbagel/jellyseerr/commit/048fa967f2e5b23831ac9917c703934c50ef75f0))
* repeat notifications for available 4k media ([30361f2](https://github.com/fallenbagel/jellyseerr/commit/30361f2ab751d9a882a9120e0f3df28dc42cc2cd))
* resolved issue with create slider causing incorrect form submission ([#3514](https://github.com/fallenbagel/jellyseerr/issues/3514)) ([a761b7d](https://github.com/fallenbagel/jellyseerr/commit/a761b7dd35a5bd61bb4eb0275b75d1e0977e6a2d))
* resolved user access check issue ([#3551](https://github.com/fallenbagel/jellyseerr/issues/3551)) ([2816c66](https://github.com/fallenbagel/jellyseerr/commit/2816c66300bf870d493c0665b0e984d60f707dfd))
* **server/api/jellyfin.ts:** use /Library/VirtualFolders Jellyfin API call to fetch Jellyfin libs ([8685f57](https://github.com/fallenbagel/jellyseerr/commit/8685f5796a99d9700146bae9892319db10508d68)), closes [#256](https://github.com/fallenbagel/jellyseerr/issues/256)
* **statusbadge:** handle missing season/episode number ([#3526](https://github.com/fallenbagel/jellyseerr/issues/3526)) ([01de972](https://github.com/fallenbagel/jellyseerr/commit/01de972a8fe2ea3c18d5b2f426d01b5b14d142d4))
* **tautulli:** only test connection if hostname is defined ([#3573](https://github.com/fallenbagel/jellyseerr/issues/3573)) ([f7b4dfc](https://github.com/fallenbagel/jellyseerr/commit/f7b4dfcac472d08c54779a14fc1ad3c90927df26))
* **ui:** corrected issues icon color ([#3498](https://github.com/fallenbagel/jellyseerr/issues/3498)) ([c1a47bd](https://github.com/fallenbagel/jellyseerr/commit/c1a47bd9de332cb4925974690f5a33448b5cc2e6))
### Features
* **rating:** added IMDB Radarr proxy ([#3496](https://github.com/fallenbagel/jellyseerr/issues/3496)) ([b4191f9](https://github.com/fallenbagel/jellyseerr/commit/b4191f9c65b7ff08764e61d18e7a75bc8d4b3325))
# [1.6.0](https://github.com/fallenbagel/jellyseerr/compare/v1.5.0...v1.6.0) (2023-08-04)
### Bug Fixes
* availability sync file detection ([#3371](https://github.com/fallenbagel/jellyseerr/issues/3371)) ([7522aa3](https://github.com/fallenbagel/jellyseerr/commit/7522aa31743b169c903ebdf9d4d698645d27514c))
* corrected initial fallback data load on details page ([#3395](https://github.com/fallenbagel/jellyseerr/issues/3395)) ([4bd8764](https://github.com/fallenbagel/jellyseerr/commit/4bd87647d0551c20e13589a62690a6f3e5ad8ff7))
* correctly load series fallback modal with sonarr v4 ([#3451](https://github.com/fallenbagel/jellyseerr/issues/3451)) ([e051b1d](https://github.com/fallenbagel/jellyseerr/commit/e051b1dfea9c9320cc9dd420c475ae74cff0d901))
* **deps:** update all non-major dependencies ([#3223](https://github.com/fallenbagel/jellyseerr/issues/3223)) ([f5191ad](https://github.com/fallenbagel/jellyseerr/commit/f5191aded680357522a65bbdcc40d162b8fbf594))
* error deleting users with over 1000 requests ([#3376](https://github.com/fallenbagel/jellyseerr/issues/3376)) ([ac77b03](https://github.com/fallenbagel/jellyseerr/commit/ac77b037d5fb0c54f5edf4b29d04adb57aef388f))
* external url regex is now consistent with internal url ([33ec443](https://github.com/fallenbagel/jellyseerr/commit/33ec4436fb82e1eb1bc97dd650088c27785e9d94))
* externalLinkBlock ([46cd4d0](https://github.com/fallenbagel/jellyseerr/commit/46cd4d01d9a3cf17d79350c5e678202820272299))
* fix regex for internal url to use a more effecient one ([e848386](https://github.com/fallenbagel/jellyseerr/commit/e848386d10f05f157e7a6dde8847ecab50c169ac))
* fixes RT ratings for tv shows ([#3492](https://github.com/fallenbagel/jellyseerr/issues/3492)) ([04fbd00](https://github.com/fallenbagel/jellyseerr/commit/04fbd00d4ac29045592588ef8b664d1916991e37)), closes [#3491](https://github.com/fallenbagel/jellyseerr/issues/3491)
* **genreselector:** fix searching in Genre filter ([#3468](https://github.com/fallenbagel/jellyseerr/issues/3468)) ([d7fa35e](https://github.com/fallenbagel/jellyseerr/commit/d7fa35e066cf371797aaa46ca464aa531ba8fb35))
* handle search results with collections ([#3393](https://github.com/fallenbagel/jellyseerr/issues/3393)) ([70b1540](https://github.com/fallenbagel/jellyseerr/commit/70b1540ae23e83e01013856a9e06ad39e600922d))
* lock body scroll when using webkit ([#3399](https://github.com/fallenbagel/jellyseerr/issues/3399)) ([c27f960](https://github.com/fallenbagel/jellyseerr/commit/c27f96096ac8cc6c387f9d1dde5b263576ac2132))
* **logs:** jellyfin auth error now has the severity warn consistent with local login ([cc041b5](https://github.com/fallenbagel/jellyseerr/commit/cc041b5e0aa2b67573edba5919772b77a5111162)), closes [#224](https://github.com/fallenbagel/jellyseerr/issues/224)
* make a (shallow) copy of radarr/sonarr tags into a request before adding user tags ([#3485](https://github.com/fallenbagel/jellyseerr/issues/3485)) ([48f7666](https://github.com/fallenbagel/jellyseerr/commit/48f76662d5c08156f1da3f47e216c5f02668f64b))
* **ui:** corrected default badge hover opacity ([#3369](https://github.com/fallenbagel/jellyseerr/issues/3369)) ([a4d07f5](https://github.com/fallenbagel/jellyseerr/commit/a4d07f5afab613317d96c9c6e9b47157a5a28986))
* **ui:** corrected mobile menu spacing in collection details ([#3432](https://github.com/fallenbagel/jellyseerr/issues/3432)) ([77a33cb](https://github.com/fallenbagel/jellyseerr/commit/77a33cb74d744bb747b791785799b632af8c7862))
* **ui:** Make play symbol white ([1fe4bb8](https://github.com/fallenbagel/jellyseerr/commit/1fe4bb8a0415a72791ced75a2fba1027287398d5))
* **ui:** Resize Emby icon and add margins ([ad69d67](https://github.com/fallenbagel/jellyseerr/commit/ad69d6715e976630092bfbbb1843886523551014))
* **watchlist:** add validation for creation request ([03316c6](https://github.com/fallenbagel/jellyseerr/commit/03316c642d1ecf89753789af08caf6e3aac80113))
* **watchlist:** fix github code scanning ([c08897b](https://github.com/fallenbagel/jellyseerr/commit/c08897bdc1cff65862c62347572bbbd01b6c36ac))
### Features
* **add watchlist:** adding midding functionality from overserr ([5f1c10d](https://github.com/fallenbagel/jellyseerr/commit/5f1c10d50aaa430bcda96218ef2cc12a0eb926f3))
* adds streaming services custom slider ([#3361](https://github.com/fallenbagel/jellyseerr/issues/3361)) ([2520d8f](https://github.com/fallenbagel/jellyseerr/commit/2520d8f739abfde608f3ef66a9fbe6b7b5c6647a))
* auto tagging requested media with username ([#3338](https://github.com/fallenbagel/jellyseerr/issues/3338)) ([24f268b](https://github.com/fallenbagel/jellyseerr/commit/24f268b6cb67d9a8d8675cd6e09dd83a7f499add))
* **discover:** support filtering by tmdb user vote count on discover page ([#3407](https://github.com/fallenbagel/jellyseerr/issues/3407)) ([aa84977](https://github.com/fallenbagel/jellyseerr/commit/aa849776809dfe891e67ff4db6861ef44df1a774))
* **settings:** add internal url to jellyfin settings form ([0a30cd3](https://github.com/fallenbagel/jellyseerr/commit/0a30cd356d217a39546c016cc8bfa6ff6ad75e3e)), closes [#194](https://github.com/fallenbagel/jellyseerr/issues/194)
* **src/components/externallinkblock/index.tsx:** support Emby icon ([672061c](https://github.com/fallenbagel/jellyseerr/commit/672061cd646c97c9954790c8e50eac88ea2666e9))
* **tooltip:** email tooltip now appears when hovered over info icon ([cd7930e](https://github.com/fallenbagel/jellyseerr/commit/cd7930eef98451a781e5c9dc5ec223600a379f42))
* translations update ([47287c3](https://github.com/fallenbagel/jellyseerr/commit/47287c368885d14bd1a56e3e8318ce22dd0f6ddf)), closes [#381](https://github.com/fallenbagel/jellyseerr/issues/381)
* **watchlist:** add translation for en ([b7e3d28](https://github.com/fallenbagel/jellyseerr/commit/b7e3d285ed35b623062eceb0d99035cafbf075a6))
# [1.5.0](https://github.com/fallenbagel/jellyseerr/compare/v1.4.1...v1.5.0) (2023-04-20)
### Bug Fixes
* add better checks on 4k detection of series ([bc9017f](https://github.com/fallenbagel/jellyseerr/commit/bc9017f54d84ec24c4d74d38e1b4e24219425d41))
* added a refresh interval if download status is in progress ([#3275](https://github.com/fallenbagel/jellyseerr/issues/3275)) ([1e2c6f4](https://github.com/fallenbagel/jellyseerr/commit/1e2c6f46ab66c836f321b5d8e34f1e8124c0b542))
* **build:** increase threshold for amount of data to be fetched when SSR'ing ([#3320](https://github.com/fallenbagel/jellyseerr/issues/3320)) ([d7b83d2](https://github.com/fallenbagel/jellyseerr/commit/d7b83d22cee3d20db564cc0564d42802b02327e3))
* disable availability sync temporarily ([2e5cf22](https://github.com/fallenbagel/jellyseerr/commit/2e5cf226265686012329248e7f729fec324c3deb))
* hide remove button when default service is not configured ([7d4455b](https://github.com/fallenbagel/jellyseerr/commit/7d4455ba6bfd12e2730f7085cbb87df246f01d22))
* **jellyfin scan:** temporary workaround fix for jellyfin scan when display specials within season ([38fb66d](https://github.com/fallenbagel/jellyseerr/commit/38fb66d31e41232c01898d0d362af8338eb7b960)), closes [#215](https://github.com/fallenbagel/jellyseerr/issues/215) [#176](https://github.com/fallenbagel/jellyseerr/issues/176) [#246](https://github.com/fallenbagel/jellyseerr/issues/246)
* lint issues ([bcd2bb7](https://github.com/fallenbagel/jellyseerr/commit/bcd2bb7c96810f5a6932f42468a628d2db1bc771))
* logger was set to info for the wrong logs ([#3354](https://github.com/fallenbagel/jellyseerr/issues/3354)) ([c36a4ba](https://github.com/fallenbagel/jellyseerr/commit/c36a4ba2b8df05873f5dfd0946a9bc3dc4ecfd1d))
* remove unnecessary parenthesis from api key generation ([#3336](https://github.com/fallenbagel/jellyseerr/issues/3336)) ([6bd3f01](https://github.com/fallenbagel/jellyseerr/commit/6bd3f015d65507efca60279007bd2b86ee860643))
* **snapcraft:** use the correct config folder for image cache ([#3302](https://github.com/fallenbagel/jellyseerr/issues/3302)) ([c93467b](https://github.com/fallenbagel/jellyseerr/commit/c93467b3acf2c256324297e7e8f21e9944005dd4))
* **ui:** hide mini status badge if non-4K media status is unknown ([#3346](https://github.com/fallenbagel/jellyseerr/issues/3346)) ([50f06da](https://github.com/fallenbagel/jellyseerr/commit/50f06dabbffc693f0843584a64d1d96e77982820))
* **ui:** hide search bar behind slideover when opened ([#3348](https://github.com/fallenbagel/jellyseerr/issues/3348)) ([b3882de](https://github.com/fallenbagel/jellyseerr/commit/b3882de8930a70adb2f93a27be6370bfa1826587))
* **ui:** prevent title cards from flickering when quickly hovering across them ([#3349](https://github.com/fallenbagel/jellyseerr/issues/3349)) ([eb5502a](https://github.com/fallenbagel/jellyseerr/commit/eb5502a16f86e37a933f6beca0678c2d228e77d5))
* **watchlist:** correctly load more than 20 watchlist items ([#3351](https://github.com/fallenbagel/jellyseerr/issues/3351)) ([af880a6](https://github.com/fallenbagel/jellyseerr/commit/af880a6c839794b34bddcd7e0fe56353aa48ba36))
### Features
* add a button in ManageSlideOver to remove the movie and the file from Radarr/Sonarr ([2e74584](https://github.com/fallenbagel/jellyseerr/commit/2e7458457e995dd3ec6dd96035fe997646cdd446))
* availability sync rework ([#3219](https://github.com/fallenbagel/jellyseerr/issues/3219)) ([ae38183](https://github.com/fallenbagel/jellyseerr/commit/ae3818304b2f75222d1bd223ece94f829a3b42d0)), closes [#377](https://github.com/fallenbagel/jellyseerr/issues/377)
* full title of download item on hover with tooltip ([#3296](https://github.com/fallenbagel/jellyseerr/issues/3296)) ([33e7691](https://github.com/fallenbagel/jellyseerr/commit/33e7691b94d7d369a0a1410e434850bc51e5572e))
### Performance Improvements
* **imageproxy:** do not set cookies to image proxy so CDNs can cache images ([#3332](https://github.com/fallenbagel/jellyseerr/issues/3332)) ([966639d](https://github.com/fallenbagel/jellyseerr/commit/966639df430d32f6bfebdb16314dc4590d21caf8))
## [1.4.1](https://github.com/fallenbagel/jellyseerr/compare/v1.4.0...v1.4.1) (2023-01-31)
### Bug Fixes
* pass in library type when scanning recently added items ([#3287](https://github.com/fallenbagel/jellyseerr/issues/3287)) ([8942eb8](https://github.com/fallenbagel/jellyseerr/commit/8942eb8b7c4fa1d16aa2e72e8ba7120a653c9aa2))
* **ui:** air date will use UTC for timezone ([#3297](https://github.com/fallenbagel/jellyseerr/issues/3297)) ([3e43586](https://github.com/fallenbagel/jellyseerr/commit/3e43586acc0804c3fff524509caa890a104e132b))
* **ui:** correct range slider styling in chrome ([#3299](https://github.com/fallenbagel/jellyseerr/issues/3299)) ([d954328](https://github.com/fallenbagel/jellyseerr/commit/d9543289111d72245564d25d300a71b0ea3954ba))
* **ui:** show 5 icons when possible on mobile menu ([#3298](https://github.com/fallenbagel/jellyseerr/issues/3298)) ([7040da1](https://github.com/fallenbagel/jellyseerr/commit/7040da1334f6d18e19a494c73caa17f7df552dfe))
* **ui:** style range thumbs correctly for firefox ([#3294](https://github.com/fallenbagel/jellyseerr/issues/3294)) ([9d10e6a](https://github.com/fallenbagel/jellyseerr/commit/9d10e6a88c0996671f1d9d20792e1930dbc82329))
# [1.4.0](https://github.com/fallenbagel/jellyseerr/compare/v1.3.0...v1.4.0) (2023-01-29)
### Bug Fixes
* add bg-opacity to in-progress status badges ([#3190](https://github.com/fallenbagel/jellyseerr/issues/3190)) ([68223f4](https://github.com/fallenbagel/jellyseerr/commit/68223f4b1e98b01825516dcba39cbb2d3df31a70))
* added download status and title to request card/item error components ([#3186](https://github.com/fallenbagel/jellyseerr/issues/3186)) ([3309f77](https://github.com/fallenbagel/jellyseerr/commit/3309f77aa4be1d70b27693531c119a8e26822518))
* arrow icons were misplaced on mobile in slider edit ([#3260](https://github.com/fallenbagel/jellyseerr/issues/3260)) ([d328485](https://github.com/fallenbagel/jellyseerr/commit/d328485161b9cae6a70ef0713b4878207bc6015e))
* **build:** update usage of publish snap action ([#3272](https://github.com/fallenbagel/jellyseerr/issues/3272)) ([51b05cd](https://github.com/fallenbagel/jellyseerr/commit/51b05cd8fbb5d332807d8c00b2ffb7b10c3d0179))
* changed overflow scroll to only if necessary ([#3184](https://github.com/fallenbagel/jellyseerr/issues/3184)) ([27feeea](https://github.com/fallenbagel/jellyseerr/commit/27feeea69121336557deda1f32b65a5daa146f82))
* convert genre/studio to string in create slider ([#3201](https://github.com/fallenbagel/jellyseerr/issues/3201)) ([93afead](https://github.com/fallenbagel/jellyseerr/commit/93afead92e497f2e5bce67a34fffdaa08d20c7f2))
* correct checkbox position (again) for slider edits ([#3227](https://github.com/fallenbagel/jellyseerr/issues/3227)) ([3ba6df1](https://github.com/fallenbagel/jellyseerr/commit/3ba6df1a41c084c4a6a90354338047623abef521))
* correct grid sizing for webkit on streaming services ([#3248](https://github.com/fallenbagel/jellyseerr/issues/3248)) ([6fd11cf](https://github.com/fallenbagel/jellyseerr/commit/6fd11cf4254e1a19310592bec78a6de52bc073a8))
* correct issue detail bottom padding on mobile displays ([#3268](https://github.com/fallenbagel/jellyseerr/issues/3268)) ([3db010b](https://github.com/fallenbagel/jellyseerr/commit/3db010b9eaec62aa08d973a61caf1801471bbf3e))
* correct link to correct keyword results for series ([#3208](https://github.com/fallenbagel/jellyseerr/issues/3208)) ([4e9be7a](https://github.com/fallenbagel/jellyseerr/commit/4e9be7a3f7304ee7be5ee6fd34b1ea8f6c0cf399))
* correct spacing between sliders ([#3225](https://github.com/fallenbagel/jellyseerr/issues/3225)) ([62e2de7](https://github.com/fallenbagel/jellyseerr/commit/62e2de70bf37b72d5f63370b662d4103a642775b))
* correctly check mobile menu permissions ([#3271](https://github.com/fallenbagel/jellyseerr/issues/3271)) ([f4a22dc](https://github.com/fallenbagel/jellyseerr/commit/f4a22dc437404558f301ccfc195cf0a300dd1ff2))
* correctly restore selected streaming service filters ([#3249](https://github.com/fallenbagel/jellyseerr/issues/3249)) ([154f3e7](https://github.com/fallenbagel/jellyseerr/commit/154f3e72efbf0b663358b3029156f54516f01a2f))
* create shared class to add bottom spacing ([#3269](https://github.com/fallenbagel/jellyseerr/issues/3269)) ([5d1c6f7](https://github.com/fallenbagel/jellyseerr/commit/5d1c6f706555613d97ed9e61d8b665543c2f239b))
* **deps:** pin dependency @headlessui/react to 1.7.7 ([#3194](https://github.com/fallenbagel/jellyseerr/issues/3194)) [skip ci] ([c4b16ab](https://github.com/fallenbagel/jellyseerr/commit/c4b16abc62647c74215155942a4230a31a238677))
* **deps:** update dependency @heroicons/react to v2 ([#2970](https://github.com/fallenbagel/jellyseerr/issues/2970)) ([dd48d59](https://github.com/fallenbagel/jellyseerr/commit/dd48d59b20e2d1800ea30912116f4a4f1bb7928f))
* **deps:** update dependency axios to v1 ([#3202](https://github.com/fallenbagel/jellyseerr/issues/3202)) ([421029e](https://github.com/fallenbagel/jellyseerr/commit/421029ebab66c9a6622ba47e56d7f6473524cce4))
* **deps:** update dependency swr to v2 ([#3212](https://github.com/fallenbagel/jellyseerr/issues/3212)) ([7b6db50](https://github.com/fallenbagel/jellyseerr/commit/7b6db50ae55b1fc60d19a5cff62dd46bb989fa51))
* **experimental:** use new RT API (sorta) ([#3179](https://github.com/fallenbagel/jellyseerr/issues/3179)) ([357cab8](https://github.com/fallenbagel/jellyseerr/commit/357cab87ac7752b8e119b51c938b343c661d83c2))
* improve small screen layout for discover editing ([#3221](https://github.com/fallenbagel/jellyseerr/issues/3221)) ([d23b213](https://github.com/fallenbagel/jellyseerr/commit/d23b2132de05f072f7f9daad83d81421d747cf99))
* include new package calendar css in build ([#3235](https://github.com/fallenbagel/jellyseerr/issues/3235)) ([c2a1a20](https://github.com/fallenbagel/jellyseerr/commit/c2a1a20a3bb20039a1936c7fe0ecb9e8311a0aea))
* issues with issues ([#3267](https://github.com/fallenbagel/jellyseerr/issues/3267)) ([fd21971](https://github.com/fallenbagel/jellyseerr/commit/fd219717c01c558814d7a80de6304272b5a7944e))
* multiple genre filtering now works ([#3282](https://github.com/fallenbagel/jellyseerr/issues/3282)) ([5076938](https://github.com/fallenbagel/jellyseerr/commit/507693881b939819413f0959df5ef6b7a357eb5c))
* prevent double encode if we are on /search endpoint ([#3238](https://github.com/fallenbagel/jellyseerr/issues/3238)) ([a343f8a](https://github.com/fallenbagel/jellyseerr/commit/a343f8ad915491a9c81512c7e541a1dac8906025))
* **request:** approve request when retrying request ([#3234](https://github.com/fallenbagel/jellyseerr/issues/3234)) ([b515701](https://github.com/fallenbagel/jellyseerr/commit/b5157010c46cd9083993d5ee0172007b83d631da))
* **request:** mark request as approved if media is already available when retrying failed request ([#3244](https://github.com/fallenbagel/jellyseerr/issues/3244)) ([cb65074](https://github.com/fallenbagel/jellyseerr/commit/cb650745f6a33e69391a633e6d272831f314e098))
* restore border to ghost button and fix discover slider visibility toggle position ([#3226](https://github.com/fallenbagel/jellyseerr/issues/3226)) ([2eebb7f](https://github.com/fallenbagel/jellyseerr/commit/2eebb7fd3941b34fe9472aaf9d28265df8cce311))
* restore status badges on titles on actors page when hide available media enabled ([#3206](https://github.com/fallenbagel/jellyseerr/issues/3206)) ([9d3446d](https://github.com/fallenbagel/jellyseerr/commit/9d3446d370499c3251159393e5c791b01225e05c))
* screen would zoom on mobile if date picker input was selected ([#3241](https://github.com/fallenbagel/jellyseerr/issues/3241)) ([3aefddd](https://github.com/fallenbagel/jellyseerr/commit/3aefddd48834d86150d5f5cceb2d08af3a78847b))
* series displayed an empty season with series list/request modal ([#3147](https://github.com/fallenbagel/jellyseerr/issues/3147)) ([2179637](https://github.com/fallenbagel/jellyseerr/commit/2179637d437999290eaa4152f6f37c71fc3d8ba3))
* tooltip shows properly if not in progress ([#3185](https://github.com/fallenbagel/jellyseerr/issues/3185)) ([6face8c](https://github.com/fallenbagel/jellyseerr/commit/6face8cc4564b978fb98af32659b326d8c5cede8))
* **ui:** series first air date sorting ([#3283](https://github.com/fallenbagel/jellyseerr/issues/3283)) ([374c78c](https://github.com/fallenbagel/jellyseerr/commit/374c78c989cc86bb144a954a91d5d183c4b591c0))
* update StatusBadgeMini to shrink on title cards (and remove ring) ([#3210](https://github.com/fallenbagel/jellyseerr/issues/3210)) ([042a1a9](https://github.com/fallenbagel/jellyseerr/commit/042a1a950fdd4d4a61edf4bc19657f9b7a526da8))
### Features
* add discover customization ([#3182](https://github.com/fallenbagel/jellyseerr/issues/3182)) ([cd35748](https://github.com/fallenbagel/jellyseerr/commit/cd3574851a12517cbfadc109e6412a7a9e44c114))
* add keywords to movie/series detail pages ([#3204](https://github.com/fallenbagel/jellyseerr/issues/3204)) ([e084649](https://github.com/fallenbagel/jellyseerr/commit/e084649878a58c296786141d12dd69a69a27ee85))
* add streaming services filter ([#3247](https://github.com/fallenbagel/jellyseerr/issues/3247)) ([1154156](https://github.com/fallenbagel/jellyseerr/commit/1154156459403494e8daf0c89a3ba356aeea1d97))
* discover inline customization ([#3220](https://github.com/fallenbagel/jellyseerr/issues/3220)) ([8bd10b5](https://github.com/fallenbagel/jellyseerr/commit/8bd10b5bf3d1b8069872b616c7c8596caeb4937e))
* discover overhaul (filters!) ([#3232](https://github.com/fallenbagel/jellyseerr/issues/3232)) ([dd00e48](https://github.com/fallenbagel/jellyseerr/commit/dd00e48f59054b44bef6b32a2c169e59f6175051))
* discover slider edit arrow buttons for reordering ([#3259](https://github.com/fallenbagel/jellyseerr/issues/3259)) ([da00d45](https://github.com/fallenbagel/jellyseerr/commit/da00d454e17e8b00d04f6e26f6dd5153ed6ced81))
* **lang:** translations update from Hosted Weblate ([#3030](https://github.com/fallenbagel/jellyseerr/issues/3030)) ([0d8b390](https://github.com/fallenbagel/jellyseerr/commit/0d8b390b678731e76bd1f0f8a0a4952c11e77f4d))
* new mobile menu ([#3251](https://github.com/fallenbagel/jellyseerr/issues/3251)) ([fcbca17](https://github.com/fallenbagel/jellyseerr/commit/fcbca1722f31f32633a57bc5048f46c9da057d87))
* translations update from Hosted Weblate ([#3218](https://github.com/fallenbagel/jellyseerr/issues/3218)) ([5940ff7](https://github.com/fallenbagel/jellyseerr/commit/5940ff7f5f62eed9ac5aa6f02803418aaa09813a))
* **ui:** add episode number to front of episode name in season details ([#3086](https://github.com/fallenbagel/jellyseerr/issues/3086)) ([a672b32](https://github.com/fallenbagel/jellyseerr/commit/a672b324ec391a20f6f3a1daed82a8d276a52c2c))
* **ui:** request card progress bar ([#3123](https://github.com/fallenbagel/jellyseerr/issues/3123)) ([03853a1](https://github.com/fallenbagel/jellyseerr/commit/03853a1b9155c8a2153c8885022a74619af1bc15))
# [1.3.0](https://github.com/fallenbagel/jellyseerr/compare/v1.2.1...v1.3.0) (2023-01-02)
### Bug Fixes

View File

@@ -14,7 +14,7 @@ RUN \
;; \
esac
RUN npm install --global pnpm
RUN npm install --global pnpm@9
COPY package.json pnpm-lock.yaml postinstall-win.js ./
RUN CYPRESS_INSTALL_BINARY=0 pnpm install --frozen-lockfile
@@ -29,7 +29,7 @@ RUN pnpm build
# remove development dependencies
RUN pnpm prune --prod --ignore-scripts
RUN rm -rf src server .next/cache
RUN rm -rf src server .next/cache charts gen-docs docs
RUN touch config/DOCKER
@@ -45,7 +45,7 @@ WORKDIR /app
RUN apk add --no-cache tzdata tini && rm -rf /tmp/*
RUN npm install -g pnpm
RUN npm install -g pnpm@9
# copy from build image
COPY --from=BUILD_IMAGE /app ./

View File

@@ -3,7 +3,7 @@ FROM node:22-alpine
COPY . /app
WORKDIR /app
Run npm install --global pnpm
RUN npm install --global pnpm@9
RUN pnpm install

View File

@@ -11,7 +11,7 @@
<a href="http://translate.jellyseerr.dev/engage/jellyseerr/"><img src="http://translate.jellyseerr.dev/widget/jellyseerr/jellyseerr-frontend/svg-badge.svg" alt="Translation status" /></a>
<a href="https://github.com/fallenbagel/jellyseerr/blob/develop/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/fallenbagel/jellyseerr"></a>
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
<a href="#contributors-"><img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-64-orange.svg"/></a>
<a href="#contributors-"><img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-65-orange.svg"/></a>
<!-- ALL-CONTRIBUTORS-BADGE:END -->
**Jellyseerr** is a free and open source software application for managing requests for your media library. It integrates with the media server of your choice: [Jellyfin](https://jellyfin.org), [Plex](https://plex.tv), and [Emby](https://emby.media/). In addition, it integrates with your existing services, such as **[Sonarr](https://sonarr.tv/)**, **[Radarr](https://radarr.video/)**.
@@ -168,6 +168,7 @@ Thanks goes to these wonderful people from Overseerr ([emoji key](https://allcon
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/methbkts"><img src="https://avatars.githubusercontent.com/u/30674934?v=4?s=100" width="100px;" alt="Metin Bektas"/><br /><sub><b>Metin Bektas</b></sub></a><br /><a href="#infra-methbkts" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/andrewkolda"><img src="https://avatars.githubusercontent.com/u/158614532?v=4?s=100" width="100px;" alt="andrewkolda"/><br /><sub><b>andrewkolda</b></sub></a><br /><a href="#design-andrewkolda" title="Design">🎨</a></td>
</tr>
</tbody>
</table>

View File

@@ -3,8 +3,8 @@ kubeVersion: ">=1.23.0-0"
name: jellyseerr-chart
description: Jellyseerr helm chart for Kubernetes
type: application
version: 1.3.0
appVersion: "2.2.3"
version: 2.1.1
appVersion: "2.3.0"
maintainers:
- name: Jellyseerr
url: https://github.com/Fallenbagel/jellyseerr

View File

@@ -1,6 +1,6 @@
# jellyseerr-chart
![Version: 1.3.0](https://img.shields.io/badge/Version-1.3.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.2.3](https://img.shields.io/badge/AppVersion-2.2.3-informational?style=flat-square)
![Version: 2.1.1](https://img.shields.io/badge/Version-2.1.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.3.0](https://img.shields.io/badge/AppVersion-2.3.0-informational?style=flat-square)
Jellyseerr helm chart for Kubernetes
@@ -25,10 +25,6 @@ Kubernetes: `>=1.23.0-0`
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| affinity | object | `{}` | |
| autoscaling.enabled | bool | `false` | |
| autoscaling.maxReplicas | int | `100` | |
| autoscaling.minReplicas | int | `1` | |
| autoscaling.targetCPUUtilizationPercentage | int | `80` | |
| config | object | `{"persistence":{"accessModes":["ReadWriteOnce"],"annotations":{},"name":"","size":"5Gi","volumeName":""}}` | Creating PVC to store configuration |
| config.persistence.accessModes | list | `["ReadWriteOnce"]` | Access modes of persistent disk |
| config.persistence.annotations | object | `{}` | Annotations for PVCs |
@@ -39,7 +35,7 @@ Kubernetes: `>=1.23.0-0`
| extraEnvFrom | list | `[]` | Environment variables from secrets or configmaps to add to the jellyseerr pods |
| fullnameOverride | string | `""` | |
| image.pullPolicy | string | `"IfNotPresent"` | |
| image.registry | string | `"docker.io"` | |
| image.registry | string | `"ghcr.io"` | |
| image.repository | string | `"fallenbagel/jellyseerr"` | |
| image.sha | string | `""` | |
| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. |
@@ -67,3 +63,5 @@ Kubernetes: `>=1.23.0-0`
| serviceAccount.name | string | `""` | If not set and create is true, a name is generated using the fullname template |
| strategy | object | `{"type":"Recreate"}` | Deployment strategy |
| tolerations | list | `[]` | |
| volumeMounts | list | `[]` | Additional volumeMounts on the output Deployment definition. |
| volumes | list | `[]` | Additional volumes on the output Deployment definition. |

View File

@@ -5,9 +5,7 @@ metadata:
labels:
{{- include "jellyseerr.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
strategy:
type: {{ .Values.strategy.type }}
selector:
@@ -67,10 +65,16 @@ spec:
volumeMounts:
- name: config
mountPath: /app/config
{{- with .Values.volumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
volumes:
- name: config
persistentVolumeClaim:
claimName: {{ include "jellyseerr.configPersistenceName" . }}
{{- with .Values.volumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}

View File

@@ -1,32 +0,0 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "jellyseerr.fullname" . }}
labels:
{{- include "jellyseerr.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "jellyseerr.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View File

@@ -1,7 +1,7 @@
replicaCount: 1
image:
registry: docker.io
registry: ghcr.io
repository: fallenbagel/jellyseerr
pullPolicy: IfNotPresent
# -- Overrides the image tag whose default is the chart appVersion.
@@ -94,12 +94,18 @@ resources: {}
# cpu: 100m
# memory: 128Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
# -- Additional volumes on the output Deployment definition.
volumes: []
# - name: foo
# secret:
# secretName: mysecret
# optional: false
# -- Additional volumeMounts on the output Deployment definition.
volumeMounts: []
# - name: foo
# mountPath: "/etc/foo"
# readOnly: true
nodeSelector: {}

View File

@@ -6,7 +6,6 @@ Cypress.Commands.add('login', (email, password) => {
[email, password],
() => {
cy.visit('/login');
cy.contains('Use your Overseerr account').click();
cy.get('[data-testid=email]').type(email);
cy.get('[data-testid=password]').type(password);

View File

@@ -14,6 +14,14 @@ When disabled, your mediaserver OAuth becomes the only sign-in option, and any "
This setting is **enabled** by default.
## Enable Jellyfin/Emby/Plex Sign-In
When enabled, users will be able to sign in to Jellyseerr using their Jellyfin/Emby/Plex credentials, provided they have linked their media server accounts.
When disabled, users will only be able to sign in using their email address. Users without a password set will not be able to sign in to Jellyseerr.
This setting is **enabled** by default.
## Enable New Jellyfin/Emby/Plex Sign-In
When enabled, users with access to your media server will be able to sign in to Jellyseerr even if they have not yet been imported. Users will be automatically assigned the permissions configured in the [Default Permissions](#default-permissions) setting upon first sign-in.

View File

@@ -11,7 +11,7 @@ const config: Config = {
baseUrl: '/',
trailingSlash: false,
organizationName: 'Fallenbagel',
organizationName: 'fallenbagel',
projectName: 'Jellyseerr',
deploymentBranch: 'gh-pages',
@@ -32,7 +32,7 @@ const config: Config = {
routeBasePath: '/',
path: '../docs',
editUrl:
'https://github.com/Fallenbagel/jellyseerr/edit/develop/docs/',
'https://github.com/fallenbagel/jellyseerr/edit/develop/docs/',
},
blog: false,
pages: false,
@@ -70,7 +70,7 @@ const config: Config = {
},
items: [
{
href: 'https://github.com/Fallenbagel/jellyseerr',
href: 'https://github.com/fallenbagel/jellyseerr',
label: 'GitHub',
position: 'right',
},

View File

@@ -11,6 +11,7 @@ module.exports = {
{ hostname: 'gravatar.com' },
{ hostname: 'image.tmdb.org' },
{ hostname: 'artworks.thetvdb.com' },
{ hostname: 'plex.tv' },
],
},
webpack(config) {

View File

@@ -1,6 +1,6 @@
{
"name": "jellyseerr",
"version": "2.3.0",
"version": "0.1.0",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
@@ -42,6 +42,7 @@
"@supercharge/request-ip": "1.2.0",
"@svgr/webpack": "6.5.1",
"@tanem/react-nprogress": "5.0.30",
"@types/wink-jaro-distance": "^2.0.2",
"ace-builds": "1.15.2",
"bcrypt": "5.1.0",
"bowser": "2.11.0",
@@ -85,6 +86,7 @@
"react-spring": "9.7.1",
"react-tailwindcss-datepicker-sct": "1.3.4",
"react-toast-notifications": "2.5.1",
"react-transition-group": "^4.4.5",
"react-truncate-markup": "5.1.2",
"react-use-clipboard": "1.0.9",
"reflect-metadata": "0.1.13",
@@ -94,9 +96,11 @@
"sqlite3": "5.1.4",
"swagger-ui-express": "4.6.2",
"swr": "2.2.5",
"tailwind-merge": "^2.6.0",
"typeorm": "0.3.11",
"undici": "^6.20.1",
"web-push": "3.5.0",
"wink-jaro-distance": "^2.0.0",
"winston": "3.8.2",
"winston-daily-rotate-file": "4.7.1",
"xml2js": "0.4.23",

43
pnpm-lock.yaml generated
View File

@@ -38,6 +38,9 @@ importers:
'@tanem/react-nprogress':
specifier: 5.0.30
version: 5.0.30(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@types/wink-jaro-distance':
specifier: ^2.0.2
version: 2.0.2
ace-builds:
specifier: 1.15.2
version: 1.15.2
@@ -167,6 +170,9 @@ importers:
react-toast-notifications:
specifier: 2.5.1
version: 2.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-transition-group:
specifier: ^4.4.5
version: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-truncate-markup:
specifier: 5.1.2
version: 5.1.2(react@18.3.1)
@@ -194,6 +200,9 @@ importers:
swr:
specifier: 2.2.5
version: 2.2.5(react@18.3.1)
tailwind-merge:
specifier: ^2.6.0
version: 2.6.0
typeorm:
specifier: 0.3.11
version: 0.3.11(pg@8.11.0)(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5))
@@ -203,6 +212,9 @@ importers:
web-push:
specifier: 3.5.0
version: 3.5.0
wink-jaro-distance:
specifier: ^2.0.0
version: 2.0.0
winston:
specifier: 3.8.2
version: 3.8.2
@@ -3250,6 +3262,9 @@ packages:
'@types/webxr@0.5.20':
resolution: {integrity: sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==}
'@types/wink-jaro-distance@2.0.2':
resolution: {integrity: sha512-Q79orp7qA/g/uLdFmqd5MtEa0ZfJW5X1WXikAu8IVHt24IrHWrcTNYNdPpLK5mwVg34C6FQnrv/DMtcUhjE/zA==}
'@types/xml2js@0.4.11':
resolution: {integrity: sha512-JdigeAKmCyoJUiQljjr7tQG3if9NkqGUgwEUqBvV0N7LM4HyQk7UXCnusRa1lnvXAEYJ8mw8GtZWioagNztOwA==}
@@ -8835,6 +8850,9 @@ packages:
peerDependencies:
react: ^16.11.0 || ^17.0.0 || ^18.0.0
tailwind-merge@2.6.0:
resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
tailwindcss@3.2.7:
resolution: {integrity: sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==}
engines: {node: '>=12.13.0'}
@@ -9467,6 +9485,9 @@ packages:
wide-align@1.1.5:
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
wink-jaro-distance@2.0.0:
resolution: {integrity: sha512-9bcUaXCi9N8iYpGWbFkf83OsBkg17r4hEyxusEzl+nnReLRPqxhB9YNeRn3g54SYnVRNXP029lY3HDsbdxTAuA==}
winston-daily-rotate-file@4.7.1:
resolution: {integrity: sha512-7LGPiYGBPNyGHLn9z33i96zx/bd71pjBn9tqQzO3I4Tayv94WPmBNwKC7CO1wPHdP9uvu+Md/1nr6VSH9h0iaA==}
engines: {node: '>=8'}
@@ -11281,7 +11302,7 @@ snapshots:
'@emotion/babel-plugin@11.11.0':
dependencies:
'@babel/helper-module-imports': 7.24.7
'@babel/runtime': 7.24.7
'@babel/runtime': 7.26.0
'@emotion/hash': 0.9.1
'@emotion/memoize': 0.8.1
'@emotion/serialize': 1.1.4
@@ -11311,7 +11332,7 @@ snapshots:
'@emotion/core@10.3.1(react@18.3.1)':
dependencies:
'@babel/runtime': 7.24.7
'@babel/runtime': 7.26.0
'@emotion/cache': 10.0.29
'@emotion/css': 10.0.27
'@emotion/serialize': 0.11.16
@@ -11339,7 +11360,7 @@ snapshots:
'@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.24.7
'@babel/runtime': 7.26.0
'@emotion/babel-plugin': 11.11.0
'@emotion/cache': 11.11.0
'@emotion/serialize': 1.1.4
@@ -13737,6 +13758,8 @@ snapshots:
'@types/webxr@0.5.20': {}
'@types/wink-jaro-distance@2.0.2': {}
'@types/xml2js@0.4.11':
dependencies:
'@types/node': 22.10.5
@@ -14240,13 +14263,13 @@ snapshots:
babel-plugin-macros@2.8.0:
dependencies:
'@babel/runtime': 7.24.7
'@babel/runtime': 7.26.0
cosmiconfig: 6.0.0
resolve: 1.22.8
babel-plugin-macros@3.1.0:
dependencies:
'@babel/runtime': 7.24.7
'@babel/runtime': 7.26.0
cosmiconfig: 7.1.0
resolve: 1.22.8
@@ -15336,7 +15359,7 @@ snapshots:
dom-helpers@5.2.1:
dependencies:
'@babel/runtime': 7.24.7
'@babel/runtime': 7.26.0
csstype: 3.1.3
dom-serializer@1.4.1:
@@ -19352,7 +19375,7 @@ snapshots:
react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@babel/runtime': 7.24.7
'@babel/runtime': 7.26.0
dom-helpers: 5.2.1
loose-envify: 1.4.0
prop-types: 15.8.1
@@ -19479,7 +19502,7 @@ snapshots:
regenerator-transform@0.15.2:
dependencies:
'@babel/runtime': 7.24.7
'@babel/runtime': 7.26.0
regexp.prototype.flags@1.5.2:
dependencies:
@@ -20260,6 +20283,8 @@ snapshots:
react: 18.3.1
use-sync-external-store: 1.2.2(react@18.3.1)
tailwind-merge@2.6.0: {}
tailwindcss@3.2.7(postcss@8.4.21)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)):
dependencies:
arg: 5.0.2
@@ -20905,6 +20930,8 @@ snapshots:
dependencies:
string-width: 4.2.3
wink-jaro-distance@2.0.0: {}
winston-daily-rotate-file@4.7.1(winston@3.8.2):
dependencies:
file-stream-rotator: 0.6.1

2
public/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow: /

View File

@@ -1,6 +1,7 @@
import ExternalAPI from '@server/api/externalapi';
import cacheManager from '@server/lib/cache';
import { getSettings } from '@server/lib/settings';
import jaro from 'wink-jaro-distance';
interface RTAlgoliaSearchResponse {
results: {
@@ -15,7 +16,7 @@ interface RTAlgoliaHit {
tmsId: string;
type: string;
title: string;
titles: string[];
titles?: string[];
description: string;
releaseYear: number;
rating: string;
@@ -24,9 +25,9 @@ interface RTAlgoliaHit {
isEmsSearchable: boolean;
rtId: number;
vanity: string;
aka: string[];
aka?: string[];
posterImageUrl: string;
rottenTomatoes: {
rottenTomatoes?: {
audienceScore: number;
criticsIconUrl: string;
wantToSeeCount: number;
@@ -47,6 +48,47 @@ export interface RTRating {
url: string;
}
// Tunables
const INEXACT_TITLE_FACTOR = 0.25;
const ALTERNATE_TITLE_FACTOR = 0.8;
const PER_YEAR_PENALTY = 0.4;
const MINIMUM_SCORE = 0.175;
// Normalization for title comparisons.
// Lowercase and strip non-alphanumeric (unicode-aware).
const norm = (s: string): string =>
s.toLowerCase().replace(/[^\p{L}\p{N} ]/gu, '');
// Title similarity. 1 if exact, quarter-jaro otherwise.
const similarity = (a: string, b: string): number =>
a === b ? 1 : jaro(a, b).similarity * INEXACT_TITLE_FACTOR;
// Gets the best similarity score between the searched title and all alternate
// titles of the search result. Non-main titles are penalized.
const t_score = ({ title, titles, aka }: RTAlgoliaHit, s: string): number => {
const f = (t: string, i: number) =>
similarity(norm(t), norm(s)) * (i ? ALTERNATE_TITLE_FACTOR : 1);
return Math.max(...[title].concat(aka || [], titles || []).map(f));
};
// Year difference to score: 0 -> 1.0, 1 -> 0.6, 2 -> 0.2, 3+ -> 0.0
const y_score = (r: RTAlgoliaHit, y?: number): number =>
y ? Math.max(0, 1 - Math.abs(r.releaseYear - y) * PER_YEAR_PENALTY) : 1;
// Cut score in half if result has no ratings.
const extra_score = (r: RTAlgoliaHit): number => (r.rottenTomatoes ? 1 : 0.5);
// Score search result as product of all subscores
const score = (r: RTAlgoliaHit, name: string, year?: number): number =>
t_score(r, name) * y_score(r, year) * extra_score(r);
// Score each search result and return the highest scoring result, if any
const best = (rs: RTAlgoliaHit[], name: string, year?: number): RTAlgoliaHit =>
rs
.map((r) => ({ score: score(r, name, year), result: r }))
.filter(({ score }) => score > MINIMUM_SCORE)
.sort(({ score: a }, { score: b }) => b - a)[0]?.result;
/**
* This is a best-effort API. The Rotten Tomatoes API is technically
* private and getting access costs money/requires approval.
@@ -90,47 +132,21 @@ class RottenTomatoes extends ExternalAPI {
year: number
): Promise<RTRating | null> {
try {
const filters = encodeURIComponent('isEmsSearchable=1 AND type:"movie"');
const data = await this.post<RTAlgoliaSearchResponse>('/queries', {
requests: [
{
indexName: 'content_rt',
query: name,
params: 'filters=isEmsSearchable%20%3D%201&hitsPerPage=20',
query: name.replace(/\bthe\b ?/gi, ''),
params: `filters=${filters}&hitsPerPage=20`,
},
],
});
const contentResults = data.results.find((r) => r.index === 'content_rt');
const movie = best(contentResults?.hits || [], name, year);
if (!contentResults) {
return null;
}
// First, attempt to match exact name and year
let movie = contentResults.hits.find(
(movie) => movie.releaseYear === year && movie.title === name
);
// If we don't find a movie, try to match partial name and year
if (!movie) {
movie = contentResults.hits.find(
(movie) => movie.releaseYear === year && movie.title.includes(name)
);
}
// If we still dont find a movie, try to match just on year
if (!movie) {
movie = contentResults.hits.find((movie) => movie.releaseYear === year);
}
// One last try, try exact name match only
if (!movie) {
movie = contentResults.hits.find((movie) => movie.title === name);
}
if (!movie?.rottenTomatoes) {
return null;
}
if (!movie?.rottenTomatoes) return null;
return {
title: movie.title,
@@ -158,33 +174,21 @@ class RottenTomatoes extends ExternalAPI {
year?: number
): Promise<RTRating | null> {
try {
const filters = encodeURIComponent('isEmsSearchable=1 AND type:"tv"');
const data = await this.post<RTAlgoliaSearchResponse>('/queries', {
requests: [
{
indexName: 'content_rt',
query: name,
params: 'filters=isEmsSearchable%20%3D%201&hitsPerPage=20',
params: `filters=${filters}&hitsPerPage=20`,
},
],
});
const contentResults = data.results.find((r) => r.index === 'content_rt');
const tvshow = best(contentResults?.hits || [], name, year);
if (!contentResults) {
return null;
}
let tvshow: RTAlgoliaHit | undefined = contentResults.hits[0];
if (year) {
tvshow = contentResults.hits.find(
(series) => series.releaseYear === year
);
}
if (!tvshow || !tvshow.rottenTomatoes) {
return null;
}
if (!tvshow?.rottenTomatoes) return null;
return {
title: tvshow.title,

View File

@@ -83,13 +83,13 @@ export class User {
@Column({ nullable: true })
public jellyfinUserId?: string;
@Column({ nullable: true })
@Column({ nullable: true, select: false })
public jellyfinDeviceId?: string;
@Column({ nullable: true })
@Column({ nullable: true, select: false })
public jellyfinAuthToken?: string;
@Column({ nullable: true })
@Column({ nullable: true, select: false })
public plexToken?: string;
@Column({ type: 'integer', default: 0 })

View File

@@ -30,6 +30,7 @@ export interface PublicSettingsResponse {
applicationUrl: string;
hideAvailable: boolean;
localLogin: boolean;
mediaServerLogin: boolean;
movie4kEnabled: boolean;
series4kEnabled: boolean;
discoverRegion: string;

View File

@@ -70,6 +70,35 @@ export const startJobs = (): void => {
running: () => plexFullScanner.status().running,
cancelFn: () => plexFullScanner.cancel(),
});
scheduledJobs.push({
id: 'plex-refresh-token',
name: 'Plex Refresh Token',
type: 'process',
interval: 'fixed',
cronSchedule: jobs['plex-refresh-token'].schedule,
job: schedule.scheduleJob(jobs['plex-refresh-token'].schedule, () => {
logger.info('Starting scheduled job: Plex Refresh Token', {
label: 'Jobs',
});
refreshToken.run();
}),
});
// Watchlist Sync
scheduledJobs.push({
id: 'plex-watchlist-sync',
name: 'Plex Watchlist Sync',
type: 'process',
interval: 'seconds',
cronSchedule: jobs['plex-watchlist-sync'].schedule,
job: schedule.scheduleJob(jobs['plex-watchlist-sync'].schedule, () => {
logger.info('Starting scheduled job: Plex Watchlist Sync', {
label: 'Jobs',
});
watchlistSync.syncWatchlist();
}),
});
} else if (
mediaServerType === MediaServerType.JELLYFIN ||
mediaServerType === MediaServerType.EMBY
@@ -112,21 +141,6 @@ export const startJobs = (): void => {
});
}
// Watchlist Sync
scheduledJobs.push({
id: 'plex-watchlist-sync',
name: 'Plex Watchlist Sync',
type: 'process',
interval: 'seconds',
cronSchedule: jobs['plex-watchlist-sync'].schedule,
job: schedule.scheduleJob(jobs['plex-watchlist-sync'].schedule, () => {
logger.info('Starting scheduled job: Plex Watchlist Sync', {
label: 'Jobs',
});
watchlistSync.syncWatchlist();
}),
});
// Run full radarr scan every 24 hours
scheduledJobs.push({
id: 'radarr-scan',
@@ -223,19 +237,5 @@ export const startJobs = (): void => {
}),
});
scheduledJobs.push({
id: 'plex-refresh-token',
name: 'Plex Refresh Token',
type: 'process',
interval: 'fixed',
cronSchedule: jobs['plex-refresh-token'].schedule,
job: schedule.scheduleJob(jobs['plex-refresh-token'].schedule, () => {
logger.info('Starting scheduled job: Plex Refresh Token', {
label: 'Jobs',
});
refreshToken.run();
}),
});
logger.info('Scheduled jobs loaded', { label: 'Jobs' });
};

View File

@@ -124,6 +124,7 @@ export interface MainSettings {
};
hideAvailable: boolean;
localLogin: boolean;
mediaServerLogin: boolean;
newPlexLogin: boolean;
discoverRegion: string;
streamingRegion: string;
@@ -147,6 +148,7 @@ interface FullPublicSettings extends PublicSettings {
applicationUrl: string;
hideAvailable: boolean;
localLogin: boolean;
mediaServerLogin: boolean;
movie4kEnabled: boolean;
series4kEnabled: boolean;
discoverRegion: string;
@@ -340,6 +342,7 @@ class Settings {
},
hideAvailable: false,
localLogin: true,
mediaServerLogin: true,
newPlexLogin: true,
discoverRegion: '',
streamingRegion: '',
@@ -582,6 +585,8 @@ class Settings {
applicationUrl: this.data.main.applicationUrl,
hideAvailable: this.data.main.hideAvailable,
localLogin: this.data.main.localLogin,
mediaServerLogin: this.data.main.mediaServerLogin,
jellyfinExternalHost: this.data.jellyfin.externalHostname,
jellyfinForgotPasswordUrl: this.data.jellyfin.jellyfinForgotPasswordUrl,
movie4kEnabled: this.data.radarr.some(
(radarr) => radarr.is4k && radarr.isDefault

View File

@@ -56,8 +56,9 @@ authRoutes.post('/plex', async (req, res, next) => {
}
if (
settings.main.mediaServerType != MediaServerType.PLEX &&
settings.main.mediaServerType != MediaServerType.NOT_CONFIGURED
settings.main.mediaServerType != MediaServerType.NOT_CONFIGURED &&
(settings.main.mediaServerLogin === false ||
settings.main.mediaServerType != MediaServerType.PLEX)
) {
return res.status(500).json({ error: 'Plex login is disabled' });
}
@@ -231,10 +232,13 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
//Make sure jellyfin login is enabled, but only if jellyfin && Emby is not already configured
if (
settings.main.mediaServerType !== MediaServerType.JELLYFIN &&
settings.main.mediaServerType !== MediaServerType.EMBY &&
// media server not configured, allow login for setup
settings.main.mediaServerType != MediaServerType.NOT_CONFIGURED &&
settings.jellyfin.ip !== ''
(settings.main.mediaServerLogin === false ||
// media server is neither jellyfin or emby
(settings.main.mediaServerType !== MediaServerType.JELLYFIN &&
settings.main.mediaServerType !== MediaServerType.EMBY &&
settings.jellyfin.ip !== ''))
) {
return res.status(500).json({ error: 'Jellyfin login is disabled' });
}
@@ -263,6 +267,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
// Try to find deviceId that corresponds to jellyfin user, else generate a new one
let user = await userRepository.findOne({
where: { jellyfinUsername: body.username },
select: { id: true, jellyfinDeviceId: true },
});
let deviceId = '';

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- ***** BEGIN LICENSE BLOCK *****
- Part of the Jellyfin project (https://jellyfin.media)
-
- All copyright belongs to the Jellyfin contributors; a full list can
- be found in the file CONTRIBUTORS.md
-
- This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
- To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/.
- ***** END LICENSE BLOCK ***** -->
<svg version="1.1" id="icon-transparent" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512">
<defs>
<linearGradient id="linear-gradient" gradientUnits="userSpaceOnUse" x1="110.25" y1="213.3" x2="496.14" y2="436.09">
<stop offset="0" style="stop-color:#AA5CC3"/>
<stop offset="1" style="stop-color:#00A4DC"/>
</linearGradient>
</defs>
<title>icon-transparent</title>
<g id="icon-transparent">
<path id="inner-shape" d="M256,201.6c-20.4,0-86.2,119.3-76.2,139.4s142.5,19.9,152.4,0S276.5,201.6,256,201.6z" fill="url(#linear-gradient)"/>
<path id="outer-shape" d="M256,23.3c-61.6,0-259.8,359.4-229.6,420.1s429.3,60,459.2,0S317.6,23.3,256,23.3z
M406.5,390.8c-19.6,39.3-281.1,39.8-300.9,0s110.1-275.3,150.4-275.3S426.1,351.4,406.5,390.8z" fill="url(#linear-gradient)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@@ -1,5 +1,6 @@
import type { ForwardedRef } from 'react';
import React from 'react';
import { twMerge } from 'tailwind-merge';
export type ButtonType =
| 'default'
@@ -97,7 +98,7 @@ function Button<P extends ElementTypes = 'button'>(
if (as === 'a') {
return (
<a
className={buttonStyle.join(' ')}
className={twMerge(buttonStyle)}
{...(props as React.ComponentProps<'a'>)}
ref={ref as ForwardedRef<HTMLAnchorElement>}
>
@@ -107,7 +108,7 @@ function Button<P extends ElementTypes = 'button'>(
} else {
return (
<button
className={buttonStyle.join(' ')}
className={twMerge(buttonStyle)}
{...(props as React.ComponentProps<'button'>)}
ref={ref as ForwardedRef<HTMLButtonElement>}
>

View File

@@ -0,0 +1,44 @@
import { Field } from 'formik';
import { twMerge } from 'tailwind-merge';
interface LabeledCheckboxProps {
id: string;
className?: string;
label: string;
description: string;
onChange: () => void;
children?: React.ReactNode;
}
const LabeledCheckbox: React.FC<LabeledCheckboxProps> = ({
id,
className,
label,
description,
onChange,
children,
}) => {
return (
<>
<div className={twMerge('relative flex items-start', className)}>
<div className="flex h-6 items-center">
<Field type="checkbox" id={id} name={id} onChange={onChange} />
</div>
<div className="ml-3 text-sm leading-6">
<label htmlFor="localLogin" className="block">
<div className="flex flex-col">
<span className="font-medium text-white">{label}</span>
<span className="font-normal text-gray-400">{description}</span>
</div>
</label>
</div>
</div>
{
/* can hold child checkboxes */
children && <div className="mt-4 pl-10">{children}</div>
}
</>
);
};
export default LabeledCheckbox;

View File

@@ -1,63 +1,39 @@
import Button from '@app/components/Common/Button';
import Tooltip from '@app/components/Common/Tooltip';
import SensitiveInput from '@app/components/Common/SensitiveInput';
import useSettings from '@app/hooks/useSettings';
import defineMessages from '@app/utils/defineMessages';
import { InformationCircleIcon } from '@heroicons/react/24/solid';
import { ArrowLeftOnRectangleIcon } from '@heroicons/react/24/outline';
import { ApiErrorCode } from '@server/constants/error';
import { MediaServerType, ServerType } from '@server/constants/server';
import { Field, Form, Formik } from 'formik';
import { FormattedMessage, useIntl } from 'react-intl';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
import * as Yup from 'yup';
const messages = defineMessages('components.Login', {
loginwithapp: 'Login with {appName}',
username: 'Username',
password: 'Password',
hostname: '{mediaServerName} URL',
port: 'Port',
enablessl: 'Use SSL',
urlBase: 'URL Base',
email: 'Email',
emailtooltip:
'Address does not need to be associated with your {mediaServerName} instance.',
validationhostrequired: '{mediaServerName} URL required',
validationhostformat: 'Valid URL required',
validationemailrequired: 'Email required',
validationemailformat: 'Valid email required',
validationusernamerequired: 'Username required',
validationpasswordrequired: 'Password required',
validationservertyperequired: 'Please select a server type',
validationHostnameRequired: 'You must provide a valid hostname or IP address',
validationPortRequired: 'You must provide a valid port number',
validationUrlTrailingSlash: 'URL must not end in a trailing slash',
validationUrlBaseLeadingSlash: 'URL base must have a leading slash',
validationUrlBaseTrailingSlash: 'URL base must not end in a trailing slash',
loginerror: 'Something went wrong while trying to sign in.',
adminerror: 'You must use an admin account to sign in.',
noadminerror: 'No admin user found on the server.',
credentialerror: 'The username or password is incorrect.',
invalidurlerror: 'Unable to connect to {mediaServerName} server.',
signingin: 'Signing in…',
signingin: 'Signing In…',
signin: 'Sign In',
initialsigningin: 'Connecting…',
initialsignin: 'Connect',
forgotpassword: 'Forgot Password?',
servertype: 'Server Type',
back: 'Go back',
});
interface JellyfinLoginProps {
revalidate: () => void;
initial?: boolean;
serverType?: MediaServerType;
onCancel?: () => void;
}
const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
revalidate,
initial,
serverType,
onCancel,
}) => {
const toasts = useToasts();
const intl = useIntl();
@@ -72,56 +48,29 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
: 'Media Server',
};
if (initial) {
const LoginSchema = Yup.object().shape({
hostname: Yup.string().required(
intl.formatMessage(
messages.validationhostrequired,
mediaServerFormatValues
)
),
port: Yup.number().required(
intl.formatMessage(messages.validationPortRequired)
),
urlBase: Yup.string()
.test(
'leading-slash',
intl.formatMessage(messages.validationUrlBaseLeadingSlash),
(value) => !value || value.startsWith('/')
)
.test(
'trailing-slash',
intl.formatMessage(messages.validationUrlBaseTrailingSlash),
(value) => !value || !value.endsWith('/')
),
email: Yup.string()
.email(intl.formatMessage(messages.validationemailformat))
.required(intl.formatMessage(messages.validationemailrequired)),
username: Yup.string().required(
intl.formatMessage(messages.validationusernamerequired)
),
password: Yup.string(),
});
const LoginSchema = Yup.object().shape({
username: Yup.string().required(
intl.formatMessage(messages.validationusernamerequired)
),
password: Yup.string(),
});
const baseUrl = settings.currentSettings.jellyfinExternalHost
? settings.currentSettings.jellyfinExternalHost
: settings.currentSettings.jellyfinHost;
const jellyfinForgotPasswordUrl =
settings.currentSettings.jellyfinForgotPasswordUrl;
return (
return (
<div>
<Formik
initialValues={{
username: '',
password: '',
hostname: '',
port: 8096,
useSsl: false,
urlBase: '',
email: '',
}}
validationSchema={LoginSchema}
validateOnBlur={false}
onSubmit={async (values) => {
try {
// Check if serverType is either 'Jellyfin' or 'Emby'
// if (serverType !== 'Jellyfin' && serverType !== 'Emby') {
// throw new Error('Invalid serverType'); // You can customize the error message
// }
const res = await fetch('/api/v1/auth/jellyfin', {
method: 'POST',
headers: {
@@ -130,12 +79,7 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
body: JSON.stringify({
username: values.username,
password: values.password,
hostname: values.hostname,
port: values.port,
useSsl: values.useSsl,
urlBase: values.urlBase,
email: values.email,
serverType: serverType,
email: values.username,
}),
});
if (!res.ok) throw new Error(res.statusText, { cause: res });
@@ -165,7 +109,6 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
errorMessage = messages.loginerror;
break;
}
toasts.addToast(
intl.formatMessage(errorMessage, mediaServerFormatValues),
{
@@ -178,303 +121,51 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
}
}}
>
{({
errors,
touched,
values,
setFieldValue,
isSubmitting,
isValid,
}) => (
<Form>
<div className="sm:border-t sm:border-gray-800">
<div className="flex flex-col sm:flex-row sm:gap-4">
<div className="w-full">
<label htmlFor="hostname" className="text-label">
{intl.formatMessage(
messages.hostname,
mediaServerFormatValues
)}
</label>
<div className="mt-1 mb-2 sm:col-span-2 sm:mb-0 sm:mt-0">
<div className="flex rounded-md shadow-sm">
<span className="inline-flex cursor-default items-center rounded-l-md border border-r-0 border-gray-500 bg-gray-800 px-3 text-gray-100 sm:text-sm">
{values.useSsl ? 'https://' : 'http://'}
</span>
{({ errors, touched, isSubmitting, isValid }) => {
return (
<>
<Form>
<div>
<h2 className="mb-6 -mt-1 text-center text-lg font-bold text-neutral-200">
{intl.formatMessage(messages.loginwithapp, {
appName: mediaServerFormatValues.mediaServerName,
})}
</h2>
<div className="mt-1 mb-4">
<div className="form-input-field">
<Field
id="hostname"
name="hostname"
id="username"
name="username"
type="text"
className="rounded-r-only flex-1"
placeholder={intl.formatMessage(
messages.hostname,
mediaServerFormatValues
)}
placeholder={intl.formatMessage(messages.username)}
className="!bg-gray-700/80 placeholder:text-gray-400"
/>
</div>
{errors.hostname && touched.hostname && (
<div className="error">{errors.hostname}</div>
{errors.username && touched.username && (
<div className="error">{errors.username}</div>
)}
</div>
</div>
<div className="flex-1">
<label htmlFor="port" className="text-label">
{intl.formatMessage(messages.port)}
</label>
<div className="mt-1 sm:mt-0">
<Field
id="port"
name="port"
inputMode="numeric"
type="text"
className="short flex-1"
placeholder={intl.formatMessage(messages.port)}
/>
{errors.port && touched.port && (
<div className="error">{errors.port}</div>
)}
</div>
</div>
</div>
<label htmlFor="useSsl" className="text-label mt-2">
{intl.formatMessage(messages.enablessl)}
</label>
<div className="mt-1 mb-2 sm:col-span-2">
<div className="flex rounded-md shadow-sm">
<Field
id="useSsl"
name="useSsl"
type="checkbox"
onChange={() => {
setFieldValue('useSsl', !values.useSsl);
setFieldValue('port', values.useSsl ? 8096 : 443);
}}
/>
</div>
</div>
<label htmlFor="urlBase" className="text-label mt-1">
{intl.formatMessage(messages.urlBase)}
</label>
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
<div className="flex rounded-md shadow-sm">
<Field
type="text"
inputMode="url"
id="urlBase"
name="urlBase"
placeholder={intl.formatMessage(messages.urlBase)}
/>
</div>
{errors.urlBase && touched.urlBase && (
<div className="error">{errors.urlBase}</div>
)}
</div>
<label
htmlFor="email"
className="text-label inline-flex gap-1 align-middle"
>
{intl.formatMessage(messages.email)}
<span className="label-tip">
<Tooltip
content={intl.formatMessage(
messages.emailtooltip,
mediaServerFormatValues
)}
>
<span className="tooltip-trigger">
<InformationCircleIcon className="h-4 w-4" />
</span>
</Tooltip>
</span>
</label>
<div className="mt-1 sm:col-span-2 sm:mb-2 sm:mt-0">
<div className="flex rounded-md shadow-sm">
<Field
id="email"
name="email"
type="text"
placeholder={intl.formatMessage(messages.email)}
/>
</div>
{errors.email && touched.email && (
<div className="error">{errors.email}</div>
)}
</div>
<label htmlFor="username" className="text-label">
{intl.formatMessage(messages.username)}
</label>
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
<div className="flex rounded-md shadow-sm">
<Field
id="username"
name="username"
type="text"
placeholder={intl.formatMessage(messages.username)}
/>
</div>
{errors.username && touched.username && (
<div className="error">{errors.username}</div>
)}
</div>
<label htmlFor="password" className="text-label">
{intl.formatMessage(messages.password)}
</label>
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
<div className="flexrounded-md shadow-sm">
<Field
id="password"
name="password"
type="password"
placeholder={intl.formatMessage(messages.password)}
/>
</div>
{errors.password && touched.password && (
<div className="error">{errors.password}</div>
)}
</div>
</div>
<div className="mt-8 border-t border-gray-700 pt-5">
<div className="flex flex-row-reverse justify-between">
<span className="inline-flex rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting || !isValid}
>
{isSubmitting
? intl.formatMessage(messages.signingin)
: intl.formatMessage(messages.signin)}
</Button>
</span>
{onCancel && (
<span className="inline-flex rounded-md shadow-sm">
<Button buttonType="default" onClick={() => onCancel()}>
<FormattedMessage {...messages.back} />
</Button>
</span>
)}
</div>
</div>
</Form>
)}
</Formik>
);
} else {
const LoginSchema = Yup.object().shape({
username: Yup.string().required(
intl.formatMessage(messages.validationusernamerequired)
),
password: Yup.string(),
});
const baseUrl = settings.currentSettings.jellyfinExternalHost
? settings.currentSettings.jellyfinExternalHost
: settings.currentSettings.jellyfinHost;
const jellyfinForgotPasswordUrl =
settings.currentSettings.jellyfinForgotPasswordUrl;
return (
<div>
<Formik
initialValues={{
username: '',
password: '',
}}
validationSchema={LoginSchema}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/auth/jellyfin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: values.username,
password: values.password,
email: values.username,
}),
});
if (!res.ok) throw new Error(res.statusText, { cause: res });
} catch (e) {
let errorData;
try {
errorData = await e.cause?.text();
errorData = JSON.parse(errorData);
} catch {
/* empty */
}
let errorMessage = null;
switch (errorData?.message) {
case ApiErrorCode.InvalidUrl:
errorMessage = messages.invalidurlerror;
break;
case ApiErrorCode.InvalidCredentials:
errorMessage = messages.credentialerror;
break;
case ApiErrorCode.NotAdmin:
errorMessage = messages.adminerror;
break;
case ApiErrorCode.NoAdminUser:
errorMessage = messages.noadminerror;
break;
default:
errorMessage = messages.loginerror;
break;
}
toasts.addToast(
intl.formatMessage(errorMessage, mediaServerFormatValues),
{
autoDismiss: true,
appearance: 'error',
}
);
} finally {
revalidate();
}
}}
>
{({ errors, touched, isSubmitting, isValid }) => {
return (
<>
<Form>
<div className="sm:border-t sm:border-gray-800">
<label htmlFor="username" className="text-label">
{intl.formatMessage(messages.username)}
</label>
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
<div className="flex max-w-lg rounded-md shadow-sm">
<Field
id="username"
name="username"
type="text"
placeholder={intl.formatMessage(messages.username)}
/>
</div>
{errors.username && touched.username && (
<div className="error">{errors.username}</div>
)}
<div className="mt-1 mb-2">
<div className="form-input-field">
<SensitiveInput
as="field"
id="password"
name="password"
type="password"
autoComplete="current-password"
placeholder={intl.formatMessage(messages.password)}
className="!bg-gray-700/80 placeholder:text-gray-400"
/>
</div>
<label htmlFor="password" className="text-label">
{intl.formatMessage(messages.password)}
</label>
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
<div className="flex max-w-lg rounded-md shadow-sm">
<Field
id="password"
name="password"
type="password"
placeholder={intl.formatMessage(messages.password)}
/>
</div>
<div className="flex">
{errors.password && touched.password && (
<div className="error">{errors.password}</div>
)}
</div>
</div>
<div className="mt-8 border-t border-gray-700 pt-5">
<div className="flex justify-between">
<span className="inline-flex rounded-md shadow-sm">
<Button
as="a"
buttonType="ghost"
<div className="flex-grow"></div>
{baseUrl && (
<a
href={
jellyfinForgotPasswordUrl
? `${jellyfinForgotPasswordUrl}`
@@ -485,31 +176,35 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
: ''
}forgotpassword.html`
}
className="pt-2 text-sm text-indigo-500 hover:text-indigo-400"
>
{intl.formatMessage(messages.forgotpassword)}
</Button>
</span>
<span className="inline-flex rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting || !isValid}
>
{isSubmitting
? intl.formatMessage(messages.signingin)
: intl.formatMessage(messages.signin)}
</Button>
</span>
</a>
)}
</div>
</div>
</Form>
</>
);
}}
</Formik>
</div>
);
}
</div>
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting || !isValid}
className="mt-2 w-full shadow-sm"
>
<ArrowLeftOnRectangleIcon />
<span>
{isSubmitting
? intl.formatMessage(messages.signingin)
: intl.formatMessage(messages.signin)}
</span>
</Button>
</Form>
</>
);
}}
</Formik>
</div>
);
};
export default JellyfinLogin;

View File

@@ -2,10 +2,7 @@ import Button from '@app/components/Common/Button';
import SensitiveInput from '@app/components/Common/SensitiveInput';
import useSettings from '@app/hooks/useSettings';
import defineMessages from '@app/utils/defineMessages';
import {
ArrowLeftOnRectangleIcon,
LifebuoyIcon,
} from '@heroicons/react/24/outline';
import { ArrowLeftOnRectangleIcon } from '@heroicons/react/24/outline';
import { Field, Form, Formik } from 'formik';
import Link from 'next/link';
import { useState } from 'react';
@@ -13,6 +10,7 @@ import { useIntl } from 'react-intl';
import * as Yup from 'yup';
const messages = defineMessages('components.Login', {
loginwithapp: 'Login with {appName}',
username: 'Username',
email: 'Email Address',
password: 'Password',
@@ -53,6 +51,7 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => {
password: '',
}}
validationSchema={LoginSchema}
validateOnBlur={false}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/auth/local', {
@@ -78,19 +77,24 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => {
<>
<Form>
<div>
<label htmlFor="email" className="text-label">
{intl.formatMessage(messages.email) +
' / ' +
intl.formatMessage(messages.username)}
</label>
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
<h2 className="mb-6 -mt-1 text-center text-lg font-bold text-neutral-200">
{intl.formatMessage(messages.loginwithapp, {
appName: settings.currentSettings.applicationTitle,
})}
</h2>
<div className="mt-1 mb-4">
<div className="form-input-field">
<Field
id="email"
name="email"
placeholder={`${intl.formatMessage(
messages.email
)} / ${intl.formatMessage(messages.username)}`}
type="text"
inputMode="email"
data-testid="email"
className="!bg-gray-700/80 placeholder:text-gray-400"
/>
</div>
{errors.email &&
@@ -99,25 +103,35 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => {
<div className="error">{errors.email}</div>
)}
</div>
<label htmlFor="password" className="text-label">
{intl.formatMessage(messages.password)}
</label>
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
<div className="mt-1 mb-2">
<div className="form-input-field">
<SensitiveInput
as="field"
id="password"
name="password"
type="password"
placeholder={intl.formatMessage(messages.password)}
autoComplete="current-password"
data-testid="password"
className="!bg-gray-700/80 placeholder:text-gray-400"
/>
</div>
{errors.password &&
touched.password &&
typeof errors.password === 'string' && (
<div className="error">{errors.password}</div>
<div className="flex">
{errors.password &&
touched.password &&
typeof errors.password === 'string' && (
<div className="error">{errors.password}</div>
)}
<div className="flex-grow"></div>
{passwordResetEnabled && (
<Link
href="/resetpassword"
className="pt-2 text-sm text-indigo-500 hover:text-indigo-400"
>
{intl.formatMessage(messages.forgotpassword)}
</Link>
)}
</div>
</div>
{loginError && (
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
@@ -125,37 +139,21 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => {
</div>
)}
</div>
<div className="mt-8 border-t border-gray-700 pt-5">
<div className="flex flex-row-reverse justify-between">
<span className="inline-flex rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting || !isValid}
data-testid="local-signin-button"
>
<ArrowLeftOnRectangleIcon />
<span>
{isSubmitting
? intl.formatMessage(messages.signingin)
: intl.formatMessage(messages.signin)}
</span>
</Button>
</span>
{passwordResetEnabled && (
<span className="inline-flex rounded-md shadow-sm">
<Link href="/resetpassword" passHref legacyBehavior>
<Button as="a" buttonType="ghost">
<LifebuoyIcon />
<span>
{intl.formatMessage(messages.forgotpassword)}
</span>
</Button>
</Link>
</span>
)}
</div>
</div>
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting || !isValid}
data-testid="local-signin-button"
className="mt-2 w-full shadow-sm"
>
<ArrowLeftOnRectangleIcon />
<span>
{isSubmitting
? intl.formatMessage(messages.signingin)
: intl.formatMessage(messages.signin)}
</span>
</Button>
</Form>
</>
);

View File

@@ -0,0 +1,62 @@
import PlexIcon from '@app/assets/services/plex.svg';
import Button from '@app/components/Common/Button';
import { SmallLoadingSpinner } from '@app/components/Common/LoadingSpinner';
import usePlexLogin from '@app/hooks/usePlexLogin';
import defineMessages from '@app/utils/defineMessages';
import { FormattedMessage } from 'react-intl';
const messages = defineMessages('components.Login', {
loginwithapp: 'Login with {appName}',
});
interface PlexLoginButtonProps {
onAuthToken: (authToken: string) => void;
isProcessing?: boolean;
onError?: (message: string) => void;
large?: boolean;
}
const PlexLoginButton = ({
onAuthToken,
onError,
isProcessing,
large,
}: PlexLoginButtonProps) => {
const { loading, login } = usePlexLogin({ onAuthToken, onError });
return (
<Button
className="relative flex-1 border-[#cc7b19] bg-[rgba(204,123,25,0.3)] hover:border-[#cc7b19] hover:bg-[rgba(204,123,25,0.7)] disabled:opacity-50"
onClick={login}
disabled={loading || isProcessing}
data-testid="plex-login-button"
>
{loading && (
<div className="absolute right-0 mr-4 h-4 w-4">
<SmallLoadingSpinner />
</div>
)}
{large ? (
<FormattedMessage
{...messages.loginwithapp}
values={{
appName: <PlexIcon className="mt-[2px] ml-[0.35em] w-8" />,
}}
>
{(chunks) => (
<>
{chunks.map((c) =>
typeof c === 'string' ? <span>{c}</span> : c
)}
</>
)}
</FormattedMessage>
) : (
<PlexIcon className="w-8" />
)}
</Button>
);
};
export default PlexLoginButton;

View File

@@ -1,9 +1,13 @@
import Accordion from '@app/components/Common/Accordion';
import EmbyLogo from '@app/assets/services/emby-icon-only.svg';
import JellyfinLogo from '@app/assets/services/jellyfin-icon.svg';
import PlexLogo from '@app/assets/services/plex.svg';
import Button from '@app/components/Common/Button';
import ImageFader from '@app/components/Common/ImageFader';
import PageTitle from '@app/components/Common/PageTitle';
import LanguagePicker from '@app/components/Layout/LanguagePicker';
import JellyfinLogin from '@app/components/Login/JellyfinLogin';
import LocalLogin from '@app/components/Login/LocalLogin';
import PlexLoginButton from '@app/components/PlexLoginButton';
import PlexLoginButton from '@app/components/Login/PlexLoginButton';
import useSettings from '@app/hooks/useSettings';
import { useUser } from '@app/hooks/useUser';
import defineMessages from '@app/utils/defineMessages';
@@ -12,10 +16,10 @@ import { XCircleIcon } from '@heroicons/react/24/solid';
import { MediaServerType } from '@server/constants/server';
import { useRouter } from 'next/dist/client/router';
import Image from 'next/image';
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useIntl } from 'react-intl';
import { CSSTransition, SwitchTransition } from 'react-transition-group';
import useSWR from 'swr';
import JellyfinLogin from './JellyfinLogin';
const messages = defineMessages('components.Login', {
signin: 'Sign In',
@@ -23,16 +27,21 @@ const messages = defineMessages('components.Login', {
signinwithplex: 'Use your Plex account',
signinwithjellyfin: 'Use your {mediaServerName} account',
signinwithoverseerr: 'Use your {applicationTitle} account',
orsigninwith: 'Or sign in with',
});
const Login = () => {
const intl = useIntl();
const router = useRouter();
const settings = useSettings();
const { user, revalidate } = useUser();
const [error, setError] = useState('');
const [isProcessing, setProcessing] = useState(false);
const [authToken, setAuthToken] = useState<string | undefined>(undefined);
const { user, revalidate } = useUser();
const router = useRouter();
const settings = useSettings();
const [mediaServerLogin, setMediaServerLogin] = useState(
settings.currentSettings.mediaServerLogin
);
// Effect that is triggered when the `authToken` comes back from the Plex OAuth
// We take the token and attempt to sign in. If we get a success message, we will
@@ -86,14 +95,73 @@ const Login = () => {
revalidateOnFocus: false,
});
const mediaServerFormatValues = {
mediaServerName:
settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
? 'Jellyfin'
: settings.currentSettings.mediaServerType === MediaServerType.EMBY
? 'Emby'
: undefined,
};
const mediaServerName =
settings.currentSettings.mediaServerType === MediaServerType.PLEX
? 'Plex'
: settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
? 'Jellyfin'
: settings.currentSettings.mediaServerType === MediaServerType.EMBY
? 'Emby'
: undefined;
const MediaServerLogo =
settings.currentSettings.mediaServerType === MediaServerType.PLEX
? PlexLogo
: settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
? JellyfinLogo
: settings.currentSettings.mediaServerType === MediaServerType.EMBY
? EmbyLogo
: undefined;
const isJellyfin =
settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN ||
settings.currentSettings.mediaServerType === MediaServerType.EMBY;
const mediaServerLoginRef = useRef<HTMLDivElement>(null);
const localLoginRef = useRef<HTMLDivElement>(null);
const loginRef = mediaServerLogin ? mediaServerLoginRef : localLoginRef;
const loginFormVisible =
(isJellyfin && settings.currentSettings.mediaServerLogin) ||
settings.currentSettings.localLogin;
const additionalLoginOptions = [
settings.currentSettings.mediaServerLogin &&
(settings.currentSettings.mediaServerType === MediaServerType.PLEX ? (
<PlexLoginButton
key="plex"
isProcessing={isProcessing}
onAuthToken={(authToken) => setAuthToken(authToken)}
large={!isJellyfin && !settings.currentSettings.localLogin}
/>
) : (
settings.currentSettings.localLogin &&
(mediaServerLogin ? (
<Button
key="jellyseerr"
data-testid="jellyseerr-login-button"
className="flex-1 bg-transparent"
onClick={() => setMediaServerLogin(false)}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src="/os_icon.svg"
alt={settings.currentSettings.applicationTitle}
className="mr-2 h-5"
/>
<span>{settings.currentSettings.applicationTitle}</span>
</Button>
) : (
<Button
key="mediaserver"
data-testid="mediaserver-login-button"
className="flex-1 bg-transparent"
onClick={() => setMediaServerLogin(true)}
>
<MediaServerLogo />
<span>{mediaServerName}</span>
</Button>
))
)),
].filter((o): o is JSX.Element => !!o);
return (
<div className="relative flex min-h-screen flex-col bg-gray-900 py-14">
@@ -112,9 +180,6 @@ const Login = () => {
<div className="relative h-48 w-full max-w-full">
<Image src="/logo_stacked.svg" alt="Logo" fill />
</div>
<h2 className="mt-12 text-center text-3xl font-extrabold leading-9 text-gray-100">
{intl.formatMessage(messages.signinheader)}
</h2>
</div>
<div className="relative z-50 mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div
@@ -145,65 +210,71 @@ const Login = () => {
</div>
</div>
</Transition>
<Accordion single atLeastOne>
{({ openIndexes, handleClick, AccordionContent }) => (
<>
<button
className={`w-full cursor-default bg-gray-800 bg-opacity-70 py-2 text-center text-sm font-bold text-gray-400 transition-colors duration-200 focus:outline-none sm:rounded-t-lg ${
openIndexes.includes(0) && 'text-indigo-500'
} ${
settings.currentSettings.localLogin &&
'hover:cursor-pointer hover:bg-gray-700'
}`}
onClick={() => handleClick(0)}
disabled={!settings.currentSettings.localLogin}
>
{settings.currentSettings.mediaServerType ==
MediaServerType.PLEX
? intl.formatMessage(messages.signinwithplex)
: intl.formatMessage(
messages.signinwithjellyfin,
mediaServerFormatValues
)}
</button>
<AccordionContent isOpen={openIndexes.includes(0)}>
<div className="px-10 py-8">
{settings.currentSettings.mediaServerType ==
MediaServerType.PLEX ? (
<PlexLoginButton
isProcessing={isProcessing}
onAuthToken={(authToken) => setAuthToken(authToken)}
/>
) : (
<JellyfinLogin revalidate={revalidate} />
)}
</div>
</AccordionContent>
{settings.currentSettings.localLogin && (
<div>
<button
className={`w-full cursor-default bg-gray-800 bg-opacity-70 py-2 text-center text-sm font-bold text-gray-400 transition-colors duration-200 hover:cursor-pointer hover:bg-gray-700 focus:outline-none ${
openIndexes.includes(1)
? 'text-indigo-500'
: 'sm:rounded-b-lg'
}`}
onClick={() => handleClick(1)}
>
{intl.formatMessage(messages.signinwithoverseerr, {
applicationTitle:
settings.currentSettings.applicationTitle,
})}
</button>
<AccordionContent isOpen={openIndexes.includes(1)}>
<div className="px-10 py-8">
<LocalLogin revalidate={revalidate} />
</div>
</AccordionContent>
</div>
)}
</>
)}
</Accordion>
<div className="px-10 py-8">
<SwitchTransition mode="out-in">
<CSSTransition
key={mediaServerLogin ? 'ms' : 'local'}
nodeRef={loginRef}
addEndListener={(done) => {
loginRef.current?.addEventListener(
'transitionend',
done,
false
);
}}
onEntered={() => {
document
.querySelector<HTMLInputElement>('#email, #username')
?.focus();
}}
classNames={{
appear: 'opacity-0',
appearActive: 'transition-opacity duration-500 opacity-100',
enter: 'opacity-0',
enterActive: 'transition-opacity duration-500 opacity-100',
exitActive: 'transition-opacity duration-0 opacity-0',
}}
>
<div ref={loginRef} className="button-container">
{isJellyfin &&
(mediaServerLogin ||
!settings.currentSettings.localLogin) ? (
<JellyfinLogin
serverType={settings.currentSettings.mediaServerType}
revalidate={revalidate}
/>
) : (
settings.currentSettings.localLogin && (
<LocalLogin revalidate={revalidate} />
)
)}
</div>
</CSSTransition>
</SwitchTransition>
{additionalLoginOptions.length > 0 &&
(loginFormVisible ? (
<div className="flex items-center py-5">
<div className="flex-grow border-t border-gray-600"></div>
<span className="mx-2 flex-shrink text-sm text-gray-400">
{intl.formatMessage(messages.orsigninwith)}
</span>
<div className="flex-grow border-t border-gray-600"></div>
</div>
) : (
<h2 className="mb-6 text-center text-lg font-bold text-neutral-200">
{intl.formatMessage(messages.signinheader)}
</h2>
))}
<div
className={`flex w-full flex-wrap gap-2 ${
!loginFormVisible ? 'flex-col' : ''
}`}
>
{additionalLoginOptions}
</div>
</div>
</>
</div>
</div>

View File

@@ -38,14 +38,14 @@ import {
ExclamationTriangleIcon,
EyeSlashIcon,
FilmIcon,
MinusCircleIcon,
PlayIcon,
StarIcon,
TicketIcon,
} from '@heroicons/react/24/outline';
import {
ChevronDoubleDownIcon,
ChevronDoubleUpIcon,
MinusCircleIcon,
StarIcon,
} from '@heroicons/react/24/solid';
import { type RatingResponse } from '@server/api/ratings';
import { IssueStatus } from '@server/constants/issue';
@@ -102,7 +102,7 @@ const messages = defineMessages('components.MovieDetails', {
watchlistSuccess: '<strong>{title}</strong> added to watchlist successfully!',
watchlistDeleted:
'<strong>{title}</strong> Removed from watchlist successfully!',
watchlistError: 'Something went wrong try again.',
watchlistError: 'Something went wrong. Please try again.',
removefromwatchlist: 'Remove From Watchlist',
addtowatchlist: 'Add To Watchlist',
});

View File

@@ -1,66 +0,0 @@
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import PlexOAuth from '@app/utils/plex';
import { ArrowLeftOnRectangleIcon } from '@heroicons/react/24/outline';
import { useState } from 'react';
import { useIntl } from 'react-intl';
const messages = defineMessages('components.PlexLoginButton', {
signinwithplex: 'Sign In',
signingin: 'Signing In…',
});
const plexOAuth = new PlexOAuth();
interface PlexLoginButtonProps {
onAuthToken: (authToken: string) => void;
isProcessing?: boolean;
onError?: (message: string) => void;
}
const PlexLoginButton = ({
onAuthToken,
onError,
isProcessing,
}: PlexLoginButtonProps) => {
const intl = useIntl();
const [loading, setLoading] = useState(false);
const getPlexLogin = async () => {
setLoading(true);
try {
const authToken = await plexOAuth.login();
setLoading(false);
onAuthToken(authToken);
} catch (e) {
if (onError) {
onError(e.message);
}
setLoading(false);
}
};
return (
<span className="block w-full rounded-md shadow-sm">
<button
type="button"
onClick={() => {
plexOAuth.preparePopup();
setTimeout(() => getPlexLogin(), 1500);
}}
disabled={loading || isProcessing}
className="plex-button"
>
<ArrowLeftOnRectangleIcon />
<span>
{loading
? intl.formatMessage(globalMessages.loading)
: isProcessing
? intl.formatMessage(messages.signingin)
: intl.formatMessage(messages.signinwithplex)}
</span>
</button>
</span>
);
};
export default PlexLoginButton;

View File

@@ -1,4 +1,5 @@
import Button from '@app/components/Common/Button';
import LabeledCheckbox from '@app/components/Common/LabeledCheckbox';
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
import PageTitle from '@app/components/Common/PageTitle';
import PermissionEdit from '@app/components/PermissionEdit';
@@ -13,6 +14,7 @@ import { Field, Form, Formik } from 'formik';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
import useSWR, { mutate } from 'swr';
import * as yup from 'yup';
const messages = defineMessages('components.Settings.SettingsUsers', {
users: 'Users',
@@ -20,9 +22,15 @@ const messages = defineMessages('components.Settings.SettingsUsers', {
userSettingsDescription: 'Configure global and default user settings.',
toastSettingsSuccess: 'User settings saved successfully!',
toastSettingsFailure: 'Something went wrong while saving settings.',
loginMethods: 'Login Methods',
loginMethodsTip: 'Configure login methods for users.',
localLogin: 'Enable Local Sign-In',
localLoginTip:
'Allow users to sign in using their email address and password, instead of {mediaServerName} OAuth',
'Allow users to sign in using their email address and password',
mediaServerLogin: 'Enable {mediaServerName} Sign-In',
mediaServerLoginTip:
'Allow users to sign in using their {mediaServerName} account',
atLeastOneAuth: 'At least one authentication method must be selected.',
newPlexLogin: 'Enable New {mediaServerName} Sign-In',
newPlexLoginTip:
'Allow {mediaServerName} users to sign in without first being imported',
@@ -42,6 +50,27 @@ const SettingsUsers = () => {
} = useSWR<MainSettings>('/api/v1/settings/main');
const settings = useSettings();
const schema = yup
.object()
.shape({
localLogin: yup.boolean(),
mediaServerLogin: yup.boolean(),
})
.test({
name: 'atLeastOneAuth',
test: function (values) {
const isValid = ['localLogin', 'mediaServerLogin'].some(
(field) => !!values[field]
);
if (isValid) return true;
return this.createError({
path: 'localLogin | mediaServerLogin',
message: intl.formatMessage(messages.atLeastOneAuth),
});
},
});
if (!data && !error) {
return <LoadingSpinner />;
}
@@ -52,6 +81,8 @@ const SettingsUsers = () => {
? 'Jellyfin'
: settings.currentSettings.mediaServerType === MediaServerType.EMBY
? 'Emby'
: settings.currentSettings.mediaServerType === MediaServerType.PLEX
? 'Plex'
: undefined,
};
@@ -73,6 +104,7 @@ const SettingsUsers = () => {
<Formik
initialValues={{
localLogin: data?.localLogin,
mediaServerLogin: data?.mediaServerLogin,
newPlexLogin: data?.newPlexLogin,
movieQuotaLimit: data?.defaultQuotas.movie.quotaLimit ?? 0,
movieQuotaDays: data?.defaultQuotas.movie.quotaDays ?? 7,
@@ -80,6 +112,7 @@ const SettingsUsers = () => {
tvQuotaDays: data?.defaultQuotas.tv.quotaDays ?? 7,
defaultPermissions: data?.defaultPermissions ?? 0,
}}
validationSchema={schema}
enableReinitialize
onSubmit={async (values) => {
try {
@@ -90,6 +123,7 @@ const SettingsUsers = () => {
},
body: JSON.stringify({
localLogin: values.localLogin,
mediaServerLogin: values.mediaServerLogin,
newPlexLogin: values.newPlexLogin,
defaultQuotas: {
movie: {
@@ -121,30 +155,61 @@ const SettingsUsers = () => {
}
}}
>
{({ isSubmitting, values, setFieldValue }) => {
{({ isSubmitting, isValid, values, errors, setFieldValue }) => {
return (
<Form className="section">
<div className="form-row">
<label htmlFor="localLogin" className="checkbox-label">
{intl.formatMessage(messages.localLogin)}
<span className="label-tip">
{intl.formatMessage(
messages.localLoginTip,
mediaServerFormatValues
<div
role="group"
aria-labelledby="group-label"
className="form-group"
>
<div className="form-row">
<span id="group-label" className="group-label">
{intl.formatMessage(messages.loginMethods)}
<span className="label-tip">
{intl.formatMessage(messages.loginMethodsTip)}
</span>
{'localLogin | mediaServerLogin' in errors && (
<span className="error">
{errors['localLogin | mediaServerLogin'] as string}
</span>
)}
</span>
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="localLogin"
name="localLogin"
onChange={() => {
setFieldValue('localLogin', !values.localLogin);
}}
/>
<div className="form-input-area max-w-lg">
<LabeledCheckbox
id="localLogin"
label={intl.formatMessage(messages.localLogin)}
description={intl.formatMessage(
messages.localLoginTip,
mediaServerFormatValues
)}
onChange={() =>
setFieldValue('localLogin', !values.localLogin)
}
/>
<LabeledCheckbox
id="mediaServerLogin"
className="mt-4"
label={intl.formatMessage(
messages.mediaServerLogin,
mediaServerFormatValues
)}
description={intl.formatMessage(
messages.mediaServerLoginTip,
mediaServerFormatValues
)}
onChange={() =>
setFieldValue(
'mediaServerLogin',
!values.mediaServerLogin
)
}
/>
</div>
</div>
</div>
<div className="form-row">
<label htmlFor="newPlexLogin" className="checkbox-label">
{intl.formatMessage(
@@ -229,7 +294,7 @@ const SettingsUsers = () => {
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting}
disabled={isSubmitting || !isValid}
>
<ArrowDownOnSquareIcon />
<span>

View File

@@ -0,0 +1,352 @@
import Button from '@app/components/Common/Button';
import Tooltip from '@app/components/Common/Tooltip';
import defineMessages from '@app/utils/defineMessages';
import { InformationCircleIcon } from '@heroicons/react/24/solid';
import { ApiErrorCode } from '@server/constants/error';
import { MediaServerType, ServerType } from '@server/constants/server';
import { Field, Form, Formik } from 'formik';
import { FormattedMessage, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
import * as Yup from 'yup';
const messages = defineMessages('components.Login', {
username: 'Username',
password: 'Password',
hostname: '{mediaServerName} URL',
port: 'Port',
enablessl: 'Use SSL',
urlBase: 'URL Base',
email: 'Email Address',
emailtooltip:
'Address does not need to be associated with your {mediaServerName} instance.',
validationhostrequired: '{mediaServerName} URL required',
validationhostformat: 'Valid URL required',
validationemailrequired: 'You must provide a valid email address',
validationemailformat: 'Valid email required',
validationusernamerequired: 'Username required',
validationpasswordrequired: 'You must provide a password',
validationservertyperequired: 'Please select a server type',
validationHostnameRequired: 'You must provide a valid hostname or IP address',
validationPortRequired: 'You must provide a valid port number',
validationUrlTrailingSlash: 'URL must not end in a trailing slash',
validationUrlBaseLeadingSlash: 'URL base must have a leading slash',
validationUrlBaseTrailingSlash: 'URL base must not end in a trailing slash',
loginerror: 'Something went wrong while trying to sign in.',
adminerror: 'You must use an admin account to sign in.',
noadminerror: 'No admin user found on the server.',
credentialerror: 'The username or password is incorrect.',
invalidurlerror: 'Unable to connect to {mediaServerName} server.',
signingin: 'Signing In…',
signin: 'Sign In',
initialsigningin: 'Connecting…',
initialsignin: 'Connect',
forgotpassword: 'Forgot Password?',
servertype: 'Server Type',
back: 'Go back',
});
interface JellyfinSetupProps {
revalidate: () => void;
serverType?: MediaServerType;
onCancel?: () => void;
}
function JellyfinSetup({
revalidate,
serverType,
onCancel,
}: JellyfinSetupProps) {
const toasts = useToasts();
const intl = useIntl();
const mediaServerFormatValues = {
mediaServerName:
serverType === MediaServerType.JELLYFIN
? ServerType.JELLYFIN
: serverType === MediaServerType.EMBY
? ServerType.EMBY
: 'Media Server',
};
const LoginSchema = Yup.object().shape({
hostname: Yup.string().required(
intl.formatMessage(
messages.validationhostrequired,
mediaServerFormatValues
)
),
port: Yup.number().required(
intl.formatMessage(messages.validationPortRequired)
),
urlBase: Yup.string()
.test(
'leading-slash',
intl.formatMessage(messages.validationUrlBaseLeadingSlash),
(value) => !value || value.startsWith('/')
)
.test(
'trailing-slash',
intl.formatMessage(messages.validationUrlBaseTrailingSlash),
(value) => !value || !value.endsWith('/')
),
email: Yup.string()
.email(intl.formatMessage(messages.validationemailformat))
.required(intl.formatMessage(messages.validationemailrequired)),
username: Yup.string().required(
intl.formatMessage(messages.validationusernamerequired)
),
password: Yup.string(),
});
return (
<Formik
initialValues={{
username: '',
password: '',
hostname: '',
port: 8096,
useSsl: false,
urlBase: '',
email: '',
}}
validationSchema={LoginSchema}
onSubmit={async (values) => {
try {
// Check if serverType is either 'Jellyfin' or 'Emby'
// if (serverType !== 'Jellyfin' && serverType !== 'Emby') {
// throw new Error('Invalid serverType'); // You can customize the error message
// }
const res = await fetch('/api/v1/auth/jellyfin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: values.username,
password: values.password,
hostname: values.hostname,
port: values.port,
useSsl: values.useSsl,
urlBase: values.urlBase,
email: values.email,
serverType: serverType,
}),
});
if (!res.ok) throw new Error(res.statusText, { cause: res });
} catch (e) {
let errorData;
try {
errorData = await e.cause?.text();
errorData = JSON.parse(errorData);
} catch {
/* empty */
}
let errorMessage = null;
switch (errorData?.message) {
case ApiErrorCode.InvalidUrl:
errorMessage = messages.invalidurlerror;
break;
case ApiErrorCode.InvalidCredentials:
errorMessage = messages.credentialerror;
break;
case ApiErrorCode.NotAdmin:
errorMessage = messages.adminerror;
break;
case ApiErrorCode.NoAdminUser:
errorMessage = messages.noadminerror;
break;
default:
errorMessage = messages.loginerror;
break;
}
toasts.addToast(
intl.formatMessage(errorMessage, mediaServerFormatValues),
{
autoDismiss: true,
appearance: 'error',
}
);
} finally {
revalidate();
}
}}
>
{({ errors, touched, values, setFieldValue, isSubmitting, isValid }) => (
<Form>
<div className="sm:border-t sm:border-gray-800">
<div className="flex flex-col sm:flex-row sm:gap-4">
<div className="w-full">
<label htmlFor="hostname" className="text-label">
{intl.formatMessage(
messages.hostname,
mediaServerFormatValues
)}
</label>
<div className="mt-1 mb-2 sm:col-span-2 sm:mb-0 sm:mt-0">
<div className="flex rounded-md shadow-sm">
<span className="inline-flex cursor-default items-center rounded-l-md border border-r-0 border-gray-500 bg-gray-800 px-3 text-gray-100 sm:text-sm">
{values.useSsl ? 'https://' : 'http://'}
</span>
<Field
id="hostname"
name="hostname"
type="text"
className="rounded-r-only flex-1"
placeholder={intl.formatMessage(
messages.hostname,
mediaServerFormatValues
)}
/>
</div>
{errors.hostname && touched.hostname && (
<div className="error">{errors.hostname}</div>
)}
</div>
</div>
<div className="flex-1">
<label htmlFor="port" className="text-label">
{intl.formatMessage(messages.port)}
</label>
<div className="mt-1 sm:mt-0">
<Field
id="port"
name="port"
inputMode="numeric"
type="text"
className="short flex-1"
placeholder={intl.formatMessage(messages.port)}
/>
{errors.port && touched.port && (
<div className="error">{errors.port}</div>
)}
</div>
</div>
</div>
<label htmlFor="useSsl" className="text-label mt-2">
{intl.formatMessage(messages.enablessl)}
</label>
<div className="mt-1 mb-2 sm:col-span-2">
<div className="flex rounded-md shadow-sm">
<Field
id="useSsl"
name="useSsl"
type="checkbox"
onChange={() => {
setFieldValue('useSsl', !values.useSsl);
setFieldValue('port', values.useSsl ? 8096 : 443);
}}
/>
</div>
</div>
<label htmlFor="urlBase" className="text-label mt-1">
{intl.formatMessage(messages.urlBase)}
</label>
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
<div className="flex rounded-md shadow-sm">
<Field
type="text"
inputMode="url"
id="urlBase"
name="urlBase"
placeholder={intl.formatMessage(messages.urlBase)}
/>
</div>
{errors.urlBase && touched.urlBase && (
<div className="error">{errors.urlBase}</div>
)}
</div>
<label
htmlFor="email"
className="text-label inline-flex gap-1 align-middle"
>
{intl.formatMessage(messages.email)}
<span className="label-tip">
<Tooltip
content={intl.formatMessage(
messages.emailtooltip,
mediaServerFormatValues
)}
>
<span className="tooltip-trigger">
<InformationCircleIcon className="h-4 w-4" />
</span>
</Tooltip>
</span>
</label>
<div className="mt-1 sm:col-span-2 sm:mb-2 sm:mt-0">
<div className="flex rounded-md shadow-sm">
<Field
id="email"
name="email"
type="text"
placeholder={intl.formatMessage(messages.email)}
/>
</div>
{errors.email && touched.email && (
<div className="error">{errors.email}</div>
)}
</div>
<label htmlFor="username" className="text-label">
{intl.formatMessage(messages.username)}
</label>
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
<div className="flex rounded-md shadow-sm">
<Field
id="username"
name="username"
type="text"
placeholder={intl.formatMessage(messages.username)}
/>
</div>
{errors.username && touched.username && (
<div className="error">{errors.username}</div>
)}
</div>
<label htmlFor="password" className="text-label">
{intl.formatMessage(messages.password)}
</label>
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
<div className="flexrounded-md shadow-sm">
<Field
id="password"
name="password"
type="password"
placeholder={intl.formatMessage(messages.password)}
/>
</div>
{errors.password && touched.password && (
<div className="error">{errors.password}</div>
)}
</div>
</div>
<div className="mt-8 border-t border-gray-700 pt-5">
<div className="flex flex-row-reverse justify-between">
<span className="inline-flex rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting || !isValid}
>
{isSubmitting
? intl.formatMessage(messages.signingin)
: intl.formatMessage(messages.signin)}
</Button>
</span>
{onCancel && (
<span className="inline-flex rounded-md shadow-sm">
<Button buttonType="default" onClick={() => onCancel()}>
<FormattedMessage {...messages.back} />
</Button>
</span>
)}
</div>
</div>
</Form>
)}
</Formik>
);
}
export default JellyfinSetup;

View File

@@ -1,4 +1,4 @@
import PlexLoginButton from '@app/components/PlexLoginButton';
import PlexLoginButton from '@app/components/Login/PlexLoginButton';
import { useUser } from '@app/hooks/useUser';
import defineMessages from '@app/utils/defineMessages';
import { useEffect, useState } from 'react';

View File

@@ -1,6 +1,6 @@
import Button from '@app/components/Common/Button';
import JellyfinLogin from '@app/components/Login/JellyfinLogin';
import PlexLoginButton from '@app/components/PlexLoginButton';
import PlexLoginButton from '@app/components/Login/PlexLoginButton';
import JellyfinSetup from '@app/components/Setup/JellyfinSetup';
import { useUser } from '@app/hooks/useUser';
import defineMessages from '@app/utils/defineMessages';
import { MediaServerType } from '@server/constants/server';
@@ -83,11 +83,9 @@ const SetupLogin: React.FC<LoginWithMediaServerProps> = ({
</div>
{serverType === MediaServerType.PLEX && (
<>
<div
className="px-10 py-8"
style={{ backgroundColor: 'rgba(0,0,0,0.3)' }}
>
<div className="flex justify-center bg-black/30 px-10 py-8">
<PlexLoginButton
large
onAuthToken={(authToken) => {
setMediaServerType(MediaServerType.PLEX);
setAuthToken(authToken);
@@ -102,16 +100,14 @@ const SetupLogin: React.FC<LoginWithMediaServerProps> = ({
</>
)}
{serverType === MediaServerType.JELLYFIN && (
<JellyfinLogin
initial={true}
<JellyfinSetup
revalidate={revalidate}
serverType={serverType}
onCancel={onCancel}
/>
)}
{serverType === MediaServerType.EMBY && (
<JellyfinLogin
initial={true}
<JellyfinSetup
revalidate={revalidate}
serverType={serverType}
onCancel={onCancel}

View File

@@ -133,10 +133,6 @@ const Setup = () => {
setCurrentStep(3);
}
}
if (currentStep === 3) {
validateLibraries();
}
}, [
settings.currentSettings.mediaServerType,
settings.currentSettings.initialized,
@@ -148,6 +144,13 @@ const Setup = () => {
validateLibraries,
]);
useEffect(() => {
if (currentStep === 3) {
validateLibraries();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentStep]);
const handleComplete = () => {
validateLibraries();
};

View File

@@ -51,7 +51,7 @@ const messages = defineMessages('components.TitleCard', {
watchlistDeleted:
'<strong>{title}</strong> Removed from watchlist successfully!',
watchlistCancel: 'watchlist for <strong>{title}</strong> canceled.',
watchlistError: 'Something went wrong try again.',
watchlistError: 'Something went wrong. Please try again.',
});
const TitleCard = ({

View File

@@ -41,13 +41,11 @@ import {
ExclamationTriangleIcon,
EyeSlashIcon,
FilmIcon,
PlayIcon,
} from '@heroicons/react/24/outline';
import {
ChevronDownIcon,
MinusCircleIcon,
PlayIcon,
StarIcon,
} from '@heroicons/react/24/solid';
} from '@heroicons/react/24/outline';
import { ChevronDownIcon } from '@heroicons/react/24/solid';
import type { RTRating } from '@server/api/rating/rottentomatoes';
import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants';
import { IssueStatus } from '@server/constants/issue';
@@ -103,7 +101,7 @@ const messages = defineMessages('components.TvDetails', {
watchlistSuccess: '<strong>{title}</strong> added to watchlist successfully!',
watchlistDeleted:
'<strong>{title}</strong> Removed from watchlist successfully!',
watchlistError: 'Something went wrong try again.',
watchlistError: 'Something went wrong. Please try again.',
removefromwatchlist: 'Remove From Watchlist',
addtowatchlist: 'Add To Watchlist',
});

View File

@@ -1,9 +1,10 @@
import Modal from '@app/components/Common/Modal';
import PermissionEdit from '@app/components/PermissionEdit';
import type { User } from '@app/hooks/useUser';
import { useUser } from '@app/hooks/useUser';
import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { hasPermission } from '@server/lib/permissions';
import { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -79,7 +80,10 @@ const BulkEditModal = ({
const { permissions: allPermissionsEqual } = selectedUsers.reduce(
({ permissions: aPerms }, { permissions: bPerms }) => {
return {
permissions: aPerms === bPerms ? aPerms : NaN,
permissions:
aPerms === bPerms || hasPermission(Permission.ADMIN, aPerms)
? aPerms
: NaN,
};
},
{ permissions: selectedUsers[0].permissions }

View File

@@ -14,6 +14,7 @@ const defaultSettings = {
applicationUrl: '',
hideAvailable: false,
localLogin: true,
mediaServerLogin: true,
movie4kEnabled: false,
series4kEnabled: false,
discoverRegion: '',

37
src/hooks/usePlexLogin.ts Normal file
View File

@@ -0,0 +1,37 @@
import PlexOAuth from '@app/utils/plex';
import { useState } from 'react';
const plexOAuth = new PlexOAuth();
function usePlexLogin({
onAuthToken,
onError,
}: {
onAuthToken: (authToken: string) => void;
onError?: (err: string) => void;
}) {
const [loading, setLoading] = useState(false);
const getPlexLogin = async () => {
setLoading(true);
try {
const authToken = await plexOAuth.login();
setLoading(false);
onAuthToken(authToken);
} catch (e) {
if (onError) {
onError(e.message);
}
setLoading(false);
}
};
const login = () => {
plexOAuth.preparePopup();
setTimeout(() => getPlexLogin(), 1500);
};
return { loading, login };
}
export default usePlexLogin;

View File

@@ -58,7 +58,7 @@ const globalMessages = defineMessages('i18n', {
blacklist: 'Blacklist',
blacklisted: 'Blacklisted',
blacklistSuccess: '<strong>{title}</strong> was successfully blacklisted.',
blacklistError: 'Something went wrong try again.',
blacklistError: 'Something went wrong. Please try again.',
blacklistDuplicateError:
'<strong>{title}</strong> has already been blacklisted.',
removeFromBlacklistSuccess:

View File

@@ -246,7 +246,9 @@
"components.Login.initialsigningin": "Connecting…",
"components.Login.invalidurlerror": "Unable to connect to {mediaServerName} server.",
"components.Login.loginerror": "Something went wrong while trying to sign in.",
"components.Login.loginwithapp": "Login with {appName}",
"components.Login.noadminerror": "No admin user found on the server.",
"components.Login.orsigninwith": "Or sign in with",
"components.Login.password": "Password",
"components.Login.port": "Port",
"components.Login.save": "Add",
@@ -340,7 +342,7 @@
"components.MovieDetails.tmdbuserscore": "TMDB User Score",
"components.MovieDetails.viewfullcrew": "View Full Crew",
"components.MovieDetails.watchlistDeleted": "<strong>{title}</strong> Removed from watchlist successfully!",
"components.MovieDetails.watchlistError": "Something went wrong try again.",
"components.MovieDetails.watchlistError": "Something went wrong. Please try again.",
"components.MovieDetails.watchlistSuccess": "<strong>{title}</strong> added to watchlist successfully!",
"components.MovieDetails.watchtrailer": "Watch Trailer",
"components.NotificationTypeSelector.adminissuecommentDescription": "Get notified when other users comment on issues.",
@@ -441,8 +443,6 @@
"components.PersonDetails.birthdate": "Born {birthdate}",
"components.PersonDetails.crewmember": "Crew",
"components.PersonDetails.lifespan": "{birthdate} {deathdate}",
"components.PlexLoginButton.signingin": "Signing In…",
"components.PlexLoginButton.signinwithplex": "Sign In",
"components.QuotaSelector.days": "{count, plural, one {day} other {days}}",
"components.QuotaSelector.movieRequests": "{quotaLimit} <quotaUnits>{movies} per {quotaDays} {days}</quotaUnits>",
"components.QuotaSelector.movies": "{count, plural, one {movie} other {movies}}",
@@ -954,10 +954,15 @@
"components.Settings.SettingsMain.validationApplicationUrl": "You must provide a valid URL",
"components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "URL must not end in a trailing slash",
"components.Settings.SettingsMain.validationProxyPort": "You must provide a valid port",
"components.Settings.SettingsUsers.atLeastOneAuth": "At least one authentication method must be selected.",
"components.Settings.SettingsUsers.defaultPermissions": "Default Permissions",
"components.Settings.SettingsUsers.defaultPermissionsTip": "Initial permissions assigned to new users",
"components.Settings.SettingsUsers.localLogin": "Enable Local Sign-In",
"components.Settings.SettingsUsers.localLoginTip": "Allow users to sign in using their email address and password, instead of {mediaServerName} OAuth",
"components.Settings.SettingsUsers.localLoginTip": "Allow users to sign in using their email address and password",
"components.Settings.SettingsUsers.loginMethods": "Login Methods",
"components.Settings.SettingsUsers.loginMethodsTip": "Configure login methods for users.",
"components.Settings.SettingsUsers.mediaServerLogin": "Enable {mediaServerName} Sign-In",
"components.Settings.SettingsUsers.mediaServerLoginTip": "Allow users to sign in using their {mediaServerName} account",
"components.Settings.SettingsUsers.movieRequestLimitLabel": "Global Movie Request Limit",
"components.Settings.SettingsUsers.newPlexLogin": "Enable New {mediaServerName} Sign-In",
"components.Settings.SettingsUsers.newPlexLoginTip": "Allow {mediaServerName} users to sign in without first being imported",
@@ -1171,7 +1176,7 @@
"components.TitleCard.tvdbid": "TheTVDB ID",
"components.TitleCard.watchlistCancel": "watchlist for <strong>{title}</strong> canceled.",
"components.TitleCard.watchlistDeleted": "<strong>{title}</strong> Removed from watchlist successfully!",
"components.TitleCard.watchlistError": "Something went wrong try again.",
"components.TitleCard.watchlistError": "Something went wrong. Please try again.",
"components.TitleCard.watchlistSuccess": "<strong>{title}</strong> added to watchlist successfully!",
"components.TvDetails.Season.noepisodes": "Episode list unavailable.",
"components.TvDetails.Season.somethingwentwrong": "Something went wrong while retrieving season data.",
@@ -1209,7 +1214,7 @@
"components.TvDetails.tmdbuserscore": "TMDB User Score",
"components.TvDetails.viewfullcrew": "View Full Crew",
"components.TvDetails.watchlistDeleted": "<strong>{title}</strong> Removed from watchlist successfully!",
"components.TvDetails.watchlistError": "Something went wrong try again.",
"components.TvDetails.watchlistError": "Something went wrong. Please try again.",
"components.TvDetails.watchlistSuccess": "<strong>{title}</strong> added to watchlist successfully!",
"components.TvDetails.watchtrailer": "Watch Trailer",
"components.UserList.accounttype": "Type",
@@ -1393,7 +1398,7 @@
"i18n.back": "Back",
"i18n.blacklist": "Blacklist",
"i18n.blacklistDuplicateError": "<strong>{title}</strong> has already been blacklisted.",
"i18n.blacklistError": "Something went wrong try again.",
"i18n.blacklistError": "Something went wrong. Please try again.",
"i18n.blacklistSuccess": "<strong>{title}</strong> was successfully blacklisted.",
"i18n.blacklisted": "Blacklisted",
"i18n.cancel": "Cancel",

View File

@@ -194,6 +194,7 @@ CoreApp.getInitialProps = async (initialProps) => {
movie4kEnabled: false,
series4kEnabled: false,
localLogin: true,
mediaServerLogin: true,
discoverRegion: '',
streamingRegion: '',
originalLanguage: '',

View File

@@ -74,15 +74,6 @@
top: env(safe-area-inset-top);
}
.plex-button {
@apply flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-center text-sm font-medium text-white transition duration-150 ease-in-out disabled:opacity-50;
background-color: #cc7b19;
}
.plex-button:hover {
background: #f19a30;
}
.server-type-button {
@apply rounded-md border border-gray-500 bg-gray-700 px-4 py-2 text-white transition duration-150 ease-in-out hover:bg-gray-500;
}
@@ -354,9 +345,8 @@
@apply relative -ml-px inline-flex items-center border border-gray-500 bg-indigo-600 bg-opacity-80 px-3 py-2 text-sm font-medium leading-5 text-white transition duration-150 ease-in-out last:rounded-r-md hover:bg-opacity-100 active:bg-gray-100 active:text-gray-700 sm:px-3.5;
}
.button-md svg,
button.input-action svg,
.plex-button svg {
.button-md :where(svg),
button.input-action svg {
@apply ml-2 mr-2 h-5 w-5 first:ml-0 last:mr-0;
}