fix: correctly deal with tmdb id duplicates between movies/series

fixes #526
This commit is contained in:
sct
2020-12-29 10:26:37 +00:00
parent 5295f74915
commit 721ed9a930
10 changed files with 108 additions and 24 deletions

View File

@@ -40,12 +40,15 @@ class Media {
} }
} }
public static async getMedia(id: number): Promise<Media | undefined> { public static async getMedia(
id: number,
mediaType: MediaType
): Promise<Media | undefined> {
const mediaRepository = getRepository(Media); const mediaRepository = getRepository(Media);
try { try {
const media = await mediaRepository.findOne({ const media = await mediaRepository.findOne({
where: { tmdbId: id }, where: { tmdbId: id, mediaType },
relations: ['requests'], relations: ['requests'],
}); });
@@ -62,7 +65,7 @@ class Media {
@Column({ type: 'varchar' }) @Column({ type: 'varchar' })
public mediaType: MediaType; public mediaType: MediaType;
@Column({ unique: true }) @Column()
@Index() @Index()
public tmdbId: number; public tmdbId: number;
@@ -70,7 +73,7 @@ class Media {
@Index() @Index()
public tvdbId?: number; public tvdbId?: number;
@Column({ unique: true, nullable: true }) @Column({ nullable: true })
@Index() @Index()
public imdbId?: string; public imdbId?: string;

View File

@@ -44,11 +44,11 @@ class JobPlexSync {
this.isRecentOnly = isRecentOnly ?? false; this.isRecentOnly = isRecentOnly ?? false;
} }
private async getExisting(tmdbId: number) { private async getExisting(tmdbId: number, mediaType: MediaType) {
const mediaRepository = getRepository(Media); const mediaRepository = getRepository(Media);
const existing = await mediaRepository.findOne({ const existing = await mediaRepository.findOne({
where: { tmdbId: tmdbId }, where: { tmdbId: tmdbId, mediaType },
}); });
return existing; return existing;
@@ -78,7 +78,10 @@ class JobPlexSync {
} }
}); });
const existing = await this.getExisting(newMedia.tmdbId); const existing = await this.getExisting(
newMedia.tmdbId,
MediaType.MOVIE
);
if (existing && existing.status === MediaStatus.AVAILABLE) { if (existing && existing.status === MediaStatus.AVAILABLE) {
this.log(`Title exists and is already available ${metadata.title}`); this.log(`Title exists and is already available ${metadata.title}`);
@@ -115,7 +118,7 @@ class JobPlexSync {
throw new Error('Unable to find TMDB ID'); throw new Error('Unable to find TMDB ID');
} }
const existing = await this.getExisting(tmdbMovieId); const existing = await this.getExisting(tmdbMovieId, MediaType.MOVIE);
if (existing && existing.status === MediaStatus.AVAILABLE) { if (existing && existing.status === MediaStatus.AVAILABLE) {
this.log(`Title exists and is already available ${plexitem.title}`); this.log(`Title exists and is already available ${plexitem.title}`);
} else if (existing && existing.status !== MediaStatus.AVAILABLE) { } else if (existing && existing.status !== MediaStatus.AVAILABLE) {
@@ -184,9 +187,7 @@ class JobPlexSync {
if (tvShow && metadata) { if (tvShow && metadata) {
// Lets get the available seasons from plex // Lets get the available seasons from plex
const seasons = tvShow.seasons; const seasons = tvShow.seasons;
const media = await mediaRepository.findOne({ const media = await this.getExisting(tvShow.id, MediaType.TV);
where: { tmdbId: tvShow.id, mediaType: MediaType.TV },
});
const newSeasons: Season[] = []; const newSeasons: Season[] = [];

View File

@@ -0,0 +1,52 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class RemoveTmdbIdUniqueConstraint1609236552057
implements MigrationInterface {
name = 'RemoveTmdbIdUniqueConstraint1609236552057';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_7ff2d11f6a83cb52386eaebe74"`);
await queryRunner.query(`DROP INDEX "IDX_41a289eb1fa489c1bc6f38d9c3"`);
await queryRunner.query(`DROP INDEX "IDX_7157aad07c73f6a6ae3bbd5ef5"`);
await queryRunner.query(
`CREATE TABLE "temporary_media" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "mediaType" varchar NOT NULL, "tmdbId" integer NOT NULL, "tvdbId" integer, "imdbId" varchar, "status" integer NOT NULL DEFAULT (1), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "lastSeasonChange" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), CONSTRAINT "UQ_41a289eb1fa489c1bc6f38d9c3c" UNIQUE ("tvdbId"))`
);
await queryRunner.query(
`INSERT INTO "temporary_media"("id", "mediaType", "tmdbId", "tvdbId", "imdbId", "status", "createdAt", "updatedAt", "lastSeasonChange") SELECT "id", "mediaType", "tmdbId", "tvdbId", "imdbId", "status", "createdAt", "updatedAt", "lastSeasonChange" FROM "media"`
);
await queryRunner.query(`DROP TABLE "media"`);
await queryRunner.query(`ALTER TABLE "temporary_media" RENAME TO "media"`);
await queryRunner.query(
`CREATE INDEX "IDX_7ff2d11f6a83cb52386eaebe74" ON "media" ("imdbId") `
);
await queryRunner.query(
`CREATE INDEX "IDX_41a289eb1fa489c1bc6f38d9c3" ON "media" ("tvdbId") `
);
await queryRunner.query(
`CREATE INDEX "IDX_7157aad07c73f6a6ae3bbd5ef5" ON "media" ("tmdbId") `
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_7157aad07c73f6a6ae3bbd5ef5"`);
await queryRunner.query(`DROP INDEX "IDX_41a289eb1fa489c1bc6f38d9c3"`);
await queryRunner.query(`DROP INDEX "IDX_7ff2d11f6a83cb52386eaebe74"`);
await queryRunner.query(`ALTER TABLE "media" RENAME TO "temporary_media"`);
await queryRunner.query(
`CREATE TABLE "media" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "mediaType" varchar NOT NULL, "tmdbId" integer NOT NULL, "tvdbId" integer, "imdbId" varchar, "status" integer NOT NULL DEFAULT (1), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "lastSeasonChange" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), CONSTRAINT "UQ_41a289eb1fa489c1bc6f38d9c3c" UNIQUE ("tvdbId"), CONSTRAINT "UQ_7157aad07c73f6a6ae3bbd5ef5e" UNIQUE ("tmdbId"))`
);
await queryRunner.query(
`INSERT INTO "media"("id", "mediaType", "tmdbId", "tvdbId", "imdbId", "status", "createdAt", "updatedAt", "lastSeasonChange") SELECT "id", "mediaType", "tmdbId", "tvdbId", "imdbId", "status", "createdAt", "updatedAt", "lastSeasonChange" FROM "temporary_media"`
);
await queryRunner.query(`DROP TABLE "temporary_media"`);
await queryRunner.query(
`CREATE INDEX "IDX_7157aad07c73f6a6ae3bbd5ef5" ON "media" ("tmdbId") `
);
await queryRunner.query(
`CREATE INDEX "IDX_41a289eb1fa489c1bc6f38d9c3" ON "media" ("tvdbId") `
);
await queryRunner.query(
`CREATE INDEX "IDX_7ff2d11f6a83cb52386eaebe74" ON "media" ("imdbId") `
);
}
}

View File

@@ -1,4 +1,5 @@
import { TmdbCollection } from '../api/themoviedb'; import { TmdbCollection } from '../api/themoviedb';
import { MediaType } from '../constants/media';
import Media from '../entity/Media'; import Media from '../entity/Media';
import { mapMovieResult, MovieResult } from './Search'; import { mapMovieResult, MovieResult } from './Search';
@@ -23,7 +24,9 @@ export const mapCollection = (
parts: collection.parts.map((part) => parts: collection.parts.map((part) =>
mapMovieResult( mapMovieResult(
part, part,
media?.find((req) => req.tmdbId === part.id) media?.find(
(req) => req.tmdbId === part.id && req.mediaType === MediaType.MOVIE
)
) )
), ),
}); });

View File

@@ -3,6 +3,7 @@ import type {
TmdbPersonResult, TmdbPersonResult,
TmdbTvResult, TmdbTvResult,
} from '../api/themoviedb'; } from '../api/themoviedb';
import { MediaType as MainMediaType } from '../constants/media';
import Media from '../entity/Media'; import Media from '../entity/Media';
export type MediaType = 'tv' | 'movie' | 'person'; export type MediaType = 'tv' | 'movie' | 'person';
@@ -122,12 +123,18 @@ export const mapSearchResults = (
case 'movie': case 'movie':
return mapMovieResult( return mapMovieResult(
result, result,
media?.find((req) => req.tmdbId === result.id) media?.find(
(req) =>
req.tmdbId === result.id && req.mediaType === MainMediaType.MOVIE
)
); );
case 'tv': case 'tv':
return mapTvResult( return mapTvResult(
result, result,
media?.find((req) => req.tmdbId === result.id) media?.find(
(req) =>
req.tmdbId === result.id && req.mediaType === MainMediaType.TV
)
); );
default: default:
return mapPersonResult(result); return mapPersonResult(result);

View File

@@ -26,7 +26,9 @@ discoverRoutes.get('/movies', async (req, res) => {
results: data.results.map((result) => results: data.results.map((result) =>
mapMovieResult( mapMovieResult(
result, result,
media.find((req) => req.tmdbId === result.id) media.find(
(req) => req.tmdbId === result.id && req.mediaType === MediaType.MOVIE
)
) )
), ),
}); });

View File

@@ -5,6 +5,7 @@ import { mapMovieResult } from '../models/Search';
import Media from '../entity/Media'; import Media from '../entity/Media';
import RottenTomatoes from '../api/rottentomatoes'; import RottenTomatoes from '../api/rottentomatoes';
import logger from '../logger'; import logger from '../logger';
import { MediaType } from '../constants/media';
const movieRoutes = Router(); const movieRoutes = Router();
@@ -17,7 +18,7 @@ movieRoutes.get('/:id', async (req, res, next) => {
language: req.query.language as string, language: req.query.language as string,
}); });
const media = await Media.getMedia(tmdbMovie.id); const media = await Media.getMedia(tmdbMovie.id, MediaType.MOVIE);
return res.status(200).json(mapMovieDetails(tmdbMovie, media)); return res.status(200).json(mapMovieDetails(tmdbMovie, media));
} catch (e) { } catch (e) {
@@ -49,7 +50,9 @@ movieRoutes.get('/:id/recommendations', async (req, res) => {
results: results.results.map((result) => results: results.results.map((result) =>
mapMovieResult( mapMovieResult(
result, result,
media.find((req) => req.tmdbId === result.id) media.find(
(req) => req.tmdbId === result.id && req.mediaType === MediaType.MOVIE
)
) )
), ),
}); });
@@ -75,7 +78,9 @@ movieRoutes.get('/:id/similar', async (req, res) => {
results: results.results.map((result) => results: results.results.map((result) =>
mapMovieResult( mapMovieResult(
result, result,
media.find((req) => req.tmdbId === result.id) media.find(
(req) => req.tmdbId === result.id && req.mediaType === MediaType.MOVIE
)
) )
), ),
}); });

