From 8dc1d8196c67bee0e772941445c294f0ca367961 Mon Sep 17 00:00:00 2001 From: Gauthier Date: Mon, 31 Mar 2025 20:06:33 +0200 Subject: [PATCH] fix: correct "Remove from *arr" button (#1544) This PR fixes the "Delete from *arr" button in the request list. It checks from the API whether the *arr server corresponding to the request still exists before displaying the remove button, and fixes a cache removal issue that could cause problems when deleting recently added media. This PR also reverts #1476, which introduced problems during removal. fix #1494 --- server/api/externalapi.ts | 2 +- server/api/servarr/radarr.ts | 5 +- server/api/servarr/sonarr.ts | 6 +- server/interfaces/api/requestInterfaces.ts | 5 +- server/routes/media.ts | 20 ----- server/routes/request.ts | 34 +++++++- .../RequestList/RequestItem/index.tsx | 80 +++++++------------ src/components/RequestList/index.tsx | 13 +-- 8 files changed, 75 insertions(+), 90 deletions(-) diff --git a/server/api/externalapi.ts b/server/api/externalapi.ts index d17ebf99e..85612808b 100644 --- a/server/api/externalapi.ts +++ b/server/api/externalapi.ts @@ -289,7 +289,7 @@ class ExternalAPI { return data; } - protected removeCache(endpoint: string, options?: Record) { + protected removeCache(endpoint: string, options?: Record) { const cacheKey = this.serializeCacheKey(endpoint, { ...this.params, ...options, diff --git a/server/api/servarr/radarr.ts b/server/api/servarr/radarr.ts index b5e12d2ec..638af88a2 100644 --- a/server/api/servarr/radarr.ts +++ b/server/api/servarr/radarr.ts @@ -274,10 +274,13 @@ class RadarrAPI extends ServarrBase<{ movieId: number }> { if (tmdbId) { this.removeCache('/movie/lookup', { term: `tmdb:${tmdbId}`, + headers: this.defaultHeaders, }); } if (externalId) { - this.removeCache(`/movie/${externalId}`); + this.removeCache(`/movie/${externalId}`, { + headers: this.defaultHeaders, + }); } }; } diff --git a/server/api/servarr/sonarr.ts b/server/api/servarr/sonarr.ts index 0a9c27322..0cbd4a57d 100644 --- a/server/api/servarr/sonarr.ts +++ b/server/api/servarr/sonarr.ts @@ -368,14 +368,18 @@ class SonarrAPI extends ServarrBase<{ if (tvdbId) { this.removeCache('/series/lookup', { term: `tvdb:${tvdbId}`, + headers: this.defaultHeaders, }); } if (externalId) { - this.removeCache(`/series/${externalId}`); + this.removeCache(`/series/${externalId}`, { + headers: this.defaultHeaders, + }); } if (title) { this.removeCache('/series/lookup', { term: title, + headers: this.defaultHeaders, }); } }; diff --git a/server/interfaces/api/requestInterfaces.ts b/server/interfaces/api/requestInterfaces.ts index 88b1201de..4a41ae993 100644 --- a/server/interfaces/api/requestInterfaces.ts +++ b/server/interfaces/api/requestInterfaces.ts @@ -3,7 +3,10 @@ import type { MediaRequest } from '@server/entity/MediaRequest'; import type { NonFunctionProperties, PaginatedResponse } from './common'; export interface RequestResultsResponse extends PaginatedResponse { - results: NonFunctionProperties[]; + results: (NonFunctionProperties & { + profileName?: string; + canRemove?: boolean; + })[]; } export type MediaRequestBody = { diff --git a/server/routes/media.ts b/server/routes/media.ts index 3ad197c9d..60191e5de 100644 --- a/server/routes/media.ts +++ b/server/routes/media.ts @@ -237,19 +237,6 @@ mediaRoutes.delete( } if (isMovie) { - // check if the movie exists - try { - await (service as RadarrAPI).getMovie({ - id: parseInt( - is4k - ? (media.externalServiceSlug4k as string) - : (media.externalServiceSlug as string) - ), - }); - } catch { - return res.status(204).send(); - } - // remove the movie await (service as RadarrAPI).removeMovie( parseInt( is4k @@ -264,13 +251,6 @@ mediaRoutes.delete( if (!tvdbId) { throw new Error('TVDB ID not found'); } - // check if the series exists - try { - await (service as SonarrAPI).getSeriesByTvdbId(tvdbId); - } catch { - return res.status(204).send(); - } - // remove the series await (service as SonarrAPI).removeSerie(tvdbId); } diff --git a/server/routes/request.ts b/server/routes/request.ts index 89e5352fa..50d4a6f00 100644 --- a/server/routes/request.ts +++ b/server/routes/request.ts @@ -189,7 +189,7 @@ requestRoutes.get, RequestResultsResponse>( ); // add profile names to the media requests, with undefined if not found - const requestsWithProfileNames = requests.map((r) => { + let mappedRequests = requests.map((r) => { switch (r.type) { case MediaType.MOVIE: { const profileName = radarrServers @@ -212,6 +212,36 @@ requestRoutes.get, RequestResultsResponse>( } }); + // add canRemove prop if user has permission + if (req.user?.hasPermission(Permission.MANAGE_REQUESTS)) { + mappedRequests = mappedRequests.map((r) => { + switch (r.type) { + case MediaType.MOVIE: { + return { + ...r, + // check if the radarr server for this request is configured + canRemove: radarrServers.some( + (server) => + server.id === + (r.is4k ? r.media.serviceId4k : r.media.serviceId) + ), + }; + } + case MediaType.TV: { + return { + ...r, + // check if the sonarr server for this request is configured + canRemove: sonarrServers.some( + (server) => + server.id === + (r.is4k ? r.media.serviceId4k : r.media.serviceId) + ), + }; + } + } + }); + } + return res.status(200).json({ pageInfo: { pages: Math.ceil(requestCount / pageSize), @@ -219,7 +249,7 @@ requestRoutes.get, RequestResultsResponse>( results: requestCount, page: Math.ceil(skip / pageSize) + 1, }, - results: requestsWithProfileNames, + results: mappedRequests, }); } catch (e) { next({ status: 500, message: e.message }); diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx index 037590e5d..dd72ee7e8 100644 --- a/src/components/RequestList/RequestItem/index.tsx +++ b/src/components/RequestList/RequestItem/index.tsx @@ -17,10 +17,10 @@ import { TrashIcon, XMarkIcon, } from '@heroicons/react/24/solid'; -import { MediaRequestStatus, MediaType } from '@server/constants/media'; +import { MediaRequestStatus } from '@server/constants/media'; import type { MediaRequest } from '@server/entity/MediaRequest'; import type { NonFunctionProperties } from '@server/interfaces/api/common'; -import type { RadarrSettings, SonarrSettings } from '@server/lib/settings'; +import type { RequestResultsResponse } from '@server/interfaces/api/requestInterfaces'; import type { MovieDetails } from '@server/models/Movie'; import type { TvDetails } from '@server/models/Tv'; import Link from 'next/link'; @@ -292,18 +292,11 @@ const RequestItemError = ({ }; interface RequestItemProps { - request: NonFunctionProperties & { profileName?: string }; + request: RequestResultsResponse['results'][number]; revalidateList: () => void; - radarrData?: RadarrSettings[]; - sonarrData?: SonarrSettings[]; } -const RequestItem = ({ - request, - revalidateList, - radarrData, - sonarrData, -}: RequestItemProps) => { +const RequestItem = ({ request, revalidateList }: RequestItemProps) => { const settings = useSettings(); const { ref, inView } = useInView({ triggerOnce: true, @@ -398,23 +391,6 @@ const RequestItem = ({ iOSPlexUrl4k: requestData?.media?.iOSPlexUrl4k, }); - const serviceExists = () => { - if (title?.mediaInfo) { - if (title?.mediaInfo.mediaType === MediaType.MOVIE) { - return ( - radarrData?.find((radarr) => radarr.id === request.serverId) !== - undefined - ); - } else { - return ( - sonarrData?.find((sonarr) => sonarr.id === request.serverId) !== - undefined - ); - } - } - return false; - }; - if (!title && !error) { return (
deleteRequest()} - confirmText={intl.formatMessage(globalMessages.areyousure)} - className="w-full" - > - - {intl.formatMessage(messages.deleterequest)} - - )} - {hasPermission(Permission.MANAGE_REQUESTS) && - title?.mediaInfo?.serviceId && - serviceExists() && ( - deleteMediaFile()} - confirmText={intl.formatMessage(globalMessages.areyousure)} - className="w-full" - > - - - {intl.formatMessage(messages.removearr, { - arr: request.type === 'movie' ? 'Radarr' : 'Sonarr', - })} - - + <> + deleteRequest()} + confirmText={intl.formatMessage(globalMessages.areyousure)} + className="w-full" + > + + {intl.formatMessage(messages.deleterequest)} + + {request.canRemove && ( + deleteMediaFile()} + confirmText={intl.formatMessage(globalMessages.areyousure)} + className="w-full" + > + + + {intl.formatMessage(messages.removearr, { + arr: request.type === 'movie' ? 'Radarr' : 'Sonarr', + })} + + + )} + )} {requestData.status === MediaRequestStatus.PENDING && hasPermission(Permission.MANAGE_REQUESTS) && ( diff --git a/src/components/RequestList/index.tsx b/src/components/RequestList/index.tsx index 468c0853e..6cdf5b0bf 100644 --- a/src/components/RequestList/index.tsx +++ b/src/components/RequestList/index.tsx @@ -17,8 +17,6 @@ import { FunnelIcon, } from '@heroicons/react/24/solid'; import type { RequestResultsResponse } from '@server/interfaces/api/requestInterfaces'; -import { Permission } from '@server/lib/permissions'; -import type { RadarrSettings, SonarrSettings } from '@server/lib/settings'; import Link from 'next/link'; import { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; @@ -53,7 +51,7 @@ const RequestList = () => { const { user } = useUser({ id: Number(router.query.userId), }); - const { user: currentUser, hasPermission } = useUser(); + const { user: currentUser } = useUser(); const [currentFilter, setCurrentFilter] = useState(Filter.PENDING); const [currentSort, setCurrentSort] = useState('added'); const [currentSortDirection, setCurrentSortDirection] = @@ -64,13 +62,6 @@ const RequestList = () => { const pageIndex = page - 1; const updateQueryParams = useUpdateQueryParams({ page: page.toString() }); - const { data: radarrData } = useSWR( - hasPermission(Permission.ADMIN) ? '/api/v1/settings/radarr' : null - ); - const { data: sonarrData } = useSWR( - hasPermission(Permission.ADMIN) ? '/api/v1/settings/sonarr' : null - ); - const { data, error, @@ -254,8 +245,6 @@ const RequestList = () => { revalidate()} - radarrData={radarrData} - sonarrData={sonarrData} />
);