@headless-lms/core 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/events.d.ts +42 -24
- package/dist/assets/events.js +1 -1
- package/dist/assets/events.js.map +1 -1
- package/dist/assets/file-urls.d.ts +20 -0
- package/dist/assets/file-urls.js +25 -0
- package/dist/assets/file-urls.js.map +1 -0
- package/dist/assets/index.d.ts +6 -4
- package/dist/assets/index.js +4 -2
- package/dist/assets/jobs.d.ts +15 -0
- package/dist/assets/jobs.js +30 -0
- package/dist/assets/jobs.js.map +1 -0
- package/dist/assets/model.d.ts +2 -2
- package/dist/assets/ports.d.ts +46 -15
- package/dist/assets/service.d.ts +38 -7
- package/dist/assets/service.js +287 -38
- package/dist/assets/service.js.map +1 -1
- package/dist/automations/actions.d.ts +13 -24
- package/dist/automations/actions.js +106 -42
- package/dist/automations/actions.js.map +1 -1
- package/dist/automations/catalog.js +226 -3
- package/dist/automations/catalog.js.map +1 -1
- package/dist/automations/community-derivations.d.ts +14 -0
- package/dist/automations/community-derivations.js +439 -0
- package/dist/automations/community-derivations.js.map +1 -0
- package/dist/automations/community-mail.d.ts +98 -0
- package/dist/automations/community-mail.js +70 -0
- package/dist/automations/community-mail.js.map +1 -0
- package/dist/automations/events.d.ts +18 -0
- package/dist/automations/index.d.ts +13 -4
- package/dist/automations/index.js +10 -2
- package/dist/automations/model.d.ts +10 -3
- package/dist/automations/model.js +15 -2
- package/dist/automations/model.js.map +1 -1
- package/dist/automations/notifications/debounce.d.ts +25 -0
- package/dist/automations/notifications/debounce.js +93 -0
- package/dist/automations/notifications/debounce.js.map +1 -0
- package/dist/automations/notifications/defaults.d.ts +8 -0
- package/dist/automations/notifications/defaults.js +316 -0
- package/dist/automations/notifications/defaults.js.map +1 -0
- package/dist/automations/notifications/delivery.d.ts +28 -0
- package/dist/automations/notifications/delivery.js +88 -0
- package/dist/automations/notifications/delivery.js.map +1 -0
- package/dist/automations/notifications/processor.d.ts +64 -0
- package/dist/automations/notifications/processor.js +270 -0
- package/dist/automations/notifications/processor.js.map +1 -0
- package/dist/automations/notifications/rules/content.d.ts +6 -0
- package/dist/automations/notifications/rules/content.js +39 -0
- package/dist/automations/notifications/rules/content.js.map +1 -0
- package/dist/automations/notifications/rules/discussion.d.ts +7 -0
- package/dist/automations/notifications/rules/discussion.js +678 -0
- package/dist/automations/notifications/rules/discussion.js.map +1 -0
- package/dist/automations/notifications/rules/entitlements.d.ts +6 -0
- package/dist/automations/notifications/rules/entitlements.js +249 -0
- package/dist/automations/notifications/rules/entitlements.js.map +1 -0
- package/dist/automations/notifications/rules/index.d.ts +6 -0
- package/dist/automations/notifications/rules/index.js +15 -0
- package/dist/automations/notifications/rules/index.js.map +1 -0
- package/dist/automations/notifications/rules/organizations.d.ts +6 -0
- package/dist/automations/notifications/rules/organizations.js +28 -0
- package/dist/automations/notifications/rules/organizations.js.map +1 -0
- package/dist/automations/notifications/rules/recipients.d.ts +32 -0
- package/dist/automations/notifications/rules/recipients.js +99 -0
- package/dist/automations/notifications/rules/recipients.js.map +1 -0
- package/dist/automations/notifications/service.d.ts +27 -0
- package/dist/automations/notifications/service.js +45 -0
- package/dist/automations/notifications/service.js.map +1 -0
- package/dist/automations/notifications/types.d.ts +7 -0
- package/dist/automations/notifications/types.js +473 -0
- package/dist/automations/notifications/types.js.map +1 -0
- package/dist/automations/ports.d.ts +121 -3
- package/dist/automations/schedule.d.ts +6 -0
- package/dist/automations/schedule.js +41 -0
- package/dist/automations/schedule.js.map +1 -0
- package/dist/automations/service.d.ts +20 -4
- package/dist/automations/service.js +246 -45
- package/dist/automations/service.js.map +1 -1
- package/dist/content/events.d.ts +712 -50
- package/dist/content/events.js +55 -1
- package/dist/content/events.js.map +1 -1
- package/dist/content/index.d.ts +6 -4
- package/dist/content/index.js +3 -1
- package/dist/content/model.d.ts +2 -2
- package/dist/content/notification-categories.d.ts +36 -0
- package/dist/content/notification-categories.js +64 -0
- package/dist/content/notification-categories.js.map +1 -0
- package/dist/content/ports.d.ts +90 -18
- package/dist/content/service.d.ts +57 -18
- package/dist/content/service.js +520 -25
- package/dist/content/service.js.map +1 -1
- package/dist/content/titles.d.ts +22 -0
- package/dist/content/titles.js +69 -0
- package/dist/content/titles.js.map +1 -0
- package/dist/content/types.d.ts +2 -2
- package/dist/discussion/authors.d.ts +10 -0
- package/dist/discussion/authors.js +23 -0
- package/dist/discussion/authors.js.map +1 -0
- package/dist/discussion/chats.d.ts +109 -0
- package/dist/discussion/chats.js +831 -0
- package/dist/discussion/chats.js.map +1 -0
- package/dist/discussion/digests.d.ts +24 -0
- package/dist/discussion/digests.js +104 -0
- package/dist/discussion/digests.js.map +1 -0
- package/dist/discussion/events.d.ts +785 -56
- package/dist/discussion/events.js +165 -5
- package/dist/discussion/events.js.map +1 -1
- package/dist/discussion/index.d.ts +12 -3
- package/dist/discussion/index.js +9 -1
- package/dist/discussion/jobs.d.ts +16 -0
- package/dist/discussion/jobs.js +40 -0
- package/dist/discussion/jobs.js.map +1 -0
- package/dist/discussion/mentions.d.ts +11 -0
- package/dist/discussion/mentions.js +35 -0
- package/dist/discussion/mentions.js.map +1 -0
- package/dist/discussion/ports.d.ts +372 -28
- package/dist/discussion/ports.js +6 -1
- package/dist/discussion/ports.js.map +1 -0
- package/dist/discussion/posts.d.ts +113 -0
- package/dist/discussion/posts.js +775 -0
- package/dist/discussion/posts.js.map +1 -0
- package/dist/discussion/service.d.ts +42 -11
- package/dist/discussion/service.js +364 -85
- package/dist/discussion/service.js.map +1 -1
- package/dist/discussion/subscriptions.d.ts +37 -0
- package/dist/discussion/subscriptions.js +99 -0
- package/dist/discussion/subscriptions.js.map +1 -0
- package/dist/discussion/titles.d.ts +26 -0
- package/dist/discussion/titles.js +84 -0
- package/dist/discussion/titles.js.map +1 -0
- package/dist/entitlements/events.d.ts +178 -11
- package/dist/entitlements/events.js +28 -1
- package/dist/entitlements/events.js.map +1 -1
- package/dist/entitlements/index.d.ts +6 -4
- package/dist/entitlements/index.js +3 -1
- package/dist/entitlements/jobs.d.ts +12 -0
- package/dist/entitlements/jobs.js +18 -0
- package/dist/entitlements/jobs.js.map +1 -0
- package/dist/entitlements/model.d.ts +3 -2
- package/dist/entitlements/ports.d.ts +128 -11
- package/dist/entitlements/rules.d.ts +12 -0
- package/dist/entitlements/rules.js +17 -0
- package/dist/entitlements/rules.js.map +1 -0
- package/dist/entitlements/service.d.ts +57 -3
- package/dist/entitlements/service.js +492 -2
- package/dist/entitlements/service.js.map +1 -1
- package/dist/identity/access.d.ts +6 -0
- package/dist/identity/access.js +7 -0
- package/dist/identity/access.js.map +1 -0
- package/dist/identity/devices.d.ts +30 -0
- package/dist/identity/devices.js +107 -0
- package/dist/identity/devices.js.map +1 -0
- package/dist/identity/events.d.ts +2 -2
- package/dist/identity/index.d.ts +7 -4
- package/dist/identity/index.js +4 -1
- package/dist/identity/model.d.ts +2 -1
- package/dist/identity/notification-settings.d.ts +28 -0
- package/dist/identity/notification-settings.js +113 -0
- package/dist/identity/notification-settings.js.map +1 -0
- package/dist/identity/ports.d.ts +97 -3
- package/dist/identity/service.d.ts +14 -3
- package/dist/identity/service.js +62 -5
- package/dist/identity/service.js.map +1 -1
- package/dist/integrations/events.d.ts +3 -3
- package/dist/integrations/model.js +1 -1
- package/dist/integrations/model.js.map +1 -1
- package/dist/integrations/service.js +2 -2
- package/dist/integrations/service.js.map +1 -1
- package/dist/organizations/access-scopes.d.ts +19 -0
- package/dist/organizations/access-scopes.js +89 -0
- package/dist/organizations/access-scopes.js.map +1 -0
- package/dist/organizations/events.d.ts +75 -33
- package/dist/organizations/events.js +6 -0
- package/dist/organizations/events.js.map +1 -1
- package/dist/organizations/index.d.ts +8 -6
- package/dist/organizations/index.js +5 -3
- package/dist/organizations/members.d.ts +8 -5
- package/dist/organizations/members.js.map +1 -1
- package/dist/organizations/notification-categories.d.ts +32 -0
- package/dist/organizations/notification-categories.js +51 -0
- package/dist/organizations/notification-categories.js.map +1 -0
- package/dist/organizations/ports.d.ts +17 -11
- package/dist/organizations/roles.d.ts +6 -10
- package/dist/organizations/roles.js +19 -7
- package/dist/organizations/roles.js.map +1 -1
- package/dist/organizations/service.d.ts +22 -11
- package/dist/organizations/service.js +164 -47
- package/dist/organizations/service.js.map +1 -1
- package/dist/organizations/types.d.ts +1 -10
- package/dist/progress/events.d.ts +14 -14
- package/dist/progress/index.d.ts +2 -2
- package/dist/progress/ports.d.ts +6 -1
- package/dist/progress/service.d.ts +3 -1
- package/dist/progress/service.js +6 -2
- package/dist/progress/service.js.map +1 -1
- package/dist/reporting/admin/index.d.ts +4 -0
- package/dist/reporting/admin/index.js +2 -0
- package/dist/reporting/admin/model.d.ts +108 -0
- package/dist/reporting/admin/ports.d.ts +27 -0
- package/dist/reporting/admin/service.d.ts +22 -0
- package/dist/reporting/admin/service.js +38 -0
- package/dist/reporting/admin/service.js.map +1 -0
- package/dist/reporting/member/index.d.ts +5 -0
- package/dist/reporting/member/index.js +2 -0
- package/dist/reporting/member/model.d.ts +157 -0
- package/dist/reporting/member/ports.d.ts +48 -0
- package/dist/reporting/member/service.d.ts +92 -0
- package/dist/reporting/member/service.js +364 -0
- package/dist/reporting/member/service.js.map +1 -0
- package/dist/shared/actor.d.ts +9 -0
- package/dist/shared/errors.d.ts +19 -1
- package/dist/shared/errors.js +27 -1
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/id.d.ts +14 -0
- package/dist/shared/id.js +15 -1
- package/dist/shared/id.js.map +1 -1
- package/dist/shared/mailer.d.ts +15 -2
- package/dist/shared/mailer.js +32 -5
- package/dist/shared/mailer.js.map +1 -1
- package/dist/shared/network.d.ts +10 -0
- package/dist/shared/network.js +211 -0
- package/dist/shared/network.js.map +1 -0
- package/dist/shared/notification-categories.d.ts +16 -0
- package/dist/shared/notification-categories.js +94 -0
- package/dist/shared/notification-categories.js.map +1 -0
- package/dist/shared/notification-settings.d.ts +14 -0
- package/dist/shared/notification-settings.js +61 -0
- package/dist/shared/notification-settings.js.map +1 -0
- package/dist/shared/ports.d.ts +48 -2
- package/dist/shared/settings.d.ts +2 -1
- package/dist/shared/settings.js +1 -0
- package/dist/shared/settings.js.map +1 -1
- package/dist/types/assets.d.ts +2 -2
- package/dist/types/automations.d.ts +15 -1
- package/dist/types/content.d.ts +2 -2
- package/dist/types/discussion.d.ts +11 -3
- package/dist/types/email-templates.d.ts +216 -1
- package/dist/types/entitlements.d.ts +2 -2
- package/dist/types/identity.d.ts +48 -2
- package/dist/types/index.d.ts +9 -8
- package/dist/types/notifications.d.ts +157 -0
- package/dist/types/ports.d.ts +6 -0
- package/dist/types/schemas/assets.d.ts +65 -37
- package/dist/types/schemas/assets.js +17 -12
- package/dist/types/schemas/assets.js.map +1 -1
- package/dist/types/schemas/automations.d.ts +9 -1
- package/dist/types/schemas/automations.js +13 -6
- package/dist/types/schemas/automations.js.map +1 -1
- package/dist/types/schemas/content.d.ts +396 -3
- package/dist/types/schemas/content.js +161 -3
- package/dist/types/schemas/content.js.map +1 -1
- package/dist/types/schemas/discussion.d.ts +443 -3
- package/dist/types/schemas/discussion.js +233 -4
- package/dist/types/schemas/discussion.js.map +1 -1
- package/dist/types/schemas/entitlements.d.ts +36 -2
- package/dist/types/schemas/entitlements.js +23 -1
- package/dist/types/schemas/entitlements.js.map +1 -1
- package/dist/types/schemas/identity.d.ts +27 -1
- package/dist/types/schemas/identity.js +14 -2
- package/dist/types/schemas/identity.js.map +1 -1
- package/dist/types/schemas/index.d.ts +9 -8
- package/dist/types/schemas/index.js +9 -8
- package/dist/types/schemas/notifications.d.ts +95 -0
- package/dist/types/schemas/notifications.js +56 -0
- package/dist/types/schemas/notifications.js.map +1 -0
- package/dist/types/schemas/organizations.d.ts +8 -0
- package/dist/types/schemas/organizations.js +2 -0
- package/dist/types/schemas/organizations.js.map +1 -1
- package/dist/types/schemas/progress.d.ts +3 -3
- package/dist/types/schemas/shared.d.ts +3 -1
- package/dist/types/schemas/shared.js +5 -1
- package/dist/types/schemas/shared.js.map +1 -1
- package/package.json +2 -2
- package/dist/reporting/courses/index.d.ts +0 -4
- package/dist/reporting/courses/index.js +0 -2
- package/dist/reporting/courses/model.d.ts +0 -24
- package/dist/reporting/courses/ports.d.ts +0 -13
- package/dist/reporting/courses/service.d.ts +0 -19
- package/dist/reporting/courses/service.js +0 -20
- package/dist/reporting/courses/service.js.map +0 -1
- package/dist/reporting/dashboard/index.d.ts +0 -4
- package/dist/reporting/dashboard/index.js +0 -2
- package/dist/reporting/dashboard/model.d.ts +0 -14
- package/dist/reporting/dashboard/ports.d.ts +0 -13
- package/dist/reporting/dashboard/service.d.ts +0 -19
- package/dist/reporting/dashboard/service.js +0 -20
- package/dist/reporting/dashboard/service.js.map +0 -1
- package/dist/reporting/learn/index.d.ts +0 -5
- package/dist/reporting/learn/index.js +0 -2
- package/dist/reporting/learn/model.d.ts +0 -23
- package/dist/reporting/learn/ports.d.ts +0 -43
- package/dist/reporting/learn/service.d.ts +0 -49
- package/dist/reporting/learn/service.js +0 -116
- package/dist/reporting/learn/service.js.map +0 -1
- package/dist/reporting/students/index.d.ts +0 -4
- package/dist/reporting/students/index.js +0 -2
- package/dist/reporting/students/model.d.ts +0 -51
- package/dist/reporting/students/model.js +0 -1
- package/dist/reporting/students/ports.d.ts +0 -15
- package/dist/reporting/students/ports.js +0 -1
- package/dist/reporting/students/service.d.ts +0 -20
- package/dist/reporting/students/service.js +0 -23
- package/dist/reporting/students/service.js.map +0 -1
- /package/dist/reporting/{courses → admin}/model.js +0 -0
- /package/dist/reporting/{courses → admin}/ports.js +0 -0
- /package/dist/reporting/{dashboard → member}/model.js +0 -0
- /package/dist/reporting/{dashboard → member}/ports.js +0 -0
- /package/dist/{reporting/learn/model.js → shared/actor.js} +0 -0
- /package/dist/{reporting/learn/ports.js → types/notifications.js} +0 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { genId } from "../../shared/id.js";
|
|
2
|
+
import { NotFoundError } from "../../shared/errors.js";
|
|
3
|
+
import { noopLogger } from "../../shared/logger.js";
|
|
4
|
+
import { resolveChannels } from "../../shared/notification-settings.js";
|
|
5
|
+
//#region src/automations/notifications/processor.ts
|
|
6
|
+
const IN_APP = "inApp";
|
|
7
|
+
const PUSH = "push";
|
|
8
|
+
const MESSAGE_SENT = "message.sent";
|
|
9
|
+
const DEVICE_WINDOW_MS = 720 * 60 * 60 * 1e3;
|
|
10
|
+
const noChatDebounce = {
|
|
11
|
+
arm: async () => {},
|
|
12
|
+
mentioned: async () => {}
|
|
13
|
+
};
|
|
14
|
+
function sameKeys(a, b) {
|
|
15
|
+
const left = new Set(a);
|
|
16
|
+
const right = new Set(b);
|
|
17
|
+
return left.size === right.size && [...left].every((key) => right.has(key));
|
|
18
|
+
}
|
|
19
|
+
function chatOf(refs) {
|
|
20
|
+
if (refs.parent?.type === "chat") return refs.parent.id;
|
|
21
|
+
return refs.subject?.type === "chat" ? refs.subject.id : null;
|
|
22
|
+
}
|
|
23
|
+
var NotificationProcessor = class {
|
|
24
|
+
rules;
|
|
25
|
+
reads;
|
|
26
|
+
types;
|
|
27
|
+
channels;
|
|
28
|
+
categories;
|
|
29
|
+
settings;
|
|
30
|
+
devices;
|
|
31
|
+
repo;
|
|
32
|
+
engine;
|
|
33
|
+
debounce;
|
|
34
|
+
now;
|
|
35
|
+
logger;
|
|
36
|
+
constructor(params) {
|
|
37
|
+
this.rules = params.rules;
|
|
38
|
+
this.reads = params.reads;
|
|
39
|
+
this.types = params.types;
|
|
40
|
+
this.channels = params.channels;
|
|
41
|
+
this.categories = params.categories;
|
|
42
|
+
this.settings = params.settings;
|
|
43
|
+
this.devices = params.devices;
|
|
44
|
+
this.repo = params.repo;
|
|
45
|
+
this.engine = params.engine;
|
|
46
|
+
this.debounce = params.debounce ?? noChatDebounce;
|
|
47
|
+
this.now = params.now ?? (() => /* @__PURE__ */ new Date());
|
|
48
|
+
this.logger = params.logger ?? noopLogger;
|
|
49
|
+
}
|
|
50
|
+
async process(event) {
|
|
51
|
+
const rule = Object.prototype.hasOwnProperty.call(this.rules, event.type) ? this.rules[event.type] : void 0;
|
|
52
|
+
if (!rule) return;
|
|
53
|
+
const lists = await rule(event, this.reads);
|
|
54
|
+
await this.file(event.orgId, event.id, lists, {
|
|
55
|
+
occurredAt: new Date(event.occurredAt),
|
|
56
|
+
debounce: true
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async file(orgId, sourceEventId, lists, options) {
|
|
60
|
+
const valid = lists.filter((list) => this.isValid(orgId, sourceEventId, list));
|
|
61
|
+
const filed = [];
|
|
62
|
+
for (const group of this.groups(valid)) {
|
|
63
|
+
const categories = await this.categoriesOf(orgId, sourceEventId, group.communityId);
|
|
64
|
+
if (!categories) continue;
|
|
65
|
+
const expanded = [];
|
|
66
|
+
for (const list of group.lists) {
|
|
67
|
+
const ready = list.audience ? await this.expand(orgId, list, categories) : list;
|
|
68
|
+
if (options.debounce && ready.type === "message.sent") {
|
|
69
|
+
await this.debounce.arm(orgId, ready, options.occurredAt);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
expanded.push(ready);
|
|
73
|
+
}
|
|
74
|
+
filed.push(...await this.match(orgId, sourceEventId, group.communityId, expanded, categories));
|
|
75
|
+
}
|
|
76
|
+
if (filed.length > 0) {
|
|
77
|
+
await this.repo.insert(orgId, filed.map((entry) => entry.notification), filed.flatMap((entry) => entry.deliveries));
|
|
78
|
+
this.logger.info("notifications filed", {
|
|
79
|
+
orgId,
|
|
80
|
+
sourceEventId,
|
|
81
|
+
count: filed.length,
|
|
82
|
+
types: [...new Set(filed.map((entry) => entry.notification.type))]
|
|
83
|
+
});
|
|
84
|
+
await this.mentionsInChats(orgId, filed);
|
|
85
|
+
}
|
|
86
|
+
await this.dispatch(orgId, sourceEventId);
|
|
87
|
+
}
|
|
88
|
+
isValid(orgId, sourceEventId, list) {
|
|
89
|
+
const drop = (reason) => {
|
|
90
|
+
this.logger.error("notification recipient list dropped", {
|
|
91
|
+
orgId,
|
|
92
|
+
sourceEventId,
|
|
93
|
+
type: list.type,
|
|
94
|
+
scope: list.scope,
|
|
95
|
+
reason
|
|
96
|
+
});
|
|
97
|
+
return false;
|
|
98
|
+
};
|
|
99
|
+
const declaration = this.types.get(list.type);
|
|
100
|
+
if (!declaration) return drop("unknown type");
|
|
101
|
+
if (!declaration.scopes.includes(list.scope)) return drop("scope not declared");
|
|
102
|
+
const keys = declaration.display[list.scope] ?? [];
|
|
103
|
+
if (!sameKeys(Object.keys(list.display), keys)) return drop("display keys do not match the declaration");
|
|
104
|
+
if (list.recipients === void 0 === (list.audience === void 0)) return drop("a list needs either recipients or an audience");
|
|
105
|
+
if ((list.audience ? [list.audience.reason] : (list.recipients ?? []).flatMap((recipient) => recipient.reasons)).some((reason) => !declaration.reasons.includes(reason))) return drop("reason not allowed for the type");
|
|
106
|
+
if (list.audience && list.communityId === null) return drop("an audience needs a community");
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
groups(lists) {
|
|
110
|
+
const byCommunity = /* @__PURE__ */ new Map();
|
|
111
|
+
for (const list of lists) {
|
|
112
|
+
const group = byCommunity.get(list.communityId) ?? {
|
|
113
|
+
communityId: list.communityId,
|
|
114
|
+
lists: []
|
|
115
|
+
};
|
|
116
|
+
group.lists.push(list);
|
|
117
|
+
byCommunity.set(list.communityId, group);
|
|
118
|
+
}
|
|
119
|
+
return [...byCommunity.values()];
|
|
120
|
+
}
|
|
121
|
+
async categoriesOf(orgId, sourceEventId, communityId) {
|
|
122
|
+
try {
|
|
123
|
+
return await this.categories.listFor(orgId, communityId);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
if (!(err instanceof NotFoundError)) throw err;
|
|
126
|
+
this.logger.warn("notification group dropped: the community no longer exists", {
|
|
127
|
+
orgId,
|
|
128
|
+
sourceEventId,
|
|
129
|
+
communityId
|
|
130
|
+
});
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async expand(orgId, list, categories) {
|
|
135
|
+
const audience = list.audience;
|
|
136
|
+
const communityId = list.communityId;
|
|
137
|
+
const category = categories.find((candidate) => candidate.covers.some((cover) => cover.type === list.type && cover.reasons.includes(audience.reason)));
|
|
138
|
+
let orgUserIds = [];
|
|
139
|
+
if (category) if (resolveChannels(category, [], audience.spaceId, this.channels.list()).some((channel) => channel.enabled)) orgUserIds = await this.reads.spaceOpeners(orgId, audience.spaceId);
|
|
140
|
+
else {
|
|
141
|
+
const switched = await this.reads.switchedOn(orgId, communityId, category.key, audience.spaceId);
|
|
142
|
+
orgUserIds = switched.length > 0 ? await this.reads.spaceOpenersAmong(orgId, audience.spaceId, switched) : [];
|
|
143
|
+
}
|
|
144
|
+
const excluded = new Set(audience.exclude);
|
|
145
|
+
const candidates = [...new Set(orgUserIds)].filter((id) => !excluded.has(id));
|
|
146
|
+
const muted = candidates.length > 0 ? new Set(await this.reads.mutedAmong(orgId, {
|
|
147
|
+
type: "space",
|
|
148
|
+
id: audience.spaceId
|
|
149
|
+
}, candidates, this.now())) : /* @__PURE__ */ new Set();
|
|
150
|
+
const kept = candidates.filter((id) => !muted.has(id));
|
|
151
|
+
const userIds = kept.length > 0 ? await this.reads.userIdsOf(orgId, kept) : {};
|
|
152
|
+
const recipients = kept.flatMap((orgUserId) => {
|
|
153
|
+
const userId = userIds[orgUserId];
|
|
154
|
+
return userId ? [{
|
|
155
|
+
orgUserId,
|
|
156
|
+
userId,
|
|
157
|
+
reasons: [audience.reason]
|
|
158
|
+
}] : [];
|
|
159
|
+
});
|
|
160
|
+
const { audience: _audience, ...rest } = list;
|
|
161
|
+
return {
|
|
162
|
+
...rest,
|
|
163
|
+
recipients
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
collect(lists) {
|
|
167
|
+
const people = /* @__PURE__ */ new Map();
|
|
168
|
+
for (const list of lists) for (const recipient of list.recipients ?? []) {
|
|
169
|
+
const person = people.get(recipient.userId) ?? {
|
|
170
|
+
orgUserId: recipient.orgUserId,
|
|
171
|
+
userId: recipient.userId,
|
|
172
|
+
pairs: []
|
|
173
|
+
};
|
|
174
|
+
person.pairs.push({
|
|
175
|
+
list,
|
|
176
|
+
type: list.type,
|
|
177
|
+
reasons: [...new Set(recipient.reasons)]
|
|
178
|
+
});
|
|
179
|
+
people.set(recipient.userId, person);
|
|
180
|
+
}
|
|
181
|
+
return [...people.values()];
|
|
182
|
+
}
|
|
183
|
+
async match(orgId, sourceEventId, communityId, lists, categories) {
|
|
184
|
+
const people = this.collect(lists);
|
|
185
|
+
if (people.length === 0 || categories.length === 0) return [];
|
|
186
|
+
const settings = await this.settings.getMany(orgId, people.map((person) => person.userId), communityId ? { communityId } : "org", categories.map((category) => category.key));
|
|
187
|
+
const configured = this.channels.list();
|
|
188
|
+
const withDevice = await this.peopleWithDevice(orgId, people, categories, settings, configured);
|
|
189
|
+
const filed = [];
|
|
190
|
+
for (const person of people) {
|
|
191
|
+
const entry = this.fileOne(sourceEventId, communityId, person, categories, settings[person.userId] ?? [], configured, withDevice.has(person.userId));
|
|
192
|
+
if (entry) filed.push(entry);
|
|
193
|
+
}
|
|
194
|
+
return filed;
|
|
195
|
+
}
|
|
196
|
+
async peopleWithDevice(orgId, people, categories, settings, configured) {
|
|
197
|
+
const wanting = people.filter((person) => categories.some((category) => this.coveredPairs(category, person.pairs).some(({ pair }) => resolveChannels(category, settings[person.userId] ?? [], pair.list.spaceId, configured).some((channel) => channel.enabled && channel.channel === "push"))));
|
|
198
|
+
if (wanting.length === 0) return /* @__PURE__ */ new Set();
|
|
199
|
+
const since = /* @__PURE__ */ new Date(this.now().getTime() - DEVICE_WINDOW_MS);
|
|
200
|
+
return new Set(await this.devices.withActiveDevices(orgId, wanting.map((person) => person.userId), since));
|
|
201
|
+
}
|
|
202
|
+
fileOne(sourceEventId, communityId, person, categories, settings, configured, hasDevice) {
|
|
203
|
+
for (const category of categories) for (const { pair, cover } of this.coveredPairs(category, person.pairs)) {
|
|
204
|
+
let on = resolveChannels(category, settings, pair.list.spaceId, configured).filter((channel) => channel.enabled).map((channel) => channel.channel);
|
|
205
|
+
if (!hasDevice) on = on.filter((channel) => channel !== PUSH);
|
|
206
|
+
if (on.length > 0) return this.filed(sourceEventId, communityId, person, category, pair, cover, on);
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
filed(sourceEventId, communityId, person, category, pair, cover, on) {
|
|
211
|
+
const list = pair.list;
|
|
212
|
+
const notificationId = genId("notification");
|
|
213
|
+
const reasons = [...new Set(person.pairs.flatMap((candidate) => candidate.reasons))];
|
|
214
|
+
return {
|
|
215
|
+
notification: {
|
|
216
|
+
id: notificationId,
|
|
217
|
+
userId: person.userId,
|
|
218
|
+
sourceEventId,
|
|
219
|
+
type: pair.type,
|
|
220
|
+
scope: list.scope,
|
|
221
|
+
reasons,
|
|
222
|
+
matchedReason: cover.reasons.find((reason) => pair.reasons.includes(reason)),
|
|
223
|
+
category: category.key,
|
|
224
|
+
communityId,
|
|
225
|
+
spaceId: list.spaceId,
|
|
226
|
+
actorOrgUserId: list.actorOrgUserId,
|
|
227
|
+
subject: list.subject,
|
|
228
|
+
parent: list.parent,
|
|
229
|
+
display: list.display,
|
|
230
|
+
inApp: on.includes(IN_APP),
|
|
231
|
+
createdAt: this.now()
|
|
232
|
+
},
|
|
233
|
+
deliveries: on.filter((channel) => channel !== "inApp" && this.channels.get(channel) !== void 0).map((channel) => ({
|
|
234
|
+
id: genId("notificationDelivery"),
|
|
235
|
+
notificationId,
|
|
236
|
+
channel
|
|
237
|
+
}))
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
coveredPairs(category, pairs) {
|
|
241
|
+
const covered = [];
|
|
242
|
+
for (const pair of pairs) {
|
|
243
|
+
if (category.perSpace === true && pair.list.spaceId === null) continue;
|
|
244
|
+
const cover = category.covers.find((candidate) => candidate.type === pair.type && candidate.reasons.some((reason) => pair.reasons.includes(reason)));
|
|
245
|
+
if (cover) covered.push({
|
|
246
|
+
pair,
|
|
247
|
+
cover
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return covered;
|
|
251
|
+
}
|
|
252
|
+
async mentionsInChats(orgId, filed) {
|
|
253
|
+
for (const { notification } of filed) {
|
|
254
|
+
if (notification.type !== "mention" || notification.scope !== "chat") continue;
|
|
255
|
+
const chatId = chatOf(notification);
|
|
256
|
+
if (chatId) await this.debounce.mentioned(orgId, notification.userId, chatId);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async dispatch(orgId, sourceEventId) {
|
|
260
|
+
const pending = await this.repo.pendingDeliveriesForEvent(orgId, sourceEventId);
|
|
261
|
+
for (const delivery of pending) await this.engine.deliver({
|
|
262
|
+
orgId,
|
|
263
|
+
deliveryId: delivery.id
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
//#endregion
|
|
268
|
+
export { DEVICE_WINDOW_MS, IN_APP, MESSAGE_SENT, NotificationProcessor, PUSH, chatOf };
|
|
269
|
+
|
|
270
|
+
//# sourceMappingURL=processor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"processor.js","names":[],"sources":["../../../src/automations/notifications/processor.ts"],"sourcesContent":["import type { DevicesService, NotificationSettingsService } from '../../identity/index.js';\nimport { NotFoundError } from '../../shared/errors.js';\nimport { genId } from '../../shared/id.js';\nimport { noopLogger } from '../../shared/logger.js';\nimport { resolveChannels } from '../../shared/notification-settings.js';\nimport type {\n ChannelRegistry,\n DomainEvent,\n Logger,\n NotificationTypeRegistry,\n} from '../../shared/ports.js';\nimport type {\n NotificationCategory,\n Reason,\n RecipientList,\n StoredSetting,\n} from '../../types/index.js';\nimport type {\n AutomationEngine,\n ChatDebounce,\n NewDelivery,\n NewNotification,\n NotificationCategories,\n NotificationReads,\n NotificationsRepository,\n RecipientRule,\n} from '../ports.js';\n\nexport const IN_APP = 'inApp';\nexport const PUSH = 'push';\nexport const MESSAGE_SENT = 'message.sent';\nexport const DEVICE_WINDOW_MS = 30 * 24 * 60 * 60 * 1000;\n\nconst noChatDebounce: ChatDebounce = {\n arm: async () => {},\n mentioned: async () => {},\n};\n\ntype Recipient = NonNullable<RecipientList['recipients']>[number];\n\ninterface Pair {\n list: RecipientList;\n type: string;\n reasons: Reason[];\n}\n\ninterface Person {\n orgUserId: string;\n userId: string;\n pairs: Pair[];\n}\n\ninterface Group {\n communityId: string | null;\n lists: RecipientList[];\n}\n\ninterface Filed {\n notification: NewNotification;\n deliveries: NewDelivery[];\n}\n\nexport type NotificationProcessorParams = {\n rules: Record<string, RecipientRule>;\n reads: NotificationReads;\n types: Pick<NotificationTypeRegistry, 'get'>;\n channels: Pick<ChannelRegistry, 'list' | 'get'>;\n categories: NotificationCategories;\n settings: Pick<NotificationSettingsService, 'getMany'>;\n devices: Pick<DevicesService, 'withActiveDevices'>;\n repo: Pick<NotificationsRepository, 'insert' | 'pendingDeliveriesForEvent'>;\n engine: Pick<AutomationEngine, 'deliver'>;\n debounce?: ChatDebounce;\n now?: () => Date;\n logger?: Logger;\n};\n\nfunction sameKeys(a: string[], b: string[]): boolean {\n const left = new Set(a);\n const right = new Set(b);\n return left.size === right.size && [...left].every((key) => right.has(key));\n}\n\nexport function chatOf(refs: Pick<RecipientList, 'subject' | 'parent'>): string | null {\n if (refs.parent?.type === 'chat') {\n return refs.parent.id;\n }\n return refs.subject?.type === 'chat' ? refs.subject.id : null;\n}\n\nexport class NotificationProcessor {\n private readonly rules: Record<string, RecipientRule>;\n private readonly reads: NotificationReads;\n private readonly types: Pick<NotificationTypeRegistry, 'get'>;\n private readonly channels: Pick<ChannelRegistry, 'list' | 'get'>;\n private readonly categories: NotificationCategories;\n private readonly settings: Pick<NotificationSettingsService, 'getMany'>;\n private readonly devices: Pick<DevicesService, 'withActiveDevices'>;\n private readonly repo: Pick<NotificationsRepository, 'insert' | 'pendingDeliveriesForEvent'>;\n private readonly engine: Pick<AutomationEngine, 'deliver'>;\n private readonly debounce: ChatDebounce;\n private readonly now: () => Date;\n private readonly logger: Logger;\n\n constructor(params: NotificationProcessorParams) {\n this.rules = params.rules;\n this.reads = params.reads;\n this.types = params.types;\n this.channels = params.channels;\n this.categories = params.categories;\n this.settings = params.settings;\n this.devices = params.devices;\n this.repo = params.repo;\n this.engine = params.engine;\n this.debounce = params.debounce ?? noChatDebounce;\n this.now = params.now ?? (() => new Date());\n this.logger = params.logger ?? noopLogger;\n }\n\n async process(event: DomainEvent): Promise<void> {\n const rule = Object.prototype.hasOwnProperty.call(this.rules, event.type)\n ? this.rules[event.type]\n : undefined;\n if (!rule) {\n return;\n }\n const lists = await rule(event, this.reads);\n await this.file(event.orgId, event.id, lists, {\n occurredAt: new Date(event.occurredAt),\n debounce: true,\n });\n }\n\n async file(\n orgId: string,\n sourceEventId: string,\n lists: RecipientList[],\n options: { occurredAt: Date; debounce: boolean },\n ): Promise<void> {\n const valid = lists.filter((list) => this.isValid(orgId, sourceEventId, list));\n const filed: Filed[] = [];\n for (const group of this.groups(valid)) {\n const categories = await this.categoriesOf(orgId, sourceEventId, group.communityId);\n if (!categories) {\n continue;\n }\n const expanded: RecipientList[] = [];\n for (const list of group.lists) {\n const ready = list.audience ? await this.expand(orgId, list, categories) : list;\n if (options.debounce && ready.type === MESSAGE_SENT) {\n await this.debounce.arm(orgId, ready, options.occurredAt);\n continue;\n }\n expanded.push(ready);\n }\n filed.push(\n ...(await this.match(orgId, sourceEventId, group.communityId, expanded, categories)),\n );\n }\n if (filed.length > 0) {\n await this.repo.insert(\n orgId,\n filed.map((entry) => entry.notification),\n filed.flatMap((entry) => entry.deliveries),\n );\n this.logger.info('notifications filed', {\n orgId,\n sourceEventId,\n count: filed.length,\n types: [...new Set(filed.map((entry) => entry.notification.type))],\n });\n await this.mentionsInChats(orgId, filed);\n }\n await this.dispatch(orgId, sourceEventId);\n }\n\n private isValid(orgId: string, sourceEventId: string, list: RecipientList): boolean {\n const drop = (reason: string): false => {\n this.logger.error('notification recipient list dropped', {\n orgId,\n sourceEventId,\n type: list.type,\n scope: list.scope,\n reason,\n });\n return false;\n };\n const declaration = this.types.get(list.type);\n if (!declaration) {\n return drop('unknown type');\n }\n if (!declaration.scopes.includes(list.scope)) {\n return drop('scope not declared');\n }\n const keys = declaration.display[list.scope] ?? [];\n if (!sameKeys(Object.keys(list.display), keys)) {\n return drop('display keys do not match the declaration');\n }\n if ((list.recipients === undefined) === (list.audience === undefined)) {\n return drop('a list needs either recipients or an audience');\n }\n const reasons = list.audience\n ? [list.audience.reason]\n : (list.recipients ?? []).flatMap((recipient) => recipient.reasons);\n if (reasons.some((reason) => !declaration.reasons.includes(reason))) {\n return drop('reason not allowed for the type');\n }\n if (list.audience && list.communityId === null) {\n return drop('an audience needs a community');\n }\n return true;\n }\n\n private groups(lists: RecipientList[]): Group[] {\n const byCommunity = new Map<string | null, Group>();\n for (const list of lists) {\n const group = byCommunity.get(list.communityId) ?? {\n communityId: list.communityId,\n lists: [],\n };\n group.lists.push(list);\n byCommunity.set(list.communityId, group);\n }\n return [...byCommunity.values()];\n }\n\n private async categoriesOf(\n orgId: string,\n sourceEventId: string,\n communityId: string | null,\n ): Promise<NotificationCategory[] | null> {\n try {\n return await this.categories.listFor(orgId, communityId);\n } catch (err) {\n if (!(err instanceof NotFoundError)) {\n throw err;\n }\n this.logger.warn('notification group dropped: the community no longer exists', {\n orgId,\n sourceEventId,\n communityId,\n });\n return null;\n }\n }\n\n private async expand(\n orgId: string,\n list: RecipientList,\n categories: NotificationCategory[],\n ): Promise<RecipientList> {\n const audience = list.audience!;\n const communityId = list.communityId!;\n const category = categories.find((candidate) =>\n candidate.covers.some(\n (cover) => cover.type === list.type && cover.reasons.includes(audience.reason),\n ),\n );\n let orgUserIds: string[] = [];\n if (category) {\n const onByDefault = resolveChannels(\n category,\n [],\n audience.spaceId,\n this.channels.list(),\n ).some((channel) => channel.enabled);\n if (onByDefault) {\n orgUserIds = await this.reads.spaceOpeners(orgId, audience.spaceId);\n } else {\n const switched = await this.reads.switchedOn(\n orgId,\n communityId,\n category.key,\n audience.spaceId,\n );\n orgUserIds =\n switched.length > 0\n ? await this.reads.spaceOpenersAmong(orgId, audience.spaceId, switched)\n : [];\n }\n }\n const excluded = new Set(audience.exclude);\n const candidates = [...new Set(orgUserIds)].filter((id) => !excluded.has(id));\n const muted =\n candidates.length > 0\n ? new Set(\n await this.reads.mutedAmong(\n orgId,\n { type: 'space', id: audience.spaceId },\n candidates,\n this.now(),\n ),\n )\n : new Set<string>();\n const kept = candidates.filter((id) => !muted.has(id));\n const userIds = kept.length > 0 ? await this.reads.userIdsOf(orgId, kept) : {};\n const recipients: Recipient[] = kept.flatMap((orgUserId) => {\n const userId = userIds[orgUserId];\n return userId ? [{ orgUserId, userId, reasons: [audience.reason] }] : [];\n });\n const { audience: _audience, ...rest } = list;\n return { ...rest, recipients };\n }\n\n private collect(lists: RecipientList[]): Person[] {\n const people = new Map<string, Person>();\n for (const list of lists) {\n for (const recipient of list.recipients ?? []) {\n const person = people.get(recipient.userId) ?? {\n orgUserId: recipient.orgUserId,\n userId: recipient.userId,\n pairs: [],\n };\n person.pairs.push({ list, type: list.type, reasons: [...new Set(recipient.reasons)] });\n people.set(recipient.userId, person);\n }\n }\n return [...people.values()];\n }\n\n private async match(\n orgId: string,\n sourceEventId: string,\n communityId: string | null,\n lists: RecipientList[],\n categories: NotificationCategory[],\n ): Promise<Filed[]> {\n const people = this.collect(lists);\n if (people.length === 0 || categories.length === 0) {\n return [];\n }\n const settings = await this.settings.getMany(\n orgId,\n people.map((person) => person.userId),\n communityId ? { communityId } : 'org',\n categories.map((category) => category.key),\n );\n const configured = this.channels.list();\n const withDevice = await this.peopleWithDevice(\n orgId,\n people,\n categories,\n settings,\n configured,\n );\n const filed: Filed[] = [];\n for (const person of people) {\n const entry = this.fileOne(\n sourceEventId,\n communityId,\n person,\n categories,\n settings[person.userId] ?? [],\n configured,\n withDevice.has(person.userId),\n );\n if (entry) {\n filed.push(entry);\n }\n }\n return filed;\n }\n\n private async peopleWithDevice(\n orgId: string,\n people: Person[],\n categories: NotificationCategory[],\n settings: Record<string, StoredSetting[]>,\n configured: string[],\n ): Promise<Set<string>> {\n const wanting = people.filter((person) =>\n categories.some((category) =>\n this.coveredPairs(category, person.pairs).some(({ pair }) =>\n resolveChannels(\n category,\n settings[person.userId] ?? [],\n pair.list.spaceId,\n configured,\n ).some((channel) => channel.enabled && channel.channel === PUSH),\n ),\n ),\n );\n if (wanting.length === 0) {\n return new Set();\n }\n const since = new Date(this.now().getTime() - DEVICE_WINDOW_MS);\n return new Set(\n await this.devices.withActiveDevices(\n orgId,\n wanting.map((person) => person.userId),\n since,\n ),\n );\n }\n\n private fileOne(\n sourceEventId: string,\n communityId: string | null,\n person: Person,\n categories: NotificationCategory[],\n settings: StoredSetting[],\n configured: string[],\n hasDevice: boolean,\n ): Filed | null {\n for (const category of categories) {\n for (const { pair, cover } of this.coveredPairs(category, person.pairs)) {\n let on = resolveChannels(category, settings, pair.list.spaceId, configured)\n .filter((channel) => channel.enabled)\n .map((channel) => channel.channel);\n if (!hasDevice) {\n on = on.filter((channel) => channel !== PUSH);\n }\n if (on.length > 0) {\n return this.filed(sourceEventId, communityId, person, category, pair, cover, on);\n }\n }\n }\n return null;\n }\n\n private filed(\n sourceEventId: string,\n communityId: string | null,\n person: Person,\n category: NotificationCategory,\n pair: Pair,\n cover: NotificationCategory['covers'][number],\n on: string[],\n ): Filed {\n const list = pair.list;\n const notificationId = genId('notification');\n const reasons = [...new Set(person.pairs.flatMap((candidate) => candidate.reasons))];\n return {\n notification: {\n id: notificationId,\n userId: person.userId,\n sourceEventId,\n type: pair.type,\n scope: list.scope,\n reasons,\n matchedReason: cover.reasons.find((reason) => pair.reasons.includes(reason))!,\n category: category.key,\n communityId,\n spaceId: list.spaceId,\n actorOrgUserId: list.actorOrgUserId,\n subject: list.subject,\n parent: list.parent,\n display: list.display,\n inApp: on.includes(IN_APP),\n createdAt: this.now(),\n },\n deliveries: on\n .filter((channel) => channel !== IN_APP && this.channels.get(channel) !== undefined)\n .map((channel) => ({\n id: genId('notificationDelivery'),\n notificationId,\n channel,\n })),\n };\n }\n\n private coveredPairs(\n category: NotificationCategory,\n pairs: Pair[],\n ): { pair: Pair; cover: NotificationCategory['covers'][number] }[] {\n const covered: { pair: Pair; cover: NotificationCategory['covers'][number] }[] = [];\n for (const pair of pairs) {\n if (category.perSpace === true && pair.list.spaceId === null) {\n continue;\n }\n const cover = category.covers.find(\n (candidate) =>\n candidate.type === pair.type &&\n candidate.reasons.some((reason) => pair.reasons.includes(reason)),\n );\n if (cover) {\n covered.push({ pair, cover });\n }\n }\n return covered;\n }\n\n private async mentionsInChats(orgId: string, filed: Filed[]): Promise<void> {\n for (const { notification } of filed) {\n if (notification.type !== 'mention' || notification.scope !== 'chat') {\n continue;\n }\n const chatId = chatOf(notification);\n if (chatId) {\n await this.debounce.mentioned(orgId, notification.userId, chatId);\n }\n }\n }\n\n private async dispatch(orgId: string, sourceEventId: string): Promise<void> {\n const pending = await this.repo.pendingDeliveriesForEvent(orgId, sourceEventId);\n for (const delivery of pending) {\n await this.engine.deliver({ orgId, deliveryId: delivery.id });\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { contentEvents } from "../../../content/events.js";
|
|
2
|
+
import { Reasons, actorOr, eventAt, listOf, recipientsOf } from "./recipients.js";
|
|
3
|
+
//#region src/automations/notifications/rules/content.ts
|
|
4
|
+
const activityPublished = async (event, reads) => {
|
|
5
|
+
const parsed = contentEvents.activityPublished.safeParse(event);
|
|
6
|
+
if (!parsed.success) return [];
|
|
7
|
+
const { activityId, courseId } = parsed.data.data;
|
|
8
|
+
const orgId = event.orgId;
|
|
9
|
+
const activity = await reads.activity(orgId, activityId);
|
|
10
|
+
if (!activity) return [];
|
|
11
|
+
return listOf({
|
|
12
|
+
type: "course.activity_added",
|
|
13
|
+
scope: "org",
|
|
14
|
+
actorOrgUserId: actorOr(event, null),
|
|
15
|
+
communityId: null,
|
|
16
|
+
spaceId: null,
|
|
17
|
+
subject: {
|
|
18
|
+
type: "activity",
|
|
19
|
+
id: activityId
|
|
20
|
+
},
|
|
21
|
+
parent: {
|
|
22
|
+
type: "course",
|
|
23
|
+
id: courseId
|
|
24
|
+
},
|
|
25
|
+
display: {
|
|
26
|
+
courseTitle: activity.courseTitle,
|
|
27
|
+
moduleTitle: activity.moduleTitle,
|
|
28
|
+
activityTitle: activity.title
|
|
29
|
+
}
|
|
30
|
+
}, await recipientsOf(reads, orgId, new Reasons().add(await reads.courseHolders(orgId, courseId), "course_member"), {
|
|
31
|
+
exclude: [actorOr(event, null)],
|
|
32
|
+
at: eventAt(event)
|
|
33
|
+
}));
|
|
34
|
+
};
|
|
35
|
+
const CONTENT_RULES = { "content.course.activity.published": activityPublished };
|
|
36
|
+
//#endregion
|
|
37
|
+
export { CONTENT_RULES };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.js","names":[],"sources":["../../../../src/automations/notifications/rules/content.ts"],"sourcesContent":["import { contentEvents } from '../../../content/index.js';\nimport type { RecipientRule } from '../../ports.js';\nimport { actorOr, eventAt, listOf, Reasons, recipientsOf } from './recipients.js';\n\nconst activityPublished: RecipientRule = async (event, reads) => {\n const parsed = contentEvents.activityPublished.safeParse(event);\n if (!parsed.success) {\n return [];\n }\n const { activityId, courseId } = parsed.data.data;\n const orgId = event.orgId;\n const activity = await reads.activity(orgId, activityId);\n if (!activity) {\n return [];\n }\n return listOf(\n {\n type: 'course.activity_added',\n scope: 'org',\n actorOrgUserId: actorOr(event, null),\n communityId: null,\n spaceId: null,\n subject: { type: 'activity', id: activityId },\n parent: { type: 'course', id: courseId },\n display: {\n courseTitle: activity.courseTitle,\n moduleTitle: activity.moduleTitle,\n activityTitle: activity.title,\n },\n },\n await recipientsOf(\n reads,\n orgId,\n new Reasons().add(await reads.courseHolders(orgId, courseId), 'course_member'),\n { exclude: [actorOr(event, null)], at: eventAt(event) },\n ),\n );\n};\n\nexport const CONTENT_RULES: Record<string, RecipientRule> = {\n 'content.course.activity.published': activityPublished,\n};\n"],"mappings":";;;AAIA,MAAM,oBAAmC,OAAO,OAAO,UAAU;CAC/D,MAAM,SAAS,cAAc,kBAAkB,UAAU,KAAK;CAC9D,IAAI,CAAC,OAAO,SACV,OAAO,CAAC;CAEV,MAAM,EAAE,YAAY,aAAa,OAAO,KAAK;CAC7C,MAAM,QAAQ,MAAM;CACpB,MAAM,WAAW,MAAM,MAAM,SAAS,OAAO,UAAU;CACvD,IAAI,CAAC,UACH,OAAO,CAAC;CAEV,OAAO,OACL;EACE,MAAM;EACN,OAAO;EACP,gBAAgB,QAAQ,OAAO,IAAI;EACnC,aAAa;EACb,SAAS;EACT,SAAS;GAAE,MAAM;GAAY,IAAI;EAAW;EAC5C,QAAQ;GAAE,MAAM;GAAU,IAAI;EAAS;EACvC,SAAS;GACP,aAAa,SAAS;GACtB,aAAa,SAAS;GACtB,eAAe,SAAS;EAC1B;CACF,GACA,MAAM,aACJ,OACA,OACA,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,MAAM,cAAc,OAAO,QAAQ,GAAG,eAAe,GAC7E;EAAE,SAAS,CAAC,QAAQ,OAAO,IAAI,CAAC;EAAG,IAAI,QAAQ,KAAK;CAAE,CACxD,CACF;AACF;AAEA,MAAa,gBAA+C,EAC1D,qCAAqC,kBACvC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { RecipientRule } from "../../ports.js";
|
|
2
|
+
//#region src/automations/notifications/rules/discussion.d.ts
|
|
3
|
+
declare function messageSummary(count: number): string;
|
|
4
|
+
declare const DISCUSSION_RULES: Record<string, RecipientRule>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { DISCUSSION_RULES, messageSummary };
|
|
7
|
+
//# sourceMappingURL=discussion.d.ts.map
|