874 lines
37 KiB
JavaScript
874 lines
37 KiB
JavaScript
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 associés à un groupe
|
||
// 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");
|
||
const getQuestionnaires = await db.sequelize.query("SELECT `id` FROM `Questionnaires` WHERE (`title` LIKE :search OR `keywords` LIKE :search) AND `isPublished` = 1 AND GroupId IS NOT NULL", { 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 associés à un groupe
|
||
// 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");
|
||
const getQuestionnaires=await db.sequelize.query("SELECT `id` FROM `Questionnaires` WHERE `isPublished` = 1 AND GroupId IS NOT NULL 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 groupes, puis aux tags qui font la même chose
|
||
exports.HTMLRegenerate= async (req, res, next) =>
|
||
{
|
||
try
|
||
{
|
||
await checkQuestionnairesNeedToBePublished();
|
||
const nb=await checkQuestionnairesPublishedHaveHTML(true);
|
||
let messageToNext=txtQuestionnaire.haveBeenRegenerated.replace("#NB1", nb);
|
||
creaNewQuestionnairesJson();// Provoque mise à jour du HTLM, etc.
|
||
// Les groupes sont aussi à regénérés :
|
||
const nbGroups=await groupCtrl.checkGroupsNeedToBePublished(true);
|
||
messageToNext=messageToNext.replace("#NB2", nbGroups);
|
||
res.messageToNext=messageToNext;// Adapter par le middleware suivant.
|
||
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épertoire 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);
|
||
}
|
||
}
|
||
|
||
// Teste si des questionnaires doivent être publiés + si des questionnaires publiés n'ont pas de fichier html
|
||
// Si au moins un fichier html est créé, il faut aussi actualiser la page d'accueil & co
|
||
exports.checkQuestionnairesNeedToBePublished = async (req, res, next) =>
|
||
{
|
||
try
|
||
{
|
||
console.log("Je teste si des questionnaires doivent être publiés.");
|
||
const nb = await checkQuestionnairesNeedToBePublished();// actualise bd, json, html, listes d'articles... si l'élement devient publiable.
|
||
//const nb=await checkQuestionnairesPublishedHaveHTML();
|
||
//if(nb > 0)
|
||
//creaNewQuestionnairesJson();// Provoque mise à jour du HTLM, etc.
|
||
res.status(200).json(txtQuestionnaire.haveBeenPublished.replace(":NB", nb));
|
||
console.log(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))
|
||
await groupCtrl.creaGroupJson(Questionnaire.GroupId);// actualise aussi le HTML du quiz + les fichiers html des autres éléments du groupe
|
||
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("<br>", " ").replace("</p>", " ")), 200),
|
||
author: questionnaire.Questionnaire.CreatorName,
|
||
pageTitle: questionnaire.Questionnaire.title+" ("+txtQuestionnaire.questionnairesName+")",
|
||
contentTitle: questionnaire.Questionnaire.title,
|
||
questionnaire: questionnaire,
|
||
linkCanonical: config.siteUrlProd+"/"+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("<br>", " ").replace("</p>", " ")), 200),
|
||
author: questionnaire.Questionnaire.CreatorName,
|
||
pageTitle: questionnaire.Questionnaire.title,
|
||
contentTitle: questionnaire.Questionnaire.title,
|
||
questionnaire: questionnaire,
|
||
group: groupInfos,
|
||
nextQuestionnaire: nextQuestionnaire,
|
||
linkCanonical: config.siteUrlProd+"/"+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");
|
||
let nb = 0;
|
||
const questionnaires=await db.sequelize.query("SELECT `id` FROM `Questionnaires` WHERE `publishingAt` < NOW() AND `isPublished` = false", { type: QueryTypes.SELECT });
|
||
for(let i in questionnaires)
|
||
{
|
||
const 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 la création du HTML en impactant celui du groupe, etc.
|
||
nb++;
|
||
}
|
||
}
|
||
return nb;
|
||
}
|
||
|
||
// 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, 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 non groupés 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.sequelize.query("SELECT DISTINCT `id` FROM `Questionnaires`", { type: QueryTypes.SELECT });
|
||
const QuestionnairesPublished=await db.sequelize.query("SELECT DISTINCT `id` FROM `Questionnaires` WHERE `isPublished`=1", { type: QueryTypes.SELECT });
|
||
const QuestionnairesWithoutGroup=await db.sequelize.query("SELECT DISTINCT `id` FROM `Questionnaires` WHERE `GroupId` IS NULL", { type: QueryTypes.SELECT });
|
||
const QuestionnairesWithoutGroupPublished=await db.sequelize.query("SELECT DISTINCT `id` FROM `Questionnaires` WHERE `GroupId` IS NULL AND `isPublished`=1", { type: QueryTypes.SELECT });
|
||
if(Questionnaires && QuestionnairesPublished)
|
||
{
|
||
const stats =
|
||
{
|
||
nbTot : Questionnaires.length,
|
||
nbPublished : QuestionnairesPublished.length,
|
||
nbTotWithoutGroup : QuestionnairesWithoutGroup.length,
|
||
nbWithoutGroupPublished : QuestionnairesWithoutGroupPublished.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");
|
||
const txtUser = require("../lang/"+config.adminLang+"/user");
|
||
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,
|
||
txtUser: txtUser,
|
||
pageLang: config.adminLang,
|
||
metaDescription: txtGeneral.siteMetaDescription,
|
||
pageTitle: txtGeneral.siteHTMLTitle,
|
||
contentTitle: config.siteName,
|
||
questionnaires: questionnaires,
|
||
linkCanonical: config.siteUrlProd
|
||
}
|
||
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.siteUrlProd+"/"+configQuestionnaires.dirWebNews;
|
||
html=await compiledFunction(pageDatas);
|
||
await toolFile.createHTML(configQuestionnaires.dirHTMLNews, "index", html);
|
||
// + le flux ATOM
|
||
compiledFunction=pug.compileFile("./views/"+config.theme+"/atom.pug");
|
||
const xml=await compiledFunction(pageDatas);
|
||
await toolFile.createXML(config.dirHTML, "atom", xml);
|
||
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; |