diff --git a/src/server/api/routers/overseerr.ts b/src/server/api/routers/overseerr.ts index 7e4dc10a7..1671cfef5 100644 --- a/src/server/api/routers/overseerr.ts +++ b/src/server/api/routers/overseerr.ts @@ -166,4 +166,49 @@ export const overseerrRouter = createTRPCRouter({ }); }); }), + decide: publicProcedure + .input( + z.object({ + configName: z.string(), + id: z.number(), + isApproved: z.boolean(), + }) + ) + .mutation(async ({ input }) => { + const config = getConfig(input.configName); + Consola.log( + `Got a request to ${input.isApproved ? 'approve' : 'decline'} a request`, + input.id + ); + const app = config.apps.find( + (app) => app.integration?.type === 'overseerr' || app.integration?.type === 'jellyseerr' + ); + + const apiKey = app?.integration?.properties.find((x) => x.field === 'apiKey')?.value; + if (!apiKey) { + throw new TRPCError({ + code: 'BAD_REQUEST', + message: 'No app found', + }); + } + const appUrl = new URL(app.url); + const action = input.isApproved ? 'approve' : 'decline'; + return axios + .post( + `${appUrl.origin}/api/v1/request/${input.id}/${action}`, + {}, + { + headers: { + 'X-Api-Key': apiKey, + }, + } + ) + .then((res) => res.data) + .catch((err) => { + throw new TRPCError({ + code: 'INTERNAL_SERVER_ERROR', + message: err.message, + }); + }); + }), }); diff --git a/src/widgets/media-requests/MediaRequestListTile.tsx b/src/widgets/media-requests/MediaRequestListTile.tsx index 493091068..1f4e8fc7d 100644 --- a/src/widgets/media-requests/MediaRequestListTile.tsx +++ b/src/widgets/media-requests/MediaRequestListTile.tsx @@ -10,16 +10,16 @@ import { Text, Tooltip, } from '@mantine/core'; -import { useTranslation } from 'next-i18next'; -import { IconCheck, IconGitPullRequest, IconThumbDown, IconThumbUp } from '@tabler/icons-react'; -import { useMutation } from '@tanstack/react-query'; -import axios from 'axios'; import { notifications } from '@mantine/notifications'; +import { IconCheck, IconGitPullRequest, IconThumbDown, IconThumbUp } from '@tabler/icons-react'; +import { useTranslation } from 'next-i18next'; +import { api } from '~/utils/api'; import { defineWidget } from '../helper'; import { WidgetLoading } from '../loading'; import { IWidget } from '../widgets'; import { useMediaRequestQuery } from './media-request-query'; import { MediaRequest, MediaRequestStatus } from './media-request-types'; +import { useConfigContext } from '~/config/provider'; const definition = defineWidget({ id: 'media-requests-list', @@ -45,21 +45,55 @@ interface MediaRequestListWidgetProps { widget: MediaRequestListWidget; } -function MediaRequestListTile({ widget }: MediaRequestListWidgetProps) { - const { t } = useTranslation('modules/media-requests-list'); - const { data, refetch, isLoading } = useMediaRequestQuery(); - // Use mutation to approve or deny a pending request - const mutate = useMutation({ - mutationFn: async (e: { request: MediaRequest; action: string }) => { - const data = await axios.put(`/api/modules/overseerr/${e.request.id}?action=${e.action}`); - notifications.show({ - title: t('requestUpdated'), - message: t('requestUpdatedMessage', { title: e.request.name }), - color: 'blue', - }); - refetch(); +type MediaRequestDecisionVariables = { + request: MediaRequest; + isApproved: boolean; +}; +const useMediaRequestDecisionMutation = () => { + const { name: configName } = useConfigContext(); + const utils = api.useContext(); + const { mutateAsync } = api.overseerr.decide.useMutation({ + onSuccess() { + utils.mediaRequest.all.invalidate(); }, }); + return async (variables: MediaRequestDecisionVariables) => { + const action = variables.isApproved ? 'Approving' : 'Declining'; + notifications.show({ + id: `decide-${variables.request.id}`, + color: 'yellow', + title: `${action} request...`, + message: undefined, + loading: true, + }); + await mutateAsync( + { + configName: configName!, + id: variables.request.id, + isApproved: variables.isApproved, + }, + { + onSuccess(_data, variables) { + const title = variables.isApproved ? 'Request was approved!' : 'Request was declined!'; + notifications.update({ + id: `decide-${variables.id}`, + color: 'teal', + title, + message: undefined, + icon: , + autoClose: 2000, + }); + }, + } + ); + }; +}; + +function MediaRequestListTile({ widget }: MediaRequestListWidgetProps) { + const { t } = useTranslation('modules/media-requests-list'); + const { data, isLoading } = useMediaRequestQuery(); + // Use mutation to approve or deny a pending request + const decideAsync = useMediaRequestDecisionMutation(); if (!data || isLoading) { return ; @@ -157,18 +191,10 @@ function MediaRequestListTile({ widget }: MediaRequestListWidgetProps) { loading: true, }); - await mutate.mutateAsync({ request: item, action: 'approve' }).then(() => - notifications.update({ - id: `approve ${item.id}`, - color: 'teal', - title: 'Request was approved!', - message: undefined, - icon: , - autoClose: 2000, - }) - ); - - await refetch(); + await decideAsync({ + request: item, + isApproved: true, + }); }} > @@ -179,8 +205,10 @@ function MediaRequestListTile({ widget }: MediaRequestListWidgetProps) { variant="light" color="red" onClick={async () => { - await mutate.mutateAsync({ request: item, action: 'decline' }); - await refetch(); + await decideAsync({ + request: item, + isApproved: false, + }); }} >