mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
9b7ccf8cb5
* fix: Resolve no-floating-promises lint errors Adds await or void to 10 unhandled promises. Notable fixes: a test assertion using `.resolves` was never awaited, and a custom emoji fetch with setState was running during render instead of in an effect. * chore: Promote no-floating-promises to error Now that all occurrences are fixed, prevent regressions.
95 lines
3.0 KiB
TypeScript
95 lines
3.0 KiB
TypeScript
import { subHours } from "date-fns";
|
|
import ShareDocumentUpdatedEmail from "@server/emails/templates/ShareDocumentUpdatedEmail";
|
|
import Logger from "@server/logging/Logger";
|
|
import { Document, Share, ShareSubscription } from "@server/models";
|
|
import type { RevisionEvent } from "@server/types";
|
|
import { BaseTask, TaskPriority } from "./base/BaseTask";
|
|
|
|
export default class ShareSubscriptionNotificationsTask extends BaseTask<RevisionEvent> {
|
|
public async perform(event: RevisionEvent) {
|
|
const document = await Document.findByPk(event.documentId);
|
|
if (!document) {
|
|
return;
|
|
}
|
|
|
|
// Collect the document's ID and all ancestor IDs by walking up the tree.
|
|
// A subscription scoped to any of these documents covers the updated one.
|
|
const scopeIds: string[] = [document.id];
|
|
let parentId = document.parentDocumentId;
|
|
while (parentId) {
|
|
scopeIds.push(parentId);
|
|
const parent = await Document.findByPk(parentId, {
|
|
attributes: ["id", "parentDocumentId"],
|
|
});
|
|
if (!parent) {
|
|
break;
|
|
}
|
|
parentId = parent.parentDocumentId;
|
|
}
|
|
|
|
// Find all active subscriptions scoped to this document or any ancestor,
|
|
// joined to a published share that allows subscriptions.
|
|
const subscriptions = await ShareSubscription.scope("active").findAll({
|
|
where: { documentId: scopeIds },
|
|
include: [
|
|
{
|
|
model: Share.unscoped(),
|
|
required: true,
|
|
where: {
|
|
published: true,
|
|
revokedAt: null,
|
|
allowSubscriptions: true,
|
|
},
|
|
include: [{ association: "team", required: true }],
|
|
},
|
|
],
|
|
});
|
|
|
|
for (const subscription of subscriptions) {
|
|
// Skip ancestor-scoped subscriptions when the share doesn't include
|
|
// child documents — the updated document wouldn't be accessible.
|
|
if (
|
|
subscription.documentId !== document.id &&
|
|
!subscription.share.includeChildDocuments
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
// Throttle: only one notification per 6 hours
|
|
if (
|
|
subscription.lastNotifiedAt &&
|
|
subscription.lastNotifiedAt > subHours(new Date(), 6)
|
|
) {
|
|
Logger.info(
|
|
"processor",
|
|
`suppressing share subscription notification to ${subscription.id} as recently notified`
|
|
);
|
|
continue;
|
|
}
|
|
|
|
const baseShareUrl = subscription.share.canonicalUrl;
|
|
const shareUrl =
|
|
document.id !== subscription.share.documentId && document.path
|
|
? `${baseShareUrl.replace(/\/$/, "")}${document.path}`
|
|
: baseShareUrl;
|
|
|
|
await new ShareDocumentUpdatedEmail({
|
|
to: subscription.email,
|
|
shareSubscriptionId: subscription.id,
|
|
documentTitle: document.titleWithDefault,
|
|
shareUrl,
|
|
revisionId: event.modelId,
|
|
}).schedule();
|
|
|
|
subscription.lastNotifiedAt = new Date();
|
|
await subscription.save();
|
|
}
|
|
}
|
|
|
|
public get options() {
|
|
return {
|
|
priority: TaskPriority.Background,
|
|
};
|
|
}
|
|
}
|