feat(add watchlist): adding midding functionality from overserr

feat(add watchlist): adding missing functionality from overserr
This commit is contained in:
Yalagin
2023-04-27 21:19:36 +07:00
parent 53f6a890b9
commit 5f1c10d50a
30 changed files with 534 additions and 20 deletions

View File

@@ -3,6 +3,8 @@ import SonarrAPI from '@server/api/servarr/sonarr';
import { MediaStatus, MediaType } from '@server/constants/media';
import { MediaServerType } from '@server/constants/server';
import { getRepository } from '@server/datasource';
import type { User } from '@server/entity/User';
import { Watchlist } from '@server/entity/Watchlist';
import type { DownloadingItem } from '@server/lib/downloadtracker';
import downloadTracker from '@server/lib/downloadtracker';
import { getSettings } from '@server/lib/settings';
@@ -12,7 +14,6 @@ import {
Column,
CreateDateColumn,
Entity,
In,
Index,
OneToMany,
PrimaryGeneratedColumn,
@@ -25,6 +26,7 @@ import Season from './Season';
@Entity()
class Media {
public static async getRelatedMedia(
user: User | undefined,
tmdbIds: number | number[]
): Promise<Media[]> {
const mediaRepository = getRepository(Media);
@@ -37,9 +39,16 @@ class Media {
finalIds = tmdbIds;
}
const media = await mediaRepository.find({
where: { tmdbId: In(finalIds) },
});
const media = await mediaRepository
.createQueryBuilder('media')
.leftJoinAndSelect(
'media.watchlists',
'watchlist',
'media.id= watchlist.media and watchlist.requestedBy = :userId',
{ userId: user?.id }
) //,
.where(' media.tmdbId in (:...finalIds)', { finalIds })
.getMany();
return media;
} catch (e) {
@@ -94,6 +103,9 @@ class Media {
@OneToMany(() => MediaRequest, (request) => request.media, { cascade: true })
public requests: MediaRequest[];
@OneToMany(() => Watchlist, (watchlist) => watchlist.media)
public watchlists: null | Watchlist[];
@OneToMany(() => Season, (season) => season.media, {
cascade: true,
eager: true,

View File

@@ -1,6 +1,7 @@
import { MediaRequestStatus, MediaType } from '@server/constants/media';
import { UserType } from '@server/constants/user';
import { getRepository } from '@server/datasource';
import { Watchlist } from '@server/entity/Watchlist';
import type { QuotaResponse } from '@server/interfaces/api/userInterfaces';
import PreparedEmail from '@server/lib/email';
import type { PermissionCheckOptions } from '@server/lib/permissions';
@@ -103,6 +104,9 @@ export class User {
@OneToMany(() => MediaRequest, (request) => request.requestedBy)
public requests: MediaRequest[];
@OneToMany(() => Watchlist, (watchlist) => watchlist.requestedBy)
public watchlists: Watchlist[];
@Column({ nullable: true })
public movieQuotaLimit?: number;

148
server/entity/Watchlist.ts Normal file
View File

@@ -0,0 +1,148 @@
import TheMovieDb from '@server/api/themoviedb';
import { MediaType } from '@server/constants/media';
import { getRepository } from '@server/datasource';
import Media from '@server/entity/Media';
import { User } from '@server/entity/User';
import type { WatchlistItem } from '@server/interfaces/api/discoverInterfaces';
import logger from '@server/logger';
import {
Column,
CreateDateColumn,
Entity,
Index,
ManyToOne,
PrimaryGeneratedColumn,
Unique,
UpdateDateColumn,
} from 'typeorm';
export class DuplicateWatchlistRequestError extends Error {}
export class NotFoundError extends Error {
constructor(message = 'Not found') {
super(message);
this.name = 'NotFoundError';
}
}
@Entity()
@Unique('UNIQUE_USER_DB', ['tmdbId', 'requestedBy'])
export class Watchlist implements WatchlistItem {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'varchar' })
public ratingKey = '';
@Column({ type: 'varchar' })
public mediaType: MediaType;
@Column({ type: 'varchar' })
title = '';
@Column()
@Index()
public tmdbId: number;
@ManyToOne(() => User, (user) => user.watchlists, {
eager: true,
onDelete: 'CASCADE',
})
public requestedBy: User;
@ManyToOne(() => Media, (media) => media.watchlists, {
eager: true,
onDelete: 'CASCADE',
})
public media: Media;
@CreateDateColumn()
public createdAt: Date;
@UpdateDateColumn()
public updatedAt: Date;
constructor(init?: Partial<Watchlist>) {
Object.assign(this, init);
}
public static async createWatchlist(
watchlistRequest: Watchlist,
user: User
): Promise<Watchlist> {
const watchlistRepository = getRepository(this);
const mediaRepository = getRepository(Media);
const tmdb = new TheMovieDb();
const tmdbMedia =
watchlistRequest.mediaType === MediaType.MOVIE
? await tmdb.getMovie({ movieId: watchlistRequest.tmdbId })
: await tmdb.getTvShow({ tvId: watchlistRequest.tmdbId });
const existing = await watchlistRepository
.createQueryBuilder('watchlist')
.leftJoinAndSelect('watchlist.requestedBy', 'user')
.where('user.id = :userId', { userId: user.id })
.andWhere('watchlist.tmdbId = :tmdbId', {
tmdbId: watchlistRequest.tmdbId,
})
.andWhere('watchlist.mediaType = :mediaType', {
mediaType: watchlistRequest.mediaType,
})
.getMany();
if (existing && existing.length > 0) {
logger.warn('Duplicate request for watchlist blocked', {
tmdbId: watchlistRequest.tmdbId,
mediaType: watchlistRequest.mediaType,
label: 'Watchlist',
});
throw new DuplicateWatchlistRequestError();
}
let media = await mediaRepository.findOne({
where: {
tmdbId: watchlistRequest.tmdbId,
mediaType: watchlistRequest.mediaType,
},
});
if (!media) {
media = new Media({
tmdbId: tmdbMedia.id,
tvdbId: tmdbMedia.external_ids.tvdb_id,
mediaType: watchlistRequest.mediaType,
});
}
const watchlist = new this({
...watchlistRequest,
requestedBy: user,
media,
});
await mediaRepository.save(media);
await watchlistRepository.save(watchlist);
return watchlist;
}
public static async deleteWatchlist(
tmdbId: Watchlist['tmdbId'],
user: User
): Promise<Watchlist | null> {
const watchlistRepository = getRepository(this);
const watchlist = await watchlistRepository.findOneBy({
tmdbId,
requestedBy: { id: user.id },
});
if (!watchlist) {
throw new NotFoundError('not Found');
}
if (watchlist) {
await watchlistRepository.delete(watchlist.id);
}
return watchlist;
}
}