@headless-lms/server 0.1.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/LICENSE +21 -0
- package/README.md +48 -0
- package/dist/app/container.d.ts +124 -0
- package/dist/app/container.js +312 -0
- package/dist/app/container.js.map +1 -0
- package/dist/app/db.d.ts +2 -0
- package/dist/app/db.js +2 -0
- package/dist/app/integrations.d.ts +7 -0
- package/dist/app/integrations.js +44 -0
- package/dist/app/integrations.js.map +1 -0
- package/dist/app/notifications.d.ts +7 -0
- package/dist/app/notifications.js +23 -0
- package/dist/app/notifications.js.map +1 -0
- package/dist/http/config.d.ts +25 -0
- package/dist/http/config.js +1 -0
- package/dist/http/fastify.d.ts +35 -0
- package/dist/http/plugins/auth.d.ts +11 -0
- package/dist/http/plugins/auth.js +47 -0
- package/dist/http/plugins/auth.js.map +1 -0
- package/dist/http/plugins/cors.d.ts +7 -0
- package/dist/http/plugins/cors.js +22 -0
- package/dist/http/plugins/cors.js.map +1 -0
- package/dist/http/plugins/error-handler.d.ts +6 -0
- package/dist/http/plugins/error-handler.js +64 -0
- package/dist/http/plugins/error-handler.js.map +1 -0
- package/dist/http/plugins/openapi.d.ts +7 -0
- package/dist/http/plugins/openapi.js +63 -0
- package/dist/http/plugins/openapi.js.map +1 -0
- package/dist/http/routes/activities.d.ts +7 -0
- package/dist/http/routes/activities.js +163 -0
- package/dist/http/routes/activities.js.map +1 -0
- package/dist/http/routes/assets.d.ts +7 -0
- package/dist/http/routes/assets.js +168 -0
- package/dist/http/routes/assets.js.map +1 -0
- package/dist/http/routes/automations.d.ts +7 -0
- package/dist/http/routes/automations.js +158 -0
- package/dist/http/routes/automations.js.map +1 -0
- package/dist/http/routes/courses.d.ts +7 -0
- package/dist/http/routes/courses.js +130 -0
- package/dist/http/routes/courses.js.map +1 -0
- package/dist/http/routes/dashboard.d.ts +7 -0
- package/dist/http/routes/dashboard.js +42 -0
- package/dist/http/routes/dashboard.js.map +1 -0
- package/dist/http/routes/discussion.d.ts +7 -0
- package/dist/http/routes/discussion.js +122 -0
- package/dist/http/routes/discussion.js.map +1 -0
- package/dist/http/routes/downloads.d.ts +7 -0
- package/dist/http/routes/downloads.js +208 -0
- package/dist/http/routes/downloads.js.map +1 -0
- package/dist/http/routes/entitlements.d.ts +7 -0
- package/dist/http/routes/entitlements.js +69 -0
- package/dist/http/routes/entitlements.js.map +1 -0
- package/dist/http/routes/identity.d.ts +6 -0
- package/dist/http/routes/identity.js +6 -0
- package/dist/http/routes/identity.js.map +1 -0
- package/dist/http/routes/integrations.d.ts +7 -0
- package/dist/http/routes/integrations.js +210 -0
- package/dist/http/routes/integrations.js.map +1 -0
- package/dist/http/routes/learn/assets.d.ts +8 -0
- package/dist/http/routes/learn/assets.js +32 -0
- package/dist/http/routes/learn/assets.js.map +1 -0
- package/dist/http/routes/learn/comments.d.ts +7 -0
- package/dist/http/routes/learn/comments.js +175 -0
- package/dist/http/routes/learn/comments.js.map +1 -0
- package/dist/http/routes/learn/courses.d.ts +7 -0
- package/dist/http/routes/learn/courses.js +75 -0
- package/dist/http/routes/learn/courses.js.map +1 -0
- package/dist/http/routes/learn/downloads.d.ts +7 -0
- package/dist/http/routes/learn/downloads.js +76 -0
- package/dist/http/routes/learn/downloads.js.map +1 -0
- package/dist/http/routes/learn/index.d.ts +7 -0
- package/dist/http/routes/learn/index.js +19 -0
- package/dist/http/routes/learn/index.js.map +1 -0
- package/dist/http/routes/learn/org.d.ts +7 -0
- package/dist/http/routes/learn/org.js +50 -0
- package/dist/http/routes/learn/org.js.map +1 -0
- package/dist/http/routes/learn/progress.d.ts +7 -0
- package/dist/http/routes/learn/progress.js +63 -0
- package/dist/http/routes/learn/progress.js.map +1 -0
- package/dist/http/routes/organizations.d.ts +7 -0
- package/dist/http/routes/organizations.js +219 -0
- package/dist/http/routes/organizations.js.map +1 -0
- package/dist/http/routes/progress.d.ts +6 -0
- package/dist/http/routes/progress.js +6 -0
- package/dist/http/routes/progress.js.map +1 -0
- package/dist/http/routes/students.d.ts +7 -0
- package/dist/http/routes/students.js +125 -0
- package/dist/http/routes/students.js.map +1 -0
- package/dist/http/routes.d.ts +7 -0
- package/dist/http/routes.js +37 -0
- package/dist/http/routes.js.map +1 -0
- package/dist/http/schemas/activities.d.ts +69 -0
- package/dist/http/schemas/activities.js +25 -0
- package/dist/http/schemas/activities.js.map +1 -0
- package/dist/http/schemas/assets.d.ts +142 -0
- package/dist/http/schemas/assets.js +24 -0
- package/dist/http/schemas/assets.js.map +1 -0
- package/dist/http/schemas/automations.d.ts +154 -0
- package/dist/http/schemas/automations.js +26 -0
- package/dist/http/schemas/automations.js.map +1 -0
- package/dist/http/schemas/content.d.ts +113 -0
- package/dist/http/schemas/content.js +29 -0
- package/dist/http/schemas/content.js.map +1 -0
- package/dist/http/schemas/dashboard.d.ts +26 -0
- package/dist/http/schemas/dashboard.js +18 -0
- package/dist/http/schemas/dashboard.js.map +1 -0
- package/dist/http/schemas/discussion.d.ts +340 -0
- package/dist/http/schemas/discussion.js +78 -0
- package/dist/http/schemas/discussion.js.map +1 -0
- package/dist/http/schemas/downloads.d.ts +110 -0
- package/dist/http/schemas/downloads.js +34 -0
- package/dist/http/schemas/downloads.js.map +1 -0
- package/dist/http/schemas/entitlements.d.ts +97 -0
- package/dist/http/schemas/entitlements.js +41 -0
- package/dist/http/schemas/entitlements.js.map +1 -0
- package/dist/http/schemas/index.d.ts +17 -0
- package/dist/http/schemas/index.js +17 -0
- package/dist/http/schemas/integrations.d.ts +93 -0
- package/dist/http/schemas/integrations.js +47 -0
- package/dist/http/schemas/integrations.js.map +1 -0
- package/dist/http/schemas/invites.d.ts +95 -0
- package/dist/http/schemas/invites.js +62 -0
- package/dist/http/schemas/invites.js.map +1 -0
- package/dist/http/schemas/learn.d.ts +142 -0
- package/dist/http/schemas/learn.js +58 -0
- package/dist/http/schemas/learn.js.map +1 -0
- package/dist/http/schemas/members.d.ts +88 -0
- package/dist/http/schemas/members.js +27 -0
- package/dist/http/schemas/members.js.map +1 -0
- package/dist/http/schemas/organizations.d.ts +22 -0
- package/dist/http/schemas/organizations.js +20 -0
- package/dist/http/schemas/organizations.js.map +1 -0
- package/dist/http/schemas/settings.d.ts +23 -0
- package/dist/http/schemas/settings.js +20 -0
- package/dist/http/schemas/settings.js.map +1 -0
- package/dist/http/schemas/shared.d.ts +57 -0
- package/dist/http/schemas/shared.js +54 -0
- package/dist/http/schemas/shared.js.map +1 -0
- package/dist/http/schemas/students.d.ts +64 -0
- package/dist/http/schemas/students.js +25 -0
- package/dist/http/schemas/students.js.map +1 -0
- package/dist/http/scope.d.ts +22 -0
- package/dist/http/scope.js +28 -0
- package/dist/http/scope.js.map +1 -0
- package/dist/http/server.d.ts +8 -0
- package/dist/http/server.js +33 -0
- package/dist/http/server.js.map +1 -0
- package/dist/http/web-bridge.d.ts +9 -0
- package/dist/http/web-bridge.js +27 -0
- package/dist/http/web-bridge.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { ErrorBody } from "../schemas/shared.js";
|
|
2
|
+
import { ActionInvocationResult, AvailableIntegrationsList, ConfigureRequest, ConnectRequest, Connection, ConnectionActionParams, ConnectionIdParam, ConnectionsList, InvokeActionRequest, ReconnectRequest } from "../schemas/integrations.js";
|
|
3
|
+
import "../schemas/index.js";
|
|
4
|
+
import { NotFoundError } from "@headless-lms/core/shared/errors";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
//#region src/http/routes/integrations.ts
|
|
7
|
+
function toApi(connection) {
|
|
8
|
+
const { credentialRef: _credentialRef, ...rest } = connection;
|
|
9
|
+
return rest;
|
|
10
|
+
}
|
|
11
|
+
/** Resolve the session's active org to the domain org id, or 400 and return null. */
|
|
12
|
+
async function resolveOrgId(req, reply) {
|
|
13
|
+
if (!req.orgId) {
|
|
14
|
+
await reply.code(400).send({
|
|
15
|
+
error: "no_active_org",
|
|
16
|
+
message: "No active organization"
|
|
17
|
+
});
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return req.orgId;
|
|
21
|
+
}
|
|
22
|
+
async function integrationsRoutes(app, container) {
|
|
23
|
+
const r = app.withTypeProvider();
|
|
24
|
+
const integrations = container.integrations;
|
|
25
|
+
const tags = ["Integrations"];
|
|
26
|
+
r.route({
|
|
27
|
+
method: "GET",
|
|
28
|
+
url: "/api/integrations/available",
|
|
29
|
+
preHandler: app.requireOrgSession,
|
|
30
|
+
schema: {
|
|
31
|
+
operationId: "listAvailableIntegrations",
|
|
32
|
+
tags,
|
|
33
|
+
summary: "List the integrations this deployment supports, with their config schemas",
|
|
34
|
+
response: {
|
|
35
|
+
200: AvailableIntegrationsList,
|
|
36
|
+
401: ErrorBody
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
handler: async () => integrations.available()
|
|
40
|
+
});
|
|
41
|
+
r.route({
|
|
42
|
+
method: "GET",
|
|
43
|
+
url: "/api/integrations",
|
|
44
|
+
preHandler: app.requireOrgSession,
|
|
45
|
+
schema: {
|
|
46
|
+
operationId: "listConnections",
|
|
47
|
+
tags,
|
|
48
|
+
summary: "List the organization's integration connections",
|
|
49
|
+
response: {
|
|
50
|
+
200: ConnectionsList,
|
|
51
|
+
400: ErrorBody,
|
|
52
|
+
401: ErrorBody
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
handler: async (req, reply) => {
|
|
56
|
+
const orgId = await resolveOrgId(req, reply);
|
|
57
|
+
if (!orgId) return;
|
|
58
|
+
return (await integrations.list(orgId)).map(toApi);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
r.route({
|
|
62
|
+
method: "POST",
|
|
63
|
+
url: "/api/integrations",
|
|
64
|
+
preHandler: app.requireOrgSession,
|
|
65
|
+
schema: {
|
|
66
|
+
operationId: "connectIntegration",
|
|
67
|
+
tags,
|
|
68
|
+
summary: "Connect an integration (stores its secrets encrypted)",
|
|
69
|
+
body: ConnectRequest,
|
|
70
|
+
response: {
|
|
71
|
+
201: Connection,
|
|
72
|
+
400: ErrorBody,
|
|
73
|
+
401: ErrorBody,
|
|
74
|
+
409: ErrorBody
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
handler: async (req, reply) => {
|
|
78
|
+
const orgId = await resolveOrgId(req, reply);
|
|
79
|
+
if (!orgId) return;
|
|
80
|
+
const connection = await integrations.connect(orgId, req.body);
|
|
81
|
+
return reply.code(201).send(toApi(connection));
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
r.route({
|
|
85
|
+
method: "GET",
|
|
86
|
+
url: "/api/integrations/:id",
|
|
87
|
+
preHandler: app.requireOrgSession,
|
|
88
|
+
schema: {
|
|
89
|
+
operationId: "getConnection",
|
|
90
|
+
tags,
|
|
91
|
+
summary: "Get a connection",
|
|
92
|
+
params: ConnectionIdParam,
|
|
93
|
+
response: {
|
|
94
|
+
200: Connection,
|
|
95
|
+
400: ErrorBody,
|
|
96
|
+
401: ErrorBody,
|
|
97
|
+
404: ErrorBody
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
handler: async (req, reply) => {
|
|
101
|
+
const orgId = await resolveOrgId(req, reply);
|
|
102
|
+
if (!orgId) return;
|
|
103
|
+
const connection = await integrations.get(orgId, req.params.id);
|
|
104
|
+
if (!connection) throw new NotFoundError("Connection", req.params.id);
|
|
105
|
+
return toApi(connection);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
r.route({
|
|
109
|
+
method: "PATCH",
|
|
110
|
+
url: "/api/integrations/:id",
|
|
111
|
+
preHandler: app.requireOrgSession,
|
|
112
|
+
schema: {
|
|
113
|
+
operationId: "configureConnection",
|
|
114
|
+
tags,
|
|
115
|
+
summary: "Change a connection's configuration or active flag",
|
|
116
|
+
params: ConnectionIdParam,
|
|
117
|
+
body: ConfigureRequest,
|
|
118
|
+
response: {
|
|
119
|
+
200: Connection,
|
|
120
|
+
400: ErrorBody,
|
|
121
|
+
401: ErrorBody,
|
|
122
|
+
404: ErrorBody
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
handler: async (req, reply) => {
|
|
126
|
+
const orgId = await resolveOrgId(req, reply);
|
|
127
|
+
if (!orgId) return;
|
|
128
|
+
const connection = await integrations.configure(orgId, req.params.id, req.body);
|
|
129
|
+
if (!connection) throw new NotFoundError("Connection", req.params.id);
|
|
130
|
+
return toApi(connection);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
r.route({
|
|
134
|
+
method: "POST",
|
|
135
|
+
url: "/api/integrations/:id/reconnect",
|
|
136
|
+
preHandler: app.requireOrgSession,
|
|
137
|
+
schema: {
|
|
138
|
+
operationId: "reconnectIntegration",
|
|
139
|
+
tags,
|
|
140
|
+
summary: "Replace a connection's secrets (re-authenticate)",
|
|
141
|
+
params: ConnectionIdParam,
|
|
142
|
+
body: ReconnectRequest,
|
|
143
|
+
response: {
|
|
144
|
+
200: Connection,
|
|
145
|
+
400: ErrorBody,
|
|
146
|
+
401: ErrorBody,
|
|
147
|
+
404: ErrorBody
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
handler: async (req, reply) => {
|
|
151
|
+
const orgId = await resolveOrgId(req, reply);
|
|
152
|
+
if (!orgId) return;
|
|
153
|
+
const connection = await integrations.reconnect(orgId, req.params.id, req.body.secrets);
|
|
154
|
+
if (!connection) throw new NotFoundError("Connection", req.params.id);
|
|
155
|
+
return toApi(connection);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
r.route({
|
|
159
|
+
method: "POST",
|
|
160
|
+
url: "/api/integrations/:id/actions/:actionId",
|
|
161
|
+
preHandler: app.requireOrgSession,
|
|
162
|
+
schema: {
|
|
163
|
+
operationId: "invokeConnectionAction",
|
|
164
|
+
tags,
|
|
165
|
+
summary: "Invoke one of a connection's actions (e.g. a listing that feeds a picker)",
|
|
166
|
+
params: ConnectionActionParams,
|
|
167
|
+
body: InvokeActionRequest,
|
|
168
|
+
response: {
|
|
169
|
+
200: ActionInvocationResult,
|
|
170
|
+
400: ErrorBody,
|
|
171
|
+
401: ErrorBody,
|
|
172
|
+
404: ErrorBody,
|
|
173
|
+
409: ErrorBody,
|
|
174
|
+
502: ErrorBody
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
handler: async (req, reply) => {
|
|
178
|
+
const orgId = await resolveOrgId(req, reply);
|
|
179
|
+
if (!orgId) return;
|
|
180
|
+
return { output: await integrations.invoke(orgId, req.params.id, req.params.actionId, req.body.input ?? {}) };
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
r.route({
|
|
184
|
+
method: "DELETE",
|
|
185
|
+
url: "/api/integrations/:id",
|
|
186
|
+
preHandler: app.requireOrgSession,
|
|
187
|
+
schema: {
|
|
188
|
+
operationId: "disconnectIntegration",
|
|
189
|
+
tags,
|
|
190
|
+
summary: "Disconnect an integration (destroys its stored secrets)",
|
|
191
|
+
params: ConnectionIdParam,
|
|
192
|
+
response: {
|
|
193
|
+
204: z.void(),
|
|
194
|
+
400: ErrorBody,
|
|
195
|
+
401: ErrorBody,
|
|
196
|
+
404: ErrorBody
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
handler: async (req, reply) => {
|
|
200
|
+
const orgId = await resolveOrgId(req, reply);
|
|
201
|
+
if (!orgId) return;
|
|
202
|
+
if (!await integrations.disconnect(orgId, req.params.id)) throw new NotFoundError("Connection", req.params.id);
|
|
203
|
+
return reply.code(204).send();
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
//#endregion
|
|
208
|
+
export { integrationsRoutes };
|
|
209
|
+
|
|
210
|
+
//# sourceMappingURL=integrations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integrations.js","names":[],"sources":["../../../src/http/routes/integrations.ts"],"sourcesContent":["// HTTP routes for integrations (an org's connections to external services).\n// All require a session with an active organization. Credentials are\n// write-only: accepted on connect/reconnect, never present in a response —\n// `toApi` strips the credentialRef so not even the reference leaks.\nimport type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport {\n ActionInvocationResult,\n AvailableIntegrationsList,\n ConfigureRequest,\n ConnectRequest,\n Connection,\n ConnectionActionParams,\n ConnectionIdParam,\n ConnectionsList,\n ErrorBody,\n InvokeActionRequest,\n ReconnectRequest,\n} from '../schemas/index.js';\nimport type { Connection as DomainConnection } from '@headless-lms/core/integrations';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../app/container.js';\n\nfunction toApi(connection: DomainConnection): Connection {\n const { credentialRef: _credentialRef, ...rest } = connection;\n return rest;\n}\n\n/** Resolve the session's active org to the domain org id, or 400 and return null. */\nasync function resolveOrgId(\n req: FastifyRequest,\n reply: FastifyReply,\n): Promise<string | null> {\n if (!req.orgId) {\n await reply.code(400).send({ error: 'no_active_org', message: 'No active organization' });\n return null;\n }\n return req.orgId;\n}\n\nexport async function integrationsRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const integrations = container.integrations;\n const tags = ['Integrations'];\n\n r.route({\n method: 'GET',\n url: '/api/integrations/available',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listAvailableIntegrations',\n tags,\n summary: 'List the integrations this deployment supports, with their config schemas',\n response: { 200: AvailableIntegrationsList, 401: ErrorBody },\n },\n handler: async () => integrations.available(),\n });\n\n r.route({\n method: 'GET',\n url: '/api/integrations',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listConnections',\n tags,\n summary: \"List the organization's integration connections\",\n response: { 200: ConnectionsList, 400: ErrorBody, 401: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n return (await integrations.list(orgId)).map(toApi);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/integrations',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'connectIntegration',\n tags,\n summary: 'Connect an integration (stores its secrets encrypted)',\n body: ConnectRequest,\n response: { 201: Connection, 400: ErrorBody, 401: ErrorBody, 409: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const connection = await integrations.connect(orgId, req.body);\n return reply.code(201).send(toApi(connection));\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/integrations/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getConnection',\n tags,\n summary: 'Get a connection',\n params: ConnectionIdParam,\n response: { 200: Connection, 400: ErrorBody, 401: ErrorBody, 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const connection = await integrations.get(orgId, req.params.id);\n if (!connection) {\n throw new NotFoundError('Connection', req.params.id);\n }\n return toApi(connection);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/integrations/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'configureConnection',\n tags,\n summary: \"Change a connection's configuration or active flag\",\n params: ConnectionIdParam,\n body: ConfigureRequest,\n response: { 200: Connection, 400: ErrorBody, 401: ErrorBody, 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const connection = await integrations.configure(orgId, req.params.id, req.body);\n if (!connection) {\n throw new NotFoundError('Connection', req.params.id);\n }\n return toApi(connection);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/integrations/:id/reconnect',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reconnectIntegration',\n tags,\n summary: \"Replace a connection's secrets (re-authenticate)\",\n params: ConnectionIdParam,\n body: ReconnectRequest,\n response: { 200: Connection, 400: ErrorBody, 401: ErrorBody, 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const connection = await integrations.reconnect(orgId, req.params.id, req.body.secrets);\n if (!connection) {\n throw new NotFoundError('Connection', req.params.id);\n }\n return toApi(connection);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/integrations/:id/actions/:actionId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'invokeConnectionAction',\n tags,\n summary: \"Invoke one of a connection's actions (e.g. a listing that feeds a picker)\",\n params: ConnectionActionParams,\n body: InvokeActionRequest,\n response: {\n 200: ActionInvocationResult,\n 400: ErrorBody,\n 401: ErrorBody,\n 404: ErrorBody,\n 409: ErrorBody,\n 502: ErrorBody,\n },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const output = await integrations.invoke(\n orgId,\n req.params.id,\n req.params.actionId,\n req.body.input ?? {},\n );\n return { output: output as Record<string, unknown> };\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/integrations/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'disconnectIntegration',\n tags,\n summary: 'Disconnect an integration (destroys its stored secrets)',\n params: ConnectionIdParam,\n response: { 204: z.void(), 400: ErrorBody, 401: ErrorBody, 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const removed = await integrations.disconnect(orgId, req.params.id);\n if (!removed) {\n throw new NotFoundError('Connection', req.params.id);\n }\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Container } from "../../../app/container.js";
|
|
2
|
+
import "../../../index.js";
|
|
3
|
+
import { FastifyInstance } from "fastify";
|
|
4
|
+
//#region src/http/routes/learn/assets.d.ts
|
|
5
|
+
declare function learnAssetsRoutes(app: FastifyInstance, container: Container): Promise<void>;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { learnAssetsRoutes };
|
|
8
|
+
//# sourceMappingURL=assets.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ErrorBody } from "../../schemas/shared.js";
|
|
2
|
+
import { AssetIdParam, DownloadTicket } from "../../schemas/assets.js";
|
|
3
|
+
import "../../schemas/index.js";
|
|
4
|
+
import { NotFoundError } from "@headless-lms/core/shared/errors";
|
|
5
|
+
//#region src/http/routes/learn/assets.ts
|
|
6
|
+
async function learnAssetsRoutes(app, container) {
|
|
7
|
+
app.withTypeProvider().route({
|
|
8
|
+
method: "POST",
|
|
9
|
+
url: "/api/learn/assets/:id/url",
|
|
10
|
+
preHandler: app.requireOrgSession,
|
|
11
|
+
schema: {
|
|
12
|
+
operationId: "getAssetDownloadUrl",
|
|
13
|
+
tags: ["Learn"],
|
|
14
|
+
summary: "Get a short-lived presigned URL to serve an asset to the student",
|
|
15
|
+
params: AssetIdParam,
|
|
16
|
+
response: {
|
|
17
|
+
200: DownloadTicket,
|
|
18
|
+
401: ErrorBody,
|
|
19
|
+
404: ErrorBody
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
handler: async (req) => {
|
|
23
|
+
const ticket = await container.assets.requestDownload(req.orgId, req.params.id);
|
|
24
|
+
if (!ticket) throw new NotFoundError("Asset", req.params.id);
|
|
25
|
+
return ticket;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { learnAssetsRoutes };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=assets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets.js","names":[],"sources":["../../../../src/http/routes/learn/assets.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { AssetIdParam, DownloadTicket, ErrorBody } from '../../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '@headless-lms/server';\n\nexport async function learnAssetsRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n\n // Content embeds assets by stable `assetId`; the URL persisted at authoring\n // time is a long-expired presign. The student surface mints a fresh\n // short-lived ticket here, scoped to the session's portal org.\n r.route({\n method: 'POST',\n url: '/api/learn/assets/:id/url',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getAssetDownloadUrl',\n tags: ['Learn'],\n summary: 'Get a short-lived presigned URL to serve an asset to the student',\n params: AssetIdParam,\n response: { 200: DownloadTicket, 401: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n\n // No body: the student surface never renames a download, so the asset's\n // own filename stands.\n const ticket = await container.assets.requestDownload(req.orgId, req.params.id);\n if (!ticket) {\n throw new NotFoundError('Asset', req.params.id);\n }\n return ticket;\n },\n });\n}\n"],"mappings":";;;;;AAMA,eAAsB,kBAAkB,KAAsB,WAAqC;CAMjG,IALc,iBAKd,CAAC,CAAC,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,OAAO;GACd,SAAS;GACT,QAAQ;GACR,UAAU;IAAE,KAAK;IAAgB,KAAK;IAAW,KAAK;GAAU;EAClE;EACA,SAAS,OAAO,QAAQ;GAItB,MAAM,SAAS,MAAM,UAAU,OAAO,gBAAgB,IAAI,OAAO,IAAI,OAAO,EAAE;GAC9E,IAAI,CAAC,QACH,MAAM,IAAI,cAAc,SAAS,IAAI,OAAO,EAAE;GAEhD,OAAO;EACT;CACF,CAAC;AACH"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Container } from "../../../app/container.js";
|
|
2
|
+
import { FastifyInstance } from "fastify";
|
|
3
|
+
//#region src/http/routes/learn/comments.d.ts
|
|
4
|
+
declare function learnCommentsRoutes(app: FastifyInstance, container: Container): Promise<void>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { learnCommentsRoutes };
|
|
7
|
+
//# sourceMappingURL=comments.d.ts.map
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { UnauthorizedError } from "../../plugins/auth.js";
|
|
2
|
+
import { ErrorBody } from "../../schemas/shared.js";
|
|
3
|
+
import { ActivityComments, Comment, CommentIdParam, CommentReactions, CommentReport, CommentView, DiscussionActivityParam, EditComment, PostComment, ReportComment, SetCommentReaction } from "../../schemas/discussion.js";
|
|
4
|
+
import "../../schemas/index.js";
|
|
5
|
+
//#region src/http/routes/learn/comments.ts
|
|
6
|
+
async function learnCommentsRoutes(app, container) {
|
|
7
|
+
const r = app.withTypeProvider();
|
|
8
|
+
const discussion = container.discussion;
|
|
9
|
+
r.route({
|
|
10
|
+
method: "GET",
|
|
11
|
+
url: "/api/learn/activities/:activityId/comments",
|
|
12
|
+
preHandler: app.requireOrgSession,
|
|
13
|
+
schema: {
|
|
14
|
+
operationId: "listActivityComments",
|
|
15
|
+
tags: ["Learn"],
|
|
16
|
+
summary: "Read an activity's comments",
|
|
17
|
+
params: DiscussionActivityParam,
|
|
18
|
+
response: {
|
|
19
|
+
200: ActivityComments,
|
|
20
|
+
404: ErrorBody
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
handler: async (req) => {
|
|
24
|
+
const orgId = req.orgId;
|
|
25
|
+
const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);
|
|
26
|
+
if (!orgUser) throw new UnauthorizedError();
|
|
27
|
+
return discussion.activityComments(orgId, req.params.activityId, orgUser);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
r.route({
|
|
31
|
+
method: "POST",
|
|
32
|
+
url: "/api/learn/activities/:activityId/comments",
|
|
33
|
+
preHandler: app.requireOrgSession,
|
|
34
|
+
schema: {
|
|
35
|
+
operationId: "postComment",
|
|
36
|
+
tags: ["Learn"],
|
|
37
|
+
summary: "Post a comment or reply on an activity",
|
|
38
|
+
params: DiscussionActivityParam,
|
|
39
|
+
body: PostComment,
|
|
40
|
+
response: {
|
|
41
|
+
200: CommentView,
|
|
42
|
+
403: ErrorBody,
|
|
43
|
+
404: ErrorBody
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
handler: async (req) => {
|
|
47
|
+
const orgId = req.orgId;
|
|
48
|
+
const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);
|
|
49
|
+
if (!orgUser) throw new UnauthorizedError();
|
|
50
|
+
return discussion.postComment(orgId, {
|
|
51
|
+
actor: orgUser,
|
|
52
|
+
activityId: req.params.activityId,
|
|
53
|
+
parentId: req.body.parentId,
|
|
54
|
+
body: req.body.body
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
r.route({
|
|
59
|
+
method: "PATCH",
|
|
60
|
+
url: "/api/learn/comments/:commentId",
|
|
61
|
+
preHandler: app.requireOrgSession,
|
|
62
|
+
schema: {
|
|
63
|
+
operationId: "editComment",
|
|
64
|
+
tags: ["Learn"],
|
|
65
|
+
summary: "Revise your own comment",
|
|
66
|
+
params: CommentIdParam,
|
|
67
|
+
body: EditComment,
|
|
68
|
+
response: {
|
|
69
|
+
200: CommentView,
|
|
70
|
+
403: ErrorBody,
|
|
71
|
+
404: ErrorBody
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
handler: async (req) => {
|
|
75
|
+
const orgId = req.orgId;
|
|
76
|
+
const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);
|
|
77
|
+
if (!orgUser) throw new UnauthorizedError();
|
|
78
|
+
return discussion.edit(orgId, req.params.commentId, orgUser, req.body.body);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
r.route({
|
|
82
|
+
method: "DELETE",
|
|
83
|
+
url: "/api/learn/comments/:commentId",
|
|
84
|
+
preHandler: app.requireOrgSession,
|
|
85
|
+
schema: {
|
|
86
|
+
operationId: "deleteComment",
|
|
87
|
+
tags: ["Learn"],
|
|
88
|
+
summary: "Delete a comment",
|
|
89
|
+
params: CommentIdParam,
|
|
90
|
+
response: {
|
|
91
|
+
200: Comment,
|
|
92
|
+
403: ErrorBody,
|
|
93
|
+
404: ErrorBody
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
handler: async (req) => {
|
|
97
|
+
const orgId = req.orgId;
|
|
98
|
+
const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);
|
|
99
|
+
if (!orgUser) throw new UnauthorizedError();
|
|
100
|
+
return discussion.remove(orgId, req.params.commentId, orgUser);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
r.route({
|
|
104
|
+
method: "PUT",
|
|
105
|
+
url: "/api/learn/comments/:commentId/reaction",
|
|
106
|
+
preHandler: app.requireOrgSession,
|
|
107
|
+
schema: {
|
|
108
|
+
operationId: "setCommentReaction",
|
|
109
|
+
tags: ["Learn"],
|
|
110
|
+
summary: "Set your reaction to a comment, replacing any you already had",
|
|
111
|
+
params: CommentIdParam,
|
|
112
|
+
body: SetCommentReaction,
|
|
113
|
+
response: {
|
|
114
|
+
200: CommentReactions,
|
|
115
|
+
403: ErrorBody,
|
|
116
|
+
404: ErrorBody
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
handler: async (req) => {
|
|
120
|
+
const orgId = req.orgId;
|
|
121
|
+
const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);
|
|
122
|
+
if (!orgUser) throw new UnauthorizedError();
|
|
123
|
+
return discussion.setReaction(orgId, req.params.commentId, orgUser, req.body.emoji);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
r.route({
|
|
127
|
+
method: "DELETE",
|
|
128
|
+
url: "/api/learn/comments/:commentId/reaction",
|
|
129
|
+
preHandler: app.requireOrgSession,
|
|
130
|
+
schema: {
|
|
131
|
+
operationId: "clearCommentReaction",
|
|
132
|
+
tags: ["Learn"],
|
|
133
|
+
summary: "Withdraw your reaction from a comment",
|
|
134
|
+
params: CommentIdParam,
|
|
135
|
+
response: {
|
|
136
|
+
200: CommentReactions,
|
|
137
|
+
403: ErrorBody,
|
|
138
|
+
404: ErrorBody
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
handler: async (req) => {
|
|
142
|
+
const orgId = req.orgId;
|
|
143
|
+
const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);
|
|
144
|
+
if (!orgUser) throw new UnauthorizedError();
|
|
145
|
+
return discussion.setReaction(orgId, req.params.commentId, orgUser, null);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
r.route({
|
|
149
|
+
method: "POST",
|
|
150
|
+
url: "/api/learn/comments/:commentId/reports",
|
|
151
|
+
preHandler: app.requireOrgSession,
|
|
152
|
+
schema: {
|
|
153
|
+
operationId: "reportComment",
|
|
154
|
+
tags: ["Learn"],
|
|
155
|
+
summary: "Flag a comment for moderator attention",
|
|
156
|
+
params: CommentIdParam,
|
|
157
|
+
body: ReportComment,
|
|
158
|
+
response: {
|
|
159
|
+
200: CommentReport,
|
|
160
|
+
403: ErrorBody,
|
|
161
|
+
404: ErrorBody
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
handler: async (req) => {
|
|
165
|
+
const orgId = req.orgId;
|
|
166
|
+
const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);
|
|
167
|
+
if (!orgUser) throw new UnauthorizedError();
|
|
168
|
+
return discussion.reportComment(orgId, req.params.commentId, orgUser, req.body.reason);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
export { learnCommentsRoutes };
|
|
174
|
+
|
|
175
|
+
//# sourceMappingURL=comments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comments.js","names":[],"sources":["../../../../src/http/routes/learn/comments.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n ActivityComments,\n Comment,\n CommentIdParam,\n CommentReactions,\n CommentReport,\n CommentView,\n DiscussionActivityParam,\n EditComment,\n ErrorBody,\n PostComment,\n ReportComment,\n SetCommentReaction,\n} from '../../schemas/index.js';\nimport type { Container } from '../../../app/container.js';\nimport { UnauthorizedError } from '../../plugins/auth.js';\n\nexport async function learnCommentsRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const discussion = container.discussion;\n\n r.route({\n method: 'GET',\n url: '/api/learn/activities/:activityId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listActivityComments',\n tags: ['Learn'],\n summary: \"Read an activity's comments\",\n params: DiscussionActivityParam,\n response: { 200: ActivityComments, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.activityComments(orgId, req.params.activityId, orgUser);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/learn/activities/:activityId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'postComment',\n tags: ['Learn'],\n summary: 'Post a comment or reply on an activity',\n params: DiscussionActivityParam,\n body: PostComment,\n response: { 200: CommentView, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.postComment(orgId, {\n actor: orgUser,\n activityId: req.params.activityId,\n parentId: req.body.parentId,\n body: req.body.body,\n });\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/learn/comments/:commentId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'editComment',\n tags: ['Learn'],\n summary: 'Revise your own comment',\n params: CommentIdParam,\n body: EditComment,\n response: { 200: CommentView, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.edit(orgId, req.params.commentId, orgUser, req.body.body);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/learn/comments/:commentId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteComment',\n tags: ['Learn'],\n summary: 'Delete a comment',\n params: CommentIdParam,\n response: { 200: Comment, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.remove(orgId, req.params.commentId, orgUser);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/learn/comments/:commentId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setCommentReaction',\n tags: ['Learn'],\n summary: 'Set your reaction to a comment, replacing any you already had',\n params: CommentIdParam,\n body: SetCommentReaction,\n response: { 200: CommentReactions, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.setReaction(orgId, req.params.commentId, orgUser, req.body.emoji);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/learn/comments/:commentId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'clearCommentReaction',\n tags: ['Learn'],\n summary: 'Withdraw your reaction from a comment',\n params: CommentIdParam,\n response: { 200: CommentReactions, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.setReaction(orgId, req.params.commentId, orgUser, null);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/learn/comments/:commentId/reports',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reportComment',\n tags: ['Learn'],\n summary: 'Flag a comment for moderator attention',\n params: CommentIdParam,\n body: ReportComment,\n response: { 200: CommentReport, 403: ErrorBody, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgId = req.orgId;\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return discussion.reportComment(orgId, req.params.commentId, orgUser, req.body.reason);\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Container } from "../../../app/container.js";
|
|
2
|
+
import { FastifyInstance } from "fastify";
|
|
3
|
+
//#region src/http/routes/learn/courses.d.ts
|
|
4
|
+
declare function learnCoursesRoutes(app: FastifyInstance, container: Container): Promise<void>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { learnCoursesRoutes };
|
|
7
|
+
//# sourceMappingURL=courses.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { UnauthorizedError } from "../../plugins/auth.js";
|
|
2
|
+
import { ErrorBody } from "../../schemas/shared.js";
|
|
3
|
+
import { Course } from "../../schemas/content.js";
|
|
4
|
+
import { LearnCourseIdParam, LearnCourses, LearnModules } from "../../schemas/learn.js";
|
|
5
|
+
import "../../schemas/index.js";
|
|
6
|
+
import { NotFoundError } from "@headless-lms/core/shared/errors";
|
|
7
|
+
//#region src/http/routes/learn/courses.ts
|
|
8
|
+
async function learnCoursesRoutes(app, container) {
|
|
9
|
+
const r = app.withTypeProvider();
|
|
10
|
+
const learn = container.reporting.learn;
|
|
11
|
+
r.route({
|
|
12
|
+
method: "GET",
|
|
13
|
+
url: "/api/learn/courses",
|
|
14
|
+
preHandler: app.requireOrgSession,
|
|
15
|
+
schema: {
|
|
16
|
+
operationId: "listLearnCourses",
|
|
17
|
+
tags: ["Learn"],
|
|
18
|
+
summary: "List the student's enrolled courses",
|
|
19
|
+
response: { 200: LearnCourses }
|
|
20
|
+
},
|
|
21
|
+
handler: async (req) => {
|
|
22
|
+
const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);
|
|
23
|
+
if (!orgUser) throw new UnauthorizedError();
|
|
24
|
+
return learn.listCourses(req.orgId, orgUser.id);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
r.route({
|
|
28
|
+
method: "GET",
|
|
29
|
+
url: "/api/learn/courses/:courseId",
|
|
30
|
+
preHandler: app.requireOrgSession,
|
|
31
|
+
schema: {
|
|
32
|
+
operationId: "getLearnCourse",
|
|
33
|
+
tags: ["Learn"],
|
|
34
|
+
summary: "Get one enrolled course",
|
|
35
|
+
params: LearnCourseIdParam,
|
|
36
|
+
response: {
|
|
37
|
+
200: Course,
|
|
38
|
+
404: ErrorBody
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
handler: async (req) => {
|
|
42
|
+
const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);
|
|
43
|
+
if (!orgUser) throw new UnauthorizedError();
|
|
44
|
+
const course = await learn.getCourse(req.orgId, orgUser.id, req.params.courseId);
|
|
45
|
+
if (!course) throw new NotFoundError("Course", req.params.courseId);
|
|
46
|
+
return course;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
r.route({
|
|
50
|
+
method: "GET",
|
|
51
|
+
url: "/api/learn/courses/:courseId/modules",
|
|
52
|
+
preHandler: app.requireOrgSession,
|
|
53
|
+
schema: {
|
|
54
|
+
operationId: "listLearnModules",
|
|
55
|
+
tags: ["Learn"],
|
|
56
|
+
summary: "List an enrolled course's module/activity tree",
|
|
57
|
+
params: LearnCourseIdParam,
|
|
58
|
+
response: {
|
|
59
|
+
200: LearnModules,
|
|
60
|
+
404: ErrorBody
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
handler: async (req) => {
|
|
64
|
+
const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);
|
|
65
|
+
if (!orgUser) throw new UnauthorizedError();
|
|
66
|
+
const modules = await learn.listModules(req.orgId, orgUser.id, req.params.courseId);
|
|
67
|
+
if (!modules) throw new NotFoundError("Course", req.params.courseId);
|
|
68
|
+
return modules;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { learnCoursesRoutes };
|
|
74
|
+
|
|
75
|
+
//# sourceMappingURL=courses.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"courses.js","names":[],"sources":["../../../../src/http/routes/learn/courses.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n Course,\n ErrorBody,\n LearnCourseIdParam,\n LearnCourses,\n LearnModules,\n} from '../../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../../app/container.js';\nimport { UnauthorizedError } from '../../plugins/auth.js';\n\nexport async function learnCoursesRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const learn = container.reporting.learn;\n\n r.route({\n method: 'GET',\n url: '/api/learn/courses',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listLearnCourses',\n tags: ['Learn'],\n summary: \"List the student's enrolled courses\",\n response: { 200: LearnCourses },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n return learn.listCourses(req.orgId, orgUser.id);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/learn/courses/:courseId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getLearnCourse',\n tags: ['Learn'],\n summary: 'Get one enrolled course',\n params: LearnCourseIdParam,\n response: { 200: Course, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n const course = await learn.getCourse(req.orgId, orgUser.id, req.params.courseId);\n if (!course) {\n throw new NotFoundError('Course', req.params.courseId);\n }\n return course;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/learn/courses/:courseId/modules',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listLearnModules',\n tags: ['Learn'],\n summary: \"List an enrolled course's module/activity tree\",\n params: LearnCourseIdParam,\n response: { 200: LearnModules, 404: ErrorBody },\n },\n handler: async (req) => {\n const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);\n if (!orgUser) {\n throw new UnauthorizedError();\n }\n const modules = await learn.listModules(req.orgId, orgUser.id, req.params.courseId);\n if (!modules) {\n throw new NotFoundError('Course', req.params.courseId);\n }\n return modules;\n },\n });\n}\n"],"mappings":";;;;;;;AAaA,eAAsB,mBACpB,KACA,WACe;CACf,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,QAAQ,UAAU,UAAU;CAElC,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,OAAO;GACd,SAAS;GACT,UAAU,EAAE,KAAK,aAAa;EAChC;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB;GAE9B,OAAO,MAAM,YAAY,IAAI,OAAO,QAAQ,EAAE;EAChD;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,OAAO;GACd,SAAS;GACT,QAAQ;GACR,UAAU;IAAE,KAAK;IAAQ,KAAK;GAAU;EAC1C;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB;GAE9B,MAAM,SAAS,MAAM,MAAM,UAAU,IAAI,OAAO,QAAQ,IAAI,IAAI,OAAO,QAAQ;GAC/E,IAAI,CAAC,QACH,MAAM,IAAI,cAAc,UAAU,IAAI,OAAO,QAAQ;GAEvD,OAAO;EACT;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,OAAO;GACd,SAAS;GACT,QAAQ;GACR,UAAU;IAAE,KAAK;IAAc,KAAK;GAAU;EAChD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,UAAU,cAAc,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9E,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB;GAE9B,MAAM,UAAU,MAAM,MAAM,YAAY,IAAI,OAAO,QAAQ,IAAI,IAAI,OAAO,QAAQ;GAClF,IAAI,CAAC,SACH,MAAM,IAAI,cAAc,UAAU,IAAI,OAAO,QAAQ;GAEvD,OAAO;EACT;CACF,CAAC;AACH"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Container } from "../../../app/container.js";
|
|
2
|
+
import { FastifyInstance } from "fastify";
|
|
3
|
+
//#region src/http/routes/learn/downloads.d.ts
|
|
4
|
+
declare function learnDownloadsRoutes(app: FastifyInstance, container: Container): Promise<void>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { learnDownloadsRoutes };
|
|
7
|
+
//# sourceMappingURL=downloads.d.ts.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { UnauthorizedError } from "../../plugins/auth.js";
|
|
2
|
+
import { ErrorBody } from "../../schemas/shared.js";
|
|
3
|
+
import { DownloadAssetParams, DownloadIdParam } from "../../schemas/downloads.js";
|
|
4
|
+
import { LearnDownload, LearnDownloads } from "../../schemas/learn.js";
|
|
5
|
+
import "../../schemas/index.js";
|
|
6
|
+
import { NotFoundError } from "@headless-lms/core/shared/errors";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
//#region src/http/routes/learn/downloads.ts
|
|
9
|
+
async function learnDownloadsRoutes(app, container) {
|
|
10
|
+
const r = app.withTypeProvider();
|
|
11
|
+
const learn = container.reporting.learn;
|
|
12
|
+
r.route({
|
|
13
|
+
method: "GET",
|
|
14
|
+
url: "/api/learn/downloads",
|
|
15
|
+
preHandler: app.requireOrgSession,
|
|
16
|
+
schema: {
|
|
17
|
+
operationId: "listLearnDownloads",
|
|
18
|
+
tags: ["Learn"],
|
|
19
|
+
summary: "Downloads the student is actively entitled to",
|
|
20
|
+
response: { 200: LearnDownloads }
|
|
21
|
+
},
|
|
22
|
+
handler: async (req) => {
|
|
23
|
+
const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);
|
|
24
|
+
if (!orgUser) throw new UnauthorizedError();
|
|
25
|
+
return learn.listDownloads(req.orgId, orgUser.id);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
r.route({
|
|
29
|
+
method: "GET",
|
|
30
|
+
url: "/api/learn/downloads/:downloadId",
|
|
31
|
+
preHandler: app.requireOrgSession,
|
|
32
|
+
schema: {
|
|
33
|
+
operationId: "getLearnDownload",
|
|
34
|
+
tags: ["Learn"],
|
|
35
|
+
summary: "One entitled download and its ordered assets",
|
|
36
|
+
params: DownloadIdParam,
|
|
37
|
+
response: {
|
|
38
|
+
200: LearnDownload,
|
|
39
|
+
404: ErrorBody
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
handler: async (req) => {
|
|
43
|
+
const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);
|
|
44
|
+
if (!orgUser) throw new UnauthorizedError();
|
|
45
|
+
const result = await learn.getDownload(req.orgId, orgUser.id, req.params.downloadId);
|
|
46
|
+
if (!result) throw new NotFoundError("Download", req.params.downloadId);
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
r.route({
|
|
51
|
+
method: "GET",
|
|
52
|
+
url: "/api/learn/downloads/:downloadId/assets/:assetId",
|
|
53
|
+
preHandler: app.requireOrgSession,
|
|
54
|
+
schema: {
|
|
55
|
+
operationId: "getLearnDownloadAsset",
|
|
56
|
+
tags: ["Learn"],
|
|
57
|
+
summary: "Redirect to a short-lived signed URL for an entitled asset",
|
|
58
|
+
params: DownloadAssetParams,
|
|
59
|
+
response: {
|
|
60
|
+
302: z.void(),
|
|
61
|
+
404: ErrorBody
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
handler: async (req, reply) => {
|
|
65
|
+
const orgUser = await container.organizations.getOrgUser(req.orgId, req.userId);
|
|
66
|
+
if (!orgUser) throw new UnauthorizedError();
|
|
67
|
+
const signed = await learn.downloadAssetUrl(req.orgId, orgUser.id, req.params.downloadId, req.params.assetId);
|
|
68
|
+
if (!signed) throw new NotFoundError("Download asset", req.params.assetId);
|
|
69
|
+
return reply.header("cache-control", "no-store").header("referrer-policy", "no-referrer").redirect(signed.url, 302);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
export { learnDownloadsRoutes };
|
|
75
|
+
|
|
76
|
+
//# sourceMappingURL=downloads.js.map
|