diff --git a/server/api/notificationSettings.js b/server/api/notificationSettings.js index 58d1fe99..bbcd64c6 100644 --- a/server/api/notificationSettings.js +++ b/server/api/notificationSettings.js @@ -2,7 +2,7 @@ import Router from "koa-router"; import auth from "../middlewares/authentication"; -import { NotificationSetting } from "../models"; +import { Team, NotificationSetting } from "../models"; import policy from "../policies"; import { presentNotificationSetting } from "../presenters"; @@ -62,16 +62,23 @@ router.post("notificationSettings.unsubscribe", async (ctx) => { ctx.assertUuid(id, "id is required"); ctx.assertPresent(token, "token is required"); - const setting = await NotificationSetting.findByPk(id); - if (setting) { - if (token !== setting.unsubscribeToken) { - ctx.redirect(`${process.env.URL}?notice=invalid-auth`); - } + const setting = await NotificationSetting.findByPk(id, { + include: [ + { + model: Team, + required: true, + as: "team", + }, + ], + }); + if (setting && setting.unsubscribeToken === token) { await setting.destroy(); + ctx.redirect(`${setting.team.url}/settings/notifications?success`); + return; } - ctx.redirect(`${process.env.URL}/settings/notifications?success`); + ctx.redirect(`${process.env.URL}?notice=invalid-auth`); }); export default router; diff --git a/server/services/notifications.js b/server/services/notifications.js index 337a6c5d..d8e11e51 100644 --- a/server/services/notifications.js +++ b/server/services/notifications.js @@ -1,4 +1,5 @@ // @flow +import * as Sentry from "@sentry/node"; import type { DocumentEvent, CollectionEvent, Event } from "../events"; import mailer from "../mailer"; import { @@ -9,29 +10,21 @@ import { NotificationSetting, } from "../models"; import { Op } from "../sequelize"; +import { createQueue } from "../utils/queue"; -export default class Notifications { - async on(event: Event) { - switch (event.name) { - case "documents.publish": - case "documents.update": - return this.documentUpdated(event); - case "collections.create": - return this.collectionCreated(event); - default: - } - } +const notificationsQueue = createQueue("notifications"); - async documentUpdated(event: DocumentEvent) { - // lets not send a notification on every autosave update - if (event.data && event.data.autosave) return; - - // wait until the user has finished editing - if (event.data && !event.data.done) return; +notificationsQueue.process(async (job) => { + const event = job.data; + try { const document = await Document.findByPk(event.documentId); if (!document) return; + // If the document has been updated since we initially queued a notification + // abort sending a notification – this functions as a debounce. + if (document.updatedAt > new Date(event.createdAt)) return; + const { collection } = document; if (!collection) return; @@ -79,6 +72,35 @@ export default class Notifications { unsubscribeUrl: setting.unsubscribeUrl, }); }); + } catch (error) { + if (process.env.SENTRY_DSN) { + Sentry.withScope(function (scope) { + scope.setExtra("event", event); + Sentry.captureException(error); + }); + } else { + throw error; + } + } +}); + +export default class Notifications { + async on(event: Event) { + switch (event.name) { + case "documents.publish": + case "documents.update": + return this.documentUpdated(event); + case "collections.create": + return this.collectionCreated(event); + default: + } + } + + async documentUpdated(event: DocumentEvent) { + notificationsQueue.add(event, { + delay: 5 * 60 * 1000, + removeOnComplete: true, + }); } async collectionCreated(event: CollectionEvent) {