@headless-lms/server 0.1.0 → 0.2.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 (101) hide show
  1. package/README.md +1 -1
  2. package/dist/app/container.d.ts +7 -3
  3. package/dist/app/container.js +57 -26
  4. package/dist/app/container.js.map +1 -1
  5. package/dist/app/notifications.js +4 -1
  6. package/dist/app/notifications.js.map +1 -1
  7. package/dist/http/plugins/error-handler.d.ts +1 -1
  8. package/dist/http/plugins/error-handler.js +15 -4
  9. package/dist/http/plugins/error-handler.js.map +1 -1
  10. package/dist/http/plugins/openapi.js +25 -22
  11. package/dist/http/plugins/openapi.js.map +1 -1
  12. package/dist/http/routes/activities.js +34 -2
  13. package/dist/http/routes/activities.js.map +1 -1
  14. package/dist/http/routes/assets.js +4 -0
  15. package/dist/http/routes/assets.js.map +1 -1
  16. package/dist/http/routes/automations.js +32 -8
  17. package/dist/http/routes/automations.js.map +1 -1
  18. package/dist/http/routes/bundles.d.ts +7 -0
  19. package/dist/http/routes/bundles.js +170 -0
  20. package/dist/http/routes/bundles.js.map +1 -0
  21. package/dist/http/routes/course-analytics.d.ts +7 -0
  22. package/dist/http/routes/course-analytics.js +58 -0
  23. package/dist/http/routes/course-analytics.js.map +1 -0
  24. package/dist/http/routes/courses.js +8 -8
  25. package/dist/http/routes/courses.js.map +1 -1
  26. package/dist/http/routes/dashboard.js +2 -2
  27. package/dist/http/routes/dashboard.js.map +1 -1
  28. package/dist/http/routes/discussion.js +1 -1
  29. package/dist/http/routes/discussion.js.map +1 -1
  30. package/dist/http/routes/downloads.js +10 -10
  31. package/dist/http/routes/downloads.js.map +1 -1
  32. package/dist/http/routes/integrations.js.map +1 -1
  33. package/dist/http/routes/learn/assets.js +1 -1
  34. package/dist/http/routes/learn/assets.js.map +1 -1
  35. package/dist/http/routes/learn/comments.js +7 -7
  36. package/dist/http/routes/learn/comments.js.map +1 -1
  37. package/dist/http/routes/learn/courses.js +26 -3
  38. package/dist/http/routes/learn/courses.js.map +1 -1
  39. package/dist/http/routes/learn/downloads.js +3 -3
  40. package/dist/http/routes/learn/downloads.js.map +1 -1
  41. package/dist/http/routes/learn/org.js +2 -2
  42. package/dist/http/routes/learn/org.js.map +1 -1
  43. package/dist/http/routes/learn/progress.js +2 -2
  44. package/dist/http/routes/learn/progress.js.map +1 -1
  45. package/dist/http/routes/organizations.js.map +1 -1
  46. package/dist/http/routes/students.js +27 -6
  47. package/dist/http/routes/students.js.map +1 -1
  48. package/dist/http/routes/webhooks.d.ts +7 -0
  49. package/dist/http/routes/webhooks.js +159 -0
  50. package/dist/http/routes/webhooks.js.map +1 -0
  51. package/dist/http/routes.js +6 -0
  52. package/dist/http/routes.js.map +1 -1
  53. package/dist/http/schemas/activities.d.ts +34 -1
  54. package/dist/http/schemas/activities.js +7 -2
  55. package/dist/http/schemas/activities.js.map +1 -1
  56. package/dist/http/schemas/assets.d.ts +11 -7
  57. package/dist/http/schemas/assets.js.map +1 -1
  58. package/dist/http/schemas/automations.d.ts +75 -8
  59. package/dist/http/schemas/automations.js +13 -2
  60. package/dist/http/schemas/automations.js.map +1 -1
  61. package/dist/http/schemas/bundles.d.ts +63 -0
  62. package/dist/http/schemas/bundles.js +23 -0
  63. package/dist/http/schemas/bundles.js.map +1 -0
  64. package/dist/http/schemas/content.d.ts +18 -11
  65. package/dist/http/schemas/content.js +1 -2
  66. package/dist/http/schemas/content.js.map +1 -1
  67. package/dist/http/schemas/course-analytics.d.ts +29 -0
  68. package/dist/http/schemas/course-analytics.js +21 -0
  69. package/dist/http/schemas/course-analytics.js.map +1 -0
  70. package/dist/http/schemas/dashboard.js.map +1 -1
  71. package/dist/http/schemas/discussion.d.ts +9 -9
  72. package/dist/http/schemas/discussion.js.map +1 -1
  73. package/dist/http/schemas/downloads.d.ts +2 -0
  74. package/dist/http/schemas/downloads.js.map +1 -1
  75. package/dist/http/schemas/entitlements.d.ts +14 -6
  76. package/dist/http/schemas/entitlements.js +4 -2
  77. package/dist/http/schemas/entitlements.js.map +1 -1
  78. package/dist/http/schemas/index.d.ts +6 -4
  79. package/dist/http/schemas/index.js +6 -4
  80. package/dist/http/schemas/integrations.d.ts +4 -4
  81. package/dist/http/schemas/integrations.js.map +1 -1
  82. package/dist/http/schemas/invites.js +2 -1
  83. package/dist/http/schemas/invites.js.map +1 -1
  84. package/dist/http/schemas/learn.d.ts +11 -1
  85. package/dist/http/schemas/learn.js.map +1 -1
  86. package/dist/http/schemas/members.js.map +1 -1
  87. package/dist/http/schemas/organizations.js.map +1 -1
  88. package/dist/http/schemas/settings.js.map +1 -1
  89. package/dist/http/schemas/shared.d.ts +1 -0
  90. package/dist/http/schemas/shared.js +3 -1
  91. package/dist/http/schemas/shared.js.map +1 -1
  92. package/dist/http/schemas/students.d.ts +30 -2
  93. package/dist/http/schemas/students.js +21 -2
  94. package/dist/http/schemas/students.js.map +1 -1
  95. package/dist/http/server.js +4 -2
  96. package/dist/http/server.js.map +1 -1
  97. package/dist/index.d.ts +2 -2
  98. package/dist/index.js.map +1 -1
  99. package/dist/package.js +6 -0
  100. package/dist/package.js.map +1 -0
  101. package/package.json +5 -5
