@headless-lms/server 0.3.0 → 0.5.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 (228) hide show
  1. package/README.md +14 -0
  2. package/dist/app/community-mail.d.ts +10 -10
  3. package/dist/app/community-mail.js +72 -28
  4. package/dist/app/community-mail.js.map +1 -1
  5. package/dist/app/container.d.ts +48 -10
  6. package/dist/app/container.js +268 -48
  7. package/dist/app/container.js.map +1 -1
  8. package/dist/app/notification-channels.d.ts +47 -0
  9. package/dist/app/notification-channels.js +175 -0
  10. package/dist/app/notification-channels.js.map +1 -0
  11. package/dist/app/notification-reads.d.ts +23 -0
  12. package/dist/app/notification-reads.js +74 -0
  13. package/dist/app/notification-reads.js.map +1 -0
  14. package/dist/app/platform.d.ts +14 -0
  15. package/dist/app/platform.js +14 -0
  16. package/dist/app/platform.js.map +1 -0
  17. package/dist/http/config.d.ts +4 -0
  18. package/dist/http/errors.d.ts +1 -0
  19. package/dist/http/errors.js +24 -4
  20. package/dist/http/errors.js.map +1 -1
  21. package/dist/http/fastify.d.ts +8 -0
  22. package/dist/http/mcp/catalogue.d.ts +11 -9
  23. package/dist/http/mcp/catalogue.js +15 -21
  24. package/dist/http/mcp/catalogue.js.map +1 -1
  25. package/dist/http/mcp/challenge.js +3 -3
  26. package/dist/http/mcp/challenge.js.map +1 -1
  27. package/dist/http/mcp/route.js +7 -7
  28. package/dist/http/mcp/route.js.map +1 -1
  29. package/dist/http/mcp/test-support.d.ts +6 -5
  30. package/dist/http/mcp/test-support.js +32 -11
  31. package/dist/http/mcp/test-support.js.map +1 -1
  32. package/dist/http/mcp/tool.d.ts +3 -9
  33. package/dist/http/mcp/tool.js +14 -6
  34. package/dist/http/mcp/tool.js.map +1 -1
  35. package/dist/http/mcp/tools/automations.js +10 -20
  36. package/dist/http/mcp/tools/automations.js.map +1 -1
  37. package/dist/http/mcp/tools/catalog.js +25 -42
  38. package/dist/http/mcp/tools/catalog.js.map +1 -1
  39. package/dist/http/mcp/tools/comments.js +9 -19
  40. package/dist/http/mcp/tools/comments.js.map +1 -1
  41. package/dist/http/mcp/tools/communities.js +16 -31
  42. package/dist/http/mcp/tools/communities.js.map +1 -1
  43. package/dist/http/mcp/tools/community-members.js +9 -16
  44. package/dist/http/mcp/tools/community-members.js.map +1 -1
  45. package/dist/http/mcp/tools/community-moderation.js +13 -25
  46. package/dist/http/mcp/tools/community-moderation.js.map +1 -1
  47. package/dist/http/mcp/tools/courses.js +11 -21
  48. package/dist/http/mcp/tools/courses.js.map +1 -1
  49. package/dist/http/mcp/tools/events.d.ts +6 -0
  50. package/dist/http/mcp/tools/events.js +170 -0
  51. package/dist/http/mcp/tools/events.js.map +1 -0
  52. package/dist/http/mcp/tools/index.d.ts +5 -1
  53. package/dist/http/mcp/tools/index.js +45 -10
  54. package/dist/http/mcp/tools/index.js.map +1 -1
  55. package/dist/http/mcp/tools/organization.d.ts +6 -0
  56. package/dist/http/mcp/tools/organization.js +214 -0
  57. package/dist/http/mcp/tools/organization.js.map +1 -0
  58. package/dist/http/mcp/tools/people.js +30 -49
  59. package/dist/http/mcp/tools/people.js.map +1 -1
  60. package/dist/http/plugins/auth.js +37 -8
  61. package/dist/http/plugins/auth.js.map +1 -1
  62. package/dist/http/plugins/error-handler.d.ts +5 -1
  63. package/dist/http/plugins/error-handler.js +13 -3
  64. package/dist/http/plugins/error-handler.js.map +1 -1
  65. package/dist/http/plugins/openapi.js +18 -2
  66. package/dist/http/plugins/openapi.js.map +1 -1
  67. package/dist/http/plugins/platform.d.ts +7 -0
  68. package/dist/http/plugins/platform.js +13 -0
  69. package/dist/http/plugins/platform.js.map +1 -0
  70. package/dist/http/routes/access.js +54 -36
  71. package/dist/http/routes/access.js.map +1 -1
  72. package/dist/http/routes/activities.js +45 -45
  73. package/dist/http/routes/activities.js.map +1 -1
  74. package/dist/http/routes/assets.js +30 -65
  75. package/dist/http/routes/assets.js.map +1 -1
  76. package/dist/http/routes/automations.js +37 -42
  77. package/dist/http/routes/automations.js.map +1 -1
  78. package/dist/http/routes/bundles.js +33 -36
  79. package/dist/http/routes/bundles.js.map +1 -1
  80. package/dist/http/routes/communities.js +144 -136
  81. package/dist/http/routes/communities.js.map +1 -1
  82. package/dist/http/routes/community-moderation.js +83 -137
  83. package/dist/http/routes/community-moderation.js.map +1 -1
  84. package/dist/http/routes/course-analytics.js +32 -15
  85. package/dist/http/routes/course-analytics.js.map +1 -1
  86. package/dist/http/routes/courses.js +25 -32
  87. package/dist/http/routes/courses.js.map +1 -1
  88. package/dist/http/routes/dashboard.js +9 -9
  89. package/dist/http/routes/dashboard.js.map +1 -1
  90. package/dist/http/routes/discussion.js +21 -39
  91. package/dist/http/routes/discussion.js.map +1 -1
  92. package/dist/http/routes/downloads.js +41 -47
  93. package/dist/http/routes/downloads.js.map +1 -1
  94. package/dist/http/routes/entitlements.js +19 -23
  95. package/dist/http/routes/entitlements.js.map +1 -1
  96. package/dist/http/routes/events.d.ts +7 -0
  97. package/dist/http/routes/events.js +333 -0
  98. package/dist/http/routes/events.js.map +1 -0
  99. package/dist/http/routes/integrations.js +40 -73
  100. package/dist/http/routes/integrations.js.map +1 -1
  101. package/dist/http/routes/member/assets.js +10 -13
  102. package/dist/http/routes/member/assets.js.map +1 -1
  103. package/dist/http/routes/member/chats.js +177 -75
  104. package/dist/http/routes/member/chats.js.map +1 -1
  105. package/dist/http/routes/member/comments.js +65 -26
  106. package/dist/http/routes/member/comments.js.map +1 -1
  107. package/dist/http/routes/member/community.js +68 -51
  108. package/dist/http/routes/member/community.js.map +1 -1
  109. package/dist/http/routes/member/courses.js +12 -19
  110. package/dist/http/routes/member/courses.js.map +1 -1
  111. package/dist/http/routes/member/devices.d.ts +7 -0
  112. package/dist/http/routes/member/devices.js +97 -0
  113. package/dist/http/routes/member/devices.js.map +1 -0
  114. package/dist/http/routes/member/downloads.js +6 -9
  115. package/dist/http/routes/member/downloads.js.map +1 -1
  116. package/dist/http/routes/member/events.d.ts +7 -0
  117. package/dist/http/routes/member/events.js +386 -0
  118. package/dist/http/routes/member/events.js.map +1 -0
  119. package/dist/http/routes/member/files.js +71 -46
  120. package/dist/http/routes/member/files.js.map +1 -1
  121. package/dist/http/routes/member/index.js +6 -0
  122. package/dist/http/routes/member/index.js.map +1 -1
  123. package/dist/http/routes/member/notification-settings.d.ts +7 -0
  124. package/dist/http/routes/member/notification-settings.js +100 -0
  125. package/dist/http/routes/member/notification-settings.js.map +1 -0
  126. package/dist/http/routes/member/notifications.js +69 -45
  127. package/dist/http/routes/member/notifications.js.map +1 -1
  128. package/dist/http/routes/member/org.js +5 -15
  129. package/dist/http/routes/member/org.js.map +1 -1
  130. package/dist/http/routes/member/posts.js +94 -95
  131. package/dist/http/routes/member/posts.js.map +1 -1
  132. package/dist/http/routes/member/profile.js +15 -6
  133. package/dist/http/routes/member/profile.js.map +1 -1
  134. package/dist/http/routes/member/progress.js +7 -11
  135. package/dist/http/routes/member/progress.js.map +1 -1
  136. package/dist/http/routes/organizations.js +415 -110
  137. package/dist/http/routes/organizations.js.map +1 -1
  138. package/dist/http/routes/platform.d.ts +7 -0
  139. package/dist/http/routes/platform.js +61 -0
  140. package/dist/http/routes/platform.js.map +1 -0
  141. package/dist/http/routes/students.js +12 -75
  142. package/dist/http/routes/students.js.map +1 -1
  143. package/dist/http/routes/webhooks.js +30 -39
  144. package/dist/http/routes/webhooks.js.map +1 -1
  145. package/dist/http/routes.js +9 -0
  146. package/dist/http/routes.js.map +1 -1
  147. package/dist/http/schemas/access.d.ts +346 -64
  148. package/dist/http/schemas/access.js +39 -12
  149. package/dist/http/schemas/access.js.map +1 -1
  150. package/dist/http/schemas/assets.d.ts +13 -8
  151. package/dist/http/schemas/assets.js +6 -5
  152. package/dist/http/schemas/assets.js.map +1 -1
  153. package/dist/http/schemas/automations.d.ts +4 -8
  154. package/dist/http/schemas/chats.d.ts +238 -36
  155. package/dist/http/schemas/chats.js +14 -7
  156. package/dist/http/schemas/chats.js.map +1 -1
  157. package/dist/http/schemas/community.d.ts +140 -33
  158. package/dist/http/schemas/community.js +25 -9
  159. package/dist/http/schemas/community.js.map +1 -1
  160. package/dist/http/schemas/course-analytics.d.ts +51 -1
  161. package/dist/http/schemas/course-analytics.js +19 -1
  162. package/dist/http/schemas/course-analytics.js.map +1 -1
  163. package/dist/http/schemas/dashboard.d.ts +1 -0
  164. package/dist/http/schemas/dashboard.js +2 -1
  165. package/dist/http/schemas/dashboard.js.map +1 -1
  166. package/dist/http/schemas/discussion.d.ts +45 -73
  167. package/dist/http/schemas/discussion.js +16 -4
  168. package/dist/http/schemas/discussion.js.map +1 -1
  169. package/dist/http/schemas/entitlements.d.ts +9 -12
  170. package/dist/http/schemas/entitlements.js +11 -5
  171. package/dist/http/schemas/entitlements.js.map +1 -1
  172. package/dist/http/schemas/events.d.ts +527 -0
  173. package/dist/http/schemas/events.js +64 -0
  174. package/dist/http/schemas/events.js.map +1 -0
  175. package/dist/http/schemas/files.d.ts +15 -5
  176. package/dist/http/schemas/files.js +10 -2
  177. package/dist/http/schemas/files.js.map +1 -1
  178. package/dist/http/schemas/index.d.ts +14 -12
  179. package/dist/http/schemas/index.js +14 -12
  180. package/dist/http/schemas/invites.d.ts +3 -47
  181. package/dist/http/schemas/invites.js +4 -25
  182. package/dist/http/schemas/invites.js.map +1 -1
  183. package/dist/http/schemas/member.d.ts +94 -7
  184. package/dist/http/schemas/member.js +7 -3
  185. package/dist/http/schemas/member.js.map +1 -1
  186. package/dist/http/schemas/members.d.ts +329 -65
  187. package/dist/http/schemas/members.js +67 -25
  188. package/dist/http/schemas/members.js.map +1 -1
  189. package/dist/http/schemas/notifications.d.ts +502 -175
  190. package/dist/http/schemas/notifications.js +148 -28
  191. package/dist/http/schemas/notifications.js.map +1 -1
  192. package/dist/http/schemas/platform.d.ts +37 -0
  193. package/dist/http/schemas/platform.js +19 -0
  194. package/dist/http/schemas/platform.js.map +1 -0
  195. package/dist/http/schemas/posts.d.ts +92 -38
  196. package/dist/http/schemas/posts.js +4 -4
  197. package/dist/http/schemas/posts.js.map +1 -1
  198. package/dist/http/schemas/profile.d.ts +1 -1
  199. package/dist/http/schemas/shared.d.ts +10 -13
  200. package/dist/http/schemas/shared.js +3 -11
  201. package/dist/http/schemas/shared.js.map +1 -1
  202. package/dist/http/schemas/students.d.ts +22 -0
  203. package/dist/http/schemas/students.js +3 -1
  204. package/dist/http/schemas/students.js.map +1 -1
  205. package/dist/http/scope.d.ts +9 -12
  206. package/dist/http/scope.js +30 -35
  207. package/dist/http/scope.js.map +1 -1
  208. package/dist/http/server.js +9 -0
  209. package/dist/http/server.js.map +1 -1
  210. package/dist/http/shutdown.d.ts +11 -0
  211. package/dist/http/shutdown.js +40 -0
  212. package/dist/http/shutdown.js.map +1 -0
  213. package/dist/http/sse.d.ts +19 -0
  214. package/dist/http/sse.js +104 -0
  215. package/dist/http/sse.js.map +1 -0
  216. package/dist/http/test-caller.d.ts +13 -0
  217. package/dist/http/test-caller.js +14 -0
  218. package/dist/http/test-caller.js.map +1 -0
  219. package/dist/http/well-known.js +2 -2
  220. package/dist/http/well-known.js.map +1 -1
  221. package/dist/index.d.ts +5 -2
  222. package/dist/index.js +4 -2
  223. package/dist/index.js.map +1 -1
  224. package/dist/packages/server/package.js +1 -1
  225. package/package.json +7 -6
  226. package/dist/http/routes/member/actor.d.ts +0 -14
  227. package/dist/http/routes/member/actor.js +0 -11
  228. package/dist/http/routes/member/actor.js.map +0 -1
