@headless-lms/server 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/README.md +14 -0
  2. package/dist/app/community-mail.d.ts +5 -3
  3. package/dist/app/community-mail.js +60 -12
  4. package/dist/app/community-mail.js.map +1 -1
  5. package/dist/app/container.d.ts +24 -3
  6. package/dist/app/container.js +102 -38
  7. package/dist/app/container.js.map +1 -1
  8. package/dist/app/notification-channels.js +1 -0
  9. package/dist/app/notification-channels.js.map +1 -1
  10. package/dist/app/notification-reads.d.ts +3 -3
  11. package/dist/app/notification-reads.js +7 -28
  12. package/dist/app/notification-reads.js.map +1 -1
  13. package/dist/app/platform.d.ts +14 -0
  14. package/dist/app/platform.js +14 -0
  15. package/dist/app/platform.js.map +1 -0
  16. package/dist/http/config.d.ts +4 -0
  17. package/dist/http/errors.js +9 -4
  18. package/dist/http/errors.js.map +1 -1
  19. package/dist/http/fastify.d.ts +8 -0
  20. package/dist/http/mcp/catalogue.d.ts +11 -9
  21. package/dist/http/mcp/catalogue.js +15 -21
  22. package/dist/http/mcp/catalogue.js.map +1 -1
  23. package/dist/http/mcp/challenge.js +3 -3
  24. package/dist/http/mcp/challenge.js.map +1 -1
  25. package/dist/http/mcp/route.js +7 -7
  26. package/dist/http/mcp/route.js.map +1 -1
  27. package/dist/http/mcp/test-support.d.ts +6 -5
  28. package/dist/http/mcp/test-support.js +32 -11
  29. package/dist/http/mcp/test-support.js.map +1 -1
  30. package/dist/http/mcp/tool.d.ts +3 -9
  31. package/dist/http/mcp/tool.js +14 -6
  32. package/dist/http/mcp/tool.js.map +1 -1
  33. package/dist/http/mcp/tools/automations.js +10 -20
  34. package/dist/http/mcp/tools/automations.js.map +1 -1
  35. package/dist/http/mcp/tools/catalog.js +25 -42
  36. package/dist/http/mcp/tools/catalog.js.map +1 -1
  37. package/dist/http/mcp/tools/comments.js +9 -19
  38. package/dist/http/mcp/tools/comments.js.map +1 -1
  39. package/dist/http/mcp/tools/communities.js +16 -31
  40. package/dist/http/mcp/tools/communities.js.map +1 -1
  41. package/dist/http/mcp/tools/community-members.js +9 -16
  42. package/dist/http/mcp/tools/community-members.js.map +1 -1
  43. package/dist/http/mcp/tools/community-moderation.js +13 -25
  44. package/dist/http/mcp/tools/community-moderation.js.map +1 -1
  45. package/dist/http/mcp/tools/courses.js +11 -21
  46. package/dist/http/mcp/tools/courses.js.map +1 -1
  47. package/dist/http/mcp/tools/events.d.ts +6 -0
  48. package/dist/http/mcp/tools/events.js +170 -0
  49. package/dist/http/mcp/tools/events.js.map +1 -0
  50. package/dist/http/mcp/tools/index.d.ts +5 -1
  51. package/dist/http/mcp/tools/index.js +45 -10
  52. package/dist/http/mcp/tools/index.js.map +1 -1
  53. package/dist/http/mcp/tools/organization.d.ts +6 -0
  54. package/dist/http/mcp/tools/organization.js +214 -0
  55. package/dist/http/mcp/tools/organization.js.map +1 -0
  56. package/dist/http/mcp/tools/people.js +30 -49
  57. package/dist/http/mcp/tools/people.js.map +1 -1
  58. package/dist/http/plugins/auth.js +37 -8
  59. package/dist/http/plugins/auth.js.map +1 -1
  60. package/dist/http/plugins/error-handler.d.ts +5 -1
  61. package/dist/http/plugins/error-handler.js +10 -2
  62. package/dist/http/plugins/error-handler.js.map +1 -1
  63. package/dist/http/plugins/openapi.js +18 -2
  64. package/dist/http/plugins/openapi.js.map +1 -1
  65. package/dist/http/plugins/platform.d.ts +7 -0
  66. package/dist/http/plugins/platform.js +13 -0
  67. package/dist/http/plugins/platform.js.map +1 -0
  68. package/dist/http/routes/access.js +54 -36
  69. package/dist/http/routes/access.js.map +1 -1
  70. package/dist/http/routes/activities.js +45 -45
  71. package/dist/http/routes/activities.js.map +1 -1
  72. package/dist/http/routes/assets.js +30 -65
  73. package/dist/http/routes/assets.js.map +1 -1
  74. package/dist/http/routes/automations.js +37 -42
  75. package/dist/http/routes/automations.js.map +1 -1
  76. package/dist/http/routes/bundles.js +33 -36
  77. package/dist/http/routes/bundles.js.map +1 -1
  78. package/dist/http/routes/communities.js +93 -145
  79. package/dist/http/routes/communities.js.map +1 -1
  80. package/dist/http/routes/community-moderation.js +83 -137
  81. package/dist/http/routes/community-moderation.js.map +1 -1
  82. package/dist/http/routes/course-analytics.js +32 -15
  83. package/dist/http/routes/course-analytics.js.map +1 -1
  84. package/dist/http/routes/courses.js +25 -32
  85. package/dist/http/routes/courses.js.map +1 -1
  86. package/dist/http/routes/dashboard.js +9 -9
  87. package/dist/http/routes/dashboard.js.map +1 -1
  88. package/dist/http/routes/discussion.js +21 -39
  89. package/dist/http/routes/discussion.js.map +1 -1
  90. package/dist/http/routes/downloads.js +41 -47
  91. package/dist/http/routes/downloads.js.map +1 -1
  92. package/dist/http/routes/entitlements.js +19 -23
  93. package/dist/http/routes/entitlements.js.map +1 -1
  94. package/dist/http/routes/events.d.ts +7 -0
  95. package/dist/http/routes/events.js +333 -0
  96. package/dist/http/routes/events.js.map +1 -0
  97. package/dist/http/routes/integrations.js +40 -73
  98. package/dist/http/routes/integrations.js.map +1 -1
  99. package/dist/http/routes/member/assets.js +10 -13
  100. package/dist/http/routes/member/assets.js.map +1 -1
  101. package/dist/http/routes/member/chats.js +170 -47
  102. package/dist/http/routes/member/chats.js.map +1 -1
  103. package/dist/http/routes/member/comments.js +65 -26
  104. package/dist/http/routes/member/comments.js.map +1 -1
  105. package/dist/http/routes/member/community.js +45 -52
  106. package/dist/http/routes/member/community.js.map +1 -1
  107. package/dist/http/routes/member/courses.js +12 -19
  108. package/dist/http/routes/member/courses.js.map +1 -1
  109. package/dist/http/routes/member/devices.js +9 -10
  110. package/dist/http/routes/member/devices.js.map +1 -1
  111. package/dist/http/routes/member/downloads.js +6 -9
  112. package/dist/http/routes/member/downloads.js.map +1 -1
  113. package/dist/http/routes/member/events.d.ts +7 -0
  114. package/dist/http/routes/member/events.js +386 -0
  115. package/dist/http/routes/member/events.js.map +1 -0
  116. package/dist/http/routes/member/files.js +71 -46
  117. package/dist/http/routes/member/files.js.map +1 -1
  118. package/dist/http/routes/member/index.js +2 -0
  119. package/dist/http/routes/member/index.js.map +1 -1
  120. package/dist/http/routes/member/notification-settings.js +15 -23
  121. package/dist/http/routes/member/notification-settings.js.map +1 -1
  122. package/dist/http/routes/member/notifications.js +16 -16
  123. package/dist/http/routes/member/notifications.js.map +1 -1
  124. package/dist/http/routes/member/org.js +5 -15
  125. package/dist/http/routes/member/org.js.map +1 -1
  126. package/dist/http/routes/member/posts.js +67 -68
  127. package/dist/http/routes/member/posts.js.map +1 -1
  128. package/dist/http/routes/member/profile.js +15 -6
  129. package/dist/http/routes/member/profile.js.map +1 -1
  130. package/dist/http/routes/member/progress.js +7 -11
  131. package/dist/http/routes/member/progress.js.map +1 -1
  132. package/dist/http/routes/organizations.js +319 -135
  133. package/dist/http/routes/organizations.js.map +1 -1
  134. package/dist/http/routes/platform.d.ts +7 -0
  135. package/dist/http/routes/platform.js +61 -0
  136. package/dist/http/routes/platform.js.map +1 -0
  137. package/dist/http/routes/students.js +12 -75
  138. package/dist/http/routes/students.js.map +1 -1
  139. package/dist/http/routes/webhooks.js +30 -39
  140. package/dist/http/routes/webhooks.js.map +1 -1
  141. package/dist/http/routes.js +9 -0
  142. package/dist/http/routes.js.map +1 -1
  143. package/dist/http/schemas/access.d.ts +346 -64
  144. package/dist/http/schemas/access.js +39 -12
  145. package/dist/http/schemas/access.js.map +1 -1
  146. package/dist/http/schemas/assets.d.ts +19 -14
  147. package/dist/http/schemas/assets.js +6 -5
  148. package/dist/http/schemas/assets.js.map +1 -1
  149. package/dist/http/schemas/automations.d.ts +4 -4
  150. package/dist/http/schemas/chats.d.ts +211 -32
  151. package/dist/http/schemas/chats.js +13 -4
  152. package/dist/http/schemas/chats.js.map +1 -1
  153. package/dist/http/schemas/community.d.ts +91 -44
  154. package/dist/http/schemas/community.js +21 -8
  155. package/dist/http/schemas/community.js.map +1 -1
  156. package/dist/http/schemas/course-analytics.d.ts +51 -1
  157. package/dist/http/schemas/course-analytics.js +19 -1
  158. package/dist/http/schemas/course-analytics.js.map +1 -1
  159. package/dist/http/schemas/dashboard.d.ts +1 -0
  160. package/dist/http/schemas/dashboard.js +2 -1
  161. package/dist/http/schemas/dashboard.js.map +1 -1
  162. package/dist/http/schemas/discussion.d.ts +22 -72
  163. package/dist/http/schemas/discussion.js +2 -2
  164. package/dist/http/schemas/discussion.js.map +1 -1
  165. package/dist/http/schemas/entitlements.d.ts +9 -12
  166. package/dist/http/schemas/entitlements.js +11 -5
  167. package/dist/http/schemas/entitlements.js.map +1 -1
  168. package/dist/http/schemas/events.d.ts +527 -0
  169. package/dist/http/schemas/events.js +64 -0
  170. package/dist/http/schemas/events.js.map +1 -0
  171. package/dist/http/schemas/files.d.ts +15 -5
  172. package/dist/http/schemas/files.js +10 -2
  173. package/dist/http/schemas/files.js.map +1 -1
  174. package/dist/http/schemas/index.d.ts +11 -9
  175. package/dist/http/schemas/index.js +11 -9
  176. package/dist/http/schemas/integrations.d.ts +4 -4
  177. package/dist/http/schemas/invites.d.ts +3 -47
  178. package/dist/http/schemas/invites.js +4 -25
  179. package/dist/http/schemas/invites.js.map +1 -1
  180. package/dist/http/schemas/member.d.ts +94 -7
  181. package/dist/http/schemas/member.js +7 -3
  182. package/dist/http/schemas/member.js.map +1 -1
  183. package/dist/http/schemas/members.d.ts +333 -69
  184. package/dist/http/schemas/members.js +67 -25
  185. package/dist/http/schemas/members.js.map +1 -1
  186. package/dist/http/schemas/notifications.d.ts +57 -53
  187. package/dist/http/schemas/platform.d.ts +37 -0
  188. package/dist/http/schemas/platform.js +19 -0
  189. package/dist/http/schemas/platform.js.map +1 -0
  190. package/dist/http/schemas/posts.d.ts +55 -35
  191. package/dist/http/schemas/posts.js +4 -4
  192. package/dist/http/schemas/posts.js.map +1 -1
  193. package/dist/http/schemas/profile.d.ts +1 -1
  194. package/dist/http/schemas/shared.d.ts +1 -11
  195. package/dist/http/schemas/shared.js +1 -10
  196. package/dist/http/schemas/shared.js.map +1 -1
  197. package/dist/http/schemas/students.d.ts +25 -3
  198. package/dist/http/schemas/students.js +3 -1
  199. package/dist/http/schemas/students.js.map +1 -1
  200. package/dist/http/scope.d.ts +9 -13
  201. package/dist/http/scope.js +29 -40
  202. package/dist/http/scope.js.map +1 -1
  203. package/dist/http/server.js +9 -0
  204. package/dist/http/server.js.map +1 -1
  205. package/dist/http/shutdown.d.ts +11 -0
  206. package/dist/http/shutdown.js +40 -0
  207. package/dist/http/shutdown.js.map +1 -0
  208. package/dist/http/sse.d.ts +19 -0
  209. package/dist/http/sse.js +104 -0
  210. package/dist/http/sse.js.map +1 -0
  211. package/dist/http/test-caller.d.ts +13 -0
  212. package/dist/http/test-caller.js +14 -0
  213. package/dist/http/test-caller.js.map +1 -0
  214. package/dist/http/well-known.js +2 -2
  215. package/dist/http/well-known.js.map +1 -1
  216. package/dist/index.d.ts +4 -2
  217. package/dist/index.js +4 -2
  218. package/dist/index.js.map +1 -1
  219. package/dist/packages/server/package.js +1 -1
  220. package/package.json +7 -6
  221. package/dist/http/routes/member/actor.d.ts +0 -14
  222. package/dist/http/routes/member/actor.js +0 -11
  223. package/dist/http/routes/member/actor.js.map +0 -1
