const { Op, QueryTypes } = require("sequelize"); const pug = require("pug"); const striptags = require("striptags"); const config = require("../config/main.js"); const configQuestionnaires = require("../config/questionnaires.js"); const configTags = require("../config/tags.js"); const configLinks = require("../config/links.js"); const configIllustrations = require("../config/illustrations.js"); const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js"); const tool = require("../tools/main"); const toolError = require("../tools/error"); const toolFile = require("../tools/file"); const toolMail = require("../tools/mail"); const groupCtrl = require("./group"); const questionCtrl = require("./question"); const illustrationCtrl = require("./illustration"); const tagCtrl = require("./tag"); const userCtrl = require("./user"); const txtGeneral = require("../lang/"+config.adminLang+"/general"); const txtQuestionnaire = require("../lang/"+config.adminLang+"/questionnaire"); const txtQuestionnaireAccess = require("../lang/"+config.adminLang+"/questionnaireaccess"); const txtIllustration = require("../lang/"+config.adminLang+"/illustration"); exports.create = async (req, res, next) => { try { if(tool.isEmpty(req.body.GroupId) && !tool.isEmpty(req.body.rankInGroup)) res.status(400).json({ errors : [txtQuestionnaire.needGroupIfRank] }); else { const db = require("../models/index"); req.body.CreatorId=req.connectedUser.User.id; if(!tool.isEmpty(req.body.GroupId) && tool.isEmpty(req.body.rankInGroup)) req.body.rankInGroup=1; const questionnaire=await db["Questionnaire"].create({ ...req.body }, { fields: ["title", "slug", "introduction", "keywords", "publishingAt", "language", "estimatedTime", "CreatorId", "GroupId", "rankInGroup"] }); creaStatsQuestionnairesJson(); // Si un groupe a été sélectionné, il faut mettre à jour les fichiers du groupe : if(!tool.isEmpty(req.body.GroupId)) { const groupInfos = await groupCtrl.creaGroupJson(req.body.GroupId); // Si le nouveau quiz est publiable, le fichier HTML de l'éventuel quiz/élément précédent du groupe doit être actualisé // Si le nouveau n'est pas publiable, il ne sera pas listé dans groupInfos.Questionnaires donc pas d'incidence if(groupInfos !== false) { for(let i in groupInfos.Questionnaires) { if(groupInfos.Questionnaires[i].id === questionnaire.id && i != 0) { let j=parseInt(i)-1;// !! "i" n'est pas un nombre ! if(groupInfos.Questionnaires[j] !== undefined) await creaQuestionnaireHTML(groupInfos.Questionnaires[j].id); } } } } // id utile au middleware suivant (classement tags) qui s'occupe aussi de retourner une réponse si ok : req.body.QuestionnaireId=questionnaire.id; next(); } } catch(e) { const returnAPI=toolError.returnSequelize(e); if(returnAPI.messages) { res.status(returnAPI.status).json({ errors : returnAPI.messages }); next(); } else next(e); } } exports.modify = async (req, res, next) => { try { const db = require("../models/index"); const questionnaire=await searchQuestionnaireById(req.params.id); if(!questionnaire) { const Err=new Error; error.status=404; error.message=txtQuestionnaire.notFound+" ("+req.params.id+")"; throw Err; } else if(req.connectedUser.User.status==="creator" && req.connectedUser.User.id!==questionnaire.CreatorId) res.status(401).json({ errors: txtGeneral.notAllowed }); else if(tool.isEmpty(req.body.GroupId) && !tool.isEmpty(req.body.rankInGroup)) res.status(400).json({ errors : [txtQuestionnaire.needGroupIfRank] }); else { if(!tool.isEmpty(req.body.GroupId) && tool.isEmpty(req.body.rankInGroup)) req.body.rankInGroup=1; await db["Questionnaire"].update({ ...req.body }, { where: { id : req.params.id } , fields: ["title", "slug", "introduction", "keywords", "publishingAt", "language", "estimatedTime", "GroupId", "rankInGroup"], limit:1 }); creaStatsQuestionnairesJson();// le nombre de quizs publiés peut avoir changé // Si le questionnaire a été déplacé d'un groupe, il faut actualiser les fichiers de l'ancien groupe : if(req.body.GroupId != questionnaire.Questionnaire.GroupId && !tool.isEmpty(questionnaire.Questionnaire.GroupId)) { //(ne pas utiliser "!==" car types différents) const groupInfos = await groupCtrl.creaGroupJson(questionnaire.Questionnaire.GroupId); // Peut aussi avoir un impact sur les autres élement dans l'ancien groupe, notamment celui qui le précédait if(groupInfos !== false) { for(let i in groupInfos.Questionnaires) await creaQuestionnaireHTML(groupInfos.Questionnaires[i].id); } } } // id utile au middleware suivant (classement tags) qui s'occupe aussi de retourner une réponse si ok : req.body.QuestionnaireId=req.params.id; next(); } catch(e) { const returnAPI=toolError.returnSequelize(e); if(returnAPI.messages) { res.status(returnAPI.status).json({ errors : returnAPI.messages }); next(); } else next(e); } } exports.delete = async (req, res, next) => { try { const del=deleteQuestionnaire(req.params.id, req); if(typeof del===Error) throw Err; else res.status(200).json({ message: txtGeneral.deleteOkMessage }); next(); } catch(e) { next(e); } } exports.getOneQuestionnaireById = async (req, res, next) => { try { const datas=await searchQuestionnaireById(req.params.id, true); if(datas) res.status(200).json(datas); else res.status(404).json({ errors:txtQuestionnaire.notFound }); next(); } catch(e) { next(e); } } exports.showOneQuestionnaireById = async (req, res, next) => { try { // Seuls certains utilisateurs peuvent avoir accès à cette route : const connectedUser=await userCtrl.checkTokenUser(req.params.token); if(connectedUser === false) res.status(403).json({ errors:txtGeneral.failAuthToken }); else { if(["admin", "manager", "creator"].indexOf(connectedUser.User.status) === -1) res.status(403).json({ errors:txtGeneral.notAllowed }); else { const HTML=await creaQuestionnaireHTML(req.params.id, true); if(HTML) { res.setHeader("Content-Type", "text/html"); res.send(HTML); } else res.status(404).json({ errors:txtQuestionnaire.notFound }); } } next(); } catch(e) { next(e); } } // Recherche par mots-clés parmis tous les questionnaires publiés en filtrant parmi ceux auxquels l'abonné a déjà répondu ou pas // Seul un certain nombre de résultats est renvoyé (pagination) exports.searchQuestionnaires = async (req, res, next) => { try { let search=tool.trimIfNotNull(req.body.searchQuestionnaires); if(search === null || search === "" || search.length < config.minSearchQuestionnaires) res.status(400).json(txtQuestionnaireAccess.searchIsNotLongEnough); else { const db = require("../models/index"); let getQuestionnaires; if(!tool.isEmpty(req.body.onlyAnswers) && !tool.isEmpty(req.connectedUser.User.id)) getQuestionnaires=await db.sequelize.query("SELECT DISTINCT `Questionnaires`.`id` FROM `Questionnaires` INNER JOIN `Answers` WHERE `Answers`.`QuestionnaireId`=`Questionnaires`.`id` AND `Answers`.`UserId`=:id AND (`title` LIKE :search OR `keywords` LIKE :search) AND `isPublished` = 1", { replacements: { id:req.connectedUser.User.id, search: "%"+search+"%" }, type: QueryTypes.SELECT }); else getQuestionnaires=await db.sequelize.query("SELECT `id` FROM `Questionnaires` WHERE (`title` LIKE :search OR `keywords` LIKE :search) AND `isPublished` = 1", { replacements: { search: "%"+search+"%" }, type: QueryTypes.SELECT }); let begin=0, end, output=""; if(!tool.isEmpty(req.body.begin)) begin=parseInt(req.body.begin, 10); end=begin+configTpl.nbQuestionnairesUserHomePage-1;// tableau commence à 0 ! if(req.body.output!==undefined) output=req.body.output; datas=await getListingsQuestionnairesOuput(getQuestionnaires, begin, end, output); res.status(200).json(datas); } next(); } catch(e) { next(e); } } // Recherche aléatoire parmi tous les questionnaires publiés // De nouveau on peut filtrer ou non ceux auxquels l'utilisateur a répondu // Par contre, ici pas de pagination car on maîtrise le nombre de résultats envoyés exports.getRandomQuestionnaires = async (req, res, next) => { try { const db = require("../models/index"); let getQuestionnaires; if(!tool.isEmpty(req.body.onlyAnswers) && !tool.isEmpty(req.connectedUser.User.id)) getQuestionnaires=await db.sequelize.query("SELECT DISTINCT `Questionnaires`.`id` FROM `Questionnaires` INNER JOIN `Answers` WHERE `Answers`.`QuestionnaireId`=`Questionnaires`.`id` AND `Answers`.`UserId`=:id AND `isPublished` = 1 ORDER BY RAND() LIMIT "+configQuestionnaires.nbRandomResults, { replacements: { id:req.connectedUser.User.id }, type: QueryTypes.SELECT }); else getQuestionnaires=await db.sequelize.query("SELECT `id` FROM `Questionnaires` WHERE `isPublished` = 1 ORDER BY RAND() LIMIT "+configQuestionnaires.nbRandomResults, { type: QueryTypes.SELECT }); let begin=0, end; end=begin+configTpl.nbQuestionnairesUserHomePage-1; datas=await getListingsQuestionnairesOuput(getQuestionnaires, begin, end, "html"); res.status(200).json(datas); next(); } catch(e) { next(e); } } // Recherche par mots-clés parmis tous les questionnaires, y compris ceux non publiés exports.searchAdminQuestionnaires = async (req, res, next) => { try { let search=tool.trimIfNotNull(req.body.searchQuestionnaires); if(search === null || search === "" || search.length < config.minSearchQuestionnaires) res.status(400).json(txtQuestionnaireAccess.searchIsNotLongEnough); else { const db = require("../models/index"); const getQuestionnaires=await db.sequelize.query("SELECT `id`,`title` FROM `Questionnaires` WHERE (`title` LIKE :search OR `introduction` LIKE :search OR `keywords` LIKE :search) ORDER BY `title` ASC", { replacements: { search: "%"+search+"%" }, type: QueryTypes.SELECT }); res.status(200).json(getQuestionnaires); } next(); } catch(e) { next(e); } } // Retourne les statistiques concernant les questionnaires exports.getStats = async(req, res, next) => { try { const stats=await getStatsQuestionnaires(); res.status(200).json(stats); } catch(e) { next(e); } } // Liste des prochains questionnaires devant être publiés. // On vérifie à chaque fois si ils ont ce qu'il faut pour être publiables // On en profite pour chercher la prochaine date sans questionnaire programmé exports.getListNextQuestionnaires = async(req, res, next) => { try { let questionnaires=await getNextQuestionnaires(); const dayWithoutPublication=[0,6];// à déclarer dans config + gérer cas où aucun jour n'est obligatoire let dateNeeded="", questionnairesDatas, dateQuestionnaireTS, previousDayNeededTS, previousDayTS; for(let i in questionnaires) { questionnairesDatas=await searchQuestionnaireById(questionnaires[i].id); questionnaires[i].isPublishable=checkQuestionnaireIsPublishable(questionnairesDatas, false); // le questionnaire est-il complet ? dateQuestionnaireTS=new Date(questionnaires[i].datePublishing).getTime(); if(dateNeeded==="") { // je commence par chercher le jour précédent pour lequel je dois avoir publié quelque chose : previousDayTS=new Date(new Date(dateQuestionnaireTS-3600*1000*24)); previousDayNeededTS=0; while (previousDayNeededTS===0) { if(dayWithoutPublication.indexOf(previousDayTS.getDay())===-1) previousDayNeededTS=previousDayTS; else previousDayTS=new Date(previousDayTS.getTime()-3600*1000*24); } // si il n'y a pas de quiz précédent, cette date est celle que je cherche if(!questionnaires[i-1]) { if(previousDayNeededTS >= Date.now()) dateNeeded=previousDayNeededTS; } else { // sinon je compare la date du précédent quiz à celle pour laquelle j'ai besoin d'un quiz if(new Date(questionnaires[i-1].datePublishing).getTime() < previousDayNeededTS) dateNeeded=previousDayNeededTS; } } } if(questionnaires.length > 0 && dateNeeded==="") dateNeeded=new Date(dateQuestionnaireTS+3600*1000*24);// le jour suivant celui du dernier questionnaire else dateNeeded=new Date(Date.now()+3600*1000*24);// mais il est possible que rien n'ai été publié ce jour, le quiz du jour étant absent de la liste traitée res.status(200).json({questionnaires: questionnaires, dateNeeded: dateNeeded }); next(); } catch(e) { next(e); } } // Test si des questionnaires doivent être publiés puis (re)génère tous les fichiers HTML des questionnaires + les pages accueil + news // La requête est ensuite passé aux tags qui font la même chose exports.HTMLRegenerate= async (req, res, next) => { try { await checkQuestionnairesNeedToBePublished(); const nb=await checkQuestionnairesPublishedHaveHTML(true); await creaNewQuestionnairesJson();// provoque mise à jour du HTLM, RSS, etc. res.messageToNext=txtQuestionnaire.haveBeenRegenerated.replace("#NB1", nb); next(); } catch(e) { next(e); } } // CRONS // Supprime fichiers json de questionnaires n'existant plus. exports.deleteJsonFiles= async (req, res, next) => { try { const db = require("../models/index"); const questionnaires=await db["Questionnaire"].findAll({ attributes: ["id"] }); let saveFiles=["last.json","stats.json"];// dans le même répertoir et à garder. for(let i in questionnaires) saveFiles.push(questionnaires[i].id+".json"); const deleteFiles = await toolFile.deleteFilesInDirectory(configQuestionnaires.dirCacheQuestionnaires, saveFiles); res.status(200).json(deleteFiles); next(); } catch(e) { next(e); } } // test si des questionnaires doivent être publiés // + si des questionnaires publiés n'ont pas fichier html // si fichier html créé il faut aussi actualiser la page d'accueil & co exports.checkQuestionnairesNeedToBePublished= async (req, res, next) => { try { await checkQuestionnairesNeedToBePublished(); const nb=await checkQuestionnairesPublishedHaveHTML(); if(nb > 0) creaNewQuestionnairesJson();// provoque mise à jour du HTLM, RSS, etc. res.status(200).json(txtQuestionnaire.haveBeenPublished.replace(":NB", nb)); next(); } catch(e) { next(e); } } // FONCTIONS PARTAGÉES const creaQuestionnaireJson = async (id) => { const db=require("../models/index"); const Questionnaire=await db["Questionnaire"].findByPk(id); if(Questionnaire) { let datas={ Questionnaire }; const Tags=await db["QuestionnaireClassification"].findAll({ where: { QuestionnaireId: Questionnaire.id }, attributes: ["TagId"] }); if(Tags) datas.Tags=Tags; const Illustrations=await db["Illustration"].findAll({ where: { QuestionnaireId: Questionnaire.id } }); if(Illustrations) datas.Illustrations=Illustrations; const Links=await db["Link"].findAll({ where: { QuestionnaireId: Questionnaire.id } }); if(Links) datas.Links=Links; const Questions=await db["Question"].findAll({ where: { QuestionnaireId: Questionnaire.id }, order: [["rank", "ASC"], ["createdAt", "ASC"]], attributes: ["id"] }); if(Questions) datas.Questions=Questions; const wasPublished=datas.Questionnaire.isPublished; datas.Questionnaire.isPublished=checkQuestionnaireIsPublishable(datas); // Important d'écrire le fichier ici, car il est nécessaire aux fonctions appelées par la suite await toolFile.createJSON(config.dirCacheQuestionnaires, id, datas); if(datas.Questionnaire.isPublished !== wasPublished) { await db["Questionnaire"].update({ isPublished:datas.Questionnaire.isPublished }, { where: { id : id } , fields: ["isPublished"], limit:1 }); // + Peut impacter la liste des tags utilisés : tagCtrl.creaUsedTagsJson(); // Et si le quiz était publié jusqu'ici, il me faut supprimer son fichier HTML. if(wasPublished) toolFile.deleteFile(config.dirHTMLQuestionnaires, Questionnaire.slug+".html"); } // Peut impacter la liste des derniers quizs si des informations affichées ont changé creaNewQuestionnairesJson(); // + les listes de quizs / tags : for(let i in Tags) tagCtrl.creaQuestionnairesTagJson(Tags[i].TagId) // ! Json + HTML, donc potentiellement long. if(datas.Questionnaire.isPublished) await creaQuestionnaireHTML(id); // Si le questionnaire est l'élément d'un groupe, il faut actualiser les fichiers du groupe // Il faut le faire ici et non dans le contrôleur de mise à jour, car des chgts des éléments annexes (liens, questions...) peuvent aussi impacter les fichiers du groupe ou avoir rendu le questionnaire publiable, etc. if(!tool.isEmpty(Questionnaire.GroupId)) { const groupInfos = await groupCtrl.creaGroupJson(Questionnaire.GroupId); // Idem pour le HTML des éventuels autres quizs du groupe car title, slug, rank, etc. peuvent avoir changé if(groupInfos !== false) { for(let i in groupInfos.Questionnaires) await creaQuestionnaireHTML(groupInfos.Questionnaires[i].id); } } return datas; } else return false; } exports.creaQuestionnaireJson = creaQuestionnaireJson; // Supprime un questionnaire et toutes ses dépendances const deleteQuestionnaire = async (id, req) => { const db = require("../models/index"); const questionnaire=await searchQuestionnaireById(id); if(!questionnaire) { const Err=new Error; error.status=404; error.message=txtQuestionnaire.notFound+" ("+req.params.id+")"; return Err; } else if(req.connectedUser.User.status==="creator" && req.connectedUser.User.id!==questionnaire.CreatorId) { const Err=new Error; error.status=401; error.message=txtGeneral.notAllowed; return Err; } else { // Suppression des fichiers associés en plus du sql. Inutile pour link qui n'a pas de fichier. // À faire avant la suppression SQL du questionnaire qui entraîne des suppressions en cascade dans la base de données. for(let i in questionnaire.Questions) await questionCtrl.deleteQuestionById(questionnaire.Questions[i].id); for(let i in questionnaire.Illustrations) await illustrationCtrl.deleteIllustrationById(questionnaire.Illustrations[i].id); const nb=await db["Questionnaire"].destroy( { where: { id : id }, limit:1 }); if(nb===1)// = json existant, bien que sql déjà supprimé ? { toolFile.deleteJSON(configQuestionnaires.dirCacheQuestionnaires, id); // + HTML : toolFile.deleteFile(configQuestionnaires.dirHTMLQuestionnaires, questionnaire.Questionnaire.slug+".html"); // Si ce questionnaire était l'élément d'un groupe, il faut actualiser ses fichiers : if(!tool.isEmpty(questionnaire.Questionnaire.GroupId)) { const groupInfos=await groupCtrl.creaGroupJson(questionnaire.Questionnaire.GroupId); // Idem pour le HTML des éventuels autres quizs du groupe, notamment si un d'entre eux le précédait if(groupInfos !== false) { for(let i in groupInfos.Questionnaires) await creaQuestionnaireHTML(groupInfos.Questionnaires[i].id); } } // Actualisation de liste des questionnaires pour les tags concernés. // Ici au contraire, les enregistrements doivent être supprimés avant. for(let i in questionnaire.Tags) tagCtrl.creaQuestionnairesTagJson(questionnaire.Tags[i].TagId); // La suppression peut éventuellement concerner un des derniers questionnaires, donc : await creaNewQuestionnairesJson(); await creaStatsQuestionnairesJson(); return true; } } } exports.deleteQuestionnaire = deleteQuestionnaire; const checkQuestionnaireIsPublishable = (datas, checkDate=true) => { if(checkDate) { if(datas.Questionnaire.publishingAt===null) return false; else { const today=new Date(); today.setHours(0,0,0,0);// !! attention au décalage horaire du fait de l'enregistrement en UTC dans mysql const publishingAt=new Date(datas.Questionnaire.publishingAt); if (publishingAt.getTime() > today.getTime()) return false; } } if(datas.Questions === undefined || datas.Questions.length < config.nbQuestionsMin) return false;// le nombre de réponses mini étant contrôlé au moment de l'enregistrement de la question if(datas.Tags === undefined || datas.Tags.length < config.nbTagsMin) return false; if(datas.Links === undefined || datas.Links.length < config.nbLinksMin) return false; if(datas.Illustrations === undefined || datas.Illustrations.length < config.nbIllustrationsMin) return false; return true; } const creaQuestionnaireHTML = async (id, preview=false) => { // deux possibilités : // -- si élément d'un groupe de quiz : juste le texte sans les questions // -- si quiz automone : toutes les infos const questionnaire=await searchQuestionnaireById(id, true); if(!questionnaire) return false; if(questionnaire.Questionnaire.isPublished===false && preview===false) return false; if(!tool.isEmpty(questionnaire.Questionnaire.GroupId)) return creaQuestionnaireInGroupHTML(questionnaire, preview); else { const compiledFunction = pug.compileFile("./views/"+config.theme+"/quiz.pug"); const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js"); const pageDatas = { config: config, configTpl: configTpl, tool: tool, txtGeneral : txtGeneral, txtQuestionnaire: txtQuestionnaire, txtIllustration: txtIllustration, pageLang: questionnaire.Questionnaire.language, metaDescription: tool.shortenIfLongerThan(config.siteName+" : "+striptags(questionnaire.Questionnaire.introduction.replace("
", " ").replace("