@@ -1,7 +1,10 @@
1
+ import { buildNotificationRegistries, configuredPushSender } from "./notification-channels.js";
1
2
  import { loadIntegrations } from "./integrations.js";
2
3
  import { mcpEndpoints } from "./mcp-endpoints.js";
3
4
  import { communityMail } from "./community-mail.js";
4
- import { DrizzleAdminRepository, DrizzleAssetsRepository, DrizzleAutomationRunsRepository, DrizzleAutomationsRepository, DrizzleChatRepository, DrizzleConnectionsRepository, DrizzleContentRepository, DrizzleCredentialStore, DrizzleDiscussionRepository, DrizzleEntitlementsRepository, DrizzleIdentityRepository, DrizzleMemberRepository, DrizzleMembersRepository, DrizzleNotificationsRepository, DrizzleOrganizationsRepository, DrizzleOutboxAppender, DrizzleOutboxStore, DrizzlePostRepository, DrizzleProgressRepository, DrizzleSettingsRepository, DrizzleUnitOfWork, createDb } from "@headless-lms/adapter-db";
5
+ import { notificationReads } from "./notification-reads.js";
6
+ import { platformKeyCheck } from "./platform.js";
7
+ import { DrizzleAdminRepository, DrizzleAssetsRepository, DrizzleAutomationRunsRepository, DrizzleAutomationsRepository, DrizzleChatRepository, DrizzleConnectionsRepository, DrizzleContentRepository, DrizzleCredentialStore, DrizzleDevicesRepository, DrizzleDigestRunRepository, DrizzleDiscussionRepository, DrizzleEntitlementsRepository, DrizzleIdentityRepository, DrizzleMemberRepository, DrizzleMembersRepository, DrizzleMentionsRepository, DrizzleNotificationSettingsRepository, DrizzleNotificationsRepository, DrizzleOrganizationsRepository, DrizzleOutboxAppender, DrizzleOutboxStore, DrizzlePostRepository, DrizzleProgressRepository, DrizzleRolesRepository, DrizzleSettingsRepository, DrizzleSubscriptionsRepository, DrizzleUnitOfWork, createDb } from "@headless-lms/adapter-db";
5
8
  import { InMemoryEventBus } from "@headless-lms/adapter-defaults/events";
