From 0ba4ed735a7600e27f34272afd1840957aa0a976 Mon Sep 17 00:00:00 2001 From: 0fflineuser <0fflineuser@cock.li> Date: Sat, 8 Nov 2025 17:02:10 +0100 Subject: [PATCH 1/8] feat: new permission named DELETE_MEDIA so that we can allow users to remove medias from arrs --- .github/renovate/docker.json5 | 5 +--- .github/renovate/pnpm.json5 | 1 - .gitignore | 3 +++ .vscode/settings.json | 4 +--- seerr-api.yml | 4 ++-- server/lib/permissions.ts | 1 + server/routes/media.ts | 6 +++-- src/components/ManageSlideOver/index.tsx | 29 +++++++++++++++++------- src/components/PermissionEdit/index.tsx | 9 ++++++++ 9 files changed, 42 insertions(+), 20 deletions(-) diff --git a/.github/renovate/docker.json5 b/.github/renovate/docker.json5 index 7c97fefc4a..7e02faa641 100644 --- a/.github/renovate/docker.json5 +++ b/.github/renovate/docker.json5 @@ -1,10 +1,7 @@ { $schema: 'https://docs.renovatebot.com/renovate-schema.json', - extends: [ - 'docker:enableMajor', - 'docker:pinDigests' - ], + extends: ['docker:enableMajor', 'docker:pinDigests'], packageRules: [ { diff --git a/.github/renovate/pnpm.json5 b/.github/renovate/pnpm.json5 index 2ec1f41eaf..fb14c65364 100644 --- a/.github/renovate/pnpm.json5 +++ b/.github/renovate/pnpm.json5 @@ -7,5 +7,4 @@ lockFileMaintenance: { enabled: true, }, - } diff --git a/.gitignore b/.gitignore index c417acb091..e071137a0f 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,6 @@ tsconfig.tsbuildinfo # Config Cache Directory config/cache + +# Allow server directory for Telescope find_files +!/server diff --git a/.vscode/settings.json b/.vscode/settings.json index dbb4a2ccf8..92ad828392 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,8 +20,6 @@ "files.associations": { "globals.css": "tailwindcss" }, - "i18n-ally.localesPaths": [ - "src/i18n/locale" - ], + "i18n-ally.localesPaths": ["src/i18n/locale"], "yaml.format.singleQuote": true } diff --git a/seerr-api.yml b/seerr-api.yml index bf9d882712..3f613da5e1 100644 --- a/seerr-api.yml +++ b/seerr-api.yml @@ -6860,7 +6860,7 @@ paths: /media/{mediaId}/file: delete: summary: Delete media file - description: Removes a media file from radarr/sonarr. The `ADMIN` permission is required to perform this action. + description: Removes a media file from radarr/sonarr. The `ADMIN` or `DELETE_MEDIA` permission is required to perform this action. tags: - media parameters: @@ -6929,7 +6929,7 @@ paths: description: | Returns play count, play duration, and users who have watched the media. - Requires the `ADMIN` permission. + Requires the `ADMIN` or `DELETE_MEDIA` permission. tags: - media parameters: diff --git a/server/lib/permissions.ts b/server/lib/permissions.ts index bc477169c0..e8ea85256e 100644 --- a/server/lib/permissions.ts +++ b/server/lib/permissions.ts @@ -28,6 +28,7 @@ export enum Permission { RECENT_VIEW = 67108864, WATCHLIST_VIEW = 134217728, MANAGE_BLACKLIST = 268435456, + DELETE_MEDIA = 536870912, VIEW_BLACKLIST = 1073741824, } diff --git a/server/routes/media.ts b/server/routes/media.ts index 8f52efae86..129aa3a735 100644 --- a/server/routes/media.ts +++ b/server/routes/media.ts @@ -189,7 +189,9 @@ mediaRoutes.delete( mediaRoutes.delete( '/:id/file', - isAuthenticated(Permission.MANAGE_REQUESTS), + isAuthenticated([Permission.ADMIN, Permission.DELETE_MEDIA], { + type: 'or', + }), async (req, res, next) => { try { const settings = getSettings(); @@ -282,7 +284,7 @@ mediaRoutes.delete( mediaRoutes.get<{ id: string }, MediaWatchDataResponse>( '/:id/watch_data', - isAuthenticated(Permission.ADMIN), + isAuthenticated([Permission.ADMIN, Permission.DELETE_MEDIA], { type: 'or' }), async (req, res, next) => { const settings = getSettings().tautulli; diff --git a/src/components/ManageSlideOver/index.tsx b/src/components/ManageSlideOver/index.tsx index 7a2c1047e3..df24a1534e 100644 --- a/src/components/ManageSlideOver/index.tsx +++ b/src/components/ManageSlideOver/index.tsx @@ -99,15 +99,19 @@ const ManageSlideOver = ({ const { data: watchData } = useSWR( settings.currentSettings.mediaServerType === MediaServerType.PLEX && data.mediaInfo && - hasPermission(Permission.ADMIN) + hasPermission([Permission.ADMIN, Permission.DELETE_MEDIA], { type: 'or' }) ? `/api/v1/media/${data.mediaInfo.id}/watch_data` : null ); const { data: radarrData } = useSWR( - hasPermission(Permission.ADMIN) ? '/api/v1/settings/radarr' : null + hasPermission([Permission.ADMIN, Permission.DELETE_MEDIA], { type: 'or' }) + ? '/api/v1/settings/radarr' + : null ); const { data: sonarrData } = useSWR( - hasPermission(Permission.ADMIN) ? '/api/v1/settings/sonarr' : null + hasPermission([Permission.ADMIN, Permission.DELETE_MEDIA], { type: 'or' }) + ? '/api/v1/settings/sonarr' + : null ); const deleteMedia = async () => { @@ -130,6 +134,8 @@ const ManageSlideOver = ({ }; const isDefaultService = () => { + // TODO: need to fix this + return true; if (data.mediaInfo) { if (data.mediaInfo.mediaType === MediaType.MOVIE) { return ( @@ -312,7 +318,9 @@ const ManageSlideOver = ({ )} - {hasPermission(Permission.ADMIN) && + {hasPermission([Permission.ADMIN, Permission.DELETE_MEDIA], { + type: 'or', + }) && (data.mediaInfo?.serviceUrl || data.mediaInfo?.tautulliUrl || watchData?.data) && ( @@ -435,8 +443,9 @@ const ManageSlideOver = ({ )} - - {hasPermission(Permission.ADMIN) && + {hasPermission([Permission.ADMIN, Permission.DELETE_MEDIA], { + type: 'or', + }) && data?.mediaInfo?.serviceUrl && isDefaultService() && (
@@ -472,7 +481,9 @@ const ManageSlideOver = ({
)} - {hasPermission(Permission.ADMIN) && + {hasPermission([Permission.ADMIN, Permission.DELETE_MEDIA], { + type: 'or', + }) && (data.mediaInfo?.serviceUrl4k || data.mediaInfo?.tautulliUrl4k || watchData?.data4k) && ( @@ -633,7 +644,9 @@ const ManageSlideOver = ({ )} - {hasPermission(Permission.ADMIN) && + {hasPermission([Permission.ADMIN, Permission.DELETE_MEDIA], { + type: 'or', + }) && data?.mediaInfo && data.mediaInfo.status !== MediaStatus.BLACKLISTED && (
diff --git a/src/components/PermissionEdit/index.tsx b/src/components/PermissionEdit/index.tsx index 5a861de8ae..68f4b75c5e 100644 --- a/src/components/PermissionEdit/index.tsx +++ b/src/components/PermissionEdit/index.tsx @@ -17,6 +17,9 @@ export const messages = defineMessages('components.PermissionEdit', { managerequests: 'Manage Requests', managerequestsDescription: 'Grant permission to manage media requests. All requests made by a user with this permission will be automatically approved.', + deletemedia: 'Delete Media', + deletemediaDescription: + 'Grant permission to delete media from Sonarr and Radarr.', request: 'Request', requestDescription: 'Grant permission to submit requests for non-4K media.', requestMovies: 'Request Movies', @@ -164,6 +167,12 @@ export const PermissionEdit = ({ }, ], }, + { + id: 'deletemedia', + name: intl.formatMessage(messages.deletemedia), + description: intl.formatMessage(messages.deletemediaDescription), + permission: Permission.DELETE_MEDIA, + }, { id: 'request', name: intl.formatMessage(messages.request), From 154f78c388c1f0c501692e237577924c26c33472 Mon Sep 17 00:00:00 2001 From: 0fflineuser <0fflineuser@cock.li> Date: Sat, 8 Nov 2025 17:14:39 +0100 Subject: [PATCH 2/8] fix: ran `pnpm i18n:extract` --- src/i18n/locale/en.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 34963834d2..35cb3eb896 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -415,6 +415,8 @@ "components.PermissionEdit.blacklistedItemsDescription": "Grant permission to blacklist media.", "components.PermissionEdit.createissues": "Report Issues", "components.PermissionEdit.createissuesDescription": "Grant permission to report media issues.", + "components.PermissionEdit.deletemedia": "Delete Media", + "components.PermissionEdit.deletemediaDescription": "Grant permission to delete media from Sonarr and Radarr.", "components.PermissionEdit.manageblacklist": "Manage Blacklist", "components.PermissionEdit.manageblacklistDescription": "Grant permission to manage blacklisted media.", "components.PermissionEdit.manageissues": "Manage Issues", From c922f8eb838cc4e5077087e169eac7bf97523ea9 Mon Sep 17 00:00:00 2001 From: 0fflineuser <0fflineuser@cock.li> Date: Sat, 8 Nov 2025 17:20:37 +0100 Subject: [PATCH 3/8] fix: ran pnpm build --- src/components/ManageSlideOver/index.tsx | 36 ++++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/components/ManageSlideOver/index.tsx b/src/components/ManageSlideOver/index.tsx index df24a1534e..1d825d54a4 100644 --- a/src/components/ManageSlideOver/index.tsx +++ b/src/components/ManageSlideOver/index.tsx @@ -136,24 +136,24 @@ const ManageSlideOver = ({ const isDefaultService = () => { // TODO: need to fix this return true; - if (data.mediaInfo) { - if (data.mediaInfo.mediaType === MediaType.MOVIE) { - return ( - radarrData?.find( - (radarr) => - radarr.isDefault && radarr.id === data.mediaInfo?.serviceId - ) !== undefined - ); - } else { - return ( - sonarrData?.find( - (sonarr) => - sonarr.isDefault && sonarr.id === data.mediaInfo?.serviceId - ) !== undefined - ); - } - } - return false; + // if (data.mediaInfo) { + // if (data.mediaInfo.mediaType === MediaType.MOVIE) { + // return ( + // radarrData?.find( + // (radarr) => + // radarr.isDefault && radarr.id === data.mediaInfo?.serviceId + // ) !== undefined + // ); + // } else { + // return ( + // sonarrData?.find( + // (sonarr) => + // sonarr.isDefault && sonarr.id === data.mediaInfo?.serviceId + // ) !== undefined + // ); + // } + // } + // return false; }; const isDefault4kService = () => { From ab63067900217d3bb51f0465cda7d99128869b16 Mon Sep 17 00:00:00 2001 From: 0fflineuser <0fflineuser@cock.li> Date: Tue, 11 Nov 2025 17:02:17 +0100 Subject: [PATCH 4/8] fix: reverts and retrict what the DELETE_MEDIA permission sees in the UI --- .github/renovate/docker.json5 | 5 +- .github/renovate/pnpm.json5 | 1 + src/components/ManageSlideOver/index.tsx | 42 ++++++++--------- src/components/MovieDetails/index.tsx | 5 +- src/components/TvDetails/index.tsx | 58 +++++++++++++----------- 5 files changed, 60 insertions(+), 51 deletions(-) diff --git a/.github/renovate/docker.json5 b/.github/renovate/docker.json5 index 7e02faa641..7c97fefc4a 100644 --- a/.github/renovate/docker.json5 +++ b/.github/renovate/docker.json5 @@ -1,7 +1,10 @@ { $schema: 'https://docs.renovatebot.com/renovate-schema.json', - extends: ['docker:enableMajor', 'docker:pinDigests'], + extends: [ + 'docker:enableMajor', + 'docker:pinDigests' + ], packageRules: [ { diff --git a/.github/renovate/pnpm.json5 b/.github/renovate/pnpm.json5 index fb14c65364..2ec1f41eaf 100644 --- a/.github/renovate/pnpm.json5 +++ b/.github/renovate/pnpm.json5 @@ -7,4 +7,5 @@ lockFileMaintenance: { enabled: true, }, + } diff --git a/src/components/ManageSlideOver/index.tsx b/src/components/ManageSlideOver/index.tsx index 1d825d54a4..afe74d272d 100644 --- a/src/components/ManageSlideOver/index.tsx +++ b/src/components/ManageSlideOver/index.tsx @@ -134,26 +134,24 @@ const ManageSlideOver = ({ }; const isDefaultService = () => { - // TODO: need to fix this - return true; - // if (data.mediaInfo) { - // if (data.mediaInfo.mediaType === MediaType.MOVIE) { - // return ( - // radarrData?.find( - // (radarr) => - // radarr.isDefault && radarr.id === data.mediaInfo?.serviceId - // ) !== undefined - // ); - // } else { - // return ( - // sonarrData?.find( - // (sonarr) => - // sonarr.isDefault && sonarr.id === data.mediaInfo?.serviceId - // ) !== undefined - // ); - // } - // } - // return false; + if (data.mediaInfo) { + if (data.mediaInfo.mediaType === MediaType.MOVIE) { + return ( + radarrData?.find( + (radarr) => + radarr.isDefault && radarr.id === data.mediaInfo?.serviceId + ) !== undefined + ); + } else { + return ( + sonarrData?.find( + (sonarr) => + sonarr.isDefault && sonarr.id === data.mediaInfo?.serviceId + ) !== undefined + ); + } + } + return false; }; const isDefault4kService = () => { @@ -481,7 +479,7 @@ const ManageSlideOver = ({
)} - {hasPermission([Permission.ADMIN, Permission.DELETE_MEDIA], { + {hasPermission([Permission.ADMIN], { type: 'or', }) && (data.mediaInfo?.serviceUrl4k || @@ -644,7 +642,7 @@ const ManageSlideOver = ({ )} - {hasPermission([Permission.ADMIN, Permission.DELETE_MEDIA], { + {hasPermission([Permission.ADMIN], { type: 'or', }) && data?.mediaInfo && diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index eaa4d4b078..aa5a129463 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -648,7 +648,10 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => { )} - {hasPermission(Permission.MANAGE_REQUESTS) && + {hasPermission( + [Permission.MANAGE_REQUESTS, Permission.DELETE_MEDIA], + { type: 'or' } + ) && data.mediaInfo && (data.mediaInfo.jellyfinMediaId || data.mediaInfo.jellyfinMediaId4k || diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index 03b1e9ba9a..a420e44d27 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -692,33 +692,37 @@ const TvDetails = ({ tv }: TvDetailsProps) => { )} - {hasPermission(Permission.MANAGE_REQUESTS) && data.mediaInfo && ( - - - - )} + {hasPermission( + [Permission.MANAGE_REQUESTS, Permission.DELETE_MEDIA], + { type: 'or' } + ) && + data.mediaInfo && ( + + + + )}
From a8d61ed5c2e5a1aa641fe55f6587fdf4bf4ca4c1 Mon Sep 17 00:00:00 2001 From: 0fflineuser <0fflineuser@cock.li> Date: Tue, 11 Nov 2025 17:46:44 +0100 Subject: [PATCH 5/8] fix: fixed 403 when querying settings for arrs data resulting in them being undefined --- server/routes/index.ts | 18 ++++++++++++++++++ server/routes/settings/radarr.ts | 6 ------ server/routes/settings/sonarr.ts | 6 ------ 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/server/routes/index.ts b/server/routes/index.ts index 6155f3508f..dfa83d1689 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -146,6 +146,24 @@ router.get( } } ); + +router.get( + '/settings/radarr', + isAuthenticated([Permission.ADMIN, Permission.DELETE_MEDIA], { type: 'or' }), + (_req, res) => { + const settings = getSettings(); + return res.status(200).json(settings.radarr); + } +); + +router.get( + '/settings/sonarr', + isAuthenticated([Permission.ADMIN, Permission.DELETE_MEDIA], { type: 'or' }), + (_req, res) => { + const settings = getSettings(); + return res.status(200).json(settings.sonarr); + } +); router.use('/settings', isAuthenticated(Permission.ADMIN), settingsRoutes); router.use('/search', isAuthenticated(), searchRoutes); router.use('/discover', isAuthenticated(), discoverRoutes); diff --git a/server/routes/settings/radarr.ts b/server/routes/settings/radarr.ts index efa5866582..cc927e3a00 100644 --- a/server/routes/settings/radarr.ts +++ b/server/routes/settings/radarr.ts @@ -6,12 +6,6 @@ import { Router } from 'express'; const radarrRoutes = Router(); -radarrRoutes.get('/', (_req, res) => { - const settings = getSettings(); - - res.status(200).json(settings.radarr); -}); - radarrRoutes.post('/', async (req, res) => { const settings = getSettings(); diff --git a/server/routes/settings/sonarr.ts b/server/routes/settings/sonarr.ts index 84bf4d7932..e0d7f5efc9 100644 --- a/server/routes/settings/sonarr.ts +++ b/server/routes/settings/sonarr.ts @@ -6,12 +6,6 @@ import { Router } from 'express'; const sonarrRoutes = Router(); -sonarrRoutes.get('/', (_req, res) => { - const settings = getSettings(); - - res.status(200).json(settings.sonarr); -}); - sonarrRoutes.post('/', async (req, res) => { const settings = getSettings(); From 901bac38b7e4726afc1d8b61a23884f3205e1a2f Mon Sep 17 00:00:00 2001 From: 0fflineuser <0fflineuser@cock.li> Date: Tue, 11 Nov 2025 17:56:20 +0100 Subject: [PATCH 6/8] fix: added DELETE_MEDIA permission to /media/{mediaId} route --- seerr-api.yml | 2 +- server/routes/media.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/seerr-api.yml b/seerr-api.yml index 3f613da5e1..6a70188a8d 100644 --- a/seerr-api.yml +++ b/seerr-api.yml @@ -6843,7 +6843,7 @@ paths: /media/{mediaId}: delete: summary: Delete media item - description: Removes a media item. The `MANAGE_REQUESTS` permission is required to perform this action. + description: Removes a media item. The `MANAGE_REQUESTS` or `DELETE_MEDIA` permission is required to perform this action. tags: - media parameters: diff --git a/server/routes/media.ts b/server/routes/media.ts index 129aa3a735..058117b516 100644 --- a/server/routes/media.ts +++ b/server/routes/media.ts @@ -165,7 +165,9 @@ mediaRoutes.post< mediaRoutes.delete( '/:id', - isAuthenticated(Permission.MANAGE_REQUESTS), + isAuthenticated([Permission.ADMIN, Permission.DELETE_MEDIA], { + type: 'or', + }), async (req, res, next) => { try { const mediaRepository = getRepository(Media); From 6bd6c1e1542050dda0e4b875a135b4acacbfb8cc Mon Sep 17 00:00:00 2001 From: 0fflineuser <0fflineuser@cock.li> Date: Tue, 11 Nov 2025 18:48:31 +0100 Subject: [PATCH 7/8] fix: permission typo --- server/routes/media.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routes/media.ts b/server/routes/media.ts index 058117b516..393d90a006 100644 --- a/server/routes/media.ts +++ b/server/routes/media.ts @@ -165,7 +165,7 @@ mediaRoutes.post< mediaRoutes.delete( '/:id', - isAuthenticated([Permission.ADMIN, Permission.DELETE_MEDIA], { + isAuthenticated([Permission.MANAGE_REQUESTS, Permission.DELETE_MEDIA], { type: 'or', }), async (req, res, next) => { From deb6b7bd7b32e89bcfec3dd47e929865f9eeb028 Mon Sep 17 00:00:00 2001 From: 0fflineuser <0fflineuser@cock.li> Date: Tue, 11 Nov 2025 19:20:53 +0100 Subject: [PATCH 8/8] fix: permissions fixes in UI for ManageSlideOver --- src/components/ManageSlideOver/index.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/ManageSlideOver/index.tsx b/src/components/ManageSlideOver/index.tsx index afe74d272d..b5c10d036f 100644 --- a/src/components/ManageSlideOver/index.tsx +++ b/src/components/ManageSlideOver/index.tsx @@ -479,9 +479,7 @@ const ManageSlideOver = ({
)} - {hasPermission([Permission.ADMIN], { - type: 'or', - }) && + {hasPermission(Permission.ADMIN) && (data.mediaInfo?.serviceUrl4k || data.mediaInfo?.tautulliUrl4k || watchData?.data4k) && ( @@ -642,9 +640,7 @@ const ManageSlideOver = ({ )} - {hasPermission([Permission.ADMIN], { - type: 'or', - }) && + {hasPermission(Permission.ADMIN) && data?.mediaInfo && data.mediaInfo.status !== MediaStatus.BLACKLISTED && (