", " ")), 200), author: questionnaire.Questionnaire.CreatorName, pageTitle: questionnaire.Questionnaire.title+" ("+txtQuestionnaire.questionnairesName+")", contentTitle: questionnaire.Questionnaire.title, questionnaire: questionnaire, linkCanonical: config.siteUrl+"/"+config.dirWebQuestionnaires+"/"+questionnaire.Questionnaire.slug+".html" } const html=await compiledFunction(pageDatas); if(preview===false) { await toolFile.createHTML(config.dirHTMLQuestionnaires, questionnaire.Questionnaire.slug, html); return true; } else return html; } } exports.creaQuestionnaireHTML = creaQuestionnaireHTML; const creaQuestionnaireInGroupHTML = async (questionnaire, preview=false) => { if(questionnaire === undefined) return false; if(questionnaire.Questionnaire.isPublished === false && preview === false) return false; const compiledFunction = pug.compileFile("./views/"+config.theme+"/quiz-element.pug"); const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js"); const txtUser = require("../lang/"+config.adminLang+"/user"); // J'ai aussi besoin de certaines informations du groupe : const groupInfos=await groupCtrl.searchGroupById(questionnaire.Questionnaire.GroupId, false); if(!groupInfos) return false; // + Certaines infos de l'élément suivant du groupe, s'il existe : let nextQuestionnaire=null; for(let i in groupInfos.Questionnaires) if(groupInfos.Questionnaires[i].id === questionnaire.Questionnaire.id) { let j=parseInt(i)+1;// !! "i" n'est pas un nombre ! if(groupInfos.Questionnaires[j] !== undefined) nextQuestionnaire=await searchQuestionnaireById(groupInfos.Questionnaires[j].id, false); } const pageDatas= { config: config, configQuestionnaires: configQuestionnaires, configTpl: configTpl, tool: tool, txtGeneral : txtGeneral, txtQuestionnaire: txtQuestionnaire, txtIllustration: txtIllustration, txtUser: txtUser, pageLang: questionnaire.Questionnaire.language, metaDescription: tool.shortenIfLongerThan(config.siteName+" : "+striptags(questionnaire.Questionnaire.introduction.replace("
", " ").replace("

