@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 +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 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 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. 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 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. 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 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. 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 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/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 actorOf(container, req.orgId, req.userId);\n return posts.unschedule(req.orgId, req.params.postId, actor);\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: '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 actorOf(container, req.orgId, req.userId);\n await container.subscriptions.setPost(req.orgId, actor, req.params.postId, req.body);\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":";;;;;;;;;AA8BA,eAAsB,iBAAiB,KAAsB,WAAqC;CAChG,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,QAAQ,UAAU;CACxB,MAAM,OAAO,CAAC,OAAO;CAErB,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,aAAa;GACb,UAAU;IAAE,KAAK;IAAc,GAAG,SAAS,KAAK,GAAG;GAAE;EACvD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,WAAW,IAAI,OAAO,aAAa,IAAI,OAAO,KAAK;EAClE;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,aAAa;GACb,UAAU;IAAE,KAAK;IAAc,GAAG,SAAS,KAAK,GAAG;GAAE;EACvD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,QAAQ,IAAI,OAAO,aAAa,IAAI,OAAO,KAAK;EAC/D;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,aAAa;GACb,UAAU;IAAE,KAAK;IAAc,GAAG,SAAS,KAAK,GAAG;GAAE;EACvD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,eAAe,IAAI,OAAO,aAAa,IAAI,OAAO,KAAK;EACtE;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,aAAa;GACb,UAAU;IAAE,KAAK;IAAc,GAAG,SAAS,KAAK,GAAG;GAAE;EACvD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,UAAU,IAAI,OAAO,aAAa,IAAI,OAAO,SAAS,IAAI,OAAO,KAAK;EACrF;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;IAAY,GAAG,SAAS,KAAK,GAAG;GAAE;EACrD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,gBAAgB,IAAI,OAAO,aAAa,IAAI,OAAO,SAAS,KAAK;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,MAAM;GACN,UAAU;IAAE,KAAK;IAAU,GAAG,SAAS,KAAK,KAAK,KAAK,KAAK,GAAG;GAAE;EAClE;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,OAAO,MAAM,MAAM,OACvB;IAAE,GAAG,IAAI;IAAM,aAAa,IAAI,OAAO;IAAa,SAAS,IAAI,OAAO;GAAQ,GAChF,KACF;GACA,IAAI,IAAI,KAAK;IAAE,QAAQ,KAAK;IAAI,QAAQ,KAAK;GAAO,GAAG,cAAc;GACrE,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI;EAClC;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;IAAU,GAAG,SAAS,KAAK,GAAG;GAAE;EACnD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,QAAQ,IAAI,OAAO,QAAQ,KAAK;EAC/C;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;IAAU,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EACxD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,OAAO,IAAI,OAAO,QAAQ,IAAI,MAAM,KAAK;EACxD;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;IAAU,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EACxD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,OAAO,IAAI,OAAO,QAAQ,KAAK;EAC9C;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;IAAU,GAAG,SAAS,KAAK,KAAK,KAAK,GAAG;GAAE;EAC7D;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,SAAS,IAAI,OAAO,QAAQ,IAAI,KAAK,aAAa,KAAK;EACtE;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;IAAU,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EACxD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,WAAW,IAAI,OAAO,QAAQ,KAAK;EAClD;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;IAAU,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EACxD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,QAAQ,IAAI,OAAO,QAAQ,MAAM,KAAK;EACrD;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;IAAU,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EACxD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,OAAO,IAAI,OAAO,QAAQ,MAAM,KAAK;EACpD;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;IAAU,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EACxD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,QAAQ,IAAI,OAAO,QAAQ,MAAM,KAAK;EACrD;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;IAAU,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EACxD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,IAAI,IAAI,OAAO,QAAQ,KAAK;EAC3C;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;IAAU,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EACxD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,MAAM,IAAI,OAAO,QAAQ,KAAK;EAC7C;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;IAAU,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EACxD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,cAAc,IAAI,OAAO,QAAQ,KAAK;EACrD;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;IAAU,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EACxD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,aAAa,IAAI,OAAO,QAAQ,KAAK;EACpD;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;IAAU,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EACxD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,aAAa,IAAI,OAAO,QAAQ,IAAI,KAAK,SAAS,KAAK;EACtE;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,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,YAAY,IAAI,OAAO,QAAQ,IAAI,KAAK,OAAO,KAAK;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,UAAU;IAAE,KAAK;IAAW,GAAG,SAAS,KAAK,GAAG;GAAE;EACpD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,MAAM,YAAY,IAAI,OAAO,QAAQ,MAAM,KAAK;EACzD;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,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,UAAU,cAAc,QAAQ,IAAI,OAAO,QAAQ,IAAI,MAAM,KAAK;GACxE,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK;EAC9B;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,EAAE,KAAK;IAAG,GAAG,SAAS,KAAK,GAAG;GAAE;EACnD;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,MAAM,SAAS,IAAI,OAAO,QAAQ,KAAK;GAC7C,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK;EAC9B;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,EAAE,KAAK;IAAG,GAAG,SAAS,KAAK,GAAG;GAAE;EACnD;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,MAAM,WAAW,IAAI,OAAO,QAAQ,KAAK;GAC/C,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK;EAC9B;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;IAAU,GAAG,SAAS,KAAK,GAAG;GAAE;EACnD;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,OAAO,MAAM,MAAM,KAAK,IAAI,OAAO,QAAQ,IAAI,KAAK,QAAQ,KAAK;GACvE,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI;EAClC;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,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,MAAM,aAAa,IAAI,OAAO,QAAQ,IAAI,KAAK,YAAY,MAAM,KAAK;GAC5E,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK;EAC9B;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,aAAa;GACb,UAAU;IAAE,KAAK;IAAkB,GAAG,SAAS,KAAK,GAAG;GAAE;EAC3D;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,UAAU,WAAW,aAAa,IAAI,OAAO,QAAQ,IAAI,OAAO,KAAK;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,KAAK,GAAG;GAAE;EAC3D;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,UAAU,MAAM,UAAU,WAAW,YACzC;IAAE,QAAQ,IAAI,OAAO;IAAQ,UAAU,IAAI,KAAK;IAAU,MAAM,IAAI,KAAK;GAAK,GAC9E,KACF;GACA,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,OAAO;EACrC;CACF,CAAC;AACH"}
@@ -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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"profile.js","names":[],"sources":["../../../../src/http/routes/member/profile.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport type { Container } from '../../../app/container.js';\nimport { actorOf } from './actor.js';\nimport { UpdateProfile, UserProfile, refusals } from '../../schemas/index.js';\n\nexport async function memberProfileRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const tags = ['Profile'];\n\n r.route({\n method: 'GET',\n url: '/api/member/profile',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberProfile',\n tags,\n summary: 'Read your profile',\n description:\n '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.',\n response: { 200: UserProfile },\n },\n handler: async (req) => {\n return container.identity.profileOf(req.orgId, req.userId);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/member/profile',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateMemberProfile',\n tags,\n summary: 'Update your profile',\n description:\n 'For a member of the organization. Changes only the fields sent; null clears one. 404 when `imageAssetId` is not a finalized image you uploaded here.',\n body: UpdateProfile,\n response: { 200: UserProfile, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n return container.identity.updateProfile(\n req.orgId,\n req.userId,\n req.userId,\n orgUser.id,\n req.body,\n );\n },\n });\n}\n"],"mappings":";;;;;AAMA,eAAsB,oBACpB,KACA,WACe;CACf,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,OAAO,CAAC,SAAS;CAEvB,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,UAAU,EAAE,KAAK,YAAY;EAC/B;EACA,SAAS,OAAO,QAAQ;GACtB,OAAO,UAAU,SAAS,UAAU,IAAI,OAAO,IAAI,MAAM;EAC3D;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,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,UAAU,SAAS,cACxB,IAAI,OACJ,IAAI,QACJ,IAAI,QACJ,QAAQ,IACR,IAAI,IACN;EACF;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"profile.js","names":[],"sources":["../../../../src/http/routes/member/profile.ts"],"sourcesContent":["import type { FastifyInstance, FastifyRequest } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport type { Container } from '../../../app/container.js';\nimport { resolveCaller } from '../../scope.js';\nimport { UpdateProfile, UserProfile, refusals } from '../../schemas/index.js';\n\nexport async function memberProfileRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const tags = ['Profile'];\n\n async function memberOf(req: FastifyRequest) {\n const caller = await resolveCaller(container, req);\n return { ...caller.actor, userId: caller.userId };\n }\n\n r.route({\n method: 'GET',\n url: '/api/member/profile',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberProfile',\n tags,\n summary: 'Read your profile',\n description:\n '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.',\n response: { 200: UserProfile, ...refusals(403) },\n },\n handler: async (req) => {\n return container.identity.profileOf(await memberOf(req));\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/member/profile',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateMemberProfile',\n tags,\n summary: 'Update your profile',\n description:\n 'For a member of the organization. Changes only the fields sent; null clears one. 404 when `imageAssetId` is not a finalized image you uploaded here.',\n body: UpdateProfile,\n response: { 200: UserProfile, ...refusals(403, 404) },\n },\n handler: async (req) => {\n return container.identity.updateProfile(req.body, await memberOf(req));\n },\n });\n}\n"],"mappings":";;;;;AAMA,eAAsB,oBACpB,KACA,WACe;CACf,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,OAAO,CAAC,SAAS;CAEvB,eAAe,SAAS,KAAqB;EAC3C,MAAM,SAAS,MAAM,cAAc,WAAW,GAAG;EACjD,OAAO;GAAE,GAAG,OAAO;GAAO,QAAQ,OAAO;EAAO;CAClD;CAEA,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,UAAU;IAAE,KAAK;IAAa,GAAG,SAAS,GAAG;GAAE;EACjD;EACA,SAAS,OAAO,QAAQ;GACtB,OAAO,UAAU,SAAS,UAAU,MAAM,SAAS,GAAG,CAAC;EACzD;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,MAAM;GACN,UAAU;IAAE,KAAK;IAAa,GAAG,SAAS,KAAK,GAAG;GAAE;EACtD;EACA,SAAS,OAAO,QAAQ;GACtB,OAAO,UAAU,SAAS,cAAc,IAAI,MAAM,MAAM,SAAS,GAAG,CAAC;EACvE;CACF,CAAC;AACH"}
@@ -1,8 +1,7 @@
1
1
  import { refusals } from "../../schemas/shared.js";
2
+ import { resolveActor } from "../../scope.js";
2
3
  import { CourseProgress, MemberCourseIdParam, ProgressStatus, ReportProgress } from "../../schemas/member.js";
3
4
  import "../../schemas/index.js";
4
- import { actorOf } from "./actor.js";
5
- import { NotFoundError } from "@headless-lms/core/shared/errors";
6
5
  //#region src/http/routes/member/progress.ts
7
6
  async function memberProgressRoutes(app, container) {
8
7
  const r = app.withTypeProvider();
@@ -15,7 +14,7 @@ async function memberProgressRoutes(app, container) {
15
14
  operationId: "reportProgress",
16
15
  tags: ["Progress"],
17
16
  summary: "Report usage on an activity; the progress service decides completion",
18
- description: "For a member who can open the course. Reports facts, never a decision: the service decides whether the activity is complete. 404 when the activity or its course is not published, or you cannot open it.",
17
+ description: "For a member who can open the course, through a grant or `courses:read` on it. Reports facts, never a decision: the service decides whether the activity is complete. 404 when the activity is not published, or you cannot open its course.",
19
18
  body: ReportProgress,
20
19
  response: {
21
20
  200: ProgressStatus,
@@ -23,12 +22,11 @@ async function memberProgressRoutes(app, container) {
23
22
  }
24
23
  },
25
24
  handler: async (req) => {
26
- const orgUser = await actorOf(container, req.orgId, req.userId);
27
- return { status: (await container.progress.report(req.orgId, {
28
- orgUserId: orgUser.id,
25
+ const actor = await resolveActor(container, req);
26
+ return { status: (await container.progress.report({
29
27
  activityId: req.body.activity,
30
28
  reports: req.body.reports
31
- })).completedAt ? "completed" : "in-progress" };
29
+ }, actor)).completedAt ? "completed" : "in-progress" };
32
30
  }
33
31
  });
34
32
  r.route({
@@ -47,10 +45,8 @@ async function memberProgressRoutes(app, container) {
47
45
  }
48
46
  },
49
47
  handler: async (req) => {
50
- const orgUser = await actorOf(container, req.orgId, req.userId);
51
- const view = await member.courseProgress(req.orgId, orgUser.id, req.params.courseId);
52
- if (!view) throw new NotFoundError("Course", req.params.courseId);
53
- return view;
48
+ const actor = await resolveActor(container, req);
49
+ return member.courseProgress(req.params.courseId, actor);
54
50
  }
55
51
  });
56
52
  }
