@headless-lms/server 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/README.md +14 -0
  2. package/dist/app/community-mail.d.ts +10 -10
  3. package/dist/app/community-mail.js +72 -28
  4. package/dist/app/community-mail.js.map +1 -1
  5. package/dist/app/container.d.ts +48 -10
  6. package/dist/app/container.js +268 -48
  7. package/dist/app/container.js.map +1 -1
  8. package/dist/app/notification-channels.d.ts +47 -0
  9. package/dist/app/notification-channels.js +175 -0
  10. package/dist/app/notification-channels.js.map +1 -0
  11. package/dist/app/notification-reads.d.ts +23 -0
  12. package/dist/app/notification-reads.js +74 -0
  13. package/dist/app/notification-reads.js.map +1 -0
  14. package/dist/app/platform.d.ts +14 -0
  15. package/dist/app/platform.js +14 -0
  16. package/dist/app/platform.js.map +1 -0
  17. package/dist/http/config.d.ts +4 -0
  18. package/dist/http/errors.d.ts +1 -0
  19. package/dist/http/errors.js +24 -4
  20. package/dist/http/errors.js.map +1 -1
  21. package/dist/http/fastify.d.ts +8 -0
  22. package/dist/http/mcp/catalogue.d.ts +11 -9
  23. package/dist/http/mcp/catalogue.js +15 -21
  24. package/dist/http/mcp/catalogue.js.map +1 -1
  25. package/dist/http/mcp/challenge.js +3 -3
  26. package/dist/http/mcp/challenge.js.map +1 -1
  27. package/dist/http/mcp/route.js +7 -7
  28. package/dist/http/mcp/route.js.map +1 -1
  29. package/dist/http/mcp/test-support.d.ts +6 -5
  30. package/dist/http/mcp/test-support.js +32 -11
  31. package/dist/http/mcp/test-support.js.map +1 -1
  32. package/dist/http/mcp/tool.d.ts +3 -9
  33. package/dist/http/mcp/tool.js +14 -6
  34. package/dist/http/mcp/tool.js.map +1 -1
  35. package/dist/http/mcp/tools/automations.js +10 -20
  36. package/dist/http/mcp/tools/automations.js.map +1 -1
  37. package/dist/http/mcp/tools/catalog.js +25 -42
  38. package/dist/http/mcp/tools/catalog.js.map +1 -1
  39. package/dist/http/mcp/tools/comments.js +9 -19
  40. package/dist/http/mcp/tools/comments.js.map +1 -1
  41. package/dist/http/mcp/tools/communities.js +16 -31
  42. package/dist/http/mcp/tools/communities.js.map +1 -1
  43. package/dist/http/mcp/tools/community-members.js +9 -16
  44. package/dist/http/mcp/tools/community-members.js.map +1 -1
  45. package/dist/http/mcp/tools/community-moderation.js +13 -25
  46. package/dist/http/mcp/tools/community-moderation.js.map +1 -1
  47. package/dist/http/mcp/tools/courses.js +11 -21
  48. package/dist/http/mcp/tools/courses.js.map +1 -1
  49. package/dist/http/mcp/tools/events.d.ts +6 -0
  50. package/dist/http/mcp/tools/events.js +170 -0
  51. package/dist/http/mcp/tools/events.js.map +1 -0
  52. package/dist/http/mcp/tools/index.d.ts +5 -1
  53. package/dist/http/mcp/tools/index.js +45 -10
  54. package/dist/http/mcp/tools/index.js.map +1 -1
  55. package/dist/http/mcp/tools/organization.d.ts +6 -0
  56. package/dist/http/mcp/tools/organization.js +214 -0
  57. package/dist/http/mcp/tools/organization.js.map +1 -0
  58. package/dist/http/mcp/tools/people.js +30 -49
  59. package/dist/http/mcp/tools/people.js.map +1 -1
  60. package/dist/http/plugins/auth.js +37 -8
  61. package/dist/http/plugins/auth.js.map +1 -1
  62. package/dist/http/plugins/error-handler.d.ts +5 -1
  63. package/dist/http/plugins/error-handler.js +13 -3
  64. package/dist/http/plugins/error-handler.js.map +1 -1
  65. package/dist/http/plugins/openapi.js +18 -2
  66. package/dist/http/plugins/openapi.js.map +1 -1
  67. package/dist/http/plugins/platform.d.ts +7 -0
  68. package/dist/http/plugins/platform.js +13 -0
  69. package/dist/http/plugins/platform.js.map +1 -0
  70. package/dist/http/routes/access.js +54 -36
  71. package/dist/http/routes/access.js.map +1 -1
  72. package/dist/http/routes/activities.js +45 -45
  73. package/dist/http/routes/activities.js.map +1 -1
  74. package/dist/http/routes/assets.js +30 -65
  75. package/dist/http/routes/assets.js.map +1 -1
  76. package/dist/http/routes/automations.js +37 -42
  77. package/dist/http/routes/automations.js.map +1 -1
  78. package/dist/http/routes/bundles.js +33 -36
  79. package/dist/http/routes/bundles.js.map +1 -1
  80. package/dist/http/routes/communities.js +144 -136
  81. package/dist/http/routes/communities.js.map +1 -1
  82. package/dist/http/routes/community-moderation.js +83 -137
  83. package/dist/http/routes/community-moderation.js.map +1 -1
  84. package/dist/http/routes/course-analytics.js +32 -15
  85. package/dist/http/routes/course-analytics.js.map +1 -1
  86. package/dist/http/routes/courses.js +25 -32
  87. package/dist/http/routes/courses.js.map +1 -1
  88. package/dist/http/routes/dashboard.js +9 -9
  89. package/dist/http/routes/dashboard.js.map +1 -1
  90. package/dist/http/routes/discussion.js +21 -39
  91. package/dist/http/routes/discussion.js.map +1 -1
  92. package/dist/http/routes/downloads.js +41 -47
  93. package/dist/http/routes/downloads.js.map +1 -1
  94. package/dist/http/routes/entitlements.js +19 -23
  95. package/dist/http/routes/entitlements.js.map +1 -1
  96. package/dist/http/routes/events.d.ts +7 -0
  97. package/dist/http/routes/events.js +333 -0
  98. package/dist/http/routes/events.js.map +1 -0
  99. package/dist/http/routes/integrations.js +40 -73
  100. package/dist/http/routes/integrations.js.map +1 -1
  101. package/dist/http/routes/member/assets.js +10 -13
  102. package/dist/http/routes/member/assets.js.map +1 -1
  103. package/dist/http/routes/member/chats.js +177 -75
  104. package/dist/http/routes/member/chats.js.map +1 -1
  105. package/dist/http/routes/member/comments.js +65 -26
  106. package/dist/http/routes/member/comments.js.map +1 -1
  107. package/dist/http/routes/member/community.js +68 -51
  108. package/dist/http/routes/member/community.js.map +1 -1
  109. package/dist/http/routes/member/courses.js +12 -19
  110. package/dist/http/routes/member/courses.js.map +1 -1
  111. package/dist/http/routes/member/devices.d.ts +7 -0
  112. package/dist/http/routes/member/devices.js +97 -0
  113. package/dist/http/routes/member/devices.js.map +1 -0
  114. package/dist/http/routes/member/downloads.js +6 -9
  115. package/dist/http/routes/member/downloads.js.map +1 -1
  116. package/dist/http/routes/member/events.d.ts +7 -0
  117. package/dist/http/routes/member/events.js +386 -0
  118. package/dist/http/routes/member/events.js.map +1 -0
  119. package/dist/http/routes/member/files.js +71 -46
  120. package/dist/http/routes/member/files.js.map +1 -1
  121. package/dist/http/routes/member/index.js +6 -0
  122. package/dist/http/routes/member/index.js.map +1 -1
  123. package/dist/http/routes/member/notification-settings.d.ts +7 -0
  124. package/dist/http/routes/member/notification-settings.js +100 -0
  125. package/dist/http/routes/member/notification-settings.js.map +1 -0
  126. package/dist/http/routes/member/notifications.js +69 -45
  127. package/dist/http/routes/member/notifications.js.map +1 -1
  128. package/dist/http/routes/member/org.js +5 -15
  129. package/dist/http/routes/member/org.js.map +1 -1
  130. package/dist/http/routes/member/posts.js +94 -95
  131. package/dist/http/routes/member/posts.js.map +1 -1
  132. package/dist/http/routes/member/profile.js +15 -6
  133. package/dist/http/routes/member/profile.js.map +1 -1
  134. package/dist/http/routes/member/progress.js +7 -11
  135. package/dist/http/routes/member/progress.js.map +1 -1
  136. package/dist/http/routes/organizations.js +415 -110
  137. package/dist/http/routes/organizations.js.map +1 -1
  138. package/dist/http/routes/platform.d.ts +7 -0
  139. package/dist/http/routes/platform.js +61 -0
  140. package/dist/http/routes/platform.js.map +1 -0
  141. package/dist/http/routes/students.js +12 -75
  142. package/dist/http/routes/students.js.map +1 -1
  143. package/dist/http/routes/webhooks.js +30 -39
  144. package/dist/http/routes/webhooks.js.map +1 -1
  145. package/dist/http/routes.js +9 -0
  146. package/dist/http/routes.js.map +1 -1
  147. package/dist/http/schemas/access.d.ts +346 -64
  148. package/dist/http/schemas/access.js +39 -12
  149. package/dist/http/schemas/access.js.map +1 -1
  150. package/dist/http/schemas/assets.d.ts +13 -8
  151. package/dist/http/schemas/assets.js +6 -5
  152. package/dist/http/schemas/assets.js.map +1 -1
  153. package/dist/http/schemas/automations.d.ts +4 -8
  154. package/dist/http/schemas/chats.d.ts +238 -36
  155. package/dist/http/schemas/chats.js +14 -7
  156. package/dist/http/schemas/chats.js.map +1 -1
  157. package/dist/http/schemas/community.d.ts +140 -33
  158. package/dist/http/schemas/community.js +25 -9
  159. package/dist/http/schemas/community.js.map +1 -1
  160. package/dist/http/schemas/course-analytics.d.ts +51 -1
  161. package/dist/http/schemas/course-analytics.js +19 -1
  162. package/dist/http/schemas/course-analytics.js.map +1 -1
  163. package/dist/http/schemas/dashboard.d.ts +1 -0
  164. package/dist/http/schemas/dashboard.js +2 -1
  165. package/dist/http/schemas/dashboard.js.map +1 -1
  166. package/dist/http/schemas/discussion.d.ts +45 -73
  167. package/dist/http/schemas/discussion.js +16 -4
  168. package/dist/http/schemas/discussion.js.map +1 -1
  169. package/dist/http/schemas/entitlements.d.ts +9 -12
  170. package/dist/http/schemas/entitlements.js +11 -5
  171. package/dist/http/schemas/entitlements.js.map +1 -1
  172. package/dist/http/schemas/events.d.ts +527 -0
  173. package/dist/http/schemas/events.js +64 -0
  174. package/dist/http/schemas/events.js.map +1 -0
  175. package/dist/http/schemas/files.d.ts +15 -5
  176. package/dist/http/schemas/files.js +10 -2
  177. package/dist/http/schemas/files.js.map +1 -1
  178. package/dist/http/schemas/index.d.ts +14 -12
  179. package/dist/http/schemas/index.js +14 -12
  180. package/dist/http/schemas/invites.d.ts +3 -47
  181. package/dist/http/schemas/invites.js +4 -25
  182. package/dist/http/schemas/invites.js.map +1 -1
  183. package/dist/http/schemas/member.d.ts +94 -7
  184. package/dist/http/schemas/member.js +7 -3
  185. package/dist/http/schemas/member.js.map +1 -1
  186. package/dist/http/schemas/members.d.ts +329 -65
  187. package/dist/http/schemas/members.js +67 -25
  188. package/dist/http/schemas/members.js.map +1 -1
  189. package/dist/http/schemas/notifications.d.ts +502 -175
  190. package/dist/http/schemas/notifications.js +148 -28
  191. package/dist/http/schemas/notifications.js.map +1 -1
  192. package/dist/http/schemas/platform.d.ts +37 -0
  193. package/dist/http/schemas/platform.js +19 -0
  194. package/dist/http/schemas/platform.js.map +1 -0
  195. package/dist/http/schemas/posts.d.ts +92 -38
  196. package/dist/http/schemas/posts.js +4 -4
  197. package/dist/http/schemas/posts.js.map +1 -1
  198. package/dist/http/schemas/profile.d.ts +1 -1
  199. package/dist/http/schemas/shared.d.ts +10 -13
  200. package/dist/http/schemas/shared.js +3 -11
  201. package/dist/http/schemas/shared.js.map +1 -1
  202. package/dist/http/schemas/students.d.ts +22 -0
  203. package/dist/http/schemas/students.js +3 -1
  204. package/dist/http/schemas/students.js.map +1 -1
  205. package/dist/http/scope.d.ts +9 -12
  206. package/dist/http/scope.js +30 -35
  207. package/dist/http/scope.js.map +1 -1
  208. package/dist/http/server.js +9 -0
  209. package/dist/http/server.js.map +1 -1
  210. package/dist/http/shutdown.d.ts +11 -0
  211. package/dist/http/shutdown.js +40 -0
  212. package/dist/http/shutdown.js.map +1 -0
  213. package/dist/http/sse.d.ts +19 -0
  214. package/dist/http/sse.js +104 -0
  215. package/dist/http/sse.js.map +1 -0
  216. package/dist/http/test-caller.d.ts +13 -0
  217. package/dist/http/test-caller.js +14 -0
  218. package/dist/http/test-caller.js.map +1 -0
  219. package/dist/http/well-known.js +2 -2
  220. package/dist/http/well-known.js.map +1 -1
  221. package/dist/index.d.ts +5 -2
  222. package/dist/index.js +4 -2
  223. package/dist/index.js.map +1 -1
  224. package/dist/packages/server/package.js +1 -1
  225. package/package.json +7 -6
  226. package/dist/http/routes/member/actor.d.ts +0 -14
  227. package/dist/http/routes/member/actor.js +0 -11
  228. package/dist/http/routes/member/actor.js.map +0 -1
