feat(blocklist): add support for collections (#1841)
This commit is contained in:
@@ -4798,6 +4798,49 @@ paths:
|
||||
responses:
|
||||
'204':
|
||||
description: Succesfully removed media item
|
||||
/blocklist/collection/{collectionId}:
|
||||
post:
|
||||
summary: Add collection to blocklist
|
||||
description: Adds all movies in a collection to the blocklist
|
||||
tags:
|
||||
- blocklist
|
||||
parameters:
|
||||
- in: path
|
||||
name: collectionId
|
||||
description: Collection ID
|
||||
required: true
|
||||
example: '1424991'
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
description: Successfully added collection to blocklist
|
||||
'500':
|
||||
description: Error adding collection to blocklist
|
||||
delete:
|
||||
summary: Remove collection from blocklist
|
||||
description: Removes all movies in a collection from the blocklist
|
||||
tags:
|
||||
- blocklist
|
||||
parameters:
|
||||
- in: path
|
||||
name: collectionId
|
||||
description: Collection ID
|
||||
required: true
|
||||
example: '1424991'
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Successfully removed collection from blocklist
|
||||
'500':
|
||||
description: Error removing collection from blocklist
|
||||
/watchlist:
|
||||
post:
|
||||
summary: Add media to watchlist
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { MediaType } from '@server/constants/media';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import { MediaStatus, MediaType } from '@server/constants/media';
|
||||
import dataSource, { getRepository } from '@server/datasource';
|
||||
import { Blocklist } from '@server/entity/Blocklist';
|
||||
import Media from '@server/entity/Media';
|
||||
import type { BlocklistResultsResponse } from '@server/interfaces/api/blocklistInterfaces';
|
||||
@@ -7,7 +8,7 @@ import { Permission } from '@server/lib/permissions';
|
||||
import logger from '@server/logger';
|
||||
import { isAuthenticated } from '@server/middleware/auth';
|
||||
import { Router } from 'express';
|
||||
import { EntityNotFoundError, QueryFailedError } from 'typeorm';
|
||||
import { EntityNotFoundError, In, QueryFailedError } from 'typeorm';
|
||||
import { z } from 'zod';
|
||||
|
||||
const blocklistRoutes = Router();
|
||||
@@ -17,6 +18,7 @@ export const blocklistAdd = z.object({
|
||||
mediaType: z.nativeEnum(MediaType),
|
||||
title: z.coerce.string().optional(),
|
||||
user: z.coerce.number(),
|
||||
blocklistedTags: z.string().optional(),
|
||||
});
|
||||
|
||||
const blocklistGet = z.object({
|
||||
@@ -158,6 +160,107 @@ blocklistRoutes.post(
|
||||
}
|
||||
);
|
||||
|
||||
blocklistRoutes.post(
|
||||
'/collection/:id',
|
||||
isAuthenticated([Permission.MANAGE_BLOCKLIST], {
|
||||
type: 'or',
|
||||
}),
|
||||
async (req, res, next) => {
|
||||
try {
|
||||
const tmdb = new TheMovieDb();
|
||||
const collection = await tmdb.getCollection({
|
||||
collectionId: Number(req.params.id),
|
||||
language: req.locale,
|
||||
});
|
||||
|
||||
const uniqueParts = [
|
||||
...new Map(collection.parts.map((p) => [p.id, p])).values(),
|
||||
];
|
||||
const partIds = uniqueParts.map((p) => p.id);
|
||||
if (partIds.length === 0) {
|
||||
return res.status(201).send();
|
||||
}
|
||||
|
||||
await dataSource.transaction(async (em) => {
|
||||
const blocklistRepository = em.getRepository(Blocklist);
|
||||
const mediaRepository = em.getRepository(Media);
|
||||
|
||||
const [existingBlocklists, existingMedia] = await Promise.all([
|
||||
blocklistRepository.find({
|
||||
where: { tmdbId: In(partIds), mediaType: MediaType.MOVIE },
|
||||
}),
|
||||
mediaRepository.find({
|
||||
where: { tmdbId: In(partIds), mediaType: MediaType.MOVIE },
|
||||
}),
|
||||
]);
|
||||
const blocklistByTmdbId = new Map(
|
||||
existingBlocklists.map((b) => [b.tmdbId, b])
|
||||
);
|
||||
const mediaByTmdbId = new Map(existingMedia.map((m) => [m.tmdbId, m]));
|
||||
|
||||
await Promise.all(
|
||||
uniqueParts.map(async (part) => {
|
||||
if (blocklistByTmdbId.has(part.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let blocklist = new Blocklist({
|
||||
tmdbId: part.id,
|
||||
mediaType: MediaType.MOVIE,
|
||||
title: part.title,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
try {
|
||||
await blocklistRepository.save(blocklist);
|
||||
} catch (error) {
|
||||
if (
|
||||
!(error instanceof QueryFailedError) ||
|
||||
error.driverError.errno !== 19
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
const row = await blocklistRepository.findOne({
|
||||
where: { tmdbId: part.id, mediaType: MediaType.MOVIE },
|
||||
});
|
||||
if (!row) {
|
||||
throw error;
|
||||
}
|
||||
blocklist = row;
|
||||
}
|
||||
|
||||
let media = mediaByTmdbId.get(part.id);
|
||||
if (!media) {
|
||||
media = new Media({
|
||||
tmdbId: part.id,
|
||||
status: MediaStatus.BLOCKLISTED,
|
||||
status4k: MediaStatus.BLOCKLISTED,
|
||||
mediaType: MediaType.MOVIE,
|
||||
blocklist: Promise.resolve(blocklist),
|
||||
});
|
||||
} else {
|
||||
media.status = MediaStatus.BLOCKLISTED;
|
||||
media.status4k = MediaStatus.BLOCKLISTED;
|
||||
media.blocklist = Promise.resolve(blocklist);
|
||||
}
|
||||
|
||||
await mediaRepository.save(media);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return res.status(201).send();
|
||||
} catch (e) {
|
||||
logger.error('Error blocklisting collection', {
|
||||
label: 'Blocklist',
|
||||
errorMessage: e.message,
|
||||
collectionId: req.params.id,
|
||||
});
|
||||
return next({ status: 500, message: e.message });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
blocklistRoutes.delete(
|
||||
'/:id',
|
||||
isAuthenticated([Permission.MANAGE_BLOCKLIST], {
|
||||
@@ -208,4 +311,54 @@ blocklistRoutes.delete(
|
||||
}
|
||||
);
|
||||
|
||||
blocklistRoutes.delete(
|
||||
'/collection/:id',
|
||||
isAuthenticated([Permission.MANAGE_BLOCKLIST], {
|
||||
type: 'or',
|
||||
}),
|
||||
async (req, res, next) => {
|
||||
try {
|
||||
const tmdb = new TheMovieDb();
|
||||
const collection = await tmdb.getCollection({
|
||||
collectionId: Number(req.params.id),
|
||||
language: req.locale,
|
||||
});
|
||||
|
||||
await dataSource.transaction(async (em) => {
|
||||
const blocklistRepository = em.getRepository(Blocklist);
|
||||
const mediaRepository = em.getRepository(Media);
|
||||
|
||||
await Promise.all(
|
||||
collection.parts.map(async (part) => {
|
||||
const blocklistItem = await blocklistRepository.findOne({
|
||||
where: { tmdbId: part.id, mediaType: MediaType.MOVIE },
|
||||
});
|
||||
|
||||
if (blocklistItem) {
|
||||
await blocklistRepository.remove(blocklistItem);
|
||||
|
||||
const mediaItem = await mediaRepository.findOne({
|
||||
where: { tmdbId: part.id, mediaType: MediaType.MOVIE },
|
||||
});
|
||||
|
||||
if (mediaItem) {
|
||||
await mediaRepository.remove(mediaItem);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return res.status(204).send();
|
||||
} catch (e) {
|
||||
logger.error('Error unblocklisting collection', {
|
||||
label: 'Blocklist',
|
||||
errorMessage: e.message,
|
||||
collectionId: req.params.id,
|
||||
});
|
||||
return next({ status: 500, message: e.message });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default blocklistRoutes;
|
||||
|
||||
@@ -2,6 +2,8 @@ import Modal from '@app/components/Common/Modal';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { Transition } from '@headlessui/react';
|
||||
|
||||
import type { Collection } from '@server/models/Collection';
|
||||
import type { MovieDetails } from '@server/models/Movie';
|
||||
import type { TvDetails } from '@server/models/Tv';
|
||||
import axios from 'axios';
|
||||
@@ -21,8 +23,18 @@ const messages = defineMessages('component.BlocklistModal', {
|
||||
blocklisting: 'Blocklisting',
|
||||
});
|
||||
|
||||
const isCollection = (
|
||||
data: MovieDetails | TvDetails | Collection | null
|
||||
): data is Collection => {
|
||||
return (
|
||||
data !== null &&
|
||||
data !== undefined &&
|
||||
(data as Collection).parts !== undefined
|
||||
);
|
||||
};
|
||||
|
||||
const isMovie = (
|
||||
movie: MovieDetails | TvDetails | null
|
||||
movie: MovieDetails | TvDetails | Collection | null
|
||||
): movie is MovieDetails => {
|
||||
if (!movie) return false;
|
||||
return (movie as MovieDetails).title !== undefined;
|
||||
@@ -37,7 +49,9 @@ const BlocklistModal = ({
|
||||
isUpdating,
|
||||
}: BlocklistModalProps) => {
|
||||
const intl = useIntl();
|
||||
const [data, setData] = useState<TvDetails | MovieDetails | null>(null);
|
||||
const [data, setData] = useState<
|
||||
TvDetails | MovieDetails | Collection | null
|
||||
>(null);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -68,11 +82,19 @@ const BlocklistModal = ({
|
||||
loading={!data && !error}
|
||||
backgroundClickable
|
||||
title={`${intl.formatMessage(globalMessages.blocklist)} ${
|
||||
isMovie(data)
|
||||
? intl.formatMessage(globalMessages.movie)
|
||||
: intl.formatMessage(globalMessages.tvshow)
|
||||
type === 'collection'
|
||||
? intl.formatMessage(globalMessages.collection)
|
||||
: isMovie(data)
|
||||
? intl.formatMessage(globalMessages.movie)
|
||||
: intl.formatMessage(globalMessages.tvshow)
|
||||
}`}
|
||||
subTitle={`${
|
||||
isCollection(data)
|
||||
? data.name
|
||||
: isMovie(data)
|
||||
? data.title
|
||||
: data?.name
|
||||
}`}
|
||||
subTitle={`${isMovie(data) ? data.title : data?.name}`}
|
||||
onCancel={onCancel}
|
||||
onOk={onComplete}
|
||||
okText={
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import BlocklistModal from '@app/components/BlocklistModal';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import ButtonWithDropdown from '@app/components/Common/ButtonWithDropdown';
|
||||
import CachedImage from '@app/components/Common/CachedImage';
|
||||
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
|
||||
import PageTitle from '@app/components/Common/PageTitle';
|
||||
import Tooltip from '@app/components/Common/Tooltip';
|
||||
import RequestModal from '@app/components/RequestModal';
|
||||
import Slider from '@app/components/Slider';
|
||||
import StatusBadge from '@app/components/StatusBadge';
|
||||
@@ -12,19 +15,27 @@ import globalMessages from '@app/i18n/globalMessages';
|
||||
import ErrorPage from '@app/pages/_error';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
|
||||
import { ArrowDownTrayIcon } from '@heroicons/react/24/outline';
|
||||
import {
|
||||
ArrowDownTrayIcon,
|
||||
EyeIcon,
|
||||
EyeSlashIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { MediaStatus } from '@server/constants/media';
|
||||
import type { Collection } from '@server/models/Collection';
|
||||
import axios from 'axios';
|
||||
import { uniq } from 'lodash';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import useSWR from 'swr';
|
||||
|
||||
const messages = defineMessages('components.CollectionDetails', {
|
||||
overview: 'Overview',
|
||||
numberofmovies: '{count} Movies',
|
||||
removefromblocklistpartialcount:
|
||||
'{removeLabel} ({count, plural, one {# movie} other {# movies}})',
|
||||
requestcollection: 'Request Collection',
|
||||
requestcollection4k: 'Request Collection in 4K',
|
||||
});
|
||||
@@ -40,6 +51,9 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
|
||||
const { hasPermission } = useUser();
|
||||
const [requestModal, setRequestModal] = useState(false);
|
||||
const [is4k, setIs4k] = useState(false);
|
||||
const [showBlocklistModal, setShowBlocklistModal] = useState(false);
|
||||
const [isBlocklistUpdating, setIsBlocklistUpdating] = useState(false);
|
||||
const { addToast } = useToasts();
|
||||
|
||||
const returnCollectionDownloadItems = (data: Collection | undefined) => {
|
||||
const [downloadStatus, downloadStatus4k] = [
|
||||
@@ -70,6 +84,63 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
|
||||
const { data: genres } =
|
||||
useSWR<{ id: number; name: string }[]>(`/api/v1/genres/movie`);
|
||||
|
||||
const onClickHideItemBtn = async (): Promise<void> => {
|
||||
setIsBlocklistUpdating(true);
|
||||
|
||||
try {
|
||||
await axios.post(`/api/v1/blocklist/collection/${data?.id}`);
|
||||
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.blocklistSuccess, {
|
||||
title: data?.name,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'success', autoDismiss: true }
|
||||
);
|
||||
|
||||
revalidate();
|
||||
} catch {
|
||||
addToast(intl.formatMessage(globalMessages.blocklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
});
|
||||
}
|
||||
|
||||
setIsBlocklistUpdating(false);
|
||||
setShowBlocklistModal(false);
|
||||
};
|
||||
|
||||
const onClickUnblocklistBtn = async (): Promise<void> => {
|
||||
if (!data) return;
|
||||
|
||||
setIsBlocklistUpdating(true);
|
||||
|
||||
try {
|
||||
await axios.delete(`/api/v1/blocklist/collection/${data.id}`);
|
||||
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.removeFromBlocklistSuccess, {
|
||||
title: data.name,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'success', autoDismiss: true }
|
||||
);
|
||||
|
||||
revalidate();
|
||||
} catch {
|
||||
addToast(intl.formatMessage(globalMessages.blocklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
});
|
||||
}
|
||||
|
||||
setIsBlocklistUpdating(false);
|
||||
};
|
||||
|
||||
const [downloadStatus, downloadStatus4k] = useMemo(() => {
|
||||
const downloadItems = returnCollectionDownloadItems(data);
|
||||
return [downloadItems.downloadStatus, downloadItems.downloadStatus4k];
|
||||
@@ -97,7 +168,17 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
|
||||
let collectionStatus = MediaStatus.UNKNOWN;
|
||||
let collectionStatus4k = MediaStatus.UNKNOWN;
|
||||
|
||||
if (
|
||||
const blocklistedParts = data.parts.filter(
|
||||
(part) =>
|
||||
part.mediaInfo && part.mediaInfo.status === MediaStatus.BLOCKLISTED
|
||||
);
|
||||
const isCollectionBlocklisted = blocklistedParts.length > 0;
|
||||
const isCollectionPartiallyBlocklisted =
|
||||
blocklistedParts.length > 0 && blocklistedParts.length < data.parts.length;
|
||||
|
||||
if (isCollectionBlocklisted) {
|
||||
collectionStatus = MediaStatus.BLOCKLISTED;
|
||||
} else if (
|
||||
data.parts.every(
|
||||
(part) =>
|
||||
part.mediaInfo && part.mediaInfo.status === MediaStatus.AVAILABLE
|
||||
@@ -152,6 +233,11 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
|
||||
part.mediaInfo.status4k === MediaStatus.UNKNOWN
|
||||
).length > 0;
|
||||
|
||||
const blocklistVisibility = hasPermission(
|
||||
[Permission.MANAGE_BLOCKLIST, Permission.VIEW_BLOCKLIST],
|
||||
{ type: 'or' }
|
||||
);
|
||||
|
||||
const collectionAttributes: React.ReactNode[] = [];
|
||||
|
||||
collectionAttributes.push(
|
||||
@@ -188,11 +274,6 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
const blocklistVisibility = hasPermission(
|
||||
[Permission.MANAGE_BLOCKLIST, Permission.VIEW_BLOCKLIST],
|
||||
{ type: 'or' }
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="media-page"
|
||||
@@ -231,6 +312,15 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
|
||||
}}
|
||||
onCancel={() => setRequestModal(false)}
|
||||
/>
|
||||
<BlocklistModal
|
||||
tmdbId={data.id}
|
||||
type="collection"
|
||||
show={showBlocklistModal}
|
||||
onCancel={() => setShowBlocklistModal(false)}
|
||||
onComplete={onClickHideItemBtn}
|
||||
isUpdating={isBlocklistUpdating}
|
||||
/>
|
||||
|
||||
<div className="media-header">
|
||||
<div className="media-poster">
|
||||
<CachedImage
|
||||
@@ -254,6 +344,11 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
|
||||
status={collectionStatus}
|
||||
downloadItem={downloadStatus}
|
||||
title={titles}
|
||||
statusLabelOverride={
|
||||
isCollectionPartiallyBlocklisted
|
||||
? intl.formatMessage(globalMessages.partiallyblocklisted)
|
||||
: undefined
|
||||
}
|
||||
inProgress={data.parts.some(
|
||||
(part) => (part.mediaInfo?.downloadStatus ?? []).length > 0
|
||||
)}
|
||||
@@ -292,6 +387,48 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
|
||||
</span>
|
||||
</div>
|
||||
<div className="media-actions">
|
||||
{hasPermission([Permission.MANAGE_BLOCKLIST], { type: 'or' }) &&
|
||||
(isCollectionBlocklisted ? (
|
||||
<Tooltip
|
||||
content={
|
||||
blocklistedParts.length === data.parts.length
|
||||
? intl.formatMessage(globalMessages.removefromBlocklist)
|
||||
: intl.formatMessage(
|
||||
messages.removefromblocklistpartialcount,
|
||||
{
|
||||
removeLabel: intl.formatMessage(
|
||||
globalMessages.removefromBlocklist
|
||||
),
|
||||
count: blocklistedParts.length,
|
||||
}
|
||||
)
|
||||
}
|
||||
>
|
||||
<Button
|
||||
buttonType="ghost"
|
||||
className="z-40 mr-2"
|
||||
buttonSize="md"
|
||||
onClick={onClickUnblocklistBtn}
|
||||
disabled={isBlocklistUpdating}
|
||||
>
|
||||
<EyeIcon />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip
|
||||
content={intl.formatMessage(globalMessages.addToBlocklist)}
|
||||
>
|
||||
<Button
|
||||
buttonType="ghost"
|
||||
className="z-40 mr-2"
|
||||
buttonSize="md"
|
||||
onClick={() => setShowBlocklistModal(true)}
|
||||
disabled={isBlocklistUpdating}
|
||||
>
|
||||
<EyeSlashIcon />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
))}
|
||||
{(hasRequestable || hasRequestable4k) && (
|
||||
<ButtonWithDropdown
|
||||
buttonType="primary"
|
||||
@@ -349,8 +486,9 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
|
||||
isEmpty={data.parts.length === 0}
|
||||
items={data.parts
|
||||
.filter((title) => {
|
||||
if (!blocklistVisibility)
|
||||
if (!blocklistVisibility) {
|
||||
return title.mediaInfo?.status !== MediaStatus.BLOCKLISTED;
|
||||
}
|
||||
return title;
|
||||
})
|
||||
.map((title) => (
|
||||
@@ -365,6 +503,7 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
|
||||
userScore={title.voteAverage}
|
||||
year={title.releaseDate}
|
||||
mediaType={title.mediaType}
|
||||
mutateParent={revalidate}
|
||||
/>
|
||||
))}
|
||||
/>
|
||||
|
||||
@@ -31,6 +31,7 @@ interface StatusBadgeProps {
|
||||
tmdbId?: number;
|
||||
mediaType?: 'movie' | 'tv';
|
||||
title?: string | string[];
|
||||
statusLabelOverride?: string;
|
||||
}
|
||||
|
||||
const StatusBadge = ({
|
||||
@@ -43,6 +44,7 @@ const StatusBadge = ({
|
||||
tmdbId,
|
||||
mediaType,
|
||||
title,
|
||||
statusLabelOverride,
|
||||
}: StatusBadgeProps) => {
|
||||
const intl = useIntl();
|
||||
const { hasPermission } = useUser();
|
||||
@@ -364,7 +366,9 @@ const StatusBadge = ({
|
||||
<Tooltip content={mediaLinkDescription}>
|
||||
<Badge badgeType="danger" href={mediaLink}>
|
||||
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
|
||||
status: intl.formatMessage(globalMessages.blocklisted),
|
||||
status:
|
||||
statusLabelOverride ??
|
||||
intl.formatMessage(globalMessages.blocklisted),
|
||||
})}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
|
||||
@@ -175,12 +175,16 @@ const TitleCard = ({
|
||||
|
||||
if (topNode) {
|
||||
try {
|
||||
await axios.post('/api/v1/blocklist', {
|
||||
tmdbId: id,
|
||||
mediaType,
|
||||
title,
|
||||
user: user?.id,
|
||||
});
|
||||
if (mediaType === 'collection') {
|
||||
await axios.post(`/api/v1/blocklist/collection/${id}`);
|
||||
} else {
|
||||
await axios.post('/api/v1/blocklist', {
|
||||
tmdbId: id,
|
||||
mediaType,
|
||||
title,
|
||||
user: user?.id,
|
||||
});
|
||||
}
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.blocklistSuccess, {
|
||||
@@ -191,6 +195,9 @@ const TitleCard = ({
|
||||
{ appearance: 'success', autoDismiss: true }
|
||||
);
|
||||
setCurrentStatus(MediaStatus.BLOCKLISTED);
|
||||
if (mutateParent) {
|
||||
mutateParent();
|
||||
}
|
||||
} catch (e) {
|
||||
if (e?.response?.status === 412) {
|
||||
addToast(
|
||||
@@ -225,22 +232,57 @@ const TitleCard = ({
|
||||
const topNode = cardRef.current;
|
||||
|
||||
if (topNode) {
|
||||
const res = await axios.delete(
|
||||
`/api/v1/blocklist/${id}?mediaType=${mediaType}`
|
||||
);
|
||||
try {
|
||||
if (mediaType === 'collection') {
|
||||
const res = await axios.delete(`/api/v1/blocklist/collection/${id}`);
|
||||
|
||||
if (res.status === 204) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.removeFromBlocklistSuccess, {
|
||||
title,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'success', autoDismiss: true }
|
||||
);
|
||||
setCurrentStatus(MediaStatus.UNKNOWN);
|
||||
} else {
|
||||
if (res.status === 204) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.removeFromBlocklistSuccess, {
|
||||
title,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'success', autoDismiss: true }
|
||||
);
|
||||
setCurrentStatus(MediaStatus.UNKNOWN);
|
||||
if (mutateParent) {
|
||||
mutateParent();
|
||||
}
|
||||
} else {
|
||||
addToast(intl.formatMessage(globalMessages.blocklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const res = await axios.delete(
|
||||
`/api/v1/blocklist/${id}?mediaType=${mediaType}`
|
||||
);
|
||||
|
||||
if (res.status === 204) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.removeFromBlocklistSuccess, {
|
||||
title,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'success', autoDismiss: true }
|
||||
);
|
||||
setCurrentStatus(MediaStatus.UNKNOWN);
|
||||
if (mutateParent) {
|
||||
mutateParent();
|
||||
}
|
||||
} else {
|
||||
addToast(intl.formatMessage(globalMessages.blocklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
addToast(intl.formatMessage(globalMessages.blocklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
|
||||
@@ -60,6 +60,7 @@ const globalMessages = defineMessages('i18n', {
|
||||
resolved: 'Resolved',
|
||||
blocklist: 'Blocklist',
|
||||
blocklisted: 'Blocklisted',
|
||||
partiallyblocklisted: 'Partially Blocklisted',
|
||||
blocklistSuccess: '<strong>{title}</strong> was successfully blocklisted.',
|
||||
blocklistError: 'Something went wrong. Please try again.',
|
||||
blocklistDuplicateError:
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"components.Blocklist.showAllBlocklisted": "Show All Blocklisted Media",
|
||||
"components.CollectionDetails.numberofmovies": "{count} Movies",
|
||||
"components.CollectionDetails.overview": "Overview",
|
||||
"components.CollectionDetails.removefromblocklistpartialcount": "{removeLabel} ({count, plural, one {# movie} other {# movies}})",
|
||||
"components.CollectionDetails.requestcollection": "Request Collection",
|
||||
"components.CollectionDetails.requestcollection4k": "Request Collection in 4K",
|
||||
"components.Discover.CreateSlider.addSlider": "Add Slider",
|
||||
@@ -1601,6 +1602,7 @@
|
||||
"i18n.notrequested": "Not Requested",
|
||||
"i18n.open": "Open",
|
||||
"i18n.partiallyavailable": "Partially Available",
|
||||
"i18n.partiallyblocklisted": "Partially Blocklisted",
|
||||
"i18n.pending": "Pending",
|
||||
"i18n.previous": "Previous",
|
||||
"i18n.processing": "Processing",
|
||||
|
||||
Reference in New Issue
Block a user