@learnpack/learnpack 4.0.6 → 4.0.8

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.
@@ -1,330 +1,327 @@
1
- import Console from "../../utils/console"
2
- import * as express from "express"
3
- import * as fs from "fs"
4
- import * as bodyParser from "body-parser"
5
- import socket from "../socket"
6
- import queue from "../../utils/fileQueue"
7
- // import gitpod from '../gitpod'
8
- import { detect, filterFiles } from "../config/exercise"
9
- import { IFile } from "../../models/file"
10
- import { IConfigObj, TEntries } from "../../models/config"
11
- import { IConfigManager } from "../../models/config-manager"
12
- import { IExercise } from "../../models/exercise-obj"
13
- import SessionManager from "../../managers/session"
14
- import TelemetryManager from "../telemetry"
15
- import { eventManager } from "../../utils/osOperations"
16
-
17
- const withHandler =
18
- (func: (req: express.Request, res: express.Response) => void) =>
19
- (req: express.Request, res: express.Response) => {
20
- try {
21
- func(req, res)
22
- } catch (error) {
23
- Console.debug(error)
24
- const _err = {
25
- message: (error as TypeError).message || "There has been an error",
26
- status: (error as any).status || 500,
27
- type: (error as any).type || null,
28
- }
29
- Console.error(_err.message)
30
-
31
- // send rep to the server
32
- res.status(_err.status)
33
- res.json(_err)
34
- }
35
- }
36
-
37
- export default async function (
38
- app: express.Application,
39
- configObject: IConfigObj,
40
- configManager: IConfigManager
41
- ) {
42
- const { config, exercises } = configObject
43
- const session = await SessionManager.get(configManager?.get())
44
-
45
- const dispatcher = queue.dispatcher({
46
- create: true,
47
- path: `${config?.dirPath}/vscode_queue.json`,
48
- })
49
- app.get(
50
- "/config",
51
- withHandler((_: express.Request, res: express.Response) => {
52
- res.json(configObject)
53
- })
54
- )
55
-
56
- // Added this line to parse the json body
57
-
58
- const jsonBodyParser = bodyParser.json()
59
- // Trying to log in from frontend
60
- app.post(
61
- "/login",
62
- jsonBodyParser,
63
- withHandler(async (req: express.Request, res: express.Response) => {
64
- const email = req.body.email
65
- const password = req.body.password
66
-
67
- SessionManager.destroy()
68
- const payload = await SessionManager.loginWeb(email, password)
69
-
70
- res.json(payload)
71
- })
72
- )
73
- app.post(
74
- "/set-rigobot-token",
75
- jsonBodyParser,
76
- withHandler(async (req: express.Request, res: express.Response) => {
77
- const token = req.body.token
78
- // Ensure token is provided in the request body
79
- if (!token) {
80
- return res.status(400).json({ error: "Token is required" })
81
- }
82
-
83
- try {
84
- const tokenSaved = await SessionManager.setRigoToken(token)
85
- // Check if the token was saved successfully
86
- if (tokenSaved) {
87
- res.json({ status: "ok" })
88
- } else {
89
- res.status(500).json({ error: "Failed to save the token" })
90
- }
91
- } catch {
92
- // Handle any unexpected errors during the process
93
- res.status(500).json({ error: "Internal server error" })
94
- }
95
- })
96
- )
97
- app.get(
98
- "/check/rigo/status",
99
- withHandler(async (_: express.Request, res: express.Response) => {
100
- const payload = await SessionManager.getPayload()
101
-
102
- if (payload && payload.rigobot && payload.rigobot.key) {
103
- res.json({ rigoToken: payload.rigobot.key })
104
- } else {
105
- res
106
- .status(400)
107
- .json({ details: `Rigobot token not found, please log in first!` })
108
- }
109
- })
110
- )
111
-
112
- // symbolic link to maintain path compatiblity
113
- const fetchStaticAsset = withHandler((req, res) => {
114
- const filePath = `${config?.dirPath}/assets/${req.params.filePath}`
115
- if (!fs.existsSync(filePath))
116
- throw new Error("File not found: " + filePath)
117
- const content = fs.readFileSync(filePath)
118
- res.write(content)
119
- res.end()
120
- })
121
-
122
- app.get(
123
- `${
124
- config?.dirPath.indexOf("./") === 0 ?
125
- config.dirPath.slice(1) :
126
- config?.dirPath
127
- }/assets/:filePath`,
128
- fetchStaticAsset
129
- )
130
-
131
- app.get("/assets/:filePath", fetchStaticAsset)
132
-
133
- app.get(
134
- "/exercise",
135
- withHandler((_: express.Request, res: express.Response) => {
136
- res.json(exercises)
137
- })
138
- )
139
-
140
- app.get(
141
- "/exercise/:slug/readme",
142
- withHandler(
143
- (
144
- { params: { slug }, query: { lang } }: express.Request,
145
- res: express.Response
146
- ) => {
147
- const exercise: IExercise = configManager.getExercise(slug)
148
-
149
- if (exercise && exercise.getReadme) {
150
- const readme = exercise.getReadme((lang as string) || null)
151
- res.json(readme)
152
- } else {
153
- res.status(400)
154
- }
155
- }
156
- )
157
- )
158
-
159
- app.get(
160
- "/exercise/:slug/report",
161
- withHandler(
162
- ({ params: { slug } }: express.Request, res: express.Response) => {
163
- const exercise = configManager.getExercise(slug)
164
- if (exercise && exercise.getTestReport) {
165
- const report = exercise.getTestReport()
166
- res.json(JSON.stringify(report))
167
- }
168
- }
169
- )
170
- )
171
-
172
- app.get(
173
- "/exercise/:slug",
174
- withHandler((req: express.Request, res: express.Response) => {
175
- // no need to re-start exercise if it's already started
176
- if (
177
- configObject.currentExercise &&
178
- req.params.slug === configObject.currentExercise
179
- ) {
180
- const exercise = configManager.getExercise(req.params.slug)
181
- res.json(exercise)
182
- if (exercise.position) {
183
- TelemetryManager.registerStepEvent(
184
- exercise.position,
185
- "open_step",
186
- {}
187
- )
188
- }
189
-
190
- return
191
- }
192
-
193
- const exercise = configManager.startExercise(req.params.slug)
194
- if (exercise.position) {
195
- TelemetryManager.registerStepEvent(exercise.position, "open_step", {})
196
- }
197
-
198
- if (configObject.config?.editor.agent === "os") {
199
- eventManager.enqueue(dispatcher.events.START_EXERCISE, exercise)
200
- } else {
201
- dispatcher.enqueue(dispatcher.events.START_EXERCISE, req.params.slug)
202
- }
203
-
204
- type TEntry = "python3" | "html" | "node" | "react" | "java";
205
-
206
- const entries = new Set(
207
- Object.keys(config?.entries!).map(
208
- lang => config?.entries[lang as TEntry]
209
- )
210
- )
211
-
212
- // if we are in incremental grading, the entry file can by dinamically detected
213
- // based on the changes the student is making during the exercise
214
- if (config?.grading === "incremental") {
215
- const scanedFiles = fs.readdirSync("./")
216
-
217
- // update the file hierarchy with updates
218
- exercise.files = [
219
- ...exercise.files.filter(f => f.name.includes("test.")),
220
- ...filterFiles(scanedFiles),
221
- ]
222
- Console.debug(`Exercise updated files: `, exercise.files)
223
- }
224
-
225
- const detected = detect(
226
- configObject,
227
- exercise.files
228
- .filter(fileName => entries.has(fileName.name))
229
- .map(f => f.name || f) as string[]
230
- )
231
-
232
- // if a new language for the testing engine is detected, we replace it
233
- // if not we leave it as it was before
234
- if (config?.language && !["", "auto"].includes(config?.language)) {
235
- Console.debug(
236
- `Exercise language ignored, instead imported from configuration ${config?.language}`
237
- )
238
- exercise.language = detected?.language
239
- } else if (
240
- detected?.language &&
241
- (!config?.language || config?.language === "auto")
242
- ) {
243
- Console.debug(
244
- `Switching to ${detected.language} engine in this exercise`
245
- )
246
- exercise.language = detected.language
247
- }
248
-
249
- // WARNING: has to be the FULL PATH to the entry path
250
- // We need to detect entry in both gradings: Incremental and Isolate
251
- exercise.entry = detected?.entry
252
- Console.debug(
253
- `Exercise detected entry: ${detected?.entry} and language ${exercise.language}`
254
- )
255
-
256
- // exercises.graded and exercises.disableGrading deprecated.
257
- if (
258
- !exercise.graded ||
259
- config?.disableGrading ||
260
- config?.disabledActions?.includes("test")
261
- ) {
262
- socket.removeAllowed("test")
263
- } else {
264
- socket.addAllowed("test")
265
- }
266
-
267
- if (!exercise.entry || config?.disabledActions?.includes("build")) {
268
- socket.removeAllowed("build")
269
- } else {
270
- socket.addAllowed("build")
271
- }
272
-
273
- if (
274
- exercise.files.filter(
275
- (f: IFile) =>
276
- !f.name.toLowerCase().includes("readme.") &&
277
- !f.name.toLowerCase().includes("test.")
278
- ).length === 0 ||
279
- config?.disabledActions?.includes("reset")
280
- ) {
281
- socket.removeAllowed("reset")
282
- } else if (!config?.disabledActions?.includes("reset")) {
283
- socket.addAllowed("reset")
284
- }
285
-
286
- socket.log("ready")
287
- res.json(exercise)
288
- })
289
- )
290
-
291
- app.get(
292
- "/exercise/:slug/file/:fileName",
293
- withHandler((req: express.Request, res: express.Response) => {
294
- const exercise = configManager.getExercise(req.params.slug)
295
- if (exercise && exercise.getFile) {
296
- res.write(exercise.getFile(req.params.fileName))
297
- res.end()
298
- }
299
- })
300
- )
301
-
302
- /*
303
- app.post(
304
- "/exercise/:slug/file/:fileName",
305
- withHandler((req: express.Request, res: express.Response) => {
306
- get tokens but also, add allowed action for 'generate'
307
- use the sessionManager to keep compatibility with the cli login command.
308
- })
309
- );
310
- */
311
-
312
- const textBodyParser = bodyParser.text()
313
- app.put(
314
- "/exercise/:slug/file/:fileName",
315
- textBodyParser,
316
- withHandler((req: express.Request, res: express.Response) => {
317
- const exercise = configManager.getExercise(req.params.slug)
318
- if (exercise && exercise.saveFile) {
319
- exercise.saveFile(req.params.fileName, req.body)
320
- res.end()
321
- }
322
- })
323
- )
324
-
325
- if (config?.outputPath) {
326
- app.use("/preview", express.static(config.outputPath))
327
- }
328
-
329
- app.use("/", express.static(`${config?.dirPath}/_app`))
330
- }
1
+ import Console from "../../utils/console"
2
+ import * as express from "express"
3
+ import * as fs from "fs"
4
+ import * as bodyParser from "body-parser"
5
+ import socket from "../socket"
6
+ import queue from "../../utils/fileQueue"
7
+ // import gitpod from '../gitpod'
8
+ import { detect, filterFiles } from "../config/exercise"
9
+ import { IFile } from "../../models/file"
10
+ import { IConfigObj, TEntries } from "../../models/config"
11
+ import { IConfigManager } from "../../models/config-manager"
12
+ import { IExercise } from "../../models/exercise-obj"
13
+ import SessionManager from "../../managers/session"
14
+ import TelemetryManager from "../telemetry"
15
+ import { eventManager } from "../../utils/osOperations"
16
+
17
+ const withHandler =
18
+ (func: (req: express.Request, res: express.Response) => void) =>
19
+ (req: express.Request, res: express.Response) => {
20
+ try {
21
+ func(req, res)
22
+ } catch (error) {
23
+ Console.debug(error)
24
+ const _err = {
25
+ message: (error as TypeError).message || "There has been an error",
26
+ status: (error as any).status || 500,
27
+ type: (error as any).type || null,
28
+ }
29
+ Console.error(_err.message)
30
+
31
+ // send rep to the server
32
+ res.status(_err.status)
33
+ res.json(_err)
34
+ }
35
+ }
36
+
37
+ export default async function (
38
+ app: express.Application,
39
+ configObject: IConfigObj,
40
+ configManager: IConfigManager
41
+ ) {
42
+ const { config, exercises } = configObject
43
+ const session = await SessionManager.get(configManager?.get())
44
+
45
+ const dispatcher = queue.dispatcher({
46
+ create: true,
47
+ path: `${config?.dirPath}/vscode_queue.json`,
48
+ })
49
+ app.get(
50
+ "/config",
51
+ withHandler((_: express.Request, res: express.Response) => {
52
+ const confObject = configManager.get()
53
+ res.json(confObject)
54
+ })
55
+ )
56
+
57
+ // Added this line to parse the json body
58
+
59
+ const jsonBodyParser = bodyParser.json()
60
+ // Trying to log in from frontend
61
+ app.post(
62
+ "/login",
63
+ jsonBodyParser,
64
+ withHandler(async (req: express.Request, res: express.Response) => {
65
+ const email = req.body.email
66
+ const password = req.body.password
67
+
68
+ SessionManager.destroy()
69
+ const payload = await SessionManager.loginWeb(email, password)
70
+
71
+ res.json(payload)
72
+ })
73
+ )
74
+ app.post(
75
+ "/set-rigobot-token",
76
+ jsonBodyParser,
77
+ withHandler(async (req: express.Request, res: express.Response) => {
78
+ const token = req.body.token
79
+ // Ensure token is provided in the request body
80
+ if (!token) {
81
+ return res.status(400).json({ error: "Token is required" })
82
+ }
83
+
84
+ try {
85
+ const tokenSaved = await SessionManager.setRigoToken(token)
86
+ // Check if the token was saved successfully
87
+ if (tokenSaved) {
88
+ res.json({ status: "ok" })
89
+ } else {
90
+ res.status(500).json({ error: "Failed to save the token" })
91
+ }
92
+ } catch {
93
+ // Handle any unexpected errors during the process
94
+ res.status(500).json({ error: "Internal server error" })
95
+ }
96
+ })
97
+ )
98
+ app.get(
99
+ "/check/rigo/status",
100
+ withHandler(async (_: express.Request, res: express.Response) => {
101
+ const payload = await SessionManager.getPayload()
102
+
103
+ if (payload && payload.rigobot && payload.rigobot.key) {
104
+ res.json({ rigoToken: payload.rigobot.key })
105
+ } else {
106
+ res
107
+ .status(400)
108
+ .json({ details: `Rigobot token not found, please log in first!` })
109
+ }
110
+ })
111
+ )
112
+
113
+ // symbolic link to maintain path compatiblity
114
+ const fetchStaticAsset = withHandler((req, res) => {
115
+ const filePath = `${config?.dirPath}/assets/${req.params.filePath}`
116
+ if (!fs.existsSync(filePath))
117
+ throw new Error("File not found: " + filePath)
118
+ const content = fs.readFileSync(filePath)
119
+ res.write(content)
120
+ res.end()
121
+ })
122
+
123
+ app.get(
124
+ `${
125
+ config?.dirPath.indexOf("./") === 0 ?
126
+ config.dirPath.slice(1) :
127
+ config?.dirPath
128
+ }/assets/:filePath`,
129
+ fetchStaticAsset
130
+ )
131
+
132
+ app.get("/assets/:filePath", fetchStaticAsset)
133
+
134
+ app.get(
135
+ "/exercise",
136
+ withHandler((_: express.Request, res: express.Response) => {
137
+ res.json(exercises)
138
+ })
139
+ )
140
+
141
+ app.get(
142
+ "/exercise/:slug/readme",
143
+ withHandler(
144
+ (
145
+ { params: { slug }, query: { lang } }: express.Request,
146
+ res: express.Response
147
+ ) => {
148
+ const exercise: IExercise = configManager.getExercise(slug)
149
+
150
+ if (exercise && exercise.getReadme) {
151
+ const readme = exercise.getReadme((lang as string) || null)
152
+ res.json(readme)
153
+ } else {
154
+ res.status(400)
155
+ }
156
+ }
157
+ )
158
+ )
159
+
160
+ app.get(
161
+ "/exercise/:slug/report",
162
+ withHandler(
163
+ ({ params: { slug } }: express.Request, res: express.Response) => {
164
+ const exercise = configManager.getExercise(slug)
165
+ if (exercise && exercise.getTestReport) {
166
+ const report = exercise.getTestReport()
167
+ res.json(JSON.stringify(report))
168
+ }
169
+ }
170
+ )
171
+ )
172
+
173
+ app.get(
174
+ "/exercise/:slug",
175
+ withHandler((req: express.Request, res: express.Response) => {
176
+ // no need to re-start exercise if it's already started
177
+ if (
178
+ configObject.currentExercise &&
179
+ req.params.slug === configObject.currentExercise
180
+ ) {
181
+ const exercise = configManager.getExercise(req.params.slug)
182
+ res.json(exercise)
183
+ if (exercise.position) {
184
+ TelemetryManager.registerStepEvent(exercise.position, "open_step", {})
185
+ }
186
+
187
+ return
188
+ }
189
+
190
+ const exercise = configManager.startExercise(req.params.slug)
191
+ if (exercise.position) {
192
+ TelemetryManager.registerStepEvent(exercise.position, "open_step", {})
193
+ }
194
+
195
+ if (configObject.config?.editor.agent === "os") {
196
+ eventManager.enqueue(dispatcher.events.START_EXERCISE, exercise)
197
+ } else {
198
+ dispatcher.enqueue(dispatcher.events.START_EXERCISE, req.params.slug)
199
+ }
200
+
201
+ type TEntry = "python3" | "html" | "node" | "react" | "java"
202
+
203
+ const entries = new Set(
204
+ Object.keys(config?.entries!).map(
205
+ lang => config?.entries[lang as TEntry]
206
+ )
207
+ )
208
+
209
+ // if we are in incremental grading, the entry file can by dinamically detected
210
+ // based on the changes the student is making during the exercise
211
+ if (config?.grading === "incremental") {
212
+ const scanedFiles = fs.readdirSync("./")
213
+
214
+ // update the file hierarchy with updates
215
+ exercise.files = [
216
+ ...exercise.files.filter(f => f.name.includes("test.")),
217
+ ...filterFiles(scanedFiles),
218
+ ]
219
+ Console.debug(`Exercise updated files: `, exercise.files)
220
+ }
221
+
222
+ const detected = detect(
223
+ configObject,
224
+ exercise.files
225
+ .filter(fileName => entries.has(fileName.name))
226
+ .map(f => f.name || f) as string[]
227
+ )
228
+
229
+ // if a new language for the testing engine is detected, we replace it
230
+ // if not we leave it as it was before
231
+ if (config?.language && !["", "auto"].includes(config?.language)) {
232
+ Console.debug(
233
+ `Exercise language ignored, instead imported from configuration ${config?.language}`
234
+ )
235
+ exercise.language = detected?.language
236
+ } else if (
237
+ detected?.language &&
238
+ (!config?.language || config?.language === "auto")
239
+ ) {
240
+ Console.debug(
241
+ `Switching to ${detected.language} engine in this exercise`
242
+ )
243
+ exercise.language = detected.language
244
+ }
245
+
246
+ // WARNING: has to be the FULL PATH to the entry path
247
+ // We need to detect entry in both gradings: Incremental and Isolate
248
+ exercise.entry = detected?.entry
249
+ Console.debug(
250
+ `Exercise detected entry: ${detected?.entry} and language ${exercise.language}`
251
+ )
252
+
253
+ // exercises.graded and exercises.disableGrading deprecated.
254
+ if (
255
+ !exercise.graded ||
256
+ config?.disableGrading ||
257
+ config?.disabledActions?.includes("test")
258
+ ) {
259
+ socket.removeAllowed("test")
260
+ } else {
261
+ socket.addAllowed("test")
262
+ }
263
+
264
+ if (!exercise.entry || config?.disabledActions?.includes("build")) {
265
+ socket.removeAllowed("build")
266
+ } else {
267
+ socket.addAllowed("build")
268
+ }
269
+
270
+ if (
271
+ exercise.files.filter(
272
+ (f: IFile) =>
273
+ !f.name.toLowerCase().includes("readme.") &&
274
+ !f.name.toLowerCase().includes("test.")
275
+ ).length === 0 ||
276
+ config?.disabledActions?.includes("reset")
277
+ ) {
278
+ socket.removeAllowed("reset")
279
+ } else if (!config?.disabledActions?.includes("reset")) {
280
+ socket.addAllowed("reset")
281
+ }
282
+
283
+ socket.log("ready")
284
+ res.json(exercise)
285
+ })
286
+ )
287
+
288
+ app.get(
289
+ "/exercise/:slug/file/:fileName",
290
+ withHandler((req: express.Request, res: express.Response) => {
291
+ const exercise = configManager.getExercise(req.params.slug)
292
+ if (exercise && exercise.getFile) {
293
+ res.write(exercise.getFile(req.params.fileName))
294
+ res.end()
295
+ }
296
+ })
297
+ )
298
+
299
+ /*
300
+ app.post(
301
+ "/exercise/:slug/file/:fileName",
302
+ withHandler((req: express.Request, res: express.Response) => {
303
+ get tokens but also, add allowed action for 'generate'
304
+ use the sessionManager to keep compatibility with the cli login command.
305
+ })
306
+ );
307
+ */
308
+
309
+ const textBodyParser = bodyParser.text()
310
+ app.put(
311
+ "/exercise/:slug/file/:fileName",
312
+ textBodyParser,
313
+ withHandler((req: express.Request, res: express.Response) => {
314
+ const exercise = configManager.getExercise(req.params.slug)
315
+ if (exercise && exercise.saveFile) {
316
+ exercise.saveFile(req.params.fileName, req.body)
317
+ res.end()
318
+ }
319
+ })
320
+ )
321
+
322
+ if (config?.outputPath) {
323
+ app.use("/preview", express.static(config.outputPath))
324
+ }
325
+
326
+ app.use("/", express.static(`${config?.dirPath}/_app`))
327
+ }