diff --git a/overseerr-api.yml b/overseerr-api.yml index 48ff3003b..dda8286ef 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -923,6 +923,15 @@ components: type: number sound: type: string + NotificationSettings: + type: object + properties: + enabled: + type: boolean + example: true + autoapprovalEnabled: + type: boolean + example: false NotificationEmailSettings: type: object properties: @@ -1763,6 +1772,37 @@ paths: nextExecutionTime: type: string example: '2020-09-02T05:02:23.000Z' + /settings/notifications: + get: + summary: Return current notification settings + description: Returns current notification settings in JSON format + tags: + - settings + responses: + '200': + description: Returned settings + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationSettings' + post: + summary: Update notification settings + description: Update current notification settings with provided values + tags: + - settings + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationSettings' + responses: + '200': + description: 'Values were sucessfully updated' + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationSettings' /settings/notifications/email: get: summary: Return current email notification settings diff --git a/server/entity/MediaRequest.ts b/server/entity/MediaRequest.ts index 0e9ca8cac..e7a5f29f9 100644 --- a/server/entity/MediaRequest.ts +++ b/server/entity/MediaRequest.ts @@ -201,6 +201,18 @@ export class MediaRequest { } } + @AfterInsert() + public async autoapprovalNotification(): Promise { + const settings = getSettings().notifications; + + if ( + settings.autoapprovalEnabled && + this.status === MediaRequestStatus.APPROVED + ) { + this.notifyApprovedOrDeclined(); + } + } + @AfterUpdate() @AfterInsert() public async updateParentStatus(): Promise { diff --git a/server/lib/notifications/index.ts b/server/lib/notifications/index.ts index 23fba1161..d43412177 100644 --- a/server/lib/notifications/index.ts +++ b/server/lib/notifications/index.ts @@ -1,4 +1,5 @@ import logger from '../../logger'; +import { getSettings } from '../settings'; import type { NotificationAgent, NotificationPayload } from './agents/agent'; export enum Notification { @@ -43,11 +44,12 @@ class NotificationManager { type: Notification, payload: NotificationPayload ): void { + const settings = getSettings().notifications; logger.info(`Sending notification for ${Notification[type]}`, { label: 'Notifications', }); this.activeAgents.forEach((agent) => { - if (agent.shouldSend(type)) { + if (settings.enabled && agent.shouldSend(type)) { agent.send(type, payload); } }); diff --git a/server/lib/settings.ts b/server/lib/settings.ts index 42abe877c..e1e947339 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -126,6 +126,8 @@ interface NotificationAgents { } interface NotificationSettings { + enabled: boolean; + autoapprovalEnabled: boolean; agents: NotificationAgents; } @@ -168,6 +170,8 @@ class Settings { initialized: false, }, notifications: { + enabled: true, + autoapprovalEnabled: false, agents: { email: { enabled: false, diff --git a/server/routes/settings/notifications.ts b/server/routes/settings/notifications.ts index 10b0e7a51..7f52e7dbd 100644 --- a/server/routes/settings/notifications.ts +++ b/server/routes/settings/notifications.ts @@ -10,6 +10,29 @@ import WebhookAgent from '../../lib/notifications/agents/webhook'; const notificationRoutes = Router(); +notificationRoutes.get('/', (_req, res) => { + const settings = getSettings().notifications; + return res.status(200).json({ + enabled: settings.enabled, + autoapprovalEnabled: settings.autoapprovalEnabled, + }); +}); + +notificationRoutes.post('/', (req, res) => { + const settings = getSettings(); + + Object.assign(settings.notifications, { + enabled: req.body.enabled, + autoapprovalEnabled: req.body.autoapprovalEnabled, + }); + settings.save(); + + return res.status(200).json({ + enabled: settings.notifications.enabled, + autoapprovalEnabled: settings.notifications.autoapprovalEnabled, + }); +}); + notificationRoutes.get('/discord', (_req, res) => { const settings = getSettings(); diff --git a/src/components/Settings/SettingsNotifications.tsx b/src/components/Settings/SettingsNotifications.tsx index 8815b14f3..b7e0a9e37 100644 --- a/src/components/Settings/SettingsNotifications.tsx +++ b/src/components/Settings/SettingsNotifications.tsx @@ -7,11 +7,25 @@ import SlackLogo from '../../assets/extlogos/slack.svg'; import TelegramLogo from '../../assets/extlogos/telegram.svg'; import PushoverLogo from '../../assets/extlogos/pushover.svg'; import Bolt from '../../assets/bolt.svg'; +import { Field, Form, Formik } from 'formik'; +import useSWR from 'swr'; +import Error from '../../pages/_error'; +import LoadingSpinner from '../Common/LoadingSpinner'; +import axios from 'axios'; +import { useToasts } from 'react-toast-notifications'; +import Button from '../Common/Button'; const messages = defineMessages({ + save: 'Save Changes', + saving: 'Saving…', notificationsettings: 'Notification Settings', notificationsettingsDescription: + 'Global notification configuration. The settings below affect all notification agents.', + notificationAgentsSettings: 'Notification Agents', + notificationAgentSettingsDescription: 'Here you can pick and choose what types of notifications to send and through what types of services.', + notificationsettingssaved: 'Notification settings saved!', + notificationsettingsfailed: 'Notification settings failed to save.', }); interface SettingsRoute { @@ -106,6 +120,8 @@ const settingsRoutes: SettingsRoute[] = [ const SettingsNotifications: React.FC = ({ children }) => { const router = useRouter(); const intl = useIntl(); + const { addToast } = useToasts(); + const { data, error, revalidate } = useSWR('/api/v1/settings/notifications'); const activeLinkColor = 'bg-indigo-700'; @@ -134,6 +150,14 @@ const SettingsNotifications: React.FC = ({ children }) => { ); }; + if (!data && !error) { + return ; + } + + if (!data) { + return ; + } + return ( <>
@@ -144,6 +168,110 @@ const SettingsNotifications: React.FC = ({ children }) => { {intl.formatMessage(messages.notificationsettingsDescription)}

+
+ { + try { + await axios.post('/api/v1/settings/notifications', { + enabled: values.enabled, + autoapprovalEnabled: values.autoapprovalEnabled, + }); + addToast(intl.formatMessage(messages.notificationsettingssaved), { + appearance: 'success', + autoDismiss: true, + }); + } catch (e) { + addToast( + intl.formatMessage(messages.notificationsettingsfailed), + { + appearance: 'error', + autoDismiss: true, + } + ); + } finally { + revalidate(); + } + }} + > + {({ isSubmitting, values, setFieldValue }) => { + return ( +
+
+ +
+ { + setFieldValue('enabled', !values.enabled); + }} + className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox" + /> +
+
+
+ +
+ { + setFieldValue( + 'autoapprovalEnabled', + !values.autoapprovalEnabled + ); + }} + className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox" + /> +
+
+
+
+ + + +
+
+
+ ); + }} +
+
+
+

+ {intl.formatMessage(messages.notificationAgentsSettings)} +

+

+ {intl.formatMessage(messages.notificationAgentSettingsDescription)} +

+