@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,9 +1,8 @@
1
1
  import { refusals } from "../schemas/shared.js";
2
- import { resolveScope } from "../scope.js";
2
+ import { resolveActor } from "../scope.js";
3
3
  import { AddBundleItem, Bundle, BundleIdParam, BundleItem, BundleItemParams, BundlesPage, BundlesQuery, CreateBundle, UpdateBundle } from "../schemas/bundles.js";
4
4
  import "../schemas/index.js";
5
5
  import { z } from "zod";
6
- import { NotFoundError } from "@headless-lms/core/shared/errors";
7
6
  //#region src/http/routes/bundles.ts
8
7
  async function bundlesRoutes(app, container) {
9
8
  const r = app.withTypeProvider();
@@ -15,8 +14,8 @@ async function bundlesRoutes(app, container) {
15
14
  schema: {
16
15
  operationId: "listBundles",
17
16
  tags: ["Content"],
18
- summary: "List bundles (staff)",
19
- description: "Staff only (owner, admin, instructor).",
17
+ summary: "List bundles",
18
+ description: "Requires `bundles:read`.",
20
19
  querystring: BundlesQuery,
21
20
  response: {
22
21
  200: BundlesPage,
@@ -24,8 +23,8 @@ async function bundlesRoutes(app, container) {
24
23
  }
25
24
  },
26
25
  handler: async (req) => {
27
- const scope = await resolveScope(container, req);
28
- return content.listBundles(scope.orgId, req.query);
26
+ const actor = await resolveActor(container, req);
27
+ return content.listBundles(req.query, actor);
29
28
  }
30
29
  });
31
30
  r.route({
@@ -35,8 +34,8 @@ async function bundlesRoutes(app, container) {
35
34
  schema: {
36
35
  operationId: "getBundle",
37
36
  tags: ["Content"],
38
- summary: "Get a bundle (staff)",
39
- description: "Staff only (owner, admin, instructor).",
37
+ summary: "Get a bundle",
38
+ description: "Requires `bundles:read`.",
40
39
  params: BundleIdParam,
41
40
  response: {
42
41
  200: Bundle,
@@ -44,10 +43,8 @@ async function bundlesRoutes(app, container) {
44
43
  }
45
44
  },
46
45
  handler: async (req) => {
47
- const scope = await resolveScope(container, req);
48
- const bundle = await content.getBundle(scope.orgId, req.params.bundleId);
49
- if (!bundle) throw new NotFoundError("Bundle", req.params.bundleId);
50
- return bundle;
46
+ const actor = await resolveActor(container, req);
47
+ return content.getBundle(req.params.bundleId, actor);
51
48
  }
52
49
  });
53
50
  r.route({
@@ -57,8 +54,8 @@ async function bundlesRoutes(app, container) {
57
54
  schema: {
58
55
  operationId: "createBundle",
59
56
  tags: ["Content"],
60
- summary: "Create a bundle, optionally with initial content (staff)",
61
- description: "Staff only (owner, admin, instructor). 404 when an initial content id is unknown.",
57
+ summary: "Create a bundle, optionally with initial content",
58
+ description: `Requires \`bundles:write\`. 404 when an initial content id is unknown.`,
62
59
  body: CreateBundle,
63
60
  response: {
64
61
  201: Bundle,
@@ -66,8 +63,8 @@ async function bundlesRoutes(app, container) {
66
63
  }
67
64
  },
68
65
  handler: async (req, reply) => {
69
- const scope = await resolveScope(container, req);
70
- const bundle = await content.createBundle(scope.orgId, req.body);
66
+ const actor = await resolveActor(container, req);
67
+ const bundle = await content.createBundle(req.body, actor);
71
68
  return reply.code(201).send(bundle);
72
69
  }
73
70
  });
@@ -78,8 +75,8 @@ async function bundlesRoutes(app, container) {
78
75
  schema: {
79
76
  operationId: "updateBundle",
80
77
  tags: ["Content"],
81
- summary: "Rename a bundle (staff)",
82
- description: "Staff only (owner, admin, instructor).",
78
+ summary: "Rename a bundle",
79
+ description: "Requires `bundles:write`.",
83
80
  params: BundleIdParam,
84
81
  body: UpdateBundle,
85
82
  response: {
@@ -88,8 +85,8 @@ async function bundlesRoutes(app, container) {
88
85
  }
89
86
  },
90
87
  handler: async (req) => {
91
- const scope = await resolveScope(container, req);
92
- return content.updateBundle(scope.orgId, req.params.bundleId, req.body);
88
+ const actor = await resolveActor(container, req);
89
+ return content.updateBundle(req.params.bundleId, req.body, actor);
93
90
  }
94
91
  });
95
92
  r.route({
@@ -99,8 +96,8 @@ async function bundlesRoutes(app, container) {
99
96
  schema: {
100
97
  operationId: "deleteBundle",
101
98
  tags: ["Content"],
102
- summary: "Delete a bundle (staff)",
103
- description: "Staff only (owner, admin, instructor). Its content survives; access granted through the bundle ends.",
99
+ summary: "Delete a bundle",
100
+ description: `Requires \`bundles:delete\`. Its content survives; access granted through the bundle ends.`,
104
101
  params: BundleIdParam,
105
102
  response: {
106
103
  204: z.void(),
@@ -108,8 +105,8 @@ async function bundlesRoutes(app, container) {
108
105
  }
109
106
  },
110
107
  handler: async (req, reply) => {
111
- const scope = await resolveScope(container, req);
112
- await content.deleteBundle(scope.orgId, req.params.bundleId);
108
+ const actor = await resolveActor(container, req);
109
+ await content.deleteBundle(req.params.bundleId, actor);
113
110
  return reply.code(204).send();
114
111
  }
115
112
  });
@@ -120,8 +117,8 @@ async function bundlesRoutes(app, container) {
120
117
  schema: {
121
118
  operationId: "listBundleItems",
122
119
  tags: ["Content"],
123
- summary: "List a bundle's content (staff)",
124
- description: "Staff only (owner, admin, instructor).",
120
+ summary: "List a bundle's content",
121
+ description: "Requires `bundles:read`.",
125
122
  params: BundleIdParam,
126
123
  response: {
127
124
  200: z.array(BundleItem),
@@ -129,8 +126,8 @@ async function bundlesRoutes(app, container) {
129
126
  }
130
127
  },
131
128
  handler: async (req) => {
132
- const scope = await resolveScope(container, req);
133
- return content.listBundleItems(scope.orgId, req.params.bundleId);
129
+ const actor = await resolveActor(container, req);
130
+ return content.listBundleItems(req.params.bundleId, actor);
134
131
  }
135
132
  });
136
133
  r.route({
@@ -140,8 +137,8 @@ async function bundlesRoutes(app, container) {
140
137
  schema: {
141
138
  operationId: "addBundleItem",
142
139
  tags: ["Content"],
143
- summary: "Add content to a bundle (staff)",
144
- description: "Staff only (owner, admin, instructor). Everyone who holds the bundle can open the content from then on. Returns the bundle's content.",
140
+ summary: "Add content to a bundle",
141
+ description: `Requires \`bundles:write\`. Everyone who holds the bundle can open the content from then on. Returns the bundle's content.`,
145
142
  params: BundleIdParam,
146
143
  body: AddBundleItem,
147
144
  response: {
@@ -150,8 +147,8 @@ async function bundlesRoutes(app, container) {
150
147
  }
151
148
  },
152
149
  handler: async (req) => {
153
- const scope = await resolveScope(container, req);
154
- return content.addBundleItem(scope.orgId, req.params.bundleId, req.body.contentId);
150
+ const actor = await resolveActor(container, req);
151
+ return content.addBundleItem(req.params.bundleId, req.body.contentId, actor);
155
152
  }
156
153
  });
157
154
  r.route({
@@ -161,8 +158,8 @@ async function bundlesRoutes(app, container) {
161
158
  schema: {
162
159
  operationId: "removeBundleItem",
163
160
  tags: ["Content"],
164
- summary: "Remove content from a bundle (staff)",
165
- description: "Staff only (owner, admin, instructor). The content survives; access to it through the bundle ends. Returns the bundle's content.",
161
+ summary: "Remove content from a bundle",
162
+ description: `Requires \`bundles:write\`. The content survives; access to it through the bundle ends. Returns the bundle's content.`,
166
163
  params: BundleItemParams,
167
164
  response: {
168
165
  200: z.array(BundleItem),
@@ -170,8 +167,8 @@ async function bundlesRoutes(app, container) {
170
167
  }
171
168
  },
172
169
  handler: async (req) => {
173
- const scope = await resolveScope(container, req);
174
- return content.removeBundleItem(scope.orgId, req.params.bundleId, req.params.contentId);
170
+ const actor = await resolveActor(container, req);
171
+ return content.removeBundleItem(req.params.bundleId, req.params.contentId, actor);
175
172
  }
176
173
  });
177
174
  }
@@ -1 +1 @@
1
- {"version":3,"file":"bundles.js","names":[],"sources":["../../../src/http/routes/bundles.ts"],"sourcesContent":["// HTTP routes for bundles — named groupings of content items. Request +\n// response are validated against the shared contract schemas by the Zod type\n// provider, and @fastify/swagger reads the same schemas to build the OpenAPI\n// spec.\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n AddBundleItem,\n Bundle,\n BundleIdParam,\n BundleItem,\n BundleItemParams,\n BundlesPage,\n BundlesQuery,\n CreateBundle,\n UpdateBundle,\n refusals,\n} from '../schemas/index.js';\nimport { z } from 'zod';\nimport type { Container } from '../../app/container.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport { resolveScope } from '../scope.js';\n\nexport async function bundlesRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const content = container.content;\n\n r.route({\n method: 'GET',\n url: '/api/bundles',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listBundles',\n tags: ['Content'],\n summary: 'List bundles (staff)',\n description: 'Staff only (owner, admin, instructor).',\n querystring: BundlesQuery,\n response: { 200: BundlesPage, ...refusals(403) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.listBundles(scope.orgId, req.query);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/bundles/:bundleId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getBundle',\n tags: ['Content'],\n summary: 'Get a bundle (staff)',\n description: 'Staff only (owner, admin, instructor).',\n params: BundleIdParam,\n response: { 200: Bundle, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const bundle = await content.getBundle(scope.orgId, req.params.bundleId);\n if (!bundle) {\n throw new NotFoundError('Bundle', req.params.bundleId);\n }\n return bundle;\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/bundles',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createBundle',\n tags: ['Content'],\n summary: 'Create a bundle, optionally with initial content (staff)',\n description:\n 'Staff only (owner, admin, instructor). 404 when an initial content id is unknown.',\n body: CreateBundle,\n response: { 201: Bundle, ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const bundle = await content.createBundle(scope.orgId, req.body);\n return reply.code(201).send(bundle);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/bundles/:bundleId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateBundle',\n tags: ['Content'],\n summary: 'Rename a bundle (staff)',\n description: 'Staff only (owner, admin, instructor).',\n params: BundleIdParam,\n body: UpdateBundle,\n response: { 200: Bundle, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.updateBundle(scope.orgId, req.params.bundleId, req.body);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/bundles/:bundleId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteBundle',\n tags: ['Content'],\n summary: 'Delete a bundle (staff)',\n description:\n 'Staff only (owner, admin, instructor). Its content survives; access granted through the bundle ends.',\n params: BundleIdParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n await content.deleteBundle(scope.orgId, req.params.bundleId);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/bundles/:bundleId/items',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listBundleItems',\n tags: ['Content'],\n summary: \"List a bundle's content (staff)\",\n description: 'Staff only (owner, admin, instructor).',\n params: BundleIdParam,\n response: { 200: z.array(BundleItem), ...refusals(403) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.listBundleItems(scope.orgId, req.params.bundleId);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/bundles/:bundleId/items',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'addBundleItem',\n tags: ['Content'],\n summary: 'Add content to a bundle (staff)',\n description:\n \"Staff only (owner, admin, instructor). Everyone who holds the bundle can open the content from then on. Returns the bundle's content.\",\n params: BundleIdParam,\n body: AddBundleItem,\n response: { 200: z.array(BundleItem), ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.addBundleItem(scope.orgId, req.params.bundleId, req.body.contentId);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/bundles/:bundleId/items/:contentId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'removeBundleItem',\n tags: ['Content'],\n summary: 'Remove content from a bundle (staff)',\n description:\n \"Staff only (owner, admin, instructor). The content survives; access to it through the bundle ends. Returns the bundle's content.\",\n params: BundleItemParams,\n response: { 200: z.array(BundleItem), ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.removeBundleItem(scope.orgId, req.params.bundleId, req.params.contentId);\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"bundles.js","names":[],"sources":["../../../src/http/routes/bundles.ts"],"sourcesContent":["// HTTP routes for bundles — named groupings of content items. Request +\n// response are validated against the shared contract schemas by the Zod type\n// provider, and @fastify/swagger reads the same schemas to build the OpenAPI\n// spec.\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n AddBundleItem,\n Bundle,\n BundleIdParam,\n BundleItem,\n BundleItemParams,\n BundlesPage,\n BundlesQuery,\n CreateBundle,\n UpdateBundle,\n refusals,\n} from '../schemas/index.js';\nimport { z } from 'zod';\nimport type { Container } from '../../app/container.js';\nimport { resolveActor } from '../scope.js';\n\nexport async function bundlesRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const content = container.content;\n\n r.route({\n method: 'GET',\n url: '/api/bundles',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listBundles',\n tags: ['Content'],\n summary: 'List bundles',\n description: 'Requires `bundles:read`.',\n querystring: BundlesQuery,\n response: { 200: BundlesPage, ...refusals(403) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.listBundles(req.query, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/bundles/:bundleId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getBundle',\n tags: ['Content'],\n summary: 'Get a bundle',\n description: 'Requires `bundles:read`.',\n params: BundleIdParam,\n response: { 200: Bundle, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.getBundle(req.params.bundleId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/bundles',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createBundle',\n tags: ['Content'],\n summary: 'Create a bundle, optionally with initial content',\n description: `Requires \\`bundles:write\\`. 404 when an initial content id is unknown.`,\n body: CreateBundle,\n response: { 201: Bundle, ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n const bundle = await content.createBundle(req.body, actor);\n return reply.code(201).send(bundle);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/bundles/:bundleId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateBundle',\n tags: ['Content'],\n summary: 'Rename a bundle',\n description: 'Requires `bundles:write`.',\n params: BundleIdParam,\n body: UpdateBundle,\n response: { 200: Bundle, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.updateBundle(req.params.bundleId, req.body, actor);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/bundles/:bundleId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteBundle',\n tags: ['Content'],\n summary: 'Delete a bundle',\n description: `Requires \\`bundles:delete\\`. Its content survives; access granted through the bundle ends.`,\n params: BundleIdParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n await content.deleteBundle(req.params.bundleId, actor);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/bundles/:bundleId/items',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listBundleItems',\n tags: ['Content'],\n summary: \"List a bundle's content\",\n description: 'Requires `bundles:read`.',\n params: BundleIdParam,\n response: { 200: z.array(BundleItem), ...refusals(403) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.listBundleItems(req.params.bundleId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/bundles/:bundleId/items',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'addBundleItem',\n tags: ['Content'],\n summary: 'Add content to a bundle',\n description: `Requires \\`bundles:write\\`. Everyone who holds the bundle can open the content from then on. Returns the bundle's content.`,\n params: BundleIdParam,\n body: AddBundleItem,\n response: { 200: z.array(BundleItem), ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.addBundleItem(req.params.bundleId, req.body.contentId, actor);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/bundles/:bundleId/items/:contentId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'removeBundleItem',\n tags: ['Content'],\n summary: 'Remove content from a bundle',\n description: `Requires \\`bundles:write\\`. The content survives; access to it through the bundle ends. Returns the bundle's content.`,\n params: BundleItemParams,\n response: { 200: z.array(BundleItem), ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.removeBundleItem(req.params.bundleId, req.params.contentId, actor);\n },\n });\n}\n"],"mappings":"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"}