", " ")), 200), author: questionnaire.Questionnaire.CreatorName, pageTitle: questionnaire.Questionnaire.title+" ("+txtQuestionnaire.questionnairesName+")", contentTitle: questionnaire.Questionnaire.title, questionnaire: questionnaire, group: groupInfos, nextQuestionnaire: nextQuestionnaire, linkCanonical: config.siteUrl+"/"+config.dirWebQuestionnaires+"/"+questionnaire.Questionnaire.slug+".html" } const html=await compiledFunction(pageDatas); if(preview===false) { await toolFile.createHTML(config.dirHTMLQuestionnaires, questionnaire.Questionnaire.slug, html); return true; } else return html; } const searchQuestionnaireById = async (id, reassemble=false) => { let questionnaire=await toolFile.readJSON(config.dirCacheQuestionnaires, id); if(!questionnaire) questionnaire=await creaQuestionnaireJson(id); if(!questionnaire) return false; if(reassemble) { const author=await userCtrl.searchUserById(questionnaire.Questionnaire.CreatorId); if(author) questionnaire.Questionnaire.CreatorName=author.User.name; if(!tool.isEmpty(questionnaire.Questionnaire.GroupId)) questionnaire.Group=await groupCtrl.searchGroupById(questionnaire.Questionnaire.GroupId, false);// !! false, sinon on risque de tourner en rond ! let questionDatas; questionsDatas=[]; for(let i in questionnaire.Questions) { questionDatas=await questionCtrl.searchQuestionById(questionnaire.Questions[i].id); if(questionDatas) questionsDatas.push(questionDatas); } questionnaire.Questions=questionsDatas; const tags=await tagCtrl.getTagsQuestionnaire(id); if(tags) questionnaire.Tags=tags; } return questionnaire; } exports.searchQuestionnaireById = searchQuestionnaireById; // Cherche si il y a des questionnaires dont la date de publication est passée mais qui ne sont pas notés comme publiés // Vérifie si ils sont publiables et si oui change leur statut et réactualise le cache json const checkQuestionnairesNeedToBePublished = async () => { const db = require("../models/index"); const questionnaires= await db.sequelize.query("SELECT `id` FROM `Questionnaires` WHERE `publishingAt` < NOW() AND `isPublished` = false", { type: QueryTypes.SELECT }); let questionnaireDatas; for(let i in questionnaires) { questionnaireDatas=await searchQuestionnaireById(questionnaires[i].id, true); if(checkQuestionnaireIsPublishable(questionnaireDatas)) { await db["Questionnaire"].update({ isPublished:true }, { where: { id : questionnaires[i].id } , fields: ["isPublished"], limit:1 }); creaQuestionnaireJson(questionnaires[i].id);// provoque normalement la création du HTML } } } // Contrôle si tous les fichiers devant être publiés ont bien leur fichier HTML, sinon le génère // Si regenerate true, tous les fichiers sont générés, même si ils existent déjà (évolution template...) // Retourne le nombre de fichiers ayant été régénérés const checkQuestionnairesPublishedHaveHTML = async (regenerate=false) => { const db = require("../models/index"); const questionnaires= await db.sequelize.query("SELECT `id`,`slug` FROM `Questionnaires` WHERE `isPublished` = true", { type: QueryTypes.SELECT }); let nb=0; for(let i in questionnaires) { if(regenerate) { await creaQuestionnaireHTML(questionnaires[i].id); nb++; } else if(await toolFile.checkIfFileExist(config.dirHTMLQuestionnaires, questionnaires[i].slug+".html")===false) { await creaQuestionnaireHTML(questionnaires[i].id); nb++; } } return nb; } // Liste des derniers questionnaires publiés (utile pour page d'accueil, flux rss, etc.) const creaNewQuestionnairesJson = async () => { const db = require("../models/index"); const Questionnaires=await db["Questionnaire"].findAll({ where: { isPublished : true }, order: [[config.fieldNewQuestionnaires, "DESC"], ["id", "DESC"]], attributes: ["id"], limit: config.nbNewQuestionnaires }); if(Questionnaires) { await toolFile.createJSON(config.dirCacheQuestionnaires, "last", Questionnaires); creaNewQuestionnairesHTML(Questionnaires); return Questionnaires; } else return false; } exports.creaNewQuestionnairesJson = creaNewQuestionnairesJson; // Se limite à compter le nombre total de questionnaires et à le stocker pour éviter de lancer une requête sql à chaque fois const creaStatsQuestionnairesJson = async () => { const db = require("../models/index"); const Questionnaires=await db["Questionnaire"].findAll({ attributes: ["id"] }); const QuestionnairesPublished=await db["Questionnaire"].findAll({ where: { isPublished : true }, attributes: ["id"] }); if(Questionnaires && QuestionnairesPublished) { const stats = { nbTot : Questionnaires.length, nbPublished : QuestionnairesPublished.length } await toolFile.createJSON(config.dirCacheQuestionnaires, "stats", stats); return stats; } else return false; } exports.creaStatsQuestionnairesJson = creaStatsQuestionnairesJson; // Retourne les données créées par la fonction précédente const getStatsQuestionnaires = async () => { let stats=await toolFile.readJSON(config.dirCacheQuestionnaires, "stats"); if(!stats) stats=await creaStatsQuestionnairesJson(); if(!stats) return false; else return stats; } exports.getStatsQuestionnaires = getStatsQuestionnaires; // Quelle est la prochaine date pour laquelle aucun questionnaire n'a été publié ? const getDateNewQuestionnaireNeeded = async (maxDays) => { const db = require("../models/index"); let dateNeed="", checkDate, addMin=0, addMax=1; while(dateNeed==="") { checkDate = await db.sequelize.query("SELECT ADDDATE(CURDATE(), :addMin) as `dateNeeded` FROM `Questionnaires` WHERE `isPublished`=1 AND `publishingAt` > ADDDATE(CURDATE(), :addMin) AND `publishingAt`< ADDDATE(CURDATE(), :addMax) ORDER BY `publishingAt` LIMIT 1", { replacements: { addMin: addMin, addMax: addMax }, type: QueryTypes.SELECT }); if(checkDate && getSubscriptions24H.length>0) dateNeed=checkDate.dateNeeded; else { addMin++; addMax++; } if(addMin>maxDays) return false; } return dateNeed; } exports.getDateNewQuestionnaireNeeded = getDateNewQuestionnaireNeeded; // Les prochains questionnaires devant être publiés (permet aux gestionnaires de voir les dates vides) const getNextQuestionnaires = async () => { const db = require("../models/index"); const questionnaires= await db.sequelize.query("SELECT `id`,`title`, `publishingAt` as `datePublishing` FROM `Questionnaires` WHERE `publishingAt` > NOW() order by `publishingAt`", { type: QueryTypes.SELECT }); return questionnaires; } exports.getNextQuestionnaires = getNextQuestionnaires; const creaNewQuestionnairesHTML = async (Questionnaires) => { // On regénère la page d'accueil avec le(s) dernier(s) questionnaire(s) mis en avant : let compiledFunction = pug.compileFile("./views/"+config.theme+"/home.pug"); const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js"); let questionnaires=[]; for(let i in Questionnaires) questionnaires.push(await searchQuestionnaireById(Questionnaires[i].id)); let pageDatas= { config: config, configTpl: configTpl, tool: tool, striptags: striptags, txtGeneral : txtGeneral, txtQuestionnaire: txtQuestionnaire, txtIllustration: txtIllustration, pageLang: config.adminLang, metaDescription: txtGeneral.siteMetaDescription, pageTitle: txtGeneral.siteHTMLTitle, contentTitle: config.siteName, questionnaires: questionnaires, linkCanonical: config.siteUrl } let html=await compiledFunction(pageDatas); await toolFile.createHTML(config.dirHTML, "index", html); // + la page listant les X derniers quizs + liste des tags compiledFunction=pug.compileFile("./views/"+config.theme+"/newQuestionnaires.pug"); pageDatas.metaDescription=configTpl.newQuestionnairesIntro; pageDatas.pageTitle=configTpl.newQuestionnairesTitle; pageDatas.linkCanonical=config.siteUrl+"/"+configQuestionnaires.dirWebTags; pageDatas.tags=await tagCtrl.getUsedTags(); html=await compiledFunction(pageDatas); await toolFile.createHTML(configQuestionnaires.dirHTMLTags, "index", html); return true; } // À partir d'une liste d'id questionnaires fournis, retourne la liste complète ou partielle (pagination) avec les infos de chaque questionnaire // Retour en html ou json suivant les cas const getListingsQuestionnairesOuput = async (Questionnaires, begin=0, end=null, output="") => { const datas= { nbTot: Questionnaires.length, questionnaires: [], html: "" }; if(datas.nbTot!==0) { if(end===null) end=datas.nbTot; for (let i = begin; i <= end; i++) { if(Questionnaires[i]===undefined) break; else datas.questionnaires.push(await searchQuestionnaireById(Questionnaires[i].id)); } // Utiles pour savoir où j'en suis rendu dans le front : datas.begin=begin; datas.end=end; if(output==="html") { 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); datas.questionnaires=null;// allège un peu le contenu retourné... } } return datas; } exports.getListingsQuestionnairesOuput = getListingsQuestionnairesOuput;