@headless-lms/server 0.4.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 (223) 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 +16 -31
  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 +71 -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 +19 -14
  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 +211 -32
  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 +91 -44
  154. package/dist/http/schemas/community.js +21 -8
  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 +15 -5
  172. package/dist/http/schemas/files.js +10 -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/integrations.d.ts +4 -4
  177. package/dist/http/schemas/invites.d.ts +3 -47
  178. package/dist/http/schemas/invites.js +4 -25
  179. package/dist/http/schemas/invites.js.map +1 -1
  180. package/dist/http/schemas/member.d.ts +94 -7
  181. package/dist/http/schemas/member.js +7 -3
  182. package/dist/http/schemas/member.js.map +1 -1
  183. package/dist/http/schemas/members.d.ts +333 -69
  184. package/dist/http/schemas/members.js +67 -25
  185. package/dist/http/schemas/members.js.map +1 -1
  186. package/dist/http/schemas/notifications.d.ts +57 -53
  187. package/dist/http/schemas/platform.d.ts +37 -0
  188. package/dist/http/schemas/platform.js +19 -0
  189. package/dist/http/schemas/platform.js.map +1 -0
  190. package/dist/http/schemas/posts.d.ts +55 -35
  191. package/dist/http/schemas/posts.js +4 -4
  192. package/dist/http/schemas/posts.js.map +1 -1
  193. package/dist/http/schemas/profile.d.ts +1 -1
  194. package/dist/http/schemas/shared.d.ts +1 -11
  195. package/dist/http/schemas/shared.js +1 -10
  196. package/dist/http/schemas/shared.js.map +1 -1
  197. package/dist/http/schemas/students.d.ts +25 -3
  198. package/dist/http/schemas/students.js +3 -1
  199. package/dist/http/schemas/students.js.map +1 -1
  200. package/dist/http/scope.d.ts +9 -13
  201. package/dist/http/scope.js +29 -40
  202. package/dist/http/scope.js.map +1 -1
  203. package/dist/http/server.js +9 -0
  204. package/dist/http/server.js.map +1 -1
  205. package/dist/http/shutdown.d.ts +11 -0
  206. package/dist/http/shutdown.js +40 -0
  207. package/dist/http/shutdown.js.map +1 -0
  208. package/dist/http/sse.d.ts +19 -0
  209. package/dist/http/sse.js +104 -0
  210. package/dist/http/sse.js.map +1 -0
  211. package/dist/http/test-caller.d.ts +13 -0
  212. package/dist/http/test-caller.js +14 -0
  213. package/dist/http/test-caller.js.map +1 -0
  214. package/dist/http/well-known.js +2 -2
  215. package/dist/http/well-known.js.map +1 -1
  216. package/dist/index.d.ts +4 -2
  217. package/dist/index.js +4 -2
  218. package/dist/index.js.map +1 -1
  219. package/dist/packages/server/package.js +1 -1
  220. package/package.json +7 -6
  221. package/dist/http/routes/member/actor.d.ts +0 -14
  222. package/dist/http/routes/member/actor.js +0 -11
  223. package/dist/http/routes/member/actor.js.map +0 -1
@@ -1,11 +1,10 @@
1
1
  import { refusals } from "../schemas/shared.js";
2
- import { resolveConfiguratorScope, resolveScope } from "../scope.js";
2
+ import { resolveActor } from "../scope.js";
3
3
  import { CommunitiesPage, CommunitiesQuery, Community, CommunityAnalytics, CommunityIdParam, CommunitySpaceParam, CreateCommunity, CreateSpace, OrderedIds, PatchCommunitySettings, SetSpaceTopics, SpaceDetail, SpaceLinkBody, SpaceLinkParam, UpdateCommunity, UpdateSpace } from "../schemas/community.js";
4
4
  import { MessageQueue } from "../schemas/chats.js";
5
5
  import { NotificationCategoryList, ReplaceNotificationCategories } from "../schemas/notifications.js";
6
6
  import "../schemas/index.js";
7
7
  import { z } from "zod";
