feat(frontend/api): tv request modal (no status. only request)

This commit is contained in:
sct
2020-10-10 06:55:17 +00:00
parent 0fa007b27b
commit 608b96600a
11 changed files with 409 additions and 114 deletions

View File

@@ -5,17 +5,21 @@ import {
Column,
CreateDateColumn,
UpdateDateColumn,
TableInheritance,
AfterUpdate,
AfterInsert,
getRepository,
OneToMany,
} from 'typeorm';
import { User } from './User';
import Media from './Media';
import { MediaStatus, MediaRequestStatus, MediaType } from '../constants/media';
import { getSettings } from '../lib/settings';
import TheMovieDb from '../api/themoviedb';
import RadarrAPI from '../api/radarr';
import logger from '../logger';
import SeasonRequest from './SeasonRequest';
@Entity()
@TableInheritance({ column: { type: 'varchar', name: 'type' } })
export class MediaRequest {
@PrimaryGeneratedColumn()
public id: number;
@@ -38,9 +42,15 @@ export class MediaRequest {
@UpdateDateColumn()
public updatedAt: Date;
@Column()
@Column({ type: 'varchar' })
public type: MediaType;
@OneToMany(() => SeasonRequest, (season) => season.request, {
eager: true,
cascade: true,
})
public seasons: SeasonRequest[];
constructor(init?: Partial<MediaRequest>) {
Object.assign(this, init);
}
@@ -54,4 +64,50 @@ export class MediaRequest {
mediaRepository.save(this.media);
}
}
@AfterUpdate()
@AfterInsert()
private async sendToRadarr() {
if (
this.status === MediaRequestStatus.APPROVED &&
this.type === MediaType.MOVIE
) {
try {
const settings = getSettings();
if (settings.radarr.length === 0 && !settings.radarr[0]) {
logger.info(
'Skipped radarr request as there is no radarr configured',
{ label: 'Media Request' }
);
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,
});
logger.info('Sent request to Radarr', { label: 'Media Request' });
} catch (e) {
throw new Error(
`[MediaRequest] Request failed to send to radarr: ${e.message}`
);
}
}
}
}

View File

@@ -1,59 +0,0 @@
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';
import logger from '../logger';
@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]) {
logger.info(
'Skipped radarr request as there is no radarr configured',
{ label: 'Media Request' }
);
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,
});
logger.info('Sent request to Radarr', { label: 'Media Request' });
} catch (e) {
throw new Error(
`[MediaRequest] Request failed to send to radarr: ${e.message}`
);
}
}
}
}
export default MovieRequest;

View File

@@ -6,8 +6,8 @@ import {
UpdateDateColumn,
ManyToOne,
} from 'typeorm';
import TvRequest from './TvRequest';
import { MediaRequestStatus } from '../constants/media';
import { MediaRequest } from './MediaRequest';
@Entity()
class SeasonRequest {
@@ -20,8 +20,8 @@ class SeasonRequest {
@Column({ type: 'int', default: MediaRequestStatus.PENDING })
public status: MediaRequestStatus;
@ManyToOne(() => TvRequest, (request) => request.seasons)
public request: TvRequest;
@ManyToOne(() => MediaRequest, (request) => request.seasons)
public request: MediaRequest;
@CreateDateColumn()
public createdAt: Date;

View File

@@ -1,16 +0,0 @@
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;

View File

@@ -40,7 +40,7 @@ interface Season {
seasonNumber: number;
}
interface SeasonWithEpisodes extends Season {
export interface SeasonWithEpisodes extends Season {
episodes: Episode[];
externalIds: ExternalIds;
}

View File

@@ -5,9 +5,8 @@ import { getRepository } from 'typeorm';
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';
import SeasonRequest from '../entity/SeasonRequest';
const requestRoutes = Router();
@@ -43,6 +42,7 @@ requestRoutes.post(
async (req, res, next) => {
const tmdb = new TheMovieDb();
const mediaRepository = getRepository(Media);
const requestRepository = getRepository(MediaRequest);
try {
const tmdbMedia =
@@ -52,6 +52,7 @@ requestRoutes.post(
let media = await mediaRepository.findOne({
where: { tmdbId: req.body.mediaId },
relations: ['requests'],
});
if (!media) {
@@ -65,9 +66,8 @@ requestRoutes.post(
}
if (req.body.mediaType === 'movie') {
const requestRepository = getRepository(MovieRequest);
const request = new MovieRequest({
const request = new MediaRequest({
type: MediaType.MOVIE,
media,
requestedBy: req.user,
// If the user is an admin or has the "auto approve" permission, automatically approve the request
@@ -79,15 +79,50 @@ requestRoutes.post(
await requestRepository.save(request);
return res.status(201).json(request);
} else if (req.body.mediaType === 'tv') {
const requestRepository = getRepository(TvRequest);
const requestedSeasons = req.body.seasons as number[];
let existingSeasons: number[] = [];
const request = new TvRequest({
// We need to check existing requests on this title to make sure we don't double up on seasons that were
// already requested. In the case they were, we just throw out any duplicates but still approve the request.
// (Unless there are no seasons, in which case we abort)
if (media.requests) {
existingSeasons = media.requests.reduce((seasons, request) => {
const combinedSeasons = request.seasons.map(
(season) => season.seasonNumber
);
return [...seasons, ...combinedSeasons];
}, [] as number[]);
}
const finalSeasons = requestedSeasons.filter(
(rs) => !existingSeasons.includes(rs)
);
if (finalSeasons.length === 0) {
return next({
status: 500,
message: 'No seasons available to request',
});
}
const request = new MediaRequest({
type: MediaType.TV,
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,
seasons: finalSeasons.map(
(sn) =>
new SeasonRequest({
seasonNumber: sn,
status: req.user?.hasPermission(Permission.AUTO_APPROVE)
? MediaRequestStatus.APPROVED
: MediaRequestStatus.PENDING,
})
),
});
await requestRepository.save(request);