@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,11 +1,11 @@
|
|
|
1
1
|
import { resolveScope } from "../scope.js";
|
|
2
|
-
import { ActivityPathParam, CourseIdPathParam, CreateModule, ModuleList, ModulePathParam, ReorderInput, SaveActivity, UpdateModule } from "../schemas/activities.js";
|
|
2
|
+
import { ActivityAssetList, ActivityList, ActivityPathParam, CourseIdPathParam, CreateModule, ModuleList, ModulePathParam, ReorderInput, SaveActivity, UpdateModule } from "../schemas/activities.js";
|
|
3
3
|
import "../schemas/index.js";
|
|
4
4
|
//#region src/http/routes/activities.ts
|
|
5
5
|
async function activitiesRoutes(app, container) {
|
|
6
6
|
const r = app.withTypeProvider();
|
|
7
7
|
const content = container.content;
|
|
8
|
-
const tags = ["
|
|
8
|
+
const tags = ["Content"];
|
|
9
9
|
r.route({
|
|
10
10
|
method: "GET",
|
|
11
11
|
url: "/api/courses/:courseId/modules",
|
|
@@ -22,6 +22,38 @@ async function activitiesRoutes(app, container) {
|
|
|
22
22
|
return content.listCourseModules(scope.orgId, req.params.courseId);
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
|
+
r.route({
|
|
26
|
+
method: "GET",
|
|
27
|
+
url: "/api/courses/:courseId/activities",
|
|
28
|
+
preHandler: app.requireOrgSession,
|
|
29
|
+
schema: {
|
|
30
|
+
operationId: "listActivities",
|
|
31
|
+
tags,
|
|
32
|
+
summary: "List a course's activities",
|
|
33
|
+
params: CourseIdPathParam,
|
|
34
|
+
response: { 200: ActivityList }
|
|
35
|
+
},
|
|
36
|
+
handler: async (req) => {
|
|
37
|
+
const scope = await resolveScope(container, req);
|
|
38
|
+
return content.listCourseActivities(scope.orgId, req.params.courseId);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
r.route({
|
|
42
|
+
method: "GET",
|
|
43
|
+
url: "/api/courses/:courseId/activity-assets",
|
|
44
|
+
preHandler: app.requireOrgSession,
|
|
45
|
+
schema: {
|
|
46
|
+
operationId: "listActivityAssets",
|
|
47
|
+
tags,
|
|
48
|
+
summary: "List a course's activity→asset links",
|
|
49
|
+
params: CourseIdPathParam,
|
|
50
|
+
response: { 200: ActivityAssetList }
|
|
51
|
+
},
|
|
52
|
+
handler: async (req) => {
|
|
53
|
+
const scope = await resolveScope(container, req);
|
|
54
|
+
return content.listCourseActivityAssets(scope.orgId, req.params.courseId);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
25
57
|
r.route({
|
|
26
58
|
method: "POST",
|
|
27
59
|
url: "/api/courses/:courseId/modules",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activities.js","names":[],"sources":["../../../src/http/routes/activities.ts"],"sourcesContent":["// HTTP routes for course modules + activities. Write operations return the\n// course's full, reordered module list (how the editor re-renders). Nested under\n// courses. All require a session with an active organization; handlers resolve\n// the session's active org to the domain org id and scope every call by it.\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n ActivityPathParam,\n CourseIdPathParam,\n CreateModule,\n ModuleList,\n ModulePathParam,\n ReorderInput,\n SaveActivity,\n UpdateModule,\n} from '../schemas/index.js';\nimport type { Container } from '../../app/container.js';\nimport { resolveScope } from '../scope.js';\n\nexport async function activitiesRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n // Modules/activities are part of the content aggregate; grouped under the Courses tag.\n const content = container.content;\n const tags = ['Courses'];\n\n r.route({\n method: 'GET',\n url: '/api/courses/:courseId/modules',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listModules',\n tags,\n summary: \"List a course's modules\",\n params: CourseIdPathParam,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.listCourseModules(scope.orgId, req.params.courseId);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/courses/:courseId/modules',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createModule',\n tags,\n summary: 'Add a module',\n params: CourseIdPathParam,\n body: CreateModule,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.createModule(scope.orgId, req.params.courseId, req.body.title);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/courses/:courseId/modules/reorder',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reorderModules',\n tags,\n summary: 'Reorder modules',\n params: CourseIdPathParam,\n body: ReorderInput,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.reorderModules(scope.orgId, req.params.courseId, req.body.orderedIds);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/courses/:courseId/modules/:moduleId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateModule',\n tags,\n summary: 'Rename a module',\n params: ModulePathParam,\n body: UpdateModule,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.updateModule(\n scope.orgId,\n req.params.courseId,\n req.params.moduleId,\n req.body.title,\n );\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/courses/:courseId/modules/:moduleId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteModule',\n tags,\n summary: 'Delete a module',\n params: ModulePathParam,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.deleteModule(scope.orgId, req.params.courseId, req.params.moduleId);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/courses/:courseId/modules/:moduleId/activities/reorder',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reorderActivities',\n tags,\n summary: 'Reorder activities in a module',\n params: ModulePathParam,\n body: ReorderInput,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.reorderActivities(\n scope.orgId,\n req.params.courseId,\n req.params.moduleId,\n req.body.orderedIds,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/courses/:courseId/modules/:moduleId/activities',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createActivity',\n tags,\n summary: 'Add an activity',\n params: ModulePathParam,\n body: SaveActivity,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.saveActivity(scope.orgId, req.params.courseId, req.params.moduleId, req.body);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/courses/:courseId/modules/:moduleId/activities/:activityId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateActivity',\n tags,\n summary: 'Update an activity',\n params: ActivityPathParam,\n body: SaveActivity,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.saveActivity(\n scope.orgId,\n req.params.courseId,\n req.params.moduleId,\n req.body,\n req.params.activityId,\n );\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/courses/:courseId/modules/:moduleId/activities/:activityId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteActivity',\n tags,\n summary: 'Delete an activity',\n params: ActivityPathParam,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.deleteActivity(\n scope.orgId,\n req.params.courseId,\n req.params.moduleId,\n req.params.activityId,\n );\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"activities.js","names":[],"sources":["../../../src/http/routes/activities.ts"],"sourcesContent":["// HTTP routes for course modules + activities. Write operations return the\n// course's full, reordered module list (how the editor re-renders). Nested under\n// courses. All require a session with an active organization; handlers resolve\n// the session's active org to the domain org id and scope every call by it.\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n ActivityAssetList,\n ActivityList,\n ActivityPathParam,\n CourseIdPathParam,\n CreateModule,\n ModuleList,\n ModulePathParam,\n ReorderInput,\n SaveActivity,\n UpdateModule,\n} from '../schemas/index.js';\nimport type { Container } from '../../app/container.js';\nimport { resolveScope } from '../scope.js';\n\nexport async function activitiesRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n // Modules/activities are part of the content aggregate; grouped under the Courses tag.\n const content = container.content;\n const tags = ['Content'];\n\n r.route({\n method: 'GET',\n url: '/api/courses/:courseId/modules',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listModules',\n tags,\n summary: \"List a course's modules\",\n params: CourseIdPathParam,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.listCourseModules(scope.orgId, req.params.courseId);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/courses/:courseId/activities',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listActivities',\n tags,\n summary: \"List a course's activities\",\n params: CourseIdPathParam,\n response: { 200: ActivityList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.listCourseActivities(scope.orgId, req.params.courseId);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/courses/:courseId/activity-assets',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listActivityAssets',\n tags,\n summary: \"List a course's activity→asset links\",\n params: CourseIdPathParam,\n response: { 200: ActivityAssetList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.listCourseActivityAssets(scope.orgId, req.params.courseId);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/courses/:courseId/modules',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createModule',\n tags,\n summary: 'Add a module',\n params: CourseIdPathParam,\n body: CreateModule,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.createModule(scope.orgId, req.params.courseId, req.body.title);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/courses/:courseId/modules/reorder',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reorderModules',\n tags,\n summary: 'Reorder modules',\n params: CourseIdPathParam,\n body: ReorderInput,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.reorderModules(scope.orgId, req.params.courseId, req.body.orderedIds);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/courses/:courseId/modules/:moduleId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateModule',\n tags,\n summary: 'Rename a module',\n params: ModulePathParam,\n body: UpdateModule,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.updateModule(\n scope.orgId,\n req.params.courseId,\n req.params.moduleId,\n req.body.title,\n );\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/courses/:courseId/modules/:moduleId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteModule',\n tags,\n summary: 'Delete a module',\n params: ModulePathParam,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.deleteModule(scope.orgId, req.params.courseId, req.params.moduleId);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/courses/:courseId/modules/:moduleId/activities/reorder',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reorderActivities',\n tags,\n summary: 'Reorder activities in a module',\n params: ModulePathParam,\n body: ReorderInput,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.reorderActivities(\n scope.orgId,\n req.params.courseId,\n req.params.moduleId,\n req.body.orderedIds,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/courses/:courseId/modules/:moduleId/activities',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createActivity',\n tags,\n summary: 'Add an activity',\n params: ModulePathParam,\n body: SaveActivity,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.saveActivity(scope.orgId, req.params.courseId, req.params.moduleId, req.body);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/courses/:courseId/modules/:moduleId/activities/:activityId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateActivity',\n tags,\n summary: 'Update an activity',\n params: ActivityPathParam,\n body: SaveActivity,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.saveActivity(\n scope.orgId,\n req.params.courseId,\n req.params.moduleId,\n req.body,\n req.params.activityId,\n );\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/courses/:courseId/modules/:moduleId/activities/:activityId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteActivity',\n tags,\n summary: 'Delete an activity',\n params: ActivityPathParam,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.deleteActivity(\n scope.orgId,\n req.params.courseId,\n req.params.moduleId,\n req.params.activityId,\n );\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -117,6 +117,10 @@ async function assetsRoutes(app, container) {
|
|
|
117
117
|
method: "POST",
|
|
118
118
|
url: "/api/assets/:id/download-url",
|
|
119
119
|
preHandler: app.requireOrgSession,
|
|
120
|
+
preValidation: (req, _reply, done) => {
|
|
121
|
+
req.body ??= {};
|
|
122
|
+
done();
|
|
123
|
+
},
|
|
120
124
|
schema: {
|
|
121
125
|
operationId: "requestAssetDownload",
|
|
122
126
|
tags,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assets.js","names":[],"sources":["../../../src/http/routes/assets.ts"],"sourcesContent":["// HTTP routes for the assets media library + content delivery. All require a\n// session with an active organization; assets are stored under the org's\n// private prefix and served only through short-lived presigned URLs.\n//\n// `req.orgId` is already the domain organization id these tables key on — the\n// session guard translates it from better-auth's id once per request.\nimport type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport {\n Asset,\n AssetIdParam,\n AssetsPage,\n AssetsQuery,\n DownloadTicket,\n ErrorBody,\n RequestDownload,\n RequestUpload,\n UploadTicket,\n} from '../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../app/container.js';\n\n/** Resolve the session's active org to the domain org id, or 400 and return null. */\nasync function resolveOrgId(
|
|
1
|
+
{"version":3,"file":"assets.js","names":[],"sources":["../../../src/http/routes/assets.ts"],"sourcesContent":["// HTTP routes for the assets media library + content delivery. All require a\n// session with an active organization; assets are stored under the org's\n// private prefix and served only through short-lived presigned URLs.\n//\n// `req.orgId` is already the domain organization id these tables key on — the\n// session guard translates it from better-auth's id once per request.\nimport type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport {\n Asset,\n AssetIdParam,\n AssetsPage,\n AssetsQuery,\n DownloadTicket,\n ErrorBody,\n RequestDownload,\n RequestUpload,\n UploadTicket,\n} from '../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../app/container.js';\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 assetsRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const assets = container.assets;\n const tags = ['Assets'];\n\n // Register an asset + get a presigned upload URL. Kept at /api/uploads as the\n // \"start an upload\" action; the resulting asset lives in the library below.\n r.route({\n method: 'POST',\n url: '/api/uploads',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'requestUpload',\n tags,\n summary: 'Register an asset and get a presigned upload URL',\n body: RequestUpload,\n response: { 201: UploadTicket, 400: ErrorBody, 401: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const ticket = await assets.requestUpload(orgId, {\n // Domain `users.id`, like every other org-scoped row — not the auth id.\n uploadedBy: req.userId,\n ...req.body,\n });\n return reply.code(201).send(ticket);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/assets/:id/confirm',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'confirmAsset',\n tags,\n summary: 'Confirm an upload completed (captures size + content type)',\n params: AssetIdParam,\n response: { 200: Asset, 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 asset = await assets.confirm(orgId, req.params.id);\n if (!asset) {\n throw new NotFoundError('Asset', req.params.id);\n }\n return asset;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/assets',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listAssets',\n tags,\n summary: \"Browse the organization's media library\",\n querystring: AssetsQuery,\n response: { 200: AssetsPage, 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 assets.list(orgId, req.query);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/assets/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getAsset',\n tags,\n summary: \"Get an asset's metadata\",\n params: AssetIdParam,\n response: { 200: Asset, 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 asset = await assets.get(orgId, req.params.id);\n if (!asset) {\n throw new NotFoundError('Asset', req.params.id);\n }\n return asset;\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/assets/:id/download-url',\n preHandler: app.requireOrgSession,\n // Every body field is optional, so a bodyless POST is valid — the\n // generated SDK omits the body entirely when no field is set, which\n // Fastify hands to validation as `null`. Normalize before it gets there.\n preValidation: (req, _reply, done) => {\n req.body ??= {};\n done();\n },\n schema: {\n operationId: 'requestAssetDownload',\n tags,\n summary: 'Get a short-lived presigned URL to download/serve an asset',\n params: AssetIdParam,\n body: RequestDownload,\n response: { 200: DownloadTicket, 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 ticket = await assets.requestDownload(orgId, req.params.id, req.body.filename);\n if (!ticket) {\n throw new NotFoundError('Asset', req.params.id);\n }\n return ticket;\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/assets/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteAsset',\n tags,\n summary: 'Delete an asset (removes the object from storage)',\n params: AssetIdParam,\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 assets.remove(orgId, req.params.id);\n if (!removed) {\n throw new NotFoundError('Asset', req.params.id);\n }\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveScope } from "../scope.js";
|
|
2
2
|
import { ErrorBody } from "../schemas/shared.js";
|
|
3
|
-
import { Automation, AutomationIdParam, AutomationRunsPage, AutomationRunsQuery, AvailableAction, AvailableTriggers, CreateAutomationBody, UpdateAutomationBody } from "../schemas/automations.js";
|
|
3
|
+
import { Automation, AutomationIdParam, AutomationRun, AutomationRunParams, AutomationRunsPage, AutomationRunsQuery, AutomationsQuery, AvailableAction, AvailableTriggers, CreateAutomationBody, UpdateAutomationBody } from "../schemas/automations.js";
|
|
4
4
|
import "../schemas/index.js";
|
|
5
5
|
import { NotFoundError } from "@headless-lms/core/shared/errors";
|
|
6
6
|
import { z } from "zod";
|
|
@@ -16,12 +16,13 @@ async function automationsRoutes(app, container) {
|
|
|
16
16
|
schema: {
|
|
17
17
|
operationId: "listAutomations",
|
|
18
18
|
tags,
|
|
19
|
-
summary: "List automations",
|
|
19
|
+
summary: "List automations, optionally only workflows or only webhooks",
|
|
20
|
+
querystring: AutomationsQuery,
|
|
20
21
|
response: { 200: z.array(Automation) }
|
|
21
22
|
},
|
|
22
23
|
handler: async (req) => {
|
|
23
24
|
const scope = await resolveScope(container, req);
|
|
24
|
-
return automations.list(scope.orgId);
|
|
25
|
+
return automations.list(scope.orgId, req.query);
|
|
25
26
|
}
|
|
26
27
|
});
|
|
27
28
|
r.route({
|
|
@@ -61,7 +62,7 @@ async function automationsRoutes(app, container) {
|
|
|
61
62
|
schema: {
|
|
62
63
|
operationId: "createAutomation",
|
|
63
64
|
tags,
|
|
64
|
-
summary: "Create
|
|
65
|
+
summary: "Create a workflow automation",
|
|
65
66
|
body: CreateAutomationBody,
|
|
66
67
|
response: { 201: Automation }
|
|
67
68
|
},
|
|
@@ -99,12 +100,13 @@ async function automationsRoutes(app, container) {
|
|
|
99
100
|
schema: {
|
|
100
101
|
operationId: "updateAutomation",
|
|
101
102
|
tags,
|
|
102
|
-
summary: "Update
|
|
103
|
+
summary: "Update a workflow automation (webhooks change through the webhook endpoints)",
|
|
103
104
|
params: AutomationIdParam,
|
|
104
105
|
body: UpdateAutomationBody,
|
|
105
106
|
response: {
|
|
106
107
|
200: Automation,
|
|
107
|
-
404: ErrorBody
|
|
108
|
+
404: ErrorBody,
|
|
109
|
+
409: ErrorBody
|
|
108
110
|
}
|
|
109
111
|
},
|
|
110
112
|
handler: async (req) => {
|
|
@@ -121,11 +123,12 @@ async function automationsRoutes(app, container) {
|
|
|
121
123
|
schema: {
|
|
122
124
|
operationId: "deleteAutomation",
|
|
123
125
|
tags,
|
|
124
|
-
summary: "Delete
|
|
126
|
+
summary: "Delete a workflow automation (webhooks are deleted through the webhook endpoints)",
|
|
125
127
|
params: AutomationIdParam,
|
|
126
128
|
response: {
|
|
127
129
|
204: z.void(),
|
|
128
|
-
404: ErrorBody
|
|
130
|
+
404: ErrorBody,
|
|
131
|
+
409: ErrorBody
|
|
129
132
|
}
|
|
130
133
|
},
|
|
131
134
|
handler: async (req, reply) => {
|
|
@@ -151,6 +154,27 @@ async function automationsRoutes(app, container) {
|
|
|
151
154
|
return automations.listRuns(scope.orgId, req.params.id, req.query);
|
|
152
155
|
}
|
|
153
156
|
});
|
|
157
|
+
r.route({
|
|
158
|
+
method: "POST",
|
|
159
|
+
url: "/api/automations/:id/runs/:runId/rerun",
|
|
160
|
+
preHandler: app.requireOrgSession,
|
|
161
|
+
schema: {
|
|
162
|
+
operationId: "rerunAutomationRun",
|
|
163
|
+
tags,
|
|
164
|
+
summary: "Run a finished run again",
|
|
165
|
+
description: "Dispatches the same run again with the automation's current steps; the new outcome replaces the run's previous one. For a webhook this resends the delivery to its current URL. A run that is still running can't be rerun (409).",
|
|
166
|
+
params: AutomationRunParams,
|
|
167
|
+
response: {
|
|
168
|
+
200: AutomationRun,
|
|
169
|
+
404: ErrorBody,
|
|
170
|
+
409: ErrorBody
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
handler: async (req) => {
|
|
174
|
+
const scope = await resolveScope(container, req);
|
|
175
|
+
return automations.rerun(scope.orgId, req.params.id, req.params.runId);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
154
178
|
}
|
|
155
179
|
//#endregion
|
|
156
180
|
export { automationsRoutes };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"automations.js","names":[],"sources":["../../../src/http/routes/automations.ts"],"sourcesContent":["// HTTP routes for the automations context: rules that match a trigger (a\n// domain event type) against enabled automations and run an ordered list of\n// actions, plus the run history each automation accumulates.\nimport type { FastifyInstance } from 'fastify';\nimport { z } from 'zod';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n Automation,\n AutomationIdParam,\n AutomationRunsPage,\n AutomationRunsQuery,\n AvailableAction,\n AvailableTriggers,\n CreateAutomationBody,\n ErrorBody,\n UpdateAutomationBody,\n} from '../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../app/container.js';\nimport { resolveScope } from '../scope.js';\n\nexport async function automationsRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const automations = container.automations;\n const tags = ['Automations'];\n\n r.route({\n method: 'GET',\n url: '/api/automations',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listAutomations',\n tags,\n summary: 'List automations',\n response: { 200: z.array(Automation) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return automations.list(scope.orgId);\n },\n });\n\n // Before `/api/automations/:id` — otherwise `:id` would swallow these path segments.\n r.route({\n method: 'GET',\n url: '/api/automations/actions',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listAutomationActions',\n tags,\n summary: 'List the actions automations can use',\n response: { 200: z.array(AvailableAction) },\n },\n handler: async (req) => {\n await resolveScope(container, req);\n return automations.availableActions();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/automations/triggers',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listAutomationTriggers',\n tags,\n summary: 'List the domain events automations can react to',\n response: { 200: AvailableTriggers },\n },\n handler: async (req) => {\n await resolveScope(container, req);\n return automations.availableTriggers();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/automations',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createAutomation',\n tags,\n summary: 'Create
|
|
1
|
+
{"version":3,"file":"automations.js","names":[],"sources":["../../../src/http/routes/automations.ts"],"sourcesContent":["// HTTP routes for the automations context: rules that match a trigger (a\n// domain event type) against enabled automations and run an ordered list of\n// actions, plus the run history each automation accumulates.\nimport type { FastifyInstance } from 'fastify';\nimport { z } from 'zod';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n Automation,\n AutomationIdParam,\n AutomationRun,\n AutomationRunParams,\n AutomationRunsPage,\n AutomationRunsQuery,\n AutomationsQuery,\n AvailableAction,\n AvailableTriggers,\n CreateAutomationBody,\n ErrorBody,\n UpdateAutomationBody,\n} from '../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../app/container.js';\nimport { resolveScope } from '../scope.js';\n\nexport async function automationsRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const automations = container.automations;\n const tags = ['Automations'];\n\n r.route({\n method: 'GET',\n url: '/api/automations',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listAutomations',\n tags,\n summary: 'List automations, optionally only workflows or only webhooks',\n querystring: AutomationsQuery,\n response: { 200: z.array(Automation) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return automations.list(scope.orgId, req.query);\n },\n });\n\n // Before `/api/automations/:id` — otherwise `:id` would swallow these path segments.\n r.route({\n method: 'GET',\n url: '/api/automations/actions',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listAutomationActions',\n tags,\n summary: 'List the actions automations can use',\n response: { 200: z.array(AvailableAction) },\n },\n handler: async (req) => {\n await resolveScope(container, req);\n return automations.availableActions();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/automations/triggers',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listAutomationTriggers',\n tags,\n summary: 'List the domain events automations can react to',\n response: { 200: AvailableTriggers },\n },\n handler: async (req) => {\n await resolveScope(container, req);\n return automations.availableTriggers();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/automations',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createAutomation',\n tags,\n summary: 'Create a workflow automation',\n body: CreateAutomationBody,\n response: { 201: Automation },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const automation = await automations.create(scope.orgId, req.body);\n return reply.code(201).send(automation);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/automations/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getAutomation',\n tags,\n summary: 'Get an automation by id',\n params: AutomationIdParam,\n response: { 200: Automation, 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const automation = await automations.get(scope.orgId, req.params.id);\n if (!automation) {\n throw new NotFoundError('Automation', req.params.id);\n }\n return automation;\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/automations/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateAutomation',\n tags,\n summary: 'Update a workflow automation (webhooks change through the webhook endpoints)',\n params: AutomationIdParam,\n body: UpdateAutomationBody,\n response: { 200: Automation, 404: ErrorBody, 409: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const automation = await automations.update(scope.orgId, req.params.id, req.body);\n if (!automation) {\n throw new NotFoundError('Automation', req.params.id);\n }\n return automation;\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/automations/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteAutomation',\n tags,\n summary: 'Delete a workflow automation (webhooks are deleted through the webhook endpoints)',\n params: AutomationIdParam,\n response: { 204: z.void(), 404: ErrorBody, 409: ErrorBody },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const deleted = await automations.delete(scope.orgId, req.params.id);\n if (!deleted) {\n throw new NotFoundError('Automation', req.params.id);\n }\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/automations/:id/runs',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listAutomationRuns',\n tags,\n summary:\n \"List an automation's runs — a deleted automation's runs remain reachable (audit trail)\",\n params: AutomationIdParam,\n querystring: AutomationRunsQuery,\n response: { 200: AutomationRunsPage },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n // No existence pre-check: runs deliberately survive automation deletion (audit trail).\n return automations.listRuns(scope.orgId, req.params.id, req.query);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/automations/:id/runs/:runId/rerun',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'rerunAutomationRun',\n tags,\n summary: 'Run a finished run again',\n description:\n \"Dispatches the same run again with the automation's current steps; the new outcome replaces the run's previous one. For a webhook this resends the delivery to its current URL. A run that is still running can't be rerun (409).\",\n params: AutomationRunParams,\n response: { 200: AutomationRun, 404: ErrorBody, 409: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return automations.rerun(scope.orgId, req.params.id, req.params.runId);\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Container } from "../../app/container.js";
|
|
2
|
+
import { FastifyInstance } from "fastify";
|
|
3
|
+
//#region src/http/routes/bundles.d.ts
|
|
4
|
+
declare function bundlesRoutes(app: FastifyInstance, container: Container): Promise<void>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { bundlesRoutes };
|
|
7
|
+
//# sourceMappingURL=bundles.d.ts.map
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { resolveScope } from "../scope.js";
|
|
2
|
+
import { ErrorBody } from "../schemas/shared.js";
|
|
3
|
+
import { AddBundleItem, Bundle, BundleIdParam, BundleItem, BundleItemParams, BundlesPage, BundlesQuery, CreateBundle, UpdateBundle } from "../schemas/bundles.js";
|
|
4
|
+
import "../schemas/index.js";
|
|
5
|
+
import { NotFoundError } from "@headless-lms/core/shared/errors";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
//#region src/http/routes/bundles.ts
|
|
8
|
+
async function bundlesRoutes(app, container) {
|
|
9
|
+
const r = app.withTypeProvider();
|
|
10
|
+
const content = container.content;
|
|
11
|
+
r.route({
|
|
12
|
+
method: "GET",
|
|
13
|
+
url: "/api/bundles",
|
|
14
|
+
preHandler: app.requireOrgSession,
|
|
15
|
+
schema: {
|
|
16
|
+
operationId: "listBundles",
|
|
17
|
+
tags: ["Content"],
|
|
18
|
+
summary: "List bundles",
|
|
19
|
+
querystring: BundlesQuery,
|
|
20
|
+
response: { 200: BundlesPage }
|
|
21
|
+
},
|
|
22
|
+
handler: async (req) => {
|
|
23
|
+
const scope = await resolveScope(container, req);
|
|
24
|
+
return content.listBundles(scope.orgId, req.query);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
r.route({
|
|
28
|
+
method: "GET",
|
|
29
|
+
url: "/api/bundles/:bundleId",
|
|
30
|
+
preHandler: app.requireOrgSession,
|
|
31
|
+
schema: {
|
|
32
|
+
operationId: "getBundle",
|
|
33
|
+
tags: ["Content"],
|
|
34
|
+
summary: "Get a bundle by id",
|
|
35
|
+
params: BundleIdParam,
|
|
36
|
+
response: {
|
|
37
|
+
200: Bundle,
|
|
38
|
+
404: ErrorBody
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
handler: async (req) => {
|
|
42
|
+
const scope = await resolveScope(container, req);
|
|
43
|
+
const bundle = await content.getBundle(scope.orgId, req.params.bundleId);
|
|
44
|
+
if (!bundle) throw new NotFoundError("Bundle", req.params.bundleId);
|
|
45
|
+
return bundle;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
r.route({
|
|
49
|
+
method: "POST",
|
|
50
|
+
url: "/api/bundles",
|
|
51
|
+
preHandler: app.requireOrgSession,
|
|
52
|
+
schema: {
|
|
53
|
+
operationId: "createBundle",
|
|
54
|
+
tags: ["Content"],
|
|
55
|
+
summary: "Create a bundle, optionally with initial content",
|
|
56
|
+
body: CreateBundle,
|
|
57
|
+
response: {
|
|
58
|
+
201: Bundle,
|
|
59
|
+
404: ErrorBody
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
handler: async (req, reply) => {
|
|
63
|
+
const scope = await resolveScope(container, req);
|
|
64
|
+
const bundle = await content.createBundle(scope.orgId, req.body);
|
|
65
|
+
return reply.code(201).send(bundle);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
r.route({
|
|
69
|
+
method: "PATCH",
|
|
70
|
+
url: "/api/bundles/:bundleId",
|
|
71
|
+
preHandler: app.requireOrgSession,
|
|
72
|
+
schema: {
|
|
73
|
+
operationId: "updateBundle",
|
|
74
|
+
tags: ["Content"],
|
|
75
|
+
summary: "Update a bundle",
|
|
76
|
+
params: BundleIdParam,
|
|
77
|
+
body: UpdateBundle,
|
|
78
|
+
response: {
|
|
79
|
+
200: Bundle,
|
|
80
|
+
404: ErrorBody
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
handler: async (req) => {
|
|
84
|
+
const scope = await resolveScope(container, req);
|
|
85
|
+
return content.updateBundle(scope.orgId, req.params.bundleId, req.body);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
r.route({
|
|
89
|
+
method: "DELETE",
|
|
90
|
+
url: "/api/bundles/:bundleId",
|
|
91
|
+
preHandler: app.requireOrgSession,
|
|
92
|
+
schema: {
|
|
93
|
+
operationId: "deleteBundle",
|
|
94
|
+
tags: ["Content"],
|
|
95
|
+
summary: "Delete a bundle (its content survives)",
|
|
96
|
+
params: BundleIdParam,
|
|
97
|
+
response: {
|
|
98
|
+
204: z.void(),
|
|
99
|
+
404: ErrorBody
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
handler: async (req, reply) => {
|
|
103
|
+
const scope = await resolveScope(container, req);
|
|
104
|
+
await content.deleteBundle(scope.orgId, req.params.bundleId);
|
|
105
|
+
return reply.code(204).send();
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
r.route({
|
|
109
|
+
method: "GET",
|
|
110
|
+
url: "/api/bundles/:bundleId/items",
|
|
111
|
+
preHandler: app.requireOrgSession,
|
|
112
|
+
schema: {
|
|
113
|
+
operationId: "listBundleItems",
|
|
114
|
+
tags: ["Content"],
|
|
115
|
+
summary: "List a bundle's content items",
|
|
116
|
+
params: BundleIdParam,
|
|
117
|
+
response: {
|
|
118
|
+
200: z.array(BundleItem),
|
|
119
|
+
404: ErrorBody
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
handler: async (req) => {
|
|
123
|
+
const scope = await resolveScope(container, req);
|
|
124
|
+
return content.listBundleItems(scope.orgId, req.params.bundleId);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
r.route({
|
|
128
|
+
method: "POST",
|
|
129
|
+
url: "/api/bundles/:bundleId/items",
|
|
130
|
+
preHandler: app.requireOrgSession,
|
|
131
|
+
schema: {
|
|
132
|
+
operationId: "addBundleItem",
|
|
133
|
+
tags: ["Content"],
|
|
134
|
+
summary: "Add a content item to a bundle",
|
|
135
|
+
params: BundleIdParam,
|
|
136
|
+
body: AddBundleItem,
|
|
137
|
+
response: {
|
|
138
|
+
200: z.array(BundleItem),
|
|
139
|
+
404: ErrorBody
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
handler: async (req) => {
|
|
143
|
+
const scope = await resolveScope(container, req);
|
|
144
|
+
return content.addBundleItem(scope.orgId, req.params.bundleId, req.body.contentId);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
r.route({
|
|
148
|
+
method: "DELETE",
|
|
149
|
+
url: "/api/bundles/:bundleId/items/:contentId",
|
|
150
|
+
preHandler: app.requireOrgSession,
|
|
151
|
+
schema: {
|
|
152
|
+
operationId: "removeBundleItem",
|
|
153
|
+
tags: ["Content"],
|
|
154
|
+
summary: "Remove a content item from a bundle (the content survives)",
|
|
155
|
+
params: BundleItemParams,
|
|
156
|
+
response: {
|
|
157
|
+
200: z.array(BundleItem),
|
|
158
|
+
404: ErrorBody
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
handler: async (req) => {
|
|
162
|
+
const scope = await resolveScope(container, req);
|
|
163
|
+
return content.removeBundleItem(scope.orgId, req.params.bundleId, req.params.contentId);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
export { bundlesRoutes };
|
|
169
|
+
|
|
170
|
+
//# sourceMappingURL=bundles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundles.js","names":[],"sources":["../../../src/http/routes/bundles.ts"],"sourcesContent":["// HTTP routes for bundles — named groupings of content items. Request +\n// response are validated against the shared contract schemas by the Zod type\n// provider, and @fastify/swagger reads the same schemas to build the OpenAPI\n// spec.\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n AddBundleItem,\n Bundle,\n BundleIdParam,\n BundleItem,\n BundleItemParams,\n BundlesPage,\n BundlesQuery,\n CreateBundle,\n ErrorBody,\n UpdateBundle,\n} from '../schemas/index.js';\nimport { z } from 'zod';\nimport type { Container } from '../../app/container.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport { resolveScope } from '../scope.js';\n\nexport async function bundlesRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const content = container.content;\n\n r.route({\n method: 'GET',\n url: '/api/bundles',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listBundles',\n tags: ['Content'],\n summary: 'List bundles',\n querystring: BundlesQuery,\n response: { 200: BundlesPage },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.listBundles(scope.orgId, req.query);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/bundles/:bundleId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getBundle',\n tags: ['Content'],\n summary: 'Get a bundle by id',\n params: BundleIdParam,\n response: { 200: Bundle, 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const bundle = await content.getBundle(scope.orgId, req.params.bundleId);\n if (!bundle) {\n throw new NotFoundError('Bundle', req.params.bundleId);\n }\n return bundle;\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/bundles',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createBundle',\n tags: ['Content'],\n summary: 'Create a bundle, optionally with initial content',\n body: CreateBundle,\n response: { 201: Bundle, 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const bundle = await content.createBundle(scope.orgId, req.body);\n return reply.code(201).send(bundle);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/bundles/:bundleId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateBundle',\n tags: ['Content'],\n summary: 'Update a bundle',\n params: BundleIdParam,\n body: UpdateBundle,\n response: { 200: Bundle, 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.updateBundle(scope.orgId, req.params.bundleId, req.body);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/bundles/:bundleId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteBundle',\n tags: ['Content'],\n summary: 'Delete a bundle (its content survives)',\n params: BundleIdParam,\n response: { 204: z.void(), 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n await content.deleteBundle(scope.orgId, req.params.bundleId);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/bundles/:bundleId/items',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listBundleItems',\n tags: ['Content'],\n summary: \"List a bundle's content items\",\n params: BundleIdParam,\n response: { 200: z.array(BundleItem), 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.listBundleItems(scope.orgId, req.params.bundleId);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/bundles/:bundleId/items',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'addBundleItem',\n tags: ['Content'],\n summary: 'Add a content item to a bundle',\n params: BundleIdParam,\n body: AddBundleItem,\n response: { 200: z.array(BundleItem), 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.addBundleItem(scope.orgId, req.params.bundleId, req.body.contentId);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/bundles/:bundleId/items/:contentId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'removeBundleItem',\n tags: ['Content'],\n summary: 'Remove a content item from a bundle (the content survives)',\n params: BundleItemParams,\n response: { 200: z.array(BundleItem), 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.removeBundleItem(scope.orgId, req.params.bundleId, req.params.contentId);\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Container } from "../../app/container.js";
|
|
2
|
+
import { FastifyInstance } from "fastify";
|
|
3
|
+
//#region src/http/routes/course-analytics.d.ts
|
|
4
|
+
declare function courseAnalyticsRoutes(app: FastifyInstance, container: Container): Promise<void>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { courseAnalyticsRoutes };
|
|
7
|
+
//# sourceMappingURL=course-analytics.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { resolveScope } from "../scope.js";
|
|
2
|
+
import { ErrorBody } from "../schemas/shared.js";
|
|
3
|
+
import { CourseIdParam } from "../schemas/content.js";
|
|
4
|
+
import { EnrollmentSeries, EnrollmentSeriesQuery } from "../schemas/dashboard.js";
|
|
5
|
+
import { CourseAnalytics } from "../schemas/course-analytics.js";
|
|
6
|
+
import "../schemas/index.js";
|
|
7
|
+
import { NotFoundError } from "@headless-lms/core/shared/errors";
|
|
8
|
+
//#region src/http/routes/course-analytics.ts
|
|
9
|
+
async function courseAnalyticsRoutes(app, container) {
|
|
10
|
+
const r = app.withTypeProvider();
|
|
11
|
+
r.route({
|
|
12
|
+
method: "GET",
|
|
13
|
+
url: "/api/courses/:id/analytics",
|
|
14
|
+
preHandler: app.requireOrgSession,
|
|
15
|
+
schema: {
|
|
16
|
+
operationId: "getCourseAnalytics",
|
|
17
|
+
tags: ["Reporting"],
|
|
18
|
+
summary: "Completion and engagement stats for a course",
|
|
19
|
+
params: CourseIdParam,
|
|
20
|
+
response: {
|
|
21
|
+
200: CourseAnalytics,
|
|
22
|
+
404: ErrorBody
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
handler: async (req) => {
|
|
26
|
+
const scope = await resolveScope(container, req);
|
|
27
|
+
const analytics = await container.reporting.courses.analytics(scope.orgId, req.params.id);
|
|
28
|
+
if (!analytics) throw new NotFoundError("Course", req.params.id);
|
|
29
|
+
return analytics;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
r.route({
|
|
33
|
+
method: "GET",
|
|
34
|
+
url: "/api/courses/:id/analytics/enrollments",
|
|
35
|
+
preHandler: app.requireOrgSession,
|
|
36
|
+
schema: {
|
|
37
|
+
operationId: "getCourseEnrollmentSeries",
|
|
38
|
+
tags: ["Reporting"],
|
|
39
|
+
summary: "Enrollments granted per day for a course over a trailing window",
|
|
40
|
+
params: CourseIdParam,
|
|
41
|
+
querystring: EnrollmentSeriesQuery,
|
|
42
|
+
response: {
|
|
43
|
+
200: EnrollmentSeries,
|
|
44
|
+
404: ErrorBody
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
handler: async (req) => {
|
|
48
|
+
const scope = await resolveScope(container, req);
|
|
49
|
+
const series = await container.reporting.courses.enrollments(scope.orgId, req.params.id, req.query.days);
|
|
50
|
+
if (!series) throw new NotFoundError("Course", req.params.id);
|
|
51
|
+
return series;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { courseAnalyticsRoutes };
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=course-analytics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"course-analytics.js","names":[],"sources":["../../../src/http/routes/course-analytics.ts"],"sourcesContent":["// HTTP routes for per-course analytics (reporting).\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n CourseAnalytics,\n CourseIdParam,\n EnrollmentSeries,\n EnrollmentSeriesQuery,\n ErrorBody,\n} from '../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../app/container.js';\nimport { resolveScope } from '../scope.js';\n\nexport async function courseAnalyticsRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n\n r.route({\n method: 'GET',\n url: '/api/courses/:id/analytics',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getCourseAnalytics',\n tags: ['Reporting'],\n summary: 'Completion and engagement stats for a course',\n params: CourseIdParam,\n response: { 200: CourseAnalytics, 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const analytics = await container.reporting.courses.analytics(scope.orgId, req.params.id);\n if (!analytics) {\n throw new NotFoundError('Course', req.params.id);\n }\n return analytics;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/courses/:id/analytics/enrollments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getCourseEnrollmentSeries',\n tags: ['Reporting'],\n summary: 'Enrollments granted per day for a course over a trailing window',\n params: CourseIdParam,\n querystring: EnrollmentSeriesQuery,\n response: { 200: EnrollmentSeries, 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const series = await container.reporting.courses.enrollments(\n scope.orgId,\n req.params.id,\n req.query.days,\n );\n if (!series) {\n throw new NotFoundError('Course', req.params.id);\n }\n return series;\n },\n });\n}\n"],"mappings":";;;;;;;;AAcA,eAAsB,sBACpB,KACA,WACe;CACf,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,QAAQ;GACR,UAAU;IAAE,KAAK;IAAiB,KAAK;GAAU;EACnD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,YAAY,MAAM,UAAU,UAAU,QAAQ,UAAU,MAAM,OAAO,IAAI,OAAO,EAAE;GACxF,IAAI,CAAC,WACH,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,WAAW;GAClB,SAAS;GACT,QAAQ;GACR,aAAa;GACb,UAAU;IAAE,KAAK;IAAkB,KAAK;GAAU;EACpD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,SAAS,MAAM,UAAU,UAAU,QAAQ,YAC/C,MAAM,OACN,IAAI,OAAO,IACX,IAAI,MAAM,IACZ;GACA,IAAI,CAAC,QACH,MAAM,IAAI,cAAc,UAAU,IAAI,OAAO,EAAE;GAEjD,OAAO;EACT;CACF,CAAC;AACH"}
|
|
@@ -14,14 +14,14 @@ async function coursesRoutes(app, container) {
|
|
|
14
14
|
preHandler: app.requireOrgSession,
|
|
15
15
|
schema: {
|
|
16
16
|
operationId: "listCourses",
|
|
17
|
-
tags: ["
|
|
17
|
+
tags: ["Content"],
|
|
18
18
|
summary: "List courses",
|
|
19
19
|
querystring: CoursesQuery,
|
|
20
20
|
response: { 200: CoursesPage }
|
|
21
21
|
},
|
|
22
22
|
handler: async (req) => {
|
|
23
23
|
const scope = await resolveScope(container, req);
|
|
24
|
-
return courses.
|
|
24
|
+
return courses.listCourses(scope.orgId, req.query);
|
|
25
25
|
}
|
|
26
26
|
});
|
|
27
27
|
r.route({
|
|
@@ -30,7 +30,7 @@ async function coursesRoutes(app, container) {
|
|
|
30
30
|
preHandler: app.requireOrgSession,
|
|
31
31
|
schema: {
|
|
32
32
|
operationId: "getCourse",
|
|
33
|
-
tags: ["
|
|
33
|
+
tags: ["Content"],
|
|
34
34
|
summary: "Get a course by id",
|
|
35
35
|
params: CourseIdParam,
|
|
36
36
|
response: {
|
|
@@ -51,7 +51,7 @@ async function coursesRoutes(app, container) {
|
|
|
51
51
|
preHandler: app.requireOrgSession,
|
|
52
52
|
schema: {
|
|
53
53
|
operationId: "createCourse",
|
|
54
|
-
tags: ["
|
|
54
|
+
tags: ["Content"],
|
|
55
55
|
summary: "Create a course",
|
|
56
56
|
body: CreateCourse,
|
|
57
57
|
response: { 201: Course }
|
|
@@ -68,7 +68,7 @@ async function coursesRoutes(app, container) {
|
|
|
68
68
|
preHandler: app.requireOrgSession,
|
|
69
69
|
schema: {
|
|
70
70
|
operationId: "updateCourse",
|
|
71
|
-
tags: ["
|
|
71
|
+
tags: ["Content"],
|
|
72
72
|
summary: "Update a course",
|
|
73
73
|
params: CourseIdParam,
|
|
74
74
|
body: UpdateCourse,
|
|
@@ -88,7 +88,7 @@ async function coursesRoutes(app, container) {
|
|
|
88
88
|
preHandler: app.requireOrgSession,
|
|
89
89
|
schema: {
|
|
90
90
|
operationId: "updateCourseSettings",
|
|
91
|
-
tags: ["
|
|
91
|
+
tags: ["Content"],
|
|
92
92
|
summary: "Update a course settings",
|
|
93
93
|
params: CourseIdParam,
|
|
94
94
|
body: PatchCourseSettings,
|
|
@@ -100,7 +100,7 @@ async function coursesRoutes(app, container) {
|
|
|
100
100
|
handler: async (req) => {
|
|
101
101
|
const scope = await resolveScope(container, req);
|
|
102
102
|
req.log.info({ body: req.body }, "update course settings");
|
|
103
|
-
return courses.
|
|
103
|
+
return courses.patchCourseSettings(scope.orgId, req.params.id, req.body);
|
|
104
104
|
}
|
|
105
105
|
});
|
|
106
106
|
r.route({
|
|
@@ -109,7 +109,7 @@ async function coursesRoutes(app, container) {
|
|
|
109
109
|
preHandler: app.requireOrgSession,
|
|
110
110
|
schema: {
|
|
111
111
|
operationId: "deleteCourse",
|
|
112
|
-
tags: ["
|
|
112
|
+
tags: ["Content"],
|
|
113
113
|
summary: "Delete a course",
|
|
114
114
|
params: CourseIdParam,
|
|
115
115
|
response: {
|