@headless-lms/server 0.4.0 → 0.6.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 (222) hide show
  1. package/README.md +14 -0
  2. package/dist/app/community-mail.d.ts +5 -3
  3. package/dist/app/community-mail.js +60 -12
  4. package/dist/app/community-mail.js.map +1 -1
  5. package/dist/app/container.d.ts +24 -3
  6. package/dist/app/container.js +102 -38
  7. package/dist/app/container.js.map +1 -1
  8. package/dist/app/notification-channels.js +1 -0
  9. package/dist/app/notification-channels.js.map +1 -1
  10. package/dist/app/notification-reads.d.ts +3 -3
  11. package/dist/app/notification-reads.js +7 -28
  12. package/dist/app/notification-reads.js.map +1 -1
  13. package/dist/app/platform.d.ts +14 -0
  14. package/dist/app/platform.js +14 -0
  15. package/dist/app/platform.js.map +1 -0
  16. package/dist/http/config.d.ts +4 -0
  17. package/dist/http/errors.js +9 -4
  18. package/dist/http/errors.js.map +1 -1
  19. package/dist/http/fastify.d.ts +8 -0
  20. package/dist/http/mcp/catalogue.d.ts +11 -9
  21. package/dist/http/mcp/catalogue.js +15 -21
  22. package/dist/http/mcp/catalogue.js.map +1 -1
  23. package/dist/http/mcp/challenge.js +3 -3
  24. package/dist/http/mcp/challenge.js.map +1 -1
  25. package/dist/http/mcp/route.js +7 -7
  26. package/dist/http/mcp/route.js.map +1 -1
  27. package/dist/http/mcp/test-support.d.ts +6 -5
  28. package/dist/http/mcp/test-support.js +32 -11
  29. package/dist/http/mcp/test-support.js.map +1 -1
  30. package/dist/http/mcp/tool.d.ts +3 -9
  31. package/dist/http/mcp/tool.js +14 -6
  32. package/dist/http/mcp/tool.js.map +1 -1
  33. package/dist/http/mcp/tools/automations.js +10 -20
  34. package/dist/http/mcp/tools/automations.js.map +1 -1
  35. package/dist/http/mcp/tools/catalog.js +25 -42
  36. package/dist/http/mcp/tools/catalog.js.map +1 -1
  37. package/dist/http/mcp/tools/comments.js +9 -19
  38. package/dist/http/mcp/tools/comments.js.map +1 -1
  39. package/dist/http/mcp/tools/communities.js +17 -32
  40. package/dist/http/mcp/tools/communities.js.map +1 -1
  41. package/dist/http/mcp/tools/community-members.js +9 -16
  42. package/dist/http/mcp/tools/community-members.js.map +1 -1
  43. package/dist/http/mcp/tools/community-moderation.js +13 -25
  44. package/dist/http/mcp/tools/community-moderation.js.map +1 -1
  45. package/dist/http/mcp/tools/courses.js +11 -21
  46. package/dist/http/mcp/tools/courses.js.map +1 -1
  47. package/dist/http/mcp/tools/events.d.ts +6 -0
  48. package/dist/http/mcp/tools/events.js +170 -0
  49. package/dist/http/mcp/tools/events.js.map +1 -0
  50. package/dist/http/mcp/tools/index.d.ts +5 -1
  51. package/dist/http/mcp/tools/index.js +45 -10
  52. package/dist/http/mcp/tools/index.js.map +1 -1
  53. package/dist/http/mcp/tools/organization.d.ts +6 -0
  54. package/dist/http/mcp/tools/organization.js +214 -0
  55. package/dist/http/mcp/tools/organization.js.map +1 -0
  56. package/dist/http/mcp/tools/people.js +30 -49
  57. package/dist/http/mcp/tools/people.js.map +1 -1
  58. package/dist/http/plugins/auth.js +37 -8
  59. package/dist/http/plugins/auth.js.map +1 -1
  60. package/dist/http/plugins/error-handler.d.ts +5 -1
  61. package/dist/http/plugins/error-handler.js +10 -2
  62. package/dist/http/plugins/error-handler.js.map +1 -1
  63. package/dist/http/plugins/openapi.js +18 -2
  64. package/dist/http/plugins/openapi.js.map +1 -1
  65. package/dist/http/plugins/platform.d.ts +7 -0
  66. package/dist/http/plugins/platform.js +13 -0
  67. package/dist/http/plugins/platform.js.map +1 -0
  68. package/dist/http/routes/access.js +54 -36
  69. package/dist/http/routes/access.js.map +1 -1
  70. package/dist/http/routes/activities.js +45 -45
  71. package/dist/http/routes/activities.js.map +1 -1
  72. package/dist/http/routes/assets.js +30 -65
  73. package/dist/http/routes/assets.js.map +1 -1
  74. package/dist/http/routes/automations.js +37 -42
  75. package/dist/http/routes/automations.js.map +1 -1
  76. package/dist/http/routes/bundles.js +33 -36
  77. package/dist/http/routes/bundles.js.map +1 -1
  78. package/dist/http/routes/communities.js +93 -145
  79. package/dist/http/routes/communities.js.map +1 -1
  80. package/dist/http/routes/community-moderation.js +83 -137
  81. package/dist/http/routes/community-moderation.js.map +1 -1
  82. package/dist/http/routes/course-analytics.js +32 -15
  83. package/dist/http/routes/course-analytics.js.map +1 -1
  84. package/dist/http/routes/courses.js +25 -32
  85. package/dist/http/routes/courses.js.map +1 -1
  86. package/dist/http/routes/dashboard.js +9 -9
  87. package/dist/http/routes/dashboard.js.map +1 -1
  88. package/dist/http/routes/discussion.js +21 -39
  89. package/dist/http/routes/discussion.js.map +1 -1
  90. package/dist/http/routes/downloads.js +41 -47
  91. package/dist/http/routes/downloads.js.map +1 -1
  92. package/dist/http/routes/entitlements.js +19 -23
  93. package/dist/http/routes/entitlements.js.map +1 -1
  94. package/dist/http/routes/events.d.ts +7 -0
  95. package/dist/http/routes/events.js +333 -0
  96. package/dist/http/routes/events.js.map +1 -0
  97. package/dist/http/routes/integrations.js +40 -73
  98. package/dist/http/routes/integrations.js.map +1 -1
  99. package/dist/http/routes/member/assets.js +10 -13
  100. package/dist/http/routes/member/assets.js.map +1 -1
  101. package/dist/http/routes/member/chats.js +170 -47
  102. package/dist/http/routes/member/chats.js.map +1 -1
  103. package/dist/http/routes/member/comments.js +65 -26
  104. package/dist/http/routes/member/comments.js.map +1 -1
  105. package/dist/http/routes/member/community.js +45 -52
  106. package/dist/http/routes/member/community.js.map +1 -1
  107. package/dist/http/routes/member/courses.js +12 -19
  108. package/dist/http/routes/member/courses.js.map +1 -1
  109. package/dist/http/routes/member/devices.js +9 -10
  110. package/dist/http/routes/member/devices.js.map +1 -1
  111. package/dist/http/routes/member/downloads.js +6 -9
  112. package/dist/http/routes/member/downloads.js.map +1 -1
  113. package/dist/http/routes/member/events.d.ts +7 -0
  114. package/dist/http/routes/member/events.js +386 -0
  115. package/dist/http/routes/member/events.js.map +1 -0
  116. package/dist/http/routes/member/files.js +88 -46
  117. package/dist/http/routes/member/files.js.map +1 -1
  118. package/dist/http/routes/member/index.js +2 -0
  119. package/dist/http/routes/member/index.js.map +1 -1
  120. package/dist/http/routes/member/notification-settings.js +15 -23
  121. package/dist/http/routes/member/notification-settings.js.map +1 -1
  122. package/dist/http/routes/member/notifications.js +16 -16
  123. package/dist/http/routes/member/notifications.js.map +1 -1
  124. package/dist/http/routes/member/org.js +5 -15
  125. package/dist/http/routes/member/org.js.map +1 -1
  126. package/dist/http/routes/member/posts.js +67 -68
  127. package/dist/http/routes/member/posts.js.map +1 -1
  128. package/dist/http/routes/member/profile.js +15 -6
  129. package/dist/http/routes/member/profile.js.map +1 -1
  130. package/dist/http/routes/member/progress.js +7 -11
  131. package/dist/http/routes/member/progress.js.map +1 -1
  132. package/dist/http/routes/organizations.js +319 -135
  133. package/dist/http/routes/organizations.js.map +1 -1
  134. package/dist/http/routes/platform.d.ts +7 -0
  135. package/dist/http/routes/platform.js +61 -0
  136. package/dist/http/routes/platform.js.map +1 -0
  137. package/dist/http/routes/students.js +12 -75
  138. package/dist/http/routes/students.js.map +1 -1
  139. package/dist/http/routes/webhooks.js +30 -39
  140. package/dist/http/routes/webhooks.js.map +1 -1
  141. package/dist/http/routes.js +9 -0
  142. package/dist/http/routes.js.map +1 -1
  143. package/dist/http/schemas/access.d.ts +346 -64
  144. package/dist/http/schemas/access.js +39 -12
  145. package/dist/http/schemas/access.js.map +1 -1
  146. package/dist/http/schemas/assets.d.ts +8 -3
  147. package/dist/http/schemas/assets.js +6 -5
  148. package/dist/http/schemas/assets.js.map +1 -1
  149. package/dist/http/schemas/automations.d.ts +4 -4
  150. package/dist/http/schemas/chats.d.ts +216 -37
  151. package/dist/http/schemas/chats.js +13 -4
  152. package/dist/http/schemas/chats.js.map +1 -1
  153. package/dist/http/schemas/community.d.ts +105 -31
  154. package/dist/http/schemas/community.js +25 -9
  155. package/dist/http/schemas/community.js.map +1 -1
  156. package/dist/http/schemas/course-analytics.d.ts +51 -1
  157. package/dist/http/schemas/course-analytics.js +19 -1
  158. package/dist/http/schemas/course-analytics.js.map +1 -1
  159. package/dist/http/schemas/dashboard.d.ts +1 -0
  160. package/dist/http/schemas/dashboard.js +2 -1
  161. package/dist/http/schemas/dashboard.js.map +1 -1
  162. package/dist/http/schemas/discussion.d.ts +22 -72
  163. package/dist/http/schemas/discussion.js +2 -2
  164. package/dist/http/schemas/discussion.js.map +1 -1
  165. package/dist/http/schemas/entitlements.d.ts +9 -12
  166. package/dist/http/schemas/entitlements.js +11 -5
  167. package/dist/http/schemas/entitlements.js.map +1 -1
  168. package/dist/http/schemas/events.d.ts +527 -0
  169. package/dist/http/schemas/events.js +64 -0
  170. package/dist/http/schemas/events.js.map +1 -0
  171. package/dist/http/schemas/files.d.ts +24 -5
  172. package/dist/http/schemas/files.js +15 -2
  173. package/dist/http/schemas/files.js.map +1 -1
  174. package/dist/http/schemas/index.d.ts +11 -9
  175. package/dist/http/schemas/index.js +11 -9
  176. package/dist/http/schemas/invites.d.ts +3 -47
  177. package/dist/http/schemas/invites.js +4 -25
  178. package/dist/http/schemas/invites.js.map +1 -1
  179. package/dist/http/schemas/member.d.ts +94 -7
  180. package/dist/http/schemas/member.js +7 -3
  181. package/dist/http/schemas/member.js.map +1 -1
  182. package/dist/http/schemas/members.d.ts +329 -65
  183. package/dist/http/schemas/members.js +67 -25
  184. package/dist/http/schemas/members.js.map +1 -1
  185. package/dist/http/schemas/notifications.d.ts +58 -54
  186. package/dist/http/schemas/platform.d.ts +38 -0
  187. package/dist/http/schemas/platform.js +19 -0
  188. package/dist/http/schemas/platform.js.map +1 -0
  189. package/dist/http/schemas/posts.d.ts +55 -35
  190. package/dist/http/schemas/posts.js +4 -4
  191. package/dist/http/schemas/posts.js.map +1 -1
  192. package/dist/http/schemas/profile.d.ts +1 -1
  193. package/dist/http/schemas/shared.d.ts +1 -11
  194. package/dist/http/schemas/shared.js +1 -10
  195. package/dist/http/schemas/shared.js.map +1 -1
  196. package/dist/http/schemas/students.d.ts +22 -0
  197. package/dist/http/schemas/students.js +3 -1
  198. package/dist/http/schemas/students.js.map +1 -1
  199. package/dist/http/scope.d.ts +9 -13
  200. package/dist/http/scope.js +29 -40
  201. package/dist/http/scope.js.map +1 -1
  202. package/dist/http/server.js +9 -0
  203. package/dist/http/server.js.map +1 -1
  204. package/dist/http/shutdown.d.ts +11 -0
  205. package/dist/http/shutdown.js +40 -0
  206. package/dist/http/shutdown.js.map +1 -0
  207. package/dist/http/sse.d.ts +19 -0
  208. package/dist/http/sse.js +104 -0
  209. package/dist/http/sse.js.map +1 -0
  210. package/dist/http/test-caller.d.ts +13 -0
  211. package/dist/http/test-caller.js +14 -0
  212. package/dist/http/test-caller.js.map +1 -0
  213. package/dist/http/well-known.js +2 -2
  214. package/dist/http/well-known.js.map +1 -1
  215. package/dist/index.d.ts +4 -2
  216. package/dist/index.js +4 -2
  217. package/dist/index.js.map +1 -1
  218. package/dist/packages/server/package.js +1 -1
  219. package/package.json +7 -6
  220. package/dist/http/routes/member/actor.d.ts +0 -14
  221. package/dist/http/routes/member/actor.js +0 -11
  222. package/dist/http/routes/member/actor.js.map +0 -1
