refactor(blacklist): return null instead of 404 for missing keywords

This commit is contained in:
0xsysr3ll
2025-07-30 22:18:41 +02:00
parent 0c0d029969
commit 999a625b0c
10 changed files with 57 additions and 74 deletions

View File

@@ -7268,21 +7268,12 @@ paths:
example: 1
responses:
'200':
description: Keyword returned
description: Keyword returned (null if not found)
content:
application/json:
schema:
nullable: true
$ref: '#/components/schemas/Keyword'
'404':
description: Keyword not found
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: 'Keyword not found'
'500':
description: Internal server error
content:

View File

@@ -1054,7 +1054,7 @@ class TheMovieDb extends ExternalAPI {
keywordId,
}: {
keywordId: number;
}): Promise<TmdbKeyword> {
}): Promise<TmdbKeyword | null> {
try {
const data = await this.get<TmdbKeyword>(
`/keyword/${keywordId}`,
@@ -1065,7 +1065,7 @@ class TheMovieDb extends ExternalAPI {
return data;
} catch (e) {
if (e.response?.status === 404) {
throw e;
return null;
}
throw new Error(`[TMDB] Failed to fetch keyword: ${e.message}`);
}

View File

@@ -88,24 +88,17 @@ class BlacklistedTagProcessor implements RunnableScanner<StatusBase> {
// Iterate for each tag
for (const tag of blacklistedTagsArr) {
try {
await tmdb.getKeywordDetails({ keywordId: Number(tag) });
} catch (error) {
if (error.response?.status === 404) {
logger.warn('Skipping invalid keyword in blacklisted tags', {
label: 'Blacklisted Tags Processor',
keywordId: tag,
});
invalidKeywords.add(tag);
continue;
} else {
// Might be temporary service issues so do nothing
logger.error('Error checking keyword validity', {
label: 'Blacklisted Tags Processor',
keywordId: tag,
errorMessage: error.message,
});
}
const keywordDetails = await tmdb.getKeywordDetails({
keywordId: Number(tag),
});
if (keywordDetails === null) {
logger.warn('Skipping invalid keyword in blacklisted tags', {
label: 'Blacklisted Tags Processor',
keywordId: tag,
});
invalidKeywords.add(tag);
continue;
}
let queryMax = pageLimit * SortOptionsIterable.length;

View File

@@ -128,11 +128,15 @@ discoverRoutes.get('/movies', async (req, res, next) => {
if (keywords) {
const splitKeywords = keywords.split(',');
keywordData = await Promise.all(
const keywordResults = await Promise.all(
splitKeywords.map(async (keywordId) => {
return await tmdb.getKeywordDetails({ keywordId: Number(keywordId) });
})
);
keywordData = keywordResults.filter(
(keyword): keyword is TmdbKeyword => keyword !== null
);
}
return res.status(200).json({
@@ -415,11 +419,15 @@ discoverRoutes.get('/tv', async (req, res, next) => {
if (keywords) {
const splitKeywords = keywords.split(',');
keywordData = await Promise.all(
const keywordResults = await Promise.all(
splitKeywords.map(async (keywordId) => {
return await tmdb.getKeywordDetails({ keywordId: Number(keywordId) });
})
);
keywordData = keywordResults.filter(
(keyword): keyword is TmdbKeyword => keyword !== null
);
}
return res.status(200).json({

View File

@@ -330,10 +330,6 @@ router.get('/keyword/:keywordId', async (req, res, next) => {
return res.status(200).json(result);
} catch (e) {
if (e.response?.status === 404) {
return res.status(404).json({ message: 'Keyword not found' });
}
logger.debug('Something went wrong retrieving keyword data', {
label: 'API',
errorMessage: e.message,

View File

@@ -29,21 +29,13 @@ const BlacklistedTagsBadge = ({ data }: BlacklistedTagsBadgeProps) => {
const keywordIds = data.blacklistedTags.slice(1, -1).split(',');
Promise.all(
keywordIds.map(async (keywordId) => {
try {
const { data } = await axios.get<Keyword>(
`/api/v1/keyword/${keywordId}`
);
return data.name;
} catch (err) {
if (err.response?.status === 404) {
return `[Invalid: ${keywordId}]`;
}
return '';
}
const { data } = await axios.get<Keyword | null>(
`/api/v1/keyword/${keywordId}`
);
return data?.name || `[Invalid: ${keywordId}]`;
})
).then((keywords) => {
const validKeywords = keywords.filter((name) => name !== '');
setTagNamesBlacklistedFor(validKeywords.join(', '));
setTagNamesBlacklistedFor(keywords.join(', '));
});
}, [data.blacklistedTags]);

View File

@@ -5,10 +5,7 @@ import { encodeURIExtraParams } from '@app/hooks/useDiscover';
import defineMessages from '@app/utils/defineMessages';
import { Transition } from '@headlessui/react';
import { ArrowDownIcon } from '@heroicons/react/24/solid';
import type {
TmdbKeyword,
TmdbKeywordSearchResponse,
} from '@server/api/themoviedb/interfaces';
import type { TmdbKeywordSearchResponse } from '@server/api/themoviedb/interfaces';
import type { Keyword } from '@server/models/common';
import axios from 'axios';
import { useFormikContext } from 'formik';
@@ -127,19 +124,15 @@ const ControlledKeywordSelector = ({
const keywords = await Promise.all(
defaultValue.split(',').map(async (keywordId) => {
try {
const { data } = await axios.get<Keyword>(
`/api/v1/keyword/${keywordId}`
);
return data;
} catch (error) {
return null;
}
const { data } = await axios.get<Keyword | null>(
`/api/v1/keyword/${keywordId}`
);
return data;
})
);
const validKeywords: TmdbKeyword[] = keywords.filter(
(keyword) => keyword !== null
const validKeywords = keywords.filter(
(keyword): keyword is Keyword => keyword !== null
);
onChange(

View File

@@ -77,16 +77,19 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => {
const keywords = await Promise.all(
slider.data.split(',').map(async (keywordId) => {
const keyword = await axios.get<Keyword>(
const keyword = await axios.get<Keyword | null>(
`/api/v1/keyword/${keywordId}`
);
return keyword.data;
})
);
const validKeywords = keywords.filter(
(keyword): keyword is Keyword => keyword !== null
);
setDefaultDataValue(
keywords.map((keyword) => ({
validKeywords.map((keyword) => ({
label: keyword.name,
value: keyword.id,
}))

View File

@@ -309,16 +309,19 @@ export const KeywordSelector = ({
const keywords = await Promise.all(
defaultValue.split(',').map(async (keywordId) => {
const keyword = await axios.get<Keyword>(
const keyword = await axios.get<Keyword | null>(
`/api/v1/keyword/${keywordId}`
);
return keyword.data;
})
);
const validKeywords = keywords.filter(
(keyword): keyword is Keyword => keyword !== null
);
setDefaultDataValue(
keywords.map((keyword) => ({
validKeywords.map((keyword) => ({
label: keyword.name,
value: keyword.id,
}))

View File

@@ -113,12 +113,16 @@ const OverrideRuleTiles = ({
.flat()
.filter((keywordId) => keywordId)
.map(async (keywordId) => {
const response = await axios.get(`/api/v1/keyword/${keywordId}`);
const keyword: Keyword = response.data;
return keyword;
const response = await axios.get<Keyword | null>(
`/api/v1/keyword/${keywordId}`
);
return response.data;
})
);
setKeywords(keywords);
const validKeywords = keywords.filter(
(keyword): keyword is Keyword => keyword !== null
);
setKeywords(validKeywords);
const allUsersFromRules = rules
.map((rule) => rule.users)
.filter((users) => users)