356 lines
13 KiB
JavaScript
356 lines
13 KiB
JavaScript
const { Op, QueryTypes } = require("sequelize");// pour certaines requêtes sql
|
|
|
|
const pug = require("pug");
|
|
const striptags = require("striptags");
|
|
|
|
const config = require("../config/main.js");
|
|
const configQuestionnaire = require("../config/questionnaires.js");
|
|
|
|
const ctrlQuestionnaire = require("./questionnaire");
|
|
|
|
const tool = require("../tools/main");
|
|
const toolError = require("../tools/error");
|
|
const toolFile = require("../tools/file");
|
|
|
|
const txtGeneral = require("../lang/"+config.adminLang+"/general");
|
|
const txtIllustration = require("../lang/"+config.adminLang+"/illustration");
|
|
const txtQuestionnaire = require("../lang/"+config.adminLang+"/questionnaire");
|
|
const txtTag = require("../lang/"+config.adminLang+"/tag");
|
|
|
|
// Actualise le classement d'un questionnaire suivant les tags envoyés
|
|
exports.checkTags = async (req, res, next) =>
|
|
{
|
|
try
|
|
{
|
|
if(req.body.QuestionnaireId==undefined)
|
|
throw { message: txtTag.neededParams };
|
|
|
|
const tagsCurrent=await getUsedTagsQuestionnaire(req.body.QuestionnaireId);
|
|
if(tagsCurrent === false)
|
|
throw { message: txtTag.tagsForQuestionnaireNotFound };
|
|
|
|
const tagsReceived=req.body.classification.split(",");// ! peut être vide si pas/plus de classement souhaité
|
|
for(let i in tagsReceived)
|
|
tagsReceived[i]=tagsReceived[i].trim().toLowerCase();// ! gestion de la casse différente pour JS, pas pour Mysql
|
|
|
|
// les tags jusqu'ici associés sont-ils toujours utilisés ?
|
|
let deleteLink;
|
|
for (let i in tagsCurrent)
|
|
{
|
|
if(tagsReceived.indexOf(tagsCurrent[i].name.toLowerCase()) === -1)
|
|
deleteLink=await unlinkTagQuestionnaire(tagsCurrent[i].id, req.body.QuestionnaireId);
|
|
}
|
|
|
|
// parmis les tags envoyés, certains sont-ils nouveaux pour ce questionnaire ?
|
|
let findTag=false, creaLink;
|
|
for(i in tagsReceived)
|
|
{
|
|
for(j in tagsCurrent)
|
|
{
|
|
if(tagsCurrent[j].name.toLowerCase()===tagsReceived[i])
|
|
{
|
|
findTag=true;
|
|
break;
|
|
}
|
|
}
|
|
if(!findTag && tagsReceived[i]!=="") // revoir : remettre les majuscules lors de l'enregistrement ?
|
|
{
|
|
creaLink=await linkTagQuestionnaire(tagsReceived[i], req.body.QuestionnaireId);
|
|
if(creaLink!==true)
|
|
throw creaLink;// nécessaire pour retourner les erreurs du modèle (mais uniquement "tag trop long" possible)
|
|
}
|
|
findTag=false;
|
|
}
|
|
|
|
// normalement il faut attendre avant de répondre pour que cela soit pris en compte au réaffichage.
|
|
await ctrlQuestionnaire.creaQuestionnaireJson(req.body.QuestionnaireId);
|
|
|
|
if(req.method == "PUT")
|
|
res.status(200).json({ message: txtGeneral.updateOkMessage });
|
|
else if(req.method == "POST")
|
|
res.status(201).json({ message: txtGeneral.addOkMessage, id:req.body.QuestionnaireId });
|
|
next();
|
|
}
|
|
catch(e)
|
|
{
|
|
const returnAPI=toolError.returnSequelize(e);
|
|
if(returnAPI.messages)
|
|
{
|
|
res.status(returnAPI.status).json({ errors : returnAPI.messages });
|
|
next();
|
|
}
|
|
else
|
|
next(e);
|
|
}
|
|
}
|
|
|
|
// Retourne la liste des tags déjà connus débutant par l'expression donnée :
|
|
exports.getTagsBeginningBy = async (req, res, next) =>
|
|
{
|
|
try
|
|
{
|
|
let tags=await getAllTags(), tagsOk=[], search=req.body.search.trim().toLowerCase();
|
|
if(search.length >= 2)
|
|
{
|
|
for(let i in tags)
|
|
{
|
|
|
|
let item=tags[i].name.toLowerCase();// il reste le problème des accents
|
|
if(item.startsWith(search))
|
|
tagsOk.push(tags[i]);
|
|
}
|
|
}
|
|
res.status(200).json(tagsOk);
|
|
next();
|
|
}
|
|
catch(e)
|
|
{
|
|
next(e);
|
|
}
|
|
}
|
|
|
|
// Fais suite à la même fonction dans questionnaire.js
|
|
// Va récupérer la liste des tags utilisés pour classer au moins un questionnaire publié
|
|
// Puis regénère les fichiers HTML pour chacun de ces tags
|
|
exports.HTMLRegenerate= async (req, res, next) =>
|
|
{
|
|
try
|
|
{
|
|
const tagsUsed=await getUsedTags();
|
|
for(let i in tagsUsed)
|
|
await creaQuestionnairesTagJson(tagsUsed[i].id);// Provoque la regénération du json + du html.
|
|
// + Page listant tous les tags actifs :
|
|
creaUsedTagsHTML(tagsUsed);
|
|
res.status(200).json({ message: res.messageToNext.replace("#NB3", tagsUsed.length) });
|
|
res.messageToNext=null;
|
|
next();
|
|
}
|
|
catch(e)
|
|
{
|
|
next(e);
|
|
}
|
|
}
|
|
|
|
// UTILITAIRES
|
|
|
|
// Créer la liste de tous les tags présents dans la base de données
|
|
const creaAllTagsJson = async () =>
|
|
{
|
|
const db = require("../models/index");
|
|
const tags=await db["Tag"].findAll();
|
|
await toolFile.createJSON(configQuestionnaire.dirCacheTags, "all", tags);
|
|
return tags;
|
|
}
|
|
|
|
// Retourne la liste de tous les tags
|
|
const getAllTags = async () =>
|
|
{
|
|
const tags=await toolFile.readJSON(configQuestionnaire.dirCacheTags, "all");
|
|
if(tags)
|
|
return tags;
|
|
else
|
|
return await creaAllTagsJson();
|
|
}
|
|
|
|
// Créer la liste de tous tags utilisés pour classer au moins un quiz publié
|
|
const creaUsedTagsJson = async () =>
|
|
{
|
|
const db = require("../models/index");
|
|
const tags = await db.sequelize.query("SELECT DISTINCT `Tags`.`id`,`Tags`.`name`,`Tags`.`slug` FROM `Tags` INNER JOIN `QuestionnaireClassifications` INNER JOIN `Questionnaires` WHERE `Tags`.`id`=`QuestionnaireClassifications`.`TagId` AND `QuestionnaireClassifications`.`QuestionnaireId`=`Questionnaires`.`id` AND `Questionnaires`.`isPublished`=true ORDER BY `name` ASC", { type: QueryTypes.SELECT , model: db["Tag"], mapToModel: true });
|
|
await toolFile.createJSON(configQuestionnaire.dirCacheTags, "used", tags);
|
|
if(tags.length !== 0)
|
|
creaUsedTagsHTML(tags);
|
|
return tags;
|
|
}
|
|
exports.creaUsedTagsJson = creaUsedTagsJson;// utile pour actualiser en cas de publication/dépublication d'un quiz
|
|
|
|
// Créer la page HTML listant tous les tags
|
|
const creaUsedTagsHTML = async (tags) =>
|
|
{
|
|
// + la page listant les X derniers quizs + liste des tags
|
|
const compiledFunction=pug.compileFile("./views/"+config.theme+"/tagsList.pug");
|
|
const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js");
|
|
let pageDatas =
|
|
{
|
|
config: config,
|
|
configQuestionnaire: configQuestionnaire,
|
|
configTpl: configTpl,
|
|
tool: tool,
|
|
pageLang: config.adminLang,
|
|
metaDescription: configTpl.tagListMetaDesc,
|
|
pageTitle: configTpl.tagListTitle,
|
|
contentTitle: config.siteName,
|
|
tags: tags,
|
|
linkCanonical: config.siteUrlProd+"/"+configQuestionnaire.dirWebTags
|
|
}
|
|
html=await compiledFunction(pageDatas);
|
|
await toolFile.createHTML(configQuestionnaire.dirHTMLTags, "themes", html);
|
|
return true;
|
|
}
|
|
|
|
// Retourne la liste des tags utilisés
|
|
const getUsedTags = async () =>
|
|
{
|
|
const tags=await toolFile.readJSON(configQuestionnaire.dirCacheTags, "used");
|
|
if(tags)
|
|
return tags;
|
|
else
|
|
return await creaUsedTagsJson();
|
|
}
|
|
exports.getUsedTags = getUsedTags;
|
|
|
|
// Retourne la liste des tags d'un questionnaire avec leurs noms, slugs, etc.
|
|
const getUsedTagsQuestionnaire = async (id) =>
|
|
{
|
|
id=tool.trimIfNotNull(id);
|
|
if(id === null)
|
|
return false;
|
|
const questionnaire=await ctrlQuestionnaire.searchQuestionnaireById(id);
|
|
if(!questionnaire)
|
|
throw { message: txtQuestionnaire.notFound };
|
|
else
|
|
{
|
|
const allTags=await getAllTags();
|
|
let tagsQuestionnaire=[];
|
|
for(let i in questionnaire.Tags)
|
|
{
|
|
for(j in allTags)
|
|
{
|
|
if(allTags[j].id === questionnaire.Tags[i].TagId)
|
|
{
|
|
tagsQuestionnaire.push(allTags[j]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return tagsQuestionnaire;
|
|
}
|
|
}
|
|
exports.getTagsQuestionnaire = getUsedTagsQuestionnaire;
|
|
|
|
// Créer la liste complète des questionnaires publiés, classés par un tag
|
|
const creaQuestionnairesTagJson = async (id) =>
|
|
{
|
|
id=tool.trimIfNotNull(id);
|
|
if(id === null)
|
|
return false;
|
|
const db = require("../models/index");
|
|
const questionnaires = await db.sequelize.query("SELECT id FROM `Questionnaires` INNER JOIN `QuestionnaireClassifications` ON `Questionnaires`.`id`=`QuestionnaireClassifications`.`QuestionnaireId` AND `Questionnaires`.`isPublished`=1 AND `QuestionnaireClassifications`.`TagId`=:id ORDER BY "+config.fieldNewQuestionnaires+" DESC", { replacements: { id: id }, type: QueryTypes.SELECT , model: db["Questionnaire"], mapToModel: true });
|
|
await toolFile.createJSON(configQuestionnaire.dirCacheTags, "liste-"+id, questionnaires);
|
|
creaUsedTagsJson();
|
|
creaQuestionnairesTagHTML(id, questionnaires);// pas await, car potentiellement long !
|
|
return questionnaires;
|
|
}
|
|
exports.creaQuestionnairesTagJson = creaQuestionnairesTagJson;
|
|
|
|
const creaQuestionnairesTagHTML = async (id, Questionnaires) =>
|
|
{
|
|
id=tool.trimIfNotNull(id);
|
|
if(id === null || Questionnaires === null)
|
|
return false;
|
|
const tag=await searchTagById(id);
|
|
if(Questionnaires.length === 0)
|
|
{ // plus aucun quiz classé ici. Il faudrait idéalement envoyer des erreurs 404/410, etc.
|
|
// revoir aussi l'intérêt ou non de supprimer les pages suivantes, sachant qu'elles ne sont pas indexables ? ou les rendre dynamiques ?
|
|
await toolFile.deleteFile(configQuestionnaire.dirHTMLTags, tag.slug+".html");
|
|
return true;
|
|
}
|
|
const compiledFunction = pug.compileFile("./views/"+config.theme+"/tag.pug");
|
|
const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js");
|
|
const pageDatas =
|
|
{
|
|
config: config,
|
|
configTpl: configTpl,
|
|
tool: tool,
|
|
striptags: striptags,
|
|
txtGeneral : txtGeneral,
|
|
txtQuestionnaire: txtQuestionnaire,
|
|
txtIllustration: txtIllustration,
|
|
txtTag: txtTag,
|
|
pageLang: config.adminLang,
|
|
metaDescription: config.siteName+" - "+txtTag.tagMetaDescription+tag.name,
|
|
pageTitle: config.siteName+" - "+tag.name,
|
|
contentTitle: config.siteName+" : "+txtTag.tagMetaDescription+tag.name,
|
|
tagInfos: tag,
|
|
linkCanonical: config.siteUrlProd+"/"+configQuestionnaire.dirWebTags+"/"+tag.slug+".html"
|
|
}
|
|
const nbPages=Math.ceil(Questionnaires.length / configTpl.maxQuestionnairesByPage);
|
|
pageDatas.nbPages=nbPages;
|
|
let debut=0;
|
|
for (let i = 1; i <= nbPages; i++)
|
|
{
|
|
const questionnairesPage = Questionnaires.slice(debut, debut+configTpl.maxQuestionnairesByPage), questionnairesInfos=[];
|
|
for(let j in questionnairesPage)
|
|
questionnairesInfos.push(await ctrlQuestionnaire.searchQuestionnaireById(questionnairesPage[j].id));
|
|
pageDatas.questionnaires=questionnairesInfos;
|
|
pageDatas.page=i;
|
|
let url=tag.slug;
|
|
if(i!==1)
|
|
{
|
|
url=url+"-"+i;
|
|
pageDatas.metaRobots="noindex,follow";
|
|
pageDatas.linkCanonical=null;
|
|
}
|
|
const html=await compiledFunction(pageDatas);
|
|
await toolFile.createHTML(configQuestionnaire.dirHTMLTags, url, html);
|
|
debut+=configTpl.maxQuestionnairesByPage;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Retourne un tag, si il existe
|
|
const searchTagByName = async (name) =>
|
|
{
|
|
name=tool.trimIfNotNull(name);
|
|
if(name===null)
|
|
return false;
|
|
const db = require("../models/index");
|
|
const tag=await db["Tag"].findOne({ where: { name: name } }); // utile de faire en sql pour les problèmes de majuscules / minuscules
|
|
if(tag)
|
|
return tag;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
const searchTagById = async (id) =>
|
|
{
|
|
id=tool.trimIfNotNull(id);
|
|
if(id===null)
|
|
return false;
|
|
const db = require("../models/index");
|
|
const tag=await db["Tag"].findByPk(id);
|
|
if(tag)
|
|
return tag;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
// Supprime l'association entre un tag et un questionnaire + actualise la liste des questionnaires pour le tag concerné
|
|
// La mise à jour du json/HTML du questionnaire est appelée par le contrôleur appelant cette fonction
|
|
const unlinkTagQuestionnaire = async (tagId, questionnaireId) =>
|
|
{
|
|
const db = require("../models/index");
|
|
await db["QuestionnaireClassification"].destroy( { where: { TagId: tagId, QuestionnaireId : questionnaireId }, limit:1 });
|
|
creaQuestionnairesTagJson(tagId);// peut être lent !
|
|
return true;
|
|
}
|
|
|
|
// Créer l'association entre un tag et un questionnaire + actualise la liste des questionnaires / tag
|
|
// La mise à jour du json/HTML du questionnaire est appelée par le contrôleur appelant cette fonction
|
|
const linkTagQuestionnaire = async (tagName, questionnaireId) =>
|
|
{
|
|
const db = require("../models/index");
|
|
// ce tag est-il nouveau ?
|
|
let tagLinked=await searchTagByName(tagName);
|
|
if(tagLinked === false)
|
|
{
|
|
tagLinked=await db["Tag"].create({ name: tagName, slug: null }, { fields: ["name", "slug"] });// "slug : null" pour laisser le modèle le générer
|
|
creaAllTagsJson();
|
|
}
|
|
if(tagLinked)
|
|
{
|
|
await db["QuestionnaireClassification"].create({ TagId: tagLinked.id, QuestionnaireId: questionnaireId });
|
|
creaQuestionnairesTagJson(tagLinked.id);// peut être lent !
|
|
}
|
|
return true;
|
|
} |