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

View File

@@ -44,11 +44,11 @@ class JobPlexSync {
this.isRecentOnly = isRecentOnly ?? false;
}
private async getExisting(tmdbId: number) {
private async getExisting(tmdbId: number, mediaType: MediaType) {
const mediaRepository = getRepository(Media);
const existing = await mediaRepository.findOne({
where: { tmdbId: tmdbId },
where: { tmdbId: tmdbId, mediaType },
});
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) {
this.log(`Title exists and is already available ${metadata.title}`);
@@ -115,7 +118,7 @@ class JobPlexSync {
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) {
this.log(`Title exists and is already available ${plexitem.title}`);
} else if (existing && existing.status !== MediaStatus.AVAILABLE) {
@@ -184,9 +187,7 @@ class JobPlexSync {
if (tvShow && metadata) {
// Lets get the available seasons from plex
const seasons = tvShow.seasons;
const media = await mediaRepository.findOne({
where: { tmdbId: tvShow.id, mediaType: MediaType.TV },
});
const media = await this.getExisting(tvShow.id, MediaType.TV);
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 { MediaType } from '../constants/media';
import Media from '../entity/Media';
import { mapMovieResult, MovieResult } from './Search';
@@ -23,7 +24,9 @@ export const mapCollection = (
parts: collection.parts.map((part) =>
mapMovieResult(
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,
TmdbTvResult,
} from '../api/themoviedb';
import { MediaType as MainMediaType } from '../constants/media';
import Media from '../entity/Media';
export type MediaType = 'tv' | 'movie' | 'person';
@@ -122,12 +123,18 @@ export const mapSearchResults = (
case 'movie':
return mapMovieResult(
result,
media?.find((req) => req.tmdbId === result.id)
media?.find(
(req) =>
req.tmdbId === result.id && req.mediaType === MainMediaType.MOVIE
)
);
case 'tv':
return mapTvResult(
result,
media?.find((req) => req.tmdbId === result.id)
media?.find(
(req) =>
req.tmdbId === result.id && req.mediaType === MainMediaType.TV
)
);
default:
return mapPersonResult(result);

View File

@@ -26,7 +26,9 @@ discoverRoutes.get('/movies', async (req, res) => {
results: data.results.map((result) =>
mapMovieResult(
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 RottenTomatoes from '../api/rottentomatoes';
import logger from '../logger';
import { MediaType } from '../constants/media';
const movieRoutes = Router();
@@ -17,7 +18,7 @@ movieRoutes.get('/:id', async (req, res, next) => {
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));
} catch (e) {
@@ -49,7 +50,9 @@ movieRoutes.get('/:id/recommendations', async (req, res) => {
results: results.results.map((result) =>
mapMovieResult(
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) =>
mapMovieResult(
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) =>
mapCastCredits(
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) =>
mapCrewCredits(
result,
crewMedia.find((med) => med.tmdbId === result.id)
crewMedia.find(
(med) =>
med.tmdbId === result.id && med.mediaType === result.media_type
)
)
),
id: combinedCredits.id,

View File

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

View File

@@ -5,6 +5,7 @@ import { mapTvResult } from '../models/Search';
import Media from '../entity/Media';
import RottenTomatoes from '../api/rottentomatoes';
import logger from '../logger';
import { MediaType } from '../constants/media';
const tvRoutes = Router();
@@ -16,7 +17,7 @@ tvRoutes.get('/:id', async (req, res, next) => {
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));
} catch (e) {
@@ -60,7 +61,9 @@ tvRoutes.get('/:id/recommendations', async (req, res) => {
results: results.results.map((result) =>
mapTvResult(
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) =>
mapTvResult(
result,
media.find((req) => req.tmdbId === result.id)
media.find(
(req) => req.tmdbId === result.id && req.mediaType === MediaType.TV
)
)
),
});