mirror of
https://github.com/sct/overseerr.git
synced 2025-12-27 16:46:29 +01:00
* chore(release): 1.4.0 * chore(release): 1.4.1 * chore(release): 1.5.0 * chore(release): 1.6.0 * chore(release): 1.7.0 * feat: support for postgresql * test(pgsql): disable root certificate verification * test(ci): temporarily change CI for local repo * fix: don't use SQLite idiom when using PgSQL * feat(db): add flag to toggle TLS for Postgres * feat(postgres and migrations): added migrations for postgres & imporved ssl for postgres config #186 * fix: restored workflow actions * fix: access order * fix: added pushover sound migration tto initial migration * fix: added option to log queries * fix: issue with session migration * chore: relocate pushover sound migration * feat: added logging option to other datasources * chore: small tweaks for the datasource. Added docs for db setup * chore: cleanup logs * fix: added default dates to postgres migration * fix: removed psql specific relation checks * chore: added some debug sanity checks * chore: added some more debug sanity checks * chore: added some more additional debug sanity checks * chore: added some more+ additional debug sanity checks * chore: mild log cleanup * chore: more log cleanup * chore: finish log cleanup * fix: added not null to migration so typeorm doesn't delete ids * chore: cleanup extra psql code * fix: remove eager load * docs: added documentation for migration to postgres * docs: added database option to bug template * feat: created docker-compose postgres file * fix: updated ts schema to align with change to migration * fix: switch timestamp to include timezone * fix: fixed indentation in psql docker-compose * fix: changed version to 0.1.0 to remove ui notification * style: fixed prettier in docker-compose.pastgres.yaml * chore: restored CHANGELOG.md * chore: revverted ts commit * fix: update pnpm lock with pg package * chore(pnpm-lock.yaml): updated pnpm-lock * docs: update docs to add psql set up info * refactor: clean up code from cr comments * feat: migrate blacklist * fix: fix issue with cypress tests * docs: update psql docs * fix: fix psql issue in user page; fix tiny psql error when selecting by empty list * fix: incorrect current date function * fix: null contraint with mediaAddedAt; fix psql col type * refactor: removed unnecessary import * feat: add postgres migration for streaming region --------- Co-authored-by: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Co-authored-by: semantic-release-bot <semantic-release-bot@martynus.net> Co-authored-by: zackhow <zackhow@gmail.com> Co-authored-by: Ryan Algar <me@ralgar.dev> Co-authored-by: Ryan Algar <59636191+ralgar@users.noreply.github.com>
159 lines
3.9 KiB
TypeScript
159 lines
3.9 KiB
TypeScript
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';
|
|
import type { ZodNumber, ZodOptional, ZodString } from 'zod';
|
|
|
|
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',
|
|
nullable: false,
|
|
})
|
|
public media: Media;
|
|
|
|
@CreateDateColumn()
|
|
public createdAt: Date;
|
|
|
|
@UpdateDateColumn()
|
|
public updatedAt: Date;
|
|
|
|
constructor(init?: Partial<Watchlist>) {
|
|
Object.assign(this, init);
|
|
}
|
|
|
|
public static async createWatchlist({
|
|
watchlistRequest,
|
|
user,
|
|
}: {
|
|
watchlistRequest: {
|
|
mediaType: MediaType;
|
|
ratingKey?: ZodOptional<ZodString>['_output'];
|
|
title?: ZodOptional<ZodString>['_output'];
|
|
tmdbId: ZodNumber['_output'];
|
|
};
|
|
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;
|
|
}
|
|
}
|