@headless-lms/core 0.3.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.
Files changed (205) hide show
  1. package/dist/assets/events.d.ts +9 -9
  2. package/dist/assets/index.d.ts +3 -2
  3. package/dist/assets/index.js +2 -1
  4. package/dist/assets/jobs.d.ts +15 -0
  5. package/dist/assets/jobs.js +30 -0
  6. package/dist/assets/jobs.js.map +1 -0
  7. package/dist/assets/ports.d.ts +4 -3
  8. package/dist/assets/service.d.ts +0 -1
  9. package/dist/assets/service.js +0 -4
  10. package/dist/assets/service.js.map +1 -1
  11. package/dist/automations/actions.js +2 -63
  12. package/dist/automations/actions.js.map +1 -1
  13. package/dist/automations/catalog.d.ts +1 -2
  14. package/dist/automations/catalog.js +69 -62
  15. package/dist/automations/catalog.js.map +1 -1
  16. package/dist/automations/community-derivations.js +4 -4
  17. package/dist/automations/community-derivations.js.map +1 -1
  18. package/dist/automations/community-mail.d.ts +3 -17
  19. package/dist/automations/community-mail.js +1 -21
  20. package/dist/automations/community-mail.js.map +1 -1
  21. package/dist/automations/events.d.ts +0 -10
  22. package/dist/automations/index.d.ts +11 -4
  23. package/dist/automations/index.js +8 -1
  24. package/dist/automations/notifications/debounce.d.ts +25 -0
  25. package/dist/automations/notifications/debounce.js +93 -0
  26. package/dist/automations/notifications/debounce.js.map +1 -0
  27. package/dist/automations/notifications/defaults.d.ts +8 -0
  28. package/dist/automations/notifications/defaults.js +316 -0
  29. package/dist/automations/notifications/defaults.js.map +1 -0
  30. package/dist/automations/notifications/delivery.d.ts +28 -0
  31. package/dist/automations/notifications/delivery.js +88 -0
  32. package/dist/automations/notifications/delivery.js.map +1 -0
  33. package/dist/automations/notifications/processor.d.ts +64 -0
  34. package/dist/automations/notifications/processor.js +270 -0
  35. package/dist/automations/notifications/processor.js.map +1 -0
  36. package/dist/automations/notifications/rules/content.d.ts +6 -0
  37. package/dist/automations/notifications/rules/content.js +39 -0
  38. package/dist/automations/notifications/rules/content.js.map +1 -0
  39. package/dist/automations/notifications/rules/discussion.d.ts +7 -0
  40. package/dist/automations/notifications/rules/discussion.js +678 -0
  41. package/dist/automations/notifications/rules/discussion.js.map +1 -0
  42. package/dist/automations/notifications/rules/entitlements.d.ts +6 -0
  43. package/dist/automations/notifications/rules/entitlements.js +249 -0
  44. package/dist/automations/notifications/rules/entitlements.js.map +1 -0
  45. package/dist/automations/notifications/rules/index.d.ts +6 -0
  46. package/dist/automations/notifications/rules/index.js +15 -0
  47. package/dist/automations/notifications/rules/index.js.map +1 -0
  48. package/dist/automations/notifications/rules/organizations.d.ts +6 -0
  49. package/dist/automations/notifications/rules/organizations.js +28 -0
  50. package/dist/automations/notifications/rules/organizations.js.map +1 -0
  51. package/dist/automations/notifications/rules/recipients.d.ts +32 -0
  52. package/dist/automations/notifications/rules/recipients.js +99 -0
  53. package/dist/automations/notifications/rules/recipients.js.map +1 -0
  54. package/dist/automations/notifications/service.d.ts +27 -0
  55. package/dist/automations/notifications/service.js +45 -0
  56. package/dist/automations/notifications/service.js.map +1 -0
  57. package/dist/automations/notifications/types.d.ts +7 -0
  58. package/dist/automations/notifications/types.js +473 -0
  59. package/dist/automations/notifications/types.js.map +1 -0
  60. package/dist/automations/ports.d.ts +119 -5
  61. package/dist/automations/service.d.ts +10 -13
  62. package/dist/automations/service.js +21 -83
  63. package/dist/automations/service.js.map +1 -1
  64. package/dist/content/events.d.ts +130 -80
  65. package/dist/content/events.js +11 -0
  66. package/dist/content/events.js.map +1 -1
  67. package/dist/content/index.d.ts +6 -4
  68. package/dist/content/index.js +3 -1
  69. package/dist/content/model.d.ts +2 -2
  70. package/dist/content/notification-categories.d.ts +36 -0
  71. package/dist/content/notification-categories.js +64 -0
  72. package/dist/content/notification-categories.js.map +1 -0
  73. package/dist/content/ports.d.ts +25 -6
  74. package/dist/content/service.d.ts +4 -4
  75. package/dist/content/service.js +24 -8
  76. package/dist/content/service.js.map +1 -1
  77. package/dist/content/titles.d.ts +22 -0
  78. package/dist/content/titles.js +69 -0
  79. package/dist/content/titles.js.map +1 -0
  80. package/dist/content/types.d.ts +2 -2
  81. package/dist/discussion/chats.d.ts +9 -4
  82. package/dist/discussion/chats.js +80 -47
  83. package/dist/discussion/chats.js.map +1 -1
  84. package/dist/discussion/digests.d.ts +24 -0
  85. package/dist/discussion/digests.js +104 -0
  86. package/dist/discussion/digests.js.map +1 -0
  87. package/dist/discussion/events.d.ts +194 -28
  88. package/dist/discussion/events.js +74 -17
  89. package/dist/discussion/events.js.map +1 -1
  90. package/dist/discussion/index.d.ts +7 -2
  91. package/dist/discussion/index.js +6 -1
  92. package/dist/discussion/jobs.d.ts +16 -0
  93. package/dist/discussion/jobs.js +40 -0
  94. package/dist/discussion/jobs.js.map +1 -0
  95. package/dist/discussion/mentions.d.ts +11 -0
  96. package/dist/discussion/mentions.js +35 -0
  97. package/dist/discussion/mentions.js.map +1 -0
  98. package/dist/discussion/ports.d.ts +107 -24
  99. package/dist/discussion/ports.js.map +1 -1
  100. package/dist/discussion/posts.d.ts +18 -8
  101. package/dist/discussion/posts.js +207 -155
  102. package/dist/discussion/posts.js.map +1 -1
  103. package/dist/discussion/service.d.ts +17 -8
  104. package/dist/discussion/service.js +105 -41
  105. package/dist/discussion/service.js.map +1 -1
  106. package/dist/discussion/subscriptions.d.ts +37 -0
  107. package/dist/discussion/subscriptions.js +99 -0
  108. package/dist/discussion/subscriptions.js.map +1 -0
  109. package/dist/discussion/titles.d.ts +26 -0
  110. package/dist/discussion/titles.js +84 -0
  111. package/dist/discussion/titles.js.map +1 -0
  112. package/dist/entitlements/events.d.ts +20 -20
  113. package/dist/entitlements/index.d.ts +2 -1
  114. package/dist/entitlements/index.js +2 -1
  115. package/dist/entitlements/jobs.d.ts +12 -0
  116. package/dist/entitlements/jobs.js +18 -0
  117. package/dist/entitlements/jobs.js.map +1 -0
  118. package/dist/entitlements/ports.d.ts +7 -3
  119. package/dist/entitlements/service.d.ts +5 -2
  120. package/dist/entitlements/service.js +23 -3
  121. package/dist/entitlements/service.js.map +1 -1
  122. package/dist/identity/devices.d.ts +30 -0
  123. package/dist/identity/devices.js +107 -0
  124. package/dist/identity/devices.js.map +1 -0
  125. package/dist/identity/events.d.ts +2 -2
  126. package/dist/identity/index.d.ts +5 -4
  127. package/dist/identity/index.js +3 -2
  128. package/dist/identity/model.d.ts +2 -2
  129. package/dist/identity/notification-settings.d.ts +28 -0
  130. package/dist/identity/notification-settings.js +113 -0
  131. package/dist/identity/notification-settings.js.map +1 -0
  132. package/dist/identity/ports.d.ts +78 -54
  133. package/dist/integrations/events.d.ts +3 -3
  134. package/dist/organizations/events.d.ts +32 -32
  135. package/dist/organizations/index.d.ts +2 -1
  136. package/dist/organizations/index.js +2 -1
  137. package/dist/organizations/notification-categories.d.ts +32 -0
  138. package/dist/organizations/notification-categories.js +51 -0
  139. package/dist/organizations/notification-categories.js.map +1 -0
  140. package/dist/organizations/ports.d.ts +3 -0
  141. package/dist/organizations/service.d.ts +2 -0
  142. package/dist/organizations/service.js +7 -0
  143. package/dist/organizations/service.js.map +1 -1
  144. package/dist/progress/events.d.ts +14 -14
  145. package/dist/reporting/member/index.d.ts +2 -2
  146. package/dist/reporting/member/model.d.ts +56 -23
  147. package/dist/reporting/member/ports.d.ts +9 -6
  148. package/dist/reporting/member/service.d.ts +33 -14
  149. package/dist/reporting/member/service.js +138 -60
  150. package/dist/reporting/member/service.js.map +1 -1
  151. package/dist/shared/errors.d.ts +13 -1
  152. package/dist/shared/errors.js +18 -1
  153. package/dist/shared/errors.js.map +1 -1
  154. package/dist/shared/id.d.ts +3 -0
  155. package/dist/shared/id.js +4 -1
  156. package/dist/shared/id.js.map +1 -1
  157. package/dist/shared/mailer.d.ts +11 -1
  158. package/dist/shared/mailer.js +32 -5
  159. package/dist/shared/mailer.js.map +1 -1
  160. package/dist/shared/notification-categories.d.ts +16 -0
  161. package/dist/shared/notification-categories.js +94 -0
  162. package/dist/shared/notification-categories.js.map +1 -0
  163. package/dist/shared/notification-settings.d.ts +14 -0
  164. package/dist/shared/notification-settings.js +61 -0
  165. package/dist/shared/notification-settings.js.map +1 -0
  166. package/dist/shared/ports.d.ts +48 -2
  167. package/dist/shared/settings.d.ts +2 -1
  168. package/dist/shared/settings.js +1 -0
  169. package/dist/shared/settings.js.map +1 -1
  170. package/dist/types/automations.d.ts +8 -2
  171. package/dist/types/content.d.ts +2 -2
  172. package/dist/types/discussion.d.ts +2 -2
  173. package/dist/types/email-templates.d.ts +97 -3
  174. package/dist/types/identity.d.ts +2 -2
  175. package/dist/types/index.d.ts +7 -6
  176. package/dist/types/notifications.d.ts +157 -0
  177. package/dist/types/notifications.js +1 -0
  178. package/dist/types/schemas/automations.d.ts +1 -10
  179. package/dist/types/schemas/automations.js +1 -3
  180. package/dist/types/schemas/automations.js.map +1 -1
  181. package/dist/types/schemas/content.d.ts +62 -1
  182. package/dist/types/schemas/content.js +25 -3
  183. package/dist/types/schemas/content.js.map +1 -1
  184. package/dist/types/schemas/discussion.d.ts +85 -12
  185. package/dist/types/schemas/discussion.js +47 -13
  186. package/dist/types/schemas/discussion.js.map +1 -1
  187. package/dist/types/schemas/identity.d.ts +1 -85
  188. package/dist/types/schemas/identity.js +1 -42
  189. package/dist/types/schemas/identity.js.map +1 -1
  190. package/dist/types/schemas/index.d.ts +6 -5
  191. package/dist/types/schemas/index.js +6 -5
  192. package/dist/types/schemas/notifications.d.ts +95 -0
  193. package/dist/types/schemas/notifications.js +56 -0
  194. package/dist/types/schemas/notifications.js.map +1 -0
  195. package/dist/types/schemas/progress.d.ts +3 -3
  196. package/package.json +1 -1
  197. package/dist/automations/community-notifications.d.ts +0 -16
  198. package/dist/automations/community-notifications.js +0 -84
  199. package/dist/automations/community-notifications.js.map +0 -1
  200. package/dist/automations/seeds.d.ts +0 -17
  201. package/dist/automations/seeds.js +0 -235
  202. package/dist/automations/seeds.js.map +0 -1
  203. package/dist/identity/notifications.d.ts +0 -27
  204. package/dist/identity/notifications.js +0 -101
  205. package/dist/identity/notifications.js.map +0 -1
