WikiLerni/controllers/group.js

444 lines
15 KiB
JavaScript
Raw Normal View History

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;