mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat(api): decouple media requests from media info
This commit is contained in:
19
server/constants/media.ts
Normal file
19
server/constants/media.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export enum MediaRequestStatus {
|
||||
PENDING = 1,
|
||||
APPROVED,
|
||||
DECLINED,
|
||||
AVAILABLE,
|
||||
}
|
||||
|
||||
export enum MediaType {
|
||||
MOVIE = 'movie',
|
||||
TV = 'tv',
|
||||
}
|
||||
|
||||
export enum MediaStatus {
|
||||
UNKNOWN = 1,
|
||||
PENDING,
|
||||
PROCESSING,
|
||||
PARTIALLY_AVAILABLE,
|
||||
AVAILABLE,
|
||||
}
|
87
server/entity/Media.ts
Normal file
87
server/entity/Media.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
Index,
|
||||
OneToMany,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
getRepository,
|
||||
In,
|
||||
} from 'typeorm';
|
||||
import { MediaRequest } from './MediaRequest';
|
||||
import { MediaStatus, MediaType } from '../constants/media';
|
||||
|
||||
@Entity()
|
||||
class Media {
|
||||
public static async getRelatedMedia(
|
||||
tmdbIds: number | number[]
|
||||
): Promise<Media[]> {
|
||||
const mediaRepository = getRepository(Media);
|
||||
|
||||
try {
|
||||
let finalIds: number[];
|
||||
if (!Array.isArray(tmdbIds)) {
|
||||
finalIds = [tmdbIds];
|
||||
} else {
|
||||
finalIds = tmdbIds;
|
||||
}
|
||||
|
||||
const media = await mediaRepository.find({
|
||||
tmdbId: In(finalIds),
|
||||
});
|
||||
|
||||
return media;
|
||||
} catch (e) {
|
||||
console.error(e.messaage);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public static async getMedia(id: number): Promise<Media | undefined> {
|
||||
const mediaRepository = getRepository(Media);
|
||||
|
||||
try {
|
||||
const media = await mediaRepository.findOneOrFail({
|
||||
where: { tmdbId: id },
|
||||
});
|
||||
|
||||
return media;
|
||||
} catch (e) {
|
||||
console.error(e.messaage);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
public id: number;
|
||||
|
||||
@Column({ type: 'varchar' })
|
||||
public mediaType: MediaType;
|
||||
|
||||
@Column({ unique: true })
|
||||
@Index()
|
||||
public tmdbId: number;
|
||||
|
||||
@Column({ unique: true, nullable: true })
|
||||
@Index()
|
||||
public tvdbId: number;
|
||||
|
||||
@Column({ type: 'int', default: MediaStatus.UNKNOWN })
|
||||
public status: MediaStatus;
|
||||
|
||||
@OneToMany(() => MediaRequest, (request) => request.media)
|
||||
public requests: MediaRequest;
|
||||
|
||||
@CreateDateColumn()
|
||||
public createdAt: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
public updatedAt: Date;
|
||||
|
||||
constructor(init?: Partial<Media>) {
|
||||
Object.assign(this, init);
|
||||
}
|
||||
}
|
||||
|
||||
export default Media;
|
@@ -5,144 +5,53 @@ import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
getRepository,
|
||||
In,
|
||||
Index,
|
||||
TableInheritance,
|
||||
AfterUpdate,
|
||||
AfterInsert,
|
||||
getRepository,
|
||||
} from 'typeorm';
|
||||
import { User } from './User';
|
||||
import RadarrAPI from '../api/radarr';
|
||||
import { getSettings } from '../lib/settings';
|
||||
import TheMovieDb from '../api/themoviedb';
|
||||
|
||||
export enum MediaRequestStatus {
|
||||
PENDING = 1,
|
||||
APPROVED,
|
||||
DECLINED,
|
||||
AVAILABLE,
|
||||
}
|
||||
import Media from './Media';
|
||||
import { MediaStatus, MediaRequestStatus, MediaType } from '../constants/media';
|
||||
|
||||
@Entity()
|
||||
@TableInheritance({ column: { type: 'varchar', name: 'type' } })
|
||||
export class MediaRequest {
|
||||
public static async getRelatedRequests(
|
||||
mediaIds: number | number[]
|
||||
): Promise<MediaRequest[]> {
|
||||
const requestRepository = getRepository(MediaRequest);
|
||||
|
||||
try {
|
||||
let finalIds: number[];
|
||||
if (!Array.isArray(mediaIds)) {
|
||||
finalIds = [mediaIds];
|
||||
} else {
|
||||
finalIds = mediaIds;
|
||||
}
|
||||
|
||||
const requests = await requestRepository.find({
|
||||
mediaId: In(finalIds),
|
||||
});
|
||||
|
||||
return requests;
|
||||
} catch (e) {
|
||||
console.error(e.messaage);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public static async getRequest(
|
||||
id: number
|
||||
): Promise<MediaRequest | undefined> {
|
||||
const requestRepository = getRepository(MediaRequest);
|
||||
|
||||
try {
|
||||
const request = await requestRepository.findOneOrFail({
|
||||
where: { mediaId: id },
|
||||
});
|
||||
|
||||
return request;
|
||||
} catch (e) {
|
||||
console.error(e.messaage);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
public id: number;
|
||||
|
||||
@Column({ unique: true })
|
||||
@Index()
|
||||
public mediaId: number;
|
||||
|
||||
@Column({ unique: true, nullable: true })
|
||||
@Index()
|
||||
public tvdbId: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
public seasons?: string;
|
||||
|
||||
@Column()
|
||||
public mediaType: 'movie' | 'tv';
|
||||
|
||||
@Column({ type: 'integer' })
|
||||
public status: MediaRequestStatus;
|
||||
|
||||
@ManyToOne(() => Media, (media) => media.requests, { eager: true })
|
||||
public media: Media;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.requests, { eager: true })
|
||||
public requestedBy: User;
|
||||
|
||||
@ManyToOne(() => User, { nullable: true })
|
||||
public modifiedBy?: User;
|
||||
|
||||
@CreateDateColumn()
|
||||
public createdAt: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
public updatedAt: Date;
|
||||
|
||||
@Column()
|
||||
public type: MediaType;
|
||||
|
||||
constructor(init?: Partial<MediaRequest>) {
|
||||
Object.assign(this, init);
|
||||
}
|
||||
|
||||
@AfterUpdate()
|
||||
@AfterInsert()
|
||||
private async sendToRadarr() {
|
||||
if (
|
||||
this.mediaType === 'movie' &&
|
||||
this.status === MediaRequestStatus.APPROVED
|
||||
) {
|
||||
try {
|
||||
const settings = getSettings();
|
||||
if (settings.radarr.length === 0 && !settings.radarr[0]) {
|
||||
console.log(
|
||||
'[MediaRequest] Skipped radarr request as there is no radarr configured'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const tmdb = new TheMovieDb();
|
||||
const radarrSettings = settings.radarr[0];
|
||||
const radarr = new RadarrAPI({
|
||||
apiKey: radarrSettings.apiKey,
|
||||
url: `${radarrSettings.useSsl ? 'https' : 'http'}://${
|
||||
radarrSettings.hostname
|
||||
}:${radarrSettings.port}/api`,
|
||||
});
|
||||
const movie = await tmdb.getMovie({ movieId: this.mediaId });
|
||||
|
||||
await radarr.addMovie({
|
||||
profileId: radarrSettings.activeProfileId,
|
||||
qualityProfileId: radarrSettings.activeProfileId,
|
||||
rootFolderPath: radarrSettings.activeDirectory,
|
||||
title: movie.title,
|
||||
tmdbId: movie.id,
|
||||
year: Number(movie.release_date.slice(0, 4)),
|
||||
monitored: true,
|
||||
searchNow: true,
|
||||
});
|
||||
console.log('[MediaRequest] Sent request to Radarr');
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`[MediaRequest] Request failed to send to radarr: ${e.message}`
|
||||
);
|
||||
}
|
||||
private async updateParentStatus() {
|
||||
const mediaRepository = getRepository(Media);
|
||||
if (this.status === MediaRequestStatus.APPROVED) {
|
||||
this.media.status = MediaStatus.PROCESSING;
|
||||
mediaRepository.save(this.media);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
57
server/entity/MovieRequest.ts
Normal file
57
server/entity/MovieRequest.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { MediaRequest } from './MediaRequest';
|
||||
import { ChildEntity, AfterUpdate, AfterInsert } from 'typeorm';
|
||||
import TheMovieDb from '../api/themoviedb';
|
||||
import RadarrAPI from '../api/radarr';
|
||||
import { getSettings } from '../lib/settings';
|
||||
import { MediaType, MediaRequestStatus } from '../constants/media';
|
||||
|
||||
@ChildEntity(MediaType.MOVIE)
|
||||
class MovieRequest extends MediaRequest {
|
||||
constructor(init?: Partial<MovieRequest>) {
|
||||
super(init);
|
||||
}
|
||||
|
||||
@AfterUpdate()
|
||||
@AfterInsert()
|
||||
private async sendToRadarr() {
|
||||
if (this.status === MediaRequestStatus.APPROVED) {
|
||||
try {
|
||||
const settings = getSettings();
|
||||
if (settings.radarr.length === 0 && !settings.radarr[0]) {
|
||||
console.log(
|
||||
'[MediaRequest] Skipped radarr request as there is no radarr configured'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const tmdb = new TheMovieDb();
|
||||
const radarrSettings = settings.radarr[0];
|
||||
const radarr = new RadarrAPI({
|
||||
apiKey: radarrSettings.apiKey,
|
||||
url: `${radarrSettings.useSsl ? 'https' : 'http'}://${
|
||||
radarrSettings.hostname
|
||||
}:${radarrSettings.port}/api`,
|
||||
});
|
||||
const movie = await tmdb.getMovie({ movieId: this.media.tmdbId });
|
||||
|
||||
await radarr.addMovie({
|
||||
profileId: radarrSettings.activeProfileId,
|
||||
qualityProfileId: radarrSettings.activeProfileId,
|
||||
rootFolderPath: radarrSettings.activeDirectory,
|
||||
title: movie.title,
|
||||
tmdbId: movie.id,
|
||||
year: Number(movie.release_date.slice(0, 4)),
|
||||
monitored: true,
|
||||
searchNow: true,
|
||||
});
|
||||
console.log('[MediaRequest] Sent request to Radarr');
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`[MediaRequest] Request failed to send to radarr: ${e.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default MovieRequest;
|
37
server/entity/SeasonRequest.ts
Normal file
37
server/entity/SeasonRequest.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
ManyToOne,
|
||||
} from 'typeorm';
|
||||
import TvRequest from './TvRequest';
|
||||
import { MediaRequestStatus } from '../constants/media';
|
||||
|
||||
@Entity()
|
||||
class SeasonRequest {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id: number;
|
||||
|
||||
@Column()
|
||||
public seasonNumber: number;
|
||||
|
||||
@Column({ type: 'int', default: MediaRequestStatus.PENDING })
|
||||
public status: MediaRequestStatus;
|
||||
|
||||
@ManyToOne(() => TvRequest, (request) => request.seasons)
|
||||
public request: TvRequest;
|
||||
|
||||
@CreateDateColumn()
|
||||
public createdAt: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
public updatedAt: Date;
|
||||
|
||||
constructor(init?: Partial<SeasonRequest>) {
|
||||
Object.assign(this, init);
|
||||
}
|
||||
}
|
||||
|
||||
export default SeasonRequest;
|
16
server/entity/TvRequest.ts
Normal file
16
server/entity/TvRequest.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { MediaRequest } from './MediaRequest';
|
||||
import { ChildEntity, OneToMany } from 'typeorm';
|
||||
import SeasonRequest from './SeasonRequest';
|
||||
import { MediaType } from '../constants/media';
|
||||
|
||||
@ChildEntity(MediaType.TV)
|
||||
class TvRequest extends MediaRequest {
|
||||
@OneToMany(() => SeasonRequest, (season) => season.request)
|
||||
public seasons: SeasonRequest[];
|
||||
|
||||
constructor(init?: Partial<TvRequest>) {
|
||||
super(init);
|
||||
}
|
||||
}
|
||||
|
||||
export default TvRequest;
|
@@ -10,6 +10,7 @@ import {
|
||||
ExternalIds,
|
||||
mapExternalIds,
|
||||
} from './common';
|
||||
import Media from '../entity/Media';
|
||||
|
||||
export interface MovieDetails {
|
||||
id: number;
|
||||
@@ -46,13 +47,13 @@ export interface MovieDetails {
|
||||
cast: Cast[];
|
||||
crew: Crew[];
|
||||
};
|
||||
request?: MediaRequest;
|
||||
mediaInfo?: Media;
|
||||
externalIds: ExternalIds;
|
||||
}
|
||||
|
||||
export const mapMovieDetails = (
|
||||
movie: TmdbMovieDetails,
|
||||
request?: MediaRequest
|
||||
media?: Media
|
||||
): MovieDetails => ({
|
||||
id: movie.id,
|
||||
adult: movie.adult,
|
||||
@@ -88,5 +89,5 @@ export const mapMovieDetails = (
|
||||
crew: movie.credits.crew.map(mapCrew),
|
||||
},
|
||||
externalIds: mapExternalIds(movie.external_ids),
|
||||
request,
|
||||
mediaInfo: media,
|
||||
});
|
||||
|
@@ -4,6 +4,7 @@ import type {
|
||||
TmdbTvResult,
|
||||
} from '../api/themoviedb';
|
||||
import type { MediaRequest } from '../entity/MediaRequest';
|
||||
import Media from '../entity/Media';
|
||||
|
||||
export type MediaType = 'tv' | 'movie' | 'person';
|
||||
|
||||
@@ -18,7 +19,7 @@ interface SearchResult {
|
||||
genreIds: number[];
|
||||
overview: string;
|
||||
originalLanguage: string;
|
||||
request?: MediaRequest;
|
||||
mediaInfo?: Media;
|
||||
}
|
||||
|
||||
export interface MovieResult extends SearchResult {
|
||||
@@ -28,7 +29,7 @@ export interface MovieResult extends SearchResult {
|
||||
releaseDate: string;
|
||||
adult: boolean;
|
||||
video: boolean;
|
||||
request?: MediaRequest;
|
||||
mediaInfo?: Media;
|
||||
}
|
||||
|
||||
export interface TvResult extends SearchResult {
|
||||
@@ -53,7 +54,7 @@ export type Results = MovieResult | TvResult | PersonResult;
|
||||
|
||||
export const mapMovieResult = (
|
||||
movieResult: TmdbMovieResult,
|
||||
request?: MediaRequest
|
||||
media?: Media
|
||||
): MovieResult => ({
|
||||
id: movieResult.id,
|
||||
mediaType: 'movie',
|
||||
@@ -70,12 +71,12 @@ export const mapMovieResult = (
|
||||
voteCount: movieResult.vote_count,
|
||||
backdropPath: movieResult.backdrop_path,
|
||||
posterPath: movieResult.poster_path,
|
||||
request,
|
||||
mediaInfo: media,
|
||||
});
|
||||
|
||||
export const mapTvResult = (
|
||||
tvResult: TmdbTvResult,
|
||||
request?: MediaRequest
|
||||
media?: Media
|
||||
): TvResult => ({
|
||||
id: tvResult.id,
|
||||
firstAirDate: tvResult.first_air_Date,
|
||||
@@ -92,7 +93,7 @@ export const mapTvResult = (
|
||||
voteCount: tvResult.vote_count,
|
||||
backdropPath: tvResult.backdrop_path,
|
||||
posterPath: tvResult.poster_path,
|
||||
request,
|
||||
mediaInfo: media,
|
||||
});
|
||||
|
||||
export const mapPersonResult = (
|
||||
@@ -115,19 +116,19 @@ export const mapPersonResult = (
|
||||
|
||||
export const mapSearchResults = (
|
||||
results: (TmdbMovieResult | TmdbTvResult | TmdbPersonResult)[],
|
||||
requests?: MediaRequest[]
|
||||
media?: Media[]
|
||||
): Results[] =>
|
||||
results.map((result) => {
|
||||
switch (result.media_type) {
|
||||
case 'movie':
|
||||
return mapMovieResult(
|
||||
result,
|
||||
requests?.find((req) => req.mediaId === result.id)
|
||||
media?.find((req) => req.tmdbId === result.id)
|
||||
);
|
||||
case 'tv':
|
||||
return mapTvResult(
|
||||
result,
|
||||
requests?.find((req) => req.mediaId === result.id)
|
||||
media?.find((req) => req.tmdbId === result.id)
|
||||
);
|
||||
default:
|
||||
return mapPersonResult(result);
|
||||
|
@@ -8,12 +8,12 @@ import {
|
||||
ExternalIds,
|
||||
mapExternalIds,
|
||||
} from './common';
|
||||
import { MediaRequest } from '../entity/MediaRequest';
|
||||
import {
|
||||
TmdbTvEpisodeDetails,
|
||||
TmdbTvSeasonDetails,
|
||||
TmdbTvDetails,
|
||||
} from '../api/themoviedb';
|
||||
import type Media from '../entity/Media';
|
||||
|
||||
interface Episode {
|
||||
id: number;
|
||||
@@ -78,7 +78,7 @@ export interface TvDetails {
|
||||
crew: Crew[];
|
||||
};
|
||||
externalIds: ExternalIds;
|
||||
request?: MediaRequest;
|
||||
mediaInfo?: Media;
|
||||
}
|
||||
|
||||
const mapEpisodeDetails = (episode: TmdbTvEpisodeDetails): Episode => ({
|
||||
@@ -107,7 +107,7 @@ const mapSeasonDetails = (season: TmdbTvSeasonDetails): Season => ({
|
||||
|
||||
export const mapTvDetails = (
|
||||
show: TmdbTvDetails,
|
||||
request?: MediaRequest
|
||||
media?: Media
|
||||
): TvDetails => ({
|
||||
createdBy: show.created_by,
|
||||
episodeRunTime: show.episode_run_time,
|
||||
@@ -159,5 +159,5 @@ export const mapTvDetails = (
|
||||
crew: show.credits.crew.map(mapCrew),
|
||||
},
|
||||
externalIds: mapExternalIds(show.external_ids),
|
||||
request,
|
||||
mediaInfo: media,
|
||||
});
|
||||
|
@@ -263,8 +263,8 @@ components:
|
||||
video:
|
||||
type: boolean
|
||||
example: false
|
||||
request:
|
||||
$ref: '#/components/schemas/MediaRequest'
|
||||
mediaInfo:
|
||||
$ref: '#/components/schemas/MediaInfo'
|
||||
TvResult:
|
||||
type: object
|
||||
properties:
|
||||
@@ -306,8 +306,8 @@ components:
|
||||
type: string
|
||||
firstAirDate:
|
||||
type: string
|
||||
request:
|
||||
$ref: '#/components/schemas/MediaRequest'
|
||||
mediaInfo:
|
||||
$ref: '#/components/schemas/MediaInfo'
|
||||
PersonResult:
|
||||
type: object
|
||||
properties:
|
||||
@@ -435,8 +435,8 @@ components:
|
||||
$ref: '#/components/schemas/Crew'
|
||||
externalIds:
|
||||
$ref: '#/components/schemas/ExternalIds'
|
||||
request:
|
||||
$ref: '#/components/schemas/MediaRequest'
|
||||
mediaInfo:
|
||||
$ref: '#/components/schemas/MediaInfo'
|
||||
Episode:
|
||||
type: object
|
||||
properties:
|
||||
@@ -577,8 +577,8 @@ components:
|
||||
$ref: '#/components/schemas/Crew'
|
||||
externalIds:
|
||||
$ref: '#/components/schemas/ExternalIds'
|
||||
request:
|
||||
$ref: '#/components/schemas/MediaRequest'
|
||||
mediaInfo:
|
||||
$ref: '#/components/schemas/MediaInfo'
|
||||
MediaRequest:
|
||||
type: object
|
||||
properties:
|
||||
@@ -586,18 +586,13 @@ components:
|
||||
type: number
|
||||
example: 123
|
||||
readOnly: true
|
||||
mediaId:
|
||||
type: number
|
||||
example: 123
|
||||
description: TMDB Movie ID
|
||||
mediaType:
|
||||
type: string
|
||||
enum: [movie, tv]
|
||||
status:
|
||||
type: number
|
||||
example: 0
|
||||
description: Status of the request. 0 = PENDING APPROVAL, 1 = APPROVED, 2 = DECLINED, 3 = AVAILABLE
|
||||
description: Status of the request. 1 = PENDING APPROVAL, 2 = APPROVED, 3 = DECLINED, 4 = AVAILABLE
|
||||
readOnly: true
|
||||
media:
|
||||
$ref: '#/components/schemas/MediaInfo'
|
||||
createdAt:
|
||||
type: string
|
||||
example: '2020-09-12T10:00:27.000Z'
|
||||
@@ -616,9 +611,34 @@ components:
|
||||
nullable: true
|
||||
required:
|
||||
- id
|
||||
- mediaId
|
||||
- mediaType
|
||||
- status
|
||||
MediaInfo:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: number
|
||||
readOnly: true
|
||||
tmdbId:
|
||||
type: number
|
||||
readOnly: true
|
||||
tvdbId:
|
||||
type: number
|
||||
readOnly: true
|
||||
status:
|
||||
type: number
|
||||
requests:
|
||||
type: array
|
||||
readOnly: true
|
||||
items:
|
||||
$ref: '#/components/schemas/MediaRequest'
|
||||
createdAt:
|
||||
type: string
|
||||
example: '2020-09-12T10:00:27.000Z'
|
||||
readOnly: true
|
||||
updatedAt:
|
||||
type: string
|
||||
example: '2020-09-12T10:00:27.000Z'
|
||||
readOnly: true
|
||||
Cast:
|
||||
type: object
|
||||
properties:
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { Router } from 'express';
|
||||
import TheMovieDb from '../api/themoviedb';
|
||||
import { mapMovieResult, mapTvResult } from '../models/Search';
|
||||
import { MediaRequest } from '../entity/MediaRequest';
|
||||
import Media from '../entity/Media';
|
||||
|
||||
const discoverRoutes = Router();
|
||||
|
||||
@@ -13,7 +13,7 @@ discoverRoutes.get('/movies', async (req, res) => {
|
||||
language: req.query.language as string,
|
||||
});
|
||||
|
||||
const requests = await MediaRequest.getRelatedRequests(
|
||||
const media = await Media.getRelatedMedia(
|
||||
data.results.map((result) => result.id)
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ discoverRoutes.get('/movies', async (req, res) => {
|
||||
results: data.results.map((result) =>
|
||||
mapMovieResult(
|
||||
result,
|
||||
requests.find((req) => req.mediaId === result.id)
|
||||
media.find((req) => req.tmdbId === result.id)
|
||||
)
|
||||
),
|
||||
});
|
||||
@@ -38,7 +38,7 @@ discoverRoutes.get('/tv', async (req, res) => {
|
||||
language: req.query.language as string,
|
||||
});
|
||||
|
||||
const requests = await MediaRequest.getRelatedRequests(
|
||||
const media = await Media.getRelatedMedia(
|
||||
data.results.map((result) => result.id)
|
||||
);
|
||||
|
||||
@@ -49,7 +49,7 @@ discoverRoutes.get('/tv', async (req, res) => {
|
||||
results: data.results.map((result) =>
|
||||
mapTvResult(
|
||||
result,
|
||||
requests.find((req) => req.mediaId === result.id)
|
||||
media.find((req) => req.tmdbId === result.id)
|
||||
)
|
||||
),
|
||||
});
|
||||
|
@@ -3,6 +3,7 @@ import TheMovieDb from '../api/themoviedb';
|
||||
import { mapMovieDetails } from '../models/Movie';
|
||||
import { MediaRequest } from '../entity/MediaRequest';
|
||||
import { mapMovieResult } from '../models/Search';
|
||||
import Media from '../entity/Media';
|
||||
|
||||
const movieRoutes = Router();
|
||||
|
||||
@@ -14,9 +15,9 @@ movieRoutes.get('/:id', async (req, res) => {
|
||||
language: req.query.language as string,
|
||||
});
|
||||
|
||||
const request = await MediaRequest.getRequest(movie.id);
|
||||
const media = await Media.getMedia(movie.id);
|
||||
|
||||
return res.status(200).json(mapMovieDetails(movie, request));
|
||||
return res.status(200).json(mapMovieDetails(movie, media));
|
||||
});
|
||||
|
||||
movieRoutes.get('/:id/recommendations', async (req, res) => {
|
||||
@@ -28,7 +29,7 @@ movieRoutes.get('/:id/recommendations', async (req, res) => {
|
||||
language: req.query.language as string,
|
||||
});
|
||||
|
||||
const requests = await MediaRequest.getRelatedRequests(
|
||||
const media = await Media.getRelatedMedia(
|
||||
results.results.map((result) => result.id)
|
||||
);
|
||||
|
||||
@@ -39,7 +40,7 @@ movieRoutes.get('/:id/recommendations', async (req, res) => {
|
||||
results: results.results.map((result) =>
|
||||
mapMovieResult(
|
||||
result,
|
||||
requests.find((req) => req.mediaId === result.id)
|
||||
media.find((req) => req.tmdbId === result.id)
|
||||
)
|
||||
),
|
||||
});
|
||||
@@ -54,7 +55,7 @@ movieRoutes.get('/:id/similar', async (req, res) => {
|
||||
language: req.query.language as string,
|
||||
});
|
||||
|
||||
const requests = await MediaRequest.getRelatedRequests(
|
||||
const media = await Media.getRelatedMedia(
|
||||
results.results.map((result) => result.id)
|
||||
);
|
||||
|
||||
@@ -65,7 +66,7 @@ movieRoutes.get('/:id/similar', async (req, res) => {
|
||||
results: results.results.map((result) =>
|
||||
mapMovieResult(
|
||||
result,
|
||||
requests.find((req) => req.mediaId === result.id)
|
||||
media.find((req) => req.tmdbId === result.id)
|
||||
)
|
||||
),
|
||||
});
|
||||
|
@@ -2,8 +2,12 @@ import { Router } from 'express';
|
||||
import { isAuthenticated } from '../middleware/auth';
|
||||
import { Permission } from '../lib/permissions';
|
||||
import { getRepository } from 'typeorm';
|
||||
import { MediaRequest, MediaRequestStatus } from '../entity/MediaRequest';
|
||||
import { MediaRequest } from '../entity/MediaRequest';
|
||||
import TheMovieDb from '../api/themoviedb';
|
||||
import Media from '../entity/Media';
|
||||
import MovieRequest from '../entity/MovieRequest';
|
||||
import { MediaStatus, MediaRequestStatus, MediaType } from '../constants/media';
|
||||
import TvRequest from '../entity/TvRequest';
|
||||
|
||||
const requestRoutes = Router();
|
||||
|
||||
@@ -15,10 +19,12 @@ requestRoutes.get('/', async (req, res, next) => {
|
||||
order: {
|
||||
id: 'DESC',
|
||||
},
|
||||
relations: ['media'],
|
||||
take: 20,
|
||||
})
|
||||
: await requestRepository.find({
|
||||
where: { requestedBy: { id: req.user?.id } },
|
||||
relations: ['media'],
|
||||
order: {
|
||||
id: 'DESC',
|
||||
},
|
||||
@@ -36,26 +42,59 @@ requestRoutes.post(
|
||||
isAuthenticated(Permission.REQUEST),
|
||||
async (req, res, next) => {
|
||||
const tmdb = new TheMovieDb();
|
||||
const requestRepository = getRepository(MediaRequest);
|
||||
const mediaRepository = getRepository(Media);
|
||||
|
||||
try {
|
||||
const media =
|
||||
const tmdbMedia =
|
||||
req.body.mediaType === 'movie'
|
||||
? await tmdb.getMovie({ movieId: req.body.mediaId })
|
||||
: await tmdb.getTvShow({ tvId: req.body.mediaId });
|
||||
const request = new MediaRequest({
|
||||
mediaId: media.id,
|
||||
mediaType: req.body.mediaType,
|
||||
requestedBy: req.user,
|
||||
// If the user is an admin or has the "auto approve" permission, automatically approve the request
|
||||
status: req.user?.hasPermission(Permission.AUTO_APPROVE)
|
||||
? MediaRequestStatus.APPROVED
|
||||
: MediaRequestStatus.PENDING,
|
||||
|
||||
let media = await mediaRepository.findOne({
|
||||
where: { tmdbId: req.body.mediaId },
|
||||
});
|
||||
|
||||
await requestRepository.save(request);
|
||||
if (!media) {
|
||||
media = new Media({
|
||||
tmdbId: tmdbMedia.id,
|
||||
tvdbId: tmdbMedia.external_ids.tvdb_id,
|
||||
status: MediaStatus.PENDING,
|
||||
mediaType: req.body.mediaType,
|
||||
});
|
||||
await mediaRepository.save(media);
|
||||
}
|
||||
|
||||
return res.status(201).json(request);
|
||||
if (req.body.mediaType === 'movie') {
|
||||
const requestRepository = getRepository(MovieRequest);
|
||||
|
||||
const request = new MovieRequest({
|
||||
media,
|
||||
requestedBy: req.user,
|
||||
// If the user is an admin or has the "auto approve" permission, automatically approve the request
|
||||
status: req.user?.hasPermission(Permission.AUTO_APPROVE)
|
||||
? MediaRequestStatus.APPROVED
|
||||
: MediaRequestStatus.PENDING,
|
||||
});
|
||||
|
||||
await requestRepository.save(request);
|
||||
return res.status(201).json(request);
|
||||
} else if (req.body.mediaType === 'tv') {
|
||||
const requestRepository = getRepository(TvRequest);
|
||||
|
||||
const request = new TvRequest({
|
||||
media,
|
||||
requestedBy: req.user,
|
||||
// If the user is an admin or has the "auto approve" permission, automatically approve the request
|
||||
status: req.user?.hasPermission(Permission.AUTO_APPROVE)
|
||||
? MediaRequestStatus.APPROVED
|
||||
: MediaRequestStatus.PENDING,
|
||||
});
|
||||
|
||||
await requestRepository.save(request);
|
||||
return res.status(201).json(request);
|
||||
}
|
||||
|
||||
next({ status: 500, message: 'Invalid media type' });
|
||||
} catch (e) {
|
||||
next({ message: e.message, status: 500 });
|
||||
}
|
||||
@@ -96,7 +135,7 @@ requestRoutes.delete('/:requestId', async (req, res, next) => {
|
||||
});
|
||||
}
|
||||
|
||||
requestRepository.delete(request.id);
|
||||
await requestRepository.delete(request.id);
|
||||
|
||||
return res.status(200).json(request);
|
||||
} catch (e) {
|
||||
@@ -106,7 +145,7 @@ requestRoutes.delete('/:requestId', async (req, res, next) => {
|
||||
|
||||
requestRoutes.get<{
|
||||
requestId: string;
|
||||
status: 'pending' | 'approve' | 'decline' | 'available';
|
||||
status: 'pending' | 'approve' | 'decline';
|
||||
}>(
|
||||
'/:requestId/:status',
|
||||
isAuthenticated(Permission.MANAGE_REQUESTS),
|
||||
@@ -131,9 +170,6 @@ requestRoutes.get<{
|
||||
case 'decline':
|
||||
newStatus = MediaRequestStatus.DECLINED;
|
||||
break;
|
||||
case 'available':
|
||||
newStatus = MediaRequestStatus.AVAILABLE;
|
||||
break;
|
||||
}
|
||||
|
||||
request.status = newStatus;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { Router } from 'express';
|
||||
import TheMovieDb from '../api/themoviedb';
|
||||
import { mapSearchResults } from '../models/Search';
|
||||
import { MediaRequest } from '../entity/MediaRequest';
|
||||
import Media from '../entity/Media';
|
||||
|
||||
const searchRoutes = Router();
|
||||
|
||||
@@ -14,7 +14,7 @@ searchRoutes.get('/', async (req, res) => {
|
||||
language: req.query.language as string,
|
||||
});
|
||||
|
||||
const requests = await MediaRequest.getRelatedRequests(
|
||||
const media = await Media.getRelatedMedia(
|
||||
results.results.map((result) => result.id)
|
||||
);
|
||||
|
||||
@@ -22,7 +22,7 @@ searchRoutes.get('/', async (req, res) => {
|
||||
page: results.page,
|
||||
totalPages: results.total_pages,
|
||||
totalResults: results.total_results,
|
||||
results: mapSearchResults(results.results, requests),
|
||||
results: mapSearchResults(results.results, media),
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import TheMovieDb from '../api/themoviedb';
|
||||
import { MediaRequest } from '../entity/MediaRequest';
|
||||
import { mapTvDetails } from '../models/Tv';
|
||||
import { mapTvResult } from '../models/Search';
|
||||
import Media from '../entity/Media';
|
||||
|
||||
const tvRoutes = Router();
|
||||
|
||||
@@ -14,9 +15,9 @@ tvRoutes.get('/:id', async (req, res) => {
|
||||
language: req.query.language as string,
|
||||
});
|
||||
|
||||
const request = await MediaRequest.getRequest(tv.id);
|
||||
const media = await Media.getMedia(tv.id);
|
||||
|
||||
return res.status(200).json(mapTvDetails(tv, request));
|
||||
return res.status(200).json(mapTvDetails(tv, media));
|
||||
});
|
||||
|
||||
tvRoutes.get('/:id/recommendations', async (req, res) => {
|
||||
@@ -28,7 +29,7 @@ tvRoutes.get('/:id/recommendations', async (req, res) => {
|
||||
language: req.query.language as string,
|
||||
});
|
||||
|
||||
const requests = await MediaRequest.getRelatedRequests(
|
||||
const media = await Media.getRelatedMedia(
|
||||
results.results.map((result) => result.id)
|
||||
);
|
||||
|
||||
@@ -39,7 +40,7 @@ tvRoutes.get('/:id/recommendations', async (req, res) => {
|
||||
results: results.results.map((result) =>
|
||||
mapTvResult(
|
||||
result,
|
||||
requests.find((req) => req.mediaId === result.id)
|
||||
media.find((req) => req.tmdbId === result.id)
|
||||
)
|
||||
),
|
||||
});
|
||||
@@ -54,7 +55,7 @@ tvRoutes.get('/:id/similar', async (req, res) => {
|
||||
language: req.query.language as string,
|
||||
});
|
||||
|
||||
const requests = await MediaRequest.getRelatedRequests(
|
||||
const media = await Media.getRelatedMedia(
|
||||
results.results.map((result) => result.id)
|
||||
);
|
||||
|
||||
@@ -65,7 +66,7 @@ tvRoutes.get('/:id/similar', async (req, res) => {
|
||||
results: results.results.map((result) =>
|
||||
mapTvResult(
|
||||
result,
|
||||
requests.find((req) => req.mediaId === result.id)
|
||||
media.find((req) => req.tmdbId === result.id)
|
||||
)
|
||||
),
|
||||
});
|
||||
|
Reference in New Issue
Block a user