@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
@@ -1,7 +1,7 @@
1
1
  import { refusals } from "../../schemas/shared.js";
2
- import { ActivityComments, CommentIdParam, CommentReport, CommentView, DiscussionActivityParam, EditComment, PostComment, Reactions, ReportComment, ResolveCommentFlags, SetReaction } from "../../schemas/discussion.js";
2
+ import { resolveActor } from "../../scope.js";
3
+ import { ActivityComments, Comment, CommentIdParam, CommentReport, CommentView, DiscussionActivityParam, EditComment, PostComment, Reactions, ReportComment, ResolveCommentFlags, SetReaction } from "../../schemas/discussion.js";
3
4
  import "../../schemas/index.js";
4
- import { actorOf } from "./actor.js";
5
5
  import { z } from "zod";
6
6
  //#region src/http/routes/member/comments.ts
7
7
  async function memberCommentsRoutes(app, container) {
@@ -16,7 +16,7 @@ async function memberCommentsRoutes(app, container) {
16
16
  operationId: "listActivityComments",
17
17
  tags,
18
18
  summary: "Read an activity's comments",
19
- description: "For a student who can open the course, and staff. Returns the activity's resolved comment settings with the comments; removed comments keep their place without their body.",
19
+ description: "For anyone who can open the course: through a grant, or with `courses:read` on it. Returns the activity's resolved comment settings with the comments; removed comments keep their place without their body.",
20
20
  params: DiscussionActivityParam,
21
21
  response: {
22
22
  200: ActivityComments,
@@ -24,8 +24,8 @@ async function memberCommentsRoutes(app, container) {
24
24
  }
25
25
  },
26
26
  handler: async (req) => {
27
- const orgUser = await actorOf(container, req.orgId, req.userId);
28
- return discussion.activityComments(req.orgId, req.params.activityId, orgUser);
27
+ const actor = await resolveActor(container, req);
28
+ return discussion.activityComments(req.params.activityId, actor);
29
29
  }
30
30
  });
31
31
  r.route({
@@ -36,7 +36,7 @@ async function memberCommentsRoutes(app, container) {
36
36
  operationId: "postComment",
37
37
  tags,
38
38
  summary: "Post a comment or reply on an activity",
39
- description: "For a student who can open the course, and staff, while discussion is on for the activity (403 otherwise). Lands pending when the course requires review and you are not staff. Replies nest one level and need threading on.",
39
+ description: "For anyone who can open the course, while discussion is on for the activity (403 otherwise). Lands pending when the course requires review and you do not hold `courses:moderate` on it. Replies nest one level and need threading on.",
40
40
  params: DiscussionActivityParam,
41
41
  body: PostComment,
42
42
  response: {
@@ -45,13 +45,12 @@ async function memberCommentsRoutes(app, container) {
45
45
  }
46
46
  },
47
47
  handler: async (req, reply) => {
48
- const orgUser = await actorOf(container, req.orgId, req.userId);
49
- const comment = await discussion.postComment(req.orgId, {
50
- actor: orgUser,
48
+ const actor = await resolveActor(container, req);
49
+ const comment = await discussion.postComment({
51
50
  activityId: req.params.activityId,
52
51
  parentId: req.body.parentId,
53
52
  body: req.body.body
54
- });
53
+ }, actor);
55
54
  return reply.code(201).send(comment);
56
55
  }
57
56
  });
@@ -63,7 +62,7 @@ async function memberCommentsRoutes(app, container) {
63
62
  operationId: "editComment",
64
63
  tags,
65
64
  summary: "Revise your own comment",
66
- description: "Author only; not even staff may revise it. Discussion must be open where the comment is, and a removed comment cannot be revised.",
65
+ description: "Author only; not even a moderator may revise it. Discussion must be open where the comment is, and a removed comment cannot be revised.",
67
66
  params: CommentIdParam,
68
67
  body: EditComment,
69
68
  response: {
@@ -72,8 +71,8 @@ async function memberCommentsRoutes(app, container) {
72
71
  }
73
72
  },
74
73
  handler: async (req) => {
75
- const orgUser = await actorOf(container, req.orgId, req.userId);
76
- return discussion.edit(req.orgId, req.params.commentId, orgUser, req.body.body);
74
+ const actor = await resolveActor(container, req);
75
+ return discussion.edit(req.params.commentId, req.body.body, actor);
77
76
  }
78
77
  });
79
78
  r.route({
@@ -84,7 +83,7 @@ async function memberCommentsRoutes(app, container) {
84
83
  operationId: "deleteComment",
85
84
  tags,
86
85
  summary: "Delete your comment, or one you moderate",
87
- description: "For the author, staff on an activity comment, or a moderator on a post comment. The comment is marked removed and keeps its place in the thread. Deleting a removed comment returns it unchanged.",
86
+ description: "The author can delete their own comment; anyone else needs `communities:moderate` on the community for a post comment, or `courses:moderate` on the course for a lesson comment. The comment is marked removed and keeps its place in the thread. Deleting a removed comment returns it unchanged.",
88
87
  params: CommentIdParam,
89
88
  response: {
90
89
  200: CommentView,
@@ -92,8 +91,8 @@ async function memberCommentsRoutes(app, container) {
92
91
  }
93
92
  },
94
93
  handler: async (req) => {
95
- const orgUser = await actorOf(container, req.orgId, req.userId);
96
- return discussion.remove(req.orgId, req.params.commentId, orgUser);
94
+ const actor = await resolveActor(container, req);
95
+ return discussion.remove(req.params.commentId, "any", actor);
97
96
  }
98
97
  });
99
98
  r.route({
@@ -113,8 +112,8 @@ async function memberCommentsRoutes(app, container) {
113
112
  }
114
113
  },
115
114
  handler: async (req) => {
116
- const orgUser = await actorOf(container, req.orgId, req.userId);
117
- return discussion.setReaction(req.orgId, req.params.commentId, orgUser, req.body.emoji);
115
+ const actor = await resolveActor(container, req);
116
+ return discussion.setReaction(req.params.commentId, req.body.emoji, actor);
118
117
  }
119
118
  });
120
119
  r.route({
@@ -133,8 +132,8 @@ async function memberCommentsRoutes(app, container) {
133
132
  }
134
133
  },
135
134
  handler: async (req) => {
136
- const orgUser = await actorOf(container, req.orgId, req.userId);
137
- return discussion.setReaction(req.orgId, req.params.commentId, orgUser, null);
135
+ const actor = await resolveActor(container, req);
136
+ return discussion.setReaction(req.params.commentId, null, actor);
138
137
  }
139
138
  });