View File

@@ -45,13 +45,19 @@ personRoutes.get('/:id/combined_credits', async (req, res) => {
cast: combinedCredits.cast.map((result) => cast: combinedCredits.cast.map((result) =>
mapCastCredits( mapCastCredits(
result, result,
castMedia.find((med) => med.tmdbId === result.id) castMedia.find(
(med) =>
med.tmdbId === result.id && med.mediaType === result.media_type
)
) )
), ),
crew: combinedCredits.crew.map((result) => crew: combinedCredits.crew.map((result) =>
mapCrewCredits( mapCrewCredits(
result, result,
crewMedia.find((med) => med.tmdbId === result.id) crewMedia.find(
(med) =>
med.tmdbId === result.id && med.mediaType === result.media_type
)
) )
), ),
id: combinedCredits.id, id: combinedCredits.id,

View File

@@ -102,7 +102,7 @@ requestRoutes.post(
: await tmdb.getTvShow({ tvId: req.body.mediaId }); : await tmdb.getTvShow({ tvId: req.body.mediaId });
let media = await mediaRepository.findOne({ let media = await mediaRepository.findOne({
where: { tmdbId: req.body.mediaId }, where: { tmdbId: req.body.mediaId, mediaType: req.body.mediaType },
relations: ['requests'], relations: ['requests'],
}); });

View File

@@ -5,6 +5,7 @@ import { mapTvResult } from '../models/Search';
import Media from '../entity/Media'; import Media from '../entity/Media';
import RottenTomatoes from '../api/rottentomatoes'; import RottenTomatoes from '../api/rottentomatoes';
import logger from '../logger'; import logger from '../logger';
import { MediaType } from '../constants/media';
const tvRoutes = Router(); const tvRoutes = Router();
@@ -16,7 +17,7 @@ tvRoutes.get('/:id', async (req, res, next) => {
language: req.query.language as string, language: req.query.language as string,
}); });
const media = await Media.getMedia(tv.id); const media = await Media.getMedia(tv.id, MediaType.TV);
return res.status(200).json(mapTvDetails(tv, media)); return res.status(200).json(mapTvDetails(tv, media));
} catch (e) { } catch (e) {
@@ -60,7 +61,9 @@ tvRoutes.get('/:id/recommendations', async (req, res) => {
results: results.results.map((result) => results: results.results.map((result) =>
mapTvResult( mapTvResult(
result, result,
media.find((req) => req.tmdbId === result.id) media.find(
(req) => req.tmdbId === result.id && req.mediaType === MediaType.TV
)
) )
), ),
}); });
@@ -86,7 +89,9 @@ tvRoutes.get('/:id/similar', async (req, res) => {
results: results.results.map((result) => results: results.results.map((result) =>
mapTvResult( mapTvResult(
result, result,
media.find((req) => req.tmdbId === result.id) media.find(
(req) => req.tmdbId === result.id && req.mediaType === MediaType.TV
)
) )
), ),
}); });