diff --git a/overseerr-api.yml b/overseerr-api.yml index f574aaa66..dbb389a5b 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -1381,12 +1381,8 @@ paths: schema: type: string responses: - '200': + '204': description: Succesfully removed request - content: - application/json: - schema: - $ref: '#/components/schemas/MediaRequest' /request/{requestId}/{status}: get: summary: Update a requests status diff --git a/package.json b/package.json index 15d08ef8e..1a813ae68 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "nodemon -e ts --watch server -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", + "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", "build:server": "tsc --project server/tsconfig.json", "build:next": "next build", "build": "yarn build:next && yarn build:server", diff --git a/server/entity/MediaRequest.ts b/server/entity/MediaRequest.ts index cb55ec9ad..60b3e6d7c 100644 --- a/server/entity/MediaRequest.ts +++ b/server/entity/MediaRequest.ts @@ -9,6 +9,7 @@ import { AfterInsert, getRepository, OneToMany, + AfterRemove, } from 'typeorm'; import { User } from './User'; import Media from './Media'; @@ -67,6 +68,15 @@ export class MediaRequest { } } + @AfterRemove() + private async handleRemoveParentUpdate() { + const mediaRepository = getRepository(Media); + if (!this.media.requests || this.media.requests.length === 0) { + this.media.status = MediaStatus.UNKNOWN; + mediaRepository.save(this.media); + } + } + @AfterUpdate() @AfterInsert() private async sendToRadarr() { diff --git a/server/routes/request.ts b/server/routes/request.ts index 9b5dcc805..df1f5b576 100644 --- a/server/routes/request.ts +++ b/server/routes/request.ts @@ -7,6 +7,7 @@ import TheMovieDb from '../api/themoviedb'; import Media from '../entity/Media'; import { MediaStatus, MediaRequestStatus, MediaType } from '../constants/media'; import SeasonRequest from '../entity/SeasonRequest'; +import logger from '../logger'; const requestRoutes = Router(); @@ -63,6 +64,11 @@ requestRoutes.post( mediaType: req.body.mediaType, }); await mediaRepository.save(media); + } else { + if (media.status === MediaStatus.UNKNOWN) { + media.status = MediaStatus.PENDING; + await mediaRepository.save(media); + } } if (req.body.mediaType === 'movie') { @@ -164,7 +170,8 @@ requestRoutes.delete('/:requestId', async (req, res, next) => { if ( !req.user?.hasPermission(Permission.MANAGE_REQUESTS) && - (request.requestedBy.id !== req.user?.id || request.status > 0) + request.requestedBy.id !== req.user?.id && + request.status !== 1 ) { return next({ status: 401, @@ -172,10 +179,11 @@ requestRoutes.delete('/:requestId', async (req, res, next) => { }); } - await requestRepository.delete(request.id); + await requestRepository.remove(request); - return res.status(200).json(request); + return res.status(204).send(); } catch (e) { + logger.error(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 new file mode 100644 index 000000000..4ea663c1a --- /dev/null +++ b/src/components/Common/ButtonWithDropdown/index.tsx @@ -0,0 +1,99 @@ +import React, { + useState, + useRef, + AnchorHTMLAttributes, + ReactNode, + ButtonHTMLAttributes, +} from 'react'; +import useClickOutside from '../../../hooks/useClickOutside'; +import Transition from '../../Transition'; +import { withProperties } from '../../../utils/typeHelpers'; + +const DropdownItem: React.FC> = ({ + children, + ...props +}) => ( + + {children} + +); + +interface ButtonWithDropdownProps + extends ButtonHTMLAttributes { + text: ReactNode; + dropdownIcon?: ReactNode; +} + +const ButtonWithDropdown: React.FC = ({ + text, + children, + dropdownIcon, + ...props +}) => { + const [isOpen, setIsOpen] = useState(false); + const buttonRef = useRef(null); + useClickOutside(buttonRef, () => setIsOpen(false)); + + return ( + + + + {children && ( + + )} + +
+
+
{children}
+
+
+
+
+
+ ); +}; +export default withProperties(ButtonWithDropdown, { Item: DropdownItem }); diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index 9a187d0a9..386c6845c 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -21,6 +21,7 @@ import { useUser, Permission } from '../../hooks/useUser'; import { MediaStatus } from '../../../server/constants/media'; import RequestModal from '../RequestModal'; import Badge from '../Common/Badge'; +import ButtonWithDropdown from '../Common/ButtonWithDropdown'; const messages = defineMessages({ releasedate: 'Release Date', @@ -81,9 +82,6 @@ const MovieDetails: React.FC = ({ movie }) => { return
Broken?
; } - console.log(MediaStatus); - console.log(data); - const activeRequest = data?.mediaInfo?.requests?.[0]; return ( @@ -143,8 +141,7 @@ const MovieDetails: React.FC = ({ movie }) => {
{(!data.mediaInfo || - data.mediaInfo?.status === MediaStatus.UNKNOWN || - activeRequest) && ( + data.mediaInfo?.status === MediaStatus.UNKNOWN) && ( )} + {activeRequest && ( + + + + } + text={ + <> + + + + + + } + onClick={() => setShowRequestModal(true)} + > + {hasPermission(Permission.MANAGE_REQUESTS) && ( + <> + + + + + Approve + + + + + + Decline + + + )} + + )} {hasPermission(Permission.MANAGE_REQUESTS) && (