@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 +1 @@
1
- {"version":3,"file":"community.js","names":[],"sources":["../../../../src/http/routes/member/community.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport type { Container } from '../../../app/container.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport { actorOf } from './actor.js';\nimport {\n CommunityHome,\n CommunityHomeSpace,\n CommunityParam,\n CommunitySpaceParam,\n DirectoryPage,\n DirectoryQuery,\n OrgUserBody,\n PageQuery,\n SpaceMember,\n SpaceMemberParam,\n SpaceMembersPage,\n refusals,\n} from '../../schemas/index.js';\n\nexport async function memberCommunityRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const tags = ['Communities'];\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/home',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberCommunityHome',\n tags,\n summary: 'The spaces a member can see in a community',\n description:\n \"For a member of the community. Lists its spaces in order. Spaces the viewer can open come back in full; spaces they can see but not open come back locked, with only their name, count, rule and the viewer's standing. A by-invite space the viewer is not invited to or a member of is left out. 404 when the viewer is not a member.\",\n params: CommunityParam,\n response: { 200: CommunityHome, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const home = await container.reporting.member.communityHome(\n req.orgId,\n actor,\n req.params.communityId,\n );\n if (!home) {\n throw new NotFoundError('Community', req.params.communityId);\n }\n return home;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/spaces/:spaceId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberSpace',\n tags,\n summary: 'One space as a member sees it',\n description:\n 'For a member of the community: the space with its topics, links, settings and where the viewer stands. A space the viewer cannot open comes back locked, without its content. 404 when the viewer is not a member, the space is not in the community, or it is a by-invite space the viewer is not invited to or a member of.',\n params: CommunitySpaceParam,\n response: { 200: CommunityHomeSpace, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const space = await container.reporting.member.communitySpace(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n );\n if (!space) {\n throw new NotFoundError('Space', req.params.spaceId);\n }\n return space;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/directory',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberCommunityDirectory',\n tags,\n summary: 'People in a community',\n description:\n 'For a member of the community: everyone who holds it, with their public profile. 404 when the viewer is not a member.',\n params: CommunityParam,\n querystring: DirectoryQuery,\n response: { 200: DirectoryPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const page = await container.reporting.member.directory(\n req.orgId,\n actor,\n req.params.communityId,\n req.query,\n );\n if (!page) {\n throw new NotFoundError('Community', req.params.communityId);\n }\n return page;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listSpaceMembers',\n tags,\n summary: 'List the members of a space',\n description:\n 'For anyone who can open the space. Members see the active members; moderators also see open requests and invitations.',\n params: CommunitySpaceParam,\n querystring: PageQuery,\n response: { 200: SpaceMembersPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.listSpaceMembers(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n req.query,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/requests',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'requestSpaceMembership',\n tags,\n summary: 'Ask to join a space',\n description:\n 'For a member of the community who is not staff. Opens a request on a `by_request` space for a moderator to approve. 409 when the space is not `by_request` or you are already placed in it.',\n params: CommunitySpaceParam,\n response: { 201: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const member = await container.entitlements.requestJoin(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n );\n return reply.code(201).send(member);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/invites',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'inviteSpaceMember',\n tags,\n summary: 'Invite a person into a space you moderate',\n description:\n 'For a moderator who is a member of the community. Invites another member into a `by_invite` space; they join when they accept. 403 when the person is staff or does not hold the community; 409 when the space is not invite-only or the person is already placed.',\n params: CommunitySpaceParam,\n body: OrgUserBody,\n response: { 201: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const member = await container.entitlements.invite(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n req.body.orgUserId,\n );\n return reply.code(201).send(member);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'addSpaceMember',\n tags,\n summary: 'Add a person to a space you moderate',\n description:\n 'For a moderator who is a member of the community. Places another member straight into the space, with no request or invitation. 409 when the space is open to all members, the person is already placed, or the space is full.',\n params: CommunitySpaceParam,\n body: OrgUserBody,\n response: { 201: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const member = await container.entitlements.addSpaceMember(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n req.body.orgUserId,\n );\n return reply.code(201).send(member);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/acceptance',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'acceptSpaceInvite',\n tags,\n summary: 'Accept your invitation to a space',\n description:\n 'For a member of the community who is not staff. Makes you an active member. 409 when you have no open invitation, are already a member, or the space is full.',\n params: CommunitySpaceParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.accept(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/approval',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'approveSpaceRequest',\n tags,\n summary: \"Approve a person's request to join a space you moderate\",\n description:\n 'For a moderator who is a member of the community. Makes the requester an active member. 404 when the person has no open request; 409 when the space is full or they are already a member.',\n params: SpaceMemberParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.approve(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n req.params.orgUserId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/decline',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'declineSpaceRequest',\n tags,\n summary: \"Decline a person's request to join a space you moderate\",\n description:\n 'For a moderator who is a member of the community. Closes the open request. 404 when the person has no open request; 409 when they are already a member.',\n params: SpaceMemberParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.decline(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n req.params.orgUserId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/refusal',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'refuseSpaceInvite',\n tags,\n summary: 'Refuse your invitation to a space',\n description:\n 'For a member of the community who is not staff. 409 when you have no open invitation or are already a member.',\n params: CommunitySpaceParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.refuse(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/departure',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'leaveSpace',\n tags,\n summary: 'Leave a space',\n description:\n 'For a member of the community who is not staff. 409 when you are not a member of the space.',\n params: CommunitySpaceParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.leave(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/removal',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'removeSpaceMember',\n tags,\n summary: 'Remove a person from a space you moderate',\n description:\n 'For a moderator who is a member of the community. Revokes the placement, whether it is a membership, a request or an invitation. 409 when the space is open to all members or the placement is already revoked.',\n params: SpaceMemberParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.remove(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n req.params.orgUserId,\n );\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"community.js","names":[],"sources":["../../../../src/http/routes/member/community.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport type { Container } from '../../../app/container.js';\nimport { resolveActor } from '../../scope.js';\nimport {\n CommunityHome,\n CommunityHomeSpace,\n CommunityParam,\n CommunitySpaceParam,\n DirectoryPage,\n DirectoryQuery,\n OrgUserBody,\n PageQuery,\n SetMute,\n SpaceIdParam,\n SpaceMember,\n SpaceMemberParam,\n SpaceMembersPage,\n refusals,\n} from '../../schemas/index.js';\n\nexport async function memberCommunityRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const tags = ['Communities'];\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/home',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberCommunityHome',\n tags,\n summary: 'The spaces a member can see in a community',\n description:\n \"For a member of the community, through a grant or `communities:read` on it. Lists its spaces in order. Spaces the viewer can open come back in full; spaces they can see but not open come back locked, with only their name, count, rule and the viewer's standing. A by-invite space the viewer is not invited to or a member of is left out, unless the viewer opens every space. `viewer` carries the viewer's permissions on the community, whether they open every space, and their team flag and role name. 404 when the viewer is not a member.\",\n params: CommunityParam,\n response: { 200: CommunityHome, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.reporting.member.communityHome(req.params.communityId, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/spaces/:spaceId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberSpace',\n tags,\n summary: 'One space as a member sees it',\n description:\n 'For a member of the community: the space with its topics, links, settings and where the viewer stands. A space the viewer cannot open comes back locked, without its content. 404 when the viewer is not a member, the space is not in the community, or it is a by-invite space the viewer is not invited to or a member of.',\n params: CommunitySpaceParam,\n response: { 200: CommunityHomeSpace, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.reporting.member.communitySpace(\n req.params.communityId,\n req.params.spaceId,\n actor,\n );\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/directory',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberCommunityDirectory',\n tags,\n summary: 'People in a community',\n description:\n 'For a member of the community: everyone who holds it or holds `communities:read` on it, with their public profile, whether they are team here and under which role name, and whether they open every space. 404 when the viewer is not a member.',\n params: CommunityParam,\n querystring: DirectoryQuery,\n response: { 200: DirectoryPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.reporting.member.directory(req.params.communityId, req.query, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listSpaceMembers',\n tags,\n summary: 'List the members of a space',\n description:\n 'For anyone who can open the space. Members see the active members; holders of `communities:moderate` on the community also see open requests and invitations. Who opens every space holds no place, and is left out.',\n params: CommunitySpaceParam,\n querystring: PageQuery,\n response: { 200: SpaceMembersPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.entitlements.listSpaceMembers(\n req.params.communityId,\n req.params.spaceId,\n req.query,\n actor,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/requests',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'requestSpaceMembership',\n tags,\n summary: 'Ask to join a space',\n description:\n 'For a member of the community who does not open every space. Opens a request on a `by_request` space for a moderator to approve. 409 when the space is not `by_request` or you are already placed in it.',\n params: CommunitySpaceParam,\n response: { 201: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n const member = await container.entitlements.requestJoin(\n req.params.communityId,\n req.params.spaceId,\n actor,\n );\n return reply.code(201).send(member);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/invites',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'inviteSpaceMember',\n tags,\n summary: 'Invite a person into a space you moderate',\n description: `Requires \\`communities:moderate\\`. On the community, as its member. Invites another member into a \\`by_invite\\` space; they join when they accept. 403 when the person opens every space or is not a member of the community; 409 when the space is not invite-only or the person is already placed.`,\n params: CommunitySpaceParam,\n body: OrgUserBody,\n response: { 201: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n const member = await container.entitlements.invite(\n req.params.communityId,\n req.params.spaceId,\n req.body.orgUserId,\n actor,\n );\n return reply.code(201).send(member);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'addSpaceMember',\n tags,\n summary: 'Add a person to a space you moderate',\n description: `Requires \\`communities:moderate\\`. On the community, as its member. Places another member straight into the space, with no request or invitation. 403 when the person opens every space; 409 when the space is open to all members, the person is already placed, or the space is full.`,\n params: CommunitySpaceParam,\n body: OrgUserBody,\n response: { 201: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n const member = await container.entitlements.addSpaceMember(\n req.params.communityId,\n req.params.spaceId,\n req.body.orgUserId,\n actor,\n );\n return reply.code(201).send(member);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/accept',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'acceptSpaceInvite',\n tags,\n summary: 'Accept your invitation to a space',\n description:\n 'For a member of the community who does not open every space. Makes you an active member. 409 when you have no open invitation, are already a member, or the space is full.',\n params: CommunitySpaceParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.entitlements.accept(req.params.communityId, req.params.spaceId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/approve',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'approveSpaceRequest',\n tags,\n summary: \"Approve a person's request to join a space you moderate\",\n description: `Requires \\`communities:moderate\\`. On the community, as its member. Makes the requester an active member. 404 when the person has no open request; 409 when the space is full or they are already a member.`,\n params: SpaceMemberParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.entitlements.approve(\n req.params.communityId,\n req.params.spaceId,\n req.params.orgUserId,\n actor,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/decline',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'declineSpaceRequest',\n tags,\n summary: \"Decline a person's request to join a space you moderate\",\n description: `Requires \\`communities:moderate\\`. On the community, as its member. Closes the open request. 404 when the person has no open request; 409 when they are already a member.`,\n params: SpaceMemberParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.entitlements.decline(\n req.params.communityId,\n req.params.spaceId,\n req.params.orgUserId,\n actor,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/refuse',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'refuseSpaceInvite',\n tags,\n summary: 'Refuse your invitation to a space',\n description:\n 'For a member of the community who does not open every space. 409 when you have no open invitation or are already a member.',\n params: CommunitySpaceParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.entitlements.refuse(req.params.communityId, req.params.spaceId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/departure',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'leaveSpace',\n tags,\n summary: 'Leave a space',\n description:\n 'For a member of the community who does not open every space. 409 when you are not a member of the space.',\n params: CommunitySpaceParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.entitlements.leave(req.params.communityId, req.params.spaceId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/remove',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'removeSpaceMember',\n tags,\n summary: 'Remove a person from a space you moderate',\n description: `Requires \\`communities:moderate\\`. On the community, as its member. Revokes the placement, whether it is a membership, a request or an invitation. 409 when the space is open to all members or the placement is already revoked.`,\n params: SpaceMemberParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.entitlements.remove(\n req.params.communityId,\n req.params.spaceId,\n req.params.orgUserId,\n actor,\n );\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/spaces/:spaceId/subscription',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setSpaceSubscription',\n tags,\n summary: 'Mute a space, or clear your mute',\n description:\n 'For anyone who can open the space. `muted` stops its activity notifying you, until `mutedUntil` when given; mentions, actions on you and moderation work still notify. `none` clears the mute.',\n params: SpaceIdParam,\n body: SetMute,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n await container.subscriptions.setSpace(req.params.spaceId, req.body, actor);\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":";;;;;;;;AAsBA,eAAsB,sBACpB,KACA,WACe;CACf,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,OAAO,CAAC,aAAa;CAE3B,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK;IAAe,GAAG,SAAS,KAAK,GAAG;GAAE;EACxD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,UAAU,UAAU,OAAO,cAAc,IAAI,OAAO,aAAa,KAAK;EAC/E;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;IAAoB,GAAG,SAAS,KAAK,GAAG;GAAE;EAC7D;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,UAAU,UAAU,OAAO,eAChC,IAAI,OAAO,aACX,IAAI,OAAO,SACX,KACF;EACF;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;IAAe,GAAG,SAAS,KAAK,GAAG;GAAE;EACxD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,UAAU,UAAU,OAAO,UAAU,IAAI,OAAO,aAAa,IAAI,OAAO,KAAK;EACtF;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,aAAa,iBAC5B,IAAI,OAAO,aACX,IAAI,OAAO,SACX,IAAI,OACJ,KACF;EACF;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK;IAAa,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EAC3D;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,SAAS,MAAM,UAAU,aAAa,YAC1C,IAAI,OAAO,aACX,IAAI,OAAO,SACX,KACF;GACA,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,MAAM;EACpC;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aAAa;GACb,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,SAAS,MAAM,UAAU,aAAa,OAC1C,IAAI,OAAO,aACX,IAAI,OAAO,SACX,IAAI,KAAK,WACT,KACF;GACA,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,MAAM;EACpC;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aAAa;GACb,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,SAAS,MAAM,UAAU,aAAa,eAC1C,IAAI,OAAO,aACX,IAAI,OAAO,SACX,IAAI,KAAK,WACT,KACF;GACA,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,MAAM;EACpC;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK;IAAa,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EAC3D;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,UAAU,aAAa,OAAO,IAAI,OAAO,aAAa,IAAI,OAAO,SAAS,KAAK;EACxF;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aAAa;GACb,QAAQ;GACR,UAAU;IAAE,KAAK;IAAa,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EAC3D;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,UAAU,aAAa,QAC5B,IAAI,OAAO,aACX,IAAI,OAAO,SACX,IAAI,OAAO,WACX,KACF;EACF;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;IAAa,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EAC3D;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,UAAU,aAAa,QAC5B,IAAI,OAAO,aACX,IAAI,OAAO,SACX,IAAI,OAAO,WACX,KACF;EACF;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK;IAAa,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EAC3D;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,UAAU,aAAa,OAAO,IAAI,OAAO,aAAa,IAAI,OAAO,SAAS,KAAK;EACxF;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK;IAAa,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EAC3D;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,UAAU,aAAa,MAAM,IAAI,OAAO,aAAa,IAAI,OAAO,SAAS,KAAK;EACvF;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;IAAa,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EAC3D;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,UAAU,aAAa,OAC5B,IAAI,OAAO,aACX,IAAI,OAAO,SACX,IAAI,OAAO,WACX,KACF;EACF;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,SAAS,IAAI,OAAO,SAAS,IAAI,MAAM,KAAK;GAC1E,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK;EAC9B;CACF,CAAC;AACH"}
@@ -1,10 +1,9 @@
1
1
  import { refusals } from "../../schemas/shared.js";
2
+ import { resolveActor } from "../../scope.js";
2
3
  import { Course } from "../../schemas/content.js";
3
4
  import { ActivityList } from "../../schemas/activities.js";
4
5
  import { MemberCourseIdParam, MemberCourses, MemberModules } from "../../schemas/member.js";
5
6
  import "../../schemas/index.js";
6
- import { actorOf } from "./actor.js";
7
- import { NotFoundError } from "@headless-lms/core/shared/errors";
8
7
  //#region src/http/routes/member/courses.ts
9
8
  async function memberCoursesRoutes(app, container) {
10
9
  const r = app.withTypeProvider();
@@ -24,8 +23,8 @@ async function memberCoursesRoutes(app, container) {
24
23
  }
25
24
  },
26
25
  handler: async (req) => {
27
- const orgUser = await actorOf(container, req.orgId, req.userId);
28
- return member.listCourses(req.orgId, orgUser.id);
26
+ const actor = await resolveActor(container, req);
27
+ return member.listCourses(actor);
29
28
  }
30
29
  });