6
9
  import { PollingOutboxRelay } from "@headless-lms/adapter-defaults/events/outbox-relay";
7
10
  import { InlineAutomationEngine } from "@headless-lms/adapter-defaults/workflows";
@@ -9,25 +12,27 @@ import { FetchWebhookSender } from "@headless-lms/adapter-defaults/webhooks";
9
12
  import { EmailAdapter, StubTemplateRenderer } from "@headless-lms/adapter-defaults/email";
10
13
  import { createRootLogger, requestLogContext } from "@headless-lms/adapter-defaults/logging";
11
14
  import { StorageAdapter } from "@headless-lms/adapter-defaults/storage";
15
+ import { PlainBodyParser } from "@headless-lms/adapter-defaults/body";
12
16
  import { BetterAuth } from "@headless-lms/adapter-auth";
13
- import { ContentService } from "@headless-lms/core/content";
14
- import { EntitlementsServiceImpl } from "@headless-lms/core/entitlements";
17
+ import { CommunityNotificationCategories, ContentItemTitles, ContentService, contentJobs } from "@headless-lms/core/content";
18
+ import { EntitlementsServiceImpl, entitlementsJobs, entitlementsRecurringJobs } from "@headless-lms/core/entitlements";
15
19
  import { ProgressServiceImpl } from "@headless-lms/core/progress";