@@ -1,10 +1,10 @@
1
1
  import { PageQuery, refusals } from "../../schemas/shared.js";
2
- import { CommentView, PostComment, Reactions, SetReaction } from "../../schemas/discussion.js";
2
+ import { resolveActor } from "../../scope.js";
3
+ import { CommentView, PostComment, Reactions, SetPostSubscription, SetReaction } from "../../schemas/discussion.js";
3
4
  import { CommunitySpaceParam } from "../../schemas/community.js";
4
5
  import { CreatePost, FeedQuery, FlagReason, PostCommentsPage, PostFeedPage, PostFlag, PostIdParam, PostView, ResolvePostFlags, SchedulePost, SetPostReactions, SpaceQueue, UpdatePost } from "../../schemas/posts.js";
5
6
  import { CommunityParam } from "../../schemas/chats.js";
6
7
  import "../../schemas/index.js";
7
- import { actorOf } from "./actor.js";
8
8
  import { z } from "zod";
9
9
  //#region src/http/routes/member/posts.ts
10
10
  async function memberPostRoutes(app, container) {
@@ -28,8 +28,8 @@ async function memberPostRoutes(app, container) {
28
28
  }
29
29
  },
30
30
  handler: async (req) => {
31
- const actor = await actorOf(container, req.orgId, req.userId);
32
- return posts.memberFeed(req.orgId, actor, req.params.communityId, req.query);
31
+ const actor = await resolveActor(container, req);
32
+ return posts.memberFeed(req.params.communityId, req.query, actor);
33
33
  }