@@ -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 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: ['Content'],\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.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 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: ['Content'],\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: ['Content'],\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: ['Content'],\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.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',\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,YAAY,MAAM,OAAO,IAAI,KAAK;EACnD;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,KAAK,EAAE,MAAM,IAAI,KAAK,GAAG,wBAAwB;GACzD,OAAO,QAAQ,oBAAoB,MAAM,OAAO,IAAI,OAAO,IAAI,IAAI,IAAI;EACzE;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"}
@@ -10,7 +10,7 @@ async function dashboardRoutes(app, container) {
10
10
  preHandler: app.requireOrgSession,
11
11
  schema: {
12
12
  operationId: "getOverview",
13
- tags: ["Dashboard"],
13
+ tags: ["Reporting"],
14
14
  summary: "Back-office overview stats",
15
15
  response: { 200: OverviewStats }
16
16
  },
@@ -25,7 +25,7 @@ async function dashboardRoutes(app, container) {
25
25
  preHandler: app.requireOrgSession,
26
26
  schema: {
27
27
  operationId: "getEnrollmentSeries",
28
- tags: ["Dashboard"],
28
+ tags: ["Reporting"],
29
29
  summary: "Enrollments granted per day over a trailing window",
30
30
  querystring: EnrollmentSeriesQuery,
31
31
  response: { 200: EnrollmentSeries }
@@ -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 { 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: ['Reporting'],\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: ['Reporting'],\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"}
@@ -47,7 +47,7 @@ async function discussionRoutes(app, container) {
47
47
  url: "/api/comments/:commentId",
48
48
  preHandler: app.requireOrgSession,
49
49
  schema: {
50
- operationId: "editComment",
50
+ operationId: "publishComment",
51
51
  tags: ["Discussion"],
52
52
  summary: "Publish a comment: approves a pending one, restores a removed one",
53
53
  params: CommentIdParam,
@@ -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/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: 'publishComment',\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"}
@@ -14,7 +14,7 @@ async function downloadsRoutes(app, container) {
14
14
  preHandler: app.requireOrgSession,
15
15
  schema: {
16
16
  operationId: "listDownloads",
17
- tags: ["Downloads"],
17
+ tags: ["Content"],
18
18
  summary: "List downloads",
19
19
  querystring: DownloadsQuery,
20
20
  response: { 200: DownloadsPage }
@@ -30,7 +30,7 @@ async function downloadsRoutes(app, container) {
30
30
  preHandler: app.requireOrgSession,
31
31
  schema: {
32
32
  operationId: "getDownload",
33
- tags: ["Downloads"],
33
+ tags: ["Content"],
34
34
  summary: "Get a download by id",
35
35
  params: DownloadIdParam,
36
36
  response: {
@@ -51,7 +51,7 @@ async function downloadsRoutes(app, container) {
51
51
  preHandler: app.requireOrgSession,
52
52
  schema: {
53
53
  operationId: "createDownload",
54
- tags: ["Downloads"],
54
+ tags: ["Content"],
55
55
  summary: "Create a download",
56
56
  body: CreateDownload,
57
57
  response: { 201: Download }
@@ -68,7 +68,7 @@ async function downloadsRoutes(app, container) {
68
68
  preHandler: app.requireOrgSession,
69
69
  schema: {
70
70
  operationId: "updateDownload",
71
- tags: ["Downloads"],
71
+ tags: ["Content"],
72
72
  summary: "Update a download",
73
73
  params: DownloadIdParam,
74
74
  body: UpdateDownload,
@@ -88,7 +88,7 @@ async function downloadsRoutes(app, container) {
88
88
  preHandler: app.requireOrgSession,
89
89
  schema: {
90
90
  operationId: "deleteDownload",
91
- tags: ["Downloads"],
91
+ tags: ["Content"],
92
92
  summary: "Delete a download",
93
93
  params: DownloadIdParam,
94
94
  response: {
@@ -108,7 +108,7 @@ async function downloadsRoutes(app, container) {
108
108
  preHandler: app.requireOrgSession,
109
109
  schema: {
110
110
  operationId: "listDownloadAssets",
111
- tags: ["Downloads"],
111
+ tags: ["Content"],
112
112
  summary: "List a download's assets in order",
113
113
  params: DownloadIdParam,
114
114
  response: {
@@ -127,7 +127,7 @@ async function downloadsRoutes(app, container) {
127
127
  preHandler: app.requireOrgSession,
128
128
  schema: {
129
129
  operationId: "addDownloadAsset",
130
- tags: ["Downloads"],
130
+ tags: ["Content"],
131
131
  summary: "Link a media-library asset to a download",
132
132
  params: DownloadIdParam,
133
133
  body: AddDownloadAsset,
@@ -147,7 +147,7 @@ async function downloadsRoutes(app, container) {
147
147
  preHandler: app.requireOrgSession,
148
148
  schema: {
149
149
  operationId: "reorderDownloadAssets",
150
- tags: ["Downloads"],
150
+ tags: ["Content"],
151
151
  summary: "Reorder a download's assets (send the complete set)",
152
152
  params: DownloadIdParam,
153
153
  body: ReorderDownloadAssets,
@@ -168,7 +168,7 @@ async function downloadsRoutes(app, container) {
168
168
  preHandler: app.requireOrgSession,
169
169
  schema: {
170
170
  operationId: "renameDownloadAsset",
171
- tags: ["Downloads"],
171
+ tags: ["Content"],
172
172
  summary: "Set an asset's display name within a download",
173
173
  params: DownloadAssetParams,
174
174
  body: RenameDownloadAsset,
@@ -188,7 +188,7 @@ async function downloadsRoutes(app, container) {
188
188
  preHandler: app.requireOrgSession,
189
189
  schema: {
190
190
  operationId: "removeDownloadAsset",
191
- tags: ["Downloads"],
191
+ tags: ["Content"],
192
192
  summary: "Unlink an asset from a download (the asset itself survives)",
193
193
  params: DownloadAssetParams,
194
194
  response: {
@@ -1 +1 @@
1
- {"version":3,"file":"downloads.js","names":[],"sources":["../../../src/http/routes/downloads.ts"],"sourcesContent":["// HTTP routes for the download content type. Request + response are validated\n// against the shared contract schemas by the Zod type provider, and\n// @fastify/swagger reads the same schemas to build the OpenAPI spec.\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n AddDownloadAsset,\n Download,\n DownloadAsset,\n DownloadAssetParams,\n DownloadIdParam,\n DownloadsPage,\n DownloadsQuery,\n CreateDownload,\n ErrorBody,\n RenameDownloadAsset,\n ReorderDownloadAssets,\n UpdateDownload,\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 downloadsRoutes(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/downloads',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listDownloads',\n tags: ['Downloads'],\n summary: 'List downloads',\n querystring: DownloadsQuery,\n response: { 200: DownloadsPage },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.listDownloads(scope.orgId, req.query);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/downloads/:downloadId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getDownload',\n tags: ['Downloads'],\n summary: 'Get a download by id',\n params: DownloadIdParam,\n response: { 200: Download, 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const download = await content.getDownload(scope.orgId, req.params.downloadId);\n if (!download) {\n throw new NotFoundError('Download', req.params.downloadId);\n }\n return download;\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/downloads',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createDownload',\n tags: ['Downloads'],\n summary: 'Create a download',\n body: CreateDownload,\n response: { 201: Download },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const download = await content.createDownload(scope.orgId, req.body);\n return reply.code(201).send(download);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/downloads/:downloadId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateDownload',\n tags: ['Downloads'],\n summary: 'Update a download',\n params: DownloadIdParam,\n body: UpdateDownload,\n response: { 200: Download, 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.updateDownload(scope.orgId, req.params.downloadId, req.body);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/downloads/:downloadId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteDownload',\n tags: ['Downloads'],\n summary: 'Delete a download',\n params: DownloadIdParam,\n response: { 204: z.void(), 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n await content.removeDownload(scope.orgId, req.params.downloadId);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/downloads/:downloadId/assets',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listDownloadAssets',\n tags: ['Downloads'],\n summary: \"List a download's assets in order\",\n params: DownloadIdParam,\n response: { 200: z.array(DownloadAsset), 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.listDownloadAssets(scope.orgId, req.params.downloadId);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/downloads/:downloadId/assets',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'addDownloadAsset',\n tags: ['Downloads'],\n summary: 'Link a media-library asset to a download',\n params: DownloadIdParam,\n body: AddDownloadAsset,\n response: { 200: z.array(DownloadAsset), 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.addDownloadAsset(scope.orgId, req.params.downloadId, req.body);\n },\n });\n\n // Static segment before the parameterised sibling. Fastify's radix router\n // prefers static either way; the ordering documents the intent.\n r.route({\n method: 'PUT',\n url: '/api/downloads/:downloadId/assets/order',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reorderDownloadAssets',\n tags: ['Downloads'],\n summary: \"Reorder a download's assets (send the complete set)\",\n params: DownloadIdParam,\n body: ReorderDownloadAssets,\n response: { 200: z.array(DownloadAsset), 404: ErrorBody, 409: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.reorderDownloadAssets(\n scope.orgId,\n req.params.downloadId,\n req.body.assetIds,\n );\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/downloads/:downloadId/assets/:assetId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'renameDownloadAsset',\n tags: ['Downloads'],\n summary: \"Set an asset's display name within a download\",\n params: DownloadAssetParams,\n body: RenameDownloadAsset,\n response: { 200: z.array(DownloadAsset), 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.renameDownloadAsset(\n scope.orgId,\n req.params.downloadId,\n req.params.assetId,\n req.body.displayName,\n );\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/downloads/:downloadId/assets/:assetId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'removeDownloadAsset',\n tags: ['Downloads'],\n summary: 'Unlink an asset from a download (the asset itself survives)',\n params: DownloadAssetParams,\n response: { 200: z.array(DownloadAsset), 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.removeDownloadAsset(\n scope.orgId,\n req.params.downloadId,\n req.params.assetId,\n );\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"downloads.js","names":[],"sources":["../../../src/http/routes/downloads.ts"],"sourcesContent":["// HTTP routes for the download content type. Request + response are validated\n// against the shared contract schemas by the Zod type provider, and\n// @fastify/swagger reads the same schemas to build the OpenAPI spec.\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n AddDownloadAsset,\n Download,\n DownloadAsset,\n DownloadAssetParams,\n DownloadIdParam,\n DownloadsPage,\n DownloadsQuery,\n CreateDownload,\n ErrorBody,\n RenameDownloadAsset,\n ReorderDownloadAssets,\n UpdateDownload,\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 downloadsRoutes(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/downloads',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listDownloads',\n tags: ['Content'],\n summary: 'List downloads',\n querystring: DownloadsQuery,\n response: { 200: DownloadsPage },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.listDownloads(scope.orgId, req.query);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/downloads/:downloadId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getDownload',\n tags: ['Content'],\n summary: 'Get a download by id',\n params: DownloadIdParam,\n response: { 200: Download, 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const download = await content.getDownload(scope.orgId, req.params.downloadId);\n if (!download) {\n throw new NotFoundError('Download', req.params.downloadId);\n }\n return download;\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/downloads',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createDownload',\n tags: ['Content'],\n summary: 'Create a download',\n body: CreateDownload,\n response: { 201: Download },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const download = await content.createDownload(scope.orgId, req.body);\n return reply.code(201).send(download);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/downloads/:downloadId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateDownload',\n tags: ['Content'],\n summary: 'Update a download',\n params: DownloadIdParam,\n body: UpdateDownload,\n response: { 200: Download, 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.updateDownload(scope.orgId, req.params.downloadId, req.body);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/downloads/:downloadId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteDownload',\n tags: ['Content'],\n summary: 'Delete a download',\n params: DownloadIdParam,\n response: { 204: z.void(), 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n await content.removeDownload(scope.orgId, req.params.downloadId);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/downloads/:downloadId/assets',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listDownloadAssets',\n tags: ['Content'],\n summary: \"List a download's assets in order\",\n params: DownloadIdParam,\n response: { 200: z.array(DownloadAsset), 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.listDownloadAssets(scope.orgId, req.params.downloadId);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/downloads/:downloadId/assets',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'addDownloadAsset',\n tags: ['Content'],\n summary: 'Link a media-library asset to a download',\n params: DownloadIdParam,\n body: AddDownloadAsset,\n response: { 200: z.array(DownloadAsset), 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.addDownloadAsset(scope.orgId, req.params.downloadId, req.body);\n },\n });\n\n // Static segment before the parameterised sibling. Fastify's radix router\n // prefers static either way; the ordering documents the intent.\n r.route({\n method: 'PUT',\n url: '/api/downloads/:downloadId/assets/order',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reorderDownloadAssets',\n tags: ['Content'],\n summary: \"Reorder a download's assets (send the complete set)\",\n params: DownloadIdParam,\n body: ReorderDownloadAssets,\n response: { 200: z.array(DownloadAsset), 404: ErrorBody, 409: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.reorderDownloadAssets(scope.orgId, req.params.downloadId, req.body.assetIds);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/downloads/:downloadId/assets/:assetId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'renameDownloadAsset',\n tags: ['Content'],\n summary: \"Set an asset's display name within a download\",\n params: DownloadAssetParams,\n body: RenameDownloadAsset,\n response: { 200: z.array(DownloadAsset), 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.renameDownloadAsset(\n scope.orgId,\n req.params.downloadId,\n req.params.assetId,\n req.body.displayName,\n );\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/downloads/:downloadId/assets/:assetId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'removeDownloadAsset',\n tags: ['Content'],\n summary: 'Unlink an asset from a download (the asset itself survives)',\n params: DownloadAssetParams,\n response: { 200: z.array(DownloadAsset), 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.removeDownloadAsset(scope.orgId, req.params.downloadId, req.params.assetId);\n },\n });\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"integrations.js","names":[],"sources":["../../../src/http/routes/integrations.ts"],"sourcesContent":["// HTTP routes for integrations (an org's connections to external services).\n// All require a session with an active organization. Credentials are\n// write-only: accepted on connect/reconnect, never present in a response —\n// `toApi` strips the credentialRef so not even the reference leaks.\nimport type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport {\n ActionInvocationResult,\n AvailableIntegrationsList,\n ConfigureRequest,\n ConnectRequest,\n Connection,\n ConnectionActionParams,\n ConnectionIdParam,\n ConnectionsList,\n ErrorBody,\n InvokeActionRequest,\n ReconnectRequest,\n} from '../schemas/index.js';\nimport type { Connection as DomainConnection } from '@headless-lms/core/integrations';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../app/container.js';\n\nfunction toApi(connection: DomainConnection): Connection {\n const { credentialRef: _credentialRef, ...rest } = connection;\n return rest;\n}\n\n/** Resolve the session's active org to the domain org id, or 400 and return null. */\nasync function resolveOrgId(\n req: FastifyRequest,\n reply: FastifyReply,\n): Promise<string | null> {\n if (!req.orgId) {\n await reply.code(400).send({ error: 'no_active_org', message: 'No active organization' });\n return null;\n }\n return req.orgId;\n}\n\nexport async function integrationsRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const integrations = container.integrations;\n const tags = ['Integrations'];\n\n r.route({\n method: 'GET',\n url: '/api/integrations/available',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listAvailableIntegrations',\n tags,\n summary: 'List the integrations this deployment supports, with their config schemas',\n response: { 200: AvailableIntegrationsList, 401: ErrorBody },\n },\n handler: async () => integrations.available(),\n });\n\n r.route({\n method: 'GET',\n url: '/api/integrations',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listConnections',\n tags,\n summary: \"List the organization's integration connections\",\n response: { 200: ConnectionsList, 400: ErrorBody, 401: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n return (await integrations.list(orgId)).map(toApi);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/integrations',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'connectIntegration',\n tags,\n summary: 'Connect an integration (stores its secrets encrypted)',\n body: ConnectRequest,\n response: { 201: Connection, 400: ErrorBody, 401: ErrorBody, 409: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const connection = await integrations.connect(orgId, req.body);\n return reply.code(201).send(toApi(connection));\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/integrations/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getConnection',\n tags,\n summary: 'Get a connection',\n params: ConnectionIdParam,\n response: { 200: Connection, 400: ErrorBody, 401: ErrorBody, 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const connection = await integrations.get(orgId, req.params.id);\n if (!connection) {\n throw new NotFoundError('Connection', req.params.id);\n }\n return toApi(connection);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/integrations/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'configureConnection',\n tags,\n summary: \"Change a connection's configuration or active flag\",\n params: ConnectionIdParam,\n body: ConfigureRequest,\n response: { 200: Connection, 400: ErrorBody, 401: ErrorBody, 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const connection = await integrations.configure(orgId, req.params.id, req.body);\n if (!connection) {\n throw new NotFoundError('Connection', req.params.id);\n }\n return toApi(connection);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/integrations/:id/reconnect',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reconnectIntegration',\n tags,\n summary: \"Replace a connection's secrets (re-authenticate)\",\n params: ConnectionIdParam,\n body: ReconnectRequest,\n response: { 200: Connection, 400: ErrorBody, 401: ErrorBody, 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const connection = await integrations.reconnect(orgId, req.params.id, req.body.secrets);\n if (!connection) {\n throw new NotFoundError('Connection', req.params.id);\n }\n return toApi(connection);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/integrations/:id/actions/:actionId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'invokeConnectionAction',\n tags,\n summary: \"Invoke one of a connection's actions (e.g. a listing that feeds a picker)\",\n params: ConnectionActionParams,\n body: InvokeActionRequest,\n response: {\n 200: ActionInvocationResult,\n 400: ErrorBody,\n 401: ErrorBody,\n 404: ErrorBody,\n 409: ErrorBody,\n 502: ErrorBody,\n },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const output = await integrations.invoke(\n orgId,\n req.params.id,\n req.params.actionId,\n req.body.input ?? {},\n );\n return { output: output as Record<string, unknown> };\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/integrations/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'disconnectIntegration',\n tags,\n summary: 'Disconnect an integration (destroys its stored secrets)',\n params: ConnectionIdParam,\n response: { 204: z.void(), 400: ErrorBody, 401: ErrorBody, 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const removed = await integrations.disconnect(orgId, req.params.id);\n if (!removed) {\n throw new NotFoundError('Connection', req.params.id);\n }\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"integrations.js","names":[],"sources":["../../../src/http/routes/integrations.ts"],"sourcesContent":["// HTTP routes for integrations (an org's connections to external services).\n// All require a session with an active organization. Credentials are\n// write-only: accepted on connect/reconnect, never present in a response —\n// `toApi` strips the credentialRef so not even the reference leaks.\nimport type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport {\n ActionInvocationResult,\n AvailableIntegrationsList,\n ConfigureRequest,\n ConnectRequest,\n Connection,\n ConnectionActionParams,\n ConnectionIdParam,\n ConnectionsList,\n ErrorBody,\n InvokeActionRequest,\n ReconnectRequest,\n} from '../schemas/index.js';\nimport type { Connection as DomainConnection } from '@headless-lms/core/integrations';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../app/container.js';\n\nfunction toApi(connection: DomainConnection): Connection {\n const { credentialRef: _credentialRef, ...rest } = connection;\n return rest;\n}\n\n/** Resolve the session's active org to the domain org id, or 400 and return null. */\nasync function resolveOrgId(req: FastifyRequest, reply: FastifyReply): Promise<string | null> {\n if (!req.orgId) {\n await reply.code(400).send({ error: 'no_active_org', message: 'No active organization' });\n return null;\n }\n return req.orgId;\n}\n\nexport async function integrationsRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const integrations = container.integrations;\n const tags = ['Integrations'];\n\n r.route({\n method: 'GET',\n url: '/api/integrations/available',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listAvailableIntegrations',\n tags,\n summary: 'List the integrations this deployment supports, with their config schemas',\n response: { 200: AvailableIntegrationsList, 401: ErrorBody },\n },\n handler: async () => integrations.available(),\n });\n\n r.route({\n method: 'GET',\n url: '/api/integrations',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listConnections',\n tags,\n summary: \"List the organization's integration connections\",\n response: { 200: ConnectionsList, 400: ErrorBody, 401: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n return (await integrations.list(orgId)).map(toApi);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/integrations',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'connectIntegration',\n tags,\n summary: 'Connect an integration (stores its secrets encrypted)',\n body: ConnectRequest,\n response: { 201: Connection, 400: ErrorBody, 401: ErrorBody, 409: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const connection = await integrations.connect(orgId, req.body);\n return reply.code(201).send(toApi(connection));\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/integrations/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getConnection',\n tags,\n summary: 'Get a connection',\n params: ConnectionIdParam,\n response: { 200: Connection, 400: ErrorBody, 401: ErrorBody, 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const connection = await integrations.get(orgId, req.params.id);\n if (!connection) {\n throw new NotFoundError('Connection', req.params.id);\n }\n return toApi(connection);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/integrations/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'configureConnection',\n tags,\n summary: \"Change a connection's configuration or active flag\",\n params: ConnectionIdParam,\n body: ConfigureRequest,\n response: { 200: Connection, 400: ErrorBody, 401: ErrorBody, 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const connection = await integrations.configure(orgId, req.params.id, req.body);\n if (!connection) {\n throw new NotFoundError('Connection', req.params.id);\n }\n return toApi(connection);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/integrations/:id/reconnect',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reconnectIntegration',\n tags,\n summary: \"Replace a connection's secrets (re-authenticate)\",\n params: ConnectionIdParam,\n body: ReconnectRequest,\n response: { 200: Connection, 400: ErrorBody, 401: ErrorBody, 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const connection = await integrations.reconnect(orgId, req.params.id, req.body.secrets);\n if (!connection) {\n throw new NotFoundError('Connection', req.params.id);\n }\n return toApi(connection);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/integrations/:id/actions/:actionId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'invokeConnectionAction',\n tags,\n summary: \"Invoke one of a connection's actions (e.g. a listing that feeds a picker)\",\n params: ConnectionActionParams,\n body: InvokeActionRequest,\n response: {\n 200: ActionInvocationResult,\n 400: ErrorBody,\n 401: ErrorBody,\n 404: ErrorBody,\n 409: ErrorBody,\n 502: ErrorBody,\n },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const output = await integrations.invoke(\n orgId,\n req.params.id,\n req.params.actionId,\n req.body.input ?? {},\n );\n return { output: output as Record<string, unknown> };\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/integrations/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'disconnectIntegration',\n tags,\n summary: 'Disconnect an integration (destroys its stored secrets)',\n params: ConnectionIdParam,\n response: { 204: z.void(), 400: ErrorBody, 401: ErrorBody, 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const removed = await integrations.disconnect(orgId, req.params.id);\n if (!removed) {\n throw new NotFoundError('Connection', req.params.id);\n }\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":"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"}
@@ -10,7 +10,7 @@ async function learnAssetsRoutes(app, container) {
10
10
  preHandler: app.requireOrgSession,
11
11
  schema: {
12
12
  operationId: "getAssetDownloadUrl",
13
- tags: ["Learn"],
13
+ tags: ["Assets"],
14
14
  summary: "Get a short-lived presigned URL to serve an asset to the student",
15
15
  params: AssetIdParam,
16
16
  response: {
@@ -1 +1 @@
1
- {"version":3,"file":"assets.js","names":[],"sources":["../../../../src/http/routes/learn/assets.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { AssetIdParam, DownloadTicket, ErrorBody } from '../../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '@headless-lms/server';\n\nexport async function learnAssetsRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n\n // Content embeds assets by stable `assetId`; the URL persisted at authoring\n // time is a long-expired presign. The student surface mints a fresh\n // short-lived ticket here, scoped to the session's portal org.\n r.route({\n method: 'POST',\n url: '/api/learn/assets/:id/url',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getAssetDownloadUrl',\n tags: ['Learn'],\n summary: 'Get a short-lived presigned URL to serve an asset to the student',\n params: AssetIdParam,\n response: { 200: DownloadTicket, 401: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n\n // No body: the student surface never renames a download, so the asset's\n // own filename stands.\n const ticket = await container.assets.requestDownload(req.orgId, req.params.id);\n if (!ticket) {\n throw new NotFoundError('Asset', req.params.id);\n }\n return ticket;\n },\n });\n}\n"],"mappings":";;;;;AAMA,eAAsB,kBAAkB,KAAsB,WAAqC;CAMjG,IALc,iBAKd,CAAC,CAAC,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,OAAO;GACd,SAAS;GACT,QAAQ;GACR,UAAU;IAAE,KAAK;IAAgB,KAAK;IAAW,KAAK;GAAU;EAClE;EACA,SAAS,OAAO,QAAQ;GAItB,MAAM,SAAS,MAAM,UAAU,OAAO,gBAAgB,IAAI,OAAO,IAAI,OAAO,EAAE;GAC9E,IAAI,CAAC,QACH,MAAM,IAAI,cAAc,SAAS,IAAI,OAAO,EAAE;GAEhD,OAAO;EACT;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"assets.js","names":[],"sources":["../../../../src/http/routes/learn/assets.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { AssetIdParam, DownloadTicket, ErrorBody } from '../../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '@headless-lms/server';\n\nexport async function learnAssetsRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n\n // Content embeds assets by stable `assetId`; the URL persisted at authoring\n // time is a long-expired presign. The student surface mints a fresh\n // short-lived ticket here, scoped to the session's portal org.\n r.route({\n method: 'POST',\n url: '/api/learn/assets/:id/url',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getAssetDownloadUrl',\n tags: ['Assets'],\n summary: 'Get a short-lived presigned URL to serve an asset to the student',\n params: AssetIdParam,\n response: { 200: DownloadTicket, 401: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n // No body: the student surface never renames a download, so the asset's\n // own filename stands.\n const ticket = await container.assets.requestDownload(req.orgId, req.params.id);\n if (!ticket) {\n throw new NotFoundError('Asset', req.params.id);\n }\n return ticket;\n },\n });\n}\n"],"mappings":";;;;;AAMA,eAAsB,kBAAkB,KAAsB,WAAqC;CAMjG,IALc,iBAKd,CAAC,CAAC,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,QAAQ;GACf,SAAS;GACT,QAAQ;GACR,UAAU;IAAE,KAAK;IAAgB,KAAK;IAAW,KAAK;GAAU;EAClE;EACA,SAAS,OAAO,QAAQ;GAGtB,MAAM,SAAS,MAAM,UAAU,OAAO,gBAAgB,IAAI,OAAO,IAAI,OAAO,EAAE;GAC9E,IAAI,CAAC,QACH,MAAM,IAAI,cAAc,SAAS,IAAI,OAAO,EAAE;GAEhD,OAAO;EACT;CACF,CAAC;AACH"}
@@ -12,7 +12,7 @@ async function learnCommentsRoutes(app, container) {
12
12
  preHandler: app.requireOrgSession,
13
13
  schema: {
14
14
  operationId: "listActivityComments",
15
- tags: ["Learn"],
15
+ tags: ["Discussion"],
16
16
  summary: "Read an activity's comments",
17
17
  params: DiscussionActivityParam,
18
18
  response: {
@@ -33,7 +33,7 @@ async function learnCommentsRoutes(app, container) {
33
33
  preHandler: app.requireOrgSession,
34
34
  schema: {
35
35
  operationId: "postComment",
36
- tags: ["Learn"],
36
+ tags: ["Discussion"],
37
37
  summary: "Post a comment or reply on an activity",
38
38
  params: DiscussionActivityParam,
39
39
  body: PostComment,
@@ -61,7 +61,7 @@ async function learnCommentsRoutes(app, container) {
61
61
  preHandler: app.requireOrgSession,
62
62
  schema: {
63
63
  operationId: "editComment",
64
- tags: ["Learn"],
64
+ tags: ["Discussion"],
65
65
  summary: "Revise your own comment",
66
66
  params: CommentIdParam,
67
67
  body: EditComment,
@@ -84,7 +84,7 @@ async function learnCommentsRoutes(app, container) {
84
84
  preHandler: app.requireOrgSession,
85
85
  schema: {
86
86
  operationId: "deleteComment",
87
- tags: ["Learn"],
87
+ tags: ["Discussion"],
88
88
  summary: "Delete a comment",
89
89
  params: CommentIdParam,
90
90
  response: {
@@ -106,7 +106,7 @@ async function learnCommentsRoutes(app, container) {
106
106
  preHandler: app.requireOrgSession,
107
107
  schema: {
108
108
  operationId: "setCommentReaction",
109
- tags: ["Learn"],
109
+ tags: ["Discussion"],
110
110
  summary: "Set your reaction to a comment, replacing any you already had",
111
111
  params: CommentIdParam,
112
112
  body: SetCommentReaction,
@@ -129,7 +129,7 @@ async function learnCommentsRoutes(app, container) {
129
129
  preHandler: app.requireOrgSession,
130
130
  schema: {
131
131
  operationId: "clearCommentReaction",
132
- tags: ["Learn"],
132
+ tags: ["Discussion"],
133
133
  summary: "Withdraw your reaction from a comment",
134
134
  params: CommentIdParam,
135
135
  response: {
@@ -151,7 +151,7 @@ async function learnCommentsRoutes(app, container) {
151
151
  preHandler: app.requireOrgSession,
152
152
  schema: {
153
153
  operationId: "reportComment",
154
- tags: ["Learn"],
154
+ tags: ["Discussion"],
155
155
  summary: "Flag a comment for moderator attention",
156
156
  params: CommentIdParam,
157
157
  body: ReportComment,
@@ -1 +1 @@
1
- {"version":3,"file":"comments.js","names":[],"sources":["../../../../src/http/routes/learn/comments.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n ActivityComments,\n Comment,\n CommentIdParam,\n CommentReactions,\n CommentReport,\n CommentView,\n DiscussionActivityParam,\n EditComment,\n ErrorBody,\n PostComment,\n ReportComment,\n SetCommentReaction,\n} from '../../schemas/index.js';\nimport type { Container } from '../../../app/container.js';\nimport { UnauthorizedError } from '../../plugins/auth.js';\n\nexport async function learnCommentsRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const discussion = container.discussion;\n\n r.route({\n method: 'GET',\n url: '/api/learn/activities/:activityId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listActivityComments',\n tags: ['Learn'],\n summary: \"Read an activity's comments\",\n params: DiscussionActivityParam,\n response: { 200: ActivityComments, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.activityComments(orgId, req.params.activityId, orgUser);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/learn/activities/:activityId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'postComment',\n tags: ['Learn'],\n summary: 'Post a comment or reply on an activity',\n params: DiscussionActivityParam,\n body: PostComment,\n response: { 200: CommentView, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.postComment(orgId, {\n actor: orgUser,\n activityId: req.params.activityId,\n parentId: req.body.parentId,\n body: req.body.body,\n });\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/learn/comments/:commentId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'editComment',\n tags: ['Learn'],\n summary: 'Revise your own comment',\n params: CommentIdParam,\n body: EditComment,\n response: { 200: CommentView, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.edit(orgId, req.params.commentId, orgUser, req.body.body);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/learn/comments/:commentId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteComment',\n tags: ['Learn'],\n summary: 'Delete a comment',\n params: CommentIdParam,\n response: { 200: Comment, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.remove(orgId, req.params.commentId, orgUser);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/learn/comments/:commentId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setCommentReaction',\n tags: ['Learn'],\n summary: 'Set your reaction to a comment, replacing any you already had',\n params: CommentIdParam,\n body: SetCommentReaction,\n response: { 200: CommentReactions, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.setReaction(orgId, req.params.commentId, orgUser, req.body.emoji);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/learn/comments/:commentId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'clearCommentReaction',\n tags: ['Learn'],\n summary: 'Withdraw your reaction from a comment',\n params: CommentIdParam,\n response: { 200: CommentReactions, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.setReaction(orgId, req.params.commentId, orgUser, null);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/learn/comments/:commentId/reports',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reportComment',\n tags: ['Learn'],\n summary: 'Flag a comment for moderator attention',\n params: CommentIdParam,\n body: ReportComment,\n response: { 200: CommentReport, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.reportComment(orgId, req.params.commentId, orgUser, req.body.reason);\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"comments.js","names":[],"sources":["../../../../src/http/routes/learn/comments.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n ActivityComments,\n Comment,\n CommentIdParam,\n CommentReactions,\n CommentReport,\n CommentView,\n DiscussionActivityParam,\n EditComment,\n ErrorBody,\n PostComment,\n ReportComment,\n SetCommentReaction,\n} from '../../schemas/index.js';\nimport type { Container } from '../../../app/container.js';\nimport { UnauthorizedError } from '../../plugins/auth.js';\n\nexport async function learnCommentsRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const discussion = container.discussion;\n\n r.route({\n method: 'GET',\n url: '/api/learn/activities/:activityId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listActivityComments',\n tags: ['Discussion'],\n summary: \"Read an activity's comments\",\n params: DiscussionActivityParam,\n response: { 200: ActivityComments, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.activityComments(orgId, req.params.activityId, orgUser);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/learn/activities/:activityId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'postComment',\n tags: ['Discussion'],\n summary: 'Post a comment or reply on an activity',\n params: DiscussionActivityParam,\n body: PostComment,\n response: { 200: CommentView, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.postComment(orgId, {\n actor: orgUser,\n activityId: req.params.activityId,\n parentId: req.body.parentId,\n body: req.body.body,\n });\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/learn/comments/:commentId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'editComment',\n tags: ['Discussion'],\n summary: 'Revise your own comment',\n params: CommentIdParam,\n body: EditComment,\n response: { 200: CommentView, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.edit(orgId, req.params.commentId, orgUser, req.body.body);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/learn/comments/:commentId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteComment',\n tags: ['Discussion'],\n summary: 'Delete a comment',\n params: CommentIdParam,\n response: { 200: Comment, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.remove(orgId, req.params.commentId, orgUser);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/learn/comments/:commentId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setCommentReaction',\n tags: ['Discussion'],\n summary: 'Set your reaction to a comment, replacing any you already had',\n params: CommentIdParam,\n body: SetCommentReaction,\n response: { 200: CommentReactions, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.setReaction(orgId, req.params.commentId, orgUser, req.body.emoji);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/learn/comments/:commentId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'clearCommentReaction',\n tags: ['Discussion'],\n summary: 'Withdraw your reaction from a comment',\n params: CommentIdParam,\n response: { 200: CommentReactions, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.setReaction(orgId, req.params.commentId, orgUser, null);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/learn/comments/:commentId/reports',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reportComment',\n tags: ['Discussion'],\n summary: 'Flag a comment for moderator attention',\n params: CommentIdParam,\n body: ReportComment,\n response: { 200: CommentReport, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.reportComment(orgId, req.params.commentId, orgUser, req.body.reason);\n },\n });\n}\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { UnauthorizedError } from "../../plugins/auth.js";
2
2
  import { ErrorBody } from "../../schemas/shared.js";
3
3
  import { Course } from "../../schemas/content.js";
4
+ import { ActivityList } from "../../schemas/activities.js";
4
5
  import { LearnCourseIdParam, LearnCourses, LearnModules } from "../../schemas/learn.js";
5
6
  import "../../schemas/index.js";
6
7
  import { NotFoundError } from "@headless-lms/core/shared/errors";
@@ -14,7 +15,7 @@ async function learnCoursesRoutes(app, container) {
14
15
  preHandler: app.requireOrgSession,
15
16
  schema: {
16
17
  operationId: "listLearnCourses",
17
- tags: ["Learn"],
18
+ tags: ["Content"],
18
19
  summary: "List the student's enrolled courses",
19
20
  response: { 200: LearnCourses }
20
21
  },
@@ -30,7 +31,7 @@ async function learnCoursesRoutes(app, container) {
30
31
  preHandler: app.requireOrgSession,
31
32
  schema: {
32
33
  operationId: "getLearnCourse",
33
- tags: ["Learn"],
34
+ tags: ["Content"],
34
35
  summary: "Get one enrolled course",
35
36
  params: LearnCourseIdParam,
36
37
  response: {
@@ -52,7 +53,7 @@ async function learnCoursesRoutes(app, container) {
52
53
  preHandler: app.requireOrgSession,
53
54
  schema: {
54
55
  operationId: "listLearnModules",
55
- tags: ["Learn"],
56
+ tags: ["Content"],
56
57
  summary: "List an enrolled course's module/activity tree",
57
58
  params: LearnCourseIdParam,
58
59
  response: {
@@ -68,6 +69,28 @@ async function learnCoursesRoutes(app, container) {
68
69
  return modules;
69
70
  }
70
71
  });
72
+ r.route({
73
+ method: "GET",
74
+ url: "/api/learn/courses/:courseId/activities",
75
+ preHandler: app.requireOrgSession,
76
+ schema: {
77
+ operationId: "listLearnActivities",
78
+ tags: ["Content"],
79
+ summary: "List an enrolled course's published activities",
80
+ params: LearnCourseIdParam,
81
+ response: {
82
+ 200: ActivityList,
83
+ 404: ErrorBody
84
+ }
85
+ },
86
+ handler: async (req) => {
87
+ const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);
88
+ if (!orgUser) throw new UnauthorizedError();
89
+ const activities = await learn.listActivities(req.orgId, orgUser.id, req.params.courseId);
90
+ if (!activities) throw new NotFoundError("Course", req.params.courseId);
91
+ return activities;
92
+ }
93
+ });
71
94
  }
72
95
  //#endregion
73
96
  export { learnCoursesRoutes };
@@ -1 +1 @@
1
- {"version":3,"file":"courses.js","names":[],"sources":["../../../../src/http/routes/learn/courses.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n Course,\n ErrorBody,\n LearnCourseIdParam,\n LearnCourses,\n LearnModules,\n} from '../../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../../app/container.js';\nimport { UnauthorizedError } from '../../plugins/auth.js';\n\nexport async function learnCoursesRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const learn = container.reporting.learn;\n\n r.route({\n method: 'GET',\n url: '/api/learn/courses',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listLearnCourses',\n tags: ['Learn'],\n summary: \"List the student's enrolled courses\",\n response: { 200: LearnCourses },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return learn.listCourses(req.orgId, orgUser.id);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/learn/courses/:courseId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getLearnCourse',\n tags: ['Learn'],\n summary: 'Get one enrolled course',\n params: LearnCourseIdParam,\n response: { 200: Course, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n const course = await learn.getCourse(req.orgId, orgUser.id, req.params.courseId);\n if (!course) {\n throw new NotFoundError('Course', req.params.courseId);\n }\n return course;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/learn/courses/:courseId/modules',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listLearnModules',\n tags: ['Learn'],\n summary: \"List an enrolled course's module/activity tree\",\n params: LearnCourseIdParam,\n response: { 200: LearnModules, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n const modules = await learn.listModules(req.orgId, orgUser.id, req.params.courseId);\n if (!modules) {\n throw new NotFoundError('Course', req.params.courseId);\n }\n return modules;\n },\n });\n}\n"],"mappings":";;;;;;;AAaA,eAAsB,mBACpB,KACA,WACe;CACf,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,QAAQ,UAAU,UAAU;CAElC,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,OAAO;GACd,SAAS;GACT,UAAU,EAAE,KAAK,aAAa;EAChC;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB;GAE9B,OAAO,MAAM,YAAY,IAAI,OAAO,QAAQ,EAAE;EAChD;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,OAAO;GACd,SAAS;GACT,QAAQ;GACR,UAAU;IAAE,KAAK;IAAQ,KAAK;GAAU;EAC1C;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB;GAE9B,MAAM,SAAS,MAAM,MAAM,UAAU,IAAI,OAAO,QAAQ,IAAI,IAAI,OAAO,QAAQ;GAC/E,IAAI,CAAC,QACH,MAAM,IAAI,cAAc,UAAU,IAAI,OAAO,QAAQ;GAEvD,OAAO;EACT;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,OAAO;GACd,SAAS;GACT,QAAQ;GACR,UAAU;IAAE,KAAK;IAAc,KAAK;GAAU;EAChD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB;GAE9B,MAAM,UAAU,MAAM,MAAM,YAAY,IAAI,OAAO,QAAQ,IAAI,IAAI,OAAO,QAAQ;GAClF,IAAI,CAAC,SACH,MAAM,IAAI,cAAc,UAAU,IAAI,OAAO,QAAQ;GAEvD,OAAO;EACT;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"courses.js","names":[],"sources":["../../../../src/http/routes/learn/courses.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n ActivityList,\n Course,\n ErrorBody,\n LearnCourseIdParam,\n LearnCourses,\n LearnModules,\n} from '../../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../../app/container.js';\nimport { UnauthorizedError } from '../../plugins/auth.js';\n\nexport async function learnCoursesRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const learn = container.reporting.learn;\n\n r.route({\n method: 'GET',\n url: '/api/learn/courses',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listLearnCourses',\n tags: ['Content'],\n summary: \"List the student's enrolled courses\",\n response: { 200: LearnCourses },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return learn.listCourses(req.orgId, orgUser.id);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/learn/courses/:courseId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getLearnCourse',\n tags: ['Content'],\n summary: 'Get one enrolled course',\n params: LearnCourseIdParam,\n response: { 200: Course, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n const course = await learn.getCourse(req.orgId, orgUser.id, req.params.courseId);\n if (!course) {\n throw new NotFoundError('Course', req.params.courseId);\n }\n return course;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/learn/courses/:courseId/modules',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listLearnModules',\n tags: ['Content'],\n summary: \"List an enrolled course's module/activity tree\",\n params: LearnCourseIdParam,\n response: { 200: LearnModules, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n const modules = await learn.listModules(req.orgId, orgUser.id, req.params.courseId);\n if (!modules) {\n throw new NotFoundError('Course', req.params.courseId);\n }\n return modules;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/learn/courses/:courseId/activities',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listLearnActivities',\n tags: ['Content'],\n summary: \"List an enrolled course's published activities\",\n params: LearnCourseIdParam,\n response: { 200: ActivityList, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n const activities = await learn.listActivities(req.orgId, orgUser.id, req.params.courseId);\n if (!activities) {\n throw new NotFoundError('Course', req.params.courseId);\n }\n return activities;\n },\n });\n}\n"],"mappings":";;;;;;;;AAcA,eAAsB,mBACpB,KACA,WACe;CACf,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,QAAQ,UAAU,UAAU;CAElC,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,UAAU,EAAE,KAAK,aAAa;EAChC;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB;GAE9B,OAAO,MAAM,YAAY,IAAI,OAAO,QAAQ,EAAE;EAChD;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,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB;GAE9B,MAAM,SAAS,MAAM,MAAM,UAAU,IAAI,OAAO,QAAQ,IAAI,IAAI,OAAO,QAAQ;GAC/E,IAAI,CAAC,QACH,MAAM,IAAI,cAAc,UAAU,IAAI,OAAO,QAAQ;GAEvD,OAAO;EACT;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,QAAQ;GACR,UAAU;IAAE,KAAK;IAAc,KAAK;GAAU;EAChD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB;GAE9B,MAAM,UAAU,MAAM,MAAM,YAAY,IAAI,OAAO,QAAQ,IAAI,IAAI,OAAO,QAAQ;GAClF,IAAI,CAAC,SACH,MAAM,IAAI,cAAc,UAAU,IAAI,OAAO,QAAQ;GAEvD,OAAO;EACT;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,QAAQ;GACR,UAAU;IAAE,KAAK;IAAc,KAAK;GAAU;EAChD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB;GAE9B,MAAM,aAAa,MAAM,MAAM,eAAe,IAAI,OAAO,QAAQ,IAAI,IAAI,OAAO,QAAQ;GACxF,IAAI,CAAC,YACH,MAAM,IAAI,cAAc,UAAU,IAAI,OAAO,QAAQ;GAEvD,OAAO;EACT;CACF,CAAC;AACH"}
@@ -15,7 +15,7 @@ async function learnDownloadsRoutes(app, container) {
15
15
  preHandler: app.requireOrgSession,
16
16
  schema: {
17
17
  operationId: "listLearnDownloads",
18
- tags: ["Learn"],
18
+ tags: ["Content"],
19
19
  summary: "Downloads the student is actively entitled to",
20
20
  response: { 200: LearnDownloads }
21
21
  },
@@ -31,7 +31,7 @@ async function learnDownloadsRoutes(app, container) {
31
31
  preHandler: app.requireOrgSession,
32
32
  schema: {
33
33
  operationId: "getLearnDownload",
34
- tags: ["Learn"],
34
+ tags: ["Content"],
35
35
  summary: "One entitled download and its ordered assets",
36
36
  params: DownloadIdParam,
37
37
  response: {
@@ -53,7 +53,7 @@ async function learnDownloadsRoutes(app, container) {
53
53
  preHandler: app.requireOrgSession,
54
54
  schema: {
55
55
  operationId: "getLearnDownloadAsset",
56
- tags: ["Learn"],
56
+ tags: ["Content"],
57
57
  summary: "Redirect to a short-lived signed URL for an entitled asset",
58
58
  params: DownloadAssetParams,
59
59
  response: {
@@ -1 +1 @@
1
- {"version":3,"file":"downloads.js","names":[],"sources":["../../../../src/http/routes/learn/downloads.ts"],"sourcesContent":["import { z } from 'zod';\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n DownloadAssetParams,\n DownloadIdParam,\n ErrorBody,\n LearnDownload,\n LearnDownloads,\n} from '../../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../../app/container.js';\nimport { UnauthorizedError } from '../../plugins/auth.js';\n\nexport async function learnDownloadsRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const learn = container.reporting.learn;\n\n r.route({\n method: 'GET',\n url: '/api/learn/downloads',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listLearnDownloads',\n tags: ['Learn'],\n summary: 'Downloads the student is actively entitled to',\n response: { 200: LearnDownloads },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return learn.listDownloads(req.orgId, orgUser.id);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/learn/downloads/:downloadId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getLearnDownload',\n tags: ['Learn'],\n summary: 'One entitled download and its ordered assets',\n params: DownloadIdParam,\n response: { 200: LearnDownload, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n const result = await learn.getDownload(req.orgId, orgUser.id, req.params.downloadId);\n if (!result) {\n throw new NotFoundError('Download', req.params.downloadId);\n }\n return result;\n },\n });\n\n // One stable, resolvable URL per asset. Drops into an <a href download> in\n // any frontend — no JS, no SDK, no CORS preflight. The signed target is\n // minted per request and short-lived.\n r.route({\n method: 'GET',\n url: '/api/learn/downloads/:downloadId/assets/:assetId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getLearnDownloadAsset',\n tags: ['Learn'],\n summary: 'Redirect to a short-lived signed URL for an entitled asset',\n params: DownloadAssetParams,\n response: { 302: z.void(), 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 UnauthorizedError();\n }\n const signed = await learn.downloadAssetUrl(\n req.orgId,\n orgUser.id,\n req.params.downloadId,\n req.params.assetId,\n );\n if (!signed) {\n // Always 404, never 403 — a 403 confirms the resource exists to\n // someone not entitled to it.\n throw new NotFoundError('Download asset', req.params.assetId);\n }\n return reply\n .header('cache-control', 'no-store')\n .header('referrer-policy', 'no-referrer')\n .redirect(signed.url, 302);\n },\n });\n}\n"],"mappings":";;;;;;;;AAcA,eAAsB,qBACpB,KACA,WACe;CACf,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,QAAQ,UAAU,UAAU;CAElC,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,OAAO;GACd,SAAS;GACT,UAAU,EAAE,KAAK,eAAe;EAClC;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB;GAE9B,OAAO,MAAM,cAAc,IAAI,OAAO,QAAQ,EAAE;EAClD;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,OAAO;GACd,SAAS;GACT,QAAQ;GACR,UAAU;IAAE,KAAK;IAAe,KAAK;GAAU;EACjD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB;GAE9B,MAAM,SAAS,MAAM,MAAM,YAAY,IAAI,OAAO,QAAQ,IAAI,IAAI,OAAO,UAAU;GACnF,IAAI,CAAC,QACH,MAAM,IAAI,cAAc,YAAY,IAAI,OAAO,UAAU;GAE3D,OAAO;EACT;CACF,CAAC;CAKD,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,OAAO;GACd,SAAS;GACT,QAAQ;GACR,UAAU;IAAE,KAAK,EAAE,KAAK;IAAG,KAAK;GAAU;EAC5C;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB;GAE9B,MAAM,SAAS,MAAM,MAAM,iBACzB,IAAI,OACJ,QAAQ,IACR,IAAI,OAAO,YACX,IAAI,OAAO,OACb;GACA,IAAI,CAAC,QAGH,MAAM,IAAI,cAAc,kBAAkB,IAAI,OAAO,OAAO;GAE9D,OAAO,MACJ,OAAO,iBAAiB,UAAU,CAAC,CACnC,OAAO,mBAAmB,aAAa,CAAC,CACxC,SAAS,OAAO,KAAK,GAAG;EAC7B;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"downloads.js","names":[],"sources":["../../../../src/http/routes/learn/downloads.ts"],"sourcesContent":["import { z } from 'zod';\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n DownloadAssetParams,\n DownloadIdParam,\n ErrorBody,\n LearnDownload,\n LearnDownloads,\n} from '../../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../../app/container.js';\nimport { UnauthorizedError } from '../../plugins/auth.js';\n\nexport async function learnDownloadsRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const learn = container.reporting.learn;\n\n r.route({\n method: 'GET',\n url: '/api/learn/downloads',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listLearnDownloads',\n tags: ['Content'],\n summary: 'Downloads the student is actively entitled to',\n response: { 200: LearnDownloads },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return learn.listDownloads(req.orgId, orgUser.id);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/learn/downloads/:downloadId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getLearnDownload',\n tags: ['Content'],\n summary: 'One entitled download and its ordered assets',\n params: DownloadIdParam,\n response: { 200: LearnDownload, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n const result = await learn.getDownload(req.orgId, orgUser.id, req.params.downloadId);\n if (!result) {\n throw new NotFoundError('Download', req.params.downloadId);\n }\n return result;\n },\n });\n\n // One stable, resolvable URL per asset. Drops into an <a href download> in\n // any frontend — no JS, no SDK, no CORS preflight. The signed target is\n // minted per request and short-lived.\n r.route({\n method: 'GET',\n url: '/api/learn/downloads/:downloadId/assets/:assetId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getLearnDownloadAsset',\n tags: ['Content'],\n summary: 'Redirect to a short-lived signed URL for an entitled asset',\n params: DownloadAssetParams,\n response: { 302: z.void(), 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 UnauthorizedError();\n }\n const signed = await learn.downloadAssetUrl(\n req.orgId,\n orgUser.id,\n req.params.downloadId,\n req.params.assetId,\n );\n if (!signed) {\n // Always 404, never 403 — a 403 confirms the resource exists to\n // someone not entitled to it.\n throw new NotFoundError('Download asset', req.params.assetId);\n }\n return reply\n .header('cache-control', 'no-store')\n .header('referrer-policy', 'no-referrer')\n .redirect(signed.url, 302);\n },\n });\n}\n"],"mappings":";;;;;;;;AAcA,eAAsB,qBACpB,KACA,WACe;CACf,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,QAAQ,UAAU,UAAU;CAElC,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,UAAU,EAAE,KAAK,eAAe;EAClC;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB;GAE9B,OAAO,MAAM,cAAc,IAAI,OAAO,QAAQ,EAAE;EAClD;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;IAAe,KAAK;GAAU;EACjD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB;GAE9B,MAAM,SAAS,MAAM,MAAM,YAAY,IAAI,OAAO,QAAQ,IAAI,IAAI,OAAO,UAAU;GACnF,IAAI,CAAC,QACH,MAAM,IAAI,cAAc,YAAY,IAAI,OAAO,UAAU;GAE3D,OAAO;EACT;CACF,CAAC;CAKD,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,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB;GAE9B,MAAM,SAAS,MAAM,MAAM,iBACzB,IAAI,OACJ,QAAQ,IACR,IAAI,OAAO,YACX,IAAI,OAAO,OACb;GACA,IAAI,CAAC,QAGH,MAAM,IAAI,cAAc,kBAAkB,IAAI,OAAO,OAAO;GAE9D,OAAO,MACJ,OAAO,iBAAiB,UAAU,CAAC,CACnC,OAAO,mBAAmB,aAAa,CAAC,CACxC,SAAS,OAAO,KAAK,GAAG;EAC7B;CACF,CAAC;AACH"}
@@ -10,7 +10,7 @@ async function learnOrgRoutes(app, container) {
10
10
  preHandler: app.requireOrgSession,
11
11
  schema: {
12
12
  operationId: "getLearnOrg",
13
- tags: ["Learn"],
13
+ tags: ["Organizations"],
14
14
  summary: "Get the portal org's public identity (branding)",
15
15
  response: { 200: LearnOrg }
16
16
  },
@@ -30,7 +30,7 @@ async function learnOrgRoutes(app, container) {
30
30
  preHandler: app.requireOrgSession,
31
31
  schema: {
32
32
  operationId: "getLearnViewer",
33
- tags: ["Learn"],
33
+ tags: ["Organizations"],
34
34
  summary: "Get the caller's identity within the session's org",
35
35
  response: { 200: LearnViewer }
36
36
  },