Compare commits

..

1 Commits

Author SHA1 Message Date
fallenbagel
efb0269439 ci: attempt to fix arm64 runners with proper scoped caching
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:39:22 +08:00
27 changed files with 121 additions and 253 deletions

View File

@@ -592,15 +592,6 @@
"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-24.04
runs-on: ubuntu-22.04
container: node:22-alpine
steps:
- name: Checkout
@@ -20,7 +20,7 @@ jobs:
- name: Pnpm Setup
uses: pnpm/action-setup@v4
with:
version: 10
version: 9
- name: Get pnpm store directory
shell: sh
run: |
@@ -43,20 +43,17 @@ jobs:
- name: Build
run: pnpm build
build:
build_and_push:
name: Build & Publish Docker Images
if: github.ref == 'refs/heads/develop' && !contains(github.event.head_commit.message, '[skip ci]')
strategy:
matrix:
include:
- runner: ubuntu-24.04
- runner: ubuntu-22.04
platform: linux/amd64
- runner: ubuntu-24.04-arm
- runner: ubuntu-22.04-arm64
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
@@ -78,77 +75,28 @@ jobs:
echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV}
env:
OWNER: ${{ github.repository_owner }}
- 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
- name: Build and push
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 }}
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
tags: |
fallenbagel/jellyseerr:develop
ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop
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: merge_and_push
needs: build_and_push
if: always() && github.event_name != 'pull_request' && !contains(github.event.head_commit.message, '[skip ci]')
runs-on: ubuntu-24.04
runs-on: ubuntu-22.04
steps:
- name: Get Build Job Status
uses: technote-space/workflow-conclusion-action@v3

View File

@@ -21,7 +21,7 @@ jobs:
- name: Pnpm Setup
uses: pnpm/action-setup@v4
with:
version: 10
version: 9
- name: Cypress run
uses: cypress-io/github-action@v6
with:

View File

@@ -25,7 +25,7 @@ jobs:
- name: Pnpm Setup
uses: pnpm/action-setup@v4
with:
version: 10
version: 9
- name: Get pnpm store directory
shell: sh

View File

@@ -12,10 +12,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Pnpm Setup
uses: pnpm/action-setup@v4
with:
version: 10
- name: Get the version
id: get_version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT

View File

@@ -35,7 +35,7 @@ jobs:
- name: Pnpm Setup
uses: pnpm/action-setup@v4
with:
version: 10
version: 9
- name: Get pnpm store directory
shell: sh
run: |

View File

@@ -25,7 +25,7 @@ jobs:
- name: Pnpm Setup
uses: pnpm/action-setup@v4
with:
version: 10
version: 9
- name: Get pnpm store directory
shell: sh
@@ -42,7 +42,7 @@ jobs:
- name: Install dependencies
run: |
cd gen-docs
cd gen-docs
pnpm install --frozen-lockfile
- name: Build website

View File

@@ -29,7 +29,7 @@ RUN pnpm build
# remove development dependencies
RUN pnpm prune --prod --ignore-scripts
RUN rm -rf src server .next/cache charts gen-docs docs
RUN rm -rf src server .next/cache
RUN touch config/DOCKER

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-65-orange.svg"/></a>
<a href="#contributors-"><img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-64-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,7 +168,6 @@ 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,7 +3,7 @@ kubeVersion: ">=1.23.0-0"
name: jellyseerr-chart
description: Jellyseerr helm chart for Kubernetes
type: application
version: 2.1.1
version: 2.0.0
appVersion: "2.3.0"
maintainers:
- name: Jellyseerr

View File