34
34
  });
35
35
  r.route({
@@ -49,8 +49,8 @@ async function memberPostRoutes(app, container) {
49
49
  }
50
50
  },
51
51
  handler: async (req) => {
52
- const actor = await actorOf(container, req.orgId, req.userId);
53
- return posts.listOwn(req.orgId, actor, req.params.communityId, req.query);
52
+ const actor = await resolveActor(container, req);
53
+ return posts.listOwn(req.params.communityId, req.query, actor);
54
54
  }
55
55
  });
56
56
  r.route({
@@ -70,8 +70,8 @@ async function memberPostRoutes(app, container) {
70
70
  }
71
71
  },
72
72
  handler: async (req) => {
73
- const actor = await actorOf(container, req.orgId, req.userId);
74
- return posts.listBookmarked(req.orgId, actor, req.params.communityId, req.query);
73
+ const actor = await resolveActor(container, req);
74
+ return posts.listBookmarked(req.params.communityId, req.query, actor);
75
75
  }
76
76
  });
77
77
  r.route({
@@ -91,8 +91,8 @@ async function memberPostRoutes(app, container) {
91
91
  }
92
92
  },
93
93
  handler: async (req) => {
94
- const actor = await actorOf(container, req.orgId, req.userId);
95
- return posts.spaceFeed(req.orgId, actor, req.params.communityId, req.params.spaceId, req.query);
94
+ const actor = await resolveActor(container, req);
95
+ return posts.spaceFeed(req.params.communityId, req.params.spaceId, req.query, actor);
96
96
  }
97
97
  });
98
98
  r.route({
@@ -111,8 +111,8 @@ async function memberPostRoutes(app, container) {
111
111
  }
112
112
  },
113
113
  handler: async (req) => {
114
- const actor = await actorOf(container, req.orgId, req.userId);
115
- return posts.moderationQueue(req.orgId, actor, req.params.communityId, req.params.spaceId);
114
+ const actor = await resolveActor(container, req);
115
+ return posts.moderationQueue(req.params.communityId, req.params.spaceId, actor);
116
116
  }
117
117
  });