@@ -0,0 +1,113 @@
1
+ import { NotFoundError, ValidationError } from "../shared/errors.js";
2
+ import { noopLogger } from "../shared/logger.js";
3
+ //#region src/identity/notification-settings.ts
4
+ function communityOf(scope) {
5
+ return scope === "org" ? null : scope.communityId;
6
+ }
7
+ function keyOf(key) {
8
+ return JSON.stringify([
9
+ key.communityId,
10
+ key.spaceId,
11
+ key.category,
12
+ key.channel
13
+ ]);
14
+ }
15
+ var NotificationSettingsServiceImpl = class {
16
+ repo;
17
+ categories;
18
+ access;
19
+ channels;
20
+ logger;
21
+ constructor(params) {
22
+ this.repo = params.repo;
23
+ this.categories = params.categories;
24
+ this.access = params.access;
25
+ this.channels = params.channels;
26
+ this.logger = params.logger ?? noopLogger;
27
+ }
28
+ async get(orgId, userId, scope) {
29
+ return (await this.repo.forUsers(orgId, [userId], communityOf(scope), null)).map(({ userId: _userId, ...setting }) => setting);
30
+ }
31
+ async getMany(orgId, userIds, scope, categories) {
32
+ const unique = [...new Set(userIds)];
33
+ const result = Object.fromEntries(unique.map((userId) => [userId, []]));
34
+ if (unique.length === 0 || categories.length === 0) return result;
35
+ const rows = await this.repo.forUsers(orgId, unique, communityOf(scope), categories);
36
+ for (const { userId, ...setting } of rows) result[userId]?.push(setting);
37
+ return result;
38
+ }
39
+ async set(orgId, actor, scope, changes) {
40
+ const communityId = communityOf(scope);
41
+ let list;
42
+ if (communityId === null) list = await this.categories.orgList(orgId);
43
+ else {
44
+ if (!await this.access.canOpen(orgId, actor, communityId)) throw new NotFoundError("Community", communityId);
45
+ list = await this.categories.communityList(orgId, communityId);
46
+ }
47
+ const configured = this.channels.list();
48
+ const problems = [];
49
+ const byKey = /* @__PURE__ */ new Map();
50
+ changes.forEach((change, index) => {
51
+ const at = `settings[${index}]`;
52
+ const category = list.find((candidate) => candidate.key === change.category);
53
+ if (!category) {
54
+ problems.push({
55
+ path: `${at}.category`,
56
+ message: `there is no category ${change.category}`
57
+ });
58
+ return;
59
+ }
60
+ const offered = category.channels[change.channel];
61
+ if (!offered || !configured.includes(change.channel)) {
62
+ problems.push({
63
+ path: `${at}.channel`,
64
+ message: `${change.category} does not offer the channel ${change.channel}`
65
+ });
66
+ return;
67
+ }
68
+ if (offered.locked) {
69
+ problems.push({
70
+ path: `${at}.channel`,
71
+ message: `the channel ${change.channel} of ${change.category} is locked`
72
+ });
73
+ return;
74
+ }
75
+ if (change.spaceId !== void 0 && category.perSpace !== true) {
76
+ problems.push({
77
+ path: `${at}.spaceId`,
78
+ message: `${change.category} cannot be set per space`
79
+ });
80
+ return;
81
+ }
82
+ const key = {
83
+ communityId,
84
+ spaceId: change.spaceId ?? null,
85
+ category: change.category,
86
+ channel: change.channel
87
+ };
88
+ byKey.set(keyOf(key), {
89
+ key,
90
+ enabled: change.enabled
91
+ });
92
+ });
93
+ if (problems.length > 0) throw new ValidationError(problems);
94
+ const entries = [...byKey.values()];
95
+ await this.repo.apply(orgId, actor.userId, entries.flatMap(({ key, enabled }) => enabled === null ? [] : [{
96
+ ...key,
97
+ enabled
98
+ }]), entries.flatMap(({ key, enabled }) => enabled === null ? [key] : []), /* @__PURE__ */ new Date());
99
+ this.logger.info("notification settings changed", {
100
+ orgId,
101
+ userId: actor.userId,
102
+ communityId,
103
+ count: entries.length
104
+ });
105
+ }
106
+ switchedOn(orgId, communityId, category, spaceId) {
107
+ return this.repo.switchedOn(orgId, communityId, category, spaceId);
108
+ }
109
+ };
110
+ //#endregion
111
+ export { NotificationSettingsServiceImpl };
112
+
113
+ //# sourceMappingURL=notification-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-settings.js","names":[],"sources":["../../src/identity/notification-settings.ts"],"sourcesContent":["import type { NotificationCategory, SettingsScope, StoredSetting } from '../types/index.js';\nimport type { ChannelRegistry, Logger } from '../shared/ports.js';\nimport { NotFoundError, ValidationError, type ValidationProblem } from '../shared/errors.js';\nimport { noopLogger } from '../shared/logger.js';\nimport type {\n CommunityAccessReader,\n MemberActor,\n NotificationCategoryReader,\n NotificationSettingsRepository,\n NotificationSettingsService,\n SettingChange,\n SettingKey,\n} from './ports.js';\n\nfunction communityOf(scope: SettingsScope): string | null {\n return scope === 'org' ? null : scope.communityId;\n}\n\nfunction keyOf(key: SettingKey): string {\n return JSON.stringify([key.communityId, key.spaceId, key.category, key.channel]);\n}\n\nexport type NotificationSettingsServiceParams = {\n repo: NotificationSettingsRepository;\n categories: NotificationCategoryReader;\n access: CommunityAccessReader;\n channels: Pick<ChannelRegistry, 'list'>;\n logger?: Logger;\n};\n\nexport class NotificationSettingsServiceImpl implements NotificationSettingsService {\n private readonly repo: NotificationSettingsRepository;\n private readonly categories: NotificationCategoryReader;\n private readonly access: CommunityAccessReader;\n private readonly channels: Pick<ChannelRegistry, 'list'>;\n private readonly logger: Logger;\n\n constructor(params: NotificationSettingsServiceParams) {\n this.repo = params.repo;\n this.categories = params.categories;\n this.access = params.access;\n this.channels = params.channels;\n this.logger = params.logger ?? noopLogger;\n }\n\n async get(orgId: string, userId: string, scope: SettingsScope): Promise<StoredSetting[]> {\n const rows = await this.repo.forUsers(orgId, [userId], communityOf(scope), null);\n return rows.map(({ userId: _userId, ...setting }) => setting);\n }\n\n async getMany(\n orgId: string,\n userIds: string[],\n scope: SettingsScope,\n categories: string[],\n ): Promise<Record<string, StoredSetting[]>> {\n const unique = [...new Set(userIds)];\n const result: Record<string, StoredSetting[]> = Object.fromEntries(\n unique.map((userId) => [userId, []]),\n );\n if (unique.length === 0 || categories.length === 0) {\n return result;\n }\n const rows = await this.repo.forUsers(orgId, unique, communityOf(scope), categories);\n for (const { userId, ...setting } of rows) {\n result[userId]?.push(setting);\n }\n return result;\n }\n\n async set(\n orgId: string,\n actor: MemberActor,\n scope: SettingsScope,\n changes: SettingChange[],\n ): Promise<void> {\n const communityId = communityOf(scope);\n let list: NotificationCategory[];\n if (communityId === null) {\n list = await this.categories.orgList(orgId);\n } else {\n if (!(await this.access.canOpen(orgId, actor, communityId))) {\n throw new NotFoundError('Community', communityId);\n }\n list = await this.categories.communityList(orgId, communityId);\n }\n const configured = this.channels.list();\n const problems: ValidationProblem[] = [];\n const byKey = new Map<string, { key: SettingKey; enabled: boolean | null }>();\n changes.forEach((change, index) => {\n const at = `settings[${index}]`;\n const category = list.find((candidate) => candidate.key === change.category);\n if (!category) {\n problems.push({\n path: `${at}.category`,\n message: `there is no category ${change.category}`,\n });\n return;\n }\n const offered = category.channels[change.channel];\n if (!offered || !configured.includes(change.channel)) {\n problems.push({\n path: `${at}.channel`,\n message: `${change.category} does not offer the channel ${change.channel}`,\n });\n return;\n }\n if (offered.locked) {\n problems.push({\n path: `${at}.channel`,\n message: `the channel ${change.channel} of ${change.category} is locked`,\n });\n return;\n }\n if (change.spaceId !== undefined && category.perSpace !== true) {\n problems.push({\n path: `${at}.spaceId`,\n message: `${change.category} cannot be set per space`,\n });\n return;\n }\n const key: SettingKey = {\n communityId,\n spaceId: change.spaceId ?? null,\n category: change.category,\n channel: change.channel,\n };\n byKey.set(keyOf(key), { key, enabled: change.enabled });\n });\n if (problems.length > 0) {\n throw new ValidationError(problems);\n }\n const entries = [...byKey.values()];\n await this.repo.apply(\n orgId,\n actor.userId,\n entries.flatMap(({ key, enabled }) => (enabled === null ? [] : [{ ...key, enabled }])),\n entries.flatMap(({ key, enabled }) => (enabled === null ? [key] : [])),\n new Date(),\n );\n this.logger.info('notification settings changed', {\n orgId,\n userId: actor.userId,\n communityId,\n count: entries.length,\n });\n }\n\n switchedOn(\n orgId: string,\n communityId: string,\n category: string,\n spaceId: string | null,\n ): Promise<string[]> {\n return this.repo.switchedOn(orgId, communityId, category, spaceId);\n }\n}\n"],"mappings":"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"}
@@ -1,8 +1,10 @@
1
1
  import { JsonValue } from "../types/shared.js";
