diff --git a/server/entity/Media.ts b/server/entity/Media.ts index 7723fb9c..92a74774 100644 --- a/server/entity/Media.ts +++ b/server/entity/Media.ts @@ -40,12 +40,15 @@ class Media { } } - public static async getMedia(id: number): Promise { + public static async getMedia( + id: number, + mediaType: MediaType + ): Promise { 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; diff --git a/server/job/plexsync/index.ts b/server/job/plexsync/index.ts index 3f600c38..f92f5ee0 100644 --- a/server/job/plexsync/index.ts +++ b/server/job/plexsync/index.ts @@ -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[] = []; diff --git a/server/migration/1609236552057-RemoveTmdbIdUniqueConstraint.ts b/server/migration/1609236552057-RemoveTmdbIdUniqueConstraint.ts new file mode 100644 index 00000000..0be26699 --- /dev/null +++ b/server/migration/1609236552057-RemoveTmdbIdUniqueConstraint.ts @@ -0,0 +1,52 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RemoveTmdbIdUniqueConstraint1609236552057 + implements MigrationInterface { + name = 'RemoveTmdbIdUniqueConstraint1609236552057'; + + public async up(queryRunner: QueryRunner): Promise { + 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 { + 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") ` + ); + } +} diff --git a/server/models/Collection.ts b/server/models/Collection.ts index f80a1ad7..64709502 100644 --- a/server/models/Collection.ts +++ b/server/models/Collection.ts @@ -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 + ) ) ), }); diff --git a/server/models/Search.ts b/server/models/Search.ts index 68d8dab8..7d347207 100644 --- a/server/models/Search.ts +++ b/server/models/Search.ts @@ -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); diff --git a/server/routes/discover.ts b/server/routes/discover.ts index 1193354c..ec2116aa 100644 --- a/server/routes/discover.ts +++ b/server/routes/discover.ts @@ -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 + ) ) ), }); diff --git a/server/routes/movie.ts b/server/routes/movie.ts index af4992ed..cadaf5a7 100644 --- a/server/routes/movie.ts +++ b/server/routes/movie.ts @@ -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 + ) ) ), }); diff --git a/server/routes/person.ts b/server/routes/person.ts index dfdd4ce2..add0b0f1 100644 --- a/server/routes/person.ts +++ b/server/routes/person.ts @@ -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, diff --git a/server/routes/request.ts b/server/routes/request.ts index 857d11f7..dfafdc60 100644 --- a/server/routes/request.ts +++ b/server/routes/request.ts @@ -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'], }); diff --git a/server/routes/tv.ts b/server/routes/tv.ts index be3915b0..1ddf1f80 100644 --- a/server/routes/tv.ts +++ b/server/routes/tv.ts @@ -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 + ) ) ), });