31
30
  r.route({
@@ -36,7 +35,7 @@ async function memberCoursesRoutes(app, container) {
36
35
  operationId: "getMemberCourse",
37
36
  tags: ["Content"],
38
37
  summary: "Get one course you can open",
39
- description: "For a member of the organization. 404 when the course is not published or you do not hold it.",
38
+ description: "For a member of the organization who holds the course, or holds `courses:read` on it (which also opens it unpublished). 404 otherwise.",
40
39
  params: MemberCourseIdParam,
41
40
  response: {
42
41
  200: Course,
@@ -44,10 +43,8 @@ async function memberCoursesRoutes(app, container) {
44
43
  }
45
44
  },
46
45
  handler: async (req) => {
47
- const orgUser = await actorOf(container, req.orgId, req.userId);
48
- const course = await member.getCourse(req.orgId, orgUser.id, req.params.courseId);
49
- if (!course) throw new NotFoundError("Course", req.params.courseId);
50
- return course;
46
+ const actor = await resolveActor(container, req);
47
+ return member.getCourse(req.params.courseId, actor);
51
48
  }
52
49
  });
53
50
  r.route({
@@ -58,7 +55,7 @@ async function memberCoursesRoutes(app, container) {
58
55
  operationId: "listMemberModules",
59
56
  tags: ["Content"],
60
57
  summary: "List the modules and activities of a course you can open",
61
- description: "For a member of the organization. Published activities only. 404 when the course is not published or you do not hold it.",
58
+ description: "For a member of the organization who holds the course, or holds `courses:read` on it. Published activities only. 404 otherwise.",
62
59
  params: MemberCourseIdParam,
63
60
  response: {
64
61
  200: MemberModules,
@@ -66,10 +63,8 @@ async function memberCoursesRoutes(app, container) {
66
63
  }
67
64
  },
68
65
  handler: async (req) => {
69
- const orgUser = await actorOf(container, req.orgId, req.userId);
70
- const modules = await member.listModules(req.orgId, orgUser.id, req.params.courseId);
71
- if (!modules) throw new NotFoundError("Course", req.params.courseId);
72
- return modules;
66
+ const actor = await resolveActor(container, req);
67
+ return member.listModules(req.params.courseId, actor);
73
68
  }
74
69
  });
75
70
  r.route({
@@ -80,7 +75,7 @@ async function memberCoursesRoutes(app, container) {
80
75
  operationId: "listMemberActivities",
81
76
  tags: ["Content"],
82
77
  summary: "List the published activities of a course you can open",
83
- description: "For a member of the organization. 404 when the course is not published or you do not hold it.",
78
+ description: "For a member of the organization who holds the course, or holds `courses:read` on it. 404 otherwise.",
84
79
  params: MemberCourseIdParam,
85
80
  response: {
86
81
  200: ActivityList,
@@ -88,10 +83,8 @@ async function memberCoursesRoutes(app, container) {
88
83
  }
89
84
  },
90
85
  handler: async (req) => {
91
- const orgUser = await actorOf(container, req.orgId, req.userId);
92
- const activities = await member.listActivities(req.orgId, orgUser.id, req.params.courseId);
93
- if (!activities) throw new NotFoundError("Course", req.params.courseId);
94
- return activities;
86
+ const actor = await resolveActor(container, req);
87
+ return member.listActivities(req.params.courseId, actor);
95
88
  }
96
89
  });
97
90
  }
@@ -1 +1 @@
1
- {"version":3,"file":"courses.js","names":[],"sources":["../../../../src/http/routes/member/courses.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n ActivityList,\n Course,\n MemberCourseIdParam,\n MemberCourses,\n MemberModules,\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 memberCoursesRoutes(\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: 'GET',\n url: '/api/member/courses',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listMemberCourses',\n tags: ['Content'],\n summary: 'List the courses you can open',\n description:\n 'For a member of the organization. Published courses you hold, directly or through a bundle.',\n response: { 200: MemberCourses, ...refusals(403) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n return member.listCourses(req.orgId, orgUser.id);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/courses/:courseId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberCourse',\n tags: ['Content'],\n summary: 'Get one course you can open',\n description:\n 'For a member of the organization. 404 when the course is not published or you do not hold it.',\n params: MemberCourseIdParam,\n response: { 200: Course, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n const course = await member.getCourse(req.orgId, orgUser.id, req.params.courseId);\n if (!course) {\n throw new NotFoundError('Course', req.params.courseId);\n }\n return course;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/courses/:courseId/modules',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listMemberModules',\n tags: ['Content'],\n summary: 'List the modules and activities of a course you can open',\n description:\n 'For a member of the organization. Published activities only. 404 when the course is not published or you do not hold it.',\n params: MemberCourseIdParam,\n response: { 200: MemberModules, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n const modules = await member.listModules(req.orgId, orgUser.id, req.params.courseId);\n if (!modules) {\n throw new NotFoundError('Course', req.params.courseId);\n }\n return modules;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/courses/:courseId/activities',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listMemberActivities',\n tags: ['Content'],\n summary: 'List the published activities of a course you can open',\n description:\n 'For a member of the organization. 404 when the course is not published or you do not hold it.',\n params: MemberCourseIdParam,\n response: { 200: ActivityList, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n const activities = await member.listActivities(req.orgId, orgUser.id, req.params.courseId);\n if (!activities) {\n throw new NotFoundError('Course', req.params.courseId);\n }\n return activities;\n },\n });\n}\n"],"mappings":";;;;;;;;AAcA,eAAsB,oBACpB,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,SAAS;GAChB,SAAS;GACT,aACE;GACF,UAAU;IAAE,KAAK;IAAe,GAAG,SAAS,GAAG;GAAE;EACnD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9D,OAAO,OAAO,YAAY,IAAI,OAAO,QAAQ,EAAE;EACjD;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK;IAAQ,GAAG,SAAS,KAAK,GAAG;GAAE;EACjD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9D,MAAM,SAAS,MAAM,OAAO,UAAU,IAAI,OAAO,QAAQ,IAAI,IAAI,OAAO,QAAQ;GAChF,IAAI,CAAC,QACH,MAAM,IAAI,cAAc,UAAU,IAAI,OAAO,QAAQ;GAEvD,OAAO;EACT;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK;IAAe,GAAG,SAAS,KAAK,GAAG;GAAE;EACxD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9D,MAAM,UAAU,MAAM,OAAO,YAAY,IAAI,OAAO,QAAQ,IAAI,IAAI,OAAO,QAAQ;GACnF,IAAI,CAAC,SACH,MAAM,IAAI,cAAc,UAAU,IAAI,OAAO,QAAQ;GAEvD,OAAO;EACT;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK;IAAc,GAAG,SAAS,KAAK,GAAG;GAAE;EACvD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9D,MAAM,aAAa,MAAM,OAAO,eAAe,IAAI,OAAO,QAAQ,IAAI,IAAI,OAAO,QAAQ;GACzF,IAAI,CAAC,YACH,MAAM,IAAI,cAAc,UAAU,IAAI,OAAO,QAAQ;GAEvD,OAAO;EACT;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"courses.js","names":[],"sources":["../../../../src/http/routes/member/courses.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n ActivityList,\n Course,\n MemberCourseIdParam,\n MemberCourses,\n MemberModules,\n refusals,\n} from '../../schemas/index.js';\nimport type { Container } from '../../../app/container.js';\nimport { resolveActor } from '../../scope.js';\n\nexport async function memberCoursesRoutes(\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: 'GET',\n url: '/api/member/courses',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listMemberCourses',\n tags: ['Content'],\n summary: 'List the courses you can open',\n description:\n 'For a member of the organization. Published courses you hold, directly or through a bundle.',\n response: { 200: MemberCourses, ...refusals(403) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return member.listCourses(actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/courses/:courseId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberCourse',\n tags: ['Content'],\n summary: 'Get one course you can open',\n description:\n 'For a member of the organization who holds the course, or holds `courses:read` on it (which also opens it unpublished). 404 otherwise.',\n params: MemberCourseIdParam,\n response: { 200: Course, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return member.getCourse(req.params.courseId, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/courses/:courseId/modules',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listMemberModules',\n tags: ['Content'],\n summary: 'List the modules and activities of a course you can open',\n description:\n 'For a member of the organization who holds the course, or holds `courses:read` on it. Published activities only. 404 otherwise.',\n params: MemberCourseIdParam,\n response: { 200: MemberModules, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return member.listModules(req.params.courseId, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/courses/:courseId/activities',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listMemberActivities',\n tags: ['Content'],\n summary: 'List the published activities of a course you can open',\n description:\n 'For a member of the organization who holds the course, or holds `courses:read` on it. 404 otherwise.',\n params: MemberCourseIdParam,\n response: { 200: ActivityList, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return member.listActivities(req.params.courseId, actor);\n },\n });\n}\n"],"mappings":";;;;;;;AAaA,eAAsB,oBACpB,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,SAAS;GAChB,SAAS;GACT,aACE;GACF,UAAU;IAAE,KAAK;IAAe,GAAG,SAAS,GAAG;GAAE;EACnD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,OAAO,YAAY,KAAK;EACjC;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK;IAAQ,GAAG,SAAS,KAAK,GAAG;GAAE;EACjD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,OAAO,UAAU,IAAI,OAAO,UAAU,KAAK;EACpD;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK;IAAe,GAAG,SAAS,KAAK,GAAG;GAAE;EACxD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,OAAO,YAAY,IAAI,OAAO,UAAU,KAAK;EACtD;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK;IAAc,GAAG,SAAS,KAAK,GAAG;GAAE;EACvD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,OAAO,eAAe,IAAI,OAAO,UAAU,KAAK;EACzD;CACF,CAAC;AACH"}
@@ -0,0 +1,7 @@
1
+ import { Container } from "../../../app/container.js";
2
+ import { FastifyInstance } from "fastify";
3
+ //#region src/http/routes/member/devices.d.ts
4
+ declare function memberDeviceRoutes(app: FastifyInstance, container: Container): Promise<void>;
5
+ //#endregion
6
+ export { memberDeviceRoutes };
7
+ //# sourceMappingURL=devices.d.ts.map
@@ -0,0 +1,97 @@
1
+ import { refusals } from "../../schemas/shared.js";
2
+ import { resolveActor, resolveCaller } from "../../scope.js";
3
+ import { Device, DeviceIdParam, DeviceList, PushConfigView, RegisterDevice } from "../../schemas/notifications.js";
4
+ import "../../schemas/index.js";
5
+ import { z } from "zod";
6
+ //#region src/http/routes/member/devices.ts
7
+ async function memberDeviceRoutes(app, container) {
8
+ const r = app.withTypeProvider();
9
+ const tags = ["Notifications"];
10
+ async function memberOf(req) {
11
+ const caller = await resolveCaller(container, req);
12
+ return {
13
+ ...caller.actor,
14
+ userId: caller.userId
15
+ };
16
+ }
17
+ r.route({
18
+ method: "GET",
19
+ url: "/api/member/devices",
20
+ preHandler: app.requireOrgSession,
21
+ schema: {
22
+ operationId: "listMemberDevices",
23
+ tags,
24
+ summary: "List the devices you registered for push notifications",
25
+ description: "For anyone signed in to the organization. Each app install or browser that registered, most recently seen first. A device not seen for 30 days receives no push until it registers again.",
26
+ response: {
27
+ 200: DeviceList,
28
+ ...refusals(403)
29
+ }
30
+ },
31
+ handler: async (req) => {
32
+ return container.devices.list(await memberOf(req));
33
+ }
34
+ });
35
+ r.route({
36
+ method: "POST",
37
+ url: "/api/member/devices",
38
+ preHandler: app.requireOrgSession,
39
+ schema: {
40
+ operationId: "registerMemberDevice",
41
+ tags,
42
+ summary: "Register this device for push notifications, or refresh it",
43
+ description: "For anyone signed in to the organization. Call it after sign-in and on every launch. A new token adds a device, a known token is refreshed, and a token another person registered moves to you. A browser sends its push subscription endpoint as `token` with its `keys`; an iOS app sends its bundle id as `appId` and its APNs `environment`. 422 when the provider is not configured, an `apns` `appId` is not one of the configured bundle ids, a `webpush` device has no `keys`, or `keys` or `environment` come with a provider they do not apply to.",
44
+ body: RegisterDevice,
45
+ response: {
46
+ 200: Device,
47
+ ...refusals(400, 403, 422)
48
+ }
49
+ },
50
+ handler: async (req) => {
51
+ return container.devices.register(req.body, await memberOf(req));
52
+ }
53
+ });
54
+ r.route({
55
+ method: "DELETE",
56
+ url: "/api/member/devices/:deviceId",
57
+ preHandler: app.requireOrgSession,
58
+ schema: {
59
+ operationId: "removeMemberDevice",
60
+ tags,
61
+ summary: "Remove one of your devices",
62
+ description: "For the person who registered the device. Call it before signing out, so the next person on the device receives none of your pushes. 404 when the device is not yours.",
63
+ params: DeviceIdParam,
64
+ response: {
65
+ 204: z.void(),
66
+ ...refusals(403, 404)
67
+ }
68
+ },
69
+ handler: async (req, reply) => {
70
+ await container.devices.remove(req.params.deviceId, await memberOf(req));
71
+ return reply.code(204).send();
72
+ }
73
+ });
74
+ r.route({
75
+ method: "GET",
76
+ url: "/api/member/push-config",
77
+ preHandler: app.requireOrgSession,
78
+ schema: {
79
+ operationId: "getMemberPushConfig",
80
+ tags,
81
+ summary: "Read what a browser needs to subscribe to push notifications",
82
+ description: "For anyone signed in to the organization. `vapidPublicKey` is the `applicationServerKey` for `pushManager.subscribe`, or null when Web Push is not configured.",
83
+ response: {
84
+ 200: PushConfigView,
85
+ ...refusals(403)
86
+ }
87
+ },
88
+ handler: async (req) => {
89
+ await resolveActor(container, req);
90
+ return container.devices.pushConfig();
91
+ }
92
+ });
93
+ }
94
+ //#endregion
95
+ export { memberDeviceRoutes };
96
+
97
+ //# sourceMappingURL=devices.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devices.js","names":[],"sources":["../../../../src/http/routes/member/devices.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 type { FastifyRequest } from 'fastify';\nimport { resolveActor, resolveCaller } from '../../scope.js';\nimport {\n Device,\n DeviceIdParam,\n DeviceList,\n PushConfigView,\n RegisterDevice,\n refusals,\n} from '../../schemas/index.js';\n\nexport async function memberDeviceRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const tags = ['Notifications'];\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/devices',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listMemberDevices',\n tags,\n summary: 'List the devices you registered for push notifications',\n description:\n 'For anyone signed in to the organization. Each app install or browser that registered, most recently seen first. A device not seen for 30 days receives no push until it registers again.',\n response: { 200: DeviceList, ...refusals(403) },\n },\n handler: async (req) => {\n return container.devices.list(await memberOf(req));\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/devices',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'registerMemberDevice',\n tags,\n summary: 'Register this device for push notifications, or refresh it',\n description:\n 'For anyone signed in to the organization. Call it after sign-in and on every launch. A new token adds a device, a known token is refreshed, and a token another person registered moves to you. A browser sends its push subscription endpoint as `token` with its `keys`; an iOS app sends its bundle id as `appId` and its APNs `environment`. 422 when the provider is not configured, an `apns` `appId` is not one of the configured bundle ids, a `webpush` device has no `keys`, or `keys` or `environment` come with a provider they do not apply to.',\n body: RegisterDevice,\n response: { 200: Device, ...refusals(400, 403, 422) },\n },\n handler: async (req) => {\n return container.devices.register(req.body, await memberOf(req));\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/devices/:deviceId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'removeMemberDevice',\n tags,\n summary: 'Remove one of your devices',\n description:\n 'For the person who registered the device. Call it before signing out, so the next person on the device receives none of your pushes. 404 when the device is not yours.',\n params: DeviceIdParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n await container.devices.remove(req.params.deviceId, await memberOf(req));\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/push-config',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberPushConfig',\n tags,\n summary: 'Read what a browser needs to subscribe to push notifications',\n description:\n 'For anyone signed in to the organization. `vapidPublicKey` is the `applicationServerKey` for `pushManager.subscribe`, or null when Web Push is not configured.',\n response: { 200: PushConfigView, ...refusals(403) },\n },\n handler: async (req) => {\n await resolveActor(container, req);\n return container.devices.pushConfig();\n },\n });\n}\n"],"mappings":";;;;;;AAeA,eAAsB,mBACpB,KACA,WACe;CACf,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,OAAO,CAAC,eAAe;CAE7B,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;IAAY,GAAG,SAAS,GAAG;GAAE;EAChD;EACA,SAAS,OAAO,QAAQ;GACtB,OAAO,UAAU,QAAQ,KAAK,MAAM,SAAS,GAAG,CAAC;EACnD;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;IAAQ,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EACtD;EACA,SAAS,OAAO,QAAQ;GACtB,OAAO,UAAU,QAAQ,SAAS,IAAI,MAAM,MAAM,SAAS,GAAG,CAAC;EACjE;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,EAAE,KAAK;IAAG,GAAG,SAAS,KAAK,GAAG;GAAE;EACnD;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,UAAU,QAAQ,OAAO,IAAI,OAAO,UAAU,MAAM,SAAS,GAAG,CAAC;GACvE,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,UAAU;IAAE,KAAK;IAAgB,GAAG,SAAS,GAAG;GAAE;EACpD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,aAAa,WAAW,GAAG;GACjC,OAAO,UAAU,QAAQ,WAAW;EACtC;CACF,CAAC;AACH"}
@@ -1,9 +1,8 @@
1
1
  import { refusals } from "../../schemas/shared.js";
2
+ import { resolveActor } from "../../scope.js";
2
3
  import { DownloadIdParam } from "../../schemas/downloads.js";
3
4
  import { MemberDownload, MemberDownloads } from "../../schemas/member.js";
4
5
  import "../../schemas/index.js";
5
- import { actorOf } from "./actor.js";
6
- import { NotFoundError } from "@headless-lms/core/shared/errors";
7
6
  //#region src/http/routes/member/downloads.ts
8
7
  async function memberDownloadsRoutes(app, container) {
9
8
  const r = app.withTypeProvider();
@@ -23,8 +22,8 @@ async function memberDownloadsRoutes(app, container) {
23
22
  }
24
23
  },
25
24
  handler: async (req) => {
26
- const orgUser = await actorOf(container, req.orgId, req.userId);
27
- return member.listDownloads(req.orgId, orgUser.id);
25
+ const actor = await resolveActor(container, req);
26
+ return member.listDownloads(actor);
28
27
  }
29
28
  });
30
29
  r.route({
@@ -35,7 +34,7 @@ async function memberDownloadsRoutes(app, container) {
35
34
  operationId: "getMemberDownload",
36
35
  tags: ["Content"],
37
36
  summary: "One download you can open, with its files in order",
38
- description: "For a member of the organization. 404 when the download is not published or you do not hold it.",
37
+ description: "For a member of the organization who holds the download, or holds `downloads:read` on it (which also opens it unpublished). 404 otherwise.",
39
38
  params: DownloadIdParam,
40
39
  response: {
41
40
  200: MemberDownload,
@@ -43,10 +42,8 @@ async function memberDownloadsRoutes(app, container) {
43
42
  }
44
43
  },
45
44
  handler: async (req) => {
46
- const orgUser = await actorOf(container, req.orgId, req.userId);
47
- const result = await member.getDownload(req.orgId, orgUser.id, req.params.downloadId);
48
- if (!result) throw new NotFoundError("Download", req.params.downloadId);
49
- return result;
45
+ const actor = await resolveActor(container, req);
46
+ return member.getDownload(req.params.downloadId, actor);
50
47
  }
51
48
  });
52
49
  }
@@ -1 +1 @@
1
- {"version":3,"file":"downloads.js","names":[],"sources":["../../../../src/http/routes/member/downloads.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { DownloadIdParam, MemberDownload, MemberDownloads, refusals } 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 memberDownloadsRoutes(\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: 'GET',\n url: '/api/member/downloads',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listMemberDownloads',\n tags: ['Content'],\n summary: 'List the downloads you can open',\n description:\n 'For a member of the organization. Published downloads you hold, directly or through a bundle.',\n response: { 200: MemberDownloads, ...refusals(403) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n return member.listDownloads(req.orgId, orgUser.id);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/downloads/:downloadId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberDownload',\n tags: ['Content'],\n summary: 'One download you can open, with its files in order',\n description:\n 'For a member of the organization. 404 when the download is not published or you do not hold it.',\n params: DownloadIdParam,\n response: { 200: MemberDownload, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n const result = await member.getDownload(req.orgId, orgUser.id, req.params.downloadId);\n if (!result) {\n throw new NotFoundError('Download', req.params.downloadId);\n }\n return result;\n },\n });\n}\n"],"mappings":";;;;;;;AAOA,eAAsB,sBACpB,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,SAAS;GAChB,SAAS;GACT,aACE;GACF,UAAU;IAAE,KAAK;IAAiB,GAAG,SAAS,GAAG;GAAE;EACrD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9D,OAAO,OAAO,cAAc,IAAI,OAAO,QAAQ,EAAE;EACnD;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,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,SAAS,MAAM,OAAO,YAAY,IAAI,OAAO,QAAQ,IAAI,IAAI,OAAO,UAAU;GACpF,IAAI,CAAC,QACH,MAAM,IAAI,cAAc,YAAY,IAAI,OAAO,UAAU;GAE3D,OAAO;EACT;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"downloads.js","names":[],"sources":["../../../../src/http/routes/member/downloads.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { DownloadIdParam, MemberDownload, MemberDownloads, refusals } from '../../schemas/index.js';\nimport type { Container } from '../../../app/container.js';\nimport { resolveActor } from '../../scope.js';\n\nexport async function memberDownloadsRoutes(\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: 'GET',\n url: '/api/member/downloads',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listMemberDownloads',\n tags: ['Content'],\n summary: 'List the downloads you can open',\n description:\n 'For a member of the organization. Published downloads you hold, directly or through a bundle.',\n response: { 200: MemberDownloads, ...refusals(403) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return member.listDownloads(actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/downloads/:downloadId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberDownload',\n tags: ['Content'],\n summary: 'One download you can open, with its files in order',\n description:\n 'For a member of the organization who holds the download, or holds `downloads:read` on it (which also opens it unpublished). 404 otherwise.',\n params: DownloadIdParam,\n response: { 200: MemberDownload, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return member.getDownload(req.params.downloadId, actor);\n },\n });\n}\n"],"mappings":";;;;;;AAMA,eAAsB,sBACpB,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,SAAS;GAChB,SAAS;GACT,aACE;GACF,UAAU;IAAE,KAAK;IAAiB,GAAG,SAAS,GAAG;GAAE;EACrD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,OAAO,cAAc,KAAK;EACnC;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,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,YAAY,IAAI,OAAO,YAAY,KAAK;EACxD;CACF,CAAC;AACH"}
@@ -0,0 +1,7 @@
1
+ import { Container } from "../../../app/container.js";
2
+ import { FastifyInstance } from "fastify";
3
+ //#region src/http/routes/member/events.d.ts
4
+ declare function memberEventsRoutes(app: FastifyInstance, container: Container): Promise<void>;
5
+ //#endregion
6
+ export { memberEventsRoutes };
7
+ //# sourceMappingURL=events.d.ts.map