mirror of
https://github.com/seerr-team/seerr.git
synced 2026-10-12 02:22:17 +00:00
fix(api): stop library reads from resetting enabled flags (#3321)
This commit is contained in:
+101
-27
@@ -2321,21 +2321,6 @@ paths:
|
||||
description: Returns a list of Jellyfin libraries in a JSON array.
|
||||
tags:
|
||||
- settings
|
||||
parameters:
|
||||
- in: query
|
||||
name: sync
|
||||
description: Syncs the current libraries with the current Jellyfin server
|
||||
schema:
|
||||
type: string
|
||||
nullable: true
|
||||
- in: query
|
||||
name: enable
|
||||
explode: false
|
||||
allowReserved: true
|
||||
description: Comma separated list of libraries to enable. Any libraries not passed will be disabled!
|
||||
schema:
|
||||
type: string
|
||||
nullable: true
|
||||
responses:
|
||||
'200':
|
||||
description: 'Jellyfin libraries returned'
|
||||
@@ -2345,6 +2330,60 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/JellyfinLibrary'
|
||||
/settings/jellyfin/library/{libraryId}:
|
||||
put:
|
||||
summary: Update a single Jellyfin library
|
||||
description: Updates a single Jellyfin library with the provided values.
|
||||
tags:
|
||||
- settings
|
||||
parameters:
|
||||
- in: path
|
||||
name: libraryId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- enabled
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
example: true
|
||||
responses:
|
||||
'200':
|
||||
description: 'Jellyfin library returned'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/JellyfinLibrary'
|
||||
'400':
|
||||
description: 'Invalid request body'
|
||||
'404':
|
||||
description: 'Library does not exist'
|
||||
/settings/jellyfin/library/sync:
|
||||
post:
|
||||
summary: Sync Jellyfin libraries
|
||||
description: Syncs the current libraries with the current Jellyfin server. Enabled flags are preserved.
|
||||
tags:
|
||||
- settings
|
||||
responses:
|
||||
'200':
|
||||
description: 'Jellyfin libraries returned'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/JellyfinLibrary'
|
||||
'404':
|
||||
description: 'No libraries found'
|
||||
'501':
|
||||
description: 'Automatic library grouping is not supported'
|
||||
/settings/jellyfin/users:
|
||||
get:
|
||||
summary: Get Jellyfin Users
|
||||
@@ -2474,23 +2513,58 @@ paths:
|
||||
get:
|
||||
summary: Get Plex libraries
|
||||
description: Returns a list of Plex libraries in a JSON array.
|
||||
tags:
|
||||
- settings
|
||||
responses:
|
||||
'200':
|
||||
description: 'Plex libraries returned'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PlexLibrary'
|
||||
/settings/plex/library/{libraryId}:
|
||||
put:
|
||||
summary: Update a single Plex library
|
||||
description: Updates a single Plex library with the provided values.
|
||||
tags:
|
||||
- settings
|
||||
parameters:
|
||||
- in: query
|
||||
name: sync
|
||||
description: Syncs the current libraries with the current Plex server
|
||||
- in: path
|
||||
name: libraryId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
nullable: true
|
||||
- in: query
|
||||
name: enable
|
||||
explode: false
|
||||
allowReserved: true
|
||||
description: Comma separated list of libraries to enable. Any libraries not passed will be disabled!
|
||||
schema:
|
||||
type: string
|
||||
nullable: true
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- enabled
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
example: true
|
||||
responses:
|
||||
'200':
|
||||
description: 'Plex library returned'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PlexLibrary'
|
||||
'400':
|
||||
description: 'Invalid request body'
|
||||
'404':
|
||||
description: 'Library does not exist'
|
||||
/settings/plex/library/sync:
|
||||
post:
|
||||
summary: Sync Plex libraries
|
||||
description: Syncs the current libraries with the current Plex server. Enabled flags are preserved.
|
||||
tags:
|
||||
- settings
|
||||
responses:
|
||||
'200':
|
||||
description: 'Plex libraries returned'
|
||||
|
||||
+112
-68
@@ -39,6 +39,7 @@ import { rescheduleJob } from 'node-schedule';
|
||||
import path from 'path';
|
||||
import semver from 'semver';
|
||||
import { URL } from 'url';
|
||||
import { z } from 'zod';
|
||||
import metadataRoutes from './metadata';
|
||||
import notificationRoutes from './notifications';
|
||||
import radarrRoutes from './radarr';
|
||||
@@ -52,6 +53,10 @@ settingsRoutes.use('/sonarr', sonarrRoutes);
|
||||
settingsRoutes.use('/discover', discoverSettingRoutes);
|
||||
settingsRoutes.use('/metadatas', metadataRoutes);
|
||||
|
||||
const libraryUpdateSchema = z.object({
|
||||
enabled: z.boolean(),
|
||||
});
|
||||
|
||||
const filteredMainSettings = (
|
||||
user: User,
|
||||
main: MainSettings
|
||||
@@ -229,28 +234,47 @@ settingsRoutes.get('/plex/devices/servers', async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
settingsRoutes.get('/plex/library', async (req, res) => {
|
||||
settingsRoutes.get('/plex/library', (_req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
if (req.query.sync) {
|
||||
const userRepository = getRepository(User);
|
||||
const admin = await userRepository.findOneOrFail({
|
||||
select: { id: true, plexToken: true },
|
||||
where: { id: 1 },
|
||||
});
|
||||
const plexapi = new PlexAPI({ plexToken: admin.plexToken });
|
||||
return res.status(200).json(settings.plex.libraries);
|
||||
});
|
||||
|
||||
await plexapi.syncLibraries();
|
||||
settingsRoutes.put('/plex/library/:libraryId', async (req, res, next) => {
|
||||
const settings = getSettings();
|
||||
|
||||
const bodyResult = libraryUpdateSchema.safeParse(req.body);
|
||||
|
||||
if (!bodyResult.success) {
|
||||
return next({ status: 400, message: 'Invalid request body.' });
|
||||
}
|
||||
|
||||
const enabledLibraries = req.query.enable
|
||||
? (req.query.enable as string).split(',')
|
||||
: [];
|
||||
settings.plex.libraries = settings.plex.libraries.map((library) => ({
|
||||
...library,
|
||||
enabled: enabledLibraries.includes(library.id),
|
||||
}));
|
||||
const library = settings.plex.libraries.find(
|
||||
(l) => l.id === req.params.libraryId
|
||||
);
|
||||
|
||||
if (!library) {
|
||||
return next({ status: 404, message: 'Library does not exist.' });
|
||||
}
|
||||
|
||||
library.enabled = bodyResult.data.enabled;
|
||||
await settings.save();
|
||||
|
||||
return res.status(200).json(library);
|
||||
});
|
||||
|
||||
settingsRoutes.post('/plex/library/sync', async (_req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
const userRepository = getRepository(User);
|
||||
const admin = await userRepository.findOneOrFail({
|
||||
select: { id: true, plexToken: true },
|
||||
where: { id: 1 },
|
||||
});
|
||||
const plexapi = new PlexAPI({ plexToken: admin.plexToken });
|
||||
|
||||
await plexapi.syncLibraries();
|
||||
|
||||
return res.status(200).json(settings.plex.libraries);
|
||||
});
|
||||
|
||||
@@ -330,66 +354,86 @@ settingsRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
return res.status(200).json(settings.jellyfin);
|
||||
});
|
||||
|
||||
settingsRoutes.get('/jellyfin/library', async (req, res, next) => {
|
||||
settingsRoutes.get('/jellyfin/library', (_req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
if (req.query.sync) {
|
||||
const userRepository = getRepository(User);
|
||||
const admin = await userRepository.findOneOrFail({
|
||||
select: ['id', 'jellyfinDeviceId', 'jellyfinUserId'],
|
||||
where: { id: 1 },
|
||||
order: { id: 'ASC' },
|
||||
});
|
||||
const jellyfinClient = new JellyfinAPI(
|
||||
getHostname(),
|
||||
settings.jellyfin.apiKey,
|
||||
admin.jellyfinDeviceId ?? ''
|
||||
);
|
||||
return res.status(200).json(settings.jellyfin.libraries);
|
||||
});
|
||||
|
||||
jellyfinClient.setUserId(admin.jellyfinUserId ?? '');
|
||||
settingsRoutes.put('/jellyfin/library/:libraryId', async (req, res, next) => {
|
||||
const settings = getSettings();
|
||||
|
||||
const libraries = await jellyfinClient.getLibraries();
|
||||
const bodyResult = libraryUpdateSchema.safeParse(req.body);
|
||||
|
||||
if (libraries.length === 0) {
|
||||
// Check if no libraries are found due to the fallback to user views
|
||||
// This only affects LDAP users
|
||||
const account = await jellyfinClient.getUser();
|
||||
|
||||
// Automatic Library grouping is not supported when user views are used to get library
|
||||
if (account.Configuration.GroupedFolders?.length > 0) {
|
||||
return next({
|
||||
status: 501,
|
||||
message: ApiErrorCode.SyncErrorGroupedFolders,
|
||||
});
|
||||
}
|
||||
|
||||
return next({ status: 404, message: ApiErrorCode.SyncErrorNoLibraries });
|
||||
}
|
||||
|
||||
const newLibraries: Library[] = libraries.map((library) => {
|
||||
const existing = settings.jellyfin.libraries.find(
|
||||
(l) => l.id === library.key && l.name === library.title
|
||||
);
|
||||
|
||||
return {
|
||||
id: library.key,
|
||||
name: library.title,
|
||||
enabled: existing?.enabled ?? false,
|
||||
type: library.type,
|
||||
};
|
||||
});
|
||||
|
||||
settings.jellyfin.libraries = newLibraries;
|
||||
if (!bodyResult.success) {
|
||||
return next({ status: 400, message: 'Invalid request body.' });
|
||||
}
|
||||
|
||||
const enabledLibraries = req.query.enable
|
||||
? (req.query.enable as string).split(',')
|
||||
: [];
|
||||
settings.jellyfin.libraries = settings.jellyfin.libraries.map((library) => ({
|
||||
...library,
|
||||
enabled: enabledLibraries.includes(library.id),
|
||||
}));
|
||||
const library = settings.jellyfin.libraries.find(
|
||||
(l) => l.id === req.params.libraryId
|
||||
);
|
||||
|
||||
if (!library) {
|
||||
return next({ status: 404, message: 'Library does not exist.' });
|
||||
}
|
||||
|
||||
library.enabled = bodyResult.data.enabled;
|
||||
await settings.save();
|
||||
|
||||
return res.status(200).json(library);
|
||||
});
|
||||
|
||||
settingsRoutes.post('/jellyfin/library/sync', async (_req, res, next) => {
|
||||
const settings = getSettings();
|
||||
|
||||
const userRepository = getRepository(User);
|
||||
const admin = await userRepository.findOneOrFail({
|
||||
select: ['id', 'jellyfinDeviceId', 'jellyfinUserId'],
|
||||
where: { id: 1 },
|
||||
order: { id: 'ASC' },
|
||||
});
|
||||
const jellyfinClient = new JellyfinAPI(
|
||||
getHostname(),
|
||||
settings.jellyfin.apiKey,
|
||||
admin.jellyfinDeviceId ?? ''
|
||||
);
|
||||
|
||||
jellyfinClient.setUserId(admin.jellyfinUserId ?? '');
|
||||
|
||||
const libraries = await jellyfinClient.getLibraries();
|
||||
|
||||
if (libraries.length === 0) {
|
||||
// Check if no libraries are found due to the fallback to user views
|
||||
// This only affects LDAP users
|
||||
const account = await jellyfinClient.getUser();
|
||||
|
||||
// Automatic Library grouping is not supported when user views are used to get library
|
||||
if (account.Configuration.GroupedFolders?.length > 0) {
|
||||
return next({
|
||||
status: 501,
|
||||
message: ApiErrorCode.SyncErrorGroupedFolders,
|
||||
});
|
||||
}
|
||||
|
||||
return next({ status: 404, message: ApiErrorCode.SyncErrorNoLibraries });
|
||||
}
|
||||
|
||||
const newLibraries: Library[] = libraries.map((library) => {
|
||||
const existing = settings.jellyfin.libraries.find(
|
||||
(l) => l.id === library.key && l.name === library.title
|
||||
);
|
||||
|
||||
return {
|
||||
id: library.key,
|
||||
name: library.title,
|
||||
enabled: existing?.enabled ?? false,
|
||||
type: library.type,
|
||||
};
|
||||
});
|
||||
|
||||
settings.jellyfin.libraries = newLibraries;
|
||||
await settings.save();
|
||||
|
||||
return res.status(200).json(settings.jellyfin.libraries);
|
||||
});
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ const messages = defineMessages('components.Settings', {
|
||||
'Custom authentication with Automatic Library Grouping not supported',
|
||||
jellyfinSyncFailedGenericError:
|
||||
'Something went wrong while syncing libraries',
|
||||
toggleLibraryFailure: 'Failed to update library.',
|
||||
invalidurlerror: 'Unable to connect to {mediaServerName} server.',
|
||||
syncing: 'Syncing',
|
||||
syncJellyfin: 'Sync Libraries',
|
||||
@@ -161,18 +162,8 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
const syncLibraries = async () => {
|
||||
setIsSyncing(true);
|
||||
|
||||
const params: { sync: boolean; enable?: string } = {
|
||||
sync: true,
|
||||
};
|
||||
|
||||
if (activeLibraries.length > 0) {
|
||||
params.enable = activeLibraries.join(',');
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.get('/api/v1/settings/jellyfin/library', {
|
||||
params,
|
||||
});
|
||||
await axios.post('/api/v1/settings/jellyfin/library/sync');
|
||||
setIsSyncing(false);
|
||||
revalidate();
|
||||
} catch (e) {
|
||||
@@ -221,30 +212,23 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
|
||||
const toggleLibrary = async (libraryId: string) => {
|
||||
setIsSyncing(true);
|
||||
if (activeLibraries.includes(libraryId)) {
|
||||
const params: { enable?: string } = {};
|
||||
try {
|
||||
await axios.put(`/api/v1/settings/jellyfin/library/${libraryId}`, {
|
||||
enabled: !activeLibraries.includes(libraryId),
|
||||
});
|
||||
|
||||
if (activeLibraries.length > 1) {
|
||||
params.enable = activeLibraries
|
||||
.filter((id) => id !== libraryId)
|
||||
.join(',');
|
||||
if (onComplete) {
|
||||
onComplete();
|
||||
}
|
||||
|
||||
await axios.get('/api/v1/settings/jellyfin/library', {
|
||||
params,
|
||||
});
|
||||
} else {
|
||||
await axios.get('/api/v1/settings/jellyfin/library', {
|
||||
params: {
|
||||
enable: [...activeLibraries, libraryId].join(','),
|
||||
},
|
||||
} catch {
|
||||
addToast(intl.formatMessage(messages.toggleLibraryFailure), {
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
});
|
||||
} finally {
|
||||
setIsSyncing(false);
|
||||
revalidate();
|
||||
}
|
||||
if (onComplete) {
|
||||
onComplete();
|
||||
}
|
||||
setIsSyncing(false);
|
||||
revalidate();
|
||||
};
|
||||
|
||||
if (!data && !error) {
|
||||
|
||||
@@ -41,6 +41,8 @@ const messages = defineMessages('components.Settings', {
|
||||
toastPlexRefresh: 'Retrieving server list from Plex…',
|
||||
toastPlexRefreshSuccess: 'Plex server list retrieved successfully!',
|
||||
toastPlexRefreshFailure: 'Failed to retrieve Plex server list.',
|
||||
toastPlexSyncFailure: 'Failed to sync Plex libraries.',
|
||||
toggleLibraryFailure: 'Failed to update library.',
|
||||
toastPlexConnecting: 'Attempting to connect to Plex…',
|
||||
toastPlexConnectingSuccess: 'Plex connection established successfully!',
|
||||
toastPlexConnectingFailure: 'Failed to connect to Plex.',
|
||||
@@ -240,19 +242,17 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
|
||||
const syncLibraries = async () => {
|
||||
setIsSyncing(true);
|
||||
|
||||
const params: { sync: boolean; enable?: string } = {
|
||||
sync: true,
|
||||
};
|
||||
|
||||
if (activeLibraries.length > 0) {
|
||||
params.enable = activeLibraries.join(',');
|
||||
try {
|
||||
await axios.post('/api/v1/settings/plex/library/sync');
|
||||
} catch {
|
||||
addToast(intl.formatMessage(messages.toastPlexSyncFailure), {
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
});
|
||||
} finally {
|
||||
setIsSyncing(false);
|
||||
revalidate();
|
||||
}
|
||||
|
||||
await axios.get('/api/v1/settings/plex/library', {
|
||||
params,
|
||||
});
|
||||
setIsSyncing(false);
|
||||
revalidate();
|
||||
};
|
||||
|
||||
const refreshPresetServers = async () => {
|
||||
@@ -311,31 +311,23 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
|
||||
|
||||
const toggleLibrary = async (libraryId: string) => {
|
||||
setIsSyncing(true);
|
||||
if (activeLibraries.includes(libraryId)) {
|
||||
const params: { enable?: string } = {};
|
||||
try {
|
||||
await axios.put(`/api/v1/settings/plex/library/${libraryId}`, {
|
||||
enabled: !activeLibraries.includes(libraryId),
|
||||
});
|
||||
|
||||
if (activeLibraries.length > 1) {
|
||||
params.enable = activeLibraries
|
||||
.filter((id) => id !== libraryId)
|
||||
.join(',');
|
||||
if (onComplete) {
|
||||
onComplete();
|
||||
}
|
||||
|
||||
await axios.get('/api/v1/settings/plex/library', {
|
||||
params,
|
||||
});
|
||||
} else {
|
||||
await axios.get('/api/v1/settings/plex/library', {
|
||||
params: {
|
||||
enable: [...activeLibraries, libraryId].join(','),
|
||||
},
|
||||
} catch {
|
||||
addToast(intl.formatMessage(messages.toggleLibraryFailure), {
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
});
|
||||
} finally {
|
||||
setIsSyncing(false);
|
||||
revalidate();
|
||||
}
|
||||
|
||||
if (onComplete) {
|
||||
onComplete();
|
||||
}
|
||||
setIsSyncing(false);
|
||||
revalidate();
|
||||
};
|
||||
|
||||
if ((!data || !dataTautulli) && !error) {
|
||||
|
||||
@@ -1302,8 +1302,10 @@
|
||||
"components.Settings.toastPlexRefresh": "Retrieving server list from Plex…",
|
||||
"components.Settings.toastPlexRefreshFailure": "Failed to retrieve Plex server list.",
|
||||
"components.Settings.toastPlexRefreshSuccess": "Plex server list retrieved successfully!",
|
||||
"components.Settings.toastPlexSyncFailure": "Failed to sync Plex libraries.",
|
||||
"components.Settings.toastTautulliSettingsFailure": "Something went wrong while saving Tautulli settings.",
|
||||
"components.Settings.toastTautulliSettingsSuccess": "Tautulli settings saved successfully!",
|
||||
"components.Settings.toggleLibraryFailure": "Failed to update library.",
|
||||
"components.Settings.tvdbProviderDoesnotWork": "TVDB provider does not work, please select another metadata provider",
|
||||
"components.Settings.urlBase": "URL Base",
|
||||
"components.Settings.validationApiKey": "You must provide an API key",
|
||||
|
||||
Reference in New Issue
Block a user