2
- import { CreateUserInput, Notification, NotificationKind, NotificationPreference, UpdateUserInput, UserProfile, UserProfileView } from "../types/schemas/identity.js";
2
+ import { CreateUserInput, UpdateUserInput, UserProfile, UserProfileView } from "../types/schemas/identity.js";
3
3
  import { User } from "../types/identity.js";
4
+ import { DeviceView, NotificationCategory, PushDevice, PushProvider, SettingsScope, StoredSetting } from "../types/notifications.js";
4
5
  import "../types/index.js";
5
6
  import { OutboxAppender, UnitOfWork } from "../shared/ports.js";
7
+ import { Actor } from "../shared/actor.js";
6
8
  import "./model.js";
7
9
  import "./types.js";
8
10
  //#region src/identity/ports.d.ts
@@ -55,44 +57,6 @@ interface SessionAdmin {
55
57
  /** Points the caller's current session at `orgId`. */
56
58
  stampActiveOrganization(headers: AuthHeaders, orgId: string): Promise<boolean>;
57
59
  }
58
- interface NewNotification {
59
- userId: string;
60
- kind: NotificationKind;
61
- actorOrgUserId: string | null;
62
- communityId: string | null;
63
- spaceId: string | null;
64
- postId: string | null;
65
- commentId: string | null;
66
- chatId: string | null;
67
- messageId: string | null;
68
- }
69
- interface NotificationsQuery {
70
- page: number;
71
- pageSize: number;
72
- }
73
- interface NotificationPage {
74
- rows: Notification[];
75
- total: number;
76
- page: number;
77
- pageSize: number;
78
- unreadCount: number;
79
- }
80
- /** What automations calls to tell people. */
81
- interface NotificationDesk {
82
- notify(orgId: string, notifications: NewNotification[]): Promise<void>;
83
- preferencesFor(orgId: string, userIds: string[], kind: NotificationKind): Promise<Record<string, NotificationPreference>>;
84
- }
85
- /** A person's own notifications and preferences. */
86
- interface NotificationInbox {
87
- listNotifications(orgId: string, userId: string, query: NotificationsQuery): Promise<NotificationPage>;
88
- markNotificationRead(orgId: string, userId: string, notificationId: string): Promise<Notification>;
89
- markAllNotificationsRead(orgId: string, userId: string): Promise<{
90
- updated: number;
91
- }>;
92
- notificationPreferences(orgId: string, userId: string): Promise<NotificationPreference[]>;
93
- setNotificationPreferences(orgId: string, userId: string, preferences: NotificationPreference[]): Promise<NotificationPreference[]>;
94
- }
95
- interface NotificationService extends NotificationDesk, NotificationInbox {}
96
60
  interface IdentityService extends UserProvisioner, UserResolver, UserEditor {
97
61
  profileOf(orgId: string, userId: string): Promise<UserProfileView>;
98
62
  displayName(userId: string | null): Promise<string>;
@@ -107,25 +71,85 @@ interface IdentityRepository {
107
71
  findProfile(userId: string): Promise<UserProfile | null>;
108
72
  upsertProfile(userId: string, patch: ProfilePatch): Promise<UserProfile>;
109
73
  }
110
- interface NotificationsRepository {
111
- insert(orgId: string, rows: Notification[]): Promise<void>;
112
- page(orgId: string, userId: string, query: NotificationsQuery): Promise<{
113
- rows: Notification[];
114
- total: number;
115
- unreadCount: number;
116
- }>;
117
- markRead(orgId: string, userId: string, notificationId: string, at: Date): Promise<Notification | null>;
118
- markAllRead(orgId: string, userId: string, at: Date): Promise<number>;
119
- preferences(orgId: string, userIds: string[]): Promise<(NotificationPreference & {
120
- userId: string;
121
- })[]>;
122
- upsertPreferences(orgId: string, userId: string, preferences: NotificationPreference[]): Promise<void>;
123
- }
124
74
  interface IdentityTxScope {
125
75
  identity: IdentityRepository;
126
76
  outbox: OutboxAppender;
127
77
  }
128
78
  type IdentityUnitOfWork = UnitOfWork<IdentityTxScope>;
79
+ type MemberActor = Actor & {
80
+ userId: string;
81
+ };
82
+ interface SettingChange {
83
+ category: string;
84
+ spaceId?: string;
85
+ channel: string;
86
+ enabled: boolean | null;
87
+ }
88
+ interface NotificationSettingsService {
89
+ get(orgId: string, userId: string, scope: SettingsScope): Promise<StoredSetting[]>;
90
+ getMany(orgId: string, userIds: string[], scope: SettingsScope, categories: string[]): Promise<Record<string, StoredSetting[]>>;
91
+ set(orgId: string, actor: MemberActor, scope: SettingsScope, changes: SettingChange[]): Promise<void>;
92
+ switchedOn(orgId: string, communityId: string, category: string, spaceId: string | null): Promise<string[]>;
93
+ }
94
+ interface NotificationCategoryReader {
95
+ communityList(orgId: string, communityId: string): Promise<NotificationCategory[]>;
96
+ orgList(orgId: string): Promise<NotificationCategory[]>;
97
+ }
98
+ interface CommunityAccessReader {
99
+ canOpen(orgId: string, actor: Actor, communityId: string): Promise<boolean>;
100
+ }
101
+ interface SettingKey {
102
+ communityId: string | null;
103
+ spaceId: string | null;
104
+ category: string;
105
+ channel: string;
106
+ }
107
+ interface NotificationSettingsRepository {
108
+ forUsers(orgId: string, userIds: string[], communityId: string | null, categories: string[] | null): Promise<(StoredSetting & {
109
+ userId: string;
110
+ })[]>;
111
+ apply(orgId: string, userId: string, upserts: (SettingKey & {
112
+ enabled: boolean;
113
+ })[], deletes: SettingKey[], at: Date): Promise<void>;
114
+ switchedOn(orgId: string, communityId: string, category: string, spaceId: string | null): Promise<string[]>;
115
+ }
116
+ interface DeviceRegistration {
117
+ provider: PushProvider;
118
+ token: string;
119
+ keys?: {
120
+ p256dh: string;
121
+ auth: string;
122
+ };
123
+ appId?: string;
124
+ environment?: 'production' | 'sandbox';
125
+ }
126
+ interface DevicesService {
127
+ register(orgId: string, actor: MemberActor, input: DeviceRegistration): Promise<PushDevice>;
128
+ list(orgId: string, actor: MemberActor): Promise<DeviceView[]>;
129
+ remove(orgId: string, actor: MemberActor, deviceId: string): Promise<void>;
130
+ activeDevices(orgId: string, userId: string, since: Date): Promise<PushDevice[]>;
131
+ withActiveDevices(orgId: string, userIds: string[], since: Date): Promise<string[]>;
132
+ removeDead(orgId: string, deviceIds: string[]): Promise<void>;
133
+ pushConfig(): {
134
+ vapidPublicKey: string | null;
135
+ };
136
+ }
137
+ interface NewDevice {
138
+ userId: string;
139
+ provider: PushProvider;
140
+ token: string;
141
+ p256dh: string | null;
142
+ auth: string | null;
143
+ appId: string | null;
144
+ environment: 'production' | 'sandbox' | null;
145
+ }
146
+ interface DevicesRepository {
147
+ upsert(orgId: string, device: NewDevice, at: Date): Promise<DeviceView>;
148
+ listForUser(orgId: string, userId: string, since: Date | null): Promise<DeviceView[]>;
149
+ usersWithDevices(orgId: string, userIds: string[], since: Date): Promise<string[]>;
150
+ deleteOwn(orgId: string, userId: string, deviceId: string): Promise<boolean>;
151
+ deleteMany(orgId: string, deviceIds: string[]): Promise<void>;
152
+ }
129
153
  //#endregion
130
- export { AuthHeaders, IdentityRepository, IdentityService, IdentityTxScope, IdentityUnitOfWork, NewNotification, NotificationDesk, NotificationInbox, NotificationPage, NotificationService, NotificationsQuery, NotificationsRepository, ProfilePatch, SessionAdmin, UpdateProfileInput, UserEditor, UserProvisioner, UserResolver };
154
+ export { AuthHeaders, CommunityAccessReader, DeviceRegistration, DevicesRepository, DevicesService, IdentityRepository, IdentityService, IdentityTxScope, IdentityUnitOfWork, MemberActor, NewDevice, NotificationCategoryReader, NotificationSettingsRepository, NotificationSettingsService, ProfilePatch, SessionAdmin, SettingChange, SettingKey, UpdateProfileInput, UserEditor, UserProvisioner, UserResolver };
131
155
  //# sourceMappingURL=ports.d.ts.map
@@ -22,8 +22,8 @@ declare const integrationEvents: {
22
22
  createdAt: unknown;
23
23
  updatedAt: unknown;
24
24
  }, import("zod").ZodObject<{
25
- orgId: import("zod").ZodString;
26
25
  id: import("zod").ZodString;
26
+ orgId: import("zod").ZodString;
27
27
  createdAt: import("zod").ZodCoercedDate<unknown>;
28
28
  updatedAt: import("zod").ZodCoercedDate<unknown>;
29
29
  active: import("zod").ZodBoolean;
@@ -49,8 +49,8 @@ declare const integrationEvents: {
49
49
  createdAt: unknown;
50
50
  updatedAt: unknown;
51
51
  }, import("zod").ZodObject<{
52
- orgId: import("zod").ZodString;
53
52
  id: import("zod").ZodString;
53
+ orgId: import("zod").ZodString;
54
54
  createdAt: import("zod").ZodCoercedDate<unknown>;
55
55
  updatedAt: import("zod").ZodCoercedDate<unknown>;
56
56
  active: import("zod").ZodBoolean;
@@ -76,8 +76,8 @@ declare const integrationEvents: {
76
76
  createdAt: unknown;
77
77
  updatedAt: unknown;
78
78
  }, import("zod").ZodObject<{
79
- orgId: import("zod").ZodString;
80
79
  id: import("zod").ZodString;
80
+ orgId: import("zod").ZodString;
81
81
  createdAt: import("zod").ZodCoercedDate<unknown>;
82
82
  updatedAt: import("zod").ZodCoercedDate<unknown>;
83
83
  active: import("zod").ZodBoolean;
@@ -91,15 +91,14 @@ declare const organizationEvents: {
91
91
  createdAt: unknown;
92
92
  updatedAt: unknown;
93
93
  }, import("zod").ZodObject<{
94
- orgId: import("zod").ZodString;
95
94
  id: import("zod").ZodString;
95
+ orgId: import("zod").ZodString;
96
+ createdAt: import("zod").ZodCoercedDate<unknown>;
97
+ updatedAt: import("zod").ZodCoercedDate<unknown>;
96
98
  status: import("zod").ZodEnum<{
97
99
  active: "active";
98
100
  invited: "invited";
99
101
  }>;
100
- createdAt: import("zod").ZodCoercedDate<unknown>;
101
- updatedAt: import("zod").ZodCoercedDate<unknown>;
102
- userId: import("zod").ZodString;
103
102
  role: import("zod").ZodEnum<{
104
103
  owner: "owner";
105
104
  admin: "admin";
@@ -107,6 +106,7 @@ declare const organizationEvents: {
107
106
  moderator: "moderator";
108
107
  student: "student";
109
108
  }>;
109
+ userId: import("zod").ZodString;
110
110
  }, import("zod/v4/core").$strip>>;
111
111
  orgUserDeleted: RuntimeEventDefinition<VersionedDomainEvent<{
112
112
  id: string;
@@ -125,15 +125,14 @@ declare const organizationEvents: {
125
125
  createdAt: unknown;
126
126
  updatedAt: unknown;
127
127
  }, import("zod").ZodObject<{
128
- orgId: import("zod").ZodString;
129
128
  id: import("zod").ZodString;
129
+ orgId: import("zod").ZodString;
130
+ createdAt: import("zod").ZodCoercedDate<unknown>;
131
+ updatedAt: import("zod").ZodCoercedDate<unknown>;
130
132
  status: import("zod").ZodEnum<{
131
133
  active: "active";
132
134
  invited: "invited";
133
135
  }>;
134
- createdAt: import("zod").ZodCoercedDate<unknown>;
135
- updatedAt: import("zod").ZodCoercedDate<unknown>;
136
- userId: import("zod").ZodString;
137
136
  role: import("zod").ZodEnum<{
138
137
  owner: "owner";
139
138
  admin: "admin";
@@ -141,6 +140,7 @@ declare const organizationEvents: {
141
140
  moderator: "moderator";
142
141
  student: "student";
143
142
  }>;
143
+ userId: import("zod").ZodString;
144
144
  }, import("zod/v4/core").$strip>>;
145
145
  orgUserUpdated: RuntimeEventDefinition<VersionedDomainEvent<{
146
146
  id: string;
@@ -159,15 +159,14 @@ declare const organizationEvents: {
159
159
  createdAt: unknown;
160
160
  updatedAt: unknown;
161
161
  }, import("zod").ZodObject<{
162
- orgId: import("zod").ZodString;
163
162
  id: import("zod").ZodString;
163
+ orgId: import("zod").ZodString;
164
+ createdAt: import("zod").ZodCoercedDate<unknown>;
165
+ updatedAt: import("zod").ZodCoercedDate<unknown>;
164
166
  status: import("zod").ZodEnum<{
165
167
  active: "active";
166
168
  invited: "invited";
167
169
  }>;
168
- createdAt: import("zod").ZodCoercedDate<unknown>;
169
- updatedAt: import("zod").ZodCoercedDate<unknown>;
170
- userId: import("zod").ZodString;
171
170
  role: import("zod").ZodEnum<{
172
171
  owner: "owner";
173
172
  admin: "admin";
@@ -175,6 +174,7 @@ declare const organizationEvents: {
175
174
  moderator: "moderator";
176
175
  student: "student";
177
176
  }>;
177
+ userId: import("zod").ZodString;
178
178
  }, import("zod/v4/core").$strip>>;
179
179
  inviteCreated: RuntimeEventDefinition<VersionedDomainEvent<{
180
180
  id: string;
@@ -199,24 +199,24 @@ declare const organizationEvents: {
199
199
  createdAt: unknown;
200
200
  updatedAt: unknown;
201
201
  }, import("zod").ZodObject<{
202
- orgId: import("zod").ZodString;
203
202
  id: import("zod").ZodString;
203
+ orgId: import("zod").ZodString;
204
+ createdAt: import("zod").ZodCoercedDate<unknown>;
205
+ updatedAt: import("zod").ZodCoercedDate<unknown>;
204
206
  status: import("zod").ZodEnum<{
205
207
  pending: "pending";
206
208
  accepted: "accepted";
207
209
  rejected: "rejected";
208
210
  canceled: "canceled";
209
211
  }>;
210
- createdAt: import("zod").ZodCoercedDate<unknown>;
211
- updatedAt: import("zod").ZodCoercedDate<unknown>;
212
212
  expiresAt: import("zod").ZodNullable<import("zod").ZodCoercedDate<unknown>>;
213
- email: import("zod").ZodEmail;
214
213
  role: import("zod").ZodEnum<{
215
214
  admin: "admin";
216
215
  instructor: "instructor";
217
216
  moderator: "moderator";
218
217
  student: "student";
219
218
  }>;
219
+ email: import("zod").ZodEmail;
220
220
  invitedBy: import("zod").ZodString;
221
221
  }, import("zod/v4/core").$strip>>;
222
222
  inviteAccepted: RuntimeEventDefinition<VersionedDomainEvent<{
@@ -242,24 +242,24 @@ declare const organizationEvents: {
242
242
  createdAt: unknown;
243
243
  updatedAt: unknown;
244
244
  }, import("zod").ZodObject<{
245
- orgId: import("zod").ZodString;
246
245
  id: import("zod").ZodString;
246
+ orgId: import("zod").ZodString;
247
+ createdAt: import("zod").ZodCoercedDate<unknown>;
248
+ updatedAt: import("zod").ZodCoercedDate<unknown>;
247
249
  status: import("zod").ZodEnum<{
248
250
  pending: "pending";
249
251
  accepted: "accepted";
250
252
  rejected: "rejected";
251
253
  canceled: "canceled";
252
254
  }>;
253
- createdAt: import("zod").ZodCoercedDate<unknown>;
254
- updatedAt: import("zod").ZodCoercedDate<unknown>;
255
255
  expiresAt: import("zod").ZodNullable<import("zod").ZodCoercedDate<unknown>>;
256
- email: import("zod").ZodEmail;
257
256
  role: import("zod").ZodEnum<{
258
257
  admin: "admin";
259
258
  instructor: "instructor";
260
259
  moderator: "moderator";
261
260
  student: "student";
262
261
  }>;
262
+ email: import("zod").ZodEmail;
263
263
  invitedBy: import("zod").ZodString;
264
264
  }, import("zod/v4/core").$strip>>;
265
265
  inviteCanceled: RuntimeEventDefinition<VersionedDomainEvent<{
@@ -302,15 +302,14 @@ declare const organizationEvents: {
302
302
  createdAt: unknown;
303
303
  updatedAt: unknown;
304
304
  }, import("zod").ZodObject<{
305
- orgId: import("zod").ZodString;
306
305
  id: import("zod").ZodString;
306
+ orgId: import("zod").ZodString;
307
+ createdAt: import("zod").ZodCoercedDate<unknown>;
308
+ updatedAt: import("zod").ZodCoercedDate<unknown>;
307
309
  status: import("zod").ZodEnum<{
308
310
  active: "active";
309
311
  invited: "invited";
310
312
  }>;
311
- createdAt: import("zod").ZodCoercedDate<unknown>;
312
- updatedAt: import("zod").ZodCoercedDate<unknown>;
313
- userId: import("zod").ZodString;
314
313
  role: import("zod").ZodEnum<{
315
314
  owner: "owner";
316
315
  admin: "admin";
@@ -318,6 +317,7 @@ declare const organizationEvents: {
318
317
  moderator: "moderator";
319
318
  student: "student";
320
319
  }>;
320
+ userId: import("zod").ZodString;
321
321
  }, import("zod/v4/core").$strip>>;
322
322
  studentDeleted: RuntimeEventDefinition<VersionedDomainEvent<{
323
323
  id: string;
@@ -336,15 +336,14 @@ declare const organizationEvents: {
336
336
  createdAt: unknown;
337
337
  updatedAt: unknown;
338
338
  }, import("zod").ZodObject<{
339
- orgId: import("zod").ZodString;
340
339
  id: import("zod").ZodString;
340
+ orgId: import("zod").ZodString;
341
+ createdAt: import("zod").ZodCoercedDate<unknown>;
342
+ updatedAt: import("zod").ZodCoercedDate<unknown>;
341
343
  status: import("zod").ZodEnum<{
342
344
  active: "active";
343
345
  invited: "invited";
344
346
  }>;
345
- createdAt: import("zod").ZodCoercedDate<unknown>;
346
- updatedAt: import("zod").ZodCoercedDate<unknown>;
347
- userId: import("zod").ZodString;
348
347
  role: import("zod").ZodEnum<{
349
348
  owner: "owner";
350
349
  admin: "admin";
@@ -352,6 +351,7 @@ declare const organizationEvents: {
352
351
  moderator: "moderator";
353
352
  student: "student";
354
353
  }>;
354
+ userId: import("zod").ZodString;
355
355
  }, import("zod/v4/core").$strip>>;
356
356
  studentLinked: RuntimeEventDefinition<VersionedDomainEvent<{
357
357
  id: string;
@@ -370,15 +370,14 @@ declare const organizationEvents: {
370
370
  createdAt: unknown;
371
371
  updatedAt: unknown;
372
372
  }, import("zod").ZodObject<{
373
- orgId: import("zod").ZodString;
374
373
  id: import("zod").ZodString;
374
+ orgId: import("zod").ZodString;
375
+ createdAt: import("zod").ZodCoercedDate<unknown>;
376
+ updatedAt: import("zod").ZodCoercedDate<unknown>;
375
377
  status: import("zod").ZodEnum<{
376
378
  active: "active";
377
379
  invited: "invited";
378
380
  }>;
379
- createdAt: import("zod").ZodCoercedDate<unknown>;
380
- updatedAt: import("zod").ZodCoercedDate<unknown>;
381
- userId: import("zod").ZodString;
382
381
  role: import("zod").ZodEnum<{
383
382
  owner: "owner";
384
383
  admin: "admin";
@@ -386,6 +385,7 @@ declare const organizationEvents: {
386
385
  moderator: "moderator";
387
386
  student: "student";
388
387
  }>;
388
+ userId: import("zod").ZodString;
389
389
  }, import("zod/v4/core").$strip>>;
390
390
  };
391
391
  type OrganizationCreated = EventOf<typeof organizationEvents.organizationCreated>;
@@ -7,6 +7,7 @@ import { CreateOrganizationInput, LinkOrgUserInput, ResendStudentInviteInput, Un
7
7
  import { AuthHeaders } from "../identity/ports.js";
8
8
  import { AuthOrganization, MemberRecord, MemberWriteContext, MembersRepository, NewInviteRow, NewOrganizationRow, OrgAdmin, OrganizationService, OrganizationsRepository } from "./ports.js";
9
9
  import { OrganizationServiceImpl } from "./service.js";
10
+ import { OrgNotificationCategories } from "./notification-categories.js";
10
11
  import { InviteAccepted, InviteCanceled, InviteCreated, OrgUserDeleted, OrgUserLinked, OrgUserUpdated, OrganizationCreated, OrganizationDeleted, OrganizationEvent, OrganizationUpdated, StudentCreated, StudentDeleted, StudentLinked, organizationEvents } from "./events.js";
11
12
  import { ACCESS_SCOPES, AccessScope, SCOPE_PERMISSION, ScopeOption, assertRoleHoldsScopes, isAccessScope, isDeleteScope, roleHoldsScope, scopeOptions, scopesForRole } from "./access-scopes.js";
12
- export { ACCESS_SCOPES, type AcceptInviteInput, AccessScope, type AddOrgUserInput, type AuthHeaders, type AuthOrganization, type Capability, type CreateInviteInput, type CreateOrgUserInput, type CreateOrganizationInput, type Invite, type InviteAccepted, type InviteCanceled, type InviteCreated, InviteError, type InviteId, type InviteRole, type LinkOrgUserInput, type Member, type MemberRecord, type MemberRoleChange, type MemberStatus, type MemberWriteContext, type MembersQuery, type MembersRepository, type NewInviteRow, type NewOrganizationInput, type NewOrganizationRow, type OrgAdmin, type OrgUser, type OrgUserDeleted, type OrgUserId, type OrgUserLinked, type OrgUserStatus, type OrgUserUpdated, type Organization, type OrganizationCreated, type OrganizationDeleted, type OrganizationEvent, type OrganizationId, OrganizationRuleError, type OrganizationService, OrganizationServiceImpl, type OrganizationUpdated, type OrganizationsRepository, type Page, type Permission, ROLES, type ResendStudentInviteInput, type Role, SCOPE_PERMISSION, STAFF_ROLES, STUDENT_ROLE, ScopeOption, type StaffRole, type StudentCreated, type StudentDeleted, type StudentLinked, type TeamRole, type UnlinkOrgUserInput, type UpdateOrgUserInput, type UpdateOrganizationInput, assertRoleHoldsScopes, canModerate, capability, isAccessScope, isDeleteScope, isRole, isStaffRole, normalizeRole, organizationEvents, parseRole, roleHoldsScope, scopeOptions, scopesForRole };
13
+ export { ACCESS_SCOPES, type AcceptInviteInput, AccessScope, type AddOrgUserInput, type AuthHeaders, type AuthOrganization, type Capability, type CreateInviteInput, type CreateOrgUserInput, type CreateOrganizationInput, type Invite, type InviteAccepted, type InviteCanceled, type InviteCreated, InviteError, type InviteId, type InviteRole, type LinkOrgUserInput, type Member, type MemberRecord, type MemberRoleChange, type MemberStatus, type MemberWriteContext, type MembersQuery, type MembersRepository, type NewInviteRow, type NewOrganizationInput, type NewOrganizationRow, type OrgAdmin, OrgNotificationCategories, type OrgUser, type OrgUserDeleted, type OrgUserId, type OrgUserLinked, type OrgUserStatus, type OrgUserUpdated, type Organization, type OrganizationCreated, type OrganizationDeleted, type OrganizationEvent, type OrganizationId, OrganizationRuleError, type OrganizationService, OrganizationServiceImpl, type OrganizationUpdated, type OrganizationsRepository, type Page, type Permission, ROLES, type ResendStudentInviteInput, type Role, SCOPE_PERMISSION, STAFF_ROLES, STUDENT_ROLE, ScopeOption, type StaffRole, type StudentCreated, type StudentDeleted, type StudentLinked, type TeamRole, type UnlinkOrgUserInput, type UpdateOrgUserInput, type UpdateOrganizationInput, assertRoleHoldsScopes, canModerate, capability, isAccessScope, isDeleteScope, isRole, isStaffRole, normalizeRole, organizationEvents, parseRole, roleHoldsScope, scopeOptions, scopesForRole };
@@ -2,5 +2,6 @@ import { ROLES, STAFF_ROLES, STUDENT_ROLE, canModerate, capability, isRole, isSt
2
2
  import { InviteError, OrganizationRuleError } from "./members.js";
3
3
  import { organizationEvents } from "./events.js";
4
4
  import { OrganizationServiceImpl } from "./service.js";
5
+ import { OrgNotificationCategories } from "./notification-categories.js";
5
6
  import { ACCESS_SCOPES, SCOPE_PERMISSION, assertRoleHoldsScopes, isAccessScope, isDeleteScope, roleHoldsScope, scopeOptions, scopesForRole } from "./access-scopes.js";
6
- export { ACCESS_SCOPES, InviteError, OrganizationRuleError, OrganizationServiceImpl, ROLES, SCOPE_PERMISSION, STAFF_ROLES, STUDENT_ROLE, assertRoleHoldsScopes, canModerate, capability, isAccessScope, isDeleteScope, isRole, isStaffRole, normalizeRole, organizationEvents, parseRole, roleHoldsScope, scopeOptions, scopesForRole };
7
+ export { ACCESS_SCOPES, InviteError, OrgNotificationCategories, OrganizationRuleError, OrganizationServiceImpl, ROLES, SCOPE_PERMISSION, STAFF_ROLES, STUDENT_ROLE, assertRoleHoldsScopes, canModerate, capability, isAccessScope, isDeleteScope, isRole, isStaffRole, normalizeRole, organizationEvents, parseRole, roleHoldsScope, scopeOptions, scopesForRole };
@@ -0,0 +1,32 @@
1
+ import { Logger } from "../types/ports.js";
2
+ import { NotificationCategory } from "../types/notifications.js";
3
+ import "../types/index.js";
4
+ import { ChannelRegistry, NotificationCategoryService, NotificationTypeRegistry } from "../shared/ports.js";
5
+ import { SettingsService } from "../shared/settings.js";
6
+ //#region src/organizations/notification-categories.d.ts
7
+ type OrgNotificationCategoriesParams = {
8
+ settings: SettingsService;
9
+ types: NotificationTypeRegistry;
10
+ channels: ChannelRegistry;
11
+ logger?: Logger;
12
+ };
13
+ declare class OrgNotificationCategories implements NotificationCategoryService {
14
+ private readonly settings;
15
+ private readonly types;
16
+ private readonly channels;
17
+ private readonly logger;
18
+ constructor(params: OrgNotificationCategoriesParams);
19
+ get(orgId: string, scopeId: string): Promise<{
20
+ categories: NotificationCategory[];
21
+ source: 'default' | 'stored';
22
+ }>;
23
+ replace(orgId: string, scopeId: string, categories: NotificationCategory[]): Promise<{
24
+ categories: NotificationCategory[];
25
+ source: 'stored';
26
+ }>;
27
+ reset(orgId: string, scopeId: string): Promise<void>;
28
+ defaults(): NotificationCategory[];
29
+ }
30
+ //#endregion
31
+ export { OrgNotificationCategories, OrgNotificationCategoriesParams };
32
+ //# sourceMappingURL=notification-categories.d.ts.map
@@ -0,0 +1,51 @@
1
+ import { noopLogger } from "../shared/logger.js";
2
+ import "../shared/settings.js";
3
+ import { assertValidCategoryList, offeringConfigured, storedCategoryList } from "../shared/notification-categories.js";
4
+ //#region src/organizations/notification-categories.ts
5
+ var OrgNotificationCategories = class {
6
+ settings;
7
+ types;
8
+ channels;
9
+ logger;
10
+ constructor(params) {
11
+ this.settings = params.settings;
12
+ this.types = params.types;
13
+ this.channels = params.channels;
14
+ this.logger = params.logger ?? noopLogger;
15
+ }
16
+ async get(orgId, scopeId) {
17
+ const stored = storedCategoryList((await this.settings.get(orgId, "organizations", scopeId))?.notificationCategories);
18
+ return stored ? {
19
+ categories: offeringConfigured(stored, this.channels.list()),
20
+ source: "stored"
21
+ } : {
22
+ categories: this.defaults(),
23
+ source: "default"
24
+ };
25
+ }
26
+ async replace(orgId, scopeId, categories) {
27
+ const configured = this.channels.list();
28
+ assertValidCategoryList(categories, "org", this.types, configured);
29
+ const ordered = offeringConfigured(categories, configured);
30
+ await this.settings.patch(orgId, "organizations", scopeId, { notificationCategories: ordered });
31
+ this.logger.info("org notification categories replaced", {
32
+ orgId,
33
+ keys: categories.map((category) => category.key)
34
+ });
35
+ return {
36
+ categories: ordered,
37
+ source: "stored"
38
+ };
39
+ }
40
+ async reset(orgId, scopeId) {
41
+ await this.settings.patch(orgId, "organizations", scopeId, { notificationCategories: null });
42
+ this.logger.info("org notification categories reset", { orgId });
43
+ }
44
+ defaults() {
45
+ return this.types.defaults("org");
46
+ }
47
+ };
48
+ //#endregion
49
+ export { OrgNotificationCategories };
50
+
51
+ //# sourceMappingURL=notification-categories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-categories.js","names":[],"sources":["../../src/organizations/notification-categories.ts"],"sourcesContent":["import type { NotificationCategory } from '../types/index.js';\nimport type {\n ChannelRegistry,\n Logger,\n NotificationCategoryService,\n NotificationTypeRegistry,\n} from '../shared/ports.js';\nimport { noopLogger } from '../shared/logger.js';\nimport { SettingsNamespace, type SettingsService } from '../shared/settings.js';\nimport {\n assertValidCategoryList,\n offeringConfigured,\n storedCategoryList,\n} from '../shared/notification-categories.js';\n\nexport type OrgNotificationCategoriesParams = {\n settings: SettingsService;\n types: NotificationTypeRegistry;\n channels: ChannelRegistry;\n logger?: Logger;\n};\n\nexport class OrgNotificationCategories implements NotificationCategoryService {\n private readonly settings: SettingsService;\n private readonly types: NotificationTypeRegistry;\n private readonly channels: ChannelRegistry;\n private readonly logger: Logger;\n\n constructor(params: OrgNotificationCategoriesParams) {\n this.settings = params.settings;\n this.types = params.types;\n this.channels = params.channels;\n this.logger = params.logger ?? noopLogger;\n }\n\n async get(\n orgId: string,\n scopeId: string,\n ): Promise<{ categories: NotificationCategory[]; source: 'default' | 'stored' }> {\n const value = await this.settings.get<Record<string, unknown>>(\n orgId,\n SettingsNamespace.organizations,\n scopeId,\n );\n const stored = storedCategoryList(value?.notificationCategories);\n return stored\n ? { categories: offeringConfigured(stored, this.channels.list()), source: 'stored' }\n : { categories: this.defaults(), source: 'default' };\n }\n\n async replace(\n orgId: string,\n scopeId: string,\n categories: NotificationCategory[],\n ): Promise<{ categories: NotificationCategory[]; source: 'stored' }> {\n const configured = this.channels.list();\n assertValidCategoryList(categories, 'org', this.types, configured);\n const ordered = offeringConfigured(categories, configured);\n await this.settings.patch(orgId, SettingsNamespace.organizations, scopeId, {\n notificationCategories: ordered,\n });\n this.logger.info('org notification categories replaced', {\n orgId,\n keys: categories.map((category) => category.key),\n });\n return { categories: ordered, source: 'stored' };\n }\n\n async reset(orgId: string, scopeId: string): Promise<void> {\n await this.settings.patch(orgId, SettingsNamespace.organizations, scopeId, {\n notificationCategories: null,\n });\n this.logger.info('org notification categories reset', { orgId });\n }\n\n defaults(): NotificationCategory[] {\n return this.types.defaults('org');\n }\n}\n"],"mappings":";;;;AAsBA,IAAa,4BAAb,MAA8E;CAC5E;CACA;CACA;CACA;CAEA,YAAY,QAAyC;EACnD,KAAK,WAAW,OAAO;EACvB,KAAK,QAAQ,OAAO;EACpB,KAAK,WAAW,OAAO;EACvB,KAAK,SAAS,OAAO,UAAU;CACjC;CAEA,MAAM,IACJ,OACA,SAC+E;EAM/E,MAAM,SAAS,oBAAmB,MALd,KAAK,SAAS,IAChC,OAAA,iBAEA,OACF,EAAA,EACyC,sBAAsB;EAC/D,OAAO,SACH;GAAE,YAAY,mBAAmB,QAAQ,KAAK,SAAS,KAAK,CAAC;GAAG,QAAQ;EAAS,IACjF;GAAE,YAAY,KAAK,SAAS;GAAG,QAAQ;EAAU;CACvD;CAEA,MAAM,QACJ,OACA,SACA,YACmE;EACnE,MAAM,aAAa,KAAK,SAAS,KAAK;EACtC,wBAAwB,YAAY,OAAO,KAAK,OAAO,UAAU;EACjE,MAAM,UAAU,mBAAmB,YAAY,UAAU;EACzD,MAAM,KAAK,SAAS,MAAM,OAAA,iBAAwC,SAAS,EACzE,wBAAwB,QAC1B,CAAC;EACD,KAAK,OAAO,KAAK,wCAAwC;GACvD;GACA,MAAM,WAAW,KAAK,aAAa,SAAS,GAAG;EACjD,CAAC;EACD,OAAO;GAAE,YAAY;GAAS,QAAQ;EAAS;CACjD;CAEA,MAAM,MAAM,OAAe,SAAgC;EACzD,MAAM,KAAK,SAAS,MAAM,OAAA,iBAAwC,SAAS,EACzE,wBAAwB,KAC1B,CAAC;EACD,KAAK,OAAO,KAAK,qCAAqC,EAAE,MAAM,CAAC;CACjE;CAEA,WAAmC;EACjC,OAAO,KAAK,MAAM,SAAS,KAAK;CAClC;AACF"}