@@ -1 +1 @@
1
- {"version":3,"file":"progress.js","names":[],"sources":["../../../../src/http/routes/member/progress.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n CourseProgress,\n MemberCourseIdParam,\n ProgressStatus,\n ReportProgress,\n refusals,\n} from '../../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../../app/container.js';\nimport { actorOf } from './actor.js';\n\nexport async function memberProgressRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const member = container.reporting.member;\n\n r.route({\n method: 'POST',\n url: '/api/member/progress',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reportProgress',\n tags: ['Progress'],\n summary: 'Report usage on an activity; the progress service decides completion',\n description:\n 'For a member who can open the course. Reports facts, never a decision: the service decides whether the activity is complete. 404 when the activity or its course is not published, or you cannot open it.',\n body: ReportProgress,\n response: { 200: ProgressStatus, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n const record = await container.progress.report(req.orgId, {\n orgUserId: orgUser.id,\n activityId: req.body.activity,\n reports: req.body.reports,\n });\n return { status: record.completedAt ? ('completed' as const) : ('in-progress' as const) };\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/courses/:courseId/progress',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberCourseProgress',\n tags: ['Progress'],\n summary: 'Your progress in one course',\n description:\n \"For a member of the organization, derived from the course's current structure. 404 when the course is not published or you do not hold it.\",\n params: MemberCourseIdParam,\n response: { 200: CourseProgress, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n const view = await member.courseProgress(req.orgId, orgUser.id, req.params.courseId);\n if (!view) {\n throw new NotFoundError('Course', req.params.courseId);\n }\n return view;\n },\n });\n}\n"],"mappings":";;;;;;AAaA,eAAsB,qBACpB,KACA,WACe;CACf,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,SAAS,UAAU,UAAU;CAEnC,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,UAAU;GACjB,SAAS;GACT,aACE;GACF,MAAM;GACN,UAAU;IAAE,KAAK;IAAgB,GAAG,SAAS,KAAK,GAAG;GAAE;EACzD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAM9D,OAAO,EAAE,SAAQ,MALI,UAAU,SAAS,OAAO,IAAI,OAAO;IACxD,WAAW,QAAQ;IACnB,YAAY,IAAI,KAAK;IACrB,SAAS,IAAI,KAAK;GACpB,CAAC,EAAA,CACuB,cAAe,cAAyB,cAAwB;EAC1F;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,UAAU;GACjB,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK;IAAgB,GAAG,SAAS,KAAK,GAAG;GAAE;EACzD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9D,MAAM,OAAO,MAAM,OAAO,eAAe,IAAI,OAAO,QAAQ,IAAI,IAAI,OAAO,QAAQ;GACnF,IAAI,CAAC,MACH,MAAM,IAAI,cAAc,UAAU,IAAI,OAAO,QAAQ;GAEvD,OAAO;EACT;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"progress.js","names":[],"sources":["../../../../src/http/routes/member/progress.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n CourseProgress,\n MemberCourseIdParam,\n ProgressStatus,\n ReportProgress,\n refusals,\n} from '../../schemas/index.js';\nimport type { Container } from '../../../app/container.js';\nimport { resolveActor } from '../../scope.js';\n\nexport async function memberProgressRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const member = container.reporting.member;\n\n r.route({\n method: 'POST',\n url: '/api/member/progress',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reportProgress',\n tags: ['Progress'],\n summary: 'Report usage on an activity; the progress service decides completion',\n description:\n 'For a member who can open the course, through a grant or `courses:read` on it. Reports facts, never a decision: the service decides whether the activity is complete. 404 when the activity is not published, or you cannot open its course.',\n body: ReportProgress,\n response: { 200: ProgressStatus, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n const record = await container.progress.report(\n { activityId: req.body.activity, reports: req.body.reports },\n actor,\n );\n return { status: record.completedAt ? ('completed' as const) : ('in-progress' as const) };\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/courses/:courseId/progress',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberCourseProgress',\n tags: ['Progress'],\n summary: 'Your progress in one course',\n description:\n \"For a member of the organization, derived from the course's current structure. 404 when the course is not published or you do not hold it.\",\n params: MemberCourseIdParam,\n response: { 200: CourseProgress, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return member.courseProgress(req.params.courseId, actor);\n },\n });\n}\n"],"mappings":";;;;;AAYA,eAAsB,qBACpB,KACA,WACe;CACf,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,SAAS,UAAU,UAAU;CAEnC,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,UAAU;GACjB,SAAS;GACT,aACE;GACF,MAAM;GACN,UAAU;IAAE,KAAK;IAAgB,GAAG,SAAS,KAAK,GAAG;GAAE;EACzD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAK/C,OAAO,EAAE,SAAQ,MAJI,UAAU,SAAS,OACtC;IAAE,YAAY,IAAI,KAAK;IAAU,SAAS,IAAI,KAAK;GAAQ,GAC3D,KACF,EAAA,CACwB,cAAe,cAAyB,cAAwB;EAC1F;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,UAAU;GACjB,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK;IAAgB,GAAG,SAAS,KAAK,GAAG;GAAE;EACzD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,OAAO,eAAe,IAAI,OAAO,UAAU,KAAK;EACzD;CACF,CAAC;AACH"}