@headless-lms/server 0.1.0 → 0.3.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 (351) hide show
  1. package/README.md +1 -1
  2. package/dist/_virtual/_rolldown/runtime.js +5 -0
  3. package/dist/app/community-mail.d.ts +26 -0
  4. package/dist/app/community-mail.js +96 -0
  5. package/dist/app/community-mail.js.map +1 -0
  6. package/dist/app/container.d.ts +22 -14
  7. package/dist/app/container.js +186 -62
  8. package/dist/app/container.js.map +1 -1
  9. package/dist/app/mcp-endpoints.d.ts +13 -0
  10. package/dist/app/mcp-endpoints.js +25 -0
  11. package/dist/app/mcp-endpoints.js.map +1 -0
  12. package/dist/http/config.d.ts +2 -1
  13. package/dist/http/errors.d.ts +14 -0
  14. package/dist/http/errors.js +147 -0
  15. package/dist/http/errors.js.map +1 -0
  16. package/dist/http/mcp/catalogue.d.ts +37 -0
  17. package/dist/http/mcp/catalogue.js +69 -0
  18. package/dist/http/mcp/catalogue.js.map +1 -0
  19. package/dist/http/mcp/challenge.d.ts +7 -0
  20. package/dist/http/mcp/challenge.js +35 -0
  21. package/dist/http/mcp/challenge.js.map +1 -0
  22. package/dist/http/mcp/input-schema.d.ts +7 -0
  23. package/dist/http/mcp/input-schema.js +28 -0
  24. package/dist/http/mcp/input-schema.js.map +1 -0
  25. package/dist/http/mcp/route.d.ts +8 -0
  26. package/dist/http/mcp/route.js +75 -0
  27. package/dist/http/mcp/route.js.map +1 -0
  28. package/dist/http/mcp/server.d.ts +8 -0
  29. package/dist/http/mcp/server.js +31 -0
  30. package/dist/http/mcp/server.js.map +1 -0
  31. package/dist/http/mcp/test-support.d.ts +55 -0
  32. package/dist/http/mcp/test-support.js +151 -0
  33. package/dist/http/mcp/test-support.js.map +1 -0
  34. package/dist/http/mcp/tool.d.ts +48 -0
  35. package/dist/http/mcp/tool.js +58 -0
  36. package/dist/http/mcp/tool.js.map +1 -0
  37. package/dist/http/mcp/tools/automations.d.ts +6 -0
  38. package/dist/http/mcp/tools/automations.js +120 -0
  39. package/dist/http/mcp/tools/automations.js.map +1 -0
  40. package/dist/http/mcp/tools/catalog.d.ts +6 -0
  41. package/dist/http/mcp/tools/catalog.js +200 -0
  42. package/dist/http/mcp/tools/catalog.js.map +1 -0
  43. package/dist/http/mcp/tools/comments.d.ts +6 -0
  44. package/dist/http/mcp/tools/comments.js +70 -0
  45. package/dist/http/mcp/tools/comments.js.map +1 -0
  46. package/dist/http/mcp/tools/communities.d.ts +6 -0
  47. package/dist/http/mcp/tools/communities.js +175 -0
  48. package/dist/http/mcp/tools/communities.js.map +1 -0
  49. package/dist/http/mcp/tools/community-members.d.ts +6 -0
  50. package/dist/http/mcp/tools/community-members.js +89 -0
  51. package/dist/http/mcp/tools/community-members.js.map +1 -0
  52. package/dist/http/mcp/tools/community-moderation.d.ts +6 -0
  53. package/dist/http/mcp/tools/community-moderation.js +144 -0
  54. package/dist/http/mcp/tools/community-moderation.js.map +1 -0
  55. package/dist/http/mcp/tools/courses.d.ts +6 -0
  56. package/dist/http/mcp/tools/courses.js +121 -0
  57. package/dist/http/mcp/tools/courses.js.map +1 -0
  58. package/dist/http/mcp/tools/index.d.ts +6 -0
  59. package/dist/http/mcp/tools/index.js +23 -0
  60. package/dist/http/mcp/tools/index.js.map +1 -0
  61. package/dist/http/mcp/tools/people.d.ts +6 -0
  62. package/dist/http/mcp/tools/people.js +145 -0
  63. package/dist/http/mcp/tools/people.js.map +1 -0
  64. package/dist/http/plugins/auth.js +13 -5
  65. package/dist/http/plugins/auth.js.map +1 -1
  66. package/dist/http/plugins/error-handler.d.ts +3 -2
  67. package/dist/http/plugins/error-handler.js +52 -57
  68. package/dist/http/plugins/error-handler.js.map +1 -1
  69. package/dist/http/plugins/openapi.js +70 -20
  70. package/dist/http/plugins/openapi.js.map +1 -1
  71. package/dist/http/routes/access.d.ts +7 -0
  72. package/dist/http/routes/access.js +140 -0
  73. package/dist/http/routes/access.js.map +1 -0
  74. package/dist/http/routes/activities.js +97 -20
  75. package/dist/http/routes/activities.js.map +1 -1
  76. package/dist/http/routes/assets.js +60 -51
  77. package/dist/http/routes/assets.js.map +1 -1
  78. package/dist/http/routes/automations.js +64 -20
  79. package/dist/http/routes/automations.js.map +1 -1
  80. package/dist/http/routes/bundles.d.ts +7 -0
  81. package/dist/http/routes/bundles.js +181 -0
  82. package/dist/http/routes/bundles.js.map +1 -0
  83. package/dist/http/routes/communities.d.ts +7 -0
  84. package/dist/http/routes/communities.js +462 -0
  85. package/dist/http/routes/communities.js.map +1 -0
  86. package/dist/http/routes/community-moderation.d.ts +7 -0
  87. package/dist/http/routes/community-moderation.js +425 -0
  88. package/dist/http/routes/community-moderation.js.map +1 -0
  89. package/dist/http/routes/course-analytics.d.ts +7 -0
  90. package/dist/http/routes/course-analytics.js +60 -0
  91. package/dist/http/routes/course-analytics.js.map +1 -0
  92. package/dist/http/routes/courses.js +38 -23
  93. package/dist/http/routes/courses.js.map +1 -1
  94. package/dist/http/routes/dashboard.js +17 -8
  95. package/dist/http/routes/dashboard.js.map +1 -1
  96. package/dist/http/routes/discussion.js +35 -27
  97. package/dist/http/routes/discussion.js.map +1 -1
  98. package/dist/http/routes/downloads.js +58 -40
  99. package/dist/http/routes/downloads.js.map +1 -1
  100. package/dist/http/routes/entitlements.js +16 -7
  101. package/dist/http/routes/entitlements.js.map +1 -1
  102. package/dist/http/routes/integrations.js +38 -41
  103. package/dist/http/routes/integrations.js.map +1 -1
  104. package/dist/http/routes/member/actor.d.ts +14 -0
  105. package/dist/http/routes/member/actor.js +11 -0
  106. package/dist/http/routes/member/actor.js.map +1 -0
  107. package/dist/http/routes/member/assets.d.ts +7 -0
  108. package/dist/http/routes/member/assets.js +79 -0
  109. package/dist/http/routes/member/assets.js.map +1 -0
  110. package/dist/http/routes/member/chats.d.ts +7 -0
  111. package/dist/http/routes/member/chats.js +456 -0
  112. package/dist/http/routes/member/chats.js.map +1 -0
  113. package/dist/http/routes/member/comments.d.ts +7 -0
  114. package/dist/http/routes/member/comments.js +187 -0
  115. package/dist/http/routes/member/comments.js.map +1 -0
  116. package/dist/http/routes/member/community.d.ts +7 -0
  117. package/dist/http/routes/member/community.js +288 -0
  118. package/dist/http/routes/member/community.js.map +1 -0
  119. package/dist/http/routes/member/courses.d.ts +7 -0
  120. package/dist/http/routes/member/courses.js +101 -0
  121. package/dist/http/routes/member/courses.js.map +1 -0
  122. package/dist/http/routes/member/downloads.d.ts +7 -0
  123. package/dist/http/routes/member/downloads.js +56 -0
  124. package/dist/http/routes/member/downloads.js.map +1 -0
  125. package/dist/http/routes/member/files.d.ts +7 -0
  126. package/dist/http/routes/member/files.js +184 -0
  127. package/dist/http/routes/member/files.js.map +1 -0
  128. package/dist/http/routes/member/index.d.ts +7 -0
  129. package/dist/http/routes/member/index.js +31 -0
  130. package/dist/http/routes/member/index.js.map +1 -0
  131. package/dist/http/routes/member/notifications.d.ts +7 -0
  132. package/dist/http/routes/member/notifications.js +101 -0
  133. package/dist/http/routes/member/notifications.js.map +1 -0
  134. package/dist/http/routes/member/org.d.ts +7 -0
  135. package/dist/http/routes/member/org.js +56 -0
  136. package/dist/http/routes/member/org.js.map +1 -0
  137. package/dist/http/routes/member/posts.d.ts +7 -0
  138. package/dist/http/routes/member/posts.js +612 -0
  139. package/dist/http/routes/member/posts.js.map +1 -0
  140. package/dist/http/routes/member/profile.d.ts +7 -0
  141. package/dist/http/routes/member/profile.js +48 -0
  142. package/dist/http/routes/member/profile.js.map +1 -0
  143. package/dist/http/routes/member/progress.d.ts +7 -0
  144. package/dist/http/routes/member/progress.js +60 -0
  145. package/dist/http/routes/member/progress.js.map +1 -0
  146. package/dist/http/routes/organizations.js +49 -24
  147. package/dist/http/routes/organizations.js.map +1 -1
  148. package/dist/http/routes/students.js +43 -37
  149. package/dist/http/routes/students.js.map +1 -1
  150. package/dist/http/routes/webhooks.d.ts +7 -0
  151. package/dist/http/routes/webhooks.js +167 -0
  152. package/dist/http/routes/webhooks.js.map +1 -0
  153. package/dist/http/routes.js +14 -2
  154. package/dist/http/routes.js.map +1 -1
  155. package/dist/http/schemas/access.d.ts +146 -0
  156. package/dist/http/schemas/access.js +36 -0
  157. package/dist/http/schemas/access.js.map +1 -0
  158. package/dist/http/schemas/activities.d.ts +36 -1
  159. package/dist/http/schemas/activities.js +9 -4
  160. package/dist/http/schemas/activities.js.map +1 -1
  161. package/dist/http/schemas/assets.d.ts +76 -73
  162. package/dist/http/schemas/assets.js +23 -13
  163. package/dist/http/schemas/assets.js.map +1 -1
  164. package/dist/http/schemas/automations.d.ts +85 -8
  165. package/dist/http/schemas/automations.js +13 -2
  166. package/dist/http/schemas/automations.js.map +1 -1
  167. package/dist/http/schemas/bundles.d.ts +63 -0
  168. package/dist/http/schemas/bundles.js +23 -0
  169. package/dist/http/schemas/bundles.js.map +1 -0
  170. package/dist/http/schemas/chats.d.ts +386 -0
  171. package/dist/http/schemas/chats.js +58 -0
  172. package/dist/http/schemas/chats.js.map +1 -0
  173. package/dist/http/schemas/community.d.ts +537 -0
  174. package/dist/http/schemas/community.js +129 -0
  175. package/dist/http/schemas/community.js.map +1 -0
  176. package/dist/http/schemas/content.d.ts +22 -13
  177. package/dist/http/schemas/content.js +11 -9
  178. package/dist/http/schemas/content.js.map +1 -1
  179. package/dist/http/schemas/course-analytics.d.ts +29 -0
  180. package/dist/http/schemas/course-analytics.js +21 -0
  181. package/dist/http/schemas/course-analytics.js.map +1 -0
  182. package/dist/http/schemas/dashboard.js.map +1 -1
  183. package/dist/http/schemas/discussion.d.ts +85 -23
  184. package/dist/http/schemas/discussion.js +12 -11
  185. package/dist/http/schemas/discussion.js.map +1 -1
  186. package/dist/http/schemas/downloads.d.ts +13 -5
  187. package/dist/http/schemas/downloads.js +14 -11
  188. package/dist/http/schemas/downloads.js.map +1 -1
  189. package/dist/http/schemas/entitlements.d.ts +14 -6
  190. package/dist/http/schemas/entitlements.js +8 -6
  191. package/dist/http/schemas/entitlements.js.map +1 -1
  192. package/dist/http/schemas/files.d.ts +45 -0
  193. package/dist/http/schemas/files.js +25 -0
  194. package/dist/http/schemas/files.js.map +1 -0
  195. package/dist/http/schemas/index.d.ts +19 -10
  196. package/dist/http/schemas/index.js +19 -10
  197. package/dist/http/schemas/integrations.d.ts +4 -4
  198. package/dist/http/schemas/integrations.js.map +1 -1
  199. package/dist/http/schemas/invites.d.ts +4 -0
  200. package/dist/http/schemas/invites.js +3 -1
  201. package/dist/http/schemas/invites.js.map +1 -1
  202. package/dist/http/schemas/{learn.d.ts → member.d.ts} +42 -19
  203. package/dist/http/schemas/{learn.js → member.js} +17 -13
  204. package/dist/http/schemas/member.js.map +1 -0
  205. package/dist/http/schemas/members.d.ts +39 -6
  206. package/dist/http/schemas/members.js +17 -3
  207. package/dist/http/schemas/members.js.map +1 -1
  208. package/dist/http/schemas/notifications.d.ts +227 -0
  209. package/dist/http/schemas/notifications.js +44 -0
  210. package/dist/http/schemas/notifications.js.map +1 -0
  211. package/dist/http/schemas/organizations.js.map +1 -1
  212. package/dist/http/schemas/posts.d.ts +316 -0
  213. package/dist/http/schemas/posts.js +47 -0
  214. package/dist/http/schemas/posts.js.map +1 -0
  215. package/dist/http/schemas/profile.d.ts +23 -0
  216. package/dist/http/schemas/profile.js +15 -0
  217. package/dist/http/schemas/profile.js.map +1 -0
  218. package/dist/http/schemas/settings.js.map +1 -1
  219. package/dist/http/schemas/shared.d.ts +26 -1
  220. package/dist/http/schemas/shared.js +22 -3
  221. package/dist/http/schemas/shared.js.map +1 -1
  222. package/dist/http/schemas/students.d.ts +32 -10
  223. package/dist/http/schemas/students.js +19 -6
  224. package/dist/http/schemas/students.js.map +1 -1
  225. package/dist/http/scope.d.ts +2 -1
  226. package/dist/http/scope.js +20 -4
  227. package/dist/http/scope.js.map +1 -1
  228. package/dist/http/server.js +10 -3
  229. package/dist/http/server.js.map +1 -1
  230. package/dist/http/web-bridge.d.ts +1 -1
  231. package/dist/http/web-bridge.js +2 -2
  232. package/dist/http/web-bridge.js.map +1 -1
  233. package/dist/http/well-known.d.ts +7 -0
  234. package/dist/http/well-known.js +22 -0
  235. package/dist/http/well-known.js.map +1 -0
  236. package/dist/index.d.ts +3 -2
  237. package/dist/index.js +4 -2
  238. package/dist/index.js.map +1 -1
  239. package/dist/node_modules/.pnpm/@jridgewell_sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.js +78 -0
  240. package/dist/node_modules/.pnpm/@jridgewell_sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.js.map +1 -0
  241. package/dist/node_modules/.pnpm/@vitest_expect@2.1.9/node_modules/@vitest/expect/dist/index.js +1267 -0
  242. package/dist/node_modules/.pnpm/@vitest_expect@2.1.9/node_modules/@vitest/expect/dist/index.js.map +1 -0
  243. package/dist/node_modules/.pnpm/@vitest_pretty-format@2.1.9/node_modules/@vitest/pretty-format/dist/index.js +814 -0
  244. package/dist/node_modules/.pnpm/@vitest_pretty-format@2.1.9/node_modules/@vitest/pretty-format/dist/index.js.map +1 -0
  245. package/dist/node_modules/.pnpm/@vitest_runner@2.1.9/node_modules/@vitest/runner/dist/chunk-tasks.js +43 -0
  246. package/dist/node_modules/.pnpm/@vitest_runner@2.1.9/node_modules/@vitest/runner/dist/chunk-tasks.js.map +1 -0
  247. package/dist/node_modules/.pnpm/@vitest_runner@2.1.9/node_modules/@vitest/runner/dist/index.js +527 -0
  248. package/dist/node_modules/.pnpm/@vitest_runner@2.1.9/node_modules/@vitest/runner/dist/index.js.map +1 -0
  249. package/dist/node_modules/.pnpm/@vitest_runner@2.1.9/node_modules/@vitest/runner/dist/utils.js +2 -0
  250. package/dist/node_modules/.pnpm/@vitest_snapshot@2.1.9/node_modules/@vitest/snapshot/dist/index.js +1737 -0
  251. package/dist/node_modules/.pnpm/@vitest_snapshot@2.1.9/node_modules/@vitest/snapshot/dist/index.js.map +1 -0
  252. package/dist/node_modules/.pnpm/@vitest_spy@2.1.9/node_modules/@vitest/spy/dist/index.js +137 -0
  253. package/dist/node_modules/.pnpm/@vitest_spy@2.1.9/node_modules/@vitest/spy/dist/index.js.map +1 -0
  254. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/chunk-_commonjsHelpers.js +118 -0
  255. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/chunk-_commonjsHelpers.js.map +1 -0
  256. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/diff.js +1222 -0
  257. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/diff.js.map +1 -0
  258. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/error.js +91 -0
  259. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/error.js.map +1 -0
  260. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/helpers.js +113 -0
  261. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/helpers.js.map +1 -0
  262. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/index.js +449 -0
  263. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/index.js.map +1 -0
  264. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/source-map.js +163 -0
  265. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/source-map.js.map +1 -0
  266. package/dist/node_modules/.pnpm/chai@5.3.3/node_modules/chai/index.js +3187 -0
  267. package/dist/node_modules/.pnpm/chai@5.3.3/node_modules/chai/index.js.map +1 -0
  268. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/arguments.js +11 -0
  269. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/arguments.js.map +1 -0
  270. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/array.js +16 -0
  271. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/array.js.map +1 -0
  272. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/bigint.js +11 -0
  273. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/bigint.js.map +1 -0
  274. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/class.js +15 -0
  275. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/class.js.map +1 -0
  276. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/date.js +13 -0
  277. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/date.js.map +1 -0
  278. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/error.js +34 -0
  279. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/error.js.map +1 -0
  280. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/function.js +12 -0
  281. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/function.js.map +1 -0
  282. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/helpers.js +122 -0
  283. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/helpers.js.map +1 -0
  284. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/html.js +39 -0
  285. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/html.js.map +1 -0
  286. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/index.js +99 -0
  287. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/index.js.map +1 -0
  288. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/map.js +25 -0
  289. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/map.js.map +1 -0
  290. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/number.js +14 -0
  291. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/number.js.map +1 -0
  292. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/object.js +21 -0
  293. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/object.js.map +1 -0
  294. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/promise.js +6 -0
  295. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/promise.js.map +1 -0
  296. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/regexp.js +12 -0
  297. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/regexp.js.map +1 -0
  298. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/set.js +18 -0
  299. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/set.js.map +1 -0
  300. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/string.js +24 -0
  301. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/string.js.map +1 -0
  302. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/symbol.js +9 -0
  303. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/symbol.js.map +1 -0
  304. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/typedarray.js +30 -0
  305. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/typedarray.js.map +1 -0
  306. package/dist/node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.js +939 -0
  307. package/dist/node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.js.map +1 -0
  308. package/dist/node_modules/.pnpm/pathe@1.1.2/node_modules/pathe/dist/shared/pathe.ff20891b.js +82 -0
  309. package/dist/node_modules/.pnpm/pathe@1.1.2/node_modules/pathe/dist/shared/pathe.ff20891b.js.map +1 -0
  310. package/dist/node_modules/.pnpm/tinyrainbow@1.2.0/node_modules/tinyrainbow/dist/chunk-BVHSVHOK.js +82 -0
  311. package/dist/node_modules/.pnpm/tinyrainbow@1.2.0/node_modules/tinyrainbow/dist/chunk-BVHSVHOK.js.map +1 -0
  312. package/dist/node_modules/.pnpm/tinyrainbow@1.2.0/node_modules/tinyrainbow/dist/node.js +8 -0
  313. package/dist/node_modules/.pnpm/tinyrainbow@1.2.0/node_modules/tinyrainbow/dist/node.js.map +1 -0
  314. package/dist/node_modules/.pnpm/tinyspy@3.0.2/node_modules/tinyspy/dist/index.js +83 -0
  315. package/dist/node_modules/.pnpm/tinyspy@3.0.2/node_modules/tinyspy/dist/index.js.map +1 -0
  316. package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/_commonjsHelpers.BFTU3MAI.js +9 -0
  317. package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/_commonjsHelpers.BFTU3MAI.js.map +1 -0
  318. package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/date.W2xKR2qe.js +51 -0
  319. package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/date.W2xKR2qe.js.map +1 -0
  320. package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/utils.C8RiOc4B.js +48 -0
  321. package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/utils.C8RiOc4B.js.map +1 -0
  322. package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/vi.DgezovHB.js +2426 -0
  323. package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/vi.DgezovHB.js.map +1 -0
  324. package/dist/packages/server/package.js +6 -0
  325. package/dist/packages/server/package.js.map +1 -0
  326. package/package.json +9 -7
  327. package/dist/app/notifications.d.ts +0 -7
  328. package/dist/app/notifications.js +0 -23
  329. package/dist/app/notifications.js.map +0 -1
  330. package/dist/http/routes/learn/assets.d.ts +0 -8
  331. package/dist/http/routes/learn/assets.js +0 -32
  332. package/dist/http/routes/learn/assets.js.map +0 -1
  333. package/dist/http/routes/learn/comments.d.ts +0 -7
  334. package/dist/http/routes/learn/comments.js +0 -175
  335. package/dist/http/routes/learn/comments.js.map +0 -1
  336. package/dist/http/routes/learn/courses.d.ts +0 -7
  337. package/dist/http/routes/learn/courses.js +0 -75
  338. package/dist/http/routes/learn/courses.js.map +0 -1
  339. package/dist/http/routes/learn/downloads.d.ts +0 -7
  340. package/dist/http/routes/learn/downloads.js +0 -76
  341. package/dist/http/routes/learn/downloads.js.map +0 -1
  342. package/dist/http/routes/learn/index.d.ts +0 -7
  343. package/dist/http/routes/learn/index.js +0 -19
  344. package/dist/http/routes/learn/index.js.map +0 -1
  345. package/dist/http/routes/learn/org.d.ts +0 -7
  346. package/dist/http/routes/learn/org.js +0 -50
  347. package/dist/http/routes/learn/org.js.map +0 -1
  348. package/dist/http/routes/learn/progress.d.ts +0 -7
  349. package/dist/http/routes/learn/progress.js +0 -63
  350. package/dist/http/routes/learn/progress.js.map +0 -1
  351. package/dist/http/schemas/learn.js.map +0 -1
