|
|
|
|
@@ -108,6 +108,26 @@ export class MediaRequest {
|
|
|
|
|
requestBody.is4k ? '4K ' : ''
|
|
|
|
|
}series requests.`
|
|
|
|
|
);
|
|
|
|
|
} else if (
|
|
|
|
|
requestBody.mediaType === MediaType.MUSIC &&
|
|
|
|
|
!requestUser.hasPermission(
|
|
|
|
|
[Permission.REQUEST, Permission.REQUEST_MUSIC],
|
|
|
|
|
{ type: 'or' }
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
throw new RequestPermissionError(
|
|
|
|
|
'You do not have permission to make music requests.'
|
|
|
|
|
);
|
|
|
|
|
} else if (
|
|
|
|
|
requestBody.mediaType === MediaType.BOOK &&
|
|
|
|
|
!requestUser.hasPermission(
|
|
|
|
|
[Permission.REQUEST, Permission.REQUEST_BOOK],
|
|
|
|
|
{ type: 'or' }
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
throw new RequestPermissionError(
|
|
|
|
|
'You do not have permission to make book requests.'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const quotas = await requestUser.getQuota();
|
|
|
|
|
@@ -116,25 +136,42 @@ export class MediaRequest {
|
|
|
|
|
throw new QuotaRestrictedError('Movie Quota exceeded.');
|
|
|
|
|
} else if (requestBody.mediaType === MediaType.TV && quotas.tv.restricted) {
|
|
|
|
|
throw new QuotaRestrictedError('Series Quota exceeded.');
|
|
|
|
|
} else if (requestBody.mediaType === MediaType.MUSIC && quotas.music?.restricted) {
|
|
|
|
|
throw new QuotaRestrictedError('Music Quota exceeded.');
|
|
|
|
|
} else if (requestBody.mediaType === MediaType.BOOK && quotas.book?.restricted) {
|
|
|
|
|
throw new QuotaRestrictedError('Book Quota exceeded.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const tmdbMedia =
|
|
|
|
|
requestBody.mediaType === MediaType.MOVIE
|
|
|
|
|
? await tmdb.getMovie({ movieId: requestBody.mediaId })
|
|
|
|
|
: await tmdb.getTvShow({ tvId: requestBody.mediaId });
|
|
|
|
|
// Music and Book requests don't use TMDB - they use foreign IDs from Lidarr/Readarr
|
|
|
|
|
const isMusicOrBook = requestBody.mediaType === MediaType.MUSIC || requestBody.mediaType === MediaType.BOOK;
|
|
|
|
|
|
|
|
|
|
let tmdbMedia: any = null;
|
|
|
|
|
if (!isMusicOrBook) {
|
|
|
|
|
tmdbMedia =
|
|
|
|
|
requestBody.mediaType === MediaType.MOVIE
|
|
|
|
|
? await tmdb.getMovie({ movieId: requestBody.mediaId })
|
|
|
|
|
: await tmdb.getTvShow({ tvId: requestBody.mediaId });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let media = await mediaRepository.findOne({
|
|
|
|
|
where: {
|
|
|
|
|
tmdbId: requestBody.mediaId,
|
|
|
|
|
mediaType: requestBody.mediaType,
|
|
|
|
|
},
|
|
|
|
|
where: isMusicOrBook
|
|
|
|
|
? {
|
|
|
|
|
externalServiceId: requestBody.foreignId,
|
|
|
|
|
mediaType: requestBody.mediaType,
|
|
|
|
|
}
|
|
|
|
|
: {
|
|
|
|
|
tmdbId: requestBody.mediaId,
|
|
|
|
|
mediaType: requestBody.mediaType,
|
|
|
|
|
},
|
|
|
|
|
relations: ['requests'],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!media) {
|
|
|
|
|
media = new Media({
|
|
|
|
|
tmdbId: tmdbMedia.id,
|
|
|
|
|
tvdbId: requestBody.tvdbId ?? tmdbMedia.external_ids.tvdb_id,
|
|
|
|
|
tmdbId: isMusicOrBook ? 0 : tmdbMedia.id,
|
|
|
|
|
tvdbId: isMusicOrBook ? 0 : (requestBody.tvdbId ?? tmdbMedia.external_ids?.tvdb_id),
|
|
|
|
|
externalServiceId: isMusicOrBook ? requestBody.foreignId : undefined,
|
|
|
|
|
externalServiceTitle: isMusicOrBook ? requestBody.foreignTitle : undefined,
|
|
|
|
|
status: !requestBody.is4k ? MediaStatus.PENDING : MediaStatus.UNKNOWN,
|
|
|
|
|
status4k: requestBody.is4k ? MediaStatus.PENDING : MediaStatus.UNKNOWN,
|
|
|
|
|
mediaType: requestBody.mediaType,
|
|
|
|
|
@@ -159,16 +196,26 @@ export class MediaRequest {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existing = await requestRepository
|
|
|
|
|
const existingQuery = requestRepository
|
|
|
|
|
.createQueryBuilder('request')
|
|
|
|
|
.leftJoin('request.media', 'media')
|
|
|
|
|
.leftJoinAndSelect('request.requestedBy', 'user')
|
|
|
|
|
.where('request.is4k = :is4k', { is4k: requestBody.is4k })
|
|
|
|
|
.andWhere('media.tmdbId = :tmdbId', { tmdbId: tmdbMedia.id })
|
|
|
|
|
.andWhere('media.mediaType = :mediaType', {
|
|
|
|
|
mediaType: requestBody.mediaType,
|
|
|
|
|
})
|
|
|
|
|
.getMany();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (isMusicOrBook) {
|
|
|
|
|
existingQuery.andWhere('media.externalServiceId = :foreignId', {
|
|
|
|
|
foreignId: requestBody.foreignId,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
existingQuery.andWhere('media.tmdbId = :tmdbId', {
|
|
|
|
|
tmdbId: tmdbMedia.id,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existing = await existingQuery.getMany();
|
|
|
|
|
|
|
|
|
|
if (existing && existing.length > 0) {
|
|
|
|
|
// If there is an existing movie request that isn't declined, don't allow a new one.
|
|
|
|
|
@@ -510,6 +557,72 @@ export class MediaRequest {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle music/book request creation (simpler flow - no TMDB, no seasons, no 4K)
|
|
|
|
|
*/
|
|
|
|
|
public static async requestMusicOrBook(
|
|
|
|
|
requestBody: MediaRequestBody,
|
|
|
|
|
user: User,
|
|
|
|
|
options: MediaRequestOptions = {}
|
|
|
|
|
): Promise<MediaRequest> {
|
|
|
|
|
const mediaRepository = getRepository(Media);
|
|
|
|
|
const requestRepository = getRepository(MediaRequest);
|
|
|
|
|
|
|
|
|
|
let media = await mediaRepository.findOne({
|
|
|
|
|
where: {
|
|
|
|
|
externalServiceId: requestBody.foreignId,
|
|
|
|
|
mediaType: requestBody.mediaType,
|
|
|
|
|
},
|
|
|
|
|
relations: ['requests'],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!media) {
|
|
|
|
|
media = new Media({
|
|
|
|
|
tmdbId: 0,
|
|
|
|
|
tvdbId: 0,
|
|
|
|
|
externalServiceId: requestBody.foreignId,
|
|
|
|
|
externalServiceTitle: requestBody.foreignTitle,
|
|
|
|
|
status: MediaStatus.PENDING,
|
|
|
|
|
status4k: MediaStatus.UNKNOWN,
|
|
|
|
|
mediaType: requestBody.mediaType,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await mediaRepository.save(media);
|
|
|
|
|
|
|
|
|
|
const autoApprovePermission =
|
|
|
|
|
requestBody.mediaType === MediaType.MUSIC
|
|
|
|
|
? Permission.AUTO_APPROVE_MUSIC
|
|
|
|
|
: Permission.AUTO_APPROVE_BOOK;
|
|
|
|
|
|
|
|
|
|
const request = new MediaRequest({
|
|
|
|
|
type: requestBody.mediaType,
|
|
|
|
|
media,
|
|
|
|
|
requestedBy: user,
|
|
|
|
|
status: user.hasPermission(
|
|
|
|
|
[Permission.AUTO_APPROVE, autoApprovePermission, Permission.MANAGE_REQUESTS],
|
|
|
|
|
{ type: 'or' }
|
|
|
|
|
)
|
|
|
|
|
? MediaRequestStatus.APPROVED
|
|
|
|
|
: MediaRequestStatus.PENDING,
|
|
|
|
|
modifiedBy: user.hasPermission(
|
|
|
|
|
[Permission.AUTO_APPROVE, autoApprovePermission, Permission.MANAGE_REQUESTS],
|
|
|
|
|
{ type: 'or' }
|
|
|
|
|
)
|
|
|
|
|
? user
|
|
|
|
|
: undefined,
|
|
|
|
|
is4k: false,
|
|
|
|
|
serverId: requestBody.serverId,
|
|
|
|
|
profileId: requestBody.profileId,
|
|
|
|
|
rootFolder: requestBody.rootFolder,
|
|
|
|
|
tags: requestBody.tags,
|
|
|
|
|
isAutoRequest: options.isAutoRequest ?? false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await requestRepository.save(request);
|
|
|
|
|
return request;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@PrimaryGeneratedColumn()
|
|
|
|
|
public id: number;
|
|
|
|
|
|
|
|
|
|
|