118
118
  r.route({
@@ -123,21 +123,21 @@ async function memberPostRoutes(app, container) {
123
123
  operationId: "createPost",
124
124
  tags,
125
125
  summary: "Create a post",
126
- description: "For anyone who can open the space. `mode` decides the status: `draft`, `scheduled`, or on `submit` `published`, or `pending` when the space requires review and you are not staff. 409 when the space has topics and none is given, or a schedule is missing or in the past.",
126
+ description: "For anyone who can open the space. `mode` decides the status: `draft`, `scheduled`, or on `submit` `published`, or `pending` when the space requires review and you do not hold `communities:moderate` on the community. 409 when the space has topics and none is given. 400 when a schedule is missing or in the past. 503 when a scheduled post could not be timed; the post is then kept as a draft, and `details.postId` names it, so open that draft instead of creating the post again.",
127
127
  params: CommunitySpaceParam,
128
128
  body: CreatePost,
129
129
  response: {
130
130
  201: PostView,
131
- ...refusals(403, 404, 409)
131
+ ...refusals(400, 403, 404, 409, 503)
132
132
  }
133
133
  },
134
134
  handler: async (req, reply) => {
135
- const actor = await actorOf(container, req.orgId, req.userId);
136
- const post = await posts.create(req.orgId, actor, {
135
+ const actor = await resolveActor(container, req);
136
+ const post = await posts.create({
137
137
  ...req.body,
138
138
  communityId: req.params.communityId,
139
139
  spaceId: req.params.spaceId
140
- });
140
+ }, actor);
141
141
  req.log.info({
142
142
  postId: post.id,
143
143
  status: post.status
@@ -161,8 +161,8 @@ async function memberPostRoutes(app, container) {
161
161
  }
162
162
  },
163
163
  handler: async (req) => {
164
- const actor = await actorOf(container, req.orgId, req.userId);
165
- return posts.getPost(req.orgId, actor, req.params.postId);
164
+ const actor = await resolveActor(container, req);
165
+ return posts.getPost(req.params.postId, actor);
166
166
  }
167
167
  });
168
168
  r.route({
@@ -182,19 +182,19 @@ async function memberPostRoutes(app, container) {
182
182
  }
183
183
  },
184
184
  handler: async (req) => {
185
- const actor = await actorOf(container, req.orgId, req.userId);
186
- return posts.update(req.orgId, req.params.postId, actor, req.body);
185
+ const actor = await resolveActor(container, req);
186
+ return posts.update(req.params.postId, req.body, actor);
187
187
  }
188
188
  });
189
189
  r.route({
190
190
  method: "POST",
191
- url: "/api/member/posts/:postId/submission",
191
+ url: "/api/member/posts/:postId/submit",
192
192
  preHandler: app.requireOrgSession,
193
193
  schema: {
194
194
  operationId: "submitPost",
195
195
  tags,
196
196
  summary: "Submit your draft or scheduled post",
197
- description: "Author only. Publishes it now, or holds it for review when the space requires review and you are not staff. 409 when the post is not a draft or scheduled.",
197
+ description: "Author only. Publishes it now, or holds it for review when the space requires review and you do not hold `communities:moderate` on the community. A scheduled post is no longer published at its time. 409 when the post is not a draft or scheduled.",
198
198
  params: PostIdParam,
199
199
  response: {
200
200
  200: PostView,
@@ -202,8 +202,8 @@ async function memberPostRoutes(app, container) {
202
202
  }
203
203
  },
204
204
  handler: async (req) => {
205
- const actor = await actorOf(container, req.orgId, req.userId);
206
- return posts.submit(req.orgId, req.params.postId, actor);
205
+ const actor = await resolveActor(container, req);
206
+ return posts.submit(req.params.postId, actor);
207
207
  }
208
208
  });
209
209
  r.route({
@@ -214,22 +214,42 @@ async function memberPostRoutes(app, container) {
214
214
  operationId: "schedulePost",
215
215
  tags,
216
216
  summary: "Schedule your post",
217
- description: "Author only. The post is submitted at `scheduledAt`. 409 when it is already submitted or removed, or the time is not in the future.",
217
+ description: "Author only. Schedules a draft, or moves a scheduled post to a new time; the post is submitted at `scheduledAt`. 409 when it is already submitted or removed, or the time is not in the future. 503 when the time could not be set; a draft stays a draft and a moved post keeps its previous time, and `details.postId` names the post.",
218
218
  params: PostIdParam,
219
219
  body: SchedulePost,
220
+ response: {
221
+ 200: PostView,
222
+ ...refusals(403, 404, 409, 503)
223
+ }
224
+ },
225
+ handler: async (req) => {
226
+ const actor = await resolveActor(container, req);
227
+ return posts.schedule(req.params.postId, req.body.scheduledAt, actor);
228
+ }
229
+ });
230
+ r.route({
231
+ method: "POST",
232
+ url: "/api/member/posts/:postId/unschedule",
233
+ preHandler: app.requireOrgSession,
234
+ schema: {
235
+ operationId: "unschedulePost",
236
+ tags,
237
+ summary: "Unschedule your post",
238
+ description: "Author only. The scheduled post becomes a draft and is not submitted at its time. 409 when the post is not scheduled.",
239
+ params: PostIdParam,
220
240
  response: {
221
241
  200: PostView,
222
242
  ...refusals(403, 404, 409)
223
243
  }
224
244
  },
225
245
  handler: async (req) => {
226
- const actor = await actorOf(container, req.orgId, req.userId);
227
- return posts.schedule(req.orgId, req.params.postId, actor, req.body.scheduledAt);
246
+ const actor = await resolveActor(container, req);
247
+ return posts.unschedule(req.params.postId, actor);
228
248
  }
229
249
  });
230
250
  r.route({
231
251
  method: "POST",
232
- url: "/api/member/posts/:postId/approval",
252
+ url: "/api/member/posts/:postId/approve",
233
253
  preHandler: app.requireOrgSession,
234
254
  schema: {
235
255
  operationId: "approvePost",
@@ -243,19 +263,19 @@ async function memberPostRoutes(app, container) {
243
263
  }
244
264
  },
245
265
  handler: async (req) => {
246
- const actor = await actorOf(container, req.orgId, req.userId);
247
- return posts.approve(req.orgId, req.params.postId, actor);
266
+ const actor = await resolveActor(container, req);
267
+ return posts.approve(req.params.postId, null, actor);
248
268
  }
249
269
  });
250
270
  r.route({
251
271
  method: "POST",
252
- url: "/api/member/posts/:postId/removal",
272
+ url: "/api/member/posts/:postId/remove",
253
273
  preHandler: app.requireOrgSession,
254
274
  schema: {
255
275
  operationId: "removePost",
256
276
  tags,
257
277
  summary: "Remove your post, or one in a space you moderate",
258
- description: "For the author or a moderator. Takes a published or pending post down; a moderator removing someone else's post resolves its open flags as `removed`. 409 when it is already removed or was never submitted.",
278
+ description: "The author can remove their own post; anyone else needs `communities:moderate` on the community. Takes a published or pending post down; removing someone else's post resolves its open flags as `removed`. 409 when it is already removed or was never submitted.",
259
279
  params: PostIdParam,
260
280
  response: {
261
281
  200: PostView,
@@ -263,13 +283,13 @@ async function memberPostRoutes(app, container) {
263
283
  }
264
284
  },
265
285
  handler: async (req) => {
266
- const actor = await actorOf(container, req.orgId, req.userId);
267
- return posts.remove(req.orgId, req.params.postId, actor);
286
+ const actor = await resolveActor(container, req);
287
+ return posts.remove(req.params.postId, null, actor);
268
288
  }
269
289
  });
270
290
  r.route({
271
291
  method: "POST",
272
- url: "/api/member/posts/:postId/restoration",
292
+ url: "/api/member/posts/:postId/restore",
273
293
  preHandler: app.requireOrgSession,
274
294
  schema: {
275
295
  operationId: "restorePost",
@@ -283,8 +303,8 @@ async function memberPostRoutes(app, container) {
283
303
  }
284
304
  },
285
305
  handler: async (req) => {
286
- const actor = await actorOf(container, req.orgId, req.userId);
287
- return posts.restore(req.orgId, req.params.postId, actor);
306
+ const actor = await resolveActor(container, req);
307
+ return posts.restore(req.params.postId, null, actor);
288
308
  }
289
309
  });
290
310
  r.route({
@@ -303,8 +323,8 @@ async function memberPostRoutes(app, container) {
303
323
  }
304
324
  },
305
325
  handler: async (req) => {
306
- const actor = await actorOf(container, req.orgId, req.userId);
307
- return posts.pin(req.orgId, req.params.postId, actor);
326
+ const actor = await resolveActor(container, req);
327
+ return posts.pin(req.params.postId, actor);
308
328
  }
309
329
  });
310
330
  r.route({
@@ -323,8 +343,8 @@ async function memberPostRoutes(app, container) {
323
343
  }
324
344
  },
325
345
  handler: async (req) => {
326
- const actor = await actorOf(container, req.orgId, req.userId);
327
- return posts.unpin(req.orgId, req.params.postId, actor);
346
+ const actor = await resolveActor(container, req);
347
+ return posts.unpin(req.params.postId, actor);
328
348
  }
329
349
  });
330
350
  r.route({
@@ -343,8 +363,8 @@ async function memberPostRoutes(app, container) {
343
363
  }
344
364
  },
345
365
  handler: async (req) => {
346
- const actor = await actorOf(container, req.orgId, req.userId);
347
- return posts.closeComments(req.orgId, req.params.postId, actor);
366
+ const actor = await resolveActor(container, req);
367
+ return posts.closeComments(req.params.postId, actor);
348
368
  }
349
369
  });
350
370
  r.route({
@@ -363,8 +383,8 @@ async function memberPostRoutes(app, container) {
363
383
  }
364
384
  },
365
385
  handler: async (req) => {
366
- const actor = await actorOf(container, req.orgId, req.userId);
367
- return posts.openComments(req.orgId, req.params.postId, actor);
386
+ const actor = await resolveActor(container, req);
387
+ return posts.openComments(req.params.postId, actor);
368
388
  }
369
389
  });
370
390
  r.route({
@@ -384,8 +404,8 @@ async function memberPostRoutes(app, container) {
384
404
  }
385
405
  },
386
406
  handler: async (req) => {
387
- const actor = await actorOf(container, req.orgId, req.userId);
388
- return posts.setReactions(req.orgId, req.params.postId, actor, req.body.enabled);
407
+ const actor = await resolveActor(container, req);
408
+ return posts.setReactions(req.params.postId, req.body.enabled, actor);
389
409
  }
390
410
  });