140
139
  r.route({
@@ -154,19 +153,19 @@ async function memberCommentsRoutes(app, container) {
154
153
  }
155
154
  },
156
155
  handler: async (req) => {
157
- const orgUser = await actorOf(container, req.orgId, req.userId);
158
- return discussion.reportComment(req.orgId, req.params.commentId, orgUser, req.body.reason);
156
+ const actor = await resolveActor(container, req);
157
+ return discussion.reportComment(req.params.commentId, req.body.reason, actor);
159
158
  }
160
159
  });
161
160
  r.route({
162
161
  method: "POST",
163
- url: "/api/member/comments/:commentId/flags/resolution",
162
+ url: "/api/member/comments/:commentId/flags/resolve",
164
163
  preHandler: app.requireOrgSession,
165
164
  schema: {
166
165
  operationId: "resolveCommentFlags",
167
166
  tags,
168
167
  summary: "Dismiss the open flags on a comment in a space you moderate",
169
- description: "For a moderator of a post comment, or staff for an activity comment. Closes every open flag and leaves the comment up.",
168
+ description: "Needs `courses:moderate` on the course for an activity comment, or `communities:moderate` on the community for a post comment. Closes every open flag and leaves the comment up.",
170
169
  params: CommentIdParam,
171
170
  body: ResolveCommentFlags,
172
171
  response: {
@@ -175,11 +174,51 @@ async function memberCommentsRoutes(app, container) {
175
174
  }
176
175
  },
177
176
  handler: async (req, reply) => {
178
- const orgUser = await actorOf(container, req.orgId, req.userId);
179
- await discussion.resolveReports(req.orgId, req.params.commentId, orgUser);
177
+ const actor = await resolveActor(container, req);
178
+ await discussion.resolveReports(req.params.commentId, "any", actor);
180
179
  return reply.code(204).send();
181
180
  }
182
181
  });
182
+ r.route({
183
+ method: "POST",
184
+ url: "/api/member/comments/:commentId/restore",
185
+ preHandler: app.requireOrgSession,
186
+ schema: {
187
+ operationId: "restoreMemberComment",
188
+ tags,
189
+ summary: "Restore a removed comment where you moderate",
190
+ description: "Needs `courses:moderate` on the course for an activity comment, or `communities:moderate` on the community for a post comment. Publishes the comment again; a comment that is not removed is returned unchanged.",
191
+ params: CommentIdParam,
192
+ response: {
193
+ 200: CommentView,
194
+ ...refusals(403, 404)
195
+ }
196
+ },
197
+ handler: async (req) => {
198
+ const actor = await resolveActor(container, req);
199
+ return discussion.restore(req.params.commentId, "any", actor);
200
+ }
201
+ });
202
+ r.route({
203
+ method: "POST",
204
+ url: "/api/member/comments/:commentId/approve",
205
+ preHandler: app.requireOrgSession,
206
+ schema: {
207
+ operationId: "approveMemberComment",
208
+ tags,
209
+ summary: "Publish a comment awaiting review where you moderate",
210
+ description: "Needs `courses:moderate` on the course for an activity comment, or `communities:moderate` on the community for a post comment. 403 when the comment is not awaiting review.",
211
+ params: CommentIdParam,
212
+ response: {
213
+ 200: Comment,
214
+ ...refusals(403, 404)
215
+ }
216
+ },
217
+ handler: async (req) => {
218
+ const actor = await resolveActor(container, req);
219
+ return discussion.approve(req.params.commentId, actor);
220
+ }
221
+ });
183
222
  }
184
223
  //#endregion
185
224
  export { memberCommentsRoutes };