@@ -1,9 +1,9 @@
1
+ import { refusals } from "../schemas/shared.js";
1
2
  import { resolveScope } from "../scope.js";
2
- import { ErrorBody } from "../schemas/shared.js";
3
3
  import { Course, CourseIdParam, CourseSettings, CoursesPage, CoursesQuery, CreateCourse, PatchCourseSettings, UpdateCourse } from "../schemas/content.js";
4
4
  import "../schemas/index.js";
5
- import { NotFoundError } from "@headless-lms/core/shared/errors";
6
5
  import { z } from "zod";
6
+ import { NotFoundError } from "@headless-lms/core/shared/errors";
7
7
  //#region src/http/routes/courses.ts
8
8
  async function coursesRoutes(app, container) {
9
9
  const r = app.withTypeProvider();
@@ -14,14 +14,18 @@ async function coursesRoutes(app, container) {
14
14
  preHandler: app.requireOrgSession,
15
15
  schema: {
16
16
  operationId: "listCourses",
17
- tags: ["Courses"],
18
- summary: "List courses",
17
+ tags: ["Content"],
18
+ summary: "List courses (staff)",
19
+ description: "Staff only (owner, admin, instructor). Every course in the organization, drafts included, filtered by status and category and searched by title.",
19
20
  querystring: CoursesQuery,
20
- response: { 200: CoursesPage }
21
+ response: {
22
+ 200: CoursesPage,
23
+ ...refusals(403)
24
+ }
21
25
  },
22
26
  handler: async (req) => {
23
27
  const scope = await resolveScope(container, req);
24
- return courses.list(scope.orgId, req.query);
28
+ return courses.listCourses(scope.orgId, req.query);
25
29
  }
26
30
  });
