WikiLerni/controllers/answer.js

273 lines
11 KiB
JavaScript

const { QueryTypes } = require("sequelize");
const config = require("../config/main.js");
const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js");
const tool = require("../tools/main");
const toolFile = require("../tools/file");
const subscriptionCtrl = require("./subscription");
const questionnaireCtrl = require("./questionnaire");
const txt = require("../lang/"+config.adminLang+"/answer");
const txtGeneral = require("../lang/"+config.adminLang+"/general");
// Enregistrement d'une réponse à un questionnaire
exports.create = async (req, res, next) =>
{
try
{
const db = require("../models/index");
const checkQuestionnaireAccess=await subscriptionCtrl.checkQuestionnaireAccess(req.connectedUser.User.id, req.body.QuestionnaireId);
req.body.UserId=req.connectedUser.User.id;
if(checkQuestionnaireAccess) // l'utilisateur a déjà accès à ce questionnaire
await db["Answer"].create({ ...req.body }, { fields: ["nbQuestions", "nbCorrectAnswers", "duration", "QuestionnaireId", "UserId"] });
else
{
await Promise.all([
db["QuestionnaireAccess"].create({ ...req.body }, { fields: ["QuestionnaireId", "UserId"] }),
db["Answer"].create({ ...req.body }, { fields: ["nbQuestions", "nbCorrectAnswers", "duration", "QuestionnaireId", "UserId"] })
]);
}
// j'en profite pour remettre les pendules à l'heure !
db["User"].update({ timeDifference: req.body.timeDifference }, { where: { id : req.connectedUser.User.id }, limit:1 });
creaUserStatsAnwsersJson(req.body.UserId);
creaUserQuestionnairesWithoutAnswerJson(req.body.UserId);
creaUserAnswersJson(req.body.UserId);
res.status(201).json({ message: txt.responseSavedMessage });
next();
}
catch(e)
{ // à priori, l'utilisateur ne peut pas avoir envoyé de données incorrectes, donc erreur application pour admin
next(e);
}
}
// Retourne les réponses d'un utilisateur pour un questionnaire donné
// Si fichier réponses devient trop gros, passer par sql ?
exports.getAnswersByQuestionnaire = async(req, res, next) =>
{
try
{
const answers=await getUserAnswersByQuestionnaire(req.params.userId, req.params.questionnaireId);
res.status(200).json(answers);
next();
}
catch(e)
{
next(e);
}
}
// Retourne les statistiques de l'utilisateur
exports.getStatsByUser = async(req, res, next) =>
{
try
{
const stats=await getUserStatsAnswers(req.params.userId);
// J'ajoute les stats générales des questionnaires pour comparaison :
stats.general=await questionnaireCtrl.getStatsQuestionnaires();
res.status(200).json(stats);
}
catch(e)
{
next(e);
}
}
// Retourne la liste des questionnaires auxquels un utilisateur a accès, mais n'a pas répondu
// Ils sont listés par ordre de fraîcheur, les + récents étant en début de liste
// Un questionnaire de début et un nombre de questionnaires à retourner doivent être fournis (pagination).
exports.getQuestionnairesWithouAnswerByUser = async(req, res, next) =>
{
try
{
let datas;
if(req.params.id===undefined || req.params.begin===undefined || req.params.nb===undefined)
{
const err=new Error;
err.message=txtGeneral.neededParams;
throw err;
}
else
datas=await getUserQuestionnairesWithoutAnswer(req.params.id, req.params.begin, req.params.nb);
if(datas!==false)
{
if(req.params.output!==undefined && req.params.output=="html")
{
if(datas.questionnaires.length!=0)
{
const pug = require("pug");
const striptags = require("striptags");
const txtIllustration= require("../lang/"+config.adminLang+"/illustration");
const compiledFunction = pug.compileFile("./views/"+config.theme+"/includes/listing-questionnaires.pug");
const pageDatas=
{
tool: tool,
striptags: striptags,
txtGeneral: txtGeneral,
txtIllustration: txtIllustration,
questionnaires: datas.questionnaires,
nbQuestionnairesList:configTpl.nbQuestionnairesUserHomePage
}
datas.html=await compiledFunction(pageDatas);
}
else
datas.html="";
res.status(200).json(datas);
}
else
res.status(200).json(datas);
}
else
res.status(404).json(txtQuestionnaire.notFound);
next();
}
catch(e)
{
next(e);
}
}
// FONCTIONS UTILITAIRES
// Créer la liste des réponses d'un utilisateur
const creaUserAnswersJson = async (UserId) =>
{
const db = require("../models/index");
const userAnswers=await db.sequelize.query("SELECT `QuestionnaireId`,`nbQuestions`,`nbCorrectAnswers`,`duration`,`createdAt` FROM `Answers` WHERE `UserId`=:id ORDER BY `QuestionnaireId` DESC, `createdAt` DESC", { replacements: { id: UserId }, type: QueryTypes.SELECT });
if(userAnswers)
{
await toolFile.createJSON(config.dirCacheUsersAnswers, UserId, userAnswers);// à surveiller car fichier pouvant devenir gros ! mais utile pour SVG côté client
return userAnswers;
}
else
return false;
}
exports.creaUserAnswersJson = creaUserAnswersJson;
// Retourne les réponses d'un utilisateurs à un questionnaire
const getUserAnswersByQuestionnaire = async (UserId, QuestionnaireId) =>
{
let userAnswers=await toolFile.readJSON(config.dirCacheUsersAnswers, UserId);
if(!userAnswers)
userAnswers=await creaUserAnswersJson(UserId);
if(!userAnswers)
return false;
const answers=[];
for(let i in userAnswers)
{
if(userAnswers[i].QuestionnaireId==QuestionnaireId)// pas "===" car type de données pouvant être différents
answers.push(userAnswers[i]);
else if(answers.length!==0)// les réponses étant classées par QuestionnaireId, je peux sortir de la boucle
break;
}
return answers;
}
// À combien de questionnaire l'utilisateur a-t'il répondu, quelle est son résultat moyen ?
const creaUserStatsAnwsersJson = async (UserId) =>
{
const db = require("../models/index");
const getUserAnswers = await db["Answer"].findAll({ where: { UserId : UserId }, attributes: ["id"] });
const getUserQuestionnaires = await db["Answer"].findAll({ attributes: [[db.sequelize.fn('DISTINCT', db.sequelize.col('QuestionnaireId')), 'id']], where: { UserId : UserId }});
const getUserStats = await db.sequelize.query("SELECT ROUND(AVG(nbCorrectAnswers/nbQuestions) *100) as avgCorrectAnswers, ROUND(AVG(duration)) as avgDuration FROM Answers GROUP BY UserId HAVING UserId=:id", { replacements: { id: UserId }, type: QueryTypes.SELECT });
if(getUserAnswers && getUserQuestionnaires)
{
const stats =
{
nbAnswers : getUserAnswers.length,
nbQuestionnaires : getUserQuestionnaires.length
}
if(getUserStats && getUserAnswers.length!=0)
{
stats.avgCorrectAnswers=getUserStats[0].avgCorrectAnswers;
stats.avgDuration=getUserStats[0].avgDuration;
}
await toolFile.createJSON(config.dirCacheUsersAnswers, "stats"+UserId, stats);
return stats;
}
else
return false;
}
exports.creaUserStatsAnwsersJson = creaUserStatsAnwsersJson;
// Retourne les données créées par la fonction précédente
const getUserStatsAnswers = async (UserId) =>
{
let userStats=await toolFile.readJSON(config.dirCacheUsersAnswers, "stats"+UserId);
if(!userStats)
userStats=await creaUserStatsAnwsersJson(UserId);
if(!userStats)
return false;
else
return userStats;
}
// À combien de questionnaire les utilisateurs ont-ils répondu ces dernières 24 ? depuis le début ?
const getStatsAnswers = async () =>
{
const db = require("../models/index");
const getAnswers24H = await db.sequelize.query("SELECT `id` FROM `Answers` WHERE `createdAt` > ADDDATE(NOW(), -1)", { type: QueryTypes.SELECT });
const getAnswersTot = await db.sequelize.query("SELECT `id` FROM `Answers`", { type: QueryTypes.SELECT });
if(getAnswers24H && getAnswersTot)
{
const stats =
{
nbAnswers24H : getAnswers24H.length,
nbAnswersTot : getAnswersTot.length
}
return stats;
}
else
return false;
}
exports.getStatsAnswers = getStatsAnswers;
// Créer la liste des questionnaires proposés à l'utilisateur, mais auxquels il n'a pas encore répondu
const creaUserQuestionnairesWithoutAnswerJson = async (UserId) =>
{
UserId=tool.trimIfNotNull(UserId);
if(UserId===null)
return false;
const db = require("../models/index");
const userQuestionnaires=await db.sequelize.query("SELECT `QuestionnaireId` FROM `QuestionnaireAccesses` WHERE `UserId`=:id AND `QuestionnaireId` NOT IN (SELECT DISTINCT `QuestionnaireId` FROM Answers WHERE `UserId`=:id) ORDER BY `createdAt` DESC ", { replacements: { id: UserId }, type: QueryTypes.SELECT });
if(userQuestionnaires)
{
const questionnairesId=[];// les ids suffisent et allègent le fichier
for(i in userQuestionnaires)
questionnairesId.push(userQuestionnaires[i].QuestionnaireId);
await toolFile.createJSON(config.dirCacheUsersQuestionnaires+"/without", UserId, { ids: questionnairesId });
return { ids: questionnairesId };
}
else
return false;
}
exports.creaUserQuestionnairesWithoutAnswerJson = creaUserQuestionnairesWithoutAnswerJson;
// Retourne les données créées par la fonction précédente
const getUserQuestionnairesWithoutAnswer = async (UserId, begin=0, nb=10) =>
{
UserId=tool.trimIfNotNull(UserId);
if(UserId===null)
return false;
let userQuestionnaires=await toolFile.readJSON(config.dirCacheUsersQuestionnaires+"/without", UserId);
if(!userQuestionnaires)
userQuestionnaires=await creaUserQuestionnairesWithoutAnswerJson(UserId);
if(!userQuestionnaires)
return false;
let questionnaire, Questionnaires=[], i=begin;
const nbTot=userQuestionnaires.ids.length;
if(nb===0)
nb=nbTot;// peut être = 0 si l'utilisateur est à jour
while(i<nb && userQuestionnaires.ids[i])
{
questionnaire=await questionnaireCtrl.searchQuestionnaireById(userQuestionnaires.ids[i]);
if(questionnaire)
Questionnaires.push(questionnaire);
i++;
}
return { nbTot: nbTot, questionnaires: Questionnaires };
}
exports.getUserQuestionnairesWithoutAnswer = getUserQuestionnairesWithoutAnswer;