mirror of
https://github.com/seerr-team/seerr.git
synced 2026-10-11 22:49:57 +00:00
feat(overriderules): apply override rules to advanced requests (#2164)
This commit is contained in:
@@ -8238,6 +8238,70 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/OverrideRule'
|
||||
/overrideRule/advancedRequest:
|
||||
post:
|
||||
summary: Advanced override rule request
|
||||
description: Processes an advanced override rule request.
|
||||
tags:
|
||||
- overriderule
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- mediaType
|
||||
- tmdbId
|
||||
properties:
|
||||
mediaType:
|
||||
type: string
|
||||
tmdbId:
|
||||
type: number
|
||||
example: 1
|
||||
is4k:
|
||||
type: boolean
|
||||
example: false
|
||||
requestId:
|
||||
type: number
|
||||
nullable: true
|
||||
requestUser:
|
||||
type: number
|
||||
nullable: true
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
nullable: true
|
||||
serviceId:
|
||||
type: number
|
||||
nullable: true
|
||||
responses:
|
||||
'200':
|
||||
description: Advanced override rule request processed
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
rootFolder:
|
||||
type: string
|
||||
nullable: true
|
||||
profileId:
|
||||
type: number
|
||||
nullable: true
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
nullable: true
|
||||
'403':
|
||||
description: User does not have permission to modify the request user
|
||||
'404':
|
||||
description: User, media or request not found
|
||||
'500':
|
||||
description: Unable to evaluate override rules
|
||||
|
||||
security:
|
||||
- cookieAuth: []
|
||||
- apiKey: []
|
||||
|
||||
+23
-120
@@ -1,15 +1,13 @@
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants';
|
||||
import type { TmdbKeyword } from '@server/api/themoviedb/interfaces';
|
||||
import {
|
||||
MediaRequestStatus,
|
||||
MediaStatus,
|
||||
MediaType,
|
||||
} from '@server/constants/media';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import OverrideRule from '@server/entity/OverrideRule';
|
||||
import type { MediaRequestBody } from '@server/interfaces/api/requestInterfaces';
|
||||
import notificationManager, { Notification } from '@server/lib/notifications';
|
||||
import overrideRules from '@server/lib/overrideRules';
|
||||
import { Permission } from '@server/lib/permissions';
|
||||
import { getSettings } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
@@ -256,125 +254,30 @@ export class MediaRequest {
|
||||
let tags = requestBody.tags;
|
||||
|
||||
if (useOverrides) {
|
||||
const defaultRadarrId = requestBody.is4k
|
||||
? settings.radarr.find((r) => r.is4k && r.isDefault)?.id
|
||||
: settings.radarr.find((r) => !r.is4k && r.isDefault)?.id;
|
||||
const defaultSonarrId = requestBody.is4k
|
||||
? settings.sonarr.find((s) => s.is4k && s.isDefault)?.id
|
||||
: settings.sonarr.find((s) => !s.is4k && s.isDefault)?.id;
|
||||
|
||||
const [defaultServiceIdField, defaultServiceId] =
|
||||
requestBody.mediaType === MediaType.MOVIE
|
||||
? (['radarrServiceId', defaultRadarrId] as const)
|
||||
: (['sonarrServiceId', defaultSonarrId] as const);
|
||||
|
||||
const overrideRuleRepository = getRepository(OverrideRule);
|
||||
const overrideRules =
|
||||
defaultServiceId === undefined
|
||||
? []
|
||||
: await overrideRuleRepository.find({
|
||||
where: { [defaultServiceIdField]: defaultServiceId },
|
||||
});
|
||||
|
||||
const appliedOverrideRules = overrideRules.filter((rule) => {
|
||||
const hasAnimeKeyword =
|
||||
'results' in tmdbMedia.keywords &&
|
||||
tmdbMedia.keywords.results.some(
|
||||
(keyword: TmdbKeyword) => keyword.id === ANIME_KEYWORD_ID
|
||||
);
|
||||
|
||||
// Skip override rules if the media is an anime TV show as anime TV
|
||||
// is handled by default and override rules do not explicitly include
|
||||
// the anime keyword
|
||||
if (
|
||||
requestBody.mediaType === MediaType.TV &&
|
||||
hasAnimeKeyword &&
|
||||
(!rule.keywords ||
|
||||
!rule.keywords.split(',').map(Number).includes(ANIME_KEYWORD_ID))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
rule.users &&
|
||||
!rule.users
|
||||
.split(',')
|
||||
.some((userId) => Number(userId) === requestUser.id)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
rule.genre &&
|
||||
!rule.genre
|
||||
.split(',')
|
||||
.some((genreId) =>
|
||||
tmdbMedia.genres
|
||||
.map((genre) => genre.id)
|
||||
.includes(Number(genreId))
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
rule.language &&
|
||||
!rule.language
|
||||
.split('|')
|
||||
.some((languageId) => languageId === tmdbMedia.original_language)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
rule.keywords &&
|
||||
!rule.keywords.split(',').some((keywordId) => {
|
||||
let keywordList: TmdbKeyword[] = [];
|
||||
|
||||
if ('keywords' in tmdbMedia.keywords) {
|
||||
keywordList = tmdbMedia.keywords.keywords;
|
||||
} else if ('results' in tmdbMedia.keywords) {
|
||||
keywordList = tmdbMedia.keywords.results;
|
||||
}
|
||||
|
||||
return keywordList
|
||||
.map((keyword: TmdbKeyword) => keyword.id)
|
||||
.includes(Number(keywordId));
|
||||
})
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
const overrideRulesResult = await overrideRules({
|
||||
mediaType: requestBody.mediaType,
|
||||
is4k: requestBody.is4k || false,
|
||||
tmdbMedia,
|
||||
requestUser,
|
||||
tags,
|
||||
});
|
||||
|
||||
// hacky way to prioritize rules
|
||||
// TODO: make this better
|
||||
const prioritizedRule = appliedOverrideRules.sort((a, b) => {
|
||||
const keys: (keyof OverrideRule)[] = ['genre', 'language', 'keywords'];
|
||||
|
||||
const aSpecificity = keys.filter((key) => a[key] !== null).length;
|
||||
const bSpecificity = keys.filter((key) => b[key] !== null).length;
|
||||
|
||||
// Take the rule with the most specific condition first
|
||||
return bSpecificity - aSpecificity;
|
||||
})[0];
|
||||
|
||||
if (prioritizedRule) {
|
||||
if (prioritizedRule.rootFolder) {
|
||||
rootFolder = prioritizedRule.rootFolder;
|
||||
}
|
||||
if (prioritizedRule.profileId) {
|
||||
profileId = prioritizedRule.profileId;
|
||||
}
|
||||
if (prioritizedRule.tags) {
|
||||
tags = [
|
||||
...new Set([
|
||||
...(tags || []),
|
||||
...prioritizedRule.tags.split(',').map((tag) => Number(tag)),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
if (overrideRulesResult.rootFolder) {
|
||||
rootFolder = overrideRulesResult.rootFolder;
|
||||
}
|
||||
if (overrideRulesResult.profileId) {
|
||||
profileId = overrideRulesResult.profileId;
|
||||
}
|
||||
if (overrideRulesResult.tags) {
|
||||
tags = overrideRulesResult.tags;
|
||||
}
|
||||
if (
|
||||
overrideRulesResult.rootFolder ||
|
||||
overrideRulesResult.profileId ||
|
||||
overrideRulesResult.tags
|
||||
) {
|
||||
logger.debug('Override rule applied.', {
|
||||
label: 'Media Request',
|
||||
overrides: prioritizedRule,
|
||||
label: 'Override Rules',
|
||||
overrides: overrideRulesResult,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants';
|
||||
import type {
|
||||
TmdbKeyword,
|
||||
TmdbMovieDetails,
|
||||
TmdbTvDetails,
|
||||
} from '@server/api/themoviedb/interfaces';
|
||||
import { MediaType } from '@server/constants/media';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import OverrideRule from '@server/entity/OverrideRule';
|
||||
import type { User } from '@server/entity/User';
|
||||
import { getSettings } from '@server/lib/settings';
|
||||
|
||||
export type OverrideRulesResult = {
|
||||
rootFolder: string | null;
|
||||
profileId: number | null;
|
||||
tags: number[] | null;
|
||||
};
|
||||
|
||||
async function overrideRules({
|
||||
mediaType,
|
||||
is4k,
|
||||
tmdbMedia,
|
||||
requestUser,
|
||||
tags = null,
|
||||
serviceId,
|
||||
}: {
|
||||
mediaType: MediaType;
|
||||
is4k: boolean;
|
||||
tmdbMedia: TmdbMovieDetails | TmdbTvDetails;
|
||||
requestUser: User;
|
||||
tags?: number[] | null;
|
||||
serviceId?: number;
|
||||
}): Promise<OverrideRulesResult> {
|
||||
const settings = getSettings();
|
||||
|
||||
let rootFolder: string | null = null;
|
||||
let profileId: number | null = null;
|
||||
|
||||
const serviceIdField =
|
||||
mediaType === MediaType.MOVIE ? 'radarrServiceId' : 'sonarrServiceId';
|
||||
if (serviceId === undefined) {
|
||||
const defaultRadarrId = is4k
|
||||
? settings.radarr.find((r) => r.is4k && r.isDefault)?.id
|
||||
: settings.radarr.find((r) => !r.is4k && r.isDefault)?.id;
|
||||
const defaultSonarrId = is4k
|
||||
? settings.sonarr.find((s) => s.is4k && s.isDefault)?.id
|
||||
: settings.sonarr.find((s) => !s.is4k && s.isDefault)?.id;
|
||||
serviceId =
|
||||
mediaType === MediaType.MOVIE ? defaultRadarrId : defaultSonarrId;
|
||||
}
|
||||
|
||||
const overrideRuleRepository = getRepository(OverrideRule);
|
||||
const rules =
|
||||
serviceId === undefined
|
||||
? []
|
||||
: await overrideRuleRepository.find({
|
||||
where: { [serviceIdField]: serviceId },
|
||||
});
|
||||
|
||||
const appliedOverrideRules = rules.filter((rule) => {
|
||||
const hasAnimeKeyword =
|
||||
'results' in tmdbMedia.keywords &&
|
||||
tmdbMedia.keywords.results.some(
|
||||
(keyword: TmdbKeyword) => keyword.id === ANIME_KEYWORD_ID
|
||||
);
|
||||
|
||||
// Skip override rules if the media is an anime TV show as anime TV
|
||||
// is handled by default and override rules do not explicitly include
|
||||
// the anime keyword
|
||||
if (
|
||||
mediaType === MediaType.TV &&
|
||||
hasAnimeKeyword &&
|
||||
(!rule.keywords ||
|
||||
!rule.keywords.split(',').map(Number).includes(ANIME_KEYWORD_ID))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
rule.users &&
|
||||
!rule.users.split(',').some((userId) => Number(userId) === requestUser.id)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
rule.genre &&
|
||||
!rule.genre
|
||||
.split(',')
|
||||
.some((genreId) =>
|
||||
tmdbMedia.genres.map((genre) => genre.id).includes(Number(genreId))
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
rule.language &&
|
||||
!rule.language
|
||||
.split('|')
|
||||
.some((languageId) => languageId === tmdbMedia.original_language)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
rule.keywords &&
|
||||
!rule.keywords.split(',').some((keywordId) => {
|
||||
let keywordList: TmdbKeyword[] = [];
|
||||
|
||||
if ('keywords' in tmdbMedia.keywords) {
|
||||
keywordList = tmdbMedia.keywords.keywords;
|
||||
} else if ('results' in tmdbMedia.keywords) {
|
||||
keywordList = tmdbMedia.keywords.results;
|
||||
}
|
||||
|
||||
return keywordList
|
||||
.map((keyword: TmdbKeyword) => keyword.id)
|
||||
.includes(Number(keywordId));
|
||||
})
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// hacky way to prioritize rules
|
||||
// TODO: make this better
|
||||
const hasSpecificOverrideValue = (
|
||||
rule: OverrideRule,
|
||||
key: keyof OverrideRule
|
||||
) => {
|
||||
const value = rule[key];
|
||||
if (value == null) return false;
|
||||
return typeof value !== 'string' || value.trim() !== '';
|
||||
};
|
||||
const prioritizedRule = appliedOverrideRules.sort((a, b) => {
|
||||
const keys: (keyof OverrideRule)[] = ['genre', 'language', 'keywords'];
|
||||
const aSpecificity = keys.filter((key) =>
|
||||
hasSpecificOverrideValue(a, key)
|
||||
).length;
|
||||
const bSpecificity = keys.filter((key) =>
|
||||
hasSpecificOverrideValue(b, key)
|
||||
).length;
|
||||
// Take the rule with the most specific condition first
|
||||
return bSpecificity - aSpecificity;
|
||||
})[0];
|
||||
|
||||
if (prioritizedRule) {
|
||||
if (prioritizedRule.rootFolder) {
|
||||
rootFolder = prioritizedRule.rootFolder;
|
||||
}
|
||||
if (prioritizedRule.profileId) {
|
||||
profileId = prioritizedRule.profileId;
|
||||
}
|
||||
if (prioritizedRule.tags) {
|
||||
tags = [
|
||||
...new Set([
|
||||
...(tags || []),
|
||||
...prioritizedRule.tags.split(',').map((tag) => Number(tag)),
|
||||
]),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return { rootFolder, profileId, tags };
|
||||
}
|
||||
|
||||
export default overrideRules;
|
||||
@@ -178,11 +178,7 @@ router.use('/service', isAuthenticated(), serviceRoutes);
|
||||
router.use('/issue', isAuthenticated(), issueRoutes);
|
||||
router.use('/issueComment', isAuthenticated(), issueCommentRoutes);
|
||||
router.use('/auth', authRoutes);
|
||||
router.use(
|
||||
'/overrideRule',
|
||||
isAuthenticated(Permission.ADMIN),
|
||||
overrideRuleRoutes
|
||||
);
|
||||
router.use('/overrideRule', isAuthenticated(), overrideRuleRoutes);
|
||||
|
||||
router.get('/regions', isAuthenticated(), async (req, res, next) => {
|
||||
const tmdb = new TheMovieDb();
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import type {
|
||||
TmdbMovieDetails,
|
||||
TmdbTvDetails,
|
||||
} from '@server/api/themoviedb/interfaces';
|
||||
import { MediaType } from '@server/constants/media';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import { MediaRequest } from '@server/entity/MediaRequest';
|
||||
import OverrideRule from '@server/entity/OverrideRule';
|
||||
import { User } from '@server/entity/User';
|
||||
import type { OverrideRuleResultsResponse } from '@server/interfaces/api/overrideRuleInterfaces';
|
||||
import overrideRules, {
|
||||
type OverrideRulesResult,
|
||||
} from '@server/lib/overrideRules';
|
||||
import { Permission } from '@server/lib/permissions';
|
||||
import logger from '@server/logger';
|
||||
import { isAuthenticated } from '@server/middleware/auth';
|
||||
import { Router } from 'express';
|
||||
|
||||
@@ -61,6 +73,127 @@ overrideRuleRoutes.post<
|
||||
}
|
||||
});
|
||||
|
||||
overrideRuleRoutes.post<
|
||||
Record<string, string>,
|
||||
OverrideRulesResult,
|
||||
{
|
||||
mediaType: MediaType;
|
||||
is4k: boolean;
|
||||
tmdbId: number;
|
||||
requestUser: number;
|
||||
requestId: number | null;
|
||||
tags?: number[] | null;
|
||||
serviceId?: number;
|
||||
}
|
||||
>(
|
||||
'/advancedRequest',
|
||||
isAuthenticated([Permission.REQUEST_ADVANCED, Permission.MANAGE_REQUESTS], {
|
||||
type: 'or',
|
||||
}),
|
||||
async (req, res, next) => {
|
||||
try {
|
||||
const tmdb = new TheMovieDb();
|
||||
let tmdbMedia: TmdbMovieDetails | TmdbTvDetails;
|
||||
try {
|
||||
tmdbMedia =
|
||||
req.body.mediaType === MediaType.MOVIE
|
||||
? await tmdb.getMovie({ movieId: req.body.tmdbId })
|
||||
: await tmdb.getTvShow({ tvId: req.body.tmdbId });
|
||||
} catch {
|
||||
return next({ status: 404, message: 'Media not found' });
|
||||
}
|
||||
|
||||
const userRepository = getRepository(User);
|
||||
const requestRepository = getRepository(MediaRequest);
|
||||
|
||||
let ruleUser: User | null = req.user || null;
|
||||
|
||||
if (req.body.requestId) {
|
||||
const request = await requestRepository.findOne({
|
||||
where: {
|
||||
id: Number(req.body.requestId),
|
||||
},
|
||||
});
|
||||
|
||||
if (!request) {
|
||||
return next({ status: 404, message: 'Request not found.' });
|
||||
}
|
||||
|
||||
if (
|
||||
(request.requestedBy.id !== req.user?.id ||
|
||||
(req.body.mediaType !== MediaType.TV &&
|
||||
!req.user?.hasPermission(Permission.REQUEST_ADVANCED))) &&
|
||||
!req.user?.hasPermission(Permission.MANAGE_REQUESTS)
|
||||
) {
|
||||
return next({
|
||||
status: 403,
|
||||
message: 'You do not have permission to modify this request.',
|
||||
});
|
||||
}
|
||||
|
||||
ruleUser = request.requestedBy;
|
||||
|
||||
if (
|
||||
req.body.requestUser &&
|
||||
req.body.requestUser !== request.requestedBy.id &&
|
||||
!req.user?.hasPermission([
|
||||
Permission.MANAGE_USERS,
|
||||
Permission.MANAGE_REQUESTS,
|
||||
])
|
||||
) {
|
||||
return next({
|
||||
status: 403,
|
||||
message: 'You do not have permission to modify the request user.',
|
||||
});
|
||||
} else if (req.body.requestUser) {
|
||||
ruleUser = await userRepository.findOneOrFail({
|
||||
where: { id: req.body.requestUser },
|
||||
});
|
||||
}
|
||||
} else if (req.body.requestUser !== req.user?.id) {
|
||||
if (
|
||||
req.body.requestUser &&
|
||||
!req.user?.hasPermission([
|
||||
Permission.MANAGE_USERS,
|
||||
Permission.MANAGE_REQUESTS,
|
||||
])
|
||||
) {
|
||||
return next({
|
||||
status: 403,
|
||||
message: 'You do not have permission to modify the request user.',
|
||||
});
|
||||
} else if (req.body.requestUser) {
|
||||
ruleUser = await userRepository.findOneOrFail({
|
||||
where: { id: req.body.requestUser },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!ruleUser) {
|
||||
return next({ status: 404, message: 'User not found.' });
|
||||
}
|
||||
|
||||
const overrideRulesResult: OverrideRulesResult = await overrideRules({
|
||||
mediaType: req.body.mediaType,
|
||||
is4k: req.body.is4k,
|
||||
tmdbMedia,
|
||||
requestUser: ruleUser,
|
||||
tags: req.body.tags,
|
||||
serviceId: req.body.serviceId,
|
||||
});
|
||||
|
||||
res.status(200).json(overrideRulesResult);
|
||||
} catch (e) {
|
||||
logger.error('Something went wrong evaluating override rules', {
|
||||
label: 'API',
|
||||
errorMessage: e.message,
|
||||
tmdbId: req.body.tmdbId,
|
||||
});
|
||||
next({ status: 500, message: 'Unable to evaluate override rules.' });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
overrideRuleRoutes.put<
|
||||
{ ruleId: string },
|
||||
OverrideRule,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import CachedImage from '@app/components/Common/CachedImage';
|
||||
import { SmallLoadingSpinner } from '@app/components/Common/LoadingSpinner';
|
||||
import SlideCheckbox from '@app/components/Common/SlideCheckbox';
|
||||
import useToasts from '@app/hooks/useToasts';
|
||||
import type { User } from '@app/hooks/useUser';
|
||||
import { Permission, useUser } from '@app/hooks/useUser';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
@@ -21,7 +22,9 @@ import type {
|
||||
ServiceCommonServerWithDetails,
|
||||
} from '@server/interfaces/api/serviceInterfaces';
|
||||
import type { UserResultsResponse } from '@server/interfaces/api/userInterfaces';
|
||||
import type { OverrideRulesResult } from '@server/lib/overrideRules';
|
||||
import { hasPermission } from '@server/lib/permissions';
|
||||
import axios from 'axios';
|
||||
import { isEqual } from 'lodash';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
@@ -63,24 +66,29 @@ export type RequestOverrides = {
|
||||
|
||||
interface AdvancedRequesterProps {
|
||||
type: 'movie' | 'tv';
|
||||
tmdbId?: number;
|
||||
is4k: boolean;
|
||||
isAnime?: boolean;
|
||||
defaultOverrides?: RequestOverrides;
|
||||
requestUser?: User;
|
||||
requestId?: number;
|
||||
quota?: { movie: { limit?: number }; tv: { limit?: number } };
|
||||
onChange: (overrides: RequestOverrides) => void;
|
||||
}
|
||||
|
||||
const AdvancedRequester = ({
|
||||
type,
|
||||
tmdbId,
|
||||
is4k = false,
|
||||
isAnime = false,
|
||||
defaultOverrides,
|
||||
requestUser,
|
||||
requestId,
|
||||
quota,
|
||||
onChange,
|
||||
}: AdvancedRequesterProps) => {
|
||||
const intl = useIntl();
|
||||
const { addToast } = useToasts();
|
||||
const { user: currentUser, hasPermission: currentHasPermission } = useUser();
|
||||
const { data, error } = useSWR<ServiceCommonServer[]>(
|
||||
`/api/v1/service/${type === 'movie' ? 'radarr' : 'sonarr'}`,
|
||||
@@ -330,6 +338,68 @@ const AdvancedRequester = ({
|
||||
isIgnoreQuotaVisible,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
if (tmdbId && serverData?.server.id === selectedServer) {
|
||||
try {
|
||||
const { data: override } = await axios.post<OverrideRulesResult>(
|
||||
'/api/v1/overrideRule/advancedRequest',
|
||||
{
|
||||
mediaType: type,
|
||||
is4k,
|
||||
requestUser:
|
||||
selectedUser?.id ?? requestUser?.id ?? currentUser?.id,
|
||||
tmdbId,
|
||||
tags: selectedTags.length > 0 ? selectedTags : undefined,
|
||||
serviceId: selectedServer ?? undefined,
|
||||
requestId: requestId ?? undefined,
|
||||
}
|
||||
);
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
if (!defaultOverrides?.folder && override.rootFolder) {
|
||||
setSelectedFolder(override.rootFolder);
|
||||
}
|
||||
if (!defaultOverrides?.profile && override.profileId) {
|
||||
setSelectedProfile(override.profileId);
|
||||
}
|
||||
if (
|
||||
!defaultOverrides?.tags &&
|
||||
override.tags &&
|
||||
!isEqual(override.tags, selectedTags)
|
||||
) {
|
||||
setSelectedTags(override.tags);
|
||||
}
|
||||
} catch {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
addToast(intl.formatMessage(globalMessages.error), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [
|
||||
tmdbId,
|
||||
type,
|
||||
is4k,
|
||||
serverData?.server.id,
|
||||
selectedServer,
|
||||
selectedUserId,
|
||||
requestUser?.id,
|
||||
currentUser?.id,
|
||||
defaultOverrides?.folder,
|
||||
defaultOverrides?.profile,
|
||||
defaultOverrides?.tags,
|
||||
]);
|
||||
|
||||
if (!data && !error) {
|
||||
return (
|
||||
<div className="mb-2 w-full">
|
||||
|
||||
@@ -517,8 +517,10 @@ const CollectionRequestModal = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{(hasPermission(Permission.REQUEST_ADVANCED) ||
|
||||
hasPermission(Permission.MANAGE_REQUESTS)) && (
|
||||
{hasPermission(
|
||||
[Permission.REQUEST_ADVANCED, Permission.MANAGE_REQUESTS],
|
||||
{ type: 'or' }
|
||||
) && (
|
||||
<AdvancedRequester
|
||||
type="movie"
|
||||
is4k={is4k}
|
||||
|
||||
@@ -285,12 +285,16 @@ const MovieRequestModal = ({
|
||||
: intl.formatMessage(messages.requestfrom, {
|
||||
username: editRequest.requestedBy.displayName,
|
||||
})}
|
||||
{(hasPermission(Permission.REQUEST_ADVANCED) ||
|
||||
hasPermission(Permission.MANAGE_REQUESTS)) && (
|
||||
{hasPermission(
|
||||
[Permission.REQUEST_ADVANCED, Permission.MANAGE_REQUESTS],
|
||||
{ type: 'or' }
|
||||
) && (
|
||||
<AdvancedRequester
|
||||
type="movie"
|
||||
tmdbId={tmdbId}
|
||||
is4k={is4k}
|
||||
requestUser={editRequest.requestedBy}
|
||||
requestId={editRequest.id}
|
||||
defaultOverrides={{
|
||||
folder: editRequest.rootFolder,
|
||||
profile: editRequest.profileId,
|
||||
@@ -358,9 +362,12 @@ const MovieRequestModal = ({
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{(hasPermission(Permission.REQUEST_ADVANCED) ||
|
||||
hasPermission(Permission.MANAGE_REQUESTS)) && (
|
||||
{hasPermission(
|
||||
[Permission.REQUEST_ADVANCED, Permission.MANAGE_REQUESTS],
|
||||
{ type: 'or' }
|
||||
) && (
|
||||
<AdvancedRequester
|
||||
tmdbId={tmdbId}
|
||||
type="movie"
|
||||
is4k={is4k}
|
||||
quota={quota}
|
||||
|
||||
@@ -714,10 +714,13 @@ const TvRequestModal = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{(hasPermission(Permission.REQUEST_ADVANCED) ||
|
||||
hasPermission(Permission.MANAGE_REQUESTS)) && (
|
||||
{hasPermission(
|
||||
[Permission.REQUEST_ADVANCED, Permission.MANAGE_REQUESTS],
|
||||
{ type: 'or' }
|
||||
) && (
|
||||
<AdvancedRequester
|
||||
type="tv"
|
||||
tmdbId={tmdbId}
|
||||
is4k={is4k}
|
||||
isAnime={data?.keywords.some(
|
||||
(keyword) => keyword.id === ANIME_KEYWORD_ID
|
||||
@@ -725,6 +728,7 @@ const TvRequestModal = ({
|
||||
quota={quota}
|
||||
onChange={(overrides) => setRequestOverrides(overrides)}
|
||||
requestUser={editRequest?.requestedBy}
|
||||
requestId={editRequest?.id}
|
||||
defaultOverrides={
|
||||
editRequest
|
||||
? {
|
||||
|
||||
Reference in New Issue
Block a user