391
411
  r.route({
@@ -405,8 +425,8 @@ async function memberPostRoutes(app, container) {
405
425
  }
406
426
  },
407
427
  handler: async (req) => {
408
- const actor = await actorOf(container, req.orgId, req.userId);
409
- return posts.setReaction(req.orgId, req.params.postId, actor, req.body.emoji);
428
+ const actor = await resolveActor(container, req);
429
+ return posts.setReaction(req.params.postId, req.body.emoji, actor);
410
430
  }
411
431
  });
412
432
  r.route({
@@ -425,49 +445,29 @@ async function memberPostRoutes(app, container) {
425
445
  }
426
446
  },
427
447
  handler: async (req) => {
428
- const actor = await actorOf(container, req.orgId, req.userId);
429
- return posts.setReaction(req.orgId, req.params.postId, actor, null);
448
+ const actor = await resolveActor(container, req);
449
+ return posts.setReaction(req.params.postId, null, actor);
430
450
  }
431
451
  });
432
452
  r.route({
433
- method: "POST",
434
- url: "/api/member/posts/:postId/follow",
435
- preHandler: app.requireOrgSession,
436
- schema: {
437
- operationId: "followPost",
438
- tags,
439
- summary: "Follow a post",
440
- description: "For anyone who can read the post. You are notified of new comments on it. Following twice is harmless.",
441
- params: PostIdParam,
442
- response: {
443
- 204: z.void(),
444
- ...refusals(403, 404)
445
- }
446
- },
447
- handler: async (req, reply) => {
448
- const actor = await actorOf(container, req.orgId, req.userId);
449
- await posts.follow(req.orgId, req.params.postId, actor);
450
- return reply.code(204).send();
451
- }
452
- });
453
- r.route({
454
- method: "DELETE",
455
- url: "/api/member/posts/:postId/follow",
453
+ method: "PUT",
454
+ url: "/api/member/posts/:postId/subscription",
456
455
  preHandler: app.requireOrgSession,
457
456
  schema: {
458
- operationId: "unfollowPost",
457
+ operationId: "setPostSubscription",
459
458
  tags,
460
- summary: "Stop following a post",
461
- description: "For anyone who can read the post. Harmless when you do not follow it.",
459
+ summary: "Follow or mute a post, or clear either",
460
+ description: "For anyone who can read the post. `following` notifies you of its new comments. `muted` stops its activity notifying you, until `mutedUntil` when given; mentions and actions on you still notify. `none` clears both.",
462
461
  params: PostIdParam,
462
+ body: SetPostSubscription,
463
463
  response: {
464
464
  204: z.void(),
465
465
  ...refusals(403, 404)
466
466
  }
467
467
  },
468
468
  handler: async (req, reply) => {
469
- const actor = await actorOf(container, req.orgId, req.userId);
470
- await posts.unfollow(req.orgId, req.params.postId, actor);
469
+ const actor = await resolveActor(container, req);
470
+ await container.subscriptions.setPost(req.params.postId, req.body, actor);
471
471
  return reply.code(204).send();
472
472
  }
473
473
  });
@@ -487,8 +487,8 @@ async function memberPostRoutes(app, container) {
487
487
  }
488
488
  },
489
489
  handler: async (req, reply) => {
490
- const actor = await actorOf(container, req.orgId, req.userId);
491
- await posts.bookmark(req.orgId, req.params.postId, actor);
490
+ const actor = await resolveActor(container, req);
491
+ await posts.bookmark(req.params.postId, actor);
492
492
  return reply.code(204).send();
493
493
  }
494
494
  });
@@ -508,8 +508,8 @@ async function memberPostRoutes(app, container) {
508
508
  }
509
509
  },
510
510
  handler: async (req, reply) => {
511
- const actor = await actorOf(container, req.orgId, req.userId);
512
- await posts.unbookmark(req.orgId, req.params.postId, actor);
511
+ const actor = await resolveActor(container, req);
512
+ await posts.unbookmark(req.params.postId, actor);
513
513
  return reply.code(204).send();
514
514
  }
515
515
  });
@@ -530,14 +530,14 @@ async function memberPostRoutes(app, container) {
530
530
  }
531
531
  },
532
532
  handler: async (req, reply) => {
533
- const actor = await actorOf(container, req.orgId, req.userId);
534
- const flag = await posts.flag(req.orgId, req.params.postId, actor, req.body.reason);
533
+ const actor = await resolveActor(container, req);
534
+ const flag = await posts.flag(req.params.postId, req.body.reason, actor);
535
535
  return reply.code(201).send(flag);
536
536
  }
537
537
  });
538
538
  r.route({
539
539
  method: "POST",
540
- url: "/api/member/posts/:postId/flags/resolution",
540
+ url: "/api/member/posts/:postId/flags/resolve",
541
541
  preHandler: app.requireOrgSession,
542
542
  schema: {
543
543
  operationId: "resolvePostFlags",
@@ -552,8 +552,8 @@ async function memberPostRoutes(app, container) {
552
552
  }
553
553
  },
554
554
  handler: async (req, reply) => {
555
- const actor = await actorOf(container, req.orgId, req.userId);
556
- await posts.resolveFlags(req.orgId, req.params.postId, actor, req.body.resolution);
555
+ const actor = await resolveActor(container, req);
556
+ await posts.resolveFlags(req.params.postId, req.body.resolution, null, actor);
557
557
  return reply.code(204).send();
558
558
  }
559
559
  });
@@ -574,8 +574,8 @@ async function memberPostRoutes(app, container) {
574
574
  }
575
575
  },
576
576
  handler: async (req) => {
577
- const actor = await actorOf(container, req.orgId, req.userId);
578
- return container.discussion.postComments(req.orgId, req.params.postId, actor, req.query);
577
+ const actor = await resolveActor(container, req);
578
+ return container.discussion.postComments(req.params.postId, req.query, actor);
579
579
  }
580
580
  });
581
581
  r.route({
@@ -595,13 +595,12 @@ async function memberPostRoutes(app, container) {
595
595
  }
596
596
  },
597
597
  handler: async (req, reply) => {
598
- const actor = await actorOf(container, req.orgId, req.userId);
599
- const comment = await container.discussion.postComment(req.orgId, {
600
- actor,
598
+ const actor = await resolveActor(container, req);
599
+ const comment = await container.discussion.postComment({
601
600
  postId: req.params.postId,
602
601
  parentId: req.body.parentId,
603
602
  body: req.body.body
604
- });
603
+ }, actor);
605
604
  return reply.code(201).send(comment);
606
605
  }
607
606
  });