8
- import { NotFoundError } from "@headless-lms/core/shared/errors";
9
8
  //#region src/http/routes/communities.ts
10
9
  async function communitiesRoutes(app, container) {
11
10
  const r = app.withTypeProvider();
@@ -17,8 +16,8 @@ async function communitiesRoutes(app, container) {
17
16
  schema: {
18
17
  operationId: "listCommunities",
19
18
  tags,
20
- summary: "List communities (staff)",
21
- description: "Staff only (owner, admin, instructor). Pages every community in the organization, drafts included, filtered by `status` and searched by title.",
19
+ summary: "List communities",
20
+ description: `Requires one of \`communities:read\`, \`communities:moderate\`. Pages every community in the organization, drafts included, filtered by \`status\` and searched by title.`,
22
21
  querystring: CommunitiesQuery,
23
22
  response: {
24
23
  200: CommunitiesPage,
@@ -26,8 +25,8 @@ async function communitiesRoutes(app, container) {
26
25
  }
27
26
  },
28
27
  handler: async (req) => {
29
- const scope = await resolveScope(container, req);
30
- return content.listCommunities(scope.orgId, req.query);
28
+ const actor = await resolveActor(container, req);
29
+ return content.listCommunities(req.query, actor);
31
30
  }
32
31
  });
33
32
  r.route({
@@ -36,8 +35,8 @@ async function communitiesRoutes(app, container) {
36
35
  schema: {
37
36
  operationId: "createCommunity",
38
37
  tags,
39
- summary: "Create a community (staff)",
40
- description: "Owner or admin only. The community starts as a draft with the default direct-message settings and no spaces. Members see it once it is published and they hold it.",
38
+ summary: "Create a community",
39
+ description: `Requires \`communities:write\`. The community starts as a draft with the default direct-message settings and no spaces. Members see it once it is published and they hold it.`,
41
40
  body: CreateCommunity,
42
41
  response: {
43
42
  201: Community,
@@ -45,11 +44,8 @@ async function communitiesRoutes(app, container) {
45
44
  }
46
45
  },
47
46
  handler: async (req, reply) => {
48
- const scope = await resolveScope(container, req);
49
- const community = await content.createCommunity(scope.orgId, req.body, {
50
- id: scope.orgUserId,
51
- role: scope.role
52
- });
47
+ const actor = await resolveActor(container, req);
48
+ const community = await content.createCommunity(req.body, actor);
53
49
  req.log.info({ communityId: community.id }, "community created");
54
50
  return reply.code(201).send(community);
55
51
  }
@@ -60,8 +56,8 @@ async function communitiesRoutes(app, container) {
60
56
  schema: {
61
57
  operationId: "getCommunity",
62
58
  tags,
63
- summary: "Get a community (staff)",
64
- description: "Staff only (owner, admin, instructor). Any community of the organization, drafts included.",
59
+ summary: "Get a community",
60
+ description: `Requires one of \`communities:read\`, \`communities:moderate\`. Any community of the organization, drafts included.`,
65
61
  params: CommunityIdParam,
66
62
  response: {
67
63
  200: Community,
@@ -69,10 +65,8 @@ async function communitiesRoutes(app, container) {
69
65
  }
70
66
  },
71
67
  handler: async (req) => {
72
- const scope = await resolveScope(container, req);
73
- const community = await content.getCommunity(scope.orgId, req.params.communityId);
74
- if (!community) throw new NotFoundError("Community", req.params.communityId);
75
- return community;
68
+ const actor = await resolveActor(container, req);
69
+ return content.getCommunity(req.params.communityId, actor);
76
70
  }
77
71
  });
78
72
  r.route({
@@ -81,8 +75,8 @@ async function communitiesRoutes(app, container) {
81
75
  schema: {
82
76
  operationId: "updateCommunity",
83
77
  tags,
84
- summary: "Update a community (staff)",
85
- description: "Owner or admin only. Changes only the fields sent. `thumbnailAssetId` must be a ready library image (409 otherwise); null clears it.",
78
+ summary: "Update a community",
79
+ description: `Requires \`communities:write\`. Changes only the fields sent. \`thumbnailAssetId\` must be a ready library image (409 otherwise); null clears it. A file it does not already use must be uploaded for it, or the caller needs \`media:read\` (403 otherwise).`,
86
80
  params: CommunityIdParam,
87
81
  body: UpdateCommunity,
88
82
  response: {
@@ -91,11 +85,8 @@ async function communitiesRoutes(app, container) {
91
85
  }
92
86
  },
93
87
  handler: async (req) => {
94
- const scope = await resolveScope(container, req);
95
- return content.updateCommunity(scope.orgId, req.params.communityId, req.body, {
96
- id: scope.orgUserId,
97
- role: scope.role
98
- });
88
+ const actor = await resolveActor(container, req);
89
+ return content.updateCommunity(req.params.communityId, req.body, actor);
99
90
  }
100
91
  });
101
92
  r.route({
@@ -104,8 +95,8 @@ async function communitiesRoutes(app, container) {
104
95
  schema: {
105
96
  operationId: "deleteCommunity",
106
97
  tags,
107
- summary: "Delete a community (staff)",
108
- description: "Owner or admin only. Links to the community from other spaces are removed with it.",
98
+ summary: "Delete a community",
99
+ description: `Requires \`communities:delete\`. Links to the community from other spaces are removed with it.`,
109
100
  params: CommunityIdParam,
110
101
  response: {
111
102
  204: z.void(),
@@ -113,11 +104,8 @@ async function communitiesRoutes(app, container) {
113
104
  }
114
105
  },
115
106
  handler: async (req, reply) => {
116
- const scope = await resolveScope(container, req);
117
- await content.deleteCommunity(scope.orgId, req.params.communityId, {
118
- id: scope.orgUserId,
119
- role: scope.role
120
- });
107
+ const actor = await resolveActor(container, req);
108
+ await content.deleteCommunity(req.params.communityId, actor);
121
109
  return reply.code(204).send();
122
110
  }
123
111
  });
@@ -127,8 +115,8 @@ async function communitiesRoutes(app, container) {
127
115
  schema: {
128
116
  operationId: "patchCommunitySettings",
129
117
  tags,
130
- summary: "Change the direct-message and digest settings of a community (staff)",
131
- description: "Owner or admin only. Changes only the settings sent; the rest keep their values. The direct-message settings decide who may message whom from then on. `digest` sets when the community digest goes out, or `null` stops it; the first digest after a change goes out at the next slot.",
118
+ summary: "Change the direct-message and digest settings of a community",
119
+ description: `Requires \`communities:write\`. Changes only the settings sent; the rest keep their values. The direct-message settings decide who may message whom from then on. \`digest\` sets when the community digest goes out, or \`null\` stops it; the first digest after a change goes out at the next slot.`,
132
120
  params: CommunityIdParam,
133
121
  body: PatchCommunitySettings,
134
122
  response: {
@@ -137,11 +125,8 @@ async function communitiesRoutes(app, container) {
137
125
  }
138
126
  },
139
127
  handler: async (req) => {
140
- const scope = await resolveScope(container, req);
141
- return content.patchCommunitySettings(scope.orgId, req.params.communityId, req.body, {
142
- id: scope.orgUserId,
143
- role: scope.role
144
- });
128
+ const actor = await resolveActor(container, req);
129
+ return content.patchCommunitySettings(req.params.communityId, req.body, actor);
145
130
  }
146
131
  });
147
132
  r.route({
@@ -150,8 +135,8 @@ async function communitiesRoutes(app, container) {
150
135
  schema: {
151
136
  operationId: "getCommunityNotificationCategories",
152
137
  tags: ["Notifications"],
153
- summary: "Get a community's notification categories (staff)",
154
- description: "Owner or admin only. The list in use for the community, with `source` saying whether it is the default or a stored list.",
138
+ summary: "Get a community's notification categories",
139
+ description: `Requires \`communities:read\`. On the community. The list in use for the community, with \`source\` saying whether it is the default or a stored list.`,
155
140
  params: CommunityIdParam,
156
141
  response: {
157
142
  200: NotificationCategoryList,
@@ -159,8 +144,8 @@ async function communitiesRoutes(app, container) {
159
144
  }
160
145
  },
161
146
  handler: async (req) => {
162
- const scope = await resolveConfiguratorScope(container, req);
163
- return container.notificationCategories.community.get(scope.orgId, req.params.communityId);
147
+ const actor = await resolveActor(container, req);
148
+ return container.notificationCategories.community.get(req.params.communityId, actor);
164
149
  }
165
150
  });
166
151
  r.route({
@@ -169,8 +154,8 @@ async function communitiesRoutes(app, container) {
169
154
  schema: {
170
155
  operationId: "replaceCommunityNotificationCategories",
171
156
  tags: ["Notifications"],
172
- summary: "Replace a community's notification categories (staff)",
173
- description: "Owner or admin only. Stores the whole list for this community; other communities keep theirs. 422 when a key repeats, a type cannot be in a community list, a reason is not allowed for its type, a channel is not configured, `perSpace` is set on a type that cannot occur in a space, `spaceDefaults` appears without `perSpace` or names a channel the category does not offer, or earlier categories already cover everything a category covers.",
157
+ summary: "Replace a community's notification categories",
158
+ description: `Requires \`communities:write\`. On the community. Stores the whole list for this community; other communities keep theirs. 422 when a key repeats, a type cannot be in a community list, a reason is not allowed for its type, a channel is not configured, \`perSpace\` is set on a type that cannot occur in a space, \`spaceDefaults\` appears without \`perSpace\` or names a channel the category does not offer, or earlier categories already cover everything a category covers.`,
174
159
  params: CommunityIdParam,
175
160
  body: ReplaceNotificationCategories,
176
161
  response: {
@@ -179,8 +164,8 @@ async function communitiesRoutes(app, container) {
179
164
  }
180
165
  },
181
166
  handler: async (req) => {
182
- const scope = await resolveConfiguratorScope(container, req);
183
- return container.notificationCategories.community.replace(scope.orgId, req.params.communityId, req.body.categories);
167
+ const actor = await resolveActor(container, req);
168
+ return container.notificationCategories.community.replace(req.params.communityId, req.body.categories, actor);
184
169
  }
185
170
  });
186
171
  r.route({
@@ -189,8 +174,8 @@ async function communitiesRoutes(app, container) {
189
174
  schema: {
190
175
  operationId: "resetCommunityNotificationCategories",
191
176
  tags: ["Notifications"],
192
- summary: "Go back to the default notification categories for a community (staff)",
193
- description: "Owner or admin only. Removes the stored list; the default applies again.",
177
+ summary: "Go back to the default notification categories for a community",
178
+ description: `Requires \`communities:write\`. On the community. Removes the stored list; the default applies again.`,
194
179
  params: CommunityIdParam,
195
180
  response: {
196
181
  204: z.void(),
@@ -198,8 +183,8 @@ async function communitiesRoutes(app, container) {
198
183
  }
199
184
  },
200
185
  handler: async (req, reply) => {
201
- const scope = await resolveConfiguratorScope(container, req);
202
- await container.notificationCategories.community.reset(scope.orgId, req.params.communityId);
186
+ const actor = await resolveActor(container, req);
187
+ await container.notificationCategories.community.reset(req.params.communityId, actor);
203
188
  return reply.code(204).send();
204
189
  }
205
190
  });
@@ -209,8 +194,8 @@ async function communitiesRoutes(app, container) {
209
194
  schema: {
210
195
  operationId: "publishCommunity",
211
196
  tags,
212
- summary: "Publish a community (staff)",
213
- description: "Owner or admin only. Makes the community visible to the people who hold it.",
197
+ summary: "Publish a community",
198
+ description: `Requires \`communities:write\`. Makes the community visible to the people who hold it.`,
214
199
  params: CommunityIdParam,
215
200
  response: {
216
201
  200: Community,
@@ -218,11 +203,8 @@ async function communitiesRoutes(app, container) {
218
203
  }
219
204
  },
220
205
  handler: async (req) => {
221
- const scope = await resolveScope(container, req);
222
- return content.publishCommunity(scope.orgId, req.params.communityId, {
223
- id: scope.orgUserId,
224
- role: scope.role
225
- });
206
+ const actor = await resolveActor(container, req);
207
+ return content.publishCommunity(req.params.communityId, actor);
226
208
  }
227
209
  });
228
210
  r.route({
@@ -231,8 +213,8 @@ async function communitiesRoutes(app, container) {
231
213
  schema: {
232
214
  operationId: "unpublishCommunity",
233
215
  tags,
234
- summary: "Unpublish a community (staff)",
235
- description: "Owner or admin only. Hides the community from members again; nothing in it is deleted.",
216
+ summary: "Unpublish a community",
217
+ description: `Requires \`communities:write\`. Hides the community from members again; nothing in it is deleted.`,
236
218
  params: CommunityIdParam,
237
219
  response: {
238
220
  200: Community,
@@ -240,11 +222,8 @@ async function communitiesRoutes(app, container) {
240
222
  }
241
223
  },
242
224
  handler: async (req) => {
243
- const scope = await resolveScope(container, req);
244
- return content.unpublishCommunity(scope.orgId, req.params.communityId, {
245
- id: scope.orgUserId,
246
- role: scope.role
247
- });
225
+ const actor = await resolveActor(container, req);
226
+ return content.unpublishCommunity(req.params.communityId, actor);
248
227
  }
249
228
  });
250
229
  r.route({
@@ -253,8 +232,8 @@ async function communitiesRoutes(app, container) {
253
232
  schema: {
254
233
  operationId: "getCommunityAnalytics",
255
234
  tags,
256
- summary: "Staff analytics for a community",
257
- description: "Staff only (owner, admin, instructor). Membership, activity and moderation counts for one community.",
235
+ summary: "Analytics for a community",
236
+ description: `Requires \`communities:read\`. Membership, activity and moderation counts for one community.`,
258
237
  params: CommunityIdParam,
259
238
  response: {
260
239
  200: CommunityAnalytics,
@@ -262,10 +241,8 @@ async function communitiesRoutes(app, container) {
262
241
  }
263
242
  },
264
243
  handler: async (req) => {
265
- const scope = await resolveScope(container, req);
266
- const stats = await container.reporting.admin.communityAnalytics(scope.orgId, req.params.communityId);
267
- if (!stats) throw new NotFoundError("Community", req.params.communityId);
268
- return stats;
244
+ const actor = await resolveActor(container, req);
245
+ return container.reporting.admin.communityAnalytics(req.params.communityId, actor);
269
246
  }
270
247
  });
271
248
  r.route({
@@ -274,8 +251,8 @@ async function communitiesRoutes(app, container) {
274
251
  schema: {
275
252
  operationId: "getCommunityMessageQueue",
276
253
  tags,
277
- summary: "Open message flags in a community (staff)",
278
- description: "Staff only (owner, admin, instructor). The messages in the community's chats with open flags, with the flags and the chat each is in. Holds at most the 100 oldest open flags; working the queue brings in the next ones.",
254
+ summary: "Open message flags in a community",
255
+ description: `Requires \`communities:moderate\`. The messages in the community's chats with open flags, with the flags and the chat each is in. Holds at most the 100 oldest open flags; working the queue brings in the next ones.`,
279
256
  params: CommunityIdParam,
280
257
  response: {
281
258
  200: MessageQueue,
@@ -283,11 +260,8 @@ async function communitiesRoutes(app, container) {
283
260
  }
284
261
  },
285
262
  handler: async (req) => {
286
- const scope = await resolveScope(container, req);
287
- return container.chats.messageQueue(scope.orgId, {
288
- id: scope.orgUserId,
289
- role: scope.role
290
- }, req.params.communityId);
263
+ const actor = await resolveActor(container, req);
264
+ return container.chats.messageQueue(req.params.communityId, actor);
291
265
  }
292
266
  });
293
267
  r.route({
@@ -296,8 +270,8 @@ async function communitiesRoutes(app, container) {
296
270
  schema: {
297
271
  operationId: "listCommunitySpaces",
298
272
  tags,
299
- summary: "List the spaces in a community (staff)",
300
- description: "Staff only (owner, admin, instructor). Every space in order, with its topics and links.",
273
+ summary: "List the spaces in a community",
274
+ description: `Requires one of \`communities:read\`, \`communities:moderate\`. Every space in order, with its topics and links.`,
301
275
  params: CommunityIdParam,
302
276
  response: {
303
277
  200: z.array(SpaceDetail),
@@ -305,8 +279,8 @@ async function communitiesRoutes(app, container) {
305
279
  }
306
280
  },
307
281
  handler: async (req) => {
308
- const scope = await resolveScope(container, req);
309
- return content.listSpaces(scope.orgId, req.params.communityId);
282
+ const actor = await resolveActor(container, req);
283
+ return content.listSpaces(req.params.communityId, actor);
310
284
  }
311
285
  });
312
286
  r.route({
@@ -315,8 +289,8 @@ async function communitiesRoutes(app, container) {
315
289
  schema: {
316
290
  operationId: "createSpace",
317
291
  tags,
318
- summary: "Create a space (staff)",
319
- description: "Owner or admin only. The space is added last. An `all_members` space cannot have a capacity (409).",
292
+ summary: "Create a space",
293
+ description: `Requires \`communities:write\`. The space is added last. An \`all_members\` space cannot have a capacity (409).`,
320
294
  params: CommunityIdParam,
321
295
  body: CreateSpace,
322
296
  response: {
@@ -325,11 +299,8 @@ async function communitiesRoutes(app, container) {
325
299
  }
326
300
  },
327
301
  handler: async (req, reply) => {
328
- const scope = await resolveScope(container, req);
329
- const space = await content.createSpace(scope.orgId, req.params.communityId, req.body, {
330
- id: scope.orgUserId,
331
- role: scope.role
332
- });
302
+ const actor = await resolveActor(container, req);
303
+ const space = await content.createSpace(req.params.communityId, req.body, actor);
333
304
  return reply.code(201).send(space);
334
305
  }
335
306
  });
@@ -339,8 +310,8 @@ async function communitiesRoutes(app, container) {
339
310
  schema: {
340
311
  operationId: "reorderSpaces",
341
312
  tags,
342
- summary: "Reorder the spaces in a community (staff)",
343
- description: "Owner or admin only. Send every space id of the community exactly once (409 otherwise).",
313
+ summary: "Reorder the spaces in a community",
314
+ description: `Requires \`communities:write\`. Send every space id of the community exactly once (409 otherwise).`,
344
315
  params: CommunityIdParam,
345
316
  body: OrderedIds,
346
317
  response: {
@@ -349,11 +320,8 @@ async function communitiesRoutes(app, container) {
349
320
  }
350
321
  },
351
322
  handler: async (req) => {
352
- const scope = await resolveScope(container, req);
353
- return content.reorderSpaces(scope.orgId, req.params.communityId, req.body.ids, {
354
- id: scope.orgUserId,
355
- role: scope.role
356
- });
323
+ const actor = await resolveActor(container, req);
324
+ return content.reorderSpaces(req.params.communityId, req.body.ids, actor);
357
325
  }
358
326
  });
359
327
  r.route({
@@ -362,8 +330,8 @@ async function communitiesRoutes(app, container) {
362
330
  schema: {
363
331
  operationId: "getSpace",
364
332
  tags,
365
- summary: "Get a space (staff)",
366
- description: "Staff only (owner, admin, instructor). The space with its topics and links.",
333
+ summary: "Get a space",
334
+ description: `Requires \`communities:read\`. The space with its topics and links.`,
367
335
  params: CommunitySpaceParam,
368
336
  response: {
369
337
  200: SpaceDetail,
@@ -371,10 +339,8 @@ async function communitiesRoutes(app, container) {
371
339
  }
372
340
  },
373
341
  handler: async (req) => {
374
- const scope = await resolveScope(container, req);
375
- const space = await content.getCommunitySpace(scope.orgId, req.params.communityId, req.params.spaceId);
376
- if (!space) throw new NotFoundError("Space", req.params.spaceId);
377
- return space;
342
+ const actor = await resolveActor(container, req);
343
+ return content.getCommunitySpace(req.params.communityId, req.params.spaceId, actor);
378
344
  }
379
345
  });
380
346
  r.route({
@@ -383,8 +349,8 @@ async function communitiesRoutes(app, container) {
383
349
  schema: {
384
350
  operationId: "updateSpace",
385
351
  tags,
386
- summary: "Update a space (staff)",
387
- description: "Owner or admin only. Changes only the fields sent; `settings` merges into the current settings. An `all_members` space cannot have a capacity (409).",
352
+ summary: "Update a space",
353
+ description: `Requires \`communities:write\`. Changes only the fields sent; \`settings\` merges into the current settings. An \`all_members\` space cannot have a capacity (409).`,
388
354
  params: CommunitySpaceParam,
389
355
  body: UpdateSpace,
390
356
  response: {
@@ -393,11 +359,8 @@ async function communitiesRoutes(app, container) {
393
359
  }
394
360
  },
395
361
  handler: async (req) => {
396
- const scope = await resolveScope(container, req);
397
- return content.updateSpace(scope.orgId, req.params.communityId, req.params.spaceId, req.body, {
398
- id: scope.orgUserId,
399
- role: scope.role
400
- });
362
+ const actor = await resolveActor(container, req);
363
+ return content.updateSpace(req.params.communityId, req.params.spaceId, req.body, actor);
401
364
  }
402
365
  });
403
366
  r.route({
@@ -406,8 +369,8 @@ async function communitiesRoutes(app, container) {
406
369
  schema: {
407
370
  operationId: "deleteSpace",
408
371
  tags,
409
- summary: "Delete a space (staff)",
410
- description: "Owner or admin only. The space and everything in it go.",
372
+ summary: "Delete a space",
373
+ description: `Requires \`communities:write\`. The space and everything in it go.`,
411
374
  params: CommunitySpaceParam,
412
375
  response: {
413
376
  204: z.void(),
@@ -415,11 +378,8 @@ async function communitiesRoutes(app, container) {
415
378
  }
416
379
  },
417
380
  handler: async (req, reply) => {
418
- const scope = await resolveScope(container, req);
419
- await content.deleteSpace(scope.orgId, req.params.communityId, req.params.spaceId, {
420
- id: scope.orgUserId,
421
- role: scope.role
422
- });
381
+ const actor = await resolveActor(container, req);
382
+ await content.deleteSpace(req.params.communityId, req.params.spaceId, actor);
423
383
  return reply.code(204).send();
424
384
  }
425
385
  });
@@ -429,8 +389,8 @@ async function communitiesRoutes(app, container) {
429
389
  schema: {
430
390
  operationId: "setSpaceTopics",
431
391
  tags,
432
- summary: "Replace the topics on a space (staff)",
433
- description: "Owner or admin only. The list replaces the current topics: an entry with an `id` keeps and renames that topic, one without adds a topic, and a topic left out is deleted. Names must be unique (409), and a topic that posts use cannot be deleted (409).",
392
+ summary: "Replace the topics on a space",
393
+ description: `Requires \`communities:write\`. The list replaces the current topics: an entry with an \`id\` keeps and renames that topic, one without adds a topic, and a topic left out is deleted. Names must be unique (409), and a topic that posts use cannot be deleted (409).`,
434
394
  params: CommunitySpaceParam,
435
395
  body: SetSpaceTopics,
436
396
  response: {
@@ -439,11 +399,8 @@ async function communitiesRoutes(app, container) {
439
399
  }
440
400
  },
441
401
  handler: async (req) => {
442
- const scope = await resolveScope(container, req);
443
- return content.setSpaceTopics(scope.orgId, req.params.communityId, req.params.spaceId, req.body, {
444
- id: scope.orgUserId,
445
- role: scope.role
446
- });
402
+ const actor = await resolveActor(container, req);
403
+ return content.setSpaceTopics(req.params.communityId, req.params.spaceId, req.body, actor);
447
404
  }
448
405
  });
449
406
  r.route({
@@ -452,8 +409,8 @@ async function communitiesRoutes(app, container) {
452
409
  schema: {
453
410
  operationId: "addSpaceLink",
454
411
  tags,
455
- summary: "Link a course, download, or community into a space (staff)",
456
- description: "Owner or admin only. The link is added last. Linking the same content twice is a 409.",
412
+ summary: "Link a course, download, or community into a space",
413
+ description: `Requires \`communities:write\`. The link is added last. Linking the same content twice is a 409.`,
457
414
  params: CommunitySpaceParam,
458
415
  body: SpaceLinkBody,
459
416
  response: {
@@ -462,11 +419,8 @@ async function communitiesRoutes(app, container) {
462
419
  }
463
420
  },
464
421
  handler: async (req, reply) => {
465
- const scope = await resolveScope(container, req);
466
- const space = await content.addSpaceLink(scope.orgId, req.params.communityId, req.params.spaceId, req.body.contentId, {
467
- id: scope.orgUserId,
468
- role: scope.role
469
- });
422
+ const actor = await resolveActor(container, req);
423
+ const space = await content.addSpaceLink(req.params.communityId, req.params.spaceId, req.body.contentId, actor);
470
424
  return reply.code(201).send(space);
471
425
  }
472
426
  });
@@ -476,8 +430,8 @@ async function communitiesRoutes(app, container) {
476
430
  schema: {
477
431
  operationId: "removeSpaceLink",
478
432
  tags,
479
- summary: "Remove a link from a space (staff)",
480
- description: "Owner or admin only. The linked content itself is untouched.",
433
+ summary: "Remove a link from a space",
434
+ description: `Requires \`communities:write\`. The linked content itself is untouched.`,
481
435
  params: SpaceLinkParam,
482
436
  response: {
483
437
  200: SpaceDetail,
@@ -485,11 +439,8 @@ async function communitiesRoutes(app, container) {
485
439
  }
486
440
  },
487
441
  handler: async (req) => {
488
- const scope = await resolveScope(container, req);
489
- return content.removeSpaceLink(scope.orgId, req.params.communityId, req.params.spaceId, req.params.linkId, {
490
- id: scope.orgUserId,
491
- role: scope.role
492
- });
442
+ const actor = await resolveActor(container, req);
443
+ return content.removeSpaceLink(req.params.communityId, req.params.spaceId, req.params.linkId, actor);
493
444
  }
494
445
  });
495
446
  r.route({
@@ -498,8 +449,8 @@ async function communitiesRoutes(app, container) {
498
449
  schema: {
499
450
  operationId: "reorderSpaceLinks",
500
451
  tags,
501
- summary: "Reorder the links in a space (staff)",
502
- description: "Owner or admin only. Send every link id of the space exactly once (409 otherwise).",
452
+ summary: "Reorder the links in a space",
453
+ description: `Requires \`communities:write\`. Send every link id of the space exactly once (409 otherwise).`,
503
454
  params: CommunitySpaceParam,
504
455
  body: OrderedIds,
505
456
  response: {
@@ -508,11 +459,8 @@ async function communitiesRoutes(app, container) {
508
459
  }
509
460
  },
510
461
  handler: async (req) => {
511
- const scope = await resolveScope(container, req);
512
- return content.reorderSpaceLinks(scope.orgId, req.params.communityId, req.params.spaceId, req.body.ids, {
513
- id: scope.orgUserId,
514
- role: scope.role
515
- });
462
+ const actor = await resolveActor(container, req);
463
+ return content.reorderSpaceLinks(req.params.communityId, req.params.spaceId, req.body.ids, actor);
516
464
  }
517
465
  });
518
466
  }