package/README.md CHANGED
@@ -10,14 +10,28 @@ integration plugins. The `headless-lms` bin lives in `@headless-lms/cli`.
10
10
 
11
11
  ```ts
12
12
  import { createContainer, buildServer } from '@headless-lms/server';
13
+ import { PostgresPubSub } from '@headless-lms/adapter-pubsub-postgres';
13
14
 
14
15
  const container = await createContainer(config, {
15
16
  pluginsDir, // one folder per integration (directory name = integration id)
17
+ adapters: {
18
+ pubsub: (logger) => new PostgresPubSub({ connectionString: config.pubsubDatabaseUrl, logger }),
19
+ },
16
20
  });
17
21
  const app = await buildServer(config, container);
18
22
  await app.listen({ port: config.port, host: config.host });
23
+ await container.pubsub?.start();
24
+ await container.chatHub?.start();
25
+ container.outboxRelay.start();
26
+ await container.automationEngine.start();
19
27
  ```
20
28
 
29
+ The container constructs the outbox relay, the automation engine, `PubSub` and the
30
+ chat hub but starts none of them; the entry point starts them after `listen`, `PubSub`
31
+ and the hub before the relay. `buildServer` closes the hub in `preClose`, so open chat
32
+ streams receive their closing frame, and on close stops the relay, the engine and then
33
+ `PubSub`.
34
+
21
35
  Public surface (`src/index.ts`): `createContainer`, `buildServer`,