16
- import { ChatServiceImpl, DiscussionServiceImpl, PostServiceImpl } from "@headless-lms/core/discussion";
17
- import { IdentityServiceImpl, NotificationServiceImpl } from "@headless-lms/core/identity";
18
- import { OrganizationServiceImpl, parseRole } from "@headless-lms/core/organizations";
19
- import { AssetsServiceImpl, createFileUrls } from "@headless-lms/core/assets";
20
+ import { ChatLiveForwarder, ChatServiceImpl, DigestServiceImpl, DiscussionItemTitles, DiscussionServiceImpl, PostServiceImpl, SubscriptionServiceImpl, discussionJobs, discussionRecurringJobs } from "@headless-lms/core/discussion";
21
+ import { DevicesServiceImpl, IdentityServiceImpl, NotificationSettingsServiceImpl } from "@headless-lms/core/identity";
22
+ import { NotificationCatalog, OrgNotificationCategories, OrganizationServiceImpl } from "@headless-lms/core/organizations";
23
+ import { AssetsServiceImpl, assetsJobs, assetsRecurringJobs, createFileUrls } from "@headless-lms/core/assets";
20
24
  import { IntegrationsServiceImpl } from "@headless-lms/core/integrations";
21
- import { AutomationsServiceImpl } from "@headless-lms/core/automations";
25
+ import { AutomationsServiceImpl, DEFAULT_COMMUNITY_CATEGORIES, DEFAULT_ORG_CATEGORIES, NOTIFICATION_TYPES, NotificationDelivery, NotificationProcessor, NotificationsServiceImpl, RECIPIENT_RULES, SchedulerChatDebounce, notificationJobs } from "@headless-lms/core/automations";
26
+ import { Mailer } from "@headless-lms/core/shared/mailer";
22
27
  import { AdminReportServiceImpl } from "@headless-lms/core/reporting/admin";
23
28
  import { MemberReportServiceImpl } from "@headless-lms/core/reporting/member";
24
- import { Mailer } from "@headless-lms/core/shared/mailer";
25
29
  import { SettingsService } from "@headless-lms/core/shared/settings";
26
30
  //#region src/app/container.ts
27
31
  const OUTBOX_DEFAULTS = {
28
32
  enabled: true,
29
33
  pollIntervalMs: 1e3,
30
- batchSize: 100
34
+ batchSize: 100,
35
+ concurrency: 16
31
36
  };
