Merge upstream/develop
This commit is contained in:
@@ -76,6 +76,15 @@ export interface SonarrSeries {
|
||||
ignoreEpisodesWithoutFiles?: boolean;
|
||||
searchForMissingEpisodes?: boolean;
|
||||
};
|
||||
statistics: {
|
||||
seasonCount: number;
|
||||
episodeFileCount: number;
|
||||
episodeCount: number;
|
||||
totalEpisodeCount: number;
|
||||
sizeOnDisk: number;
|
||||
releaseGroups: string[];
|
||||
percentOfEpisodes: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AddSeriesOptions {
|
||||
@@ -116,6 +125,16 @@ class SonarrAPI extends ServarrBase<{
|
||||
}
|
||||
}
|
||||
|
||||
public async getSeriesById(id: number): Promise<SonarrSeries> {
|
||||
try {
|
||||
const response = await this.axios.get<SonarrSeries>(`/series/${id}`);
|
||||
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
throw new Error(`[Sonarr] Failed to retrieve series by ID: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async getSeriesByTitle(title: string): Promise<SonarrSeries[]> {
|
||||
try {
|
||||
const response = await this.axios.get<SonarrSeries[]>('/series/lookup', {
|
||||
|
||||
@@ -65,6 +65,8 @@ interface DiscoverMovieOptions {
|
||||
withRuntimeLte?: string;
|
||||
voteAverageGte?: string;
|
||||
voteAverageLte?: string;
|
||||
voteCountGte?: string;
|
||||
voteCountLte?: string;
|
||||
originalLanguage?: string;
|
||||
genre?: string;
|
||||
studio?: string;
|
||||
@@ -83,6 +85,8 @@ interface DiscoverTvOptions {
|
||||
withRuntimeLte?: string;
|
||||
voteAverageGte?: string;
|
||||
voteAverageLte?: string;
|
||||
voteCountGte?: string;
|
||||
voteCountLte?: string;
|
||||
includeEmptyReleaseDate?: boolean;
|
||||
originalLanguage?: string;
|
||||
genre?: string;
|
||||
@@ -460,6 +464,8 @@ class TheMovieDb extends ExternalAPI {
|
||||
withRuntimeLte,
|
||||
voteAverageGte,
|
||||
voteAverageLte,
|
||||
voteCountGte,
|
||||
voteCountLte,
|
||||
watchProviders,
|
||||
watchRegion,
|
||||
}: DiscoverMovieOptions = {}): Promise<TmdbSearchMovieResponse> => {
|
||||
@@ -504,6 +510,8 @@ class TheMovieDb extends ExternalAPI {
|
||||
'with_runtime.lte': withRuntimeLte,
|
||||
'vote_average.gte': voteAverageGte,
|
||||
'vote_average.lte': voteAverageLte,
|
||||
'vote_count.gte': voteCountGte,
|
||||
'vote_count.lte': voteCountLte,
|
||||
watch_region: watchRegion,
|
||||
with_watch_providers: watchProviders,
|
||||
},
|
||||
@@ -530,6 +538,8 @@ class TheMovieDb extends ExternalAPI {
|
||||
withRuntimeLte,
|
||||
voteAverageGte,
|
||||
voteAverageLte,
|
||||
voteCountGte,
|
||||
voteCountLte,
|
||||
watchProviders,
|
||||
watchRegion,
|
||||
}: DiscoverTvOptions = {}): Promise<TmdbSearchTvResponse> => {
|
||||
@@ -574,6 +584,8 @@ class TheMovieDb extends ExternalAPI {
|
||||
'with_runtime.lte': withRuntimeLte,
|
||||
'vote_average.gte': voteAverageGte,
|
||||
'vote_average.lte': voteAverageLte,
|
||||
'vote_count.gte': voteCountGte,
|
||||
'vote_count.lte': voteCountLte,
|
||||
with_watch_providers: watchProviders,
|
||||
watch_region: watchRegion,
|
||||
},
|
||||
|
||||
@@ -28,6 +28,18 @@ export interface TmdbTvResult extends TmdbMediaResult {
|
||||
first_air_date: string;
|
||||
}
|
||||
|
||||
export interface TmdbCollectionResult {
|
||||
id: number;
|
||||
media_type: 'collection';
|
||||
title: string;
|
||||
original_title: string;
|
||||
adult: boolean;
|
||||
poster_path?: string;
|
||||
backdrop_path?: string;
|
||||
overview: string;
|
||||
original_language: string;
|
||||
}
|
||||
|
||||
export interface TmdbPersonResult {
|
||||
id: number;
|
||||
name: string;
|
||||
@@ -45,7 +57,12 @@ interface TmdbPaginatedResponse {
|
||||
}
|
||||
|
||||
export interface TmdbSearchMultiResponse extends TmdbPaginatedResponse {
|
||||
results: (TmdbMovieResult | TmdbTvResult | TmdbPersonResult)[];
|
||||
results: (
|
||||
| TmdbMovieResult
|
||||
| TmdbTvResult
|
||||
| TmdbPersonResult
|
||||
| TmdbCollectionResult
|
||||
)[];
|
||||
}
|
||||
|
||||
export interface TmdbSearchMovieResponse extends TmdbPaginatedResponse {
|
||||
|
||||
@@ -20,6 +20,8 @@ export enum DiscoverSliderType {
|
||||
TMDB_SEARCH,
|
||||
TMDB_STUDIO,
|
||||
TMDB_NETWORK,
|
||||
TMDB_MOVIE_STREAMING_SERVICES,
|
||||
TMDB_TV_STREAMING_SERVICES,
|
||||
}
|
||||
|
||||
export const defaultSliders: Partial<DiscoverSlider>[] = [
|
||||
|
||||
@@ -704,7 +704,7 @@ export class MediaRequest {
|
||||
|
||||
let rootFolder = radarrSettings.activeDirectory;
|
||||
let qualityProfile = radarrSettings.activeProfileId;
|
||||
let tags = radarrSettings.tags;
|
||||
let tags = radarrSettings.tags ? [...radarrSettings.tags] : [];
|
||||
|
||||
if (
|
||||
this.rootFolder &&
|
||||
@@ -764,6 +764,38 @@ export class MediaRequest {
|
||||
return;
|
||||
}
|
||||
|
||||
if (radarrSettings.tagRequests) {
|
||||
let userTag = (await radarr.getTags()).find((v) =>
|
||||
v.label.startsWith(this.requestedBy.id + ' - ')
|
||||
);
|
||||
if (!userTag) {
|
||||
logger.info(`Requester has no active tag. Creating new`, {
|
||||
label: 'Media Request',
|
||||
requestId: this.id,
|
||||
mediaId: this.media.id,
|
||||
userId: this.requestedBy.id,
|
||||
newTag:
|
||||
this.requestedBy.id + ' - ' + this.requestedBy.displayName,
|
||||
});
|
||||
userTag = await radarr.createTag({
|
||||
label: this.requestedBy.id + ' - ' + this.requestedBy.displayName,
|
||||
});
|
||||
}
|
||||
if (userTag.id) {
|
||||
if (!tags?.find((v) => v === userTag?.id)) {
|
||||
tags?.push(userTag.id);
|
||||
}
|
||||
} else {
|
||||
logger.warn(`Requester has no tag and failed to add one`, {
|
||||
label: 'Media Request',
|
||||
requestId: this.id,
|
||||
mediaId: this.media.id,
|
||||
userId: this.requestedBy.id,
|
||||
radarrServer: radarrSettings.hostname + ':' + radarrSettings.port,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
media[this.is4k ? 'status4k' : 'status'] === MediaStatus.AVAILABLE
|
||||
) {
|
||||
@@ -970,7 +1002,11 @@ export class MediaRequest {
|
||||
let tags =
|
||||
seriesType === 'anime'
|
||||
? sonarrSettings.animeTags
|
||||
: sonarrSettings.tags;
|
||||
? [...sonarrSettings.animeTags]
|
||||
: []
|
||||
: sonarrSettings.tags
|
||||
? [...sonarrSettings.tags]
|
||||
: [];
|
||||
|
||||
if (
|
||||
this.rootFolder &&
|
||||
@@ -1022,6 +1058,38 @@ export class MediaRequest {
|
||||
});
|
||||
}
|
||||
|
||||
if (sonarrSettings.tagRequests) {
|
||||
let userTag = (await sonarr.getTags()).find((v) =>
|
||||
v.label.startsWith(this.requestedBy.id + ' - ')
|
||||
);
|
||||
if (!userTag) {
|
||||
logger.info(`Requester has no active tag. Creating new`, {
|
||||
label: 'Media Request',
|
||||
requestId: this.id,
|
||||
mediaId: this.media.id,
|
||||
userId: this.requestedBy.id,
|
||||
newTag:
|
||||
this.requestedBy.id + ' - ' + this.requestedBy.displayName,
|
||||
});
|
||||
userTag = await sonarr.createTag({
|
||||
label: this.requestedBy.id + ' - ' + this.requestedBy.displayName,
|
||||
});
|
||||
}
|
||||
if (userTag.id) {
|
||||
if (!tags?.find((v) => v === userTag?.id)) {
|
||||
tags?.push(userTag.id);
|
||||
}
|
||||
} else {
|
||||
logger.warn(`Requester has no tag and failed to add one`, {
|
||||
label: 'Media Request',
|
||||
requestId: this.id,
|
||||
mediaId: this.media.id,
|
||||
userId: this.requestedBy.id,
|
||||
sonarrServer: sonarrSettings.hostname + ':' + sonarrSettings.port,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const sonarrSeriesOptions: AddSeriesOptions = {
|
||||
profileId: qualityProfile,
|
||||
languageProfileId: languageProfile,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { PlexMetadata } from '@server/api/plexapi';
|
||||
import PlexAPI from '@server/api/plexapi';
|
||||
import type { RadarrMovie } from '@server/api/servarr/radarr';
|
||||
import RadarrAPI from '@server/api/servarr/radarr';
|
||||
import type { SonarrSeason } from '@server/api/servarr/sonarr';
|
||||
import type { SonarrSeason, SonarrSeries } from '@server/api/servarr/sonarr';
|
||||
import SonarrAPI from '@server/api/servarr/sonarr';
|
||||
import { MediaStatus } from '@server/constants/media';
|
||||
import { getRepository } from '@server/datasource';
|
||||
@@ -47,158 +48,150 @@ class AvailabilitySync {
|
||||
|
||||
try {
|
||||
for await (const media of this.loadAvailableMediaPaginated(pageSize)) {
|
||||
try {
|
||||
if (!this.running) {
|
||||
throw new Error('Job aborted');
|
||||
}
|
||||
if (!this.running) {
|
||||
throw new Error('Job aborted');
|
||||
}
|
||||
|
||||
const mediaExists = await this.mediaExists(media);
|
||||
const mediaExists = await this.mediaExists(media);
|
||||
|
||||
//We can not delete media so if both versions do not exist, we will change both columns to unknown or null
|
||||
if (!mediaExists) {
|
||||
if (
|
||||
media.status !== MediaStatus.UNKNOWN ||
|
||||
media.status4k !== MediaStatus.UNKNOWN
|
||||
) {
|
||||
const request = await requestRepository.find({
|
||||
relations: {
|
||||
media: true,
|
||||
},
|
||||
where: { media: { id: media.id } },
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`${
|
||||
media.mediaType === 'tv' ? media.tvdbId : media.tmdbId
|
||||
} does not exist in any of your media instances. We will change its status to unknown.`,
|
||||
{ label: 'AvailabilitySync' }
|
||||
);
|
||||
|
||||
await mediaRepository.update(media.id, {
|
||||
status: MediaStatus.UNKNOWN,
|
||||
status4k: MediaStatus.UNKNOWN,
|
||||
serviceId: null,
|
||||
serviceId4k: null,
|
||||
externalServiceId: null,
|
||||
externalServiceId4k: null,
|
||||
externalServiceSlug: null,
|
||||
externalServiceSlug4k: null,
|
||||
ratingKey: null,
|
||||
ratingKey4k: null,
|
||||
});
|
||||
|
||||
await requestRepository.remove(request);
|
||||
}
|
||||
}
|
||||
|
||||
if (media.mediaType === 'tv') {
|
||||
// ok, the show itself exists, but do all it's seasons?
|
||||
const seasons = await seasonRepository.find({
|
||||
where: [
|
||||
{ status: MediaStatus.AVAILABLE, media: { id: media.id } },
|
||||
{
|
||||
status: MediaStatus.PARTIALLY_AVAILABLE,
|
||||
media: { id: media.id },
|
||||
},
|
||||
{ status4k: MediaStatus.AVAILABLE, media: { id: media.id } },
|
||||
{
|
||||
status4k: MediaStatus.PARTIALLY_AVAILABLE,
|
||||
media: { id: media.id },
|
||||
},
|
||||
],
|
||||
// We can not delete media so if both versions do not exist, we will change both columns to unknown or null
|
||||
if (!mediaExists) {
|
||||
if (
|
||||
media.status !== MediaStatus.UNKNOWN ||
|
||||
media.status4k !== MediaStatus.UNKNOWN
|
||||
) {
|
||||
const request = await requestRepository.find({
|
||||
relations: {
|
||||
media: true,
|
||||
},
|
||||
where: { media: { id: media.id } },
|
||||
});
|
||||
|
||||
let didDeleteSeasons = false;
|
||||
for (const season of seasons) {
|
||||
if (
|
||||
!mediaExists &&
|
||||
(season.status !== MediaStatus.UNKNOWN ||
|
||||
season.status4k !== MediaStatus.UNKNOWN)
|
||||
) {
|
||||
await seasonRepository.update(
|
||||
{ id: season.id },
|
||||
logger.info(
|
||||
`Media ID ${media.id} does not exist in any of your media instances. Status will be changed to unknown.`,
|
||||
{ label: 'AvailabilitySync' }
|
||||
);
|
||||
|
||||
await mediaRepository.update(media.id, {
|
||||
status: MediaStatus.UNKNOWN,
|
||||
status4k: MediaStatus.UNKNOWN,
|
||||
serviceId: null,
|
||||
serviceId4k: null,
|
||||
externalServiceId: null,
|
||||
externalServiceId4k: null,
|
||||
externalServiceSlug: null,
|
||||
externalServiceSlug4k: null,
|
||||
ratingKey: null,
|
||||
ratingKey4k: null,
|
||||
});
|
||||
|
||||
await requestRepository.remove(request);
|
||||
}
|
||||
}
|
||||
|
||||
if (media.mediaType === 'tv') {
|
||||
// ok, the show itself exists, but do all it's seasons?
|
||||
const seasons = await seasonRepository.find({
|
||||
where: [
|
||||
{ status: MediaStatus.AVAILABLE, media: { id: media.id } },
|
||||
{
|
||||
status: MediaStatus.PARTIALLY_AVAILABLE,
|
||||
media: { id: media.id },
|
||||
},
|
||||
{ status4k: MediaStatus.AVAILABLE, media: { id: media.id } },
|
||||
{
|
||||
status4k: MediaStatus.PARTIALLY_AVAILABLE,
|
||||
media: { id: media.id },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let didDeleteSeasons = false;
|
||||
for (const season of seasons) {
|
||||
if (
|
||||
!mediaExists &&
|
||||
(season.status !== MediaStatus.UNKNOWN ||
|
||||
season.status4k !== MediaStatus.UNKNOWN)
|
||||
) {
|
||||
await seasonRepository.update(
|
||||
{ id: season.id },
|
||||
{
|
||||
status: MediaStatus.UNKNOWN,
|
||||
status4k: MediaStatus.UNKNOWN,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const seasonExists = await this.seasonExists(media, season);
|
||||
|
||||
if (!seasonExists) {
|
||||
logger.info(
|
||||
`Removing season ${season.seasonNumber}, media ID ${media.id} because it does not exist in any of your media instances.`,
|
||||
{ label: 'AvailabilitySync' }
|
||||
);
|
||||
|
||||
if (
|
||||
season.status !== MediaStatus.UNKNOWN ||
|
||||
season.status4k !== MediaStatus.UNKNOWN
|
||||
) {
|
||||
await seasonRepository.update(
|
||||
{ id: season.id },
|
||||
{
|
||||
status: MediaStatus.UNKNOWN,
|
||||
status4k: MediaStatus.UNKNOWN,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const seasonToBeDeleted = await seasonRequestRepository.findOne(
|
||||
{
|
||||
status: MediaStatus.UNKNOWN,
|
||||
status4k: MediaStatus.UNKNOWN,
|
||||
relations: {
|
||||
request: {
|
||||
media: true,
|
||||
},
|
||||
},
|
||||
where: {
|
||||
request: {
|
||||
media: {
|
||||
id: media.id,
|
||||
},
|
||||
},
|
||||
seasonNumber: season.seasonNumber,
|
||||
},
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const seasonExists = await this.seasonExists(media, season);
|
||||
|
||||
if (!seasonExists) {
|
||||
logger.info(
|
||||
`Removing season ${season.seasonNumber}, media id: ${media.tvdbId} because it does not exist in any of your media instances.`,
|
||||
{ label: 'AvailabilitySync' }
|
||||
);
|
||||
|
||||
if (
|
||||
season.status !== MediaStatus.UNKNOWN ||
|
||||
season.status4k !== MediaStatus.UNKNOWN
|
||||
) {
|
||||
await seasonRepository.update(
|
||||
{ id: season.id },
|
||||
{
|
||||
status: MediaStatus.UNKNOWN,
|
||||
status4k: MediaStatus.UNKNOWN,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const seasonToBeDeleted =
|
||||
await seasonRequestRepository.findOne({
|
||||
relations: {
|
||||
request: {
|
||||
media: true,
|
||||
},
|
||||
},
|
||||
where: {
|
||||
request: {
|
||||
media: {
|
||||
id: media.id,
|
||||
},
|
||||
},
|
||||
seasonNumber: season.seasonNumber,
|
||||
},
|
||||
});
|
||||
|
||||
if (seasonToBeDeleted) {
|
||||
await seasonRequestRepository.remove(seasonToBeDeleted);
|
||||
}
|
||||
|
||||
didDeleteSeasons = true;
|
||||
if (seasonToBeDeleted) {
|
||||
await seasonRequestRepository.remove(seasonToBeDeleted);
|
||||
}
|
||||
|
||||
didDeleteSeasons = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (didDeleteSeasons) {
|
||||
if (
|
||||
media.status === MediaStatus.AVAILABLE ||
|
||||
media.status4k === MediaStatus.AVAILABLE
|
||||
) {
|
||||
logger.info(
|
||||
`Marking media id: ${media.tvdbId} as PARTIALLY_AVAILABLE because we deleted some of its seasons.`,
|
||||
{ label: 'AvailabilitySync' }
|
||||
);
|
||||
if (didDeleteSeasons) {
|
||||
if (
|
||||
media.status === MediaStatus.AVAILABLE ||
|
||||
media.status4k === MediaStatus.AVAILABLE
|
||||
) {
|
||||
logger.info(
|
||||
`Marking media ID ${media.id} as PARTIALLY_AVAILABLE because season removal has occurred.`,
|
||||
{ label: 'AvailabilitySync' }
|
||||
);
|
||||
|
||||
if (media.status === MediaStatus.AVAILABLE) {
|
||||
await mediaRepository.update(media.id, {
|
||||
status: MediaStatus.PARTIALLY_AVAILABLE,
|
||||
});
|
||||
}
|
||||
if (media.status === MediaStatus.AVAILABLE) {
|
||||
await mediaRepository.update(media.id, {
|
||||
status: MediaStatus.PARTIALLY_AVAILABLE,
|
||||
});
|
||||
}
|
||||
|
||||
if (media.status4k === MediaStatus.AVAILABLE) {
|
||||
await mediaRepository.update(media.id, {
|
||||
status4k: MediaStatus.PARTIALLY_AVAILABLE,
|
||||
});
|
||||
}
|
||||
if (media.status4k === MediaStatus.AVAILABLE) {
|
||||
await mediaRepository.update(media.id, {
|
||||
status4k: MediaStatus.PARTIALLY_AVAILABLE,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
logger.error('Failure with media.', {
|
||||
errorMessage: ex.message,
|
||||
label: 'AvailabilitySync',
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
@@ -254,9 +247,9 @@ class AvailabilitySync {
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`${media.tmdbId} does not exist in your ${is4k ? '4k' : 'non-4k'} ${
|
||||
isTVType ? 'sonarr' : 'radarr'
|
||||
} and plex instance. We will change its status to unknown.`,
|
||||
`Media ID ${media.id} does not exist in your ${is4k ? '4k' : 'non-4k'} ${
|
||||
isTVType ? 'Sonarr' : 'Radarr'
|
||||
} and Plex instance. Status will be changed to unknown.`,
|
||||
{ label: 'AvailabilitySync' }
|
||||
);
|
||||
|
||||
@@ -306,46 +299,70 @@ class AvailabilitySync {
|
||||
apiKey: server.apiKey,
|
||||
url: RadarrAPI.buildUrl(server, '/api/v3'),
|
||||
});
|
||||
const meta = await api.getMovieByTmdbId(media.tmdbId);
|
||||
try {
|
||||
// Check if both exist or if a single non-4k or 4k exists
|
||||
// If both do not exist we will return false
|
||||
|
||||
//check if both exist or if a single non-4k or 4k exists
|
||||
//if both do not exist we will return false
|
||||
if (!server.is4k && !meta.id) {
|
||||
existsInRadarr = false;
|
||||
}
|
||||
let meta: RadarrMovie | undefined;
|
||||
|
||||
if (server.is4k && !meta.id) {
|
||||
existsInRadarr4k = false;
|
||||
if (!server.is4k && media.externalServiceId) {
|
||||
meta = await api.getMovie({ id: media.externalServiceId });
|
||||
}
|
||||
|
||||
if (server.is4k && media.externalServiceId4k) {
|
||||
meta = await api.getMovie({ id: media.externalServiceId4k });
|
||||
}
|
||||
|
||||
if (!server.is4k && (!meta || !meta.hasFile)) {
|
||||
existsInRadarr = false;
|
||||
}
|
||||
|
||||
if (server.is4k && (!meta || !meta.hasFile)) {
|
||||
existsInRadarr4k = false;
|
||||
}
|
||||
} catch (ex) {
|
||||
logger.debug(
|
||||
`Failure retrieving media ID ${media.id} from your ${
|
||||
!server.is4k ? 'non-4K' : '4K'
|
||||
} Radarr.`,
|
||||
{
|
||||
errorMessage: ex.message,
|
||||
label: 'AvailabilitySync',
|
||||
}
|
||||
);
|
||||
if (!server.is4k) {
|
||||
existsInRadarr = false;
|
||||
}
|
||||
|
||||
if (server.is4k) {
|
||||
existsInRadarr4k = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (existsInRadarr && existsInRadarr4k) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!existsInRadarr && existsInPlex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!existsInRadarr4k && existsInPlex4k) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//if only a single non-4k or 4k exists, then change entity columns accordingly
|
||||
//related media request will then be deleted
|
||||
if (!existsInRadarr && existsInRadarr4k && !existsInPlex) {
|
||||
// If only a single non-4k or 4k exists, then change entity columns accordingly
|
||||
// Related media request will then be deleted
|
||||
if (
|
||||
!existsInRadarr &&
|
||||
(existsInRadarr4k || existsInPlex4k) &&
|
||||
!existsInPlex
|
||||
) {
|
||||
if (media.status !== MediaStatus.UNKNOWN) {
|
||||
this.mediaUpdater(media, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (existsInRadarr && !existsInRadarr4k && !existsInPlex4k) {
|
||||
if (
|
||||
(existsInRadarr || existsInPlex) &&
|
||||
!existsInRadarr4k &&
|
||||
!existsInPlex4k
|
||||
) {
|
||||
if (media.status4k !== MediaStatus.UNKNOWN) {
|
||||
this.mediaUpdater(media, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (existsInRadarr || existsInRadarr4k) {
|
||||
if (existsInRadarr || existsInRadarr4k || existsInPlex || existsInPlex4k) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -357,10 +374,6 @@ class AvailabilitySync {
|
||||
existsInPlex: boolean,
|
||||
existsInPlex4k: boolean
|
||||
): Promise<boolean> {
|
||||
if (!media.tvdbId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let existsInSonarr = true;
|
||||
let existsInSonarr4k = true;
|
||||
|
||||
@@ -369,49 +382,75 @@ class AvailabilitySync {
|
||||
apiKey: server.apiKey,
|
||||
url: SonarrAPI.buildUrl(server, '/api/v3'),
|
||||
});
|
||||
try {
|
||||
// Check if both exist or if a single non-4k or 4k exists
|
||||
// If both do not exist we will return false
|
||||
|
||||
const meta = await api.getSeriesByTvdbId(media.tvdbId);
|
||||
let meta: SonarrSeries | undefined;
|
||||
|
||||
this.sonarrSeasonsCache[`${server.id}-${media.tvdbId}`] = meta.seasons;
|
||||
if (!server.is4k && media.externalServiceId) {
|
||||
meta = await api.getSeriesById(media.externalServiceId);
|
||||
this.sonarrSeasonsCache[`${server.id}-${media.externalServiceId}`] =
|
||||
meta.seasons;
|
||||
}
|
||||
|
||||
//check if both exist or if a single non-4k or 4k exists
|
||||
//if both do not exist we will return false
|
||||
if (!server.is4k && !meta.id) {
|
||||
existsInSonarr = false;
|
||||
}
|
||||
if (server.is4k && media.externalServiceId4k) {
|
||||
meta = await api.getSeriesById(media.externalServiceId4k);
|
||||
this.sonarrSeasonsCache[`${server.id}-${media.externalServiceId4k}`] =
|
||||
meta.seasons;
|
||||
}
|
||||
|
||||
if (server.is4k && !meta.id) {
|
||||
existsInSonarr4k = false;
|
||||
if (!server.is4k && (!meta || meta.statistics.episodeFileCount === 0)) {
|
||||
existsInSonarr = false;
|
||||
}
|
||||
|
||||
if (server.is4k && (!meta || meta.statistics.episodeFileCount === 0)) {
|
||||
existsInSonarr4k = false;
|
||||
}
|
||||
} catch (ex) {
|
||||
logger.debug(
|
||||
`Failure retrieving media ID ${media.id} from your ${
|
||||
!server.is4k ? 'non-4K' : '4K'
|
||||
} Sonarr.`,
|
||||
{
|
||||
errorMessage: ex.message,
|
||||
label: 'AvailabilitySync',
|
||||
}
|
||||
);
|
||||
|
||||
if (!server.is4k) {
|
||||
existsInSonarr = false;
|
||||
}
|
||||
|
||||
if (server.is4k) {
|
||||
existsInSonarr4k = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (existsInSonarr && existsInSonarr4k) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!existsInSonarr && existsInPlex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!existsInSonarr4k && existsInPlex4k) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//if only a single non-4k or 4k exists, then change entity columns accordingly
|
||||
//related media request will then be deleted
|
||||
if (!existsInSonarr && existsInSonarr4k && !existsInPlex) {
|
||||
// If only a single non-4k or 4k exists, then change entity columns accordingly
|
||||
// Related media request will then be deleted
|
||||
if (
|
||||
!existsInSonarr &&
|
||||
(existsInSonarr4k || existsInPlex4k) &&
|
||||
!existsInPlex
|
||||
) {
|
||||
if (media.status !== MediaStatus.UNKNOWN) {
|
||||
this.mediaUpdater(media, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (existsInSonarr && !existsInSonarr4k && !existsInPlex4k) {
|
||||
if (
|
||||
(existsInSonarr || existsInPlex) &&
|
||||
!existsInSonarr4k &&
|
||||
!existsInPlex4k
|
||||
) {
|
||||
if (media.status4k !== MediaStatus.UNKNOWN) {
|
||||
this.mediaUpdater(media, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (existsInSonarr || existsInSonarr4k) {
|
||||
if (existsInSonarr || existsInSonarr4k || existsInPlex || existsInPlex4k) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -424,10 +463,6 @@ class AvailabilitySync {
|
||||
seasonExistsInPlex: boolean,
|
||||
seasonExistsInPlex4k: boolean
|
||||
): Promise<boolean> {
|
||||
if (!media.tvdbId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let seasonExistsInSonarr = true;
|
||||
let seasonExistsInSonarr4k = true;
|
||||
|
||||
@@ -441,35 +476,67 @@ class AvailabilitySync {
|
||||
url: SonarrAPI.buildUrl(server, '/api/v3'),
|
||||
});
|
||||
|
||||
const seasons =
|
||||
this.sonarrSeasonsCache[`${server.id}-${media.tvdbId}`] ??
|
||||
(await api.getSeriesByTvdbId(media.tvdbId)).seasons;
|
||||
this.sonarrSeasonsCache[`${server.id}-${media.tvdbId}`] = seasons;
|
||||
try {
|
||||
// Here we can use the cache we built when we fetched the series with mediaExistsInSonarr
|
||||
// If the cache does not have data, we will fetch with the api route
|
||||
|
||||
const hasMonitoredSeason = seasons.find(
|
||||
({ monitored, seasonNumber }) =>
|
||||
monitored && season.seasonNumber === seasonNumber
|
||||
);
|
||||
let seasons: SonarrSeason[] =
|
||||
this.sonarrSeasonsCache[
|
||||
`${server.id}-${
|
||||
!server.is4k ? media.externalServiceId : media.externalServiceId4k
|
||||
}`
|
||||
];
|
||||
|
||||
if (!server.is4k && !hasMonitoredSeason) {
|
||||
seasonExistsInSonarr = false;
|
||||
if (!server.is4k && media.externalServiceId) {
|
||||
seasons =
|
||||
this.sonarrSeasonsCache[
|
||||
`${server.id}-${media.externalServiceId}`
|
||||
] ?? (await api.getSeriesById(media.externalServiceId)).seasons;
|
||||
this.sonarrSeasonsCache[`${server.id}-${media.externalServiceId}`] =
|
||||
seasons;
|
||||
}
|
||||
|
||||
if (server.is4k && media.externalServiceId4k) {
|
||||
seasons =
|
||||
this.sonarrSeasonsCache[
|
||||
`${server.id}-${media.externalServiceId4k}`
|
||||
] ?? (await api.getSeriesById(media.externalServiceId4k)).seasons;
|
||||
this.sonarrSeasonsCache[`${server.id}-${media.externalServiceId4k}`] =
|
||||
seasons;
|
||||
}
|
||||
|
||||
const seasonIsUnavailable = seasons?.find(
|
||||
({ seasonNumber, statistics }) =>
|
||||
season.seasonNumber === seasonNumber &&
|
||||
statistics?.episodeFileCount === 0
|
||||
);
|
||||
|
||||
if (!server.is4k && seasonIsUnavailable) {
|
||||
seasonExistsInSonarr = false;
|
||||
}
|
||||
|
||||
if (server.is4k && seasonIsUnavailable) {
|
||||
seasonExistsInSonarr4k = false;
|
||||
}
|
||||
} catch (ex) {
|
||||
logger.debug(
|
||||
`Failure retrieving media ID ${media.id} from your ${
|
||||
!server.is4k ? 'non-4K' : '4K'
|
||||
} Sonarr.`,
|
||||
{
|
||||
errorMessage: ex.message,
|
||||
label: 'AvailabilitySync',
|
||||
}
|
||||
);
|
||||
|
||||
if (!server.is4k) {
|
||||
seasonExistsInSonarr = false;
|
||||
}
|
||||
|
||||
if (server.is4k) {
|
||||
seasonExistsInSonarr4k = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (server.is4k && !hasMonitoredSeason) {
|
||||
seasonExistsInSonarr4k = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (seasonExistsInSonarr && seasonExistsInSonarr4k) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!seasonExistsInSonarr && seasonExistsInPlex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!seasonExistsInSonarr4k && seasonExistsInPlex4k) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const seasonToBeDeleted = await seasonRequestRepository.findOne({
|
||||
@@ -489,16 +556,16 @@ class AvailabilitySync {
|
||||
},
|
||||
});
|
||||
|
||||
//if season does not exist, we will change status to unknown and delete related season request
|
||||
//if parent media request is empty(all related seasons have been removed), parent is automatically deleted
|
||||
// If season does not exist, we will change status to unknown and delete related season request
|
||||
// If parent media request is empty(all related seasons have been removed), parent is automatically deleted
|
||||
if (
|
||||
!seasonExistsInSonarr &&
|
||||
seasonExistsInSonarr4k &&
|
||||
(seasonExistsInSonarr4k || seasonExistsInPlex4k) &&
|
||||
!seasonExistsInPlex
|
||||
) {
|
||||
if (season.status !== MediaStatus.UNKNOWN) {
|
||||
logger.info(
|
||||
`${media.tvdbId}, season: ${season.seasonNumber} does not exist in your non-4k sonarr and plex instance. We will change its status to unknown.`,
|
||||
`Season ${season.seasonNumber}, media ID ${media.id} does not exist in your non-4k Sonarr and Plex instance. Status will be changed to unknown.`,
|
||||
{ label: 'AvailabilitySync' }
|
||||
);
|
||||
await seasonRepository.update(season.id, {
|
||||
@@ -511,7 +578,7 @@ class AvailabilitySync {
|
||||
|
||||
if (media.status === MediaStatus.AVAILABLE) {
|
||||
logger.info(
|
||||
`Marking media id: ${media.tvdbId} as PARTIALLY_AVAILABLE because we deleted one of its seasons.`,
|
||||
`Marking media ID ${media.id} as PARTIALLY_AVAILABLE because season removal has occurred.`,
|
||||
{ label: 'AvailabilitySync' }
|
||||
);
|
||||
await mediaRepository.update(media.id, {
|
||||
@@ -522,13 +589,13 @@ class AvailabilitySync {
|
||||
}
|
||||
|
||||
if (
|
||||
seasonExistsInSonarr &&
|
||||
(seasonExistsInSonarr || seasonExistsInPlex) &&
|
||||
!seasonExistsInSonarr4k &&
|
||||
!seasonExistsInPlex4k
|
||||
) {
|
||||
if (season.status4k !== MediaStatus.UNKNOWN) {
|
||||
logger.info(
|
||||
`${media.tvdbId}, season: ${season.seasonNumber} does not exist in your 4k sonarr and plex instance. We will change its status to unknown.`,
|
||||
`Season ${season.seasonNumber}, media ID ${media.id} does not exist in your 4k Sonarr and Plex instance. Status will be changed to unknown.`,
|
||||
{ label: 'AvailabilitySync' }
|
||||
);
|
||||
await seasonRepository.update(season.id, {
|
||||
@@ -541,7 +608,7 @@ class AvailabilitySync {
|
||||
|
||||
if (media.status4k === MediaStatus.AVAILABLE) {
|
||||
logger.info(
|
||||
`Marking media id: ${media.tvdbId} as PARTIALLY_AVAILABLE because we deleted one of its seasons.`,
|
||||
`Marking media ID ${media.id} as PARTIALLY_AVAILABLE because season removal has occurred.`,
|
||||
{ label: 'AvailabilitySync' }
|
||||
);
|
||||
await mediaRepository.update(media.id, {
|
||||
@@ -551,7 +618,12 @@ class AvailabilitySync {
|
||||
}
|
||||
}
|
||||
|
||||
if (seasonExistsInSonarr || seasonExistsInSonarr4k) {
|
||||
if (
|
||||
seasonExistsInSonarr ||
|
||||
seasonExistsInSonarr4k ||
|
||||
seasonExistsInPlex ||
|
||||
seasonExistsInPlex4k
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -565,7 +637,7 @@ class AvailabilitySync {
|
||||
let existsInPlex = false;
|
||||
let existsInPlex4k = false;
|
||||
|
||||
//check each plex instance to see if media exists
|
||||
// Check each plex instance to see if media exists
|
||||
try {
|
||||
if (ratingKey) {
|
||||
const meta = await this.plexClient?.getMetadata(ratingKey);
|
||||
@@ -573,6 +645,7 @@ class AvailabilitySync {
|
||||
existsInPlex = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ratingKey4k) {
|
||||
const meta4k = await this.plexClient?.getMetadata(ratingKey4k);
|
||||
if (meta4k) {
|
||||
@@ -580,18 +653,17 @@ class AvailabilitySync {
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
// TODO: oof, not the nicest way of handling this, but plex-api does not leave us with any other options...
|
||||
if (!ex.message.includes('response code: 404')) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
//base case for if both media versions exist in plex
|
||||
// Base case if both media versions exist in plex
|
||||
if (existsInPlex && existsInPlex4k) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//we then check radarr or sonarr has that specific media. If not, then we will move to delete
|
||||
//if a non-4k or 4k version exists in at least one of the instances, we will only update that specific version
|
||||
// We then check radarr or sonarr has that specific media. If not, then we will move to delete
|
||||
// If a non-4k or 4k version exists in at least one of the instances, we will only update that specific version
|
||||
if (media.mediaType === 'movie') {
|
||||
const existsInRadarr = await this.mediaExistsInRadarr(
|
||||
media,
|
||||
@@ -599,10 +671,10 @@ class AvailabilitySync {
|
||||
existsInPlex4k
|
||||
);
|
||||
|
||||
//if true, media exists in at least one radarr or plex instance.
|
||||
// If true, media exists in at least one radarr or plex instance.
|
||||
if (existsInRadarr) {
|
||||
logger.warn(
|
||||
`${media.tmdbId} exists in at least one radarr or plex instance. Media will be updated if set to available.`,
|
||||
`${media.id} exists in at least one Radarr or Plex instance. Media will be updated if set to available.`,
|
||||
{
|
||||
label: 'AvailabilitySync',
|
||||
}
|
||||
@@ -619,10 +691,10 @@ class AvailabilitySync {
|
||||
existsInPlex4k
|
||||
);
|
||||
|
||||
//if true, media exists in at least one sonarr or plex instance.
|
||||
// If true, media exists in at least one sonarr or plex instance.
|
||||
if (existsInSonarr) {
|
||||
logger.warn(
|
||||
`${media.tvdbId} exists in at least one sonarr or plex instance. Media will be updated if set to available.`,
|
||||
`${media.id} exists in at least one Sonarr or Plex instance. Media will be updated if set to available.`,
|
||||
{
|
||||
label: 'AvailabilitySync',
|
||||
}
|
||||
@@ -672,7 +744,7 @@ class AvailabilitySync {
|
||||
}
|
||||
}
|
||||
|
||||
//base case for if both season versions exist in plex
|
||||
// Base case if both season versions exist in plex
|
||||
if (seasonExistsInPlex && seasonExistsInPlex4k) {
|
||||
return true;
|
||||
}
|
||||
@@ -686,7 +758,7 @@ class AvailabilitySync {
|
||||
|
||||
if (existsInSonarr) {
|
||||
logger.warn(
|
||||
`${media.tvdbId}, season: ${season.seasonNumber} exists in at least one sonarr or plex instance. Media will be updated if set to available.`,
|
||||
`Season ${season.seasonNumber}, media ID ${media.id} exists in at least one Sonarr or Plex instance. Media will be updated if set to available.`,
|
||||
{
|
||||
label: 'AvailabilitySync',
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ export interface DVRSettings {
|
||||
externalUrl?: string;
|
||||
syncEnabled: boolean;
|
||||
preventSearch: boolean;
|
||||
tagRequests: boolean;
|
||||
}
|
||||
|
||||
export interface RadarrSettings extends DVRSettings {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type {
|
||||
TmdbCollectionResult,
|
||||
TmdbMovieDetails,
|
||||
TmdbMovieResult,
|
||||
TmdbPersonDetails,
|
||||
@@ -9,7 +10,7 @@ import type {
|
||||
import { MediaType as MainMediaType } from '@server/constants/media';
|
||||
import type Media from '@server/entity/Media';
|
||||
|
||||
export type MediaType = 'tv' | 'movie' | 'person';
|
||||
export type MediaType = 'tv' | 'movie' | 'person' | 'collection';
|
||||
|
||||
interface SearchResult {
|
||||
id: number;
|
||||
@@ -43,6 +44,18 @@ export interface TvResult extends SearchResult {
|
||||
firstAirDate: string;
|
||||
}
|
||||
|
||||
export interface CollectionResult {
|
||||
id: number;
|
||||
mediaType: 'collection';
|
||||
title: string;
|
||||
originalTitle: string;
|
||||
adult: boolean;
|
||||
posterPath?: string;
|
||||
backdropPath?: string;
|
||||
overview: string;
|
||||
originalLanguage: string;
|
||||
}
|
||||
|
||||
export interface PersonResult {
|
||||
id: number;
|
||||
name: string;
|
||||
@@ -53,7 +66,7 @@ export interface PersonResult {
|
||||
knownFor: (MovieResult | TvResult)[];
|
||||
}
|
||||
|
||||
export type Results = MovieResult | TvResult | PersonResult;
|
||||
export type Results = MovieResult | TvResult | PersonResult | CollectionResult;
|
||||
|
||||
export const mapMovieResult = (
|
||||
movieResult: TmdbMovieResult,
|
||||
@@ -99,6 +112,20 @@ export const mapTvResult = (
|
||||
mediaInfo: media,
|
||||
});
|
||||
|
||||
export const mapCollectionResult = (
|
||||
collectionResult: TmdbCollectionResult
|
||||
): CollectionResult => ({
|
||||
id: collectionResult.id,
|
||||
mediaType: collectionResult.media_type || 'collection',
|
||||
adult: collectionResult.adult,
|
||||
originalLanguage: collectionResult.original_language,
|
||||
originalTitle: collectionResult.original_title,
|
||||
title: collectionResult.title,
|
||||
overview: collectionResult.overview,
|
||||
backdropPath: collectionResult.backdrop_path,
|
||||
posterPath: collectionResult.poster_path,
|
||||
});
|
||||
|
||||
export const mapPersonResult = (
|
||||
personResult: TmdbPersonResult
|
||||
): PersonResult => ({
|
||||
@@ -118,7 +145,12 @@ export const mapPersonResult = (
|
||||
});
|
||||
|
||||
export const mapSearchResults = (
|
||||
results: (TmdbMovieResult | TmdbTvResult | TmdbPersonResult)[],
|
||||
results: (
|
||||
| TmdbMovieResult
|
||||
| TmdbTvResult
|
||||
| TmdbPersonResult
|
||||
| TmdbCollectionResult
|
||||
)[],
|
||||
media?: Media[]
|
||||
): Results[] =>
|
||||
results.map((result) => {
|
||||
@@ -139,6 +171,8 @@ export const mapSearchResults = (
|
||||
req.tmdbId === result.id && req.mediaType === MainMediaType.TV
|
||||
)
|
||||
);
|
||||
case 'collection':
|
||||
return mapCollectionResult(result);
|
||||
default:
|
||||
return mapPersonResult(result);
|
||||
}
|
||||
|
||||
@@ -15,12 +15,13 @@ import { getSettings } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
import { mapProductionCompany } from '@server/models/Movie';
|
||||
import {
|
||||
mapCollectionResult,
|
||||
mapMovieResult,
|
||||
mapPersonResult,
|
||||
mapTvResult,
|
||||
} from '@server/models/Search';
|
||||
import { mapNetwork } from '@server/models/Tv';
|
||||
import { isMovie, isPerson } from '@server/utils/typeHelpers';
|
||||
import { isCollection, isMovie, isPerson } from '@server/utils/typeHelpers';
|
||||
import { Router } from 'express';
|
||||
import { sortBy } from 'lodash';
|
||||
import { z } from 'zod';
|
||||
@@ -65,6 +66,8 @@ const QueryFilterOptions = z.object({
|
||||
withRuntimeLte: z.coerce.string().optional(),
|
||||
voteAverageGte: z.coerce.string().optional(),
|
||||
voteAverageLte: z.coerce.string().optional(),
|
||||
voteCountGte: z.coerce.string().optional(),
|
||||
voteCountLte: z.coerce.string().optional(),
|
||||
network: z.coerce.string().optional(),
|
||||
watchProviders: z.coerce.string().optional(),
|
||||
watchRegion: z.coerce.string().optional(),
|
||||
@@ -96,6 +99,8 @@ discoverRoutes.get('/movies', async (req, res, next) => {
|
||||
withRuntimeLte: query.withRuntimeLte,
|
||||
voteAverageGte: query.voteAverageGte,
|
||||
voteAverageLte: query.voteAverageLte,
|
||||
voteCountGte: query.voteCountGte,
|
||||
voteCountLte: query.voteCountLte,
|
||||
watchProviders: query.watchProviders,
|
||||
watchRegion: query.watchRegion,
|
||||
});
|
||||
@@ -376,6 +381,8 @@ discoverRoutes.get('/tv', async (req, res, next) => {
|
||||
withRuntimeLte: query.withRuntimeLte,
|
||||
voteAverageGte: query.voteAverageGte,
|
||||
voteAverageLte: query.voteAverageLte,
|
||||
voteCountGte: query.voteCountGte,
|
||||
voteCountLte: query.voteCountLte,
|
||||
watchProviders: query.watchProviders,
|
||||
watchRegion: query.watchRegion,
|
||||
});
|
||||
@@ -659,6 +666,8 @@ discoverRoutes.get('/trending', async (req, res, next) => {
|
||||
)
|
||||
: isPerson(result)
|
||||
? mapPersonResult(result)
|
||||
: isCollection(result)
|
||||
? mapCollectionResult(result)
|
||||
: mapTvResult(
|
||||
result,
|
||||
media.find(
|
||||
|
||||
@@ -183,9 +183,7 @@ serviceRoutes.get<{ tmdbId: string }>(
|
||||
|
||||
const sonarr = new SonarrAPI({
|
||||
apiKey: sonarrSettings.apiKey,
|
||||
url: `${sonarrSettings.useSsl ? 'https' : 'http'}://${
|
||||
sonarrSettings.hostname
|
||||
}:${sonarrSettings.port}${sonarrSettings.baseUrl ?? ''}/api`,
|
||||
url: SonarrAPI.buildUrl(sonarrSettings, '/api/v3'),
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
@@ -383,7 +383,14 @@ router.delete<{ id: string }>(
|
||||
* we manually remove all requests from the user here so the parent media's
|
||||
* properly reflect the change.
|
||||
*/
|
||||
await requestRepository.remove(user.requests);
|
||||
await requestRepository.remove(user.requests, {
|
||||
/**
|
||||
* Break-up into groups of 1000 requests to be removed at a time.
|
||||
* Necessary for users with >1000 requests, else an SQLite 'Expression tree is too large' error occurs.
|
||||
* https://typeorm.io/repository-api#additional-options
|
||||
*/
|
||||
chunk: user.requests.length / 1000,
|
||||
});
|
||||
|
||||
await userRepository.delete(user.id);
|
||||
return res.status(200).json(user.filter());
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type {
|
||||
TmdbCollectionResult,
|
||||
TmdbMovieDetails,
|
||||
TmdbMovieResult,
|
||||
TmdbPersonDetails,
|
||||
@@ -8,17 +9,35 @@ import type {
|
||||
} from '@server/api/themoviedb/interfaces';
|
||||
|
||||
export const isMovie = (
|
||||
movie: TmdbMovieResult | TmdbTvResult | TmdbPersonResult
|
||||
movie:
|
||||
| TmdbMovieResult
|
||||
| TmdbTvResult
|
||||
| TmdbPersonResult
|
||||
| TmdbCollectionResult
|
||||
): movie is TmdbMovieResult => {
|
||||
return (movie as TmdbMovieResult).title !== undefined;
|
||||
};
|
||||
|
||||
export const isPerson = (
|
||||
person: TmdbMovieResult | TmdbTvResult | TmdbPersonResult
|
||||
person:
|
||||
| TmdbMovieResult
|
||||
| TmdbTvResult
|
||||
| TmdbPersonResult
|
||||
| TmdbCollectionResult
|
||||
): person is TmdbPersonResult => {
|
||||
return (person as TmdbPersonResult).known_for !== undefined;
|
||||
};
|
||||
|
||||
export const isCollection = (
|
||||
collection:
|
||||
| TmdbMovieResult
|
||||
| TmdbTvResult
|
||||
| TmdbPersonResult
|
||||
| TmdbCollectionResult
|
||||
): collection is TmdbCollectionResult => {
|
||||
return (collection as TmdbCollectionResult).media_type === 'collection';
|
||||
};
|
||||
|
||||
export const isMovieDetails = (
|
||||
movie: TmdbMovieDetails | TmdbTvDetails | TmdbPersonDetails
|
||||
): movie is TmdbMovieDetails => {
|
||||
|
||||
Reference in New Issue
Block a user