22
36
  `loadIntegrations`, the operational function `runMigrations` (re-exported from
23
37
  `@headless-lms/adapter-db`, which ships the `drizzle/` migration assets;
@@ -4,16 +4,18 @@ import { ChatService, DiscussionService, PostService, SubscriptionService } from
4
4
  import { IdentityService } from "@headless-lms/core/identity";
5
5
  import { OrganizationService } from "@headless-lms/core/organizations";
6
6
  import { CommunityMailReads, GrantWriter } from "@headless-lms/core/automations";
7
+ import { BodyParser } from "@headless-lms/core/shared/ports";
7
8
  //#region src/app/community-mail.d.ts
8
9
  type CommunityMailDeps = {
9
- content: Pick<ContentService, 'getCommunity' | 'getSpace' | 'listCommunitiesById'>;
10
- entitlements: Pick<EntitlementsService, 'openSpacesFor' | 'communitiesOpenedBy' | 'communityMembersAmong' | 'spaceOpenersAmong' | 'spaceOpeners' | 'communityModeratorIds' | 'countActiveSpaceMembers' | 'communityIdsOpenedFor' | 'ensureGrant'>;
11
- organizations: Pick<OrganizationService, 'getOrgUserById' | 'staffOrgUserIds'>;
10
+ content: Pick<ContentService, 'findCommunity' | 'getSpace' | 'listCommunitiesById' | 'findCourse' | 'findDownload' | 'findEvent' | 'findBundle'>;
11
+ entitlements: Pick<EntitlementsService, 'openSpacesFor' | 'communitiesOpenedBy' | 'communityMembersAmong' | 'spaceOpenersAmong' | 'spaceOpeners' | 'countActiveSpaceMembers' | 'communityIdsOpenedFor' | 'grant'>;
12
+ organizations: Pick<OrganizationService, 'getById' | 'getOrgUserById' | 'actorFor' | 'systemActor' | 'holders' | 'getRole' | 'permissionsOfMany'>;
12
13
  identity: Pick<IdentityService, 'displayName'>;
13
14
  posts: Pick<PostService, 'find'>;
14
15
  subscriptions: Pick<SubscriptionService, 'followerIds' | 'itemSubscriptions'>;
15
16
  discussion: Pick<DiscussionService, 'getComment'>;
16
17
  chats: Pick<ChatService, 'chatSummary' | 'latestMessageBody' | 'isBlocked'>;
18
+ bodies: BodyParser;
17
19
  };
18
20
  declare function communityMail(deps: CommunityMailDeps): {
19
21
  mail: Omit<CommunityMailReads, 'recentMessageRecipients'>;
@@ -1,12 +1,14 @@
1
+ import { NotFoundError } from "@headless-lms/core/shared/errors";
1
2
  //#region src/app/community-mail.ts
2
3
  function communityMail(deps) {
4
+ const system = (orgId) => deps.organizations.systemActor(orgId);
3
5
  return {
4
6
  mail: {
5
7
  async communityTitle(orgId, communityId) {
6
- return (await deps.content.getCommunity(orgId, communityId))?.title ?? null;
8
+ return (await deps.content.findCommunity(communityId, system(orgId)))?.title ?? null;
7
9
  },
8
10
  async space(orgId, spaceId) {
9
- const space = await deps.content.getSpace(orgId, spaceId);
11
+ const space = await deps.content.getSpace(spaceId, system(orgId));
10
12
  return space ? {
11
13
  name: space.name,
12
14
  communityId: space.communityId,
@@ -14,23 +16,69 @@ function communityMail(deps) {
14
16
  } : null;
15
17
  },
16
18
  async openSpaceNames(orgId, orgUserId, communityId) {
17
- const orgUser = await deps.organizations.getOrgUserById(orgId, orgUserId);
18
- if (!orgUser) return [];
19
- return (await deps.entitlements.openSpacesFor(orgId, {
20
- id: orgUser.id,
21
- role: orgUser.role
22
- }, communityId)).map((space) => space.name);
19
+ const person = await deps.organizations.actorFor(orgId, orgUserId);
20
+ if (!person) return [];
21
+ return (await deps.entitlements.openSpacesFor(communityId, person)).map((space) => space.name);
23
22
  },
24
23
  communitiesOpenedBy: (orgId, grant) => deps.entitlements.communitiesOpenedBy(orgId, grant),
25
24
  communityMembersAmong: (orgId, communityId, ids) => deps.entitlements.communityMembersAmong(orgId, communityId, ids),
26
25
  spaceOpenersAmong: (orgId, spaceId, ids) => deps.entitlements.spaceOpenersAmong(orgId, spaceId, ids),
27
26
  spaceOpeners: (orgId, spaceId) => deps.entitlements.spaceOpeners(orgId, spaceId),
28
- moderationAudience: (orgId, communityId) => deps.entitlements.communityModeratorIds(orgId, communityId),
29
- staffIds: (orgId) => deps.organizations.staffOrgUserIds(orgId),
27
+ holders: (orgId, permission, on) => deps.organizations.holders(orgId, permission, on),
28
+ async heldOnOrg(orgId, orgUserId, permissions) {
29
+ const held = (await deps.organizations.permissionsOfMany(orgId, [orgUserId])).get(orgUserId);
30
+ return held ? permissions.filter((permission) => held.has(permission)) : [];
31
+ },
32
+ async role(orgId, roleKey) {
33
+ try {
34
+ const role = await deps.organizations.getRole(roleKey, system(orgId));
35
+ return {
36
+ name: role.name,
37
+ permissions: role.permissions
38
+ };
39
+ } catch (err) {
40
+ if (err instanceof NotFoundError) return null;
41
+ throw err;
42
+ }
43
+ },
44
+ async orgName(orgId) {
45
+ return (await deps.organizations.getById(orgId))?.name ?? null;
46
+ },
47
+ async contentInfo(orgId, item) {
48
+ const actor = system(orgId);
49
+ if ("bundleId" in item) {
50
+ const bundle = await deps.content.findBundle(item.bundleId, actor);
51
+ return bundle ? {
52
+ title: bundle.name,
53
+ type: "bundle"
54
+ } : null;
55
+ }
56
+ const course = await deps.content.findCourse(item.contentId, actor);
57
+ if (course) return {
58
+ title: course.title,
59
+ type: course.type
60
+ };
61
+ const download = await deps.content.findDownload(item.contentId, actor);
62
+ if (download) return {
63
+ title: download.title,
64
+ type: download.type
65
+ };
66
+ const community = await deps.content.findCommunity(item.contentId, actor);
67
+ if (community) return {
68
+ title: community.title,
69
+ type: community.type
70
+ };
71
+ const event = await deps.content.findEvent(item.contentId, actor);
72
+ return event ? {
73
+ title: event.title,
74
+ type: event.type
75
+ } : null;
76
+ },
30
77
  async displayName(orgId, orgUserId) {
31
78
  const orgUser = await deps.organizations.getOrgUserById(orgId, orgUserId);
32
79
  return deps.identity.displayName(orgUser?.userId ?? null);
33
80
  },
81
+ postText: (body) => deps.bodies.parse(body).text,
34
82
  followerIds: (orgId, postId) => deps.subscriptions.followerIds(orgId, postId),
35
83
  async comment(orgId, commentId) {
36
84
  const comment = await deps.discussion.getComment(orgId, commentId);
@@ -77,13 +125,13 @@ function communityMail(deps) {
77
125
  activeMemberCount: (orgId, spaceId) => deps.entitlements.countActiveSpaceMembers(orgId, spaceId),
78
126
  async memberCommunityTitles(orgId, orgUserId) {
79
127
  const ids = await deps.entitlements.communityIdsOpenedFor(orgId, orgUserId);
80
- return (await deps.content.listCommunitiesById(orgId, ids)).map((community) => ({
128
+ return (await deps.content.listCommunitiesById(ids, system(orgId))).map((community) => ({
81
129
  id: community.id,
82
130
  title: community.title
83
131
  }));
84
132
  }
85
133
  },
86
- grants: { grant: (orgId, input) => deps.entitlements.ensureGrant(orgId, input) }
134
+ grants: { grant: (orgId, input) => deps.entitlements.grant(input, system(orgId)) }
87
135
  };
88
136
  }
89
137
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"community-mail.js","names":[],"sources":["../../src/app/community-mail.ts"],"sourcesContent":["import type { CommunityMailReads, GrantWriter } from '@headless-lms/core/automations';\nimport type { ContentService } from '@headless-lms/core/content';\nimport type {\n ChatService,\n DiscussionService,\n PostService,\n SubscriptionService,\n} from '@headless-lms/core/discussion';\nimport type { EntitlementsService } from '@headless-lms/core/entitlements';\nimport type { IdentityService } from '@headless-lms/core/identity';\nimport type { OrganizationService } from '@headless-lms/core/organizations';\n\nexport type CommunityMailDeps = {\n content: Pick<ContentService, 'getCommunity' | 'getSpace' | 'listCommunitiesById'>;\n entitlements: Pick<\n EntitlementsService,\n | 'openSpacesFor'\n | 'communitiesOpenedBy'\n | 'communityMembersAmong'\n | 'spaceOpenersAmong'\n | 'spaceOpeners'\n | 'communityModeratorIds'\n | 'countActiveSpaceMembers'\n | 'communityIdsOpenedFor'\n | 'ensureGrant'\n >;\n organizations: Pick<OrganizationService, 'getOrgUserById' | 'staffOrgUserIds'>;\n identity: Pick<IdentityService, 'displayName'>;\n posts: Pick<PostService, 'find'>;\n subscriptions: Pick<SubscriptionService, 'followerIds' | 'itemSubscriptions'>;\n discussion: Pick<DiscussionService, 'getComment'>;\n chats: Pick<ChatService, 'chatSummary' | 'latestMessageBody' | 'isBlocked'>;\n};\n\nexport function communityMail(deps: CommunityMailDeps): {\n mail: Omit<CommunityMailReads, 'recentMessageRecipients'>;\n grants: GrantWriter;\n} {\n const mail: Omit<CommunityMailReads, 'recentMessageRecipients'> = {\n async communityTitle(orgId, communityId) {\n return (await deps.content.getCommunity(orgId, communityId))?.title ?? null;\n },\n async space(orgId, spaceId) {\n const space = await deps.content.getSpace(orgId, spaceId);\n return space\n ? { name: space.name, communityId: space.communityId, capacity: space.capacity }\n : null;\n },\n async openSpaceNames(orgId, orgUserId, communityId) {\n const orgUser = await deps.organizations.getOrgUserById(orgId, orgUserId);\n if (!orgUser) {\n return [];\n }\n const spaces = await deps.entitlements.openSpacesFor(\n orgId,\n { id: orgUser.id, role: orgUser.role },\n communityId,\n );\n return spaces.map((space) => space.name);\n },\n communitiesOpenedBy: (orgId, grant) => deps.entitlements.communitiesOpenedBy(orgId, grant),\n communityMembersAmong: (orgId, communityId, ids) =>\n deps.entitlements.communityMembersAmong(orgId, communityId, ids),\n spaceOpenersAmong: (orgId, spaceId, ids) =>\n deps.entitlements.spaceOpenersAmong(orgId, spaceId, ids),\n spaceOpeners: (orgId, spaceId) => deps.entitlements.spaceOpeners(orgId, spaceId),\n moderationAudience: (orgId, communityId) =>\n deps.entitlements.communityModeratorIds(orgId, communityId),\n staffIds: (orgId) => deps.organizations.staffOrgUserIds(orgId),\n async displayName(orgId, orgUserId) {\n const orgUser = await deps.organizations.getOrgUserById(orgId, orgUserId);\n return deps.identity.displayName(orgUser?.userId ?? null);\n },\n followerIds: (orgId, postId) => deps.subscriptions.followerIds(orgId, postId),\n async comment(orgId, commentId) {\n const comment = await deps.discussion.getComment(orgId, commentId);\n return comment\n ? {\n orgUserId: comment.orgUserId,\n postId: comment.postId,\n activityId: comment.activityId,\n parentId: comment.parentId,\n body: comment.body,\n }\n : null;\n },\n async post(orgId, postId) {\n const post = await deps.posts.find(orgId, postId);\n return post\n ? {\n title: post.title,\n body: post.body,\n spaceId: post.spaceId,\n orgUserId: post.orgUserId,\n communityId: post.communityId,\n }\n : null;\n },\n async chat(orgId, chatId) {\n const summary = await deps.chats.chatSummary(orgId, chatId);\n if (!summary) {\n return null;\n }\n const mutes = new Map(\n (await deps.subscriptions.itemSubscriptions(orgId, { type: 'chat', id: chatId }))\n .filter((subscription) => subscription.state === 'muted')\n .map((subscription) => [subscription.orgUserId, subscription.mutedUntil]),\n );\n return {\n communityId: summary.chat.communityId,\n kind: summary.chat.kind,\n status: summary.chat.status,\n createdBy: summary.chat.createdBy,\n participants: summary.participants.map((participant) => ({\n orgUserId: participant.orgUserId,\n muted: mutes.has(participant.orgUserId),\n mutedUntil: mutes.get(participant.orgUserId) ?? null,\n leftAt: participant.leftAt,\n })),\n };\n },\n latestMessageBody: (orgId, chatId) => deps.chats.latestMessageBody(orgId, chatId),\n pairBlocked: (orgId, communityId, a, b) => deps.chats.isBlocked(orgId, communityId, a, b),\n activeMemberCount: (orgId, spaceId) =>\n deps.entitlements.countActiveSpaceMembers(orgId, spaceId),\n async memberCommunityTitles(orgId, orgUserId) {\n const ids = await deps.entitlements.communityIdsOpenedFor(orgId, orgUserId);\n const communities = await deps.content.listCommunitiesById(orgId, ids);\n return communities.map((community) => ({ id: community.id, title: community.title }));\n },\n };\n return {\n mail,\n grants: {\n grant: (orgId, input) => deps.entitlements.ensureGrant(orgId, input),\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"community-mail.js","names":[],"sources":["../../src/app/community-mail.ts"],"sourcesContent":["import type { CommunityMailReads, GrantWriter } from '@headless-lms/core/automations';\nimport type { ContentService } from '@headless-lms/core/content';\nimport type {\n ChatService,\n DiscussionService,\n PostService,\n SubscriptionService,\n} from '@headless-lms/core/discussion';\nimport type { EntitlementsService } from '@headless-lms/core/entitlements';\nimport type { IdentityService } from '@headless-lms/core/identity';\nimport type { OrganizationService } from '@headless-lms/core/organizations';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { BodyParser } from '@headless-lms/core/shared/ports';\n\nexport type CommunityMailDeps = {\n content: Pick<\n ContentService,\n | 'findCommunity'\n | 'getSpace'\n | 'listCommunitiesById'\n | 'findCourse'\n | 'findDownload'\n | 'findEvent'\n | 'findBundle'\n >;\n entitlements: Pick<\n EntitlementsService,\n | 'openSpacesFor'\n | 'communitiesOpenedBy'\n | 'communityMembersAmong'\n | 'spaceOpenersAmong'\n | 'spaceOpeners'\n | 'countActiveSpaceMembers'\n | 'communityIdsOpenedFor'\n | 'grant'\n >;\n organizations: Pick<\n OrganizationService,\n | 'getById'\n | 'getOrgUserById'\n | 'actorFor'\n | 'systemActor'\n | 'holders'\n | 'getRole'\n | 'permissionsOfMany'\n >;\n identity: Pick<IdentityService, 'displayName'>;\n posts: Pick<PostService, 'find'>;\n subscriptions: Pick<SubscriptionService, 'followerIds' | 'itemSubscriptions'>;\n discussion: Pick<DiscussionService, 'getComment'>;\n chats: Pick<ChatService, 'chatSummary' | 'latestMessageBody' | 'isBlocked'>;\n bodies: BodyParser;\n};\n\nexport function communityMail(deps: CommunityMailDeps): {\n mail: Omit<CommunityMailReads, 'recentMessageRecipients'>;\n grants: GrantWriter;\n} {\n const system = (orgId: string) => deps.organizations.systemActor(orgId);\n const mail: Omit<CommunityMailReads, 'recentMessageRecipients'> = {\n async communityTitle(orgId, communityId) {\n return (await deps.content.findCommunity(communityId, system(orgId)))?.title ?? null;\n },\n async space(orgId, spaceId) {\n const space = await deps.content.getSpace(spaceId, system(orgId));\n return space\n ? { name: space.name, communityId: space.communityId, capacity: space.capacity }\n : null;\n },\n async openSpaceNames(orgId, orgUserId, communityId) {\n const person = await deps.organizations.actorFor(orgId, orgUserId);\n if (!person) {\n return [];\n }\n const spaces = await deps.entitlements.openSpacesFor(communityId, person);\n return spaces.map((space) => space.name);\n },\n communitiesOpenedBy: (orgId, grant) => deps.entitlements.communitiesOpenedBy(orgId, grant),\n communityMembersAmong: (orgId, communityId, ids) =>\n deps.entitlements.communityMembersAmong(orgId, communityId, ids),\n spaceOpenersAmong: (orgId, spaceId, ids) =>\n deps.entitlements.spaceOpenersAmong(orgId, spaceId, ids),\n spaceOpeners: (orgId, spaceId) => deps.entitlements.spaceOpeners(orgId, spaceId),\n holders: (orgId, permission, on) => deps.organizations.holders(orgId, permission, on),\n async heldOnOrg(orgId, orgUserId, permissions) {\n const held = (await deps.organizations.permissionsOfMany(orgId, [orgUserId])).get(orgUserId);\n return held ? permissions.filter((permission) => held.has(permission)) : [];\n },\n async role(orgId, roleKey) {\n try {\n const role = await deps.organizations.getRole(roleKey, system(orgId));\n return { name: role.name, permissions: role.permissions };\n } catch (err) {\n if (err instanceof NotFoundError) {\n return null;\n }\n throw err;\n }\n },\n async orgName(orgId) {\n return (await deps.organizations.getById(orgId))?.name ?? null;\n },\n async contentInfo(orgId, item) {\n const actor = system(orgId);\n if ('bundleId' in item) {\n const bundle = await deps.content.findBundle(item.bundleId, actor);\n return bundle ? { title: bundle.name, type: 'bundle' } : null;\n }\n const course = await deps.content.findCourse(item.contentId, actor);\n if (course) {\n return { title: course.title, type: course.type };\n }\n const download = await deps.content.findDownload(item.contentId, actor);\n if (download) {\n return { title: download.title, type: download.type };\n }\n const community = await deps.content.findCommunity(item.contentId, actor);\n if (community) {\n return { title: community.title, type: community.type };\n }\n const event = await deps.content.findEvent(item.contentId, actor);\n return event ? { title: event.title, type: event.type } : null;\n },\n async displayName(orgId, orgUserId) {\n const orgUser = await deps.organizations.getOrgUserById(orgId, orgUserId);\n return deps.identity.displayName(orgUser?.userId ?? null);\n },\n postText: (body) => deps.bodies.parse(body).text,\n followerIds: (orgId, postId) => deps.subscriptions.followerIds(orgId, postId),\n async comment(orgId, commentId) {\n const comment = await deps.discussion.getComment(orgId, commentId);\n return comment\n ? {\n orgUserId: comment.orgUserId,\n postId: comment.postId,\n activityId: comment.activityId,\n parentId: comment.parentId,\n body: comment.body,\n }\n : null;\n },\n async post(orgId, postId) {\n const post = await deps.posts.find(orgId, postId);\n return post\n ? {\n title: post.title,\n body: post.body,\n spaceId: post.spaceId,\n orgUserId: post.orgUserId,\n communityId: post.communityId,\n }\n : null;\n },\n async chat(orgId, chatId) {\n const summary = await deps.chats.chatSummary(orgId, chatId);\n if (!summary) {\n return null;\n }\n const mutes = new Map(\n (await deps.subscriptions.itemSubscriptions(orgId, { type: 'chat', id: chatId }))\n .filter((subscription) => subscription.state === 'muted')\n .map((subscription) => [subscription.orgUserId, subscription.mutedUntil]),\n );\n return {\n communityId: summary.chat.communityId,\n kind: summary.chat.kind,\n status: summary.chat.status,\n createdBy: summary.chat.createdBy,\n participants: summary.participants.map((participant) => ({\n orgUserId: participant.orgUserId,\n muted: mutes.has(participant.orgUserId),\n mutedUntil: mutes.get(participant.orgUserId) ?? null,\n leftAt: participant.leftAt,\n })),\n };\n },\n latestMessageBody: (orgId, chatId) => deps.chats.latestMessageBody(orgId, chatId),\n pairBlocked: (orgId, communityId, a, b) => deps.chats.isBlocked(orgId, communityId, a, b),\n activeMemberCount: (orgId, spaceId) =>\n deps.entitlements.countActiveSpaceMembers(orgId, spaceId),\n async memberCommunityTitles(orgId, orgUserId) {\n const ids = await deps.entitlements.communityIdsOpenedFor(orgId, orgUserId);\n const communities = await deps.content.listCommunitiesById(ids, system(orgId));\n return communities.map((community) => ({ id: community.id, title: community.title }));\n },\n };\n return {\n mail,\n grants: {\n grant: (orgId, input) => deps.entitlements.grant(input, system(orgId)),\n },\n };\n}\n"],"mappings":"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"}
@@ -1,13 +1,15 @@
1
1
  import { McpEndpoints } from "./mcp-endpoints.js";
2
+ import { PlatformConfig, PlatformKeyCheck } from "./platform.js";
3
+ import { DatabasePoolOptions } from "@headless-lms/adapter-db";
2
4
  import { PollingOutboxRelayConfig } from "@headless-lms/adapter-defaults/events/outbox-relay";
3
5
  import { LogLevel, PinoInstance, RequestLogContext } from "@headless-lms/adapter-defaults/logging";
4
6
  import { BetterAuth } from "@headless-lms/adapter-auth";
5
7
  import { CommunityNotificationCategories, ContentService } from "@headless-lms/core/content";
6
8
  import { EntitlementsServiceImpl } from "@headless-lms/core/entitlements";
7
9
  import { ProgressServiceImpl } from "@headless-lms/core/progress";
8
- import { ChatServiceImpl, DiscussionServiceImpl, PostServiceImpl, SubscriptionServiceImpl } from "@headless-lms/core/discussion";
10
+ import { ChatHub, ChatHubOptions, ChatServiceImpl, DiscussionServiceImpl, PostServiceImpl, SubscriptionServiceImpl } from "@headless-lms/core/discussion";
9
11
  import { DevicesServiceImpl, IdentityServiceImpl, NotificationSettingsServiceImpl, SessionAdmin } from "@headless-lms/core/identity";
10
- import { OrgAdmin, OrgNotificationCategories, OrganizationServiceImpl } from "@headless-lms/core/organizations";
12
+ import { NotificationCatalog, OrgAdmin, OrgNotificationCategories, OrganizationServiceImpl, RoleDefinition } from "@headless-lms/core/organizations";
11
13
  import { AssetsServiceImpl } from "@headless-lms/core/assets";
12
14
  import { IntegrationsServiceImpl } from "@headless-lms/core/integrations";
13
15
  import { AutomationsServiceImpl, NotificationCategories, NotificationsServiceImpl } from "@headless-lms/core/automations";
@@ -16,7 +18,7 @@ import { AdminReportServiceImpl } from "@headless-lms/core/reporting/admin";
16
18
  import { MemberReportServiceImpl } from "@headless-lms/core/reporting/member";
17
19
  import { SettingsService } from "@headless-lms/core/shared/settings";
18
20
  import { AutomationEngine, PushConfig, WebhookSender } from "@headless-lms/core/types";
19
- import { ChannelRegistry, CredentialStore, EmailSender, Logger, NotificationChannel, NotificationTypeRegistry, ObjectStorage, OutboxRelay, PushSender, Scheduler, TemplateContext, TemplateRenderer } from "@headless-lms/core/shared/ports";
21
+ import { BodyParser, ChannelRegistry, CredentialStore, EmailSender, Logger, NotificationChannel, NotificationTypeRegistry, ObjectStorage, OutboxRelay, PubSub, PushSender, Scheduler, TemplateContext, TemplateRenderer } from "@headless-lms/core/shared/ports";
20
22
  //#region src/app/container.d.ts
21
23
  /** Installation-supplied ports; an absent slot falls back to a fail-loudly stub. */
22
24
  interface AdapterOverrides {
@@ -33,14 +35,22 @@ interface AdapterOverrides {
33
35
  /** Push credentials per provider. Absent → no provider is configured and devices cannot register. */
34
36
  push?: PushConfig;
35
37
  pushSender?: PushSender;
38
+ /** Reads post bodies: their text, mentions and images. Absent → PlainBodyParser (plain text, no images). */
39
+ bodyParser?: BodyParser;
40
+ /** Delivery to every API instance, or a factory given its logger. Absent → no live chat; the chat stream answers 503. */
41
+ pubsub?: PubSub | ((logger: Logger) => PubSub);
36
42
  }
37
43
  interface BuildContainerOptions {
38
44
  /** Installation's plugins folder, scanned by loadIntegrations. Absent → no integrations. */
39
45
  pluginsDir?: string;
40
46
  adapters?: AdapterOverrides;
47
+ roles?: readonly RoleDefinition[];
48
+ /** Live chat hub timings. Absent → the defaults (1 s state flush, 30 s session check). */
49
+ live?: ChatHubOptions;
41
50
  }
42
51
  interface Config {
43
52
  databaseUrl: string;
53
+ databasePool?: DatabasePoolOptions;
44
54
  authBaseURL: string;
45
55
  authSecret: string;
46
56
  trustedOrigins: string[];
@@ -65,8 +75,10 @@ interface Config {
65
75
  * ServerConfig.deliveryExpirySeconds — createContainer threads it through. */
66
76
  deliveryExpirySeconds: number;
67
77
  memberImageMaxBytes: number;
78
+ memberFileMaxBytes: number;
68
79
  /** Let the default webhook sender reach loopback and private network addresses. Local development only. */
69
80
  webhookAllowPrivateNetwork?: boolean;
81
+ platform?: PlatformConfig;
70
82
  }
71
83
  /** Tuning for the transactional-outbox relay. Every field is optional; the
72
84
  * container resolves against OUTBOX_DEFAULTS. */
@@ -91,6 +103,7 @@ declare function resolveLoggingConfig(config?: LoggingConfig): Required<LoggingC
91
103
  interface Container {
92
104
  auth: BetterAuth;
93
105
  mcp: McpEndpoints;
106
+ platform: PlatformKeyCheck | null;
94
107
  orgProvider: OrgAdmin;
95
108
  identity: IdentityServiceImpl;
96
109
  organizations: OrganizationServiceImpl;
@@ -110,6 +123,7 @@ interface Container {
110
123
  community: CommunityNotificationCategories;
111
124
  org: OrgNotificationCategories;
112
125
  };
126
+ notificationCatalog: NotificationCatalog;
113
127
  notificationCategoryLists: NotificationCategories;
114
128
  notificationSettings: NotificationSettingsServiceImpl;
115
129
  devices: DevicesServiceImpl;
@@ -134,6 +148,13 @@ interface Container {
134
148
  * not run a worker). buildServer stops it onClose. */
135
149
  automationEngine: AutomationEngine;
136
150
  scheduler: Scheduler;
151
+ /** The PubSub adapter — constructed but NEVER started by the container; the
152
+ * installation's entry point starts it after listen. buildServer stops it
153
+ * onClose. Null without one. */
154
+ pubsub: PubSub | null;
155
+ /** This process's live chat hub; started by the entry point after `pubsub`,
156
+ * closed by buildServer preClose. Null without a PubSub. */
157
+ chatHub: ChatHub | null;
137
158
  /** Root logger port — components receive children bound with { name }. */
138
159
  logger: Logger;
139
160
  /** The raw pino root; buildServer hands it to Fastify so HTTP shares the stream. */