27
31
  r.route({
@@ -30,12 +34,13 @@ async function coursesRoutes(app, container) {
30
34
  preHandler: app.requireOrgSession,
31
35
  schema: {
32
36
  operationId: "getCourse",
33
- tags: ["Courses"],
34
- summary: "Get a course by id",
37
+ tags: ["Content"],
38
+ summary: "Get a course (staff)",
39
+ description: "Staff only (owner, admin, instructor).",
35
40
  params: CourseIdParam,
36
41
  response: {
37
42
  200: Course,
38
- 404: ErrorBody
43
+ ...refusals(403, 404)
39
44
  }
40
45
  },
41
46
  handler: async (req) => {
@@ -51,10 +56,14 @@ async function coursesRoutes(app, container) {
51
56
  preHandler: app.requireOrgSession,
52
57
  schema: {
53
58
  operationId: "createCourse",
54
- tags: ["Courses"],
55
- summary: "Create a course",
59
+ tags: ["Content"],
60
+ summary: "Create a course (staff)",
61
+ description: "Staff only (owner, admin, instructor). The course starts as a draft. 409 when its slug is taken.",
56
62
  body: CreateCourse,
57
- response: { 201: Course }
63
+ response: {
64
+ 201: Course,
65
+ ...refusals(403, 409)
66
+ }
58
67
  },
59
68
  handler: async (req, reply) => {
60
69
  const scope = await resolveScope(container, req);
@@ -68,13 +77,14 @@ async function coursesRoutes(app, container) {
68
77
  preHandler: app.requireOrgSession,
69
78
  schema: {
70
79
  operationId: "updateCourse",
71
- tags: ["Courses"],
72
- summary: "Update a course",
80
+ tags: ["Content"],
81
+ summary: "Update a course (staff)",
82
+ description: "Staff only (owner, admin, instructor). Changes only the fields sent. `thumbnailAssetId` must be a ready library image (409 otherwise); null clears it.",
73
83
  params: CourseIdParam,
74
84
  body: UpdateCourse,
75
85
  response: {
76
86
  200: Course,
77
- 404: ErrorBody
87
+ ...refusals(403, 404, 409)
78
88
  }
79
89
  },
80
90
  handler: async (req) => {
@@ -88,19 +98,20 @@ async function coursesRoutes(app, container) {
88
98
  preHandler: app.requireOrgSession,
89
99
  schema: {
90
100
  operationId: "updateCourseSettings",
91
- tags: ["Courses"],
92
- summary: "Update a course settings",
101
+ tags: ["Content"],
102
+ summary: "Update a course's settings (staff)",
103
+ description: "Staff only (owner, admin, instructor). Merges the settings sent into the current ones.",
93
104
  params: CourseIdParam,
94
105
  body: PatchCourseSettings,
95
106
  response: {
96
107
  200: CourseSettings,
97
- 404: ErrorBody
108
+ ...refusals(403, 404)
98
109
  }
99
110
  },
100
111
  handler: async (req) => {
101
112
  const scope = await resolveScope(container, req);
102
113
  req.log.info({ body: req.body }, "update course settings");
103
- return courses.patchSettings(scope.orgId, req.params.id, req.body);
114
+ return courses.patchCourseSettings(scope.orgId, req.params.id, req.body);
104
115
  }
105
116
  });
106
117
  r.route({
@@ -109,17 +120,21 @@ async function coursesRoutes(app, container) {
109
120
  preHandler: app.requireOrgSession,
110
121
  schema: {
111
122
  operationId: "deleteCourse",
112
- tags: ["Courses"],
113
- summary: "Delete a course",
123
+ tags: ["Content"],
124
+ summary: "Delete a course (staff)",
125
+ description: "Staff only (owner, admin, instructor).",
114
126
  params: CourseIdParam,
115
127
  response: {
116
128
  204: z.void(),
117
- 404: ErrorBody
129
+ ...refusals(403, 404)
118
130
  }
119
131
  },
120
132
  handler: async (req, reply) => {
121
133
  const scope = await resolveScope(container, req);
122
- await courses.deleteCourse(scope.orgId, req.params.id);
134
+ await courses.deleteCourse(scope.orgId, req.params.id, {
135
+ id: scope.orgUserId,
136
+ role: scope.role
137
+ });
123
138
  return reply.code(204).send();
124
139
  }
125
140
  });
@@ -1 +1 @@
1
- {"version":3,"file":"courses.js","names":[],"sources":["../../../src/http/routes/courses.ts"],"sourcesContent":["// HTTP routes for the courses context. Request + response are validated against\n// the shared contract schemas by the Zod type provider, and @fastify/swagger\n// reads the same schemas to build the OpenAPI spec the SDK is generated from.\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n Course,\n CourseIdParam,\n CourseSettings,\n CoursesPage,\n CoursesQuery,\n CreateCourse,\n ErrorBody,\n PatchCourseSettings,\n UpdateCourse,\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 coursesRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const courses = container.content;\n\n r.route({\n method: 'GET',\n url: '/api/courses',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listCourses',\n tags: ['Courses'],\n summary: 'List courses',\n querystring: CoursesQuery,\n response: { 200: CoursesPage },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return courses.list(scope.orgId, req.query);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/courses/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getCourse',\n tags: ['Courses'],\n summary: 'Get a course by id',\n params: CourseIdParam,\n response: { 200: Course, 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const course = await courses.getCourse(scope.orgId, req.params.id);\n if (!course) {\n throw new NotFoundError('Course', req.params.id);\n }\n return course;\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/courses',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createCourse',\n tags: ['Courses'],\n summary: 'Create a course',\n body: CreateCourse,\n response: { 201: Course },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const course = await courses.createCourse(scope.orgId, req.body);\n return reply.code(201).send(course);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/courses/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateCourse',\n tags: ['Courses'],\n summary: 'Update a course',\n params: CourseIdParam,\n body: UpdateCourse,\n response: { 200: Course, 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return courses.updateCourse(scope.orgId, req.params.id, req.body);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/courses/:id/settings',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateCourseSettings',\n tags: ['Courses'],\n summary: 'Update a course settings',\n params: CourseIdParam,\n body: PatchCourseSettings,\n response: { 200: CourseSettings, 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n req.log.info( { body: req.body },'update course settings',);\n return courses.patchSettings(scope.orgId, req.params.id, req.body);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/courses/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteCourse',\n tags: ['Courses'],\n summary: 'Delete a course',\n params: CourseIdParam,\n response: { 204: z.void(), 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n await courses.deleteCourse(scope.orgId, req.params.id);\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":";;;;;;;AAqBA,eAAsB,cAAc,KAAsB,WAAqC;CAC7F,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,UAAU,UAAU;CAE1B,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,aAAa;GACb,UAAU,EAAE,KAAK,YAAY;EAC/B;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,QAAQ,KAAK,MAAM,OAAO,IAAI,KAAK;EAC5C;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,QAAQ;GACR,UAAU;IAAE,KAAK;IAAQ,KAAK;GAAU;EAC1C;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,SAAS,MAAM,QAAQ,UAAU,MAAM,OAAO,IAAI,OAAO,EAAE;GACjE,IAAI,CAAC,QACH,MAAM,IAAI,cAAc,UAAU,IAAI,OAAO,EAAE;GAEjD,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,MAAM;GACN,UAAU,EAAE,KAAK,OAAO;EAC1B;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,SAAS,MAAM,QAAQ,aAAa,MAAM,OAAO,IAAI,IAAI;GAC/D,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,MAAM;EACpC;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,QAAQ;GACR,MAAM;GACN,UAAU;IAAE,KAAK;IAAQ,KAAK;GAAU;EAC1C;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,QAAQ,aAAa,MAAM,OAAO,IAAI,OAAO,IAAI,IAAI,IAAI;EAClE;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,QAAQ;GACR,MAAM;GACN,UAAU;IAAE,KAAK;IAAgB,KAAK;GAAU;EAClD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,IAAI,IAAI,KAAM,EAAE,MAAM,IAAI,KAAK,GAAE,wBAAyB;GAC1D,OAAO,QAAQ,cAAc,MAAM,OAAO,IAAI,OAAO,IAAI,IAAI,IAAI;EACnE;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,QAAQ;GACR,UAAU;IAAE,KAAK,EAAE,KAAK;IAAG,KAAK;GAAU;EAC5C;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,QAAQ,aAAa,MAAM,OAAO,IAAI,OAAO,EAAE;GACrD,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK;EAC9B;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"courses.js","names":[],"sources":["../../../src/http/routes/courses.ts"],"sourcesContent":["// HTTP routes for the courses context. Request + response are validated against\n// the shared contract schemas by the Zod type provider, and @fastify/swagger\n// reads the same schemas to build the OpenAPI spec the SDK is generated from.\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n Course,\n CourseIdParam,\n CourseSettings,\n CoursesPage,\n CoursesQuery,\n CreateCourse,\n PatchCourseSettings,\n UpdateCourse,\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 coursesRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const courses = container.content;\n\n r.route({\n method: 'GET',\n url: '/api/courses',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listCourses',\n tags: ['Content'],\n summary: 'List courses (staff)',\n description:\n 'Staff only (owner, admin, instructor). Every course in the organization, drafts included, filtered by status and category and searched by title.',\n querystring: CoursesQuery,\n response: { 200: CoursesPage, ...refusals(403) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return courses.listCourses(scope.orgId, req.query);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/courses/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getCourse',\n tags: ['Content'],\n summary: 'Get a course (staff)',\n description: 'Staff only (owner, admin, instructor).',\n params: CourseIdParam,\n response: { 200: Course, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const course = await courses.getCourse(scope.orgId, req.params.id);\n if (!course) {\n throw new NotFoundError('Course', req.params.id);\n }\n return course;\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/courses',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createCourse',\n tags: ['Content'],\n summary: 'Create a course (staff)',\n description:\n 'Staff only (owner, admin, instructor). The course starts as a draft. 409 when its slug is taken.',\n body: CreateCourse,\n response: { 201: Course, ...refusals(403, 409) },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const course = await courses.createCourse(scope.orgId, req.body);\n return reply.code(201).send(course);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/courses/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateCourse',\n tags: ['Content'],\n summary: 'Update a course (staff)',\n description:\n 'Staff only (owner, admin, instructor). Changes only the fields sent. `thumbnailAssetId` must be a ready library image (409 otherwise); null clears it.',\n params: CourseIdParam,\n body: UpdateCourse,\n response: { 200: Course, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return courses.updateCourse(scope.orgId, req.params.id, req.body);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/courses/:id/settings',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateCourseSettings',\n tags: ['Content'],\n summary: \"Update a course's settings (staff)\",\n description:\n 'Staff only (owner, admin, instructor). Merges the settings sent into the current ones.',\n params: CourseIdParam,\n body: PatchCourseSettings,\n response: { 200: CourseSettings, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n req.log.info({ body: req.body }, 'update course settings');\n return courses.patchCourseSettings(scope.orgId, req.params.id, req.body);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/courses/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteCourse',\n tags: ['Content'],\n summary: 'Delete a course (staff)',\n description: 'Staff only (owner, admin, instructor).',\n params: CourseIdParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n await courses.deleteCourse(scope.orgId, req.params.id, {\n id: scope.orgUserId,\n role: scope.role,\n });\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { refusals } from "../schemas/shared.js";
1
2
  import { resolveScope } from "../scope.js";
2
3
  import { EnrollmentSeries, EnrollmentSeriesQuery, OverviewStats } from "../schemas/dashboard.js";
3
4
  import "../schemas/index.js";
@@ -10,13 +11,17 @@ async function dashboardRoutes(app, container) {
10
11
  preHandler: app.requireOrgSession,
11
12
  schema: {
12
13
  operationId: "getOverview",
13
- tags: ["Dashboard"],
14
- summary: "Back-office overview stats",
15
- response: { 200: OverviewStats }
14
+ tags: ["Reporting"],
15
+ summary: "Back-office overview stats (staff)",
16
+ description: "Staff only (owner, admin, instructor).",
17
+ response: {
18
+ 200: OverviewStats,
19
+ ...refusals(403)
20
+ }
16
21
  },
17
22
  handler: async (req) => {
18
23
  const scope = await resolveScope(container, req);
19
- return container.reporting.dashboard.overview(scope.orgId);
24
+ return container.reporting.admin.overview(scope.orgId);
20
25
  }
21
26
  });
22
27
  r.route({
@@ -25,14 +30,18 @@ async function dashboardRoutes(app, container) {
25
30
  preHandler: app.requireOrgSession,
26
31
  schema: {
27
32
  operationId: "getEnrollmentSeries",
28
- tags: ["Dashboard"],
29
- summary: "Enrollments granted per day over a trailing window",
33
+ tags: ["Reporting"],
34
+ summary: "Enrollments granted per day over a trailing window (staff)",
35
+ description: "Staff only (owner, admin, instructor).",
30
36
  querystring: EnrollmentSeriesQuery,
31
- response: { 200: EnrollmentSeries }
37
+ response: {
38
+ 200: EnrollmentSeries,
39
+ ...refusals(403)
40
+ }
32
41
  },
33
42
  handler: async (req) => {
34
43
  const scope = await resolveScope(container, req);
35
- return container.reporting.dashboard.enrollments(scope.orgId, req.query.days);
44
+ return container.reporting.admin.enrollments(scope.orgId, req.query.days);
36
45
  }
37
46
  });
38
47
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard.js","names":[],"sources":["../../../src/http/routes/dashboard.ts"],"sourcesContent":["// HTTP routes for the dashboard (overview) context.\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { EnrollmentSeries, EnrollmentSeriesQuery, OverviewStats } from '../schemas/index.js';\nimport type { Container } from '../../app/container.js';\nimport { resolveScope } from '../scope.js';\n\nexport async function dashboardRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n\n r.route({\n method: 'GET',\n url: '/api/overview',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getOverview',\n tags: ['Dashboard'],\n summary: 'Back-office overview stats',\n response: { 200: OverviewStats },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return container.reporting.dashboard.overview(scope.orgId);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/overview/enrollments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getEnrollmentSeries',\n tags: ['Dashboard'],\n summary: 'Enrollments granted per day over a trailing window',\n querystring: EnrollmentSeriesQuery,\n response: { 200: EnrollmentSeries },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return container.reporting.dashboard.enrollments(scope.orgId, req.query.days);\n },\n });\n}\n"],"mappings":";;;;AAOA,eAAsB,gBAAgB,KAAsB,WAAqC;CAC/F,MAAM,IAAI,IAAI,iBAAkC;CAEhD,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,WAAW;GAClB,SAAS;GACT,UAAU,EAAE,KAAK,cAAc;EACjC;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,UAAU,UAAU,UAAU,SAAS,MAAM,KAAK;EAC3D;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,WAAW;GAClB,SAAS;GACT,aAAa;GACb,UAAU,EAAE,KAAK,iBAAiB;EACpC;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,UAAU,UAAU,UAAU,YAAY,MAAM,OAAO,IAAI,MAAM,IAAI;EAC9E;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"dashboard.js","names":[],"sources":["../../../src/http/routes/dashboard.ts"],"sourcesContent":["// HTTP routes for the dashboard (overview) context.\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n EnrollmentSeries,\n EnrollmentSeriesQuery,\n OverviewStats,\n refusals,\n} from '../schemas/index.js';\nimport type { Container } from '../../app/container.js';\nimport { resolveScope } from '../scope.js';\n\nexport async function dashboardRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n\n r.route({\n method: 'GET',\n url: '/api/overview',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getOverview',\n tags: ['Reporting'],\n summary: 'Back-office overview stats (staff)',\n description: 'Staff only (owner, admin, instructor).',\n response: { 200: OverviewStats, ...refusals(403) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return container.reporting.admin.overview(scope.orgId);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/overview/enrollments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getEnrollmentSeries',\n tags: ['Reporting'],\n summary: 'Enrollments granted per day over a trailing window (staff)',\n description: 'Staff only (owner, admin, instructor).',\n querystring: EnrollmentSeriesQuery,\n response: { 200: EnrollmentSeries, ...refusals(403) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return container.reporting.admin.enrollments(scope.orgId, req.query.days);\n },\n });\n}\n"],"mappings":";;;;;AAYA,eAAsB,gBAAgB,KAAsB,WAAqC;CAC/F,MAAM,IAAI,IAAI,iBAAkC;CAEhD,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,WAAW;GAClB,SAAS;GACT,aAAa;GACb,UAAU;IAAE,KAAK;IAAe,GAAG,SAAS,GAAG;GAAE;EACnD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,UAAU,UAAU,MAAM,SAAS,MAAM,KAAK;EACvD;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,WAAW;GAClB,SAAS;GACT,aAAa;GACb,aAAa;GACb,UAAU;IAAE,KAAK;IAAkB,GAAG,SAAS,GAAG;GAAE;EACtD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,UAAU,UAAU,MAAM,YAAY,MAAM,OAAO,IAAI,MAAM,IAAI;EAC1E;CACF,CAAC;AACH"}
@@ -1,22 +1,30 @@
1
- import { ErrorBody } from "../schemas/shared.js";
1
+ import { refusals } from "../schemas/shared.js";
2
+ import { resolveScope } from "../scope.js";
2
3
  import { Comment, CommentIdParam, CommentView, CommentsPage, CommentsQuery, PatchComment, ReplyToComment } from "../schemas/discussion.js";
3
4
  import "../schemas/index.js";
4
- import { NotFoundError } from "@headless-lms/core/shared/errors";
5
5
  import { z } from "zod";
6
+ import { NotFoundError } from "@headless-lms/core/shared/errors";
6
7
  //#region src/http/routes/discussion.ts
7
8
  async function discussionRoutes(app, container) {
8
9
  const r = app.withTypeProvider();
9
10
  const discussion = container.discussion;
11
+ const staff = async (req) => {
12
+ await resolveScope(container, req);
13
+ };
10
14
  r.route({
11
15
  method: "GET",
12
16
  url: "/api/comments",
13
- preHandler: app.requireOrgSession,
17
+ preHandler: staff,
14
18
  schema: {
15
19
  operationId: "listComments",
16
20
  tags: ["Discussion"],
17
- summary: "List comments, filtered by course, activity, author, status or reports",
21
+ summary: "List course comments for moderation (staff)",
22
+ description: "Staff only (owner, admin, instructor). Comments on course activities, filtered by course, activity, author, status or open reports; each row carries the author's email and the open reports.",
18
23
  querystring: CommentsQuery,
19
- response: { 200: CommentsPage }
24
+ response: {
25
+ 200: CommentsPage,
26
+ ...refusals(403)
27
+ }
20
28
  },
21
29
  handler: async (req) => {
22
30
  return discussion.listComments(req.orgId, req.query);
@@ -25,88 +33,88 @@ async function discussionRoutes(app, container) {
25
33
  r.route({
26
34
  method: "DELETE",
27
35
  url: "/api/comments/:commentId",
28
- preHandler: app.requireOrgSession,
36
+ preHandler: staff,
29
37
  schema: {
30
38
  operationId: "moderateRemoveComment",
31
39
  tags: ["Discussion"],
32
- summary: "Remove a comment as a moderator",
40
+ summary: "Remove a course comment (staff)",
41
+ description: "Staff only (owner, admin, instructor). Hides the comment and resolves its open reports as `removed`. Removing a removed comment returns it unchanged.",
33
42
  params: CommentIdParam,
34
43
  response: {
35
- 200: Comment,
36
- 403: ErrorBody,
37
- 404: ErrorBody
44
+ 200: CommentView,
45
+ ...refusals(403, 404)
38
46
  }
39
47
  },
40
48
  handler: async (req) => {
41
49
  const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);
42
- return discussion.remove(req.orgId, req.params.commentId, orgUser);
50
+ return discussion.remove(req.orgId, req.params.commentId, orgUser, null);
43
51
  }
44
52
  });
45
53
  r.route({
46
54
  method: "PATCH",
47
55
  url: "/api/comments/:commentId",
48
- preHandler: app.requireOrgSession,
56
+ preHandler: staff,
49
57
  schema: {
50
- operationId: "editComment",
58
+ operationId: "publishComment",
51
59
  tags: ["Discussion"],
52
- summary: "Publish a comment: approves a pending one, restores a removed one",
60
+ summary: "Publish a course comment (staff)",
61
+ description: "Staff only (owner, admin, instructor). Approves a pending comment or restores a removed one; a published comment is returned unchanged.",
53
62
  params: CommentIdParam,
54
63
  body: PatchComment,
55
64
  response: {
56
65
  200: Comment,
57
- 403: ErrorBody,
58
- 404: ErrorBody
66
+ ...refusals(403, 404)
59
67
  }
60
68
  },
61
69
  handler: async (req) => {
62
70
  const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);
63
71
  if (!orgUser) throw new NotFoundError("OrgUser", req.userId);
64
- return discussion.publish(req.orgId, req.params.commentId, orgUser);
72
+ return discussion.publish(req.orgId, req.params.commentId, orgUser, null);
65
73
  }
66
74
  });
67
75
  r.route({
68
76
  method: "DELETE",
69
77
  url: "/api/comments/:commentId/reports",
70
- preHandler: app.requireOrgSession,
78
+ preHandler: staff,
71
79
  schema: {
72
80
  operationId: "dismissCommentReports",
73
81
  tags: ["Discussion"],
74
- summary: "Dismiss every open report on a comment",
82
+ summary: "Dismiss every open report on a course comment (staff)",
83
+ description: "Staff only (owner, admin, instructor). The comment stays up.",
75
84
  params: CommentIdParam,
76
85
  response: {
77
86
  204: z.void(),
78
- 403: ErrorBody,
79
- 404: ErrorBody
87
+ ...refusals(403, 404)
80
88
  }
81
89
  },
82
90
  handler: async (req, reply) => {
83
91
  const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);
84
92
  if (!orgUser) throw new NotFoundError("OrgUser", req.userId);
85
- await discussion.resolveReports(req.orgId, req.params.commentId, orgUser);
93
+ await discussion.resolveReports(req.orgId, req.params.commentId, orgUser, null);
86
94
  return reply.code(204).send();
87
95
  }
88
96
  });
89
97
  r.route({
90
98
  method: "POST",
91
99
  url: "/api/comments/:commentId/replies",
92
- preHandler: app.requireOrgSession,
100
+ preHandler: staff,
93
101
  schema: {
94
102
  operationId: "replyToComment",
95
103
  tags: ["Discussion"],
96
- summary: "Reply to a comment from the moderation queue",
104
+ summary: "Reply to a course comment from the moderation queue (staff)",
105
+ description: "Staff only (owner, admin, instructor). Replies nest one level: answering a reply attaches to its root comment.",
97
106
  params: CommentIdParam,
98
107
  body: ReplyToComment,
99
108
  response: {
100
109
  200: CommentView,
101
- 403: ErrorBody,
102
- 404: ErrorBody
110
+ ...refusals(403, 404)
103
111
  }
104
112
  },
105
113
  handler: async (req) => {
106
114
  const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);
107
115
  if (!orgUser) throw new NotFoundError("OrgUser", req.userId);
108
116
  const parent = await discussion.getComment(req.orgId, req.params.commentId);
109
- if (!parent) throw new NotFoundError("Comment", req.params.commentId);
117
+ if (!parent || parent.activityId === null) throw new NotFoundError("Comment", req.params.commentId);
110
118
  return discussion.postComment(req.orgId, {
111
119
  actor: orgUser,
112
120
  activityId: parent.activityId,
@@ -1 +1 @@
1
- {"version":3,"file":"discussion.js","names":[],"sources":["../../../src/http/routes/discussion.ts"],"sourcesContent":["// Moderator-facing discussion routes. The learner surface (everything under\n// `/api/learn`) lives in routes/learn/comments.ts.\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport {\n Comment,\n CommentIdParam,\n CommentsPage,\n CommentsQuery,\n CommentView,\n ErrorBody,\n PatchComment,\n ReplyToComment,\n} from '../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../app/container.js';\n\nexport async function discussionRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const discussion = container.discussion;\n\n r.route({\n method: 'GET',\n url: '/api/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listComments',\n tags: ['Discussion'],\n summary: 'List comments, filtered by course, activity, author, status or reports',\n querystring: CommentsQuery,\n response: { 200: CommentsPage },\n },\n handler: async (req) => {\n return discussion.listComments(req.orgId, req.query);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/comments/:commentId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'moderateRemoveComment',\n tags: ['Discussion'],\n summary: 'Remove a comment as a moderator',\n params: CommentIdParam,\n response: { 200: Comment, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n return discussion.remove(req.orgId, req.params.commentId, orgUser!);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/comments/:commentId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'editComment',\n tags: ['Discussion'],\n summary: 'Publish a comment: approves a pending one, restores a removed one',\n params: CommentIdParam,\n body: PatchComment,\n response: { 200: Comment, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new NotFoundError('OrgUser', req.userId);\n }\n return discussion.publish(req.orgId, req.params.commentId, orgUser);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/comments/:commentId/reports',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'dismissCommentReports',\n tags: ['Discussion'],\n summary: 'Dismiss every open report on a comment',\n params: CommentIdParam,\n response: { 204: z.void(), 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new NotFoundError('OrgUser', req.userId);\n }\n await discussion.resolveReports(req.orgId, req.params.commentId, orgUser);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/comments/:commentId/replies',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'replyToComment',\n tags: ['Discussion'],\n summary: 'Reply to a comment from the moderation queue',\n params: CommentIdParam,\n body: ReplyToComment,\n response: { 200: CommentView, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new NotFoundError('OrgUser', req.userId);\n }\n const parent = await discussion.getComment(req.orgId, req.params.commentId);\n if (!parent) {\n throw new NotFoundError('Comment', req.params.commentId);\n }\n // Replies nest one level: answering a reply attaches to its root.\n return discussion.postComment(req.orgId, {\n actor: orgUser,\n activityId: parent.activityId,\n parentId: parent.parentId ?? parent.id,\n body: req.body.body,\n });\n },\n });\n}\n"],"mappings":";;;;;;AAkBA,eAAsB,iBAAiB,KAAsB,WAAqC;CAChG,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,aAAa,UAAU;CAE7B,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,YAAY;GACnB,SAAS;GACT,aAAa;GACb,UAAU,EAAE,KAAK,aAAa;EAChC;EACA,SAAS,OAAO,QAAQ;GACtB,OAAO,WAAW,aAAa,IAAI,OAAO,IAAI,KAAK;EACrD;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,YAAY;GACnB,SAAS;GACT,QAAQ;GACR,UAAU;IAAE,KAAK;IAAS,KAAK;IAAW,KAAK;GAAU;EAC3D;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,OAAO,WAAW,OAAO,IAAI,OAAO,IAAI,OAAO,WAAW,OAAQ;EACpE;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,YAAY;GACnB,SAAS;GACT,QAAQ;GACR,MAAM;GACN,UAAU;IAAE,KAAK;IAAS,KAAK;IAAW,KAAK;GAAU;EAC3D;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,IAAI,CAAC,SACH,MAAM,IAAI,cAAc,WAAW,IAAI,MAAM;GAE/C,OAAO,WAAW,QAAQ,IAAI,OAAO,IAAI,OAAO,WAAW,OAAO;EACpE;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,YAAY;GACnB,SAAS;GACT,QAAQ;GACR,UAAU;IAAE,KAAK,EAAE,KAAK;IAAG,KAAK;IAAW,KAAK;GAAU;EAC5D;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,IAAI,CAAC,SACH,MAAM,IAAI,cAAc,WAAW,IAAI,MAAM;GAE/C,MAAM,WAAW,eAAe,IAAI,OAAO,IAAI,OAAO,WAAW,OAAO;GACxE,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK;EAC9B;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,YAAY;GACnB,SAAS;GACT,QAAQ;GACR,MAAM;GACN,UAAU;IAAE,KAAK;IAAa,KAAK;IAAW,KAAK;GAAU;EAC/D;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,IAAI,CAAC,SACH,MAAM,IAAI,cAAc,WAAW,IAAI,MAAM;GAE/C,MAAM,SAAS,MAAM,WAAW,WAAW,IAAI,OAAO,IAAI,OAAO,SAAS;GAC1E,IAAI,CAAC,QACH,MAAM,IAAI,cAAc,WAAW,IAAI,OAAO,SAAS;GAGzD,OAAO,WAAW,YAAY,IAAI,OAAO;IACvC,OAAO;IACP,YAAY,OAAO;IACnB,UAAU,OAAO,YAAY,OAAO;IACpC,MAAM,IAAI,KAAK;GACjB,CAAC;EACH;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"discussion.js","names":[],"sources":["../../../src/http/routes/discussion.ts"],"sourcesContent":["// Moderator-facing discussion routes. The learner surface (everything under\n// `/api/member`) lives in routes/member/comments.ts.\nimport type { FastifyInstance, FastifyRequest } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport {\n Comment,\n CommentIdParam,\n CommentsPage,\n CommentsQuery,\n CommentView,\n PatchComment,\n ReplyToComment,\n refusals,\n} from '../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../app/container.js';\nimport { resolveScope } from '../scope.js';\n\nexport async function discussionRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const discussion = container.discussion;\n const staff = async (req: FastifyRequest) => {\n await resolveScope(container, req);\n };\n\n r.route({\n method: 'GET',\n url: '/api/comments',\n preHandler: staff,\n schema: {\n operationId: 'listComments',\n tags: ['Discussion'],\n summary: 'List course comments for moderation (staff)',\n description:\n \"Staff only (owner, admin, instructor). Comments on course activities, filtered by course, activity, author, status or open reports; each row carries the author's email and the open reports.\",\n querystring: CommentsQuery,\n response: { 200: CommentsPage, ...refusals(403) },\n },\n handler: async (req) => {\n return discussion.listComments(req.orgId, req.query);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/comments/:commentId',\n preHandler: staff,\n schema: {\n operationId: 'moderateRemoveComment',\n tags: ['Discussion'],\n summary: 'Remove a course comment (staff)',\n description:\n 'Staff only (owner, admin, instructor). Hides the comment and resolves its open reports as `removed`. Removing a removed comment returns it unchanged.',\n params: CommentIdParam,\n response: { 200: CommentView, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n return discussion.remove(req.orgId, req.params.commentId, orgUser!, null);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/comments/:commentId',\n preHandler: staff,\n schema: {\n operationId: 'publishComment',\n tags: ['Discussion'],\n summary: 'Publish a course comment (staff)',\n description:\n 'Staff only (owner, admin, instructor). Approves a pending comment or restores a removed one; a published comment is returned unchanged.',\n params: CommentIdParam,\n body: PatchComment,\n response: { 200: Comment, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new NotFoundError('OrgUser', req.userId);\n }\n return discussion.publish(req.orgId, req.params.commentId, orgUser, null);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/comments/:commentId/reports',\n preHandler: staff,\n schema: {\n operationId: 'dismissCommentReports',\n tags: ['Discussion'],\n summary: 'Dismiss every open report on a course comment (staff)',\n description: 'Staff only (owner, admin, instructor). The comment stays up.',\n params: CommentIdParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new NotFoundError('OrgUser', req.userId);\n }\n await discussion.resolveReports(req.orgId, req.params.commentId, orgUser, null);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/comments/:commentId/replies',\n preHandler: staff,\n schema: {\n operationId: 'replyToComment',\n tags: ['Discussion'],\n summary: 'Reply to a course comment from the moderation queue (staff)',\n description:\n 'Staff only (owner, admin, instructor). Replies nest one level: answering a reply attaches to its root comment.',\n params: CommentIdParam,\n body: ReplyToComment,\n response: { 200: CommentView, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new NotFoundError('OrgUser', req.userId);\n }\n const parent = await discussion.getComment(req.orgId, req.params.commentId);\n if (!parent || parent.activityId === null) {\n throw new NotFoundError('Comment', req.params.commentId);\n }\n // Replies nest one level: answering a reply attaches to its root.\n return discussion.postComment(req.orgId, {\n actor: orgUser,\n activityId: parent.activityId,\n parentId: parent.parentId ?? parent.id,\n body: req.body.body,\n });\n },\n });\n}\n"],"mappings":"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"}
@@ -1,9 +1,9 @@
1
+ import { refusals } from "../schemas/shared.js";
1
2
  import { resolveScope } from "../scope.js";
2
- import { ErrorBody } from "../schemas/shared.js";
3
- import { AddDownloadAsset, CreateDownload, Download, DownloadAsset, DownloadAssetParams, DownloadIdParam, DownloadsPage, DownloadsQuery, RenameDownloadAsset, ReorderDownloadAssets, UpdateDownload } from "../schemas/downloads.js";
3
+ import { AddDownloadAsset, CreateDownload, Download, DownloadAssetParams, DownloadFile, DownloadIdParam, DownloadsPage, DownloadsQuery, RenameDownloadAsset, ReorderDownloadAssets, UpdateDownload } from "../schemas/downloads.js";
4
4
  import "../schemas/index.js";
5
- import { NotFoundError } from "@headless-lms/core/shared/errors";
6
5
  import { z } from "zod";
6
+ import { NotFoundError } from "@headless-lms/core/shared/errors";
7
7
  //#region src/http/routes/downloads.ts
8
8
  async function downloadsRoutes(app, container) {
9
9
  const r = app.withTypeProvider();
@@ -14,10 +14,14 @@ async function downloadsRoutes(app, container) {
14
14
  preHandler: app.requireOrgSession,
15
15
  schema: {
16
16
  operationId: "listDownloads",
17
- tags: ["Downloads"],
18
- summary: "List downloads",
17
+ tags: ["Content"],
18
+ summary: "List downloads (staff)",
19
+ description: "Staff only (owner, admin, instructor). Every download, drafts included, filtered by status and category and searched by title.",
19
20
  querystring: DownloadsQuery,
20
- response: { 200: DownloadsPage }
21
+ response: {
22
+ 200: DownloadsPage,
23
+ ...refusals(403)
24
+ }
21
25
  },
22
26
  handler: async (req) => {
23
27
  const scope = await resolveScope(container, req);
@@ -30,12 +34,13 @@ async function downloadsRoutes(app, container) {
30
34
  preHandler: app.requireOrgSession,
31
35
  schema: {
32
36
  operationId: "getDownload",
33
- tags: ["Downloads"],
34
- summary: "Get a download by id",
37
+ tags: ["Content"],
38
+ summary: "Get a download (staff)",
39
+ description: "Staff only (owner, admin, instructor).",
35
40
  params: DownloadIdParam,
36
41
  response: {
37
42
  200: Download,
38
- 404: ErrorBody
43
+ ...refusals(403, 404)
39
44
  }
40
45
  },
41
46
  handler: async (req) => {
@@ -51,10 +56,14 @@ async function downloadsRoutes(app, container) {
51
56
  preHandler: app.requireOrgSession,
52
57
  schema: {
53
58
  operationId: "createDownload",
54
- tags: ["Downloads"],
55
- summary: "Create a download",
59
+ tags: ["Content"],
60
+ summary: "Create a download (staff)",
61
+ description: "Staff only (owner, admin, instructor). The download starts as a draft. 409 when its slug is taken.",
56
62
  body: CreateDownload,
57
- response: { 201: Download }
63
+ response: {
64
+ 201: Download,
65
+ ...refusals(403, 409)
66
+ }
58
67
  },
59
68
  handler: async (req, reply) => {
60
69
  const scope = await resolveScope(container, req);
@@ -68,13 +77,14 @@ async function downloadsRoutes(app, container) {
68
77
  preHandler: app.requireOrgSession,
69
78
  schema: {
70
79
  operationId: "updateDownload",
71
- tags: ["Downloads"],
72
- summary: "Update a download",
80
+ tags: ["Content"],
81
+ summary: "Update a download (staff)",
82
+ description: "Staff only (owner, admin, instructor). Changes only the fields sent. `thumbnailAssetId` must be a ready library image (409 otherwise); null clears it.",
73
83
  params: DownloadIdParam,
74
84
  body: UpdateDownload,
75
85
  response: {
76
86
  200: Download,
77
- 404: ErrorBody
87
+ ...refusals(403, 404, 409)
78
88
  }
79
89
  },
80
90
  handler: async (req) => {
@@ -88,17 +98,21 @@ async function downloadsRoutes(app, container) {
88
98
  preHandler: app.requireOrgSession,
89
99
  schema: {
90
100
  operationId: "deleteDownload",
91
- tags: ["Downloads"],
92
- summary: "Delete a download",
101
+ tags: ["Content"],
102
+ summary: "Delete a download (staff)",
103
+ description: "Staff only (owner, admin, instructor). Its library files survive.",
93
104
  params: DownloadIdParam,
94
105
  response: {
95
106
  204: z.void(),
96
- 404: ErrorBody
107
+ ...refusals(403, 404)
97
108
  }
98
109
  },
99
110
  handler: async (req, reply) => {
100
111
  const scope = await resolveScope(container, req);
101
- await content.removeDownload(scope.orgId, req.params.downloadId);
112
+ await content.removeDownload(scope.orgId, req.params.downloadId, {
113
+ id: scope.orgUserId,
114
+ role: scope.role
115
+ });
102
116
  return reply.code(204).send();
103
117
  }
104
118
  });
@@ -108,12 +122,13 @@ async function downloadsRoutes(app, container) {
108
122
  preHandler: app.requireOrgSession,
109
123
  schema: {
110
124
  operationId: "listDownloadAssets",
111
- tags: ["Downloads"],
112
- summary: "List a download's assets in order",
125
+ tags: ["Content"],
126
+ summary: "List a download's files in order (staff)",
127
+ description: "Staff only (owner, admin, instructor).",
113
128
  params: DownloadIdParam,
114
129
  response: {
115
- 200: z.array(DownloadAsset),
116
- 404: ErrorBody
130
+ 200: z.array(DownloadFile),
131
+ ...refusals(403)
117
132
  }
118
133
  },
119
134
  handler: async (req) => {
@@ -127,13 +142,14 @@ async function downloadsRoutes(app, container) {
127
142
  preHandler: app.requireOrgSession,
128
143
  schema: {
129
144
  operationId: "addDownloadAsset",
130
- tags: ["Downloads"],
131
- summary: "Link a media-library asset to a download",
145
+ tags: ["Content"],
146
+ summary: "Add a library file to a download (staff)",
147
+ description: "Staff only (owner, admin, instructor). Added last. 409 when the file is not a ready library file or is already in the download. Returns the download's files.",
132
148
  params: DownloadIdParam,
133
149
  body: AddDownloadAsset,
134
150
  response: {
135
- 200: z.array(DownloadAsset),
136
- 404: ErrorBody
151
+ 200: z.array(DownloadFile),
152
+ ...refusals(403, 404, 409)
137
153
  }
138
154
  },
139
155
  handler: async (req) => {
@@ -147,14 +163,14 @@ async function downloadsRoutes(app, container) {
147
163
  preHandler: app.requireOrgSession,
148
164
  schema: {
149
165
  operationId: "reorderDownloadAssets",
150
- tags: ["Downloads"],
151
- summary: "Reorder a download's assets (send the complete set)",
166
+ tags: ["Content"],
167
+ summary: "Reorder a download's files (staff)",
168
+ description: "Staff only (owner, admin, instructor). Send every file id of the download exactly once (409 otherwise). Returns the download's files.",
152
169
  params: DownloadIdParam,
153
170
  body: ReorderDownloadAssets,
154
171
  response: {
155
- 200: z.array(DownloadAsset),
156
- 404: ErrorBody,
157
- 409: ErrorBody
172
+ 200: z.array(DownloadFile),
173
+ ...refusals(403, 404, 409)
158
174
  }
159
175
  },
160
176
  handler: async (req) => {
@@ -168,13 +184,14 @@ async function downloadsRoutes(app, container) {
168
184
  preHandler: app.requireOrgSession,
169
185
  schema: {
170
186
  operationId: "renameDownloadAsset",
171
- tags: ["Downloads"],
172
- summary: "Set an asset's display name within a download",
187
+ tags: ["Content"],
188
+ summary: "Set a file's display name within a download (staff)",
189
+ description: "Staff only (owner, admin, instructor). Null falls back to the filename. Returns the download's files.",
173
190
  params: DownloadAssetParams,
174
191
  body: RenameDownloadAsset,
175
192
  response: {
176
- 200: z.array(DownloadAsset),
177
- 404: ErrorBody
193
+ 200: z.array(DownloadFile),
194
+ ...refusals(403, 404)
178
195
  }
179
196
  },
180
197
  handler: async (req) => {
@@ -188,12 +205,13 @@ async function downloadsRoutes(app, container) {
188
205
  preHandler: app.requireOrgSession,
189
206
  schema: {
190
207
  operationId: "removeDownloadAsset",
191
- tags: ["Downloads"],
192
- summary: "Unlink an asset from a download (the asset itself survives)",
208
+ tags: ["Content"],
209
+ summary: "Remove a file from a download (staff)",
210
+ description: "Staff only (owner, admin, instructor). The library file itself survives. Returns the download's files.",
193
211
  params: DownloadAssetParams,
194
212
  response: {
195
- 200: z.array(DownloadAsset),
196
- 404: ErrorBody
213
+ 200: z.array(DownloadFile),
214
+ ...refusals(403, 404)
197
215
  }
198
216
  },
199
217
  handler: async (req) => {