WikiLerni/controllers/tag.js

346 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(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 (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;
}
await ctrlQuestionnaire.creaQuestionnaireJson(req.body.QuestionnaireId);// attendre avant de répondre pour que cela soit pris en compte au réaffichage.
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
res.status(200).json({ message: res.messageToNext.replace("#NB2", 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é
/// Ajouter l'actualisation de la page "parcourir" du site
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,
configTpl: configTpl,
tool: tool,
pageLang: config.adminLang,
metaDescription: configTpl.tagListMetaDesc,
pageTitle: configTpl.tagListTitle,
contentTitle: config.siteName,
tags: tags,
linkCanonical: config.siteUrl+"/"+configQuestionnaire.dirWebTags
}
html=await compiledFunction(pageDatas);
await toolFile.createHTML(configQuestionnaire.dirHTMLTags, "index", 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);// le supprimer si liste vide ?
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,
pageLang: config.adminLang,
metaDescription: config.siteName+" : "+txtTag.tagMetaDescription+tag.name,
pageTitle: config.siteName+" - "+tag.name,
contentTitle: config.siteName+" - "+tag.name,
tagInfos: tag,
linkCanonical: config.siteUrl+"/"+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;
}