@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.
- package/README.md +1 -1
- package/dist/app/container.d.ts +7 -3
- package/dist/app/container.js +57 -26
- package/dist/app/container.js.map +1 -1
- package/dist/app/notifications.js +4 -1
- package/dist/app/notifications.js.map +1 -1
- package/dist/http/plugins/error-handler.d.ts +1 -1
- package/dist/http/plugins/error-handler.js +15 -4
- package/dist/http/plugins/error-handler.js.map +1 -1
- package/dist/http/plugins/openapi.js +25 -22
- package/dist/http/plugins/openapi.js.map +1 -1
- package/dist/http/routes/activities.js +34 -2
- package/dist/http/routes/activities.js.map +1 -1
- package/dist/http/routes/assets.js +4 -0
- package/dist/http/routes/assets.js.map +1 -1
- package/dist/http/routes/automations.js +32 -8
- package/dist/http/routes/automations.js.map +1 -1
- package/dist/http/routes/bundles.d.ts +7 -0
- package/dist/http/routes/bundles.js +170 -0
- package/dist/http/routes/bundles.js.map +1 -0
- package/dist/http/routes/course-analytics.d.ts +7 -0
- package/dist/http/routes/course-analytics.js +58 -0
- package/dist/http/routes/course-analytics.js.map +1 -0
- package/dist/http/routes/courses.js +8 -8
- package/dist/http/routes/courses.js.map +1 -1
- package/dist/http/routes/dashboard.js +2 -2
- package/dist/http/routes/dashboard.js.map +1 -1
- package/dist/http/routes/discussion.js +1 -1
- package/dist/http/routes/discussion.js.map +1 -1
- package/dist/http/routes/downloads.js +10 -10
- package/dist/http/routes/downloads.js.map +1 -1
- package/dist/http/routes/integrations.js.map +1 -1
- package/dist/http/routes/learn/assets.js +1 -1
- package/dist/http/routes/learn/assets.js.map +1 -1
- package/dist/http/routes/learn/comments.js +7 -7
- package/dist/http/routes/learn/comments.js.map +1 -1
- package/dist/http/routes/learn/courses.js +26 -3
- package/dist/http/routes/learn/courses.js.map +1 -1
- package/dist/http/routes/learn/downloads.js +3 -3
- package/dist/http/routes/learn/downloads.js.map +1 -1
- package/dist/http/routes/learn/org.js +2 -2
- package/dist/http/routes/learn/org.js.map +1 -1
- package/dist/http/routes/learn/progress.js +2 -2
- package/dist/http/routes/learn/progress.js.map +1 -1
- package/dist/http/routes/organizations.js.map +1 -1
- package/dist/http/routes/students.js +27 -6
- package/dist/http/routes/students.js.map +1 -1
- package/dist/http/routes/webhooks.d.ts +7 -0
- package/dist/http/routes/webhooks.js +159 -0
- package/dist/http/routes/webhooks.js.map +1 -0
- package/dist/http/routes.js +6 -0
- package/dist/http/routes.js.map +1 -1
- package/dist/http/schemas/activities.d.ts +34 -1
- package/dist/http/schemas/activities.js +7 -2
- package/dist/http/schemas/activities.js.map +1 -1
- package/dist/http/schemas/assets.d.ts +11 -7
- package/dist/http/schemas/assets.js.map +1 -1
- package/dist/http/schemas/automations.d.ts +75 -8
- package/dist/http/schemas/automations.js +13 -2
- package/dist/http/schemas/automations.js.map +1 -1
- package/dist/http/schemas/bundles.d.ts +63 -0
- package/dist/http/schemas/bundles.js +23 -0
- package/dist/http/schemas/bundles.js.map +1 -0
- package/dist/http/schemas/content.d.ts +18 -11
- package/dist/http/schemas/content.js +1 -2
- package/dist/http/schemas/content.js.map +1 -1
- package/dist/http/schemas/course-analytics.d.ts +29 -0
- package/dist/http/schemas/course-analytics.js +21 -0
- package/dist/http/schemas/course-analytics.js.map +1 -0
- package/dist/http/schemas/dashboard.js.map +1 -1
- package/dist/http/schemas/discussion.d.ts +9 -9
- package/dist/http/schemas/discussion.js.map +1 -1
- package/dist/http/schemas/downloads.d.ts +2 -0
- package/dist/http/schemas/downloads.js.map +1 -1
- package/dist/http/schemas/entitlements.d.ts +14 -6
- package/dist/http/schemas/entitlements.js +4 -2
- package/dist/http/schemas/entitlements.js.map +1 -1
- package/dist/http/schemas/index.d.ts +6 -4
- package/dist/http/schemas/index.js +6 -4
- package/dist/http/schemas/integrations.d.ts +4 -4
- package/dist/http/schemas/integrations.js.map +1 -1
- package/dist/http/schemas/invites.js +2 -1
- package/dist/http/schemas/invites.js.map +1 -1
- package/dist/http/schemas/learn.d.ts +11 -1
- package/dist/http/schemas/learn.js.map +1 -1
- package/dist/http/schemas/members.js.map +1 -1
- package/dist/http/schemas/organizations.js.map +1 -1
- package/dist/http/schemas/settings.js.map +1 -1
- package/dist/http/schemas/shared.d.ts +1 -0
- package/dist/http/schemas/shared.js +3 -1
- package/dist/http/schemas/shared.js.map +1 -1
- package/dist/http/schemas/students.d.ts +30 -2
- package/dist/http/schemas/students.js +21 -2
- package/dist/http/schemas/students.js.map +1 -1
- package/dist/http/server.js +4 -2
- package/dist/http/server.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js.map +1 -1
- package/dist/package.js +6 -0
- package/dist/package.js.map +1 -0
- 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: ['
|
|
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: ["
|
|
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: ["
|
|
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: ['
|
|
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: "
|
|
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: '
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ['
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ['
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ['
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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: ['
|
|
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: ["
|
|
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: ["
|
|
33
|
+
tags: ["Organizations"],
|
|
34
34
|
summary: "Get the caller's identity within the session's org",
|
|
35
35
|
response: { 200: LearnViewer }
|
|
36
36
|
},
|