@@ -1,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"}
@@ -1,18 +1,17 @@
1
1
  import { refusals } from "../../schemas/shared.js";
2
+ import { resolveActor, resolveCaller } from "../../scope.js";
2
3
  import { Device, DeviceIdParam, DeviceList, PushConfigView, RegisterDevice } from "../../schemas/notifications.js";
3
4
  import "../../schemas/index.js";
4
- import { actorOf } from "./actor.js";
5
5
  import { z } from "zod";
6
6
  //#region src/http/routes/member/devices.ts
7
7
  async function memberDeviceRoutes(app, container) {
8
8
  const r = app.withTypeProvider();
9
9
  const tags = ["Notifications"];
10
- async function memberOf(orgId, userId) {
11
- const orgUser = await actorOf(container, orgId, userId);
10
+ async function memberOf(req) {
11
+ const caller = await resolveCaller(container, req);
12
12
  return {
13
- id: orgUser.id,
14
- role: orgUser.role,
15
- userId
13
+ ...caller.actor,
14
+ userId: caller.userId
16
15
  };
17
16
  }
18
17
  r.route({
@@ -30,7 +29,7 @@ async function memberDeviceRoutes(app, container) {
30
29
  }
31
30
  },
32
31
  handler: async (req) => {
33
- return container.devices.list(req.orgId, await memberOf(req.orgId, req.userId));
32
+ return container.devices.list(await memberOf(req));
34
33
  }
35
34
  });
36
35
  r.route({
@@ -49,7 +48,7 @@ async function memberDeviceRoutes(app, container) {
49
48
  }
50
49
  },
51
50
  handler: async (req) => {
52
- return container.devices.register(req.orgId, await memberOf(req.orgId, req.userId), req.body);
51
+ return container.devices.register(req.body, await memberOf(req));
53
52
  }
54
53
  });