@@ -1,6 +1,6 @@
# jellyseerr-chart
![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)
![Version: 2.0.0](https://img.shields.io/badge/Version-2.0.0-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
@@ -63,5 +63,3 @@ 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

@@ -65,16 +65,10 @@ 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

@@ -94,19 +94,6 @@ resources: {}
# cpu: 100m
# memory: 128Mi
# -- 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: {}
tolerations: []

View File

@@ -15,7 +15,7 @@ import TabItem from '@theme/TabItem';
### Prerequisites
- [Node.js 22.x](https://nodejs.org/en/download/)
- [Pnpm 10.x](https://pnpm.io/installation)
- [Pnpm 9.x](https://pnpm.io/installation)
- [Git](https://git-scm.com/downloads)
## Unix (Linux, macOS)

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

@@ -42,7 +42,6 @@
"@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",
@@ -98,7 +97,6 @@
"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",
@@ -172,7 +170,7 @@
},
"engines": {
"node": "^22.0.0",
"pnpm": "^10.0.0"
"pnpm": "^9.0.0"
},
"overrides": {
"sqlite3/node-gyp": "8.4.1",

16
pnpm-lock.yaml generated
View File

@@ -38,9 +38,6 @@ 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
@@ -206,9 +203,6 @@ 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
@@ -3256,9 +3250,6 @@ 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==}
@@ -9476,9 +9467,6 @@ 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'}
@@ -13749,8 +13737,6 @@ snapshots:
'@types/webxr@0.5.20': {}
'@types/wink-jaro-distance@2.0.2': {}
'@types/xml2js@0.4.11':
dependencies:
'@types/node': 22.10.5
@@ -20919,8 +20905,6 @@ 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

View File

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

View File

@@ -1,7 +1,6 @@
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: {
@@ -16,7 +15,7 @@ interface RTAlgoliaHit {
tmsId: string;
type: string;
title: string;
titles?: string[];
titles: string[];
description: string;
releaseYear: number;
rating: string;
@@ -25,9 +24,9 @@ interface RTAlgoliaHit {
isEmsSearchable: boolean;
rtId: number;
vanity: string;
aka?: string[];
aka: string[];
posterImageUrl: string;
rottenTomatoes?: {
rottenTomatoes: {
audienceScore: number;
criticsIconUrl: string;
wantToSeeCount: number;
@@ -48,47 +47,6 @@ 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.
@@ -132,21 +90,47 @@ 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.replace(/\bthe\b ?/gi, ''),
params: `filters=${filters}&hitsPerPage=20`,
query: name,
params: 'filters=isEmsSearchable%20%3D%201&hitsPerPage=20',
},
],
});
const contentResults = data.results.find((r) => r.index === 'content_rt');
const movie = best(contentResults?.hits || [], name, year);
if (!movie?.rottenTomatoes) return null;
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;
}
return {
title: movie.title,
@@ -174,21 +158,33 @@ 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=${filters}&hitsPerPage=20`,
params: 'filters=isEmsSearchable%20%3D%201&hitsPerPage=20',
},
],
});
const contentResults = data.results.find((r) => r.index === 'content_rt');
const tvshow = best(contentResults?.hits || [], name, year);
if (!tvshow?.rottenTomatoes) return null;
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;
}
return {
title: tvshow.title,

View File

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

View File

@@ -70,35 +70,6 @@ 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
@@ -141,6 +112,21 @@ 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',
@@ -237,5 +223,19 @@ 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

@@ -295,14 +295,6 @@ class DiscordAgent
userMentions.push(`<@&${settings.options.webhookRoleId}>`);
}
logger.debug('Discord notification details', {
username: settings.options.botUsername
? settings.options.botUsername
: getSettings().main.applicationTitle,
avatar_url: settings.options.botAvatarUrl,
embeds: [this.buildEmbed(type, payload)],
content: userMentions.join(' '),
});
const response = await fetch(settings.options.webhookUrl, {
method: 'POST',
headers: {
@@ -318,12 +310,6 @@ class DiscordAgent
} as DiscordWebhookPayload),
});
if (!response.ok) {
logger.debug('Error sending Discord notification, response not ok', {
label: 'Notifications',
type: Notification[type],
subject: payload.subject,
response: response.statusText,
});
throw new Error(response.statusText, { cause: response });
}
@@ -342,7 +328,6 @@ class DiscordAgent
subject: payload.subject,
errorMessage: e.message,
response: errorData,
stack: e.stack,
});
return false;

View File

@@ -263,7 +263,6 @@ 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

@@ -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';

View File

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

View File

@@ -41,11 +41,13 @@ import {
ExclamationTriangleIcon,
EyeSlashIcon,
FilmIcon,
MinusCircleIcon,
PlayIcon,
StarIcon,
} from '@heroicons/react/24/outline';
import { ChevronDownIcon } from '@heroicons/react/24/solid';
import {
ChevronDownIcon,
MinusCircleIcon,
StarIcon,
} 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';

View File

@@ -1,10 +1,9 @@
import Modal from '@app/components/Common/Modal';
import PermissionEdit from '@app/components/PermissionEdit';
import type { User } from '@app/hooks/useUser';
import { Permission, useUser } from '@app/hooks/useUser';
import { 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';
@@ -80,10 +79,7 @@ const BulkEditModal = ({
const { permissions: allPermissionsEqual } = selectedUsers.reduce(
({ permissions: aPerms }, { permissions: bPerms }) => {
return {
permissions:
aPerms === bPerms || hasPermission(Permission.ADMIN, aPerms)
? aPerms
: NaN,
permissions: aPerms === bPerms ? aPerms : NaN,
};
},
{ permissions: selectedUsers[0].permissions }