@headless-lms/server 0.4.0 → 0.6.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 +14 -0
- package/dist/app/community-mail.d.ts +5 -3
- package/dist/app/community-mail.js +60 -12
- package/dist/app/community-mail.js.map +1 -1
- package/dist/app/container.d.ts +24 -3
- package/dist/app/container.js +102 -38
- package/dist/app/container.js.map +1 -1
- package/dist/app/notification-channels.js +1 -0
- package/dist/app/notification-channels.js.map +1 -1
- package/dist/app/notification-reads.d.ts +3 -3
- package/dist/app/notification-reads.js +7 -28
- package/dist/app/notification-reads.js.map +1 -1
- package/dist/app/platform.d.ts +14 -0
- package/dist/app/platform.js +14 -0
- package/dist/app/platform.js.map +1 -0
- package/dist/http/config.d.ts +4 -0
- package/dist/http/errors.js +9 -4
- package/dist/http/errors.js.map +1 -1
- package/dist/http/fastify.d.ts +8 -0
- package/dist/http/mcp/catalogue.d.ts +11 -9
- package/dist/http/mcp/catalogue.js +15 -21
- package/dist/http/mcp/catalogue.js.map +1 -1
- package/dist/http/mcp/challenge.js +3 -3
- package/dist/http/mcp/challenge.js.map +1 -1
- package/dist/http/mcp/route.js +7 -7
- package/dist/http/mcp/route.js.map +1 -1
- package/dist/http/mcp/test-support.d.ts +6 -5
- package/dist/http/mcp/test-support.js +32 -11
- package/dist/http/mcp/test-support.js.map +1 -1
- package/dist/http/mcp/tool.d.ts +3 -9
- package/dist/http/mcp/tool.js +14 -6
- package/dist/http/mcp/tool.js.map +1 -1
- package/dist/http/mcp/tools/automations.js +10 -20
- package/dist/http/mcp/tools/automations.js.map +1 -1
- package/dist/http/mcp/tools/catalog.js +25 -42
- package/dist/http/mcp/tools/catalog.js.map +1 -1
- package/dist/http/mcp/tools/comments.js +9 -19
- package/dist/http/mcp/tools/comments.js.map +1 -1
- package/dist/http/mcp/tools/communities.js +17 -32
- package/dist/http/mcp/tools/communities.js.map +1 -1
- package/dist/http/mcp/tools/community-members.js +9 -16
- package/dist/http/mcp/tools/community-members.js.map +1 -1
- package/dist/http/mcp/tools/community-moderation.js +13 -25
- package/dist/http/mcp/tools/community-moderation.js.map +1 -1
- package/dist/http/mcp/tools/courses.js +11 -21
- package/dist/http/mcp/tools/courses.js.map +1 -1
- package/dist/http/mcp/tools/events.d.ts +6 -0
- package/dist/http/mcp/tools/events.js +170 -0
- package/dist/http/mcp/tools/events.js.map +1 -0
- package/dist/http/mcp/tools/index.d.ts +5 -1
- package/dist/http/mcp/tools/index.js +45 -10
- package/dist/http/mcp/tools/index.js.map +1 -1
- package/dist/http/mcp/tools/organization.d.ts +6 -0
- package/dist/http/mcp/tools/organization.js +214 -0
- package/dist/http/mcp/tools/organization.js.map +1 -0
- package/dist/http/mcp/tools/people.js +30 -49
- package/dist/http/mcp/tools/people.js.map +1 -1
- package/dist/http/plugins/auth.js +37 -8
- package/dist/http/plugins/auth.js.map +1 -1
- package/dist/http/plugins/error-handler.d.ts +5 -1
- package/dist/http/plugins/error-handler.js +10 -2
- package/dist/http/plugins/error-handler.js.map +1 -1
- package/dist/http/plugins/openapi.js +18 -2
- package/dist/http/plugins/openapi.js.map +1 -1
- package/dist/http/plugins/platform.d.ts +7 -0
- package/dist/http/plugins/platform.js +13 -0
- package/dist/http/plugins/platform.js.map +1 -0
- package/dist/http/routes/access.js +54 -36
- package/dist/http/routes/access.js.map +1 -1
- package/dist/http/routes/activities.js +45 -45
- package/dist/http/routes/activities.js.map +1 -1
- package/dist/http/routes/assets.js +30 -65
- package/dist/http/routes/assets.js.map +1 -1
- package/dist/http/routes/automations.js +37 -42
- package/dist/http/routes/automations.js.map +1 -1
- package/dist/http/routes/bundles.js +33 -36
- package/dist/http/routes/bundles.js.map +1 -1
- package/dist/http/routes/communities.js +93 -145
- package/dist/http/routes/communities.js.map +1 -1
- package/dist/http/routes/community-moderation.js +83 -137
- package/dist/http/routes/community-moderation.js.map +1 -1
- package/dist/http/routes/course-analytics.js +32 -15
- package/dist/http/routes/course-analytics.js.map +1 -1
- package/dist/http/routes/courses.js +25 -32
- package/dist/http/routes/courses.js.map +1 -1
- package/dist/http/routes/dashboard.js +9 -9
- package/dist/http/routes/dashboard.js.map +1 -1
- package/dist/http/routes/discussion.js +21 -39
- package/dist/http/routes/discussion.js.map +1 -1
- package/dist/http/routes/downloads.js +41 -47
- package/dist/http/routes/downloads.js.map +1 -1
- package/dist/http/routes/entitlements.js +19 -23
- package/dist/http/routes/entitlements.js.map +1 -1
- package/dist/http/routes/events.d.ts +7 -0
- package/dist/http/routes/events.js +333 -0
- package/dist/http/routes/events.js.map +1 -0
- package/dist/http/routes/integrations.js +40 -73
- package/dist/http/routes/integrations.js.map +1 -1
- package/dist/http/routes/member/assets.js +10 -13
- package/dist/http/routes/member/assets.js.map +1 -1
- package/dist/http/routes/member/chats.js +170 -47
- package/dist/http/routes/member/chats.js.map +1 -1
- package/dist/http/routes/member/comments.js +65 -26
- package/dist/http/routes/member/comments.js.map +1 -1
- package/dist/http/routes/member/community.js +45 -52
- package/dist/http/routes/member/community.js.map +1 -1
- package/dist/http/routes/member/courses.js +12 -19
- package/dist/http/routes/member/courses.js.map +1 -1
- package/dist/http/routes/member/devices.js +9 -10
- package/dist/http/routes/member/devices.js.map +1 -1
- package/dist/http/routes/member/downloads.js +6 -9
- package/dist/http/routes/member/downloads.js.map +1 -1
- package/dist/http/routes/member/events.d.ts +7 -0
- package/dist/http/routes/member/events.js +386 -0
- package/dist/http/routes/member/events.js.map +1 -0
- package/dist/http/routes/member/files.js +88 -46
- package/dist/http/routes/member/files.js.map +1 -1
- package/dist/http/routes/member/index.js +2 -0
- package/dist/http/routes/member/index.js.map +1 -1
- package/dist/http/routes/member/notification-settings.js +15 -23
- package/dist/http/routes/member/notification-settings.js.map +1 -1
- package/dist/http/routes/member/notifications.js +16 -16
- package/dist/http/routes/member/notifications.js.map +1 -1
- package/dist/http/routes/member/org.js +5 -15
- package/dist/http/routes/member/org.js.map +1 -1
- package/dist/http/routes/member/posts.js +67 -68
- package/dist/http/routes/member/posts.js.map +1 -1
- package/dist/http/routes/member/profile.js +15 -6
- package/dist/http/routes/member/profile.js.map +1 -1
- package/dist/http/routes/member/progress.js +7 -11
- package/dist/http/routes/member/progress.js.map +1 -1
- package/dist/http/routes/organizations.js +319 -135
- package/dist/http/routes/organizations.js.map +1 -1
- package/dist/http/routes/platform.d.ts +7 -0
- package/dist/http/routes/platform.js +61 -0
- package/dist/http/routes/platform.js.map +1 -0
- package/dist/http/routes/students.js +12 -75
- package/dist/http/routes/students.js.map +1 -1
- package/dist/http/routes/webhooks.js +30 -39
- package/dist/http/routes/webhooks.js.map +1 -1
- package/dist/http/routes.js +9 -0
- package/dist/http/routes.js.map +1 -1
- package/dist/http/schemas/access.d.ts +346 -64
- package/dist/http/schemas/access.js +39 -12
- package/dist/http/schemas/access.js.map +1 -1
- package/dist/http/schemas/assets.d.ts +8 -3
- package/dist/http/schemas/assets.js +6 -5
- package/dist/http/schemas/assets.js.map +1 -1
- package/dist/http/schemas/automations.d.ts +4 -4
- package/dist/http/schemas/chats.d.ts +216 -37
- package/dist/http/schemas/chats.js +13 -4
- package/dist/http/schemas/chats.js.map +1 -1
- package/dist/http/schemas/community.d.ts +105 -31
- package/dist/http/schemas/community.js +25 -9
- package/dist/http/schemas/community.js.map +1 -1
- package/dist/http/schemas/course-analytics.d.ts +51 -1
- package/dist/http/schemas/course-analytics.js +19 -1
- package/dist/http/schemas/course-analytics.js.map +1 -1
- package/dist/http/schemas/dashboard.d.ts +1 -0
- package/dist/http/schemas/dashboard.js +2 -1
- package/dist/http/schemas/dashboard.js.map +1 -1
- package/dist/http/schemas/discussion.d.ts +22 -72
- package/dist/http/schemas/discussion.js +2 -2
- package/dist/http/schemas/discussion.js.map +1 -1
- package/dist/http/schemas/entitlements.d.ts +9 -12
- package/dist/http/schemas/entitlements.js +11 -5
- package/dist/http/schemas/entitlements.js.map +1 -1
- package/dist/http/schemas/events.d.ts +527 -0
- package/dist/http/schemas/events.js +64 -0
- package/dist/http/schemas/events.js.map +1 -0
- package/dist/http/schemas/files.d.ts +24 -5
- package/dist/http/schemas/files.js +15 -2
- package/dist/http/schemas/files.js.map +1 -1
- package/dist/http/schemas/index.d.ts +11 -9
- package/dist/http/schemas/index.js +11 -9
- package/dist/http/schemas/invites.d.ts +3 -47
- package/dist/http/schemas/invites.js +4 -25
- package/dist/http/schemas/invites.js.map +1 -1
- package/dist/http/schemas/member.d.ts +94 -7
- package/dist/http/schemas/member.js +7 -3
- package/dist/http/schemas/member.js.map +1 -1
- package/dist/http/schemas/members.d.ts +329 -65
- package/dist/http/schemas/members.js +67 -25
- package/dist/http/schemas/members.js.map +1 -1
- package/dist/http/schemas/notifications.d.ts +58 -54
- package/dist/http/schemas/platform.d.ts +38 -0
- package/dist/http/schemas/platform.js +19 -0
- package/dist/http/schemas/platform.js.map +1 -0
- package/dist/http/schemas/posts.d.ts +55 -35
- package/dist/http/schemas/posts.js +4 -4
- package/dist/http/schemas/posts.js.map +1 -1
- package/dist/http/schemas/profile.d.ts +1 -1
- package/dist/http/schemas/shared.d.ts +1 -11
- package/dist/http/schemas/shared.js +1 -10
- package/dist/http/schemas/shared.js.map +1 -1
- package/dist/http/schemas/students.d.ts +22 -0
- package/dist/http/schemas/students.js +3 -1
- package/dist/http/schemas/students.js.map +1 -1
- package/dist/http/scope.d.ts +9 -13
- package/dist/http/scope.js +29 -40
- package/dist/http/scope.js.map +1 -1
- package/dist/http/server.js +9 -0
- package/dist/http/server.js.map +1 -1
- package/dist/http/shutdown.d.ts +11 -0
- package/dist/http/shutdown.js +40 -0
- package/dist/http/shutdown.js.map +1 -0
- package/dist/http/sse.d.ts +19 -0
- package/dist/http/sse.js +104 -0
- package/dist/http/sse.js.map +1 -0
- package/dist/http/test-caller.d.ts +13 -0
- package/dist/http/test-caller.js +14 -0
- package/dist/http/test-caller.js.map +1 -0
- package/dist/http/well-known.js +2 -2
- package/dist/http/well-known.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/packages/server/package.js +1 -1
- package/package.json +7 -6
- package/dist/http/routes/member/actor.d.ts +0 -14
- package/dist/http/routes/member/actor.js +0 -11
- package/dist/http/routes/member/actor.js.map +0 -1
|
@@ -1,31 +1,48 @@
|
|
|
1
1
|
import { refusals } from "../schemas/shared.js";
|
|
2
|
-
import {
|
|
3
|
-
import { ApiKeyIdParam, ApiKeyList, ClientIdParam, ConnectedAppList, CreateApiKey, CreatedApiKey,
|
|
2
|
+
import { resolveActor } from "../scope.js";
|
|
3
|
+
import { ApiKeyIdParam, ApiKeyList, ApiKeyRoleList, ClientIdParam, ConnectedAppList, CreateApiKey, CreatedApiKey, PermissionDefinitionList, ViewerPermissions } from "../schemas/access.js";
|
|
4
4
|
import "../schemas/index.js";
|
|
5
|
-
import { assertRoleHoldsScopes, scopeOptions } from "@headless-lms/core/organizations";
|
|
6
5
|
import { z } from "zod";
|
|
7
6
|
//#region src/http/routes/access.ts
|
|
8
7
|
async function accessRoutes(app, container) {
|
|
9
8
|
const r = app.withTypeProvider();
|
|
10
9
|
const tags = ["Access"];
|
|
10
|
+
const organizations = container.organizations;
|
|
11
11
|
r.route({
|
|
12
12
|
method: "GET",
|
|
13
|
-
url: "/api/access/
|
|
13
|
+
url: "/api/access/permissions/list",
|
|
14
14
|
preHandler: app.requireOrgSession,
|
|
15
15
|
schema: {
|
|
16
|
-
operationId: "
|
|
16
|
+
operationId: "listPermissions",
|
|
17
17
|
tags,
|
|
18
|
-
summary: "Every
|
|
19
|
-
description:
|
|
18
|
+
summary: "Every permission, with its label, description and level",
|
|
19
|
+
description: `Any active member of the organization. For role screens, the key-creation screen and the consent screen.`,
|
|
20
20
|
response: {
|
|
21
|
-
200:
|
|
21
|
+
200: PermissionDefinitionList,
|
|
22
22
|
...refusals(403)
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
handler: async (req) => {
|
|
26
|
-
|
|
26
|
+
await resolveActor(container, req);
|
|
27
|
+
return organizations.permissionList();
|
|
27
28
|
}
|
|
28
29
|
});
|
|
30
|
+
r.route({
|
|
31
|
+
method: "GET",
|
|
32
|
+
url: "/api/access/permissions",
|
|
33
|
+
preHandler: app.requireOrgSession,
|
|
34
|
+
schema: {
|
|
35
|
+
operationId: "getMyPermissions",
|
|
36
|
+
tags,
|
|
37
|
+
summary: "Your effective permissions in this organization",
|
|
38
|
+
description: `Any active member of the organization. Your org role, whether you own the organization, the permissions you hold on the organization, and the ones you hold on each content item.`,
|
|
39
|
+
response: {
|
|
40
|
+
200: ViewerPermissions,
|
|
41
|
+
...refusals(403)
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
handler: async (req) => organizations.viewerPermissions(await resolveActor(container, req))
|
|
45
|
+
});
|
|
29
46
|
r.route({
|
|
30
47
|
method: "GET",
|
|
31
48
|
url: "/api/access/apps",
|
|
@@ -34,16 +51,13 @@ async function accessRoutes(app, container) {
|
|
|
34
51
|
operationId: "listConnectedApps",
|
|
35
52
|
tags,
|
|
36
53
|
summary: "Apps connected to this organization as you",
|
|
37
|
-
description:
|
|
54
|
+
description: `Any active member of the organization. The apps you connected to this organization, each with the scopes you granted and when.`,
|
|
38
55
|
response: {
|
|
39
56
|
200: ConnectedAppList,
|
|
40
57
|
...refusals(403)
|
|
41
58
|
}
|
|
42
59
|
},
|
|
43
|
-
handler: async (req) =>
|
|
44
|
-
const scope = await resolveScope(container, req);
|
|
45
|
-
return container.auth.listConnectedApps(req.headers, scope.authOrgId);
|
|
46
|
-
}
|
|
60
|
+
handler: async (req) => organizations.listConnectedApps(await resolveActor(container, req))
|
|
47
61
|
});
|
|
48
62
|
r.route({
|
|
49
63
|
method: "DELETE",
|
|
@@ -53,7 +67,7 @@ async function accessRoutes(app, container) {
|
|
|
53
67
|
operationId: "revokeConnectedApp",
|
|
54
68
|
tags,
|
|
55
69
|
summary: "Disconnect an app",
|
|
56
|
-
description:
|
|
70
|
+
description: `Any active member of the organization. Revokes your consent and tokens for the app in this organization; its next call is refused.`,
|
|
57
71
|
params: ClientIdParam,
|
|
58
72
|
response: {
|
|
59
73
|
204: z.void(),
|
|
@@ -61,8 +75,7 @@ async function accessRoutes(app, container) {
|
|
|
61
75
|
}
|
|
62
76
|
},
|
|
63
77
|
handler: async (req, reply) => {
|
|
64
|
-
|
|
65
|
-
await container.auth.revokeConnectedApp(req.headers, scope.authOrgId, req.params.clientId);
|
|
78
|
+
await organizations.revokeConnectedApp(req.params.clientId, await resolveActor(container, req));
|
|
66
79
|
return reply.code(204).send();
|
|
67
80
|
}
|
|
68
81
|
});
|
|
@@ -74,16 +87,29 @@ async function accessRoutes(app, container) {
|
|
|
74
87
|
operationId: "listApiKeys",
|
|
75
88
|
tags,
|
|
76
89
|
summary: "Your API keys for this organization",
|
|
77
|
-
description:
|
|
90
|
+
description: `Requires \`api_keys:read\`. Your keys for this organization with their role and expiry. The key itself is never returned again after it is created.`,
|
|
78
91
|
response: {
|
|
79
92
|
200: ApiKeyList,
|
|
80
93
|
...refusals(403)
|
|
81
94
|
}
|
|
82
95
|
},
|
|
83
|
-
handler: async (req) =>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
96
|
+
handler: async (req) => organizations.listApiKeys(await resolveActor(container, req))
|
|
97
|
+
});
|
|
98
|
+
r.route({
|
|
99
|
+
method: "GET",
|
|
100
|
+
url: "/api/access/api-keys/roles",
|
|
101
|
+
preHandler: app.requireOrgSession,
|
|
102
|
+
schema: {
|
|
103
|
+
operationId: "listApiKeyRoles",
|
|
104
|
+
tags,
|
|
105
|
+
summary: "The roles you can put on an API key",
|
|
106
|
+
description: `Requires \`api_keys:write\`. The organization's roles whose every permission you hold somewhere.`,
|
|
107
|
+
response: {
|
|
108
|
+
200: ApiKeyRoleList,
|
|
109
|
+
...refusals(403)
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
handler: async (req) => organizations.listApiKeyRoles(await resolveActor(container, req))
|
|
87
113
|
});
|
|
88
114
|
r.route({
|
|
89
115
|
method: "POST",
|
|
@@ -93,22 +119,15 @@ async function accessRoutes(app, container) {
|
|
|
93
119
|
operationId: "createApiKey",
|
|
94
120
|
tags,
|
|
95
121
|
summary: "Create an API key",
|
|
96
|
-
description:
|
|
122
|
+
description: `Requires \`api_keys:write\` and a signed-in person: a request made with an API key or an app token gets 403. The key acts as you in this organization with the permissions of the role given, never beyond what you hold at the time of each request. Send it in the \`x-api-key\` header or as \`Authorization: Bearer <key>\`; every route under /api and /mcp accepts it. 403 when the role holds a permission you hold nowhere; 404 when the organization has no such role. The key is returned once.`,
|
|
97
123
|
body: CreateApiKey,
|
|
98
124
|
response: {
|
|
99
125
|
201: CreatedApiKey,
|
|
100
|
-
...refusals(400, 403)
|
|
126
|
+
...refusals(400, 403, 404)
|
|
101
127
|
}
|
|
102
128
|
},
|
|
103
129
|
handler: async (req, reply) => {
|
|
104
|
-
const
|
|
105
|
-
assertRoleHoldsScopes(scope.role, req.body.scopes);
|
|
106
|
-
const created = await container.auth.createApiKey({
|
|
107
|
-
authUserId: req.authUser.id,
|
|
108
|
-
orgId: scope.authOrgId,
|
|
109
|
-
name: req.body.name,
|
|
110
|
-
scopes: req.body.scopes
|
|
111
|
-
});
|
|
130
|
+
const created = await organizations.createApiKey(req.body, await resolveActor(container, req));
|
|
112
131
|
return reply.code(201).send(created);
|
|
113
132
|
}
|
|
114
133
|
});
|
|
@@ -119,8 +138,8 @@ async function accessRoutes(app, container) {
|
|
|
119
138
|
schema: {
|
|
120
139
|
operationId: "deleteApiKey",
|
|
121
140
|
tags,
|
|
122
|
-
summary: "
|
|
123
|
-
description:
|
|
141
|
+
summary: "Revoke an API key",
|
|
142
|
+
description: `Requires \`api_keys:delete\`. 404 when the key is not one of yours in this organization.`,
|
|
124
143
|
params: ApiKeyIdParam,
|
|
125
144
|
response: {
|
|
126
145
|
204: z.void(),
|
|
@@ -128,8 +147,7 @@ async function accessRoutes(app, container) {
|
|
|
128
147
|
}
|
|
129
148
|
},
|
|
130
149
|
handler: async (req, reply) => {
|
|
131
|
-
|
|
132
|
-
await container.auth.deleteApiKey(req.headers, scope.authOrgId, req.params.id);
|
|
150
|
+
await organizations.deleteApiKey(req.params.id, await resolveActor(container, req));
|
|
133
151
|
return reply.code(204).send();
|
|
134
152
|
}
|
|
135
153
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access.js","names":[],"sources":["../../../src/http/routes/access.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport
|
|
1
|
+
{"version":3,"file":"access.js","names":[],"sources":["../../../src/http/routes/access.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport type { Container } from '../../app/container.js';\nimport {\n ApiKeyIdParam,\n ApiKeyList,\n ApiKeyRoleList,\n ClientIdParam,\n ConnectedAppList,\n CreateApiKey,\n CreatedApiKey,\n PermissionDefinitionList,\n ViewerPermissions,\n refusals,\n} from '../schemas/index.js';\nimport { resolveActor } from '../scope.js';\n\nexport async function accessRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const tags = ['Access'];\n const organizations = container.organizations;\n\n r.route({\n method: 'GET',\n url: '/api/access/permissions/list',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listPermissions',\n tags,\n summary: 'Every permission, with its label, description and level',\n description: `Any active member of the organization. For role screens, the key-creation screen and the consent screen.`,\n response: { 200: PermissionDefinitionList, ...refusals(403) },\n },\n handler: async (req) => {\n await resolveActor(container, req);\n return organizations.permissionList();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/access/permissions',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMyPermissions',\n tags,\n summary: 'Your effective permissions in this organization',\n description: `Any active member of the organization. Your org role, whether you own the organization, the permissions you hold on the organization, and the ones you hold on each content item.`,\n response: { 200: ViewerPermissions, ...refusals(403) },\n },\n handler: async (req) => organizations.viewerPermissions(await resolveActor(container, req)),\n });\n\n r.route({\n method: 'GET',\n url: '/api/access/apps',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listConnectedApps',\n tags,\n summary: 'Apps connected to this organization as you',\n description: `Any active member of the organization. The apps you connected to this organization, each with the scopes you granted and when.`,\n response: { 200: ConnectedAppList, ...refusals(403) },\n },\n handler: async (req) => organizations.listConnectedApps(await resolveActor(container, req)),\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/access/apps/:clientId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'revokeConnectedApp',\n tags,\n summary: 'Disconnect an app',\n description: `Any active member of the organization. Revokes your consent and tokens for the app in this organization; its next call is refused.`,\n params: ClientIdParam,\n response: { 204: z.void(), ...refusals(403) },\n },\n handler: async (req, reply) => {\n await organizations.revokeConnectedApp(\n req.params.clientId,\n await resolveActor(container, req),\n );\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/access/api-keys',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listApiKeys',\n tags,\n summary: 'Your API keys for this organization',\n description: `Requires \\`api_keys:read\\`. Your keys for this organization with their role and expiry. The key itself is never returned again after it is created.`,\n response: { 200: ApiKeyList, ...refusals(403) },\n },\n handler: async (req) => organizations.listApiKeys(await resolveActor(container, req)),\n });\n\n r.route({\n method: 'GET',\n url: '/api/access/api-keys/roles',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listApiKeyRoles',\n tags,\n summary: 'The roles you can put on an API key',\n description: `Requires \\`api_keys:write\\`. The organization's roles whose every permission you hold somewhere.`,\n response: { 200: ApiKeyRoleList, ...refusals(403) },\n },\n handler: async (req) => organizations.listApiKeyRoles(await resolveActor(container, req)),\n });\n\n r.route({\n method: 'POST',\n url: '/api/access/api-keys',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createApiKey',\n tags,\n summary: 'Create an API key',\n description: `Requires \\`api_keys:write\\` and a signed-in person: a request made with an API key or an app token gets 403. The key acts as you in this organization with the permissions of the role given, never beyond what you hold at the time of each request. Send it in the \\`x-api-key\\` header or as \\`Authorization: Bearer <key>\\`; every route under /api and /mcp accepts it. 403 when the role holds a permission you hold nowhere; 404 when the organization has no such role. The key is returned once.`,\n body: CreateApiKey,\n response: { 201: CreatedApiKey, ...refusals(400, 403, 404) },\n },\n handler: async (req, reply) => {\n const created = await organizations.createApiKey(\n req.body,\n await resolveActor(container, req),\n );\n return reply.code(201).send(created);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/access/api-keys/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteApiKey',\n tags,\n summary: 'Revoke an API key',\n description: `Requires \\`api_keys:delete\\`. 404 when the key is not one of yours in this organization.`,\n params: ApiKeyIdParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n await organizations.deleteApiKey(req.params.id, await resolveActor(container, req));\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { refusals } from "../schemas/shared.js";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveActor } from "../scope.js";
|
|
3
3
|
import { ActivityAssetList, ActivityList, ActivityPathParam, CourseIdPathParam, CreateModule, ModuleList, ModulePathParam, ReorderInput, SaveActivity, UpdateModule } from "../schemas/activities.js";
|
|
4
4
|
import "../schemas/index.js";
|
|
5
5
|
//#region src/http/routes/activities.ts
|
|
@@ -14,8 +14,8 @@ async function activitiesRoutes(app, container) {
|
|
|
14
14
|
schema: {
|
|
15
15
|
operationId: "listModules",
|
|
16
16
|
tags,
|
|
17
|
-
summary: "List a course's modules
|
|
18
|
-
description:
|
|
17
|
+
summary: "List a course's modules",
|
|
18
|
+
description: `Requires \`courses:read\`. Modules in order, each with its activities.`,
|
|
19
19
|
params: CourseIdPathParam,
|
|
20
20
|
response: {
|
|
21
21
|
200: ModuleList,
|
|
@@ -23,8 +23,8 @@ async function activitiesRoutes(app, container) {
|
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
handler: async (req) => {
|
|
26
|
-
const
|
|
27
|
-
return content.listCourseModules(
|
|
26
|
+
const actor = await resolveActor(container, req);
|
|
27
|
+
return content.listCourseModules(req.params.courseId, actor);
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
30
|
r.route({
|
|
@@ -34,8 +34,8 @@ async function activitiesRoutes(app, container) {
|
|
|
34
34
|
schema: {
|
|
35
35
|
operationId: "listActivities",
|
|
36
36
|
tags,
|
|
37
|
-
summary: "List a course's activities
|
|
38
|
-
description:
|
|
37
|
+
summary: "List a course's activities",
|
|
38
|
+
description: `Requires \`courses:read\`. Every activity, published or not.`,
|
|
39
39
|
params: CourseIdPathParam,
|
|
40
40
|
response: {
|
|
41
41
|
200: ActivityList,
|
|
@@ -43,8 +43,8 @@ async function activitiesRoutes(app, container) {
|
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
45
|
handler: async (req) => {
|
|
46
|
-
const
|
|
47
|
-
return content.listCourseActivityViews(
|
|
46
|
+
const actor = await resolveActor(container, req);
|
|
47
|
+
return content.listCourseActivityViews(req.params.courseId, actor);
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
50
|
r.route({
|
|
@@ -54,8 +54,8 @@ async function activitiesRoutes(app, container) {
|
|
|
54
54
|
schema: {
|
|
55
55
|
operationId: "listActivityAssets",
|
|
56
56
|
tags,
|
|
57
|
-
summary: "List the library files each activity of a course uses
|
|
58
|
-
description:
|
|
57
|
+
summary: "List the library files each activity of a course uses",
|
|
58
|
+
description: `Requires \`courses:read\`. One row per activity and file, as recorded when the activity was saved.`,
|
|
59
59
|
params: CourseIdPathParam,
|
|
60
60
|
response: {
|
|
61
61
|
200: ActivityAssetList,
|
|
@@ -63,8 +63,8 @@ async function activitiesRoutes(app, container) {
|
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
65
|
handler: async (req) => {
|
|
66
|
-
const
|
|
67
|
-
return content.listCourseActivityAssets(
|
|
66
|
+
const actor = await resolveActor(container, req);
|
|
67
|
+
return content.listCourseActivityAssets(req.params.courseId, actor);
|
|
68
68
|
}
|
|
69
69
|
});
|
|
70
70
|
r.route({
|
|
@@ -74,8 +74,8 @@ async function activitiesRoutes(app, container) {
|
|
|
74
74
|
schema: {
|
|
75
75
|
operationId: "createModule",
|
|
76
76
|
tags,
|
|
77
|
-
summary: "Add a module
|
|
78
|
-
description:
|
|
77
|
+
summary: "Add a module",
|
|
78
|
+
description: `Requires \`courses:write\`. Added last. Returns the course's modules.`,
|
|
79
79
|
params: CourseIdPathParam,
|
|
80
80
|
body: CreateModule,
|
|
81
81
|
response: {
|
|
@@ -84,8 +84,8 @@ async function activitiesRoutes(app, container) {
|
|
|
84
84
|
}
|
|
85
85
|
},
|
|
86
86
|
handler: async (req) => {
|
|
87
|
-
const
|
|
88
|
-
return content.createModule(
|
|
87
|
+
const actor = await resolveActor(container, req);
|
|
88
|
+
return content.createModule(req.params.courseId, req.body.title, actor);
|
|
89
89
|
}
|
|
90
90
|
});
|
|
91
91
|
r.route({
|
|
@@ -95,8 +95,8 @@ async function activitiesRoutes(app, container) {
|
|
|
95
95
|
schema: {
|
|
96
96
|
operationId: "reorderModules",
|
|
97
97
|
tags,
|
|
98
|
-
summary: "Reorder a course's modules
|
|
99
|
-
description:
|
|
98
|
+
summary: "Reorder a course's modules",
|
|
99
|
+
description: `Requires \`courses:write\`. Ids that are not modules of the course are ignored. Returns the course's modules.`,
|
|
100
100
|
params: CourseIdPathParam,
|
|
101
101
|
body: ReorderInput,
|
|
102
102
|
response: {
|
|
@@ -105,8 +105,8 @@ async function activitiesRoutes(app, container) {
|
|
|
105
105
|
}
|
|
106
106
|
},
|
|
107
107
|
handler: async (req) => {
|
|
108
|
-
const
|
|
109
|
-
return content.reorderModules(
|
|
108
|
+
const actor = await resolveActor(container, req);
|
|
109
|
+
return content.reorderModules(req.params.courseId, req.body.orderedIds, actor);
|
|
110
110
|
}
|
|
111
111
|
});
|
|
112
112
|
r.route({
|
|
@@ -116,8 +116,8 @@ async function activitiesRoutes(app, container) {
|
|
|
116
116
|
schema: {
|
|
117
117
|
operationId: "updateModule",
|
|
118
118
|
tags,
|
|
119
|
-
summary: "Rename a module
|
|
120
|
-
description:
|
|
119
|
+
summary: "Rename a module",
|
|
120
|
+
description: `Requires \`courses:write\`. Returns the course's modules.`,
|
|
121
121
|
params: ModulePathParam,
|
|
122
122
|
body: UpdateModule,
|
|
123
123
|
response: {
|
|
@@ -126,8 +126,8 @@ async function activitiesRoutes(app, container) {
|
|
|
126
126
|
}
|
|
127
127
|
},
|
|
128
128
|
handler: async (req) => {
|
|
129
|
-
const
|
|
130
|
-
return content.updateModule(
|
|
129
|
+
const actor = await resolveActor(container, req);
|
|
130
|
+
return content.updateModule(req.params.courseId, req.params.moduleId, req.body.title, actor);
|
|
131
131
|
}
|
|
132
132
|
});
|
|
133
133
|
r.route({
|
|
@@ -137,8 +137,8 @@ async function activitiesRoutes(app, container) {
|
|
|
137
137
|
schema: {
|
|
138
138
|
operationId: "deleteModule",
|
|
139
139
|
tags,
|
|
140
|
-
summary: "Delete a module
|
|
141
|
-
description:
|
|
140
|
+
summary: "Delete a module",
|
|
141
|
+
description: `Requires \`courses:write\`. Returns the course's modules.`,
|
|
142
142
|
params: ModulePathParam,
|
|
143
143
|
response: {
|
|
144
144
|
200: ModuleList,
|
|
@@ -146,8 +146,8 @@ async function activitiesRoutes(app, container) {
|
|
|
146
146
|
}
|
|
147
147
|
},
|
|
148
148
|
handler: async (req) => {
|
|
149
|
-
const
|
|
150
|
-
return content.deleteModule(
|
|
149
|
+
const actor = await resolveActor(container, req);
|
|
150
|
+
return content.deleteModule(req.params.courseId, req.params.moduleId, actor);
|
|
151
151
|
}
|
|
152
152
|
});
|
|
153
153
|
r.route({
|
|
@@ -157,8 +157,8 @@ async function activitiesRoutes(app, container) {
|
|
|
157
157
|
schema: {
|
|
158
158
|
operationId: "reorderActivities",
|
|
159
159
|
tags,
|
|
160
|
-
summary: "Reorder the activities in a module
|
|
161
|
-
description:
|
|
160
|
+
summary: "Reorder the activities in a module",
|
|
161
|
+
description: `Requires \`courses:write\`. Send every activity id of the module once (409 otherwise). Returns the course's modules.`,
|
|
162
162
|
params: ModulePathParam,
|
|
163
163
|
body: ReorderInput,
|
|
164
164
|
response: {
|
|
@@ -167,8 +167,8 @@ async function activitiesRoutes(app, container) {
|
|
|
167
167
|
}
|
|
168
168
|
},
|
|
169
169
|
handler: async (req) => {
|
|
170
|
-
const
|
|
171
|
-
return content.reorderActivities(
|
|
170
|
+
const actor = await resolveActor(container, req);
|
|
171
|
+
return content.reorderActivities(req.params.courseId, req.params.moduleId, req.body.orderedIds, actor);
|
|
172
172
|
}
|
|
173
173
|
});
|
|
174
174
|
r.route({
|
|
@@ -178,8 +178,8 @@ async function activitiesRoutes(app, container) {
|
|
|
178
178
|
schema: {
|
|
179
179
|
operationId: "createActivity",
|
|
180
180
|
tags,
|
|
181
|
-
summary: "Add an activity
|
|
182
|
-
description:
|
|
181
|
+
summary: "Add an activity",
|
|
182
|
+
description: `Requires \`courses:write\`. Records the library files the activity uses; each must be a ready library file (409 otherwise). A file the course does not already use must be uploaded for the course, or the caller needs \`media:read\` (403 otherwise). Returns the course's modules.`,
|
|
183
183
|
params: ModulePathParam,
|
|
184
184
|
body: SaveActivity,
|
|
185
185
|
response: {
|
|
@@ -188,8 +188,8 @@ async function activitiesRoutes(app, container) {
|
|
|
188
188
|
}
|
|
189
189
|
},
|
|
190
190
|
handler: async (req) => {
|
|
191
|
-
const
|
|
192
|
-
return content.saveActivity(
|
|
191
|
+
const actor = await resolveActor(container, req);
|
|
192
|
+
return content.saveActivity(req.params.courseId, req.params.moduleId, req.body, null, actor);
|
|
193
193
|
}
|
|
194
194
|
});
|
|
195
195
|
r.route({
|
|
@@ -199,8 +199,8 @@ async function activitiesRoutes(app, container) {
|
|
|
199
199
|
schema: {
|
|
200
200
|
operationId: "updateActivity",
|
|
201
201
|
tags,
|
|
202
|
-
summary: "Update an activity
|
|
203
|
-
description:
|
|
202
|
+
summary: "Update an activity",
|
|
203
|
+
description: `Requires \`courses:write\`. Replaces its content and records the library files it uses; each must be a ready library file (409 otherwise). A file the course does not already use must be uploaded for the course, or the caller needs \`media:read\` (403 otherwise). Returns the course's modules.`,
|
|
204
204
|
params: ActivityPathParam,
|
|
205
205
|
body: SaveActivity,
|
|
206
206
|
response: {
|
|
@@ -209,8 +209,8 @@ async function activitiesRoutes(app, container) {
|
|
|
209
209
|
}
|
|
210
210
|
},
|
|
211
211
|
handler: async (req) => {
|
|
212
|
-
const
|
|
213
|
-
return content.saveActivity(
|
|
212
|
+
const actor = await resolveActor(container, req);
|
|
213
|
+
return content.saveActivity(req.params.courseId, req.params.moduleId, req.body, req.params.activityId, actor);
|
|
214
214
|
}
|
|
215
215
|
});
|
|
216
216
|
r.route({
|
|
@@ -220,8 +220,8 @@ async function activitiesRoutes(app, container) {
|
|
|
220
220
|
schema: {
|
|
221
221
|
operationId: "deleteActivity",
|
|
222
222
|
tags,
|
|
223
|
-
summary: "Delete an activity
|
|
224
|
-
description:
|
|
223
|
+
summary: "Delete an activity",
|
|
224
|
+
description: `Requires \`courses:write\`. Returns the course's modules.`,
|
|
225
225
|
params: ActivityPathParam,
|
|
226
226
|
response: {
|
|
227
227
|
200: ModuleList,
|
|
@@ -229,8 +229,8 @@ async function activitiesRoutes(app, container) {
|
|
|
229
229
|
}
|
|
230
230
|
},
|
|
231
231
|
handler: async (req) => {
|
|
232
|
-
const
|
|
233
|
-
return content.deleteActivity(
|
|
232
|
+
const actor = await resolveActor(container, req);
|
|
233
|
+
return content.deleteActivity(req.params.courseId, req.params.moduleId, req.params.activityId, actor);
|
|
234
234
|
}
|
|
235
235
|
});
|
|
236
236
|
}
|
|
@@ -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 ActivityAssetList,\n ActivityList,\n ActivityPathParam,\n CourseIdPathParam,\n CreateModule,\n ModuleList,\n ModulePathParam,\n ReorderInput,\n SaveActivity,\n UpdateModule,\n refusals,\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 (staff)\",\n description:\n 'Staff only (owner, admin, instructor). Modules in order, each with its activities.',\n params: CourseIdPathParam,\n response: { 200: ModuleList, ...refusals(403) },\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 (staff)\",\n description: 'Staff only (owner, admin, instructor). Every activity, published or not.',\n params: CourseIdPathParam,\n response: { 200: ActivityList, ...refusals(403) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.listCourseActivityViews(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 the library files each activity of a course uses (staff)',\n description:\n 'Staff only (owner, admin, instructor). One row per activity and file, as recorded when the activity was saved.',\n params: CourseIdPathParam,\n response: { 200: ActivityAssetList, ...refusals(403) },\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 (staff)',\n description:\n \"Staff only (owner, admin, instructor). Added last. Returns the course's modules.\",\n params: CourseIdPathParam,\n body: CreateModule,\n response: { 200: ModuleList, ...refusals(403) },\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 a course's modules (staff)\",\n description:\n \"Staff only (owner, admin, instructor). Ids that are not modules of the course are ignored. Returns the course's modules.\",\n params: CourseIdPathParam,\n body: ReorderInput,\n response: { 200: ModuleList, ...refusals(403) },\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 (staff)',\n description: \"Staff only (owner, admin, instructor). Returns the course's modules.\",\n params: ModulePathParam,\n body: UpdateModule,\n response: { 200: ModuleList, ...refusals(403, 404) },\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 (staff)',\n description: \"Staff only (owner, admin, instructor). Returns the course's modules.\",\n params: ModulePathParam,\n response: { 200: ModuleList, ...refusals(403, 404) },\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 the activities in a module (staff)',\n description:\n \"Staff only (owner, admin, instructor). Send every activity id of the module once (409 otherwise). Returns the course's modules.\",\n params: ModulePathParam,\n body: ReorderInput,\n response: { 200: ModuleList, ...refusals(403, 404, 409) },\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 (staff)',\n description:\n \"Staff only (owner, admin, instructor). Records the library files the activity uses; each must be a ready library file (409 otherwise). Returns the course's modules.\",\n params: ModulePathParam,\n body: SaveActivity,\n response: { 200: ModuleList, ...refusals(403, 404, 409) },\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 (staff)',\n description:\n \"Staff only (owner, admin, instructor). Replaces its content and records the library files it uses; each must be a ready library file (409 otherwise). Returns the course's modules.\",\n params: ActivityPathParam,\n body: SaveActivity,\n response: { 200: ModuleList, ...refusals(403, 404, 409) },\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 (staff)',\n description: \"Staff only (owner, admin, instructor). Returns the course's modules.\",\n params: ActivityPathParam,\n response: { 200: ModuleList, ...refusals(403, 404) },\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.\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 refusals,\n} from '../schemas/index.js';\nimport type { Container } from '../../app/container.js';\nimport { resolveActor } 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 description: `Requires \\`courses:read\\`. Modules in order, each with its activities.`,\n params: CourseIdPathParam,\n response: { 200: ModuleList, ...refusals(403) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.listCourseModules(req.params.courseId, actor);\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 description: `Requires \\`courses:read\\`. Every activity, published or not.`,\n params: CourseIdPathParam,\n response: { 200: ActivityList, ...refusals(403) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.listCourseActivityViews(req.params.courseId, actor);\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 the library files each activity of a course uses',\n description: `Requires \\`courses:read\\`. One row per activity and file, as recorded when the activity was saved.`,\n params: CourseIdPathParam,\n response: { 200: ActivityAssetList, ...refusals(403) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.listCourseActivityAssets(req.params.courseId, actor);\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 description: `Requires \\`courses:write\\`. Added last. Returns the course's modules.`,\n params: CourseIdPathParam,\n body: CreateModule,\n response: { 200: ModuleList, ...refusals(403) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.createModule(req.params.courseId, req.body.title, actor);\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 a course's modules\",\n description: `Requires \\`courses:write\\`. Ids that are not modules of the course are ignored. Returns the course's modules.`,\n params: CourseIdPathParam,\n body: ReorderInput,\n response: { 200: ModuleList, ...refusals(403) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.reorderModules(req.params.courseId, req.body.orderedIds, actor);\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 description: `Requires \\`courses:write\\`. Returns the course's modules.`,\n params: ModulePathParam,\n body: UpdateModule,\n response: { 200: ModuleList, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.updateModule(req.params.courseId, req.params.moduleId, req.body.title, actor);\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 description: `Requires \\`courses:write\\`. Returns the course's modules.`,\n params: ModulePathParam,\n response: { 200: ModuleList, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.deleteModule(req.params.courseId, req.params.moduleId, actor);\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 the activities in a module',\n description: `Requires \\`courses:write\\`. Send every activity id of the module once (409 otherwise). Returns the course's modules.`,\n params: ModulePathParam,\n body: ReorderInput,\n response: { 200: ModuleList, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.reorderActivities(\n req.params.courseId,\n req.params.moduleId,\n req.body.orderedIds,\n actor,\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 description: `Requires \\`courses:write\\`. Records the library files the activity uses; each must be a ready library file (409 otherwise). A file the course does not already use must be uploaded for the course, or the caller needs \\`media:read\\` (403 otherwise). Returns the course's modules.`,\n params: ModulePathParam,\n body: SaveActivity,\n response: { 200: ModuleList, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.saveActivity(req.params.courseId, req.params.moduleId, req.body, null, actor);\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 description: `Requires \\`courses:write\\`. Replaces its content and records the library files it uses; each must be a ready library file (409 otherwise). A file the course does not already use must be uploaded for the course, or the caller needs \\`media:read\\` (403 otherwise). Returns the course's modules.`,\n params: ActivityPathParam,\n body: SaveActivity,\n response: { 200: ModuleList, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.saveActivity(\n req.params.courseId,\n req.params.moduleId,\n req.body,\n req.params.activityId,\n actor,\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 description: `Requires \\`courses:write\\`. Returns the course's modules.`,\n params: ActivityPathParam,\n response: { 200: ModuleList, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.deleteActivity(\n req.params.courseId,\n req.params.moduleId,\n req.params.activityId,\n actor,\n );\n },\n });\n}\n"],"mappings":"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"}
|