55
54
  r.route({
@@ -68,7 +67,7 @@ async function memberDeviceRoutes(app, container) {
68
67
  }
69
68
  },
70
69
  handler: async (req, reply) => {
71
- await container.devices.remove(req.orgId, await memberOf(req.orgId, req.userId), req.params.deviceId);
70
+ await container.devices.remove(req.params.deviceId, await memberOf(req));
72
71
  return reply.code(204).send();
73
72
  }
74
73
  });
@@ -87,7 +86,7 @@ async function memberDeviceRoutes(app, container) {
87
86
  }
88
87
  },
89
88
  handler: async (req) => {
90
- await actorOf(container, req.orgId, req.userId);
89
+ await resolveActor(container, req);
91
90
  return container.devices.pushConfig();
92
91
  }
93
92
  });
@@ -1 +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 { actorOf } from './actor.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(orgId: string, userId: string) {\n const orgUser = await actorOf(container, orgId, userId);\n return { id: orgUser.id, role: orgUser.role, 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(req.orgId, await memberOf(req.orgId, req.userId));\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.orgId, await memberOf(req.orgId, req.userId), req.body);\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(\n req.orgId,\n await memberOf(req.orgId, req.userId),\n req.params.deviceId,\n );\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 actorOf(container, req.orgId, req.userId);\n return container.devices.pushConfig();\n },\n });\n}\n"],"mappings":";;;;;;AAcA,eAAsB,mBACpB,KACA,WACe;CACf,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,OAAO,CAAC,eAAe;CAE7B,eAAe,SAAS,OAAe,QAAgB;EACrD,MAAM,UAAU,MAAM,QAAQ,WAAW,OAAO,MAAM;EACtD,OAAO;GAAE,IAAI,QAAQ;GAAI,MAAM,QAAQ;GAAM;EAAO;CACtD;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,IAAI,OAAO,MAAM,SAAS,IAAI,OAAO,IAAI,MAAM,CAAC;EAChF;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,OAAO,MAAM,SAAS,IAAI,OAAO,IAAI,MAAM,GAAG,IAAI,IAAI;EAC9F;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,OACtB,IAAI,OACJ,MAAM,SAAS,IAAI,OAAO,IAAI,MAAM,GACpC,IAAI,OAAO,QACb;GACA,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,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9C,OAAO,UAAU,QAAQ,WAAW;EACtC;CACF,CAAC;AACH"}
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
@@ -0,0 +1,386 @@
1
+ import { PageQuery, refusals } from "../../schemas/shared.js";
2
+ import { resolveActor } from "../../scope.js";
3
+ import { AddEventAsset, AddEventAttendee, CreateEvent, Event, EventAssetParams, EventAttendeeParams, EventAttendeesPage, EventFile, EventIdParam, MemberEvent, MemberEventsPage, MemberEventsQuery, RenameEventAsset, ReorderEventAssets, UpdateEvent } from "../../schemas/events.js";
4
+ import "../../schemas/index.js";
5
+ import { z } from "zod";
6
+ //#region src/http/routes/member/events.ts
7
+ async function memberEventsRoutes(app, container) {
8
+ const r = app.withTypeProvider();
9
+ const content = container.content;
10
+ const member = container.reporting.member;
11
+ r.route({
12
+ method: "GET",
13
+ url: "/api/member/events",
14
+ preHandler: app.requireOrgSession,
15
+ schema: {
16
+ operationId: "listMemberEvents",
17
+ tags: ["Events"],
18
+ summary: "List the events you can see",
19
+ description: "For a member of the organization. The published `public` events, and the published and canceled events you attend; with `events:read`, every published and canceled event it covers. Each as the event read returns it.",
20
+ querystring: MemberEventsQuery,
21
+ response: {
22
+ 200: MemberEventsPage,
23
+ ...refusals(400, 403)
24
+ }
25
+ },
26
+ handler: async (req) => {
27
+ const actor = await resolveActor(container, req);
28
+ return member.listEvents(req.query, actor);
29
+ }
30
+ });
31
+ r.route({
32
+ method: "GET",
33
+ url: "/api/member/events/:eventId",
34
+ preHandler: app.requireOrgSession,
35
+ schema: {
36
+ operationId: "getMemberEvent",
37
+ tags: ["Events"],
38
+ summary: "Get an event, with what you can do with it",
39
+ description: "With `events:read` on the event or the org: the event in any status. As an attendee, directly or through a bundle: the published or canceled event, with the recording after the end when replay is on. Otherwise a published `public` event without its location, files and recording. 404 for anything else.",
40
+ params: EventIdParam,
41
+ response: {
42
+ 200: MemberEvent,
43
+ ...refusals(403, 404)
44
+ }
45
+ },
46
+ handler: async (req) => {
47
+ const actor = await resolveActor(container, req);
48
+ return member.getEvent(req.params.eventId, actor);
49
+ }
50
+ });
51
+ r.route({
52
+ method: "GET",
53
+ url: "/api/member/events/:eventId/calendar",
54
+ preHandler: app.requireOrgSession,
55
+ schema: {
56
+ operationId: "getEventCalendar",
57
+ tags: ["Events"],
58
+ summary: "Download an event as a calendar file",
59
+ description: "An iCalendar file with the title, the times and the event page. The location and the meeting link are included when the event read returns them. 404 when the event read does.",
60
+ params: EventIdParam,
61
+ response: {
62
+ 200: z.string().describe("`text/calendar`, one VEVENT"),
63
+ ...refusals(403, 404)
64
+ }
65
+ },
66
+ handler: async (req, reply) => {
67
+ const actor = await resolveActor(container, req);
68
+ const body = await member.eventCalendar(req.params.eventId, actor);
69
+ return reply.header("content-type", "text/calendar; charset=utf-8").header("content-disposition", `attachment; filename="${req.params.eventId}.ics"`).send(body);
70
+ }
71
+ });
72
+ r.route({
73
+ method: "GET",
74
+ url: "/api/member/events/:eventId/attendees",
75
+ preHandler: app.requireOrgSession,
76
+ schema: {
77
+ operationId: "listMemberEventAttendees",
78
+ tags: ["Events"],
79
+ summary: "List the attendees of an event you run",
80
+ description: `Requires \`events:read\`. On the event or the org. The people attending, directly or through a bundle, by grant date. \`source\` is \`bundle\` for someone who attends only through a bundle.`,
81
+ params: EventIdParam,
82
+ querystring: PageQuery,
83
+ response: {
84
+ 200: EventAttendeesPage,
85
+ ...refusals(400, 403, 404)
86
+ }
87
+ },
88
+ handler: async (req) => {
89
+ const actor = await resolveActor(container, req);
90
+ return member.eventAttendees(req.params.eventId, req.query, actor);
91
+ }
92
+ });
93
+ r.route({
94
+ method: "POST",
95
+ url: "/api/member/events",
96
+ preHandler: app.requireOrgSession,
97
+ schema: {
98
+ operationId: "createMemberEvent",
99
+ tags: ["Events"],
100
+ summary: "Create an event from the member app",
101
+ description: `Requires \`events:write\`. The event starts as a draft. 400 when it ends before it starts.`,
102
+ body: CreateEvent,
103
+ response: {
104
+ 201: Event,
105
+ ...refusals(400, 403)
106
+ }
107
+ },
108
+ handler: async (req, reply) => {
109
+ const actor = await resolveActor(container, req);
110
+ const event = await content.createEvent(req.body, actor);
111
+ return reply.code(201).send(event);
112
+ }
113
+ });
114
+ r.route({
115
+ method: "PATCH",
116
+ url: "/api/member/events/:eventId",
117
+ preHandler: app.requireOrgSession,
118
+ schema: {
119
+ operationId: "updateMemberEvent",
120
+ tags: ["Events"],
121
+ summary: "Update an event you run",
122
+ description: `Requires \`events:write\`. Changes only the fields sent; \`status\` publishes or unpublishes it. \`thumbnailAssetId\` must be a ready library image (409 \`conflict\` otherwise); null clears it. A file it does not already use must be uploaded for it, or the caller needs \`media:read\` (403 otherwise). 409 \`canceled\` when the event is canceled.`,
123
+ params: EventIdParam,
124
+ body: UpdateEvent,
125
+ response: {
126
+ 200: Event,
127
+ ...refusals(400, 403, 404, 409)
128
+ }
129
+ },
130
+ handler: async (req) => {
131
+ const actor = await resolveActor(container, req);
132
+ return content.updateEvent(req.params.eventId, req.body, actor);
133
+ }
134
+ });
135
+ r.route({
136
+ method: "POST",
137
+ url: "/api/member/events/:eventId/cancel",
138
+ preHandler: app.requireOrgSession,
139
+ schema: {
140
+ operationId: "cancelMemberEvent",
141
+ tags: ["Events"],
142
+ summary: "Cancel an event you run",
143
+ description: `Requires \`events:write\`. Canceling is final. 409 \`canceled\` when it is already canceled.`,
144
+ params: EventIdParam,
145
+ response: {
146
+ 200: Event,
147
+ ...refusals(403, 404, 409)
148
+ }
149
+ },
150
+ handler: async (req) => {
151
+ const actor = await resolveActor(container, req);
152
+ return content.cancelEvent(req.params.eventId, actor);
153
+ }
154
+ });
155
+ r.route({
156
+ method: "DELETE",
157
+ url: "/api/member/events/:eventId",
158
+ preHandler: app.requireOrgSession,
159
+ schema: {
160
+ operationId: "deleteMemberEvent",
161
+ tags: ["Events"],
162
+ summary: "Delete an event you can delete",
163
+ description: `Requires \`events:delete\`. Its attendees lose it; its library files survive.`,
164
+ params: EventIdParam,
165
+ response: {
166
+ 204: z.void(),
167
+ ...refusals(403, 404)
168
+ }
169
+ },
170
+ handler: async (req, reply) => {
171
+ const actor = await resolveActor(container, req);
172
+ await content.deleteEvent(req.params.eventId, actor);
173
+ return reply.code(204).send();
174
+ }
175
+ });
176
+ r.route({
177
+ method: "GET",
178
+ url: "/api/member/events/:eventId/assets",
179
+ preHandler: app.requireOrgSession,
180
+ schema: {
181
+ operationId: "listMemberEventAssets",
182
+ tags: ["Events"],
183
+ summary: "List the files of an event you run, in order",
184
+ description: "Requires `events:read`.",
185
+ params: EventIdParam,
186
+ response: {
187
+ 200: z.array(EventFile),
188
+ ...refusals(403, 404)
189
+ }
190
+ },
191
+ handler: async (req) => {
192
+ const actor = await resolveActor(container, req);
193
+ return content.listEventAssets(req.params.eventId, actor);
194
+ }
195
+ });
196
+ r.route({
197
+ method: "POST",
198
+ url: "/api/member/events/:eventId/assets",
199
+ preHandler: app.requireOrgSession,
200
+ schema: {
201
+ operationId: "addMemberEventAsset",
202
+ tags: ["Events"],
203
+ summary: "Attach a file to an event you run",
204
+ description: `Requires \`events:write\`. Added last. 409 when the file is not a ready library file or is already attached. A file not uploaded for the event needs \`media:read\` (403 otherwise). Returns the event's files.`,
205
+ params: EventIdParam,
206
+ body: AddEventAsset,
207
+ response: {
208
+ 200: z.array(EventFile),
209
+ ...refusals(403, 404, 409)
210
+ }
211
+ },
212
+ handler: async (req) => {
213
+ const actor = await resolveActor(container, req);
214
+ return content.addEventAsset(req.params.eventId, req.body, actor);
215
+ }
216
+ });
217
+ r.route({
218
+ method: "PUT",
219
+ url: "/api/member/events/:eventId/assets/order",
220
+ preHandler: app.requireOrgSession,
221
+ schema: {
222
+ operationId: "reorderMemberEventAssets",
223
+ tags: ["Events"],
224
+ summary: "Reorder the files of an event you run",
225
+ description: `Requires \`events:write\`. Send every attached file id exactly once (409 otherwise). Returns the event's files.`,
226
+ params: EventIdParam,
227
+ body: ReorderEventAssets,
228
+ response: {
229
+ 200: z.array(EventFile),
230
+ ...refusals(403, 404, 409)
231
+ }
232
+ },
233
+ handler: async (req) => {
234
+ const actor = await resolveActor(container, req);
235
+ return content.reorderEventAssets(req.params.eventId, req.body.assetIds, actor);
236
+ }
237
+ });
238
+ r.route({
239
+ method: "PATCH",
240
+ url: "/api/member/events/:eventId/assets/:assetId",
241
+ preHandler: app.requireOrgSession,
242
+ schema: {
243
+ operationId: "renameMemberEventAsset",
244
+ tags: ["Events"],
245
+ summary: "Set a file's display name within an event you run",
246
+ description: `Requires \`events:write\`. Null falls back to the filename. Returns the event's files.`,
247
+ params: EventAssetParams,
248
+ body: RenameEventAsset,
249
+ response: {
250
+ 200: z.array(EventFile),
251
+ ...refusals(403, 404)
252
+ }
253
+ },
254
+ handler: async (req) => {
255
+ const actor = await resolveActor(container, req);
256
+ return content.renameEventAsset(req.params.eventId, req.params.assetId, req.body.displayName, actor);
257
+ }
258
+ });
259
+ r.route({
260
+ method: "DELETE",
261
+ url: "/api/member/events/:eventId/assets/:assetId",
262
+ preHandler: app.requireOrgSession,
263
+ schema: {
264
+ operationId: "removeMemberEventAsset",
265
+ tags: ["Events"],
266
+ summary: "Remove a file from an event you run",
267
+ description: `Requires \`events:write\`. The library file itself survives. Returns the event's files.`,
268
+ params: EventAssetParams,
269
+ response: {
270
+ 200: z.array(EventFile),
271
+ ...refusals(403, 404)
272
+ }
273
+ },
274
+ handler: async (req) => {
275
+ const actor = await resolveActor(container, req);
276
+ return content.removeEventAsset(req.params.eventId, req.params.assetId, actor);
277
+ }
278
+ });
279
+ r.route({
280
+ method: "POST",
281
+ url: "/api/member/events/:eventId/register",
282
+ preHandler: app.requireOrgSession,
283
+ schema: {
284
+ operationId: "registerForEvent",
285
+ tags: ["Events"],
286
+ summary: "Register for an event",
287
+ description: "Any active member of the organization registers themselves for a published `public` event that has not started, with source `self`. 403 when the event is `private`; 404 when it does not exist or is not published; 409 `already_registered`, `started` or `full`.",
288
+ params: EventIdParam,
289
+ response: {
290
+ 204: z.void(),
291
+ ...refusals(403, 404, 409)
292
+ }
293
+ },
294
+ handler: async (req, reply) => {
295
+ const actor = await resolveActor(container, req);
296
+ await container.entitlements.grant({
297
+ orgUserId: actor.id,
298
+ contentId: req.params.eventId,
299
+ contentType: "event",
300
+ expiresAt: null
301
+ }, actor);
302
+ return reply.code(204).send();
303
+ }
304
+ });
305
+ r.route({
306
+ method: "POST",
307
+ url: "/api/member/events/:eventId/unregister",
308
+ preHandler: app.requireOrgSession,
309
+ schema: {
310
+ operationId: "cancelEventRegistration",
311
+ tags: ["Events"],
312
+ summary: "Cancel your registration for an event",
313
+ description: "Any active member of the organization revokes their own direct grant to the event, which frees the place. 404 when the caller has no direct active grant.",
314
+ params: EventIdParam,
315
+ response: {
316
+ 204: z.void(),
317
+ ...refusals(403, 404)
318
+ }
319
+ },
320
+ handler: async (req, reply) => {
321
+ const actor = await resolveActor(container, req);
322
+ await container.entitlements.revoke({
323
+ orgUserId: actor.id,
324
+ contentId: req.params.eventId,
325
+ contentType: "event"
326
+ }, actor);
327
+ return reply.code(204).send();
328
+ }
329
+ });
330
+ r.route({
331
+ method: "POST",
332
+ url: "/api/member/events/:eventId/attendees",
333
+ preHandler: app.requireOrgSession,
334
+ schema: {
335
+ operationId: "addMemberEventAttendee",
336
+ tags: ["Events"],
337
+ summary: "Add an attendee to an event you run",
338
+ description: "Requires `access:write`, or `events:write` on the event or the org. Grants the event to the person, with source `manual`. Adding someone who already attends changes nothing. 404 when the event or the person does not exist; 409 `not_published`, `ended` or `full`.",
339
+ params: EventIdParam,
340
+ body: AddEventAttendee,
341
+ response: {
342
+ 204: z.void(),
343
+ ...refusals(400, 403, 404, 409)
344
+ }
345
+ },
346
+ handler: async (req, reply) => {
347
+ const actor = await resolveActor(container, req);
348
+ await container.entitlements.grant({
349
+ orgUserId: req.body.orgUserId,
350
+ contentId: req.params.eventId,
351
+ contentType: "event",
352
+ expiresAt: null
353
+ }, actor);
354
+ return reply.code(204).send();
355
+ }
356
+ });
357
+ r.route({
358
+ method: "DELETE",
359
+ url: "/api/member/events/:eventId/attendees/:orgUserId",
360
+ preHandler: app.requireOrgSession,
361
+ schema: {
362
+ operationId: "removeMemberEventAttendee",
363
+ tags: ["Events"],
364
+ summary: "Remove an attendee from an event you run",
365
+ description: "Requires `access:write`, or `events:write` on the event or the org. Revokes the person's direct grant to the event. 404 when they have no direct active grant; someone who attends only through a bundle has none.",
366
+ params: EventAttendeeParams,
367
+ response: {
368
+ 204: z.void(),
369
+ ...refusals(403, 404)
370
+ }
371
+ },
372
+ handler: async (req, reply) => {
373
+ const actor = await resolveActor(container, req);
374
+ await container.entitlements.revoke({
375
+ orgUserId: req.params.orgUserId,
376
+ contentId: req.params.eventId,
377
+ contentType: "event"
378
+ }, actor);
379
+ return reply.code(204).send();
380
+ }
381
+ });
382
+ }
383
+ //#endregion
384
+ export { memberEventsRoutes };
385
+
386
+ //# sourceMappingURL=events.js.map