X-Git-Url: https://git.auder.net/?p=qomet.git;a=blobdiff_plain;f=models%2Fassessment.js;fp=models%2Fassessment.js;h=c7cb0fd7d6fce92d5b84794bbc6c9091617fefe9;hp=d0e0d0b520cf49967c978a2e51b1852700b4cfd9;hb=43828378be054cf3604b753e8d9ab24af911188f;hpb=7a7dc732599b358b25b770cfc27036f4b403d1b4 diff --git a/models/assessment.js b/models/assessment.js index d0e0d0b..c7cb0fd 100644 --- a/models/assessment.js +++ b/models/assessment.js @@ -1,20 +1,283 @@ -const AssessmentEntity = require("../entities/assessment"); -const CourseEntity = require("../entities/course"); +const CourseModel = require("../models/course"); +const UserModel = require("../models/user"); const ObjectId = require("bson-objectid"); -const UserEntity = require("../entities/user"); const TokenGen = require("../utils/tokenGenerator"); +const db = require("../utils/database"); const AssessmentModel = { + /* + * Structure: + * _id: BSON id + * cid: course ID + * name: varchar + * active: boolean true/false + * mode: secure | exam | open (decreasing security) + * fixed: bool (questions in fixed order; default: false) + * display: "one" or "all" (generally "all" for open questions, but...) + * time: 0, //<=0 means "untimed"; otherwise, time in seconds + * introduction: "", + * coefficient: number, default 1 + * questions: array of + * index: for paper test, like 2.1.a (?!); and quiz: 0, 1, 2, 3... + * wording: varchar (HTML) + * options: array of varchar --> if present, question type == quiz! + * fixed: bool, options in fixed order (default: false) + * answer: array of integers (for quiz) or html text (for paper); striped in exam mode + * active: boolean, is question in current assessment? + * points: points for this question (default 1) + * time: 0 (<=0: untimed) + * papers : array of + * number: student number + * inputs: array of {index,answer[array of integers or html text],startTime} + * current: index of current question (if relevant: display="one") + * startTime + * discoTime, totalDisco: last disconnect timestamp (if relevant) + total time + * discoCount: number of disconnections + * password: random string identifying student for exam session TEMPORARY + */ + + ////////////////// + // BASIC FUNCTIONS + + getById: function(aid, callback) + { + db.assessments.findOne( + { _id: aid }, + callback + ); + }, + + getByPath: function(cid, name, callback) + { + db.assessments.findOne( + { + cid: cid, + name: name, + }, + callback + ); + }, + + insert: function(cid, name, callback) + { + db.assessments.insert( + { + name: name, + cid: cid, + active: false, + mode: "exam", + fixed: false, + display: "one", + time: 0, + introduction: "", + coefficient: 1, + questions: [ ], + papers: [ ], + }, + callback + ); + }, + + getByCourse: function(cid, callback) + { + db.assessments.find( + { cid: cid }, + callback + ); + }, + + // arg: full assessment without _id field + replace: function(aid, assessment, cb) + { + // Should be: (but unsupported by mongojs) +// db.assessments.replaceOne( +// { _id: aid }, +// assessment, +// cb +// ); + // Temporary workaround: + db.assessments.update( + { _id: aid }, + { $set: assessment }, + cb + ); + }, + + getQuestions: function(aid, callback) + { + db.assessments.findOne( + { + _id: aid, + display: "all", + }, + { questions: 1}, + (err,res) => { + callback(err, !!res ? res.questions : null); + } + ); + }, + + getQuestion: function(aid, index, callback) + { + db.assessments.findOne( + { + _id: aid, + display: "one", + }, + { questions: 1}, + (err,res) => { + if (!!err || !res) + return callback(err, res); + const qIdx = res.questions.findIndex( item => { return item.index == index; }); + if (qIdx === -1) + return callback({errmsg: "Question not found"}, null); + callback(null, res.questions[qIdx]); + } + ); + }, + + getPaperByNumber: function(aid, number, callback) + { + db.assessments.findOne( + { + _id: aid, + "papers.number": number, + }, + (err,a) => { + if (!!err || !a) + return callback(err,a); + for (let p of a.papers) + { + if (p.number == number) + return callback(null,p); //reached for sure + } + } + ); + }, + + // NOTE: no callbacks for 2 next functions, failures are not so important + // (because monitored: teachers can see what's going on) + + addDisco: function(aid, number, deltaTime) + { + db.assessments.update( + { + _id: aid, + "papers.number": number, + }, + { $inc: { + "papers.$.discoCount": 1, + "papers.$.totalDisco": deltaTime, + } }, + { $set: { "papers.$.discoTime": null } } + ); + }, + + setDiscoTime: function(aid, number) + { + db.assessments.update( + { + _id: aid, + "papers.number": number, + }, + { $set: { "papers.$.discoTime": Date.now() } } + ); + }, + + getDiscoTime: function(aid, number, cb) + { + db.assessments.findOne( + { _id: aid }, + (err,a) => { + if (!!err) + return cb(err, null); + const idx = a.papers.findIndex( item => { return item.number == number; }); + cb(null, a.papers[idx].discoTime); + } + ); + }, + + hasInput: function(aid, number, password, idx, cb) + { + db.assessments.findOne( + { + _id: aid, + "papers.number": number, + "papers.password": password, + }, + (err,a) => { + if (!!err || !a) + return cb(err,a); + let papIdx = a.papers.findIndex( item => { return item.number == number; }); + for (let i of a.papers[papIdx].inputs) + { + if (i.index == idx) + return cb(null,true); + } + cb(null,false); + } + ); + }, + + // https://stackoverflow.com/questions/27874469/mongodb-push-in-nested-array + setInput: function(aid, number, password, input, callback) //input: index + arrayOfInt (or txt) + { + db.assessments.update( + { + _id: aid, + "papers.number": number, + "papers.password": password, + }, + { $push: { "papers.$.inputs": input } }, + callback + ); + }, + + endAssessment: function(aid, number, password, callback) + { + db.assessments.update( + { + _id: aid, + "papers.number": number, + "papers.password": password, + }, + { $set: { + "papers.$.endTime": Date.now(), + "papers.$.password": "", + } }, + callback + ); + }, + + remove: function(aid, cb) + { + db.assessments.remove( + { _id: aid }, + cb + ); + }, + + removeGroup: function(cid, cb) + { + db.assessments.remove( + { cid: cid }, + cb + ); + }, + + ///////////////////// + // ADVANCED FUNCTIONS + getByRefs: function(initials, code, name, cb) { - UserEntity.getByInitials(initials, (err,user) => { + UserModel.getByInitials(initials, (err,user) => { if (!!err || !user) return cb(err || {errmsg: "User not found"}); - CourseEntity.getByPath(user._id, code, (err2,course) => { + CourseModel.getByPath(user._id, code, (err2,course) => { if (!!err2 || !course) return cb(err2 || {errmsg: "Course not found"}); - AssessmentEntity.getByPath(course._id, name, (err3,assessment) => { + AssessmentModel.getByPath(course._id, name, (err3,assessment) => { if (!!err3 || !assessment) return cb(err3 || {errmsg: "Assessment not found"}); cb(null,assessment); @@ -25,7 +288,7 @@ const AssessmentModel = checkPassword: function(aid, number, password, cb) { - AssessmentEntity.getById(aid, (err,assessment) => { + AssessmentModel.getById(aid, (err,assessment) => { if (!!err || !assessment) return cb(err, assessment); const paperIdx = assessment.papers.findIndex( item => { return item.number == number; }); @@ -38,13 +301,13 @@ const AssessmentModel = add: function(uid, cid, name, cb) { // 1) Check that course is owned by user of ID uid - CourseEntity.getById(cid, (err,course) => { + CourseModel.getById(cid, (err,course) => { if (!!err || !course) return cb({errmsg: "Course retrieval failure"}); if (!course.uid.equals(uid)) return cb({errmsg:"Not your course"},undefined); // 2) Insert new blank assessment - AssessmentEntity.insert(cid, name, cb); + AssessmentModel.insert(cid, name, cb); }); }, @@ -52,10 +315,10 @@ const AssessmentModel = { const aid = ObjectId(assessment._id); // 1) Check that assessment is owned by user of ID uid - AssessmentEntity.getById(aid, (err,assessmentOld) => { + AssessmentModel.getById(aid, (err,assessmentOld) => { if (!!err || !assessmentOld) return cb({errmsg: "Assessment retrieval failure"}); - CourseEntity.getById(ObjectId(assessmentOld.cid), (err2,course) => { + CourseModel.getById(ObjectId(assessmentOld.cid), (err2,course) => { if (!!err2 || !course) return cb({errmsg: "Course retrieval failure"}); if (!course.uid.equals(uid)) @@ -63,7 +326,7 @@ const AssessmentModel = // 2) Replace assessment delete assessment["_id"]; assessment.cid = ObjectId(assessment.cid); - AssessmentEntity.replace(aid, assessment, cb); + AssessmentModel.replace(aid, assessment, cb); }); }); }, @@ -71,7 +334,7 @@ const AssessmentModel = // Set password in responses collection startSession: function(aid, number, password, cb) { - AssessmentEntity.getPaperByNumber(aid, number, (err,paper) => { + AssessmentModel.getPaperByNumber(aid, number, (err,paper) => { if (!!err) return cb(err,null); if (!paper && !!password) @@ -83,18 +346,26 @@ const AssessmentModel = if (paper.password != password) return cb({errmsg: "Wrong password"}); } - AssessmentEntity.getQuestions(aid, (err,questions) => { - if (!!err) - return cb(err,null); + AssessmentModel.getQuestions(aid, (err2,questions) => { + if (!!err2) + return cb(err2,null); if (!!paper) - return cb(null,{paper:paper,questions:questions}); + return cb(null,{paper:paper}); const pwd = TokenGen.generate(12); //arbitrary number, 12 seems enough... - AssessmentEntity.startSession(aid, number, pwd, (err2,ret) => { - cb(err2, { - questions: questions, - password: pwd, - }); - }); + db.assessments.update( + { _id: aid }, + { $push: { papers: { + number: number, + startTime: Date.now(), + endTime: undefined, + password: password, + totalDisco: 0, + discoCount: 0, + inputs: [ ], //TODO: this is stage 1, stack indexed answers. + // then build JSON tree for easier access / correct + }}}, + (err3,ret) => { cb(err3,{password:password}); } + ); }); }); }, @@ -102,12 +373,12 @@ const AssessmentModel = newAnswer: function(aid, number, password, input, cb) { // Check that student hasn't already answered - AssessmentEntity.hasInput(aid, number, password, input.index, (err,ret) => { + AssessmentModel.hasInput(aid, number, password, input.index, (err,ret) => { if (!!err) return cb(err,null); if (!!ret) return cb({errmsg:"Question already answered"},null); - AssessmentEntity.setInput(aid, number, password, input, (err2,ret2) => { + AssessmentModel.setInput(aid, number, password, input, (err2,ret2) => { if (!!err2 || !ret2) return cb(err2,ret2); return cb(null,ret2); @@ -115,17 +386,16 @@ const AssessmentModel = }); }, - // NOTE: no callbacks for 2 next functions, failures are not so important + // NOTE: no callbacks for next function, failures are not so important // (because monitored: teachers can see what's going on) - newConnection: function(aid, number) { //increment discoCount, reset discoTime to NULL, update totalDisco - AssessmentEntity.getDiscoTime(aid, number, (err,discoTime) => { + AssessmentModel.getDiscoTime(aid, number, (err,discoTime) => { if (!!discoTime) - AssessmentEntity.addDisco(aid, number, Date.now() - discoTime); + AssessmentModel.addDisco(aid, number, Date.now() - discoTime); }); }, -}; +} module.exports = AssessmentModel;