444 lines
15 KiB
JavaScript
444 lines
15 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 tool = require("../tools/main");
|
|
const toolError = require("../tools/error");
|
|
const toolFile = require("../tools/file");
|
|
|
|
const questionnaireCtrl = require("./questionnaire");
|
|
const userCtrl = require("./user");
|
|
|
|
const txtGeneral = require("../lang/"+config.adminLang+"/general");
|
|
const txtGroups = require("../lang/"+config.adminLang+"/group");
|
|
|
|
exports.create = async (req, res, next) =>
|
|
{
|
|
try
|
|
{
|
|
const db = require("../models/index");
|
|
req.body.CreatorId=req.connectedUser.User.id;
|
|
const group=await db["Group"].create({ ...req.body }, { fields: ["title", "slug", "introduction", "publishingAt", "language", "CreatorId"] });
|
|
creaGroupJson(group.id);
|
|
res.status(201).json({ message: txtGeneral.addedOkMessage, id: group.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 group=await searchGroupById(req.params.id);
|
|
if(!group)
|
|
{
|
|
const Err=new Error;
|
|
error.status=404;
|
|
error.message=txtGroups.notFound.replace("#SEARCH", req.params.id);
|
|
throw Err;
|
|
}
|
|
else if(req.connectedUser.User.status==="creator" && req.connectedUser.User.id!==group.CreatorId)
|
|
res.status(401).json({ errors: txtGeneral.notAllowed });
|
|
else
|
|
await db["Group"].update({ ...req.body }, { where: { id : req.params.id } , fields: ["title", "slug", "introduction", "publishingAt", "language"], limit:1 });
|
|
await creaGroupJson(req.params.id);
|
|
res.status(200).json({ message: txtGeneral.updateOkMessage });
|
|
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 db = require("../models/index");
|
|
const group=await searchGroupById(req.params.id);
|
|
if(!group)
|
|
{
|
|
const Err=new Error;
|
|
error.status=404;
|
|
error.message=txtGroups.notFound.replace("#SEARCH", req.params.id);
|
|
throw Err;
|
|
}
|
|
else if(req.connectedUser.User.status==="creator" && req.connectedUser.User.id!==group.CreatorId)
|
|
res.status(401).json({ errors: txtGeneral.notAllowed });
|
|
else
|
|
{
|
|
// La suppression sera bloquée par SQL si des quizs dépendent de ce groupe
|
|
// Donc il faut d'abord supprimer tous les quizs du groupe :
|
|
for(let i in group.Questionnaires)
|
|
await questionnaireCtrl.deleteQuestionnaireById(group.Questionnaires[i].id);
|
|
const nb=await db["Group"].destroy( { where: { id : req.params.id }, limit:1 });
|
|
if(nb===1)
|
|
{
|
|
toolFile.deleteJSON(configQuestionnaires.dirCacheGroups, req.params.id);
|
|
toolFile.deleteFile(configQuestionnaires.dirHTMLGroups, group.Group.slug+".html");
|
|
creaStatsGroupsJson();
|
|
res.status(200).json({ message: txtGeneral.deleteOkMessage });
|
|
}
|
|
else
|
|
{
|
|
const Err=new Error;
|
|
error.status=404;
|
|
error.message=txtGroups.deleteFailMessage.replace("#ID", req.params.id);
|
|
throw Err;
|
|
}
|
|
}
|
|
next();
|
|
}
|
|
catch(e)
|
|
{
|
|
next(e);
|
|
}
|
|
}
|
|
|
|
// Recherche par mots-clés parmis tous les groupes (y compris ceux non publiés).
|
|
// Le rank le + élevé des éléments déjà enregistrés dans le groupe permet de placer le nouveau.
|
|
exports.searchGroups = async (req, res, next) =>
|
|
{
|
|
try
|
|
{
|
|
let search=tool.trimIfNotNull(req.body.searchGroups);
|
|
if(search === null || search === "" || search.length < configQuestionnaires.searchGroups.minlength)
|
|
res.status(400).json(txtGroups.searchIsNotLongEnough.replace("#MIN", configQuestionnaires.searchGroups.minlength));
|
|
else
|
|
{
|
|
const db = require("../models/index");
|
|
const getGroups=await db.sequelize.query("SELECT `Groups`.`id`,`Groups`.`title`, MAX(`Questionnaires`.`rankInGroup`) as maxRank FROM `Groups` LEFT JOIN `Questionnaires` ON `Questionnaires`.`GroupId` = `Groups`.`id` WHERE `Groups`.`title` LIKE :search OR `Groups`.`introduction` LIKE :search GROUP BY `Groups`.`id` ORDER BY `Groups`.`title` ASC", { replacements: { search: "%"+search+"%" }, type: QueryTypes.SELECT });
|
|
res.status(200).json(getGroups);
|
|
}
|
|
next();
|
|
}
|
|
catch(e)
|
|
{
|
|
next(e);
|
|
}
|
|
}
|
|
|
|
exports.getOneById = async (req, res, next) =>
|
|
{
|
|
try
|
|
{
|
|
const datas=await searchGroupById(req.params.id, true);
|
|
if(datas)
|
|
res.status(200).json(datas);
|
|
else
|
|
res.status(404).json({ message:txtGroups.notFound.replace("#SEARCH", req.params.id) });
|
|
next();
|
|
}
|
|
catch(e)
|
|
{
|
|
next(e);
|
|
}
|
|
}
|
|
|
|
exports.showOneGroupById = async (req, res, next) =>
|
|
{
|
|
try
|
|
{
|
|
// Seuls certains utilisateurs peuvent avoir accès à cette page
|
|
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 creaGroupHTML(req.params.id, true);
|
|
if(HTML)
|
|
{
|
|
res.setHeader("Content-Type", "text/html");
|
|
res.send(HTML);
|
|
}
|
|
else
|
|
res.status(404).json({ errors:txtGeneral.notFound.replace("#SEARCH", req.params.id) });
|
|
}
|
|
}
|
|
next();
|
|
}
|
|
catch(e)
|
|
{
|
|
next(e);
|
|
}
|
|
}
|
|
|
|
// Retourne les statistiques concernant les groupes de questionnaires
|
|
exports.getStats = async(req, res, next) =>
|
|
{
|
|
try
|
|
{
|
|
const stats=await getStatsGroups();
|
|
res.status(200).json(stats);
|
|
}
|
|
catch(e)
|
|
{
|
|
next(e);
|
|
}
|
|
}
|
|
|
|
// (Re)génère tous les fichiers HTML des groupes
|
|
// La requête est ensuite passé aux tags qui font la même chose
|
|
exports.HTMLRegenerate= async (req, res, next) =>
|
|
{
|
|
try
|
|
{
|
|
const nb=await checkGroupsNeedToBePublished(true);
|
|
res.messageToNext=txtGroups.haveBeenPublished.replace("#NB", nb);
|
|
next();
|
|
}
|
|
catch(e)
|
|
{
|
|
next(e);
|
|
}
|
|
}
|
|
|
|
|
|
// CRONS
|
|
|
|
// Supprime fichiers json de groupes n'existant plus.
|
|
exports.cronDeleteJsonFiles = async (req, res, next) =>
|
|
{
|
|
try
|
|
{
|
|
const db = require("../models/index");
|
|
const groups=await db["Group"].findAll({ attributes: ["id"] });
|
|
let saveFiles=["stats.json"];// dans le même répertoire et à garder.
|
|
for(let i in groups)
|
|
saveFiles.push(groups[i].id+".json");
|
|
const deleteFiles = await toolFile.deleteFilesInDirectory(configQuestionnaires.dirCacheGroups, saveFiles);
|
|
res.status(200).json(deleteFiles);
|
|
next();
|
|
}
|
|
catch(e)
|
|
{
|
|
next(e);
|
|
}
|
|
}
|
|
|
|
// Teste si des groupes doivent être publiés
|
|
exports.cronCheckGroupsNeedToBePublished = async (req, res, next) =>
|
|
{
|
|
try
|
|
{
|
|
console.log("Je teste si des groupes doivent être publiés");
|
|
const nb=await checkGroupsNeedToBePublished();
|
|
res.status(200).json(txtGroups.haveBeenPublished.replace("#NB", nb));
|
|
console.log(txtGroups.haveBeenPublished.replace("#NB", nb));
|
|
next();
|
|
}
|
|
catch(e)
|
|
{
|
|
next(e);
|
|
}
|
|
}
|
|
|
|
|
|
// FONCTIONS PARTAGÉES
|
|
|
|
const creaGroupJson = async (id) =>
|
|
{
|
|
const db = require("../models/index");
|
|
const Group=await db["Group"].findByPk(id);
|
|
if(Group)
|
|
{
|
|
let datas={ Group };
|
|
const Questionnaires=await db["Questionnaire"].findAll({ where: { GroupId: Group.id, isPublished : true }, order: [["rankInGroup", "ASC"], ["createdAt", "ASC"]], attributes: ["id"] });
|
|
if(Questionnaires)
|
|
datas.Questionnaires=Questionnaires;
|
|
await toolFile.createJSON(configQuestionnaires.dirCacheGroups, id, datas);
|
|
// Si le groupe est publiable on génère/actualise la page HTML :
|
|
if(checkGroupIsPublishable(datas))
|
|
await creaGroupHTML(id);// !!! await important car sinon bug, plusieurs fonctions accédant au même fichier (à revoir car pas clair !)
|
|
else // dans le cas contraire, on supprime l'éventuel fichier préexistant
|
|
toolFile.deleteFile(configQuestionnaires.dirHTMLGroups, Group.slug+".html");
|
|
// Dans certains cas les fichiers HTML des quizs du groupe peuvent aussi être impactés (notamment le dernier)
|
|
for(let i in Questionnaires)
|
|
await questionnaireCtrl.creaQuestionnaireHTML(Questionnaires[i].id, false);
|
|
// + mise à jour des statistiques :
|
|
creaStatsGroupsJson();
|
|
return datas;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
exports.creaGroupJson = creaGroupJson;
|
|
|
|
const checkGroupIsPublishable = (datas, checkDate=true) =>
|
|
{
|
|
if(checkDate)
|
|
{
|
|
if(datas.Group.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.Group.publishingAt);
|
|
if (publishingAt.getTime() > today.getTime())
|
|
return false;
|
|
}
|
|
}
|
|
if(datas.Questionnaires === undefined || datas.Questionnaires.length < config.nbQuestionnairesByGroupMin)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
const creaGroupHTML = async (id, preview = false) =>
|
|
{
|
|
const group=await searchGroupById(id, true);
|
|
if(!group)
|
|
return false;
|
|
if(group.isPublishable === false && preview === false)
|
|
return false;
|
|
const txtIllustration = require("../lang/"+config.adminLang+"/illustration");
|
|
const txtQuestionnaire = require("../lang/"+config.adminLang+"/questionnaire");
|
|
const compiledFunction = pug.compileFile("./views/"+config.theme+"/quiz-group.pug");
|
|
const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js");
|
|
const pageDatas =
|
|
{
|
|
config: config,
|
|
configQuestionnaires: configQuestionnaires,
|
|
configTpl: configTpl,
|
|
tool: tool,
|
|
txtGeneral: txtGeneral,
|
|
txtGroups: txtGroups,
|
|
txtIllustration: txtIllustration,
|
|
txtQuestionnaire: txtQuestionnaire,
|
|
pageLang: group.Group.language,
|
|
metaDescription: tool.shortenIfLongerThan(config.siteName+" : "+striptags(group.Group.introduction.replace("<br>", " ").replace("</p>", " ")), 200),
|
|
author: group.Group.CreatorName,
|
|
pageTitle: txtGroups.groupsName+" "+group.Group.title,
|
|
contentTitle: group.Group.title+"("+txtGroups.groupsName+")",
|
|
group: group,
|
|
linkCanonical: config.siteUrlProd+"/"+configQuestionnaires.dirWebGroups+"/"+group.Group.slug+".html"
|
|
}
|
|
const html=await compiledFunction(pageDatas);
|
|
if(preview === false)
|
|
{
|
|
await toolFile.createHTML(configQuestionnaires.dirHTMLGroups, group.Group.slug, html);
|
|
return true;
|
|
}
|
|
else
|
|
return html;
|
|
}
|
|
|
|
// Remonte toutes les données du groupe + les données des questionnaires y étant classés si reassemble=true
|
|
const searchGroupById = async (id, reassemble=false) =>
|
|
{
|
|
let group=await toolFile.readJSON(configQuestionnaires.dirCacheGroups, id);
|
|
if(!group)
|
|
group=await creaGroupJson(id);
|
|
if(!group)
|
|
return false;
|
|
group.Group.isPublishable=checkGroupIsPublishable(group);
|
|
if(reassemble)
|
|
{
|
|
let questionnaire; Questionnaires=[];
|
|
const author=await userCtrl.searchUserById(group.Group.CreatorId);
|
|
if(author)
|
|
group.Group.CreatorName=author.User.name;
|
|
for(let i in group.Questionnaires)
|
|
{
|
|
questionnaire=await questionnaireCtrl.searchQuestionnaireById(group.Questionnaires[i].id, true);
|
|
if(questionnaire)
|
|
Questionnaires.push(questionnaire);
|
|
}
|
|
group.Questionnaires=Questionnaires;
|
|
}
|
|
return group;
|
|
}
|
|
exports.searchGroupById = searchGroupById;
|
|
|
|
// Cherche si il y a des groupes de questionnaires dont la date de publication est passée, mais qui ne sont pas publiés
|
|
// Vérifie si ils sont publiables et si oui génère le HTML
|
|
// Si regenerate=true, tous les fichiers sont (ré)générés, même s'ils existent déjà (évolution template...)
|
|
// Retourne le nombre de fichiers ayant été (ré)générés
|
|
const checkGroupsNeedToBePublished = async (regenerate=false) =>
|
|
{
|
|
const db = require("../models/index");
|
|
const groups = await db.sequelize.query("SELECT `id`,`slug` FROM `Groups` WHERE `publishingAt` < NOW()", { type: QueryTypes.SELECT });
|
|
let nb = 0;
|
|
for(let i in groups)
|
|
{
|
|
if(regenerate === false)
|
|
{
|
|
if(await toolFile.checkIfFileExist(configQuestionnaires.dirHTMLGroups, groups[i].slug+".html") === false)
|
|
{
|
|
if(await creaGroupJson(groups[i].id))// permet aussi de mettre à jour les fichiers des quizs du groupe avec lien vers le groupe
|
|
nb++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const publishedOk = await creaGroupHTML(groups[i].id);// creaGroupHTLM contrôle que le groupe est publiable.
|
|
if(publishedOk)
|
|
nb++;
|
|
}
|
|
}
|
|
return nb;
|
|
}
|
|
exports.checkGroupsNeedToBePublished = checkGroupsNeedToBePublished;
|
|
|
|
// Compte le nombre total de groupes et le stocke
|
|
const creaStatsGroupsJson = async () =>
|
|
{
|
|
const db = require("../models/index");
|
|
const Groups=await db["Group"].findAll({ attributes: ["id"] });
|
|
const GroupsPublished=await db.sequelize.query("SELECT `id` FROM `Groups` WHERE `publishingAt` < NOW()", { type: QueryTypes.SELECT });
|
|
const QuestionnairesInGroups=await db.sequelize.query("SELECT DISTINCT `id` FROM `Questionnaires` WHERE `GroupId` IS NOT NULL", { type: QueryTypes.SELECT });
|
|
if(Groups && GroupsPublished && QuestionnairesInGroups)
|
|
{
|
|
const stats =
|
|
{
|
|
nbTot : Groups.length,
|
|
nbPublished : GroupsPublished.length, // ! en fait, peuvent être passé de date et non publié
|
|
nbQuestionnaires : QuestionnairesInGroups.length
|
|
}
|
|
await toolFile.createJSON(configQuestionnaires.dirCacheGroups, "stats", stats);
|
|
return stats;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
exports.creaStatsGroupsJson = creaStatsGroupsJson;
|
|
|
|
// Retourne les données créées par la fonction précédente
|
|
const getStatsGroups = async () =>
|
|
{
|
|
let stats=await toolFile.readJSON(configQuestionnaires.dirCacheGroups, "stats");
|
|
if(!stats)
|
|
stats=await creaStatsGroupsJson();
|
|
if(!stats)
|
|
return false;
|
|
else
|
|
return stats;
|
|
}
|
|
exports.getStatsGroups = getStatsGroups; |