32
37
  function resolveOutboxConfig(config = {}) {
33
38
  const overrides = Object.fromEntries(Object.entries(config).filter(([, value]) => value !== void 0));
@@ -51,12 +56,15 @@ async function buildContainer(config, options) {
51
56
  const automationsLogger = logger.child({ name: "automations" });
52
57
  const reportingLogger = logger.child({ name: "reporting" });
53
58
  const outboxLogger = logger.child({ name: "outbox" });
54
- const db = createDb(config.databaseUrl);
59
+ const db = createDb(config.databaseUrl, logger.child({ name: "db" }), config.databasePool);
55
60
  const email = options?.adapters?.email ?? new EmailAdapter(logger.child({ name: "email" }));
56
61
  const storage = options?.adapters?.storage ?? new StorageAdapter(logger.child({ name: "storage" }));
57
62
  const templates = options?.adapters?.templates ?? new StubTemplateRenderer(logger.child({ name: "email" }));
63
+ const bodies = options?.adapters?.bodyParser ?? new PlainBodyParser();
58
64
  const workflows = options?.adapters?.workflows;
59
65
  const automationEngine = typeof workflows === "function" ? workflows(automationsLogger) : workflows ?? new InlineAutomationEngine(automationsLogger);
66
+ const pubsubOption = options?.adapters?.pubsub;
67
+ const pubsub = typeof pubsubOption === "function" ? pubsubOption(logger.child({ name: "pubsub" })) : pubsubOption ?? null;
60
68
  const mailer = new Mailer(templates, email, {
61
69
  ...config.emailBranding ?? {
62
70
  brandName: "Headless LMS",
@@ -64,7 +72,7 @@ async function buildContainer(config, options) {
64
72
  },
65
73
  studentPortalUrl: config.studentPortalUrl
66
74
  });
67
- const fileUrls = createFileUrls(config.authBaseURL);
75
+ const fileUrls = createFileUrls(config.authBaseURL, (key) => storage.publicUrl(key));
68
76
  const assets = new AssetsServiceImpl({
69
77
  storage,
70
78
  repo: new DrizzleAssetsRepository(db, assetsLogger),
@@ -73,6 +81,7 @@ async function buildContainer(config, options) {
73
81
  outbox: new DrizzleOutboxAppender(tx, outboxLogger)
74
82
  })),
75
83
  memberImageMaxBytes: config.memberImageMaxBytes,
84
+ memberFileMaxBytes: config.memberFileMaxBytes,
76
85
  downloadLinkExpirySeconds: config.deliveryExpirySeconds,
77
86
  fileUrls,
78
87
  logger: assetsLogger
@@ -89,31 +98,49 @@ async function buildContainer(config, options) {
89
98
  images: assets,
90
99
  fileUrls
91
100
  });
92
- const notifications = new NotificationServiceImpl({
93
- repo: new DrizzleNotificationsRepository(db),
94
- logger: logger.child({ name: "notifications" })
95
- });
96
101
  const organizationsRepo = new DrizzleOrganizationsRepository(db, logger.child({ name: "org" }));
97
102
  const organizationsUow = new DrizzleUnitOfWork(db, (tx) => ({
98
103
  organizations: new DrizzleOrganizationsRepository(tx, logger.child({ name: "org" })),
104
+ roles: new DrizzleRolesRepository(tx),
99
105
  outbox: new DrizzleOutboxAppender(tx, logger.child({ name: "org" }))
100
106
  }));
101
107
  const orgAdminRef = { current: void 0 };
108
+ const platform = config.platform ? platformKeyCheck(config.platform) : null;
102
109
  const organizations = new OrganizationServiceImpl({
103
110
  repo: organizationsRepo,
111
+ roles: new DrizzleRolesRepository(db),
112
+ ...options?.roles && { defaultRoles: options.roles },
113
+ apiKeys: () => {
114
+ if (!orgAdminRef.current) throw new Error("api keys not initialised");
115
+ return orgAdminRef.current;
116
+ },
117
+ accounts: () => {
118
+ if (!orgAdminRef.current) throw new Error("accounts not initialised");
119
+ return orgAdminRef.current;
120
+ },
121
+ ...config.platform && { platform: {
122
+ email: config.platform.accountEmail,
123
+ name: config.platform.accountName ?? "Platform"
124
+ } },
104
125
  membersRepo: new DrizzleMembersRepository(db, logger.child({ name: "org" })),
105
126
  people: identity,
127
+ images: assets,
106
128
  uow: organizationsUow,
107
129
  orgAdmin: () => {
108
130
  if (!orgAdminRef.current) throw new Error("orgAdmin not initialised");
109
131
  return orgAdminRef.current;
110
132
  },
133
+ sessions: () => {
134
+ if (!orgAdminRef.current) throw new Error("sessions not initialised");
135
+ return orgAdminRef.current;
136
+ },
111
137
  logger: logger.child({ name: "org" }),
112
138
  mailer,
113
139
  inviteUrls: {
114
140
  studentPortalUrl: config.studentPortalUrl,
115
141
  adminAppUrl: config.adminAppUrl
116
- }
142
+ },
143
+ grants: () => entitlements
117
144
  });
118
145
  const settings = new SettingsService(new DrizzleSettingsRepository(db, logger.child({ name: "settings" })));
119
146
  const contentUow = new DrizzleUnitOfWork(db, (tx) => ({
@@ -126,28 +153,74 @@ async function buildContainer(config, options) {
126
153
  libraryAssets: assets,
127
154
  files: assets,
128
155
  fileUrls,
156
+ scheduler: automationEngine,
129
157
  logger: contentLogger
130
158
  });
159
+ const pushConfig = options?.adapters?.push ?? {};
160
+ const pushSender = configuredPushSender(pushConfig, options?.adapters?.pushSender);
161
+ const notificationRegistries = buildNotificationRegistries({
162
+ declarations: NOTIFICATION_TYPES,
163
+ defaults: {
164
+ community: DEFAULT_COMMUNITY_CATEGORIES,
165
+ org: DEFAULT_ORG_CATEGORIES
166
+ },
167
+ mailer: options?.adapters?.email ? mailer : null,
168
+ emailOf: async (_orgId, userId) => (await identity.getUserById(userId))?.email ?? null,
169
+ push: pushSender ? {
170
+ sender: pushSender,
171
+ activeDevices: (orgId, userId, since) => devices.activeDevices(orgId, userId, since)
172
+ } : null,
173
+ channels: options?.adapters?.notificationChannels ?? []
174
+ });
175
+ const notificationCategories = {
176
+ community: new CommunityNotificationCategories({
177
+ communities: new DrizzleContentRepository(db, contentLogger),
178
+ settings,
179
+ types: notificationRegistries.types,
180
+ channels: notificationRegistries.channels,
181
+ logger: contentLogger
182
+ }),
183
+ org: new OrgNotificationCategories({
184
+ settings,
185
+ types: notificationRegistries.types,
186
+ channels: notificationRegistries.channels,
187
+ logger: logger.child({ name: "org" })
188
+ })
189
+ };
190
+ const notificationCatalog = new NotificationCatalog({
191
+ types: notificationRegistries.types,
192
+ channels: notificationRegistries.channels
193
+ });
194
+ const notificationCategoryLists = { listFor: async (orgId, communityId) => (communityId ? await notificationCategories.community.get(communityId, organizations.systemActor(orgId)) : await notificationCategories.org.get(orgId, organizations.systemActor(orgId))).categories };
131
195
  const entitlementsUow = new DrizzleUnitOfWork(db, (tx) => ({
132
196
  entitlements: new DrizzleEntitlementsRepository(tx, entitlementsLogger),
133
197
  spaces: new DrizzleContentRepository(tx, contentLogger),
134
198
  outbox: new DrizzleOutboxAppender(tx, outboxLogger)
135
199
  }));
136
- const authors = {
137
- roleOf: async (orgId, orgUserId) => {
138
- return (await organizationsRepo.findOrgUserById(orgId, orgUserId))?.role ?? null;
139
- },
140
- rolesOf: async (orgId, orgUserIds) => {
141
- const rows = await organizationsRepo.findOrgUsersByIds(orgId, orgUserIds);
142
- return Object.fromEntries(rows.map((row) => [row.id, row.role]));
143
- }
144
- };
200
+ const authors = organizations;
145
201
  const entitlements = new EntitlementsServiceImpl({
146
202
  repo: new DrizzleEntitlementsRepository(db, entitlementsLogger),
147
203
  content,
204
+ permissions: organizations,
148
205
  uow: entitlementsUow,
149
206
  logger: entitlementsLogger
150
207
  });
208
+ const identityLogger = logger.child({ name: "identity" });
209
+ const notificationSettings = new NotificationSettingsServiceImpl({
210
+ repo: new DrizzleNotificationSettingsRepository(db, identityLogger),
211
+ categories: {
212
+ communityList: async (orgId, communityId) => (await notificationCategories.community.get(communityId, organizations.systemActor(orgId))).categories,
213
+ orgList: async (orgId) => (await notificationCategories.org.get(orgId, organizations.systemActor(orgId))).categories
214
+ },
215
+ access: { canOpen: (orgId, actor, communityId) => entitlements.isCommunityMember(communityId, actor) },
216
+ channels: notificationRegistries.channels,
217
+ logger: identityLogger
218
+ });
219
+ const devices = new DevicesServiceImpl({
220
+ repo: new DrizzleDevicesRepository(db, identityLogger),
221
+ push: pushConfig,
222
+ logger: identityLogger
223
+ });
151
224
  const progressUow = new DrizzleUnitOfWork(db, (tx) => ({
152
225
  progress: new DrizzleProgressRepository(tx, progressLogger),
153
226
  outbox: new DrizzleOutboxAppender(tx, outboxLogger)
@@ -156,12 +229,15 @@ async function buildContainer(config, options) {
156
229
  repo: new DrizzleProgressRepository(db, progressLogger),
157
230
  content,
158
231
  access: entitlements,
232
+ system: organizations,
159
233
  uow: progressUow,
160
234
  logger: progressLogger
161
235
  });
162
236
  const postReads = new DrizzlePostRepository(db, discussionLogger);
163
237
  const discussionReads = new DrizzleDiscussionRepository(db, discussionLogger);
164
238
  const chatReads = new DrizzleChatRepository(db, discussionLogger);
239
+ const subscriptionReads = new DrizzleSubscriptionsRepository(db, discussionLogger);
240
+ const mentionReads = new DrizzleMentionsRepository(db, discussionLogger);
165
241
  const posts = new PostServiceImpl({
166
242
  repo: postReads,
167
243
  access: entitlements,
@@ -169,12 +245,17 @@ async function buildContainer(config, options) {
169
245
  authors,
170
246
  profiles: discussionReads,
171
247
  comments: discussionReads,
248
+ subscriptions: subscriptionReads,
249
+ mentions: mentionReads,
172
250
  images: assets,
173
251
  fileUrls,
252
+ bodies,
174
253
  uow: new DrizzleUnitOfWork(db, (tx) => ({
175
254
  posts: new DrizzlePostRepository(tx, discussionLogger),
255
+ mentions: new DrizzleMentionsRepository(tx, discussionLogger),
176
256
  outbox: new DrizzleOutboxAppender(tx, outboxLogger)
177
257
  })),
258
+ scheduler: automationEngine,
178
259
  logger: discussionLogger
179
260
  });
180
261
  const chats = new ChatServiceImpl({
@@ -183,12 +264,17 @@ async function buildContainer(config, options) {
183
264
  content,
184
265
  authors,
185
266
  profiles: discussionReads,
267
+ subscriptions: subscriptionReads,
268
+ mentions: mentionReads,
186
269
  images: assets,
187
270
  fileUrls,
188
271
  uow: new DrizzleUnitOfWork(db, (tx) => ({
189
272
  chats: new DrizzleChatRepository(tx, discussionLogger),
273
+ mentions: new DrizzleMentionsRepository(tx, discussionLogger),
190
274
  outbox: new DrizzleOutboxAppender(tx, outboxLogger)
191
275
  })),
276
+ ...pubsub && { pubsub },
277
+ ...options?.live && { live: options.live },
192
278
  logger: discussionLogger
193
279
  });
194
280
  const discussion = new DiscussionServiceImpl({
@@ -196,17 +282,68 @@ async function buildContainer(config, options) {
196
282
  access: entitlements,
197
283
  content,
198
284
  posts,
199
- communities: { listIds: (orgId) => content.listPublishedCommunityIds(orgId) },
285
+ spaces: entitlements,
286
+ authors,
287
+ mentions: mentionReads,
200
288
  fileUrls,
201
289
  uow: new DrizzleUnitOfWork(db, (tx) => ({
202
290
  discussion: new DrizzleDiscussionRepository(tx, discussionLogger),
291
+ mentions: new DrizzleMentionsRepository(tx, discussionLogger),
203
292
  outbox: new DrizzleOutboxAppender(tx, outboxLogger)
204
293
  })),
205
294
  settings,
206
295
  logger: discussionLogger
207
296
  });
297
+ const subscriptions = new SubscriptionServiceImpl({
298
+ repo: subscriptionReads,
299
+ access: entitlements,
300
+ posts,
301
+ chats,
302
+ uow: new DrizzleUnitOfWork(db, (tx) => ({
303
+ subscriptions: new DrizzleSubscriptionsRepository(tx, discussionLogger),
304
+ outbox: new DrizzleOutboxAppender(tx, outboxLogger)
305
+ })),
306
+ logger: discussionLogger
307
+ });
308
+ const digests = new DigestServiceImpl({
309
+ schedules: { communitiesWithDigest: () => content.communitiesWithDigest() },
310
+ runs: new DrizzleDigestRunRepository(db, discussionLogger),
311
+ uow: new DrizzleUnitOfWork(db, (tx) => ({
312
+ digests: new DrizzleDigestRunRepository(tx, discussionLogger),
313
+ outbox: new DrizzleOutboxAppender(tx, outboxLogger)
314
+ })),
315
+ logger: discussionLogger
316
+ });
317
+ const notificationsRepo = new DrizzleNotificationsRepository(db);
318
+ const notificationList = new NotificationsServiceImpl({
319
+ repo: notificationsRepo,
320
+ logger: automationsLogger
321
+ });
322
+ const itemTitles = {
323
+ discussion: new DiscussionItemTitles({
324
+ posts,
325
+ chats,
326
+ comments: discussionReads,
327
+ messages: chatReads,
328
+ profiles: discussionReads,
329
+ activities: content,
330
+ access: entitlements,
331
+ actors: organizations
332
+ }),
333
+ content: new ContentItemTitles({
334
+ content,
335
+ access: entitlements,
336
+ organizations
337
+ })
338
+ };
208
339
  const reporting = {
209
- admin: new AdminReportServiceImpl({ repo: new DrizzleAdminRepository(db, reportingLogger) }),
340
+ admin: new AdminReportServiceImpl({
341
+ repo: new DrizzleAdminRepository(db, reportingLogger),
342
+ permissions: organizations,
343
+ spaces: entitlements,
344
+ events: content,
345
+ fileUrls
346
+ }),
210
347
  member: new MemberReportServiceImpl({
211
348
  repo: new DrizzleMemberRepository(db, reportingLogger),
212
349
  content,
@@ -214,9 +351,14 @@ async function buildContainer(config, options) {
214
351
  files: assets,
215
352
  fileUrls,
216
353
  access: entitlements,
217
- notifications,
218
- posts,
219
- chats
354
+ people: organizations,
355
+ notifications: notificationList,
356
+ titles: itemTitles,
357
+ subscriptions,
358
+ notificationCategories,
359
+ notificationSettings,
360
+ channels: notificationRegistries.channels,
361
+ studentPortalUrl: config.studentPortalUrl
220
362
  })
221
363
  };
222
364
  const credentialStore = new DrizzleCredentialStore(db, config.credentialStoreKey, integrationsLogger);
@@ -249,23 +391,30 @@ async function buildContainer(config, options) {
249
391
  } : null;
250
392
  },
251
393
  contentInfo: async (orgId, contentId) => {
252
- const course = await content.getCourse(orgId, contentId);
394
+ const system = organizations.systemActor(orgId);
395
+ const course = await content.findCourse(contentId, system);
253
396
  if (course) return {
254
397
  id: course.id,
255
398
  title: course.title,
256
399
  type: course.type
257
400
  };
258
- const download = await content.getDownload(orgId, contentId);
401
+ const download = await content.findDownload(contentId, system);
259
402
  if (download) return {
260
403
  id: download.id,
261
404
  title: download.title,
262
405
  type: download.type
263
406
  };
264
- const community = await content.getCommunity(orgId, contentId);
265
- return community ? {
407
+ const community = await content.findCommunity(contentId, system);
408
+ if (community) return {
266
409
  id: community.id,
267
410
  title: community.title,
268
411
  type: community.type
412
+ };
413
+ const event = await content.findEvent(contentId, system);
414
+ return event ? {
415
+ id: event.id,
416
+ title: event.title,
417
+ type: event.type
269
418
  } : null;
270
419
  }
271
420
  };
@@ -276,9 +425,35 @@ async function buildContainer(config, options) {
276
425
  organizations,
277
426
  identity,
278
427
  posts,
428
+ subscriptions,
279
429
  discussion,
280
430
  chats,
281
- assets
431
+ bodies
432
+ });
433
+ const recipientReads = notificationReads({
434
+ content,
435
+ entitlements,
436
+ organizations,
437
+ identity,
438
+ posts,
439
+ subscriptions,
440
+ discussion,
441
+ chats,
442
+ bodies,
443
+ settings: notificationSettings
444
+ });
445
+ const notificationProcessor = new NotificationProcessor({
446
+ rules: RECIPIENT_RULES,
447
+ reads: recipientReads,
448
+ types: notificationRegistries.types,
449
+ channels: notificationRegistries.channels,
450
+ categories: notificationCategoryLists,
451
+ settings: notificationSettings,
452
+ devices,
453
+ repo: notificationsRepo,
454
+ engine: automationEngine,
455
+ debounce: new SchedulerChatDebounce(automationEngine),
456
+ logger: automationsLogger
282
457
  });
283
458
  const automations = new AutomationsServiceImpl({
284
459
  repo: new DrizzleAutomationsRepository(db, automationsLogger),
@@ -288,16 +463,52 @@ async function buildContainer(config, options) {
288
463
  mailer,
289
464
  lookups: mailerLookups,
290
465
  integrations,
466
+ systemActor: (orgId) => organizations.systemActor(orgId),
291
467
  credentials: credentialStore,
292
468
  webhooks: options?.adapters?.webhooks ?? new FetchWebhookSender({ allowPrivateNetwork: config.webhookAllowPrivateNetwork }),
293
469
  mail: wired.mail,
294
470
  grants: wired.grants,
295
- desk: notifications,
296
- scheduledWork: wired.scheduledWork,
471
+ delivery: new NotificationDelivery({
472
+ repo: notificationsRepo,
473
+ channels: notificationRegistries.channels,
474
+ devices,
475
+ logger: automationsLogger
476
+ }),
477
+ processor: notificationProcessor,
297
478
  logger: automationsLogger
298
479
  });
299
480
  automationEngine.register(automations);
481
+ automationEngine.registerJobs([
482
+ ...discussionJobs({
483
+ posts,
484
+ digests,
485
+ logger: discussionLogger
486
+ }),
487
+ ...contentJobs({
488
+ events: content,
489
+ logger: contentLogger
490
+ }),
491
+ ...entitlementsJobs({ grants: entitlements }),
492
+ ...assetsJobs({
493
+ assets,
494
+ orgs: organizations,
495
+ logger: assetsLogger
496
+ }),
497
+ ...notificationJobs({
498
+ processor: notificationProcessor,
499
+ reads: recipientReads,
500
+ logger: automationsLogger
501
+ })
502
+ ], [
503
+ ...discussionRecurringJobs,
504
+ ...entitlementsRecurringJobs,
505
+ ...assetsRecurringJobs
506
+ ]);
300
507
  const eventBus = new InMemoryEventBus();
508
+ if (pubsub) new ChatLiveForwarder({
509
+ pubsub,
510
+ logger: discussionLogger
511
+ }).attach(eventBus);
301
512
  eventBus.subscribeAll((event) => automations.handle(event));
302
513
  const outboxConfig = resolveOutboxConfig(config.outbox);
303
514
  const outboxRelay = new PollingOutboxRelay(new DrizzleOutboxStore(db, outboxLogger), eventBus, outboxConfig, outboxLogger);
@@ -356,6 +567,10 @@ async function buildContainer(config, options) {
356
567
  beforeCreateSession: authHook("beforeCreateSession", async (session) => {
357
568
  const person = await identity.getUserByExternalId(session.userId);
358
569
  if (!person) return;
570
+ if (organizations.isPlatformAccount(person.email)) {
571
+ authLogger.warn("sign-in refused for the platform account", { userId: person.id });
572
+ return false;
573
+ }
359
574
  const orgs = await organizations.getOrgUsersForUser(person.id);
360
575
  if (orgs.length !== 1) return;
361
576
  const org = await organizations.getById(orgs[0].orgId);
@@ -366,7 +581,7 @@ async function buildContainer(config, options) {
366
581
  } };
367
582
  }),
368
583
  beforeCreateOrganization: authHook("beforeCreateOrganization", async ({ organization: org, user: baUser }) => {
369
- const domainOrg = await organizations.createOrganization({
584
+ const domainOrg = await organizations.provisionOrganization({
370
585
  ownerId: baUser.id,
371
586
  ...org,
372
587
  logo: org.logo ?? void 0
@@ -376,22 +591,15 @@ async function buildContainer(config, options) {
376
591
  id: domainOrg.id
377
592
  } };
378
593
  }),
379
- beforeUpdateOrganization: authHook("beforeUpdateOrganization", async ({ organization }) => {
380
- await organizations.updateOrganization(organization.id, organization);
381
- }),
382
594
  beforeDeleteOrganization: authHook("beforeDeleteOrganization", async ({ organization }) => {
383
595
  await organizations.deleteOrganization(organization.id);
384
596
  }),
385
597
  beforeAddMember: authHook("beforeAddMember", async ({ member }) => {
386
598
  await organizations.addOrgUser({
387
599
  orgId: member.organizationId,
388
- userId: member.userId,
389
- role: parseRole(member.role)
600
+ userId: member.userId
390
601
  });
391
602
  }),
392
- beforeUpdateMemberRole: authHook("beforeUpdateMemberRole", async ({ member, newRole }) => {
393
- await organizations.updateOrgUser(member.organizationId, member.userId, { role: parseRole(newRole) });
394
- }),
395
603
  beforeRemoveMember: authHook("beforeRemoveMember", async ({ member }) => {
396
604
  await organizations.removeOrgUser(member.organizationId, member.userId);
397
605
  })
@@ -404,8 +612,8 @@ async function buildContainer(config, options) {
404
612
  return {
405
613
  auth,
406
614
  mcp,
615
+ platform,
407
616
  identity,
408
- notifications,
409
617
  organizations,
410
618
  orgProvider: auth,
411
619
  content,
@@ -414,9 +622,18 @@ async function buildContainer(config, options) {
414
622
  discussion,
415
623
  posts,
416
624
  chats,
625
+ subscriptions,
417
626
  assets,
418
627
  integrations,
419
628
  automations,
629
+ notificationTypes: notificationRegistries.types,
630
+ notificationChannels: notificationRegistries.channels,
631
+ notificationCategories,
632
+ notificationCatalog,
633
+ notificationCategoryLists,
634
+ notificationSettings,
635
+ devices,
636
+ notificationList,
420
637
  settings,
421
638
  reporting,
422
639
  storage,
@@ -424,6 +641,9 @@ async function buildContainer(config, options) {
424
641
  credentials: credentialStore,
425
642
  outboxRelay,
426
643
  automationEngine,
644
+ scheduler: automationEngine,
645
+ pubsub,
646
+ chatHub: chats.hub,
427
647
  logger,
428
648
  loggerInstance,
429
649
  requestContext: requestLogContext,