mirror of
https://github.com/sct/overseerr.git
synced 2025-12-26 16:27:17 +01:00
refactor(MediaRequest): consolidate lifecycle hooks into subscriber & restrict cascade to prevent recursion (#4124)
* fix(mediarequest entity): narrow cascade to insert & remove to prevent hook recursion Restrict cascade options on the MediaRequest→Media relation to only `insert` and `remove`to avoid nested subscriber/AfterUpdate recursion when saving entities. * fix(mediarequest): move methods modifying MediaRequest to its Subscriber * fix(mediasubscriber): use event.manager for parent media updates on remove Replace `getRepository(Media)` calls with `event.manager` in the `afterRemove` hook so that parent-media status resets run within the same transaction/QueryRunner (important for postgresql. Doesnt affect sqlite). * refactor(mediasubscriber): make afterInsert and afterUpdate async and await internal operations This should prevent unhandled promise rejections and ensure sequential execution of lifecycle actions --------- Co-authored-by: Gauthier <mail@gauthierth.fr>
This commit is contained in:
@@ -103,7 +103,7 @@ class Media {
|
||||
public status4k: MediaStatus;
|
||||
|
||||
@OneToMany(() => MediaRequest, (request) => request.media, {
|
||||
cascade: ['insert', 'remove'],
|
||||
cascade: ['insert', 'update'],
|
||||
})
|
||||
public requests: MediaRequest[];
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants';
|
||||
import type { TmdbKeyword } from '@server/api/themoviedb/interfaces';
|
||||
import {
|
||||
MediaRequestStatus,
|
||||
MediaStatus,
|
||||
@@ -11,13 +9,10 @@ import OverrideRule from '@server/entity/OverrideRule';
|
||||
import type { MediaRequestBody } from '@server/interfaces/api/requestInterfaces';
|
||||
import notificationManager, { Notification } from '@server/lib/notifications';
|
||||
import { Permission } from '@server/lib/permissions';
|
||||
import { getSettings } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
import { DbAwareColumn } from '@server/utils/DbColumnHelper';
|
||||
import { truncate } from 'lodash';
|
||||
import {
|
||||
AfterInsert,
|
||||
AfterLoad,
|
||||
AfterUpdate,
|
||||
Column,
|
||||
Entity,
|
||||
@@ -702,74 +697,6 @@ export class MediaRequest {
|
||||
}
|
||||
}
|
||||
|
||||
@AfterUpdate()
|
||||
@AfterInsert()
|
||||
public async updateParentStatus(): Promise<void> {
|
||||
const mediaRepository = getRepository(Media);
|
||||
const media = await mediaRepository.findOne({
|
||||
where: { id: this.media.id },
|
||||
relations: { requests: true },
|
||||
});
|
||||
if (!media) {
|
||||
logger.error('Media data not found', {
|
||||
label: 'Media Request',
|
||||
requestId: this.id,
|
||||
mediaId: this.media.id,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const seasonRequestRepository = getRepository(SeasonRequest);
|
||||
if (
|
||||
this.status === MediaRequestStatus.APPROVED &&
|
||||
// Do not update the status if the item is already partially available or available
|
||||
media[this.is4k ? 'status4k' : 'status'] !== MediaStatus.AVAILABLE &&
|
||||
media[this.is4k ? 'status4k' : 'status'] !==
|
||||
MediaStatus.PARTIALLY_AVAILABLE
|
||||
) {
|
||||
media[this.is4k ? 'status4k' : 'status'] = MediaStatus.PROCESSING;
|
||||
mediaRepository.save(media);
|
||||
}
|
||||
|
||||
if (
|
||||
media.mediaType === MediaType.MOVIE &&
|
||||
this.status === MediaRequestStatus.DECLINED &&
|
||||
media[this.is4k ? 'status4k' : 'status'] !== MediaStatus.DELETED
|
||||
) {
|
||||
media[this.is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN;
|
||||
mediaRepository.save(media);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the media type is TV, and we are declining a request,
|
||||
* we must check if its the only pending request and that
|
||||
* there the current media status is just pending (meaning no
|
||||
* other requests have yet to be approved)
|
||||
*/
|
||||
if (
|
||||
media.mediaType === MediaType.TV &&
|
||||
this.status === MediaRequestStatus.DECLINED &&
|
||||
media.requests.filter(
|
||||
(request) => request.status === MediaRequestStatus.PENDING
|
||||
).length === 0 &&
|
||||
media[this.is4k ? 'status4k' : 'status'] === MediaStatus.PENDING &&
|
||||
media[this.is4k ? 'status4k' : 'status'] !== MediaStatus.DELETED
|
||||
) {
|
||||
media[this.is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN;
|
||||
mediaRepository.save(media);
|
||||
}
|
||||
|
||||
// Approve child seasons if parent is approved
|
||||
if (
|
||||
media.mediaType === MediaType.TV &&
|
||||
this.status === MediaRequestStatus.APPROVED
|
||||
) {
|
||||
this.seasons.forEach((season) => {
|
||||
season.status = MediaRequestStatus.APPROVED;
|
||||
seasonRequestRepository.save(season);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static async sendNotification(
|
||||
entity: MediaRequest,
|
||||
media: Media,
|
||||
|
||||
Reference in New Issue
Block a user