1 const CourseModel
= require("../models/course");
2 const UserModel
= require("../models/user");
3 const ObjectId
= require("bson-objectid");
4 const TokenGen
= require("../utils/tokenGenerator");
5 const db
= require("../utils/database");
7 const EvaluationModel
=
15 * mode: secure | watch | exam | open (decreasing security)
16 * fixed: bool (questions in fixed order; default: false)
17 * display: "one" or "all" (generally "all" for open questions, but...)
18 * time: 0, global (one vaue) or per question (array of integers)
20 * coefficient: number, default 1
22 * index: for paper test, like 2.1.a (?!); and quiz: 0, 1, 2, 3...
23 * wording: varchar (HTML) with potential placeholders for params
24 * options: array of varchar --> if present, question type == quiz!
25 * fixed: bool, options in fixed order (default: false)
26 * points: points for this question (default 1)
29 * array of integers (for quiz) or
30 * html text (for paper) or
31 * function (as string, for parameterized questions)
33 * number: student number
34 * inputs: array of {index,answer[array of integers or html text],startTime}
35 * current: index of current question (if relevant: display="one")
37 * discoTime, totalDisco: last disconnect timestamp (if relevant) + total time
38 * discoCount: number of disconnections
39 * password: random string identifying student for exam session TEMPORARY
45 getById: function(eid
, callback
)
47 db
.evaluations
.findOne(
53 getByPath: function(cid
, name
, callback
)
55 db
.evaluations
.findOne(
64 insert: function(cid
, name
, callback
)
66 db
.evaluations
.insert(
85 getByCourse: function(cid
, callback
)
93 // arg: full evaluation without _id field
94 replace: function(eid
, evaluation
, cb
)
96 // Should be: (but unsupported by mongojs)
97 // db.evaluations.replaceOne(
102 // Temporary workaround:
103 db
.evaluations
.update(
105 { $set: evaluation
},
110 getQuestions: function(eid
, callback
)
112 db
.evaluations
.findOne(
119 callback(err
, !!res
? res
.questions : null);
124 getQuestion: function(eid
, index
, callback
)
126 db
.evaluations
.findOne(
134 return callback(err
, res
);
135 const qIdx
= res
.questions
.findIndex( item
=> { return item
.index
== index
; });
137 return callback({errmsg: "Question not found"}, null);
138 callback(null, res
.questions
[qIdx
]);
143 getPaperByNumber: function(eid
, number
, callback
)
145 db
.evaluations
.findOne(
148 "papers.number": number
,
152 return callback(err
,a
);
153 for (let p
of a
.papers
)
155 if (p
.number
== number
)
156 return callback(null,p
); //reached for sure
162 // NOTE: no callbacks for 2 next functions, failures are not so important
163 // (because monitored: teachers can see what's going on)
165 addDisco: function(eid
, number
, deltaTime
)
167 db
.evaluations
.update(
170 "papers.number": number
,
173 "papers.$.discoCount": 1,
174 "papers.$.totalDisco": deltaTime
,
176 { $set: { "papers.$.discoTime": null } }
180 setDiscoTime: function(eid
, number
)
182 db
.evaluations
.update(
185 "papers.number": number
,
187 { $set: { "papers.$.discoTime": Date
.now() } }
191 getDiscoTime: function(eid
, number
, cb
)
193 db
.evaluations
.findOne(
197 return cb(err
, null);
198 const idx
= a
.papers
.findIndex( item
=> { return item
.number
== number
; });
199 cb(null, a
.papers
[idx
].discoTime
);
204 hasInput: function(eid
, number
, password
, idx
, cb
)
206 db
.evaluations
.findOne(
209 "papers.number": number
,
210 "papers.password": password
,
215 let papIdx
= a
.papers
.findIndex( item
=> { return item
.number
== number
; });
216 for (let i
of a
.papers
[papIdx
].inputs
)
219 return cb(null,true);
226 // https://stackoverflow.com/questions/27874469/mongodb-push-in-nested-array
227 setInput: function(eid
, number
, password
, input
, callback
) //input: index + arrayOfInt (or txt)
229 db
.evaluations
.update(
232 "papers.number": number
,
233 "papers.password": password
,
235 { $push: { "papers.$.inputs": input
} },
240 endEvaluation: function(eid
, number
, password
, callback
)
242 db
.evaluations
.update(
245 "papers.number": number
,
246 "papers.password": password
,
249 "papers.$.password": "",
255 remove: function(eid
, cb
)
257 db
.evaluations
.remove(
263 /////////////////////
264 // ADVANCED FUNCTIONS
266 getByRefs: function(initials
, code
, name
, cb
)
268 UserModel
.getByInitials(initials
, (err
,user
) => {
270 return cb(err
|| {errmsg: "User not found"});
271 CourseModel
.getByPath(user
._id
, code
, (err2
,course
) => {
272 if (!!err2
|| !course
)
273 return cb(err2
|| {errmsg: "Course not found"});
274 EvaluationModel
.getByPath(course
._id
, name
, (err3
,evaluation
) => {
275 if (!!err3
|| !evaluation
)
276 return cb(err3
|| {errmsg: "Evaluation not found"});
283 checkPassword: function(eid
, number
, password
, cb
)
285 EvaluationModel
.getById(eid
, (err
,evaluation
) => {
286 if (!!err
|| !evaluation
)
287 return cb(err
, evaluation
);
288 const paperIdx
= evaluation
.papers
.findIndex( item
=> { return item
.number
== number
; });
290 return cb({errmsg: "Paper not found"}, false);
291 cb(null, evaluation
.papers
[paperIdx
].password
== password
);
295 add: function(uid
, cid
, name
, cb
)
297 // 1) Check that course is owned by user of ID uid
298 CourseModel
.getById(cid
, (err
,course
) => {
299 if (!!err
|| !course
)
300 return cb({errmsg: "Course retrieval failure"});
301 if (!course
.uid
.equals(uid
))
302 return cb({errmsg:"Not your course"},undefined);
303 // 2) Insert new blank evaluation
304 EvaluationModel
.insert(cid
, name
, cb
);
308 update: function(uid
, evaluation
, cb
)
310 const eid
= ObjectId(evaluation
._id
);
311 // 1) Check that evaluation is owned by user of ID uid
312 EvaluationModel
.getById(eid
, (err
,evaluationOld
) => {
313 if (!!err
|| !evaluationOld
)
314 return cb({errmsg: "Evaluation retrieval failure"});
315 CourseModel
.getById(ObjectId(evaluationOld
.cid
), (err2
,course
) => {
316 if (!!err2
|| !course
)
317 return cb({errmsg: "Course retrieval failure"});
318 if (!course
.uid
.equals(uid
))
319 return cb({errmsg:"Not your course"},undefined);
320 // 2) Replace evaluation
321 delete evaluation
["_id"];
322 evaluation
.cid
= ObjectId(evaluation
.cid
);
323 EvaluationModel
.replace(eid
, evaluation
, cb
);
328 // Set password in responses collection
329 startSession: function(eid
, number
, password
, cb
)
331 EvaluationModel
.getPaperByNumber(eid
, number
, (err
,paper
) => {
334 if (!paper
&& !!password
)
335 return cb({errmsg: "Cannot start a new exam before finishing current"},null);
339 return cb({errmsg: "Missing password"});
340 if (paper
.password
!= password
)
341 return cb({errmsg: "Wrong password"});
343 EvaluationModel
.getQuestions(eid
, (err2
,questions
) => {
345 return cb(err2
,null);
347 return cb(null,{paper:paper
});
348 const pwd
= TokenGen
.generate(12); //arbitrary number, 12 seems enough...
349 db
.evaluations
.update(
353 startTime: Date
.now(),
357 inputs: [ ], //TODO: this is stage 1, stack indexed answers.
358 // then build JSON tree for easier access / correct
360 (err3
,ret
) => { cb(err3
,{password:password
}); }
366 newAnswer: function(eid
, number
, password
, input
, cb
)
368 // Check that student hasn't already answered
369 EvaluationModel
.hasInput(eid
, number
, password
, input
.index
, (err
,ret
) => {
373 return cb({errmsg:"Question already answered"},null);
374 EvaluationModel
.setInput(eid
, number
, password
, input
, (err2
,ret2
) => {
376 return cb(err2
,ret2
);
377 return cb(null,ret2
);
382 // NOTE: no callbacks for next function, failures are not so important
383 // (because monitored: teachers can see what's going on)
384 newConnection: function(eid
, number
)
386 //increment discoCount, reset discoTime to NULL, update totalDisco
387 EvaluationModel
.getDiscoTime(eid
, number
, (err
,discoTime
) => {
389 EvaluationModel
.addDisco(eid
, number
, Date
.now() - discoTime
);
394 module
.exports
= EvaluationModel
;