@@ -1 +1 @@
1
- {"version":3,"file":"comments.js","names":[],"sources":["../../../../src/http/routes/member/comments.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport {\n ActivityComments,\n CommentIdParam,\n CommentReport,\n CommentView,\n DiscussionActivityParam,\n EditComment,\n PostComment,\n Reactions,\n ReportComment,\n ResolveCommentFlags,\n SetReaction,\n refusals,\n} from '../../schemas/index.js';\nimport type { Container } from '../../../app/container.js';\nimport { actorOf } from './actor.js';\n\nexport async function memberCommentsRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const discussion = container.discussion;\n const tags = ['Discussion'];\n\n r.route({\n method: 'GET',\n url: '/api/member/activities/:activityId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listActivityComments',\n tags,\n summary: \"Read an activity's comments\",\n description:\n \"For a student who can open the course, and staff. Returns the activity's resolved comment settings with the comments; removed comments keep their place without their body.\",\n params: DiscussionActivityParam,\n response: { 200: ActivityComments, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n return discussion.activityComments(req.orgId, req.params.activityId, orgUser);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/activities/:activityId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'postComment',\n tags,\n summary: 'Post a comment or reply on an activity',\n description:\n 'For a student who can open the course, and staff, while discussion is on for the activity (403 otherwise). Lands pending when the course requires review and you are not staff. Replies nest one level and need threading on.',\n params: DiscussionActivityParam,\n body: PostComment,\n response: { 201: CommentView, ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n const comment = await discussion.postComment(req.orgId, {\n actor: orgUser,\n activityId: req.params.activityId,\n parentId: req.body.parentId,\n body: req.body.body,\n });\n return reply.code(201).send(comment);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/member/comments/:commentId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'editComment',\n tags,\n summary: 'Revise your own comment',\n description:\n 'Author only; not even staff may revise it. Discussion must be open where the comment is, and a removed comment cannot be revised.',\n params: CommentIdParam,\n body: EditComment,\n response: { 200: CommentView, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n return discussion.edit(req.orgId, req.params.commentId, orgUser, req.body.body);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/comments/:commentId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteComment',\n tags,\n summary: 'Delete your comment, or one you moderate',\n description:\n 'For the author, staff on an activity comment, or a moderator on a post comment. The comment is marked removed and keeps its place in the thread. Deleting a removed comment returns it unchanged.',\n params: CommentIdParam,\n response: { 200: CommentView, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n return discussion.remove(req.orgId, req.params.commentId, orgUser);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/comments/:commentId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setCommentReaction',\n tags,\n summary: 'Set your reaction on a comment',\n description:\n 'For anyone who can read the comment, while reactions are on where it is (403 otherwise). Replaces any reaction you already had and returns the new counts.',\n params: CommentIdParam,\n body: SetReaction,\n response: { 200: Reactions, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n return discussion.setReaction(req.orgId, req.params.commentId, orgUser, req.body.emoji);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/comments/:commentId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'clearCommentReaction',\n tags,\n summary: 'Withdraw your reaction from a comment',\n description: 'For anyone who can read the comment. Returns the new counts.',\n params: CommentIdParam,\n response: { 200: Reactions, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n return discussion.setReaction(req.orgId, req.params.commentId, orgUser, null);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/comments/:commentId/reports',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reportComment',\n tags,\n summary: 'Flag a comment for the moderators',\n description:\n 'For anyone who can read the comment. Flagging it again while your flag is open returns that flag.',\n params: CommentIdParam,\n body: ReportComment,\n response: { 200: CommentReport, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n return discussion.reportComment(req.orgId, req.params.commentId, orgUser, req.body.reason);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/comments/:commentId/flags/resolution',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'resolveCommentFlags',\n tags,\n summary: 'Dismiss the open flags on a comment in a space you moderate',\n description:\n 'For a moderator of a post comment, or staff for an activity comment. Closes every open flag and leaves the comment up.',\n params: CommentIdParam,\n body: ResolveCommentFlags,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n await discussion.resolveReports(req.orgId, req.params.commentId, orgUser);\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":";;;;;;AAoBA,eAAsB,qBACpB,KACA,WACe;CACf,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,aAAa,UAAU;CAC7B,MAAM,OAAO,CAAC,YAAY;CAE1B,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK;IAAkB,GAAG,SAAS,KAAK,GAAG;GAAE;EAC3D;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9D,OAAO,WAAW,iBAAiB,IAAI,OAAO,IAAI,OAAO,YAAY,OAAO;EAC9E;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,MAAM;GACN,UAAU;IAAE,KAAK;IAAa,GAAG,SAAS,KAAK,GAAG;GAAE;EACtD;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,UAAU,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9D,MAAM,UAAU,MAAM,WAAW,YAAY,IAAI,OAAO;IACtD,OAAO;IACP,YAAY,IAAI,OAAO;IACvB,UAAU,IAAI,KAAK;IACnB,MAAM,IAAI,KAAK;GACjB,CAAC;GACD,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,OAAO;EACrC;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,MAAM;GACN,UAAU;IAAE,KAAK;IAAa,GAAG,SAAS,KAAK,GAAG;GAAE;EACtD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9D,OAAO,WAAW,KAAK,IAAI,OAAO,IAAI,OAAO,WAAW,SAAS,IAAI,KAAK,IAAI;EAChF;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK;IAAa,GAAG,SAAS,KAAK,GAAG;GAAE;EACtD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9D,OAAO,WAAW,OAAO,IAAI,OAAO,IAAI,OAAO,WAAW,OAAO;EACnE;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,MAAM;GACN,UAAU;IAAE,KAAK;IAAW,GAAG,SAAS,KAAK,GAAG;GAAE;EACpD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9D,OAAO,WAAW,YAAY,IAAI,OAAO,IAAI,OAAO,WAAW,SAAS,IAAI,KAAK,KAAK;EACxF;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aAAa;GACb,QAAQ;GACR,UAAU;IAAE,KAAK;IAAW,GAAG,SAAS,KAAK,GAAG;GAAE;EACpD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9D,OAAO,WAAW,YAAY,IAAI,OAAO,IAAI,OAAO,WAAW,SAAS,IAAI;EAC9E;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,MAAM;GACN,UAAU;IAAE,KAAK;IAAe,GAAG,SAAS,KAAK,GAAG;GAAE;EACxD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9D,OAAO,WAAW,cAAc,IAAI,OAAO,IAAI,OAAO,WAAW,SAAS,IAAI,KAAK,MAAM;EAC3F;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,MAAM;GACN,UAAU;IAAE,KAAK,EAAE,KAAK;IAAG,GAAG,SAAS,KAAK,GAAG;GAAE;EACnD;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,UAAU,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9D,MAAM,WAAW,eAAe,IAAI,OAAO,IAAI,OAAO,WAAW,OAAO;GACxE,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK;EAC9B;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"comments.js","names":[],"sources":["../../../../src/http/routes/member/comments.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport {\n ActivityComments,\n Comment,\n CommentIdParam,\n CommentReport,\n CommentView,\n DiscussionActivityParam,\n EditComment,\n PostComment,\n Reactions,\n ReportComment,\n ResolveCommentFlags,\n SetReaction,\n refusals,\n} from '../../schemas/index.js';\nimport type { Container } from '../../../app/container.js';\nimport { resolveActor } from '../../scope.js';\n\nexport async function memberCommentsRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const discussion = container.discussion;\n const tags = ['Discussion'];\n\n r.route({\n method: 'GET',\n url: '/api/member/activities/:activityId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listActivityComments',\n tags,\n summary: \"Read an activity's comments\",\n description:\n \"For anyone who can open the course: through a grant, or with `courses:read` on it. Returns the activity's resolved comment settings with the comments; removed comments keep their place without their body.\",\n params: DiscussionActivityParam,\n response: { 200: ActivityComments, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return discussion.activityComments(req.params.activityId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/activities/:activityId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'postComment',\n tags,\n summary: 'Post a comment or reply on an activity',\n description:\n 'For anyone who can open the course, while discussion is on for the activity (403 otherwise). Lands pending when the course requires review and you do not hold `courses:moderate` on it. Replies nest one level and need threading on.',\n params: DiscussionActivityParam,\n body: PostComment,\n response: { 201: CommentView, ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n const comment = await discussion.postComment(\n { activityId: req.params.activityId, parentId: req.body.parentId, body: req.body.body },\n actor,\n );\n return reply.code(201).send(comment);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/member/comments/:commentId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'editComment',\n tags,\n summary: 'Revise your own comment',\n description:\n 'Author only; not even a moderator may revise it. Discussion must be open where the comment is, and a removed comment cannot be revised.',\n params: CommentIdParam,\n body: EditComment,\n response: { 200: CommentView, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return discussion.edit(req.params.commentId, req.body.body, actor);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/comments/:commentId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteComment',\n tags,\n summary: 'Delete your comment, or one you moderate',\n description:\n 'The author can delete their own comment; anyone else needs `communities:moderate` on the community for a post comment, or `courses:moderate` on the course for a lesson comment. The comment is marked removed and keeps its place in the thread. Deleting a removed comment returns it unchanged.',\n params: CommentIdParam,\n response: { 200: CommentView, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return discussion.remove(req.params.commentId, 'any', actor);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/comments/:commentId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setCommentReaction',\n tags,\n summary: 'Set your reaction on a comment',\n description:\n 'For anyone who can read the comment, while reactions are on where it is (403 otherwise). Replaces any reaction you already had and returns the new counts.',\n params: CommentIdParam,\n body: SetReaction,\n response: { 200: Reactions, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return discussion.setReaction(req.params.commentId, req.body.emoji, actor);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/comments/:commentId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'clearCommentReaction',\n tags,\n summary: 'Withdraw your reaction from a comment',\n description: 'For anyone who can read the comment. Returns the new counts.',\n params: CommentIdParam,\n response: { 200: Reactions, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return discussion.setReaction(req.params.commentId, null, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/comments/:commentId/reports',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reportComment',\n tags,\n summary: 'Flag a comment for the moderators',\n description:\n 'For anyone who can read the comment. Flagging it again while your flag is open returns that flag.',\n params: CommentIdParam,\n body: ReportComment,\n response: { 200: CommentReport, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return discussion.reportComment(req.params.commentId, req.body.reason, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/comments/:commentId/flags/resolve',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'resolveCommentFlags',\n tags,\n summary: 'Dismiss the open flags on a comment in a space you moderate',\n description:\n 'Needs `courses:moderate` on the course for an activity comment, or `communities:moderate` on the community for a post comment. Closes every open flag and leaves the comment up.',\n params: CommentIdParam,\n body: ResolveCommentFlags,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n await discussion.resolveReports(req.params.commentId, 'any', actor);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/comments/:commentId/restore',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'restoreMemberComment',\n tags,\n summary: 'Restore a removed comment where you moderate',\n description:\n 'Needs `courses:moderate` on the course for an activity comment, or `communities:moderate` on the community for a post comment. Publishes the comment again; a comment that is not removed is returned unchanged.',\n params: CommentIdParam,\n response: { 200: CommentView, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return discussion.restore(req.params.commentId, 'any', actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/comments/:commentId/approve',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'approveMemberComment',\n tags,\n summary: 'Publish a comment awaiting review where you moderate',\n description:\n 'Needs `courses:moderate` on the course for an activity comment, or `communities:moderate` on the community for a post comment. 403 when the comment is not awaiting review.',\n params: CommentIdParam,\n response: { 200: Comment, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return discussion.approve(req.params.commentId, actor);\n },\n });\n}\n"],"mappings":"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"}
@@ -1,11 +1,10 @@
1
1
  import { PageQuery, refusals } from "../../schemas/shared.js";
2
+ import { resolveActor } from "../../scope.js";
2
3
  import { SetMute } from "../../schemas/discussion.js";
3
4
  import { CommunityHome, CommunityHomeSpace, CommunitySpaceParam, DirectoryPage, DirectoryQuery, SpaceIdParam, SpaceMember, SpaceMemberParam, SpaceMembersPage } from "../../schemas/community.js";
4
5
  import { CommunityParam, OrgUserBody } from "../../schemas/chats.js";
5
6
  import "../../schemas/index.js";
6
- import { actorOf } from "./actor.js";
7
7
  import { z } from "zod";
8
- import { NotFoundError } from "@headless-lms/core/shared/errors";
9
8
  //#region src/http/routes/member/community.ts
10
9
  async function memberCommunityRoutes(app, container) {
11
10
  const r = app.withTypeProvider();
@@ -18,7 +17,7 @@ async function memberCommunityRoutes(app, container) {
18
17
  operationId: "getMemberCommunityHome",
19
18
  tags,
20
19
  summary: "The spaces a member can see in a community",
21
- description: "For a member of the community. Lists its spaces in order. Spaces the viewer can open come back in full; spaces they can see but not open come back locked, with only their name, count, rule and the viewer's standing. A by-invite space the viewer is not invited to or a member of is left out. 404 when the viewer is not a member.",
20
+ description: "For a member of the community, through a grant or `communities:read` on it. Lists its spaces in order. Spaces the viewer can open come back in full; spaces they can see but not open come back locked, with only their name, count, rule and the viewer's standing. A by-invite space the viewer is not invited to or a member of is left out, unless the viewer opens every space. `viewer` carries the viewer's permissions on the community, whether they open every space, and their team flag and role name. 404 when the viewer is not a member.",
22
21
  params: CommunityParam,
23
22
  response: {
24
23
  200: CommunityHome,
@@ -26,10 +25,8 @@ async function memberCommunityRoutes(app, container) {
26
25
  }
27
26
  },
28
27
  handler: async (req) => {
29
- const actor = await actorOf(container, req.orgId, req.userId);
30
- const home = await container.reporting.member.communityHome(req.orgId, actor, req.params.communityId);
31
- if (!home) throw new NotFoundError("Community", req.params.communityId);
32
- return home;
28
+ const actor = await resolveActor(container, req);
29
+ return container.reporting.member.communityHome(req.params.communityId, actor);
33
30
  }
34
31
  });
35
32
  r.route({
@@ -48,10 +45,8 @@ async function memberCommunityRoutes(app, container) {
48
45
  }
49
46
  },
50
47
  handler: async (req) => {
51
- const actor = await actorOf(container, req.orgId, req.userId);
52
- const space = await container.reporting.member.communitySpace(req.orgId, actor, req.params.communityId, req.params.spaceId);
53
- if (!space) throw new NotFoundError("Space", req.params.spaceId);
54
- return space;
48
+ const actor = await resolveActor(container, req);
49
+ return container.reporting.member.communitySpace(req.params.communityId, req.params.spaceId, actor);
55
50
  }
56
51
  });
57
52
  r.route({
@@ -62,7 +57,7 @@ async function memberCommunityRoutes(app, container) {
62
57
  operationId: "getMemberCommunityDirectory",
63
58
  tags,
64
59
  summary: "People in a community",
65
- description: "For a member of the community: everyone who holds it, with their public profile. 404 when the viewer is not a member.",
60
+ description: "For a member of the community: everyone who holds it or holds `communities:read` on it, with their public profile, whether they are team here and under which role name, and whether they open every space. 404 when the viewer is not a member.",
66
61
  params: CommunityParam,
67
62
  querystring: DirectoryQuery,
68
63
  response: {
@@ -71,10 +66,8 @@ async function memberCommunityRoutes(app, container) {
71
66
  }
72
67
  },
73
68
  handler: async (req) => {
74
- const actor = await actorOf(container, req.orgId, req.userId);
75
- const page = await container.reporting.member.directory(req.orgId, actor, req.params.communityId, req.query);
76
- if (!page) throw new NotFoundError("Community", req.params.communityId);
77
- return page;
69
+ const actor = await resolveActor(container, req);
70
+ return container.reporting.member.directory(req.params.communityId, req.query, actor);
78
71
  }
79
72
  });
80
73
  r.route({
@@ -85,7 +78,7 @@ async function memberCommunityRoutes(app, container) {
85
78
  operationId: "listSpaceMembers",
86
79
  tags,
87
80
  summary: "List the members of a space",
88
- description: "For anyone who can open the space. Members see the active members; moderators also see open requests and invitations.",
81
+ description: "For anyone who can open the space. Members see the active members; holders of `communities:moderate` on the community also see open requests and invitations. Who opens every space holds no place, and is left out.",
89
82
  params: CommunitySpaceParam,
90
83
  querystring: PageQuery,
91
84
  response: {
@@ -94,8 +87,8 @@ async function memberCommunityRoutes(app, container) {
94
87
  }
95
88
  },
96
89
  handler: async (req) => {
97
- const actor = await actorOf(container, req.orgId, req.userId);
98
- return container.entitlements.listSpaceMembers(req.orgId, actor, req.params.communityId, req.params.spaceId, req.query);
90
+ const actor = await resolveActor(container, req);
91
+ return container.entitlements.listSpaceMembers(req.params.communityId, req.params.spaceId, req.query, actor);
99
92
  }
100
93
  });
101
94
  r.route({
@@ -106,7 +99,7 @@ async function memberCommunityRoutes(app, container) {
106
99
  operationId: "requestSpaceMembership",
107
100
  tags,
108
101
  summary: "Ask to join a space",
109
- description: "For a member of the community who is not staff. Opens a request on a `by_request` space for a moderator to approve. 409 when the space is not `by_request` or you are already placed in it.",
102
+ description: "For a member of the community who does not open every space. Opens a request on a `by_request` space for a moderator to approve. 409 when the space is not `by_request` or you are already placed in it.",
110
103
  params: CommunitySpaceParam,
111
104
  response: {
112
105
  201: SpaceMember,
@@ -114,8 +107,8 @@ async function memberCommunityRoutes(app, container) {
114
107
  }
115
108
  },
116
109
  handler: async (req, reply) => {
117
- const actor = await actorOf(container, req.orgId, req.userId);
118
- const member = await container.entitlements.requestJoin(req.orgId, actor, req.params.communityId, req.params.spaceId);
110
+ const actor = await resolveActor(container, req);
111
+ const member = await container.entitlements.requestJoin(req.params.communityId, req.params.spaceId, actor);
119
112
  return reply.code(201).send(member);
120
113
  }
121
114
  });
@@ -127,7 +120,7 @@ async function memberCommunityRoutes(app, container) {
127
120
  operationId: "inviteSpaceMember",
128
121
  tags,
129
122
  summary: "Invite a person into a space you moderate",
130
- description: "For a moderator who is a member of the community. Invites another member into a `by_invite` space; they join when they accept. 403 when the person is staff or does not hold the community; 409 when the space is not invite-only or the person is already placed.",
123
+ description: `Requires \`communities:moderate\`. On the community, as its member. Invites another member into a \`by_invite\` space; they join when they accept. 403 when the person opens every space or is not a member of the community; 409 when the space is not invite-only or the person is already placed.`,
131
124
  params: CommunitySpaceParam,
132
125
  body: OrgUserBody,
133
126
  response: {
@@ -136,8 +129,8 @@ async function memberCommunityRoutes(app, container) {
136
129
  }
137
130
  },
138
131
  handler: async (req, reply) => {
139
- const actor = await actorOf(container, req.orgId, req.userId);
140
- const member = await container.entitlements.invite(req.orgId, actor, req.params.communityId, req.params.spaceId, req.body.orgUserId);
132
+ const actor = await resolveActor(container, req);
133
+ const member = await container.entitlements.invite(req.params.communityId, req.params.spaceId, req.body.orgUserId, actor);
141
134
  return reply.code(201).send(member);
142
135
  }
143
136
  });
@@ -149,7 +142,7 @@ async function memberCommunityRoutes(app, container) {
149
142
  operationId: "addSpaceMember",
150
143
  tags,
151
144
  summary: "Add a person to a space you moderate",
152
- description: "For a moderator who is a member of the community. Places another member straight into the space, with no request or invitation. 409 when the space is open to all members, the person is already placed, or the space is full.",
145
+ description: `Requires \`communities:moderate\`. On the community, as its member. Places another member straight into the space, with no request or invitation. 403 when the person opens every space; 409 when the space is open to all members, the person is already placed, or the space is full.`,
153
146
  params: CommunitySpaceParam,
154
147
  body: OrgUserBody,
155
148
  response: {
@@ -158,20 +151,20 @@ async function memberCommunityRoutes(app, container) {
158
151
  }
159
152
  },
160
153
  handler: async (req, reply) => {
161
- const actor = await actorOf(container, req.orgId, req.userId);
162
- const member = await container.entitlements.addSpaceMember(req.orgId, actor, req.params.communityId, req.params.spaceId, req.body.orgUserId);
154
+ const actor = await resolveActor(container, req);
155
+ const member = await container.entitlements.addSpaceMember(req.params.communityId, req.params.spaceId, req.body.orgUserId, actor);
163
156
  return reply.code(201).send(member);
164
157
  }
165
158
  });
166
159
  r.route({
167
160
  method: "POST",
168
- url: "/api/member/communities/:communityId/spaces/:spaceId/acceptance",
161
+ url: "/api/member/communities/:communityId/spaces/:spaceId/accept",
169
162
  preHandler: app.requireOrgSession,
170
163
  schema: {
171
164
  operationId: "acceptSpaceInvite",
172
165
  tags,
173
166
  summary: "Accept your invitation to a space",
174
- description: "For a member of the community who is not staff. Makes you an active member. 409 when you have no open invitation, are already a member, or the space is full.",
167
+ description: "For a member of the community who does not open every space. Makes you an active member. 409 when you have no open invitation, are already a member, or the space is full.",
175
168
  params: CommunitySpaceParam,
176
169
  response: {
177
170
  200: SpaceMember,
@@ -179,19 +172,19 @@ async function memberCommunityRoutes(app, container) {
179
172
  }
180
173
  },
181
174
  handler: async (req) => {
182
- const actor = await actorOf(container, req.orgId, req.userId);
183
- return container.entitlements.accept(req.orgId, actor, req.params.communityId, req.params.spaceId);
175
+ const actor = await resolveActor(container, req);
176
+ return container.entitlements.accept(req.params.communityId, req.params.spaceId, actor);
184
177
  }
185
178
  });
186
179
  r.route({
187
180
  method: "POST",
188
- url: "/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/approval",
181
+ url: "/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/approve",
189
182
  preHandler: app.requireOrgSession,
190
183
  schema: {
191
184
  operationId: "approveSpaceRequest",
192
185
  tags,
193
186
  summary: "Approve a person's request to join a space you moderate",
194
- description: "For a moderator who is a member of the community. Makes the requester an active member. 404 when the person has no open request; 409 when the space is full or they are already a member.",
187
+ description: `Requires \`communities:moderate\`. On the community, as its member. Makes the requester an active member. 404 when the person has no open request; 409 when the space is full or they are already a member.`,
195
188
  params: SpaceMemberParam,
196
189
  response: {
197
190
  200: SpaceMember,
@@ -199,8 +192,8 @@ async function memberCommunityRoutes(app, container) {
199
192
  }
200
193
  },
201
194
  handler: async (req) => {
202
- const actor = await actorOf(container, req.orgId, req.userId);
203
- return container.entitlements.approve(req.orgId, actor, req.params.communityId, req.params.spaceId, req.params.orgUserId);
195
+ const actor = await resolveActor(container, req);
196
+ return container.entitlements.approve(req.params.communityId, req.params.spaceId, req.params.orgUserId, actor);
204
197
  }
205
198
  });
206
199
  r.route({
@@ -211,7 +204,7 @@ async function memberCommunityRoutes(app, container) {
211
204
  operationId: "declineSpaceRequest",
212
205
  tags,
213
206
  summary: "Decline a person's request to join a space you moderate",
214
- description: "For a moderator who is a member of the community. Closes the open request. 404 when the person has no open request; 409 when they are already a member.",
207
+ description: `Requires \`communities:moderate\`. On the community, as its member. Closes the open request. 404 when the person has no open request; 409 when they are already a member.`,
215
208
  params: SpaceMemberParam,
216
209
  response: {
217
210
  200: SpaceMember,
@@ -219,19 +212,19 @@ async function memberCommunityRoutes(app, container) {
219
212
  }
220
213
  },
221
214
  handler: async (req) => {
222
- const actor = await actorOf(container, req.orgId, req.userId);
223
- return container.entitlements.decline(req.orgId, actor, req.params.communityId, req.params.spaceId, req.params.orgUserId);
215
+ const actor = await resolveActor(container, req);
216
+ return container.entitlements.decline(req.params.communityId, req.params.spaceId, req.params.orgUserId, actor);
224
217
  }
225
218
  });
226
219
  r.route({
227
220
  method: "POST",
228
- url: "/api/member/communities/:communityId/spaces/:spaceId/refusal",
221
+ url: "/api/member/communities/:communityId/spaces/:spaceId/refuse",
229
222
  preHandler: app.requireOrgSession,
230
223
  schema: {
231
224
  operationId: "refuseSpaceInvite",
232
225
  tags,
233
226
  summary: "Refuse your invitation to a space",
234
- description: "For a member of the community who is not staff. 409 when you have no open invitation or are already a member.",
227
+ description: "For a member of the community who does not open every space. 409 when you have no open invitation or are already a member.",
235
228
  params: CommunitySpaceParam,
236
229
  response: {
237
230
  200: SpaceMember,
@@ -239,8 +232,8 @@ async function memberCommunityRoutes(app, container) {
239
232
  }
240
233
  },
241
234
  handler: async (req) => {
242
- const actor = await actorOf(container, req.orgId, req.userId);
243
- return container.entitlements.refuse(req.orgId, actor, req.params.communityId, req.params.spaceId);
235
+ const actor = await resolveActor(container, req);
236
+ return container.entitlements.refuse(req.params.communityId, req.params.spaceId, actor);
244
237
  }
245
238
  });
246
239
  r.route({
@@ -251,7 +244,7 @@ async function memberCommunityRoutes(app, container) {
251
244
  operationId: "leaveSpace",
252
245
  tags,
253
246
  summary: "Leave a space",
254
- description: "For a member of the community who is not staff. 409 when you are not a member of the space.",
247
+ description: "For a member of the community who does not open every space. 409 when you are not a member of the space.",
255
248
  params: CommunitySpaceParam,
256
249
  response: {
257
250
  200: SpaceMember,
@@ -259,19 +252,19 @@ async function memberCommunityRoutes(app, container) {
259
252
  }
260
253
  },
261
254
  handler: async (req) => {
262
- const actor = await actorOf(container, req.orgId, req.userId);
263
- return container.entitlements.leave(req.orgId, actor, req.params.communityId, req.params.spaceId);
255
+ const actor = await resolveActor(container, req);
256
+ return container.entitlements.leave(req.params.communityId, req.params.spaceId, actor);
264
257
  }
265
258
  });
266
259
  r.route({
267
260
  method: "POST",
268
- url: "/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/removal",
261
+ url: "/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/remove",
269
262
  preHandler: app.requireOrgSession,
270
263
  schema: {
271
264
  operationId: "removeSpaceMember",
272
265
  tags,
273
266
  summary: "Remove a person from a space you moderate",
274
- description: "For a moderator who is a member of the community. Revokes the placement, whether it is a membership, a request or an invitation. 409 when the space is open to all members or the placement is already revoked.",
267
+ description: `Requires \`communities:moderate\`. On the community, as its member. Revokes the placement, whether it is a membership, a request or an invitation. 409 when the space is open to all members or the placement is already revoked.`,
275
268
  params: SpaceMemberParam,
276
269
  response: {
277
270
  200: SpaceMember,
@@ -279,8 +272,8 @@ async function memberCommunityRoutes(app, container) {
279
272
  }
280
273
  },
281
274
  handler: async (req) => {
282
- const actor = await actorOf(container, req.orgId, req.userId);
283
- return container.entitlements.remove(req.orgId, actor, req.params.communityId, req.params.spaceId, req.params.orgUserId);
275
+ const actor = await resolveActor(container, req);
276
+ return container.entitlements.remove(req.params.communityId, req.params.spaceId, req.params.orgUserId, actor);
284
277
  }
285
278
  });
286
279
  r.route({
@@ -300,8 +293,8 @@ async function memberCommunityRoutes(app, container) {
300
293
  }
301
294
  },
302
295
  handler: async (req, reply) => {
303
- const actor = await actorOf(container, req.orgId, req.userId);
304
- await container.subscriptions.setSpace(req.orgId, actor, req.params.spaceId, req.body);
296
+ const actor = await resolveActor(container, req);
297
+ await container.subscriptions.setSpace(req.params.spaceId, req.body, actor);
305
298
  return reply.code(204).send();
306
299
  }
307
300
  });
@@ -1 +1 @@
1
- {"version":3,"file":"community.js","names":[],"sources":["../../../../src/http/routes/member/community.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport type { Container } from '../../../app/container.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport { actorOf } from './actor.js';\nimport {\n CommunityHome,\n CommunityHomeSpace,\n CommunityParam,\n CommunitySpaceParam,\n DirectoryPage,\n DirectoryQuery,\n OrgUserBody,\n PageQuery,\n SetMute,\n SpaceIdParam,\n SpaceMember,\n SpaceMemberParam,\n SpaceMembersPage,\n refusals,\n} from '../../schemas/index.js';\n\nexport async function memberCommunityRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const tags = ['Communities'];\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/home',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberCommunityHome',\n tags,\n summary: 'The spaces a member can see in a community',\n description:\n \"For a member of the community. Lists its spaces in order. Spaces the viewer can open come back in full; spaces they can see but not open come back locked, with only their name, count, rule and the viewer's standing. A by-invite space the viewer is not invited to or a member of is left out. 404 when the viewer is not a member.\",\n params: CommunityParam,\n response: { 200: CommunityHome, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const home = await container.reporting.member.communityHome(\n req.orgId,\n actor,\n req.params.communityId,\n );\n if (!home) {\n throw new NotFoundError('Community', req.params.communityId);\n }\n return home;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/spaces/:spaceId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberSpace',\n tags,\n summary: 'One space as a member sees it',\n description:\n 'For a member of the community: the space with its topics, links, settings and where the viewer stands. A space the viewer cannot open comes back locked, without its content. 404 when the viewer is not a member, the space is not in the community, or it is a by-invite space the viewer is not invited to or a member of.',\n params: CommunitySpaceParam,\n response: { 200: CommunityHomeSpace, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const space = await container.reporting.member.communitySpace(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n );\n if (!space) {\n throw new NotFoundError('Space', req.params.spaceId);\n }\n return space;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/directory',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberCommunityDirectory',\n tags,\n summary: 'People in a community',\n description:\n 'For a member of the community: everyone who holds it, with their public profile. 404 when the viewer is not a member.',\n params: CommunityParam,\n querystring: DirectoryQuery,\n response: { 200: DirectoryPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const page = await container.reporting.member.directory(\n req.orgId,\n actor,\n req.params.communityId,\n req.query,\n );\n if (!page) {\n throw new NotFoundError('Community', req.params.communityId);\n }\n return page;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listSpaceMembers',\n tags,\n summary: 'List the members of a space',\n description:\n 'For anyone who can open the space. Members see the active members; moderators also see open requests and invitations.',\n params: CommunitySpaceParam,\n querystring: PageQuery,\n response: { 200: SpaceMembersPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.listSpaceMembers(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n req.query,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/requests',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'requestSpaceMembership',\n tags,\n summary: 'Ask to join a space',\n description:\n 'For a member of the community who is not staff. Opens a request on a `by_request` space for a moderator to approve. 409 when the space is not `by_request` or you are already placed in it.',\n params: CommunitySpaceParam,\n response: { 201: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const member = await container.entitlements.requestJoin(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n );\n return reply.code(201).send(member);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/invites',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'inviteSpaceMember',\n tags,\n summary: 'Invite a person into a space you moderate',\n description:\n 'For a moderator who is a member of the community. Invites another member into a `by_invite` space; they join when they accept. 403 when the person is staff or does not hold the community; 409 when the space is not invite-only or the person is already placed.',\n params: CommunitySpaceParam,\n body: OrgUserBody,\n response: { 201: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const member = await container.entitlements.invite(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n req.body.orgUserId,\n );\n return reply.code(201).send(member);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'addSpaceMember',\n tags,\n summary: 'Add a person to a space you moderate',\n description:\n 'For a moderator who is a member of the community. Places another member straight into the space, with no request or invitation. 409 when the space is open to all members, the person is already placed, or the space is full.',\n params: CommunitySpaceParam,\n body: OrgUserBody,\n response: { 201: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const member = await container.entitlements.addSpaceMember(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n req.body.orgUserId,\n );\n return reply.code(201).send(member);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/acceptance',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'acceptSpaceInvite',\n tags,\n summary: 'Accept your invitation to a space',\n description:\n 'For a member of the community who is not staff. Makes you an active member. 409 when you have no open invitation, are already a member, or the space is full.',\n params: CommunitySpaceParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.accept(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/approval',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'approveSpaceRequest',\n tags,\n summary: \"Approve a person's request to join a space you moderate\",\n description:\n 'For a moderator who is a member of the community. Makes the requester an active member. 404 when the person has no open request; 409 when the space is full or they are already a member.',\n params: SpaceMemberParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.approve(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n req.params.orgUserId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/decline',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'declineSpaceRequest',\n tags,\n summary: \"Decline a person's request to join a space you moderate\",\n description:\n 'For a moderator who is a member of the community. Closes the open request. 404 when the person has no open request; 409 when they are already a member.',\n params: SpaceMemberParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.decline(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n req.params.orgUserId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/refusal',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'refuseSpaceInvite',\n tags,\n summary: 'Refuse your invitation to a space',\n description:\n 'For a member of the community who is not staff. 409 when you have no open invitation or are already a member.',\n params: CommunitySpaceParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.refuse(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/departure',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'leaveSpace',\n tags,\n summary: 'Leave a space',\n description:\n 'For a member of the community who is not staff. 409 when you are not a member of the space.',\n params: CommunitySpaceParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.leave(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/removal',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'removeSpaceMember',\n tags,\n summary: 'Remove a person from a space you moderate',\n description:\n 'For a moderator who is a member of the community. Revokes the placement, whether it is a membership, a request or an invitation. 409 when the space is open to all members or the placement is already revoked.',\n params: SpaceMemberParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.remove(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n req.params.orgUserId,\n );\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/spaces/:spaceId/subscription',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setSpaceSubscription',\n tags,\n summary: 'Mute a space, or clear your mute',\n description:\n 'For anyone who can open the space. `muted` stops its activity notifying you, until `mutedUntil` when given; mentions, actions on you and moderation work still notify. `none` clears the mute.',\n params: SpaceIdParam,\n body: SetMute,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n await container.subscriptions.setSpace(req.orgId, actor, req.params.spaceId, req.body);\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"community.js","names":[],"sources":["../../../../src/http/routes/member/community.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport type { Container } from '../../../app/container.js';\nimport { resolveActor } from '../../scope.js';\nimport {\n CommunityHome,\n CommunityHomeSpace,\n CommunityParam,\n CommunitySpaceParam,\n DirectoryPage,\n DirectoryQuery,\n OrgUserBody,\n PageQuery,\n SetMute,\n SpaceIdParam,\n SpaceMember,\n SpaceMemberParam,\n SpaceMembersPage,\n refusals,\n} from '../../schemas/index.js';\n\nexport async function memberCommunityRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const tags = ['Communities'];\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/home',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberCommunityHome',\n tags,\n summary: 'The spaces a member can see in a community',\n description:\n \"For a member of the community, through a grant or `communities:read` on it. Lists its spaces in order. Spaces the viewer can open come back in full; spaces they can see but not open come back locked, with only their name, count, rule and the viewer's standing. A by-invite space the viewer is not invited to or a member of is left out, unless the viewer opens every space. `viewer` carries the viewer's permissions on the community, whether they open every space, and their team flag and role name. 404 when the viewer is not a member.\",\n params: CommunityParam,\n response: { 200: CommunityHome, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.reporting.member.communityHome(req.params.communityId, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/spaces/:spaceId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberSpace',\n tags,\n summary: 'One space as a member sees it',\n description:\n 'For a member of the community: the space with its topics, links, settings and where the viewer stands. A space the viewer cannot open comes back locked, without its content. 404 when the viewer is not a member, the space is not in the community, or it is a by-invite space the viewer is not invited to or a member of.',\n params: CommunitySpaceParam,\n response: { 200: CommunityHomeSpace, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.reporting.member.communitySpace(\n req.params.communityId,\n req.params.spaceId,\n actor,\n );\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/directory',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberCommunityDirectory',\n tags,\n summary: 'People in a community',\n description:\n 'For a member of the community: everyone who holds it or holds `communities:read` on it, with their public profile, whether they are team here and under which role name, and whether they open every space. 404 when the viewer is not a member.',\n params: CommunityParam,\n querystring: DirectoryQuery,\n response: { 200: DirectoryPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.reporting.member.directory(req.params.communityId, req.query, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listSpaceMembers',\n tags,\n summary: 'List the members of a space',\n description:\n 'For anyone who can open the space. Members see the active members; holders of `communities:moderate` on the community also see open requests and invitations. Who opens every space holds no place, and is left out.',\n params: CommunitySpaceParam,\n querystring: PageQuery,\n response: { 200: SpaceMembersPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.entitlements.listSpaceMembers(\n req.params.communityId,\n req.params.spaceId,\n req.query,\n actor,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/requests',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'requestSpaceMembership',\n tags,\n summary: 'Ask to join a space',\n description:\n 'For a member of the community who does not open every space. Opens a request on a `by_request` space for a moderator to approve. 409 when the space is not `by_request` or you are already placed in it.',\n params: CommunitySpaceParam,\n response: { 201: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n const member = await container.entitlements.requestJoin(\n req.params.communityId,\n req.params.spaceId,\n actor,\n );\n return reply.code(201).send(member);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/invites',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'inviteSpaceMember',\n tags,\n summary: 'Invite a person into a space you moderate',\n description: `Requires \\`communities:moderate\\`. On the community, as its member. Invites another member into a \\`by_invite\\` space; they join when they accept. 403 when the person opens every space or is not a member of the community; 409 when the space is not invite-only or the person is already placed.`,\n params: CommunitySpaceParam,\n body: OrgUserBody,\n response: { 201: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n const member = await container.entitlements.invite(\n req.params.communityId,\n req.params.spaceId,\n req.body.orgUserId,\n actor,\n );\n return reply.code(201).send(member);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'addSpaceMember',\n tags,\n summary: 'Add a person to a space you moderate',\n description: `Requires \\`communities:moderate\\`. On the community, as its member. Places another member straight into the space, with no request or invitation. 403 when the person opens every space; 409 when the space is open to all members, the person is already placed, or the space is full.`,\n params: CommunitySpaceParam,\n body: OrgUserBody,\n response: { 201: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n const member = await container.entitlements.addSpaceMember(\n req.params.communityId,\n req.params.spaceId,\n req.body.orgUserId,\n actor,\n );\n return reply.code(201).send(member);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/accept',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'acceptSpaceInvite',\n tags,\n summary: 'Accept your invitation to a space',\n description:\n 'For a member of the community who does not open every space. Makes you an active member. 409 when you have no open invitation, are already a member, or the space is full.',\n params: CommunitySpaceParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.entitlements.accept(req.params.communityId, req.params.spaceId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/approve',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'approveSpaceRequest',\n tags,\n summary: \"Approve a person's request to join a space you moderate\",\n description: `Requires \\`communities:moderate\\`. On the community, as its member. Makes the requester an active member. 404 when the person has no open request; 409 when the space is full or they are already a member.`,\n params: SpaceMemberParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.entitlements.approve(\n req.params.communityId,\n req.params.spaceId,\n req.params.orgUserId,\n actor,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/decline',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'declineSpaceRequest',\n tags,\n summary: \"Decline a person's request to join a space you moderate\",\n description: `Requires \\`communities:moderate\\`. On the community, as its member. Closes the open request. 404 when the person has no open request; 409 when they are already a member.`,\n params: SpaceMemberParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.entitlements.decline(\n req.params.communityId,\n req.params.spaceId,\n req.params.orgUserId,\n actor,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/refuse',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'refuseSpaceInvite',\n tags,\n summary: 'Refuse your invitation to a space',\n description:\n 'For a member of the community who does not open every space. 409 when you have no open invitation or are already a member.',\n params: CommunitySpaceParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.entitlements.refuse(req.params.communityId, req.params.spaceId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/departure',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'leaveSpace',\n tags,\n summary: 'Leave a space',\n description:\n 'For a member of the community who does not open every space. 409 when you are not a member of the space.',\n params: CommunitySpaceParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.entitlements.leave(req.params.communityId, req.params.spaceId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/remove',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'removeSpaceMember',\n tags,\n summary: 'Remove a person from a space you moderate',\n description: `Requires \\`communities:moderate\\`. On the community, as its member. Revokes the placement, whether it is a membership, a request or an invitation. 409 when the space is open to all members or the placement is already revoked.`,\n params: SpaceMemberParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.entitlements.remove(\n req.params.communityId,\n req.params.spaceId,\n req.params.orgUserId,\n actor,\n );\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/spaces/:spaceId/subscription',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setSpaceSubscription',\n tags,\n summary: 'Mute a space, or clear your mute',\n description:\n 'For anyone who can open the space. `muted` stops its activity notifying you, until `mutedUntil` when given; mentions, actions on you and moderation work still notify. `none` clears the mute.',\n params: SpaceIdParam,\n body: SetMute,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n await container.subscriptions.setSpace(req.params.spaceId, req.body, actor);\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":"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"}