@@ -1 +1 @@
1
- {"version":3,"file":"posts.js","names":[],"sources":["../../../../src/http/routes/member/posts.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 { actorOf } from './actor.js';\nimport {\n CommentView,\n CommunityParam,\n CommunitySpaceParam,\n CreatePost,\n FeedQuery,\n FlagReason,\n PageQuery,\n PostCommentsPage,\n PostComment,\n PostFeedPage,\n PostFlag,\n PostIdParam,\n PostView,\n Reactions,\n ResolvePostFlags,\n SchedulePost,\n SetPostReactions,\n SetReaction,\n SpaceQueue,\n UpdatePost,\n refusals,\n} from '../../schemas/index.js';\n\nexport async function memberPostRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const posts = container.posts;\n const tags = ['Posts'];\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/feed',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberFeed',\n tags,\n summary: 'Published posts across the spaces you can open',\n description:\n 'For a member of the community. Pinned posts first, then newest first. 404 when you are not a member.',\n params: CommunityParam,\n querystring: PageQuery,\n response: { 200: PostFeedPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.memberFeed(req.orgId, actor, req.params.communityId, req.query);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/own-posts',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listOwnPosts',\n tags,\n summary: 'Your own posts in a community, newest first',\n description:\n 'For a member of the community. Your posts in any status, drafts included, in the spaces you can open. 404 when you are not a member.',\n params: CommunityParam,\n querystring: PageQuery,\n response: { 200: PostFeedPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.listOwn(req.orgId, actor, req.params.communityId, req.query);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/bookmarks',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listBookmarkedPosts',\n tags,\n summary: 'The posts you bookmarked in a community, newest bookmark first',\n description:\n 'For a member of the community. Only posts you can still read. 404 when you are not a member.',\n params: CommunityParam,\n querystring: PageQuery,\n response: { 200: PostFeedPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.listBookmarked(req.orgId, actor, req.params.communityId, req.query);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/spaces/:spaceId/feed',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getSpaceFeed',\n tags,\n summary: 'Published posts in a space',\n description:\n 'For anyone who can open the space (403 otherwise). Filter by `topicId`; an unknown topic is a 404.',\n params: CommunitySpaceParam,\n querystring: FeedQuery,\n response: { 200: PostFeedPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.spaceFeed(\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: 'GET',\n url: '/api/member/communities/:communityId/spaces/:spaceId/queue',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getSpaceQueue',\n tags,\n summary: 'The moderation queue of a space you moderate',\n description:\n 'For a moderator who can open the space. Posts awaiting review, and the open flags on posts and on comments in the space. Each list holds at most the 100 oldest entries: pending posts by creation, flags by when they were raised; working the queue brings in the next ones.',\n params: CommunitySpaceParam,\n response: { 200: SpaceQueue, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.moderationQueue(req.orgId, actor, req.params.communityId, req.params.spaceId);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/posts',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createPost',\n tags,\n summary: 'Create a post',\n description:\n 'For anyone who can open the space. `mode` decides the status: `draft`, `scheduled`, or on `submit` `published`, or `pending` when the space requires review and you are not staff. 409 when the space has topics and none is given, or a schedule is missing or in the past.',\n params: CommunitySpaceParam,\n body: CreatePost,\n response: { 201: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const post = await posts.create(req.orgId, actor, {\n ...req.body,\n communityId: req.params.communityId,\n spaceId: req.params.spaceId,\n });\n req.log.info({ postId: post.id, status: post.status }, 'post created');\n return reply.code(201).send(post);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/posts/:postId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getPost',\n tags,\n summary: 'Read a post',\n description:\n 'For anyone who can open the space while the post is published; for its author in any status; for moderators also while it is pending or removed. Any other post is a 404.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.getPost(req.orgId, actor, req.params.postId);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/member/posts/:postId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updatePost',\n tags,\n summary: 'Edit your own post',\n description:\n 'Author only. Changes only the fields sent. 409 when the post is removed, or the topic rules of the space are not met.',\n params: PostIdParam,\n body: UpdatePost,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.update(req.orgId, req.params.postId, actor, req.body);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/submission',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'submitPost',\n tags,\n summary: 'Submit your draft or scheduled post',\n description:\n 'Author only. Publishes it now, or holds it for review when the space requires review and you are not staff. 409 when the post is not a draft or scheduled.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.submit(req.orgId, req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/schedule',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'schedulePost',\n tags,\n summary: 'Schedule your post',\n description:\n 'Author only. The post is submitted at `scheduledAt`. 409 when it is already submitted or removed, or the time is not in the future.',\n params: PostIdParam,\n body: SchedulePost,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.schedule(req.orgId, req.params.postId, actor, req.body.scheduledAt);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/approval',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'approvePost',\n tags,\n summary: 'Publish a post that awaits review in a space you moderate',\n description:\n 'For a moderator who can open the space. 409 when the post is not awaiting review.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.approve(req.orgId, req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/removal',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'removePost',\n tags,\n summary: 'Remove your post, or one in a space you moderate',\n description:\n \"For the author or a moderator. Takes a published or pending post down; a moderator removing someone else's post resolves its open flags as `removed`. 409 when it is already removed or was never submitted.\",\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.remove(req.orgId, req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/restoration',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'restorePost',\n tags,\n summary: 'Restore a removed post in a space you moderate',\n description: 'For a moderator who can open the space. 409 when the post is not removed.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.restore(req.orgId, req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/pin',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'pinPost',\n tags,\n summary: 'Pin a post in a space you moderate',\n description: 'For a moderator. Pinned posts lead the feeds. 409 when the post is removed.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.pin(req.orgId, req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/unpin',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'unpinPost',\n tags,\n summary: 'Unpin a post in a space you moderate',\n description: 'For a moderator. 409 when the post is removed.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.unpin(req.orgId, req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/comments/close',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'closePostComments',\n tags,\n summary: 'Close comments on a post in a space you moderate',\n description:\n 'For a moderator. Nobody can comment until they are opened again. 409 when the post is removed.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.closeComments(req.orgId, req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/comments/open',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'openPostComments',\n tags,\n summary: 'Open comments on a post in a space you moderate',\n description: 'For a moderator. 409 when the post is removed.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.openComments(req.orgId, req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/posts/:postId/reactions',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setPostReactionPolicy',\n tags,\n summary: 'Turn reactions on or off for a post in a space you moderate',\n description:\n \"For a moderator. Overrides the space's reactions setting for this post. 409 when the post is removed.\",\n params: PostIdParam,\n body: SetPostReactions,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.setReactions(req.orgId, req.params.postId, actor, req.body.enabled);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/posts/:postId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setPostReaction',\n tags,\n summary: 'Set your reaction on a post',\n description:\n 'For anyone who can read the post, while reactions are on for it (403 otherwise). Replaces any reaction you already had and returns the new counts.',\n params: PostIdParam,\n body: SetReaction,\n response: { 200: Reactions, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.setReaction(req.orgId, req.params.postId, actor, req.body.emoji);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/posts/:postId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'clearPostReaction',\n tags,\n summary: 'Withdraw your reaction from a post',\n description:\n 'For anyone who can read the post, while reactions are on for it (403 otherwise). Returns the new counts.',\n params: PostIdParam,\n response: { 200: Reactions, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.setReaction(req.orgId, req.params.postId, actor, null);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/follow',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'followPost',\n tags,\n summary: 'Follow a post',\n description:\n 'For anyone who can read the post. You are notified of new comments on it. Following twice is harmless.',\n params: PostIdParam,\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 posts.follow(req.orgId, req.params.postId, actor);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/posts/:postId/follow',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'unfollowPost',\n tags,\n summary: 'Stop following a post',\n description: 'For anyone who can read the post. Harmless when you do not follow it.',\n params: PostIdParam,\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 posts.unfollow(req.orgId, req.params.postId, actor);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/bookmark',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'bookmarkPost',\n tags,\n summary: 'Bookmark a post',\n description: 'For anyone who can read the post. Bookmarking twice is harmless.',\n params: PostIdParam,\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 posts.bookmark(req.orgId, req.params.postId, actor);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/posts/:postId/bookmark',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'unbookmarkPost',\n tags,\n summary: 'Remove a bookmark',\n description: 'For anyone who can read the post. Harmless when you have not bookmarked it.',\n params: PostIdParam,\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 posts.unbookmark(req.orgId, req.params.postId, actor);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/flags',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'flagPost',\n tags,\n summary: 'Flag a post for the moderators',\n description:\n 'For anyone who can read the post. Flagging it again while your flag is open returns that flag.',\n params: PostIdParam,\n body: FlagReason,\n response: { 201: PostFlag, ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const flag = await posts.flag(req.orgId, req.params.postId, actor, req.body.reason);\n return reply.code(201).send(flag);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/flags/resolution',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'resolvePostFlags',\n tags,\n summary: 'Dismiss the open flags on a post in a space you moderate',\n description:\n 'For a moderator who can open the space. Closes every open flag and leaves the post up.',\n params: PostIdParam,\n body: ResolvePostFlags,\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 posts.resolveFlags(req.orgId, req.params.postId, actor, req.body.resolution);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/posts/:postId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listPostComments',\n tags,\n summary: \"Read a post's comments\",\n description:\n 'For anyone who can read the post. Pages by top-level comment, oldest first. Each page carries its top-level comments and all of their replies; `total` counts top-level comments.',\n params: PostIdParam,\n querystring: PageQuery,\n response: { 200: PostCommentsPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.discussion.postComments(req.orgId, req.params.postId, actor, req.query);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createPostComment',\n tags,\n summary: 'Comment on a post',\n description:\n 'For anyone who can read the post, while its comments are open (403 otherwise). Replies nest one level. 409 when the post is not published.',\n params: PostIdParam,\n body: PostComment,\n response: { 201: CommentView, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const comment = await container.discussion.postComment(req.orgId, {\n actor,\n postId: req.params.postId,\n parentId: req.body.parentId,\n body: req.body.body,\n });\n return reply.code(201).send(comment);\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"posts.js","names":[],"sources":["../../../../src/http/routes/member/posts.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 CommentView,\n CommunityParam,\n CommunitySpaceParam,\n CreatePost,\n FeedQuery,\n FlagReason,\n PageQuery,\n PostCommentsPage,\n PostComment,\n PostFeedPage,\n PostFlag,\n PostIdParam,\n PostView,\n Reactions,\n ResolvePostFlags,\n SchedulePost,\n SetPostReactions,\n SetPostSubscription,\n SetReaction,\n SpaceQueue,\n UpdatePost,\n refusals,\n} from '../../schemas/index.js';\n\nexport async function memberPostRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const posts = container.posts;\n const tags = ['Posts'];\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/feed',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberFeed',\n tags,\n summary: 'Published posts across the spaces you can open',\n description:\n 'For a member of the community. Pinned posts first, then newest first. 404 when you are not a member.',\n params: CommunityParam,\n querystring: PageQuery,\n response: { 200: PostFeedPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.memberFeed(req.params.communityId, req.query, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/own-posts',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listOwnPosts',\n tags,\n summary: 'Your own posts in a community, newest first',\n description:\n 'For a member of the community. Your posts in any status, drafts included, in the spaces you can open. 404 when you are not a member.',\n params: CommunityParam,\n querystring: PageQuery,\n response: { 200: PostFeedPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.listOwn(req.params.communityId, req.query, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/bookmarks',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listBookmarkedPosts',\n tags,\n summary: 'The posts you bookmarked in a community, newest bookmark first',\n description:\n 'For a member of the community. Only posts you can still read. 404 when you are not a member.',\n params: CommunityParam,\n querystring: PageQuery,\n response: { 200: PostFeedPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.listBookmarked(req.params.communityId, req.query, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/spaces/:spaceId/feed',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getSpaceFeed',\n tags,\n summary: 'Published posts in a space',\n description:\n 'For anyone who can open the space (403 otherwise). Filter by `topicId`; an unknown topic is a 404.',\n params: CommunitySpaceParam,\n querystring: FeedQuery,\n response: { 200: PostFeedPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.spaceFeed(req.params.communityId, req.params.spaceId, req.query, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/spaces/:spaceId/queue',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getSpaceQueue',\n tags,\n summary: 'The moderation queue of a space you moderate',\n description:\n 'For a moderator who can open the space. Posts awaiting review, and the open flags on posts and on comments in the space. Each list holds at most the 100 oldest entries: pending posts by creation, flags by when they were raised; working the queue brings in the next ones.',\n params: CommunitySpaceParam,\n response: { 200: SpaceQueue, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.moderationQueue(req.params.communityId, req.params.spaceId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/posts',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createPost',\n tags,\n summary: 'Create a post',\n description:\n 'For anyone who can open the space. `mode` decides the status: `draft`, `scheduled`, or on `submit` `published`, or `pending` when the space requires review and you do not hold `communities:moderate` on the community. 409 when the space has topics and none is given. 400 when a schedule is missing or in the past. 503 when a scheduled post could not be timed; the post is then kept as a draft, and `details.postId` names it, so open that draft instead of creating the post again.',\n params: CommunitySpaceParam,\n body: CreatePost,\n response: { 201: PostView, ...refusals(400, 403, 404, 409, 503) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n const post = await posts.create(\n { ...req.body, communityId: req.params.communityId, spaceId: req.params.spaceId },\n actor,\n );\n req.log.info({ postId: post.id, status: post.status }, 'post created');\n return reply.code(201).send(post);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/posts/:postId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getPost',\n tags,\n summary: 'Read a post',\n description:\n 'For anyone who can open the space while the post is published; for its author in any status; for moderators also while it is pending or removed. Any other post is a 404.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.getPost(req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/member/posts/:postId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updatePost',\n tags,\n summary: 'Edit your own post',\n description:\n 'Author only. Changes only the fields sent. 409 when the post is removed, or the topic rules of the space are not met.',\n params: PostIdParam,\n body: UpdatePost,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.update(req.params.postId, req.body, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/submit',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'submitPost',\n tags,\n summary: 'Submit your draft or scheduled post',\n description:\n 'Author only. Publishes it now, or holds it for review when the space requires review and you do not hold `communities:moderate` on the community. A scheduled post is no longer published at its time. 409 when the post is not a draft or scheduled.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.submit(req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/schedule',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'schedulePost',\n tags,\n summary: 'Schedule your post',\n description:\n 'Author only. Schedules a draft, or moves a scheduled post to a new time; the post is submitted at `scheduledAt`. 409 when it is already submitted or removed, or the time is not in the future. 503 when the time could not be set; a draft stays a draft and a moved post keeps its previous time, and `details.postId` names the post.',\n params: PostIdParam,\n body: SchedulePost,\n response: { 200: PostView, ...refusals(403, 404, 409, 503) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.schedule(req.params.postId, req.body.scheduledAt, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/unschedule',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'unschedulePost',\n tags,\n summary: 'Unschedule your post',\n description:\n 'Author only. The scheduled post becomes a draft and is not submitted at its time. 409 when the post is not scheduled.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.unschedule(req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/approve',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'approvePost',\n tags,\n summary: 'Publish a post that awaits review in a space you moderate',\n description:\n 'For a moderator who can open the space. 409 when the post is not awaiting review.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.approve(req.params.postId, null, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/remove',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'removePost',\n tags,\n summary: 'Remove your post, or one in a space you moderate',\n description:\n \"The author can remove their own post; anyone else needs `communities:moderate` on the community. Takes a published or pending post down; removing someone else's post resolves its open flags as `removed`. 409 when it is already removed or was never submitted.\",\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.remove(req.params.postId, null, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/restore',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'restorePost',\n tags,\n summary: 'Restore a removed post in a space you moderate',\n description: 'For a moderator who can open the space. 409 when the post is not removed.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.restore(req.params.postId, null, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/pin',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'pinPost',\n tags,\n summary: 'Pin a post in a space you moderate',\n description: 'For a moderator. Pinned posts lead the feeds. 409 when the post is removed.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.pin(req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/unpin',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'unpinPost',\n tags,\n summary: 'Unpin a post in a space you moderate',\n description: 'For a moderator. 409 when the post is removed.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.unpin(req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/comments/close',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'closePostComments',\n tags,\n summary: 'Close comments on a post in a space you moderate',\n description:\n 'For a moderator. Nobody can comment until they are opened again. 409 when the post is removed.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.closeComments(req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/comments/open',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'openPostComments',\n tags,\n summary: 'Open comments on a post in a space you moderate',\n description: 'For a moderator. 409 when the post is removed.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.openComments(req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/posts/:postId/reactions',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setPostReactionPolicy',\n tags,\n summary: 'Turn reactions on or off for a post in a space you moderate',\n description:\n \"For a moderator. Overrides the space's reactions setting for this post. 409 when the post is removed.\",\n params: PostIdParam,\n body: SetPostReactions,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.setReactions(req.params.postId, req.body.enabled, actor);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/posts/:postId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setPostReaction',\n tags,\n summary: 'Set your reaction on a post',\n description:\n 'For anyone who can read the post, while reactions are on for it (403 otherwise). Replaces any reaction you already had and returns the new counts.',\n params: PostIdParam,\n body: SetReaction,\n response: { 200: Reactions, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.setReaction(req.params.postId, req.body.emoji, actor);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/posts/:postId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'clearPostReaction',\n tags,\n summary: 'Withdraw your reaction from a post',\n description:\n 'For anyone who can read the post, while reactions are on for it (403 otherwise). Returns the new counts.',\n params: PostIdParam,\n response: { 200: Reactions, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return posts.setReaction(req.params.postId, null, actor);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/posts/:postId/subscription',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setPostSubscription',\n tags,\n summary: 'Follow or mute a post, or clear either',\n description:\n 'For anyone who can read the post. `following` notifies you of its new comments. `muted` stops its activity notifying you, until `mutedUntil` when given; mentions and actions on you still notify. `none` clears both.',\n params: PostIdParam,\n body: SetPostSubscription,\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.setPost(req.params.postId, req.body, actor);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/bookmark',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'bookmarkPost',\n tags,\n summary: 'Bookmark a post',\n description: 'For anyone who can read the post. Bookmarking twice is harmless.',\n params: PostIdParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n await posts.bookmark(req.params.postId, actor);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/posts/:postId/bookmark',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'unbookmarkPost',\n tags,\n summary: 'Remove a bookmark',\n description: 'For anyone who can read the post. Harmless when you have not bookmarked it.',\n params: PostIdParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n await posts.unbookmark(req.params.postId, actor);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/flags',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'flagPost',\n tags,\n summary: 'Flag a post for the moderators',\n description:\n 'For anyone who can read the post. Flagging it again while your flag is open returns that flag.',\n params: PostIdParam,\n body: FlagReason,\n response: { 201: PostFlag, ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n const flag = await posts.flag(req.params.postId, req.body.reason, actor);\n return reply.code(201).send(flag);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/flags/resolve',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'resolvePostFlags',\n tags,\n summary: 'Dismiss the open flags on a post in a space you moderate',\n description:\n 'For a moderator who can open the space. Closes every open flag and leaves the post up.',\n params: PostIdParam,\n body: ResolvePostFlags,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n await posts.resolveFlags(req.params.postId, req.body.resolution, null, actor);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/posts/:postId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listPostComments',\n tags,\n summary: \"Read a post's comments\",\n description:\n 'For anyone who can read the post. Pages by top-level comment, oldest first. Each page carries its top-level comments and all of their replies; `total` counts top-level comments.',\n params: PostIdParam,\n querystring: PageQuery,\n response: { 200: PostCommentsPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.discussion.postComments(req.params.postId, req.query, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createPostComment',\n tags,\n summary: 'Comment on a post',\n description:\n 'For anyone who can read the post, while its comments are open (403 otherwise). Replies nest one level. 409 when the post is not published.',\n params: PostIdParam,\n body: PostComment,\n response: { 201: CommentView, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n const comment = await container.discussion.postComment(\n { postId: req.params.postId, parentId: req.body.parentId, body: req.body.body },\n actor,\n );\n return reply.code(201).send(comment);\n },\n });\n}\n"],"mappings":"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"}
@@ -1,11 +1,18 @@
1
1
  import { refusals } from "../../schemas/shared.js";
2
+ import { resolveCaller } from "../../scope.js";
2
3
  import { UpdateProfile, UserProfile } from "../../schemas/profile.js";
3
4
  import "../../schemas/index.js";
4
- import { actorOf } from "./actor.js";
5
5
  //#region src/http/routes/member/profile.ts
6
6
  async function memberProfileRoutes(app, container) {
7
7
  const r = app.withTypeProvider();
8
8
  const tags = ["Profile"];
9
+ async function memberOf(req) {
10
+ const caller = await resolveCaller(container, req);
11
+ return {
12
+ ...caller.actor,
13
+ userId: caller.userId
14
+ };
15
+ }
9
16
  r.route({
10
17
  method: "GET",
11
18
  url: "/api/member/profile",
@@ -14,11 +21,14 @@ async function memberProfileRoutes(app, container) {
14
21
  operationId: "getMemberProfile",
15
22
  tags,
16
23
  summary: "Read your profile",
17
- description: "For any signed-in person. The profile is yours across organizations; `imageUrl` is set only when the photo was uploaded in this organization. Before you save one, every field is empty.",
18
- response: { 200: UserProfile }
24
+ description: "For any active member of the organization. The profile is yours across organizations; `imageUrl` is set only when the photo was uploaded in this organization. Before you save one, every field is empty.",
25
+ response: {
26
+ 200: UserProfile,
27
+ ...refusals(403)
28
+ }
19
29
  },
20
30
  handler: async (req) => {
21
- return container.identity.profileOf(req.orgId, req.userId);
31
+ return container.identity.profileOf(await memberOf(req));
22
32
  }
23
33
  });
24
34
  r.route({
@@ -37,8 +47,7 @@ async function memberProfileRoutes(app, container) {
37
47
  }
38
48
  },
39
49
  handler: async (req) => {
40
- const orgUser = await actorOf(container, req.orgId, req.userId);
41
- return container.identity.updateProfile(req.orgId, req.userId, req.userId, orgUser.id, req.body);
50
+ return container.identity.updateProfile(req.body, await memberOf(req));
42
51
  }
43
52
  });
44
53
  }