@learnpack/learnpack 2.0.2 → 2.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,255 +1,255 @@
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
-
14
- const withHandler =
15
- (func: (req: express.Request, res: express.Response) => void) =>
16
- (req: express.Request, res: express.Response) => {
17
- try {
18
- func(req, res);
19
- } catch (error) {
20
- Console.debug(error);
21
- const _err = {
22
- message: (error as TypeError).message || "There has been an error",
23
- status: (error as any).status || 500,
24
- type: (error as any).type || null,
25
- };
26
- Console.error(_err.message);
27
-
28
- // send rep to the server
29
- res.status(_err.status);
30
- res.json(_err);
31
- }
32
- };
33
-
34
- export default async function (
35
- app: express.Application,
36
- configObject: IConfigObj,
37
- configManager: IConfigManager
38
- ) {
39
- const { config, exercises } = configObject;
40
-
41
- const dispatcher = queue.dispatcher({
42
- create: true,
43
- path: `${config?.dirPath}/vscode_queue.json`,
44
- });
45
- app.get(
46
- "/config",
47
- withHandler((_: express.Request, res: express.Response) => {
48
- res.json(configObject);
49
- })
50
- );
51
-
52
- /**
53
- * TODO: replicate a socket action, the request payload must be passed to the socket as well
54
-
55
- const jsonBodyParser = bodyParser.json()
56
-
57
- type ISocketActions = "addAllowed" | "removeAllowed" | "start" | "on" | "clean" | "ask" | "reload" | "openWindow" | "log" | "emit" | "ready" | "error" | "fatal" | "success" | "onTestingFinished";
58
-
59
- app.post('/socket/:actionName', jsonBodyParser, withHandler((req, res) => {
60
- if (socket[req.params.actionName as ISocketActions] instanceof Function) {
61
- socket[req.params.actionName as ISocketActions](req.body ? req.body.data : null)
62
- res.json({ "details": "Socket call executed sucessfully" })
63
- } else res.status(400).json({ "details": `Socket action ${req.params.actionName} not found` })
64
- }))
65
- */
66
-
67
- // symbolic link to maintain path compatiblity
68
- const fetchStaticAsset = withHandler((req, res) => {
69
- const filePath = `${config?.dirPath}/assets/${req.params.filePath}`;
70
- if (!fs.existsSync(filePath))
71
- throw new Error("File not found: " + filePath);
72
- const content = fs.readFileSync(filePath);
73
- res.write(content);
74
- res.end();
75
- });
76
-
77
- app.get(
78
- `${
79
- config?.dirPath.indexOf("./") === 0 ?
80
- config.dirPath.slice(1) :
81
- config?.dirPath
82
- }/assets/:filePath`,
83
- fetchStaticAsset
84
- );
85
-
86
- app.get("/assets/:filePath", fetchStaticAsset);
87
-
88
- app.get(
89
- "/exercise",
90
- withHandler((_: express.Request, res: express.Response) => {
91
- res.json(exercises);
92
- })
93
- );
94
-
95
- app.get(
96
- "/exercise/:slug/readme",
97
- withHandler(
98
- (
99
- { params: { slug }, query: { lang } }: express.Request,
100
- res: express.Response
101
- ) => {
102
- const excercise: IExercise = configManager.getExercise(slug);
103
-
104
- if (excercise) {
105
- const readme = excercise.getReadme((lang as string) || null);
106
- res.json(readme);
107
- } else {
108
- res.status(400);
109
- }
110
- }
111
- )
112
- );
113
-
114
- app.get(
115
- "/exercise/:slug/report",
116
- withHandler(
117
- ({ params: { slug } }: express.Request, res: express.Response) => {
118
- const report = configManager.getExercise(slug).getTestReport();
119
- res.json(JSON.stringify(report));
120
- }
121
- )
122
- );
123
-
124
- app.get(
125
- "/exercise/:slug",
126
- withHandler((req: express.Request, res: express.Response) => {
127
- // no need to re-start exercise if it's already started
128
- if (
129
- configObject.currentExercise &&
130
- req.params.slug === configObject.currentExercise
131
- ) {
132
- const exercise = configManager.getExercise(req.params.slug);
133
- res.json(exercise);
134
- return;
135
- }
136
-
137
- const exercise = configManager.startExercise(req.params.slug);
138
- dispatcher.enqueue(dispatcher.events.START_EXERCISE, req.params.slug);
139
-
140
- type TEntry = "python3" | "html" | "node" | "react" | "java";
141
-
142
- const entries = new Set(
143
- Object.keys(config?.entries!).map(
144
- lang => config?.entries[lang as TEntry]
145
- )
146
- );
147
-
148
- // if we are in incremental grading, the entry file can by dinamically detected
149
- // based on the changes the student is making during the exercise
150
- if (config?.grading === "incremental") {
151
- const scanedFiles = fs.readdirSync("./");
152
-
153
- // update the file hierarchy with updates
154
- exercise.files = [
155
- ...exercise.files.filter(f => f.name.includes("test.")),
156
- ...filterFiles(scanedFiles),
157
- ];
158
- Console.debug(`Exercise updated files: `, exercise.files);
159
- }
160
-
161
- const detected = detect(
162
- configObject,
163
- exercise.files
164
- .filter(fileName => entries.has(fileName.name))
165
- .map(f => f.name || f) as string[]
166
- );
167
-
168
- // if a new language for the testing engine is detected, we replace it
169
- // if not we leave it as it was before
170
- if (config?.language && !["", "auto"].includes(config?.language)) {
171
- Console.debug(
172
- `Exercise language ignored, instead imported from configuration ${config?.language}`
173
- );
174
- exercise.language = detected?.language;
175
- } else if (
176
- detected?.language &&
177
- (!config?.language || config?.language === "auto")
178
- ) {
179
- Console.debug(
180
- `Switching to ${detected.language} engine in this exercise`
181
- );
182
- exercise.language = detected.language;
183
- }
184
-
185
- // WARNING: has to be the FULL PATH to the entry path
186
- // We need to detect entry in both gradings: Incremental and Isolate
187
- exercise.entry = detected?.entry;
188
- Console.debug(
189
- `Exercise detected entry: ${detected?.entry} and language ${exercise.language}`
190
- );
191
-
192
- if (
193
- !exercise.graded ||
194
- config?.disableGrading ||
195
- config?.disabledActions?.includes("test")
196
- ) {
197
- socket.removeAllowed("test");
198
- } else {
199
- socket.addAllowed("test");
200
- }
201
-
202
- if (!exercise.entry || config?.disabledActions?.includes("build")) {
203
- socket.removeAllowed("build");
204
- } else {
205
- socket.addAllowed("build");
206
- }
207
-
208
- if (
209
- exercise.files.filter(
210
- (f: IFile) =>
211
- !f.name.toLowerCase().includes("readme.") &&
212
- !f.name.toLowerCase().includes("test.")
213
- ).length === 0 ||
214
- config?.disabledActions?.includes("reset")
215
- ) {
216
- socket.removeAllowed("reset");
217
- } else if (!config?.disabledActions?.includes("reset")) {
218
- socket.addAllowed("reset");
219
- }
220
-
221
- socket.log("ready");
222
-
223
- res.json(exercise);
224
- })
225
- );
226
-
227
- app.get(
228
- "/exercise/:slug/file/:fileName",
229
- withHandler((req: express.Request, res: express.Response) => {
230
- res.write(
231
- configManager.getExercise(req.params.slug).getFile(req.params.fileName)
232
- );
233
- res.end();
234
- })
235
- );
236
-
237
- const textBodyParser = bodyParser.text();
238
- app.put(
239
- "/exercise/:slug/file/:fileName",
240
- textBodyParser,
241
- withHandler((req: express.Request, res: express.Response) => {
242
- // const result =
243
- configManager
244
- .getExercise(req.params.slug)
245
- .saveFile(req.params.fileName, req.body);
246
- res.end();
247
- })
248
- );
249
-
250
- if (config?.outputPath) {
251
- app.use("/preview", express.static(config.outputPath));
252
- }
253
-
254
- app.use("/", express.static(`${config?.dirPath}/_app`));
255
- }
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
+
14
+ const withHandler =
15
+ (func: (req: express.Request, res: express.Response) => void) =>
16
+ (req: express.Request, res: express.Response) => {
17
+ try {
18
+ func(req, res);
19
+ } catch (error) {
20
+ Console.debug(error);
21
+ const _err = {
22
+ message: (error as TypeError).message || "There has been an error",
23
+ status: (error as any).status || 500,
24
+ type: (error as any).type || null,
25
+ };
26
+ Console.error(_err.message);
27
+
28
+ // send rep to the server
29
+ res.status(_err.status);
30
+ res.json(_err);
31
+ }
32
+ };
33
+
34
+ export default async function (
35
+ app: express.Application,
36
+ configObject: IConfigObj,
37
+ configManager: IConfigManager
38
+ ) {
39
+ const { config, exercises } = configObject;
40
+
41
+ const dispatcher = queue.dispatcher({
42
+ create: true,
43
+ path: `${config?.dirPath}/vscode_queue.json`,
44
+ });
45
+ app.get(
46
+ "/config",
47
+ withHandler((_: express.Request, res: express.Response) => {
48
+ res.json(configObject);
49
+ })
50
+ );
51
+
52
+ /**
53
+ * TODO: replicate a socket action, the request payload must be passed to the socket as well
54
+
55
+ const jsonBodyParser = bodyParser.json()
56
+
57
+ type ISocketActions = "addAllowed" | "removeAllowed" | "start" | "on" | "clean" | "ask" | "reload" | "openWindow" | "log" | "emit" | "ready" | "error" | "fatal" | "success" | "onTestingFinished";
58
+
59
+ app.post('/socket/:actionName', jsonBodyParser, withHandler((req, res) => {
60
+ if (socket[req.params.actionName as ISocketActions] instanceof Function) {
61
+ socket[req.params.actionName as ISocketActions](req.body ? req.body.data : null)
62
+ res.json({ "details": "Socket call executed sucessfully" })
63
+ } else res.status(400).json({ "details": `Socket action ${req.params.actionName} not found` })
64
+ }))
65
+ */
66
+
67
+ // symbolic link to maintain path compatiblity
68
+ const fetchStaticAsset = withHandler((req, res) => {
69
+ const filePath = `${config?.dirPath}/assets/${req.params.filePath}`;
70
+ if (!fs.existsSync(filePath))
71
+ throw new Error("File not found: " + filePath);
72
+ const content = fs.readFileSync(filePath);
73
+ res.write(content);
74
+ res.end();
75
+ });
76
+
77
+ app.get(
78
+ `${
79
+ config?.dirPath.indexOf("./") === 0 ?
80
+ config.dirPath.slice(1) :
81
+ config?.dirPath
82
+ }/assets/:filePath`,
83
+ fetchStaticAsset
84
+ );
85
+
86
+ app.get("/assets/:filePath", fetchStaticAsset);
87
+
88
+ app.get(
89
+ "/exercise",
90
+ withHandler((_: express.Request, res: express.Response) => {
91
+ res.json(exercises);
92
+ })
93
+ );
94
+
95
+ app.get(
96
+ "/exercise/:slug/readme",
97
+ withHandler(
98
+ (
99
+ { params: { slug }, query: { lang } }: express.Request,
100
+ res: express.Response
101
+ ) => {
102
+ const excercise: IExercise = configManager.getExercise(slug);
103
+
104
+ if (excercise) {
105
+ const readme = excercise.getReadme((lang as string) || null);
106
+ res.json(readme);
107
+ } else {
108
+ res.status(400);
109
+ }
110
+ }
111
+ )
112
+ );
113
+
114
+ app.get(
115
+ "/exercise/:slug/report",
116
+ withHandler(
117
+ ({ params: { slug } }: express.Request, res: express.Response) => {
118
+ const report = configManager.getExercise(slug).getTestReport();
119
+ res.json(JSON.stringify(report));
120
+ }
121
+ )
122
+ );
123
+
124
+ app.get(
125
+ "/exercise/:slug",
126
+ withHandler((req: express.Request, res: express.Response) => {
127
+ // no need to re-start exercise if it's already started
128
+ if (
129
+ configObject.currentExercise &&
130
+ req.params.slug === configObject.currentExercise
131
+ ) {
132
+ const exercise = configManager.getExercise(req.params.slug);
133
+ res.json(exercise);
134
+ return;
135
+ }
136
+
137
+ const exercise = configManager.startExercise(req.params.slug);
138
+ dispatcher.enqueue(dispatcher.events.START_EXERCISE, req.params.slug);
139
+
140
+ type TEntry = "python3" | "html" | "node" | "react" | "java";
141
+
142
+ const entries = new Set(
143
+ Object.keys(config?.entries!).map(
144
+ lang => config?.entries[lang as TEntry]
145
+ )
146
+ );
147
+
148
+ // if we are in incremental grading, the entry file can by dinamically detected
149
+ // based on the changes the student is making during the exercise
150
+ if (config?.grading === "incremental") {
151
+ const scanedFiles = fs.readdirSync("./");
152
+
153
+ // update the file hierarchy with updates
154
+ exercise.files = [
155
+ ...exercise.files.filter(f => f.name.includes("test.")),
156
+ ...filterFiles(scanedFiles),
157
+ ];
158
+ Console.debug(`Exercise updated files: `, exercise.files);
159
+ }
160
+
161
+ const detected = detect(
162
+ configObject,
163
+ exercise.files
164
+ .filter(fileName => entries.has(fileName.name))
165
+ .map(f => f.name || f) as string[]
166
+ );
167
+
168
+ // if a new language for the testing engine is detected, we replace it
169
+ // if not we leave it as it was before
170
+ if (config?.language && !["", "auto"].includes(config?.language)) {
171
+ Console.debug(
172
+ `Exercise language ignored, instead imported from configuration ${config?.language}`
173
+ );
174
+ exercise.language = detected?.language;
175
+ } else if (
176
+ detected?.language &&
177
+ (!config?.language || config?.language === "auto")
178
+ ) {
179
+ Console.debug(
180
+ `Switching to ${detected.language} engine in this exercise`
181
+ );
182
+ exercise.language = detected.language;
183
+ }
184
+
185
+ // WARNING: has to be the FULL PATH to the entry path
186
+ // We need to detect entry in both gradings: Incremental and Isolate
187
+ exercise.entry = detected?.entry;
188
+ Console.debug(
189
+ `Exercise detected entry: ${detected?.entry} and language ${exercise.language}`
190
+ );
191
+
192
+ if (
193
+ !exercise.graded ||
194
+ config?.disableGrading ||
195
+ config?.disabledActions?.includes("test")
196
+ ) {
197
+ socket.removeAllowed("test");
198
+ } else {
199
+ socket.addAllowed("test");
200
+ }
201
+
202
+ if (!exercise.entry || config?.disabledActions?.includes("build")) {
203
+ socket.removeAllowed("build");
204
+ } else {
205
+ socket.addAllowed("build");
206
+ }
207
+
208
+ if (
209
+ exercise.files.filter(
210
+ (f: IFile) =>
211
+ !f.name.toLowerCase().includes("readme.") &&
212
+ !f.name.toLowerCase().includes("test.")
213
+ ).length === 0 ||
214
+ config?.disabledActions?.includes("reset")
215
+ ) {
216
+ socket.removeAllowed("reset");
217
+ } else if (!config?.disabledActions?.includes("reset")) {
218
+ socket.addAllowed("reset");
219
+ }
220
+
221
+ socket.log("ready");
222
+
223
+ res.json(exercise);
224
+ })
225
+ );
226
+
227
+ app.get(
228
+ "/exercise/:slug/file/:fileName",
229
+ withHandler((req: express.Request, res: express.Response) => {
230
+ res.write(
231
+ configManager.getExercise(req.params.slug).getFile(req.params.fileName)
232
+ );
233
+ res.end();
234
+ })
235
+ );
236
+
237
+ const textBodyParser = bodyParser.text();
238
+ app.put(
239
+ "/exercise/:slug/file/:fileName",
240
+ textBodyParser,
241
+ withHandler((req: express.Request, res: express.Response) => {
242
+ // const result =
243
+ configManager
244
+ .getExercise(req.params.slug)
245
+ .saveFile(req.params.fileName, req.body);
246
+ res.end();
247
+ })
248
+ );
249
+
250
+ if (config?.outputPath) {
251
+ app.use("/preview", express.static(config.outputPath));
252
+ }
253
+
254
+ app.use("/", express.static(`${config?.dirPath}/_app`));
255
+ }