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 AssessmentModel
=
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)
24 * options: array of varchar --> if present, question type == quiz!
25 * fixed: bool, options in fixed order (default: false)
26 * answer: array of integers (for quiz) or html text (for paper); striped in exam mode
27 * active: boolean, is question in current assessment?
28 * points: points for this question (default 1)
29 * param: parameter (if applicable)
31 * number: student number
32 * inputs: array of {index,answer[array of integers or html text],startTime}
33 * current: index of current question (if relevant: display="one")
35 * discoTime, totalDisco: last disconnect timestamp (if relevant) + total time
36 * discoCount: number of disconnections
37 * password: random string identifying student for exam session TEMPORARY
43 getById: function(aid
, callback
)
45 db
.assessments
.findOne(
51 getByPath: function(cid
, name
, callback
)
53 db
.assessments
.findOne(
62 insert: function(cid
, name
, callback
)
64 db
.assessments
.insert(
82 getByCourse: function(cid
, callback
)
90 // arg: full assessment without _id field
91 replace: function(aid
, assessment
, cb
)
93 // Should be: (but unsupported by mongojs)
94 // db.assessments.replaceOne(
99 // Temporary workaround:
100 db
.assessments
.update(
102 { $set: assessment
},
107 getQuestions: function(aid
, callback
)
109 db
.assessments
.findOne(
116 callback(err
, !!res
? res
.questions : null);
121 getQuestion: function(aid
, index
, callback
)
123 db
.assessments
.findOne(
131 return callback(err
, res
);
132 const qIdx
= res
.questions
.findIndex( item
=> { return item
.index
== index
; });
134 return callback({errmsg: "Question not found"}, null);
135 callback(null, res
.questions
[qIdx
]);
140 getPaperByNumber: function(aid
, number
, callback
)
142 db
.assessments
.findOne(
145 "papers.number": number
,
149 return callback(err
,a
);
150 for (let p
of a
.papers
)
152 if (p
.number
== number
)
153 return callback(null,p
); //reached for sure
159 // NOTE: no callbacks for 2 next functions, failures are not so important
160 // (because monitored: teachers can see what's going on)
162 addDisco: function(aid
, number
, deltaTime
)
164 db
.assessments
.update(
167 "papers.number": number
,
170 "papers.$.discoCount": 1,
171 "papers.$.totalDisco": deltaTime
,
173 { $set: { "papers.$.discoTime": null } }
177 setDiscoTime: function(aid
, number
)
179 db
.assessments
.update(
182 "papers.number": number
,
184 { $set: { "papers.$.discoTime": Date
.now() } }
188 getDiscoTime: function(aid
, number
, cb
)
190 db
.assessments
.findOne(
194 return cb(err
, null);
195 const idx
= a
.papers
.findIndex( item
=> { return item
.number
== number
; });
196 cb(null, a
.papers
[idx
].discoTime
);
201 hasInput: function(aid
, number
, password
, idx
, cb
)
203 db
.assessments
.findOne(
206 "papers.number": number
,
207 "papers.password": password
,
212 let papIdx
= a
.papers
.findIndex( item
=> { return item
.number
== number
; });
213 for (let i
of a
.papers
[papIdx
].inputs
)
216 return cb(null,true);
223 // https://stackoverflow.com/questions/27874469/mongodb-push-in-nested-array
224 setInput: function(aid
, number
, password
, input
, callback
) //input: index + arrayOfInt (or txt)
226 db
.assessments
.update(
229 "papers.number": number
,
230 "papers.password": password
,
232 { $push: { "papers.$.inputs": input
} },
237 endAssessment: function(aid
, number
, password
, callback
)
239 db
.assessments
.update(
242 "papers.number": number
,
243 "papers.password": password
,
246 "papers.$.endTime": Date
.now(),
247 "papers.$.password": "",
253 remove: function(aid
, cb
)
255 db
.assessments
.remove(
261 removeGroup: function(cid
, cb
)
263 db
.assessments
.remove(
269 /////////////////////
270 // ADVANCED FUNCTIONS
272 getByRefs: function(initials
, code
, name
, cb
)
274 UserModel
.getByInitials(initials
, (err
,user
) => {
276 return cb(err
|| {errmsg: "User not found"});
277 CourseModel
.getByPath(user
._id
, code
, (err2
,course
) => {
278 if (!!err2
|| !course
)
279 return cb(err2
|| {errmsg: "Course not found"});
280 AssessmentModel
.getByPath(course
._id
, name
, (err3
,assessment
) => {
281 if (!!err3
|| !assessment
)
282 return cb(err3
|| {errmsg: "Assessment not found"});
289 checkPassword: function(aid
, number
, password
, cb
)
291 AssessmentModel
.getById(aid
, (err
,assessment
) => {
292 if (!!err
|| !assessment
)
293 return cb(err
, assessment
);
294 const paperIdx
= assessment
.papers
.findIndex( item
=> { return item
.number
== number
; });
296 return cb({errmsg: "Paper not found"}, false);
297 cb(null, assessment
.papers
[paperIdx
].password
== password
);
301 add: function(uid
, cid
, name
, cb
)
303 // 1) Check that course is owned by user of ID uid
304 CourseModel
.getById(cid
, (err
,course
) => {
305 if (!!err
|| !course
)
306 return cb({errmsg: "Course retrieval failure"});
307 if (!course
.uid
.equals(uid
))
308 return cb({errmsg:"Not your course"},undefined);
309 // 2) Insert new blank assessment
310 AssessmentModel
.insert(cid
, name
, cb
);
314 update: function(uid
, assessment
, cb
)
316 const aid
= ObjectId(assessment
._id
);
317 // 1) Check that assessment is owned by user of ID uid
318 AssessmentModel
.getById(aid
, (err
,assessmentOld
) => {
319 if (!!err
|| !assessmentOld
)
320 return cb({errmsg: "Assessment retrieval failure"});
321 CourseModel
.getById(ObjectId(assessmentOld
.cid
), (err2
,course
) => {
322 if (!!err2
|| !course
)
323 return cb({errmsg: "Course retrieval failure"});
324 if (!course
.uid
.equals(uid
))
325 return cb({errmsg:"Not your course"},undefined);
326 // 2) Replace assessment
327 delete assessment
["_id"];
328 assessment
.cid
= ObjectId(assessment
.cid
);
329 AssessmentModel
.replace(aid
, assessment
, cb
);
334 // Set password in responses collection
335 startSession: function(aid
, number
, password
, cb
)
337 AssessmentModel
.getPaperByNumber(aid
, number
, (err
,paper
) => {
340 if (!paper
&& !!password
)
341 return cb({errmsg: "Cannot start a new exam before finishing current"},null);
345 return cb({errmsg: "Missing password"});
346 if (paper
.password
!= password
)
347 return cb({errmsg: "Wrong password"});
349 AssessmentModel
.getQuestions(aid
, (err2
,questions
) => {
351 return cb(err2
,null);
353 return cb(null,{paper:paper
});
354 const pwd
= TokenGen
.generate(12); //arbitrary number, 12 seems enough...
355 db
.assessments
.update(
359 startTime: Date
.now(),
364 inputs: [ ], //TODO: this is stage 1, stack indexed answers.
365 // then build JSON tree for easier access / correct
367 (err3
,ret
) => { cb(err3
,{password:password
}); }
373 newAnswer: function(aid
, number
, password
, input
, cb
)
375 // Check that student hasn't already answered
376 AssessmentModel
.hasInput(aid
, number
, password
, input
.index
, (err
,ret
) => {
380 return cb({errmsg:"Question already answered"},null);
381 AssessmentModel
.setInput(aid
, number
, password
, input
, (err2
,ret2
) => {
383 return cb(err2
,ret2
);
384 return cb(null,ret2
);
389 // NOTE: no callbacks for next function, failures are not so important
390 // (because monitored: teachers can see what's going on)
391 newConnection: function(aid
, number
)
393 //increment discoCount, reset discoTime to NULL, update totalDisco
394 AssessmentModel
.getDiscoTime(aid
, number
, (err
,discoTime
) => {
396 AssessmentModel
.addDisco(aid
, number
, Date
.now() - discoTime
);
401 module
.exports
= AssessmentModel
;