diff --git a/server/entity/MediaRequest.ts b/server/entity/MediaRequest.ts index 60b3e6d7c..36932f09c 100644 --- a/server/entity/MediaRequest.ts +++ b/server/entity/MediaRequest.ts @@ -62,10 +62,56 @@ export class MediaRequest { @AfterInsert() private async updateParentStatus() { const mediaRepository = getRepository(Media); + const media = await mediaRepository.findOne({ + where: { id: this.media.id }, + relations: ['requests'], + }); + if (!media) { + logger.error('No parent media!', { label: 'Media Request' }); + return; + } + const seasonRequestRepository = getRepository(SeasonRequest); if (this.status === MediaRequestStatus.APPROVED) { this.media.status = MediaStatus.PROCESSING; mediaRepository.save(this.media); } + + if ( + this.media.mediaType === MediaType.MOVIE && + this.status === MediaRequestStatus.DECLINED + ) { + this.media.status = MediaStatus.UNKNOWN; + mediaRepository.save(this.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.status === MediaStatus.PENDING + ) { + media.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); + }); + } } @AfterRemove() diff --git a/server/routes/request.ts b/server/routes/request.ts index df1f5b576..fbea4bff2 100644 --- a/server/routes/request.ts +++ b/server/routes/request.ts @@ -222,6 +222,10 @@ requestRoutes.get<{ return res.status(200).json(request); } catch (e) { + logger.error('Error processing request update', { + label: 'Media Request', + message: e.message, + }); next({ status: 404, message: 'Request not found' }); } } diff --git a/src/components/Common/ButtonWithDropdown/index.tsx b/src/components/Common/ButtonWithDropdown/index.tsx index 4ea663c1a..51d2abd34 100644 --- a/src/components/Common/ButtonWithDropdown/index.tsx +++ b/src/components/Common/ButtonWithDropdown/index.tsx @@ -34,19 +34,17 @@ const ButtonWithDropdown: React.FC = ({ ...props }) => { const [isOpen, setIsOpen] = useState(false); - const buttonRef = useRef(null); + const buttonRef = useRef(null); useClickOutside(buttonRef, () => setIsOpen(false)); return ( - + + + + {subText && ( +
+

+ {subText} +

+
+ )} + +
+ {children} +
+ + + + + + + , + document.body + ); +}; + +export default SlideOver; diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index 386c6845c..41b7d76cb 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -18,10 +18,15 @@ import PersonCard from '../PersonCard'; import { LanguageContext } from '../../context/LanguageContext'; import LoadingSpinner from '../Common/LoadingSpinner'; import { useUser, Permission } from '../../hooks/useUser'; -import { MediaStatus } from '../../../server/constants/media'; +import { + MediaStatus, + MediaRequestStatus, +} from '../../../server/constants/media'; import RequestModal from '../RequestModal'; import Badge from '../Common/Badge'; import ButtonWithDropdown from '../Common/ButtonWithDropdown'; +import axios from 'axios'; +import SlideOver from '../Common/SlideOver'; const messages = defineMessages({ releasedate: 'Release Date', @@ -61,6 +66,7 @@ const MovieDetails: React.FC = ({ movie }) => { const intl = useIntl(); const { locale } = useContext(LanguageContext); const [showRequestModal, setShowRequestModal] = useState(false); + const [showManager, setShowManager] = useState(false); const { data, error, revalidate } = useSWR( `/api/v1/movie/${router.query.movieId}?language=${locale}`, { @@ -82,7 +88,19 @@ const MovieDetails: React.FC = ({ movie }) => { return
Broken?
; } - const activeRequest = data?.mediaInfo?.requests?.[0]; + const activeRequest = data?.mediaInfo?.requests?.find( + (request) => request.status === MediaRequestStatus.PENDING + ); + + const modifyRequest = async (type: 'approve' | 'decline') => { + const response = await axios.get( + `/api/v1/request/${activeRequest?.id}/${type}` + ); + + if (response) { + revalidate(); + } + }; return (
= ({ movie }) => { }} onCancel={() => setShowRequestModal(false)} /> + setShowManager(false)} + subText={data.title} + > +

Requests

+
+
    + {data.mediaInfo?.requests?.map((request) => ( +
  • +
    +
    +
    +
    + + + + {request.requestedBy.username} +
    +
    + +
    +
    +
    +
    +
    + {request.status === MediaRequestStatus.AVAILABLE && ( + Available + )} + {request.status === MediaRequestStatus.APPROVED && ( + Approved + )} + {request.status === MediaRequestStatus.DECLINED && ( + Declined + )} + {request.status === MediaRequestStatus.PENDING && ( + Pending + )} +
    +
    +
    + + + + + + +
    +
    +
    +
    +
  • + ))} +
+
+
= ({ movie }) => { > {hasPermission(Permission.MANAGE_REQUESTS) && ( <> - + modifyRequest('approve')} + > = ({ movie }) => { Approve - + modifyRequest('decline')} + > = ({ movie }) => { )} {hasPermission(Permission.MANAGE_REQUESTS) && ( -
- {/* {data.mediaInfo?.status === MediaStatus.PENDING && - hasPermission(Permission.MANAGE_REQUESTS) && ( - revalidate()} - /> - )} */}

diff --git a/src/components/TitleCard/index.tsx b/src/components/TitleCard/index.tsx index 1a577e86a..a38d6a2ec 100644 --- a/src/components/TitleCard/index.tsx +++ b/src/components/TitleCard/index.tsx @@ -82,7 +82,7 @@ const TitleCard: React.FC = ({ >
@@ -92,7 +92,7 @@ const TitleCard: React.FC = ({
= ({ tv }) => { - const { hasPermission } = useUser(); + const { user, hasPermission } = useUser(); const router = useRouter(); const intl = useIntl(); const { locale } = useContext(LanguageContext); @@ -77,6 +84,24 @@ const TvDetails: React.FC = ({ tv }) => { return
Broken?
; } + const activeRequests = data.mediaInfo?.requests?.filter( + (request) => request.status === MediaRequestStatus.PENDING + ); + + const modifyRequests = async (type: 'approve' | 'decline'): Promise => { + if (!activeRequests) { + return; + } + + await Promise.all( + activeRequests.map(async (request) => { + return axios.get(`/api/v1/request/${request.id}/${type}`); + }) + ); + + revalidate(); + }; + return (
= ({ tv }) => {
{(!data.mediaInfo || - data.mediaInfo.status !== MediaStatus.AVAILABLE) && ( + data.mediaInfo.status === MediaStatus.UNKNOWN) && ( )} + {data.mediaInfo && + data.mediaInfo.status !== MediaStatus.UNKNOWN && + data.mediaInfo.status !== MediaStatus.AVAILABLE && ( + + + + } + text={ + <> + + + + + + } + onClick={() => setShowRequestModal(true)} + > + {hasPermission(Permission.MANAGE_REQUESTS) && + activeRequests && + activeRequests.length > 0 && ( + <> + modifyRequests('approve')} + > + + + + + + modifyRequests('decline')} + > + + + + + + + )} + + )} {hasPermission(Permission.MANAGE_REQUESTS) && (
- {/* {data.mediaInfo?.status === MediaStatus.PENDING && - hasPermission(Permission.MANAGE_REQUESTS) && ( - revalidate()} - /> - )} */}