WikiLerni/controllers/user.js

1079 lines
46 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const slugify = require('slugify');
const { Op, QueryTypes } = require("sequelize");// pour certaines requêtes sql
const config = require("../config/main.js");
const configUsers = require("../config/users.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 txt = require("../lang/"+config.adminLang+"/user");
const txtGeneral = require("../lang/"+config.adminLang+"/general");
const answerCtrl = require("./answer");
const subscriptionCtrl = require("./subscription");
// Retourne certaines configurations utiles aux formulaires où vues
exports.checkEmailIsFree = async (req, res, next) =>
{
try
{
const user=await searchUserByEmail(req.body.emailTest);
const response={ };
if(user)
{
response.free=false;
if(user.Subscription)
response.validated=true;
else
response.validated=false;
}
else
response.free=true;
res.status(200).json(response);
next();
}
catch(e)
{
next(e);
}
}
exports.getGodfatherId = async (req, res, next) =>
{
try
{
const godfather=await searchIdGodfather(req.body.codeTest);
if(godfather)
res.status(200).json(godfather.id);
else
res.status(204).json({ errors: txt.godfatherNotFound });
next();
}
catch(e)
{
next(e);
}
}
exports.signup = async (req, res, next) =>
{
try
{
const db = require("../models/index");
if(req.body.cguOk !== "true")
res.status(400).json({ errors: [txt.needUGCOk] });
else if(req.body.password.length < config.passwordMinLength) // si le champ est supprimé du formulaire d'inscription, générer un mot de passe ok ici
res.status(400).json({ errors: [txt.needLongPassWord.replace("MIN_LENGTH", config.passwordMinLength)] });
else
{
req.body.GodfatherId=null;
if(req.body.codeGodfather!=="")
{
const godfather=await searchIdGodfather(req.body.codeGodfather);
if(godfather)
req.body.GodfatherId=godfather.id;
}
req.body.password=await bcrypt.hash(req.body.password, config.bcryptSaltRounds);
const user=await db["User"].create({ ...req.body }, { fields: ["name", "email", "password", "newsletterOk", "GodfatherId", "timeDifference"] });
req.body.UserId=user.id;
// si l'utilisateur a répondu à un quiz avant de créer son compte, on enregistre son résultats.
if(req.body.QuestionnaireId)
{
await Promise.all([
db["QuestionnaireAccess"].create({ ...req.body }, { fields: ["QuestionnaireId", "UserId"] }),
db["Answer"].create({ ...req.body }, { fields: ["nbQuestions", "nbCorrectAnswers", "duration", "QuestionnaireId", "UserId"] })
]); // pas nécessaire ici de créer le cache JSON, car il sera créé lors de la première connexion au compte.
}
await sendValidationLink(user);
res.status(201).json({ message: txt.mailValidationMessage });
next();
}
}
catch(e)
{
const returnAPI=toolError.returnSequelize(e);
if(returnAPI.messages)
{
res.status(returnAPI.status).json({ errors : returnAPI.messages });
next();
}
else
next(e);
}
}
exports.signupValidation = async (req, res, next) =>
{
try
{
const db = require("../models/index");
const datas=await checkTokenUser(req.params.token);
if(datas)
{
if(datas.Subscription)
res.status(200).json({ errors: [txt.validationAlreadyMessage] });
else
{
const now=new Date();
await Promise.all([
db["Subscription"].create({ numberOfDays: config.freeAccountTimingInDays, numberOfDays: config.defaultReceiptDays, noticeOk: datas.User.newsletterOk, UserId: datas.User.id }),
db["User"].update({ connectedAt: now }, { where: { id : datas.User.id }, limit:1 })
]);
creaUserJson(datas.User.id);
const mapMail =
{
USER_NAME: datas.User.name,
NOM_SITE : config.siteName,
EMAIL : config.senderEmail,
LINK_URL : config.siteUrl+"/"+configTpl.connectionPage
};
const mailDatas =
{
mailSubject: txt.mailWelcomeSubject,
mailPreheader: txt.mailWelcomeSubject,
mailTitle: txt.mailWelcomeSubject,
mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.connectionPage,
mailHeaderLinkTxt: txt.mailWelcomeLinkTxt,
mailMainContent: tool.replaceAll(txt.mailWelcomeBodyHTML, mapMail),
linksCTA: [{ url:config.siteUrl+"/"+configTpl.connectionPage, txt:txt.mailWelcomeLinkTxt }],
mailRecipientAddress: datas.User.email
}
toolMail.sendMail(0, datas.User.email, txt.mailWelcomeSubject, tool.replaceAll(txt.mailWelcomeBodyTxt, mapMail), "", mailDatas);
if(datas.User.GodfatherId!==null)
{
const godfather=await searchUserById(datas.User.GodfatherId);
if(godfather)
{
mapMail.USER_NAME=godfather.User.name;
mapMail.EMAIL=datas.User.email;
const mailDatas2=
{
mailSubject: txt.mailThankGodfatherSubject,
mailPreheader: txt.mailThankGodfatherSubject,
mailTitle: txt.mailThankGodfatherSubject,
mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.connectionPage,
mailHeaderLinkTxt: txt.mailThankGodfatherLinkTxt,
mailMainContent: tool.replaceAll(txt.mailThankGodfatherBodyHTML, mapMail),
mailRecipientAddress: godfather.User.email
}
toolMail.sendMail(godfather.User.smtp, godfather.User.email, txt.mailThankGodfatherSubject, tool.replaceAll(txt.mailThankGodfatherBodyTxt, mapMail), "", mailDatas2);
}
}
res.status(200).json(
{
userId: datas.User.id,
token: jwt.sign({ userId: datas.User.id }, config.tokenPrivateKey, { expiresIn: config.tokenConnexionMinTimeInHours })
});
}
}
else
res.status(401).json({ errors: txt.badLinkValidationMessage });
next();
}
catch(e)
{
next(e);
}
}
exports.checkToken = async (req, res, next) =>
{
try
{
const datas=await checkTokenUser(req.params.token);
if(datas && datas.Subscription)
{
const beginSubTS=new Date(datas.Subscription.createdAt).getTime();
const nbDaysOk=datas.Subscription.numberOfDays-Math.round((Date.now()-beginSubTS)/1000/3600/24);
res.status(200).json(
{
isValid: true,
id: datas.User.id,
name: datas.User.name,
language: datas.User.language,
timeDifference : datas.User.timeDifference,
status: datas.User.status,
nbDaysOk : nbDaysOk
});
}
else
res.status(200).json({ isValid: false });
next();
}
catch(e)
{
next(e);
}
}
// Reçoit les données du formulaire de connexion avec mot de passe.
exports.login = async (req, res, next) =>
{
try
{
const db = require("../models/index");
// Est-ce qu'un compte existe pour l'adresse e-mail envoyée ?
const emailSend=tool.trimIfNotNull(req.body.email);
const user=await db["User"].findOne({ attributes: ["id", "password", "email", "name", "status"], where: { email: emailSend } });
if(!user)
res.status(404).json({ errors: [txt.emailNotFound] });
else
{
// Est-ce ce compte a déjà été validé par l'utilisateur ? Si non, on lui envoie un nouveau lien de validation.
const subscription=await db["Subscription"].findOne({ attributes: ["id"], where: { UserId: user.id } });
if(!subscription)
{
await sendValidationLink(user);
res.status(400).json({ errors: [txt.needValidationToLogin] });
}
else
{
const nowTS=new Date().getTime();
// L'utilisateur n'a-t-il pas testé de se connecter de trop nombreuses fois sans succès ?
const countLogin=await toolFile.readJSON(config.dirTmpLogin, slugify(emailSend));
if(countLogin && countLogin.nb >= config.maxLoginFail && countLogin.lastTime > (nowTS-config.loginFailTimeInMinutes*60*1000))
res.status(401).json({ errors: [txt.tooManyLoginFails.replace("MINUTES", config.loginFailTimeInMinutes)] });
else
{
// Le mot du passe envoyé est-il cohérent avec celui de la base de données après chiffrement ?
const valid = await bcrypt.compare(req.body.password, user.password);
if (!valid)
{
res.status(401).json({ errors: [txt.badPassword] });
// On comptabilise l'erreur :
let newCountLogin={ nb:1, lastTime:nowTS };
if(countLogin.nb && countLogin.lastTime > (nowTS-config.loginFailTimeInMinutes*60*1000))
newCountLogin.nb=countLogin.nb+1;
await toolFile.createJSON(config.dirTmpLogin, slugify(emailSend), newCountLogin);
}
else
{
// Si tout est ok, on enregistre la date de connexion + retourne un token de connexion.
const now=new Date();
const timeDifference=req.body.timeDifference;
db["User"].update({ connectedAt: now, timeDifference: timeDifference }, { where: { id : user.id }, limit:1 });
creaUserJson(user.id);
// Connexion à rallonge uniquement possible pour utilisateur de base :
let loginTime=config.tokenConnexionMinTimeInHours;
if((req.body.keepConnected==="true") && (user.status==="user"))
loginTime=config.tokenConnexionMaxTimeInDays;
// Si des données concernant un quiz ont été transmises, on les enregistre ici :
req.body.UserId=user.id;
if(req.body.QuestionnaireId)
{
const access=await subscriptionCtrl.checkQuestionnaireAccess(user.id, req.body.QuestionnaireId);
if(!access)
await db["QuestionnaireAccess"].create({ ...req.body }, { fields: ["QuestionnaireId", "UserId"] });
await db["Answer"].create({ ...req.body }, { fields: ["nbQuestions", "nbCorrectAnswers", "duration", "QuestionnaireId", "UserId"] });
answerCtrl.creaUserAnswersJson(req.body.UserId);
answerCtrl.creaUserQuestionnairesWithoutAnswerJson(req.body.UserId);
answerCtrl.creaUserStatsAnwsersJson(req.body.UserId);
}
res.status(200).json(
{
userId: user.id,
status: user.status,
connexionTime: loginTime,
token: jwt.sign({ userId: user.id }, config.tokenPrivateKey, { expiresIn: loginTime })
});
}
}
}
}
next();
}
catch(e)
{
next(e);
}
}
// Reçoit les données du formulaire de connexion avec demande de recevoir un lien de connexion.
exports.getLoginLink = async (req, res, next) =>
{
try
{
// Est-ce qu'un compte existe pour l'adresse e-mail envoyée ?
const emailSend=tool.trimIfNotNull(req.body.email);
const userDatas=await searchUserByEmail(emailSend);
if(!userDatas)
res.status(404).json({ errors: [txt.emailNotFound] });
else if(userDatas.User.status!=="user") // seuls les utilisateurs de base peuvent se connecter de cette façon.
res.status(403).json({ errors: [txtGeneral.notAllowed] });
else
{
// Est-ce ce compte a déjà été validé par l'utilisateur ? Si non, on lui envoie un nouveau lien de validation.
if(!userDatas.Subscription)
{
await sendValidationLink(userDatas.User);
res.status(401).json({ errors: [txt.needValidationToLogin] });
}
else
{
const token=jwt.sign({ userId:userDatas.User.id, keepConnected:req.body.keepConnected }, config.tokenPrivateKey, { expiresIn: config.tokenLoginLinkTimeInHours });
const mapMail =
{
USER_NAME: userDatas.User.name,
LINK_URL : config.siteUrl+"/"+configTpl.loginLinkPage+token
};
const mailDatas=
{
mailSubject: txt.mailLoginLinkSubject,
mailPreheader: txt.mailLoginLinkSubject,
mailTitle: txt.mailLoginLinkSubject,
mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.loginLinkPage+token,
mailHeaderLinkTxt: txt.mailLoginLinkTxt,
mailMainContent: tool.replaceAll(txt.mailLoginLinkBodyHTML, mapMail),
linksCTA: [{ url:config.siteUrl+"/"+configTpl.loginLinkPage+token, txt:txt.mailLoginLinkTxt }],
mailRecipientAddress: userDatas.User.email
}
await toolMail.sendMail(userDatas.User.smtp, userDatas.User.email, txt.mailLoginLinkSubject, tool.replaceAll(txt.mailLoginLinkBodyTxt, mapMail), "", mailDatas);
res.status(200).json({ message: txt.mailLoginLinkMessage.replace("*TIMING*", config.tokenLoginLinkTimeInHours) });
}
}
next();
}
catch(e)
{
next(e);
}
}
exports.checkLoginLink = async (req, res, next) =>
{
try
{
const db = require("../models/index");
const userDatas= await checkTokenUser(req.body.t);
if(userDatas.User.status!=="user")
res.status(403).json({ errors: [txtGeneral.notAllowed] });
else if(userDatas.Subscription)
{
const now=new Date();
const timeDifference=req.body.timeDifference;
db["User"].update({ connectedAt: now, timeDifference: timeDifference }, { where: { id : userDatas.User.id }, limit:1 });
creaUserJson(userDatas.User.id);
let loginTime=config.tokenConnexionMinTimeInHours;
if(userDatas.decodedToken.keepConnected===true)
loginTime=config.tokenConnexionMaxTimeInDays;
// si des données concernant un quiz ont été transmises, je les enregistre ici :
if(req.body.QuestionnaireId)
{
req.body.UserId=userDatas.User.id;
const access=await subscriptionCtrl.checkQuestionnaireAccess(userDatas.User.id, req.body.QuestionnaireId);
if(!access)
await db["QuestionnaireAccess"].create({ ...req.body }, { fields: ["QuestionnaireId", "UserId"] });
await db["Answer"].create({ ...req.body }, { fields: ["nbQuestions", "nbCorrectAnswers", "duration", "QuestionnaireId", "UserId"] });
answerCtrl.creaUserAnswersJson(req.body.UserId);
answerCtrl.creaUserStatsAnwsersJson(req.body.UserId);
answerCtrl.creaUserQuestionnairesWithoutAnswerJson(req.body.UserId);
}
res.status(200).json(
{
userId: userDatas.User.id,
connexionTime: loginTime,
token: jwt.sign({ userId: userDatas.User.id }, config.tokenPrivateKey, { expiresIn: loginTime })
});
}
else // ne devrait pas être possible...
{
await sendValidationLink(userDatas.User);
res.status(401).json({ errors: [txt.needValidationToLogin] });
}
next();
}
catch(e)
{
next(e);
}
}
exports.modify = async (req, res, next) =>
{
try
{
const db = require("../models/index");
if(req.body.newPassword && req.body.newPassword.length < config.passwordMinLength)
res.status(400).json({ errors: txt.needLongPassWord.replace("MIN_LENGTH", config.passwordMinLength) });
else
{
if(req.connectedUser.User.status==="user")
{
await Promise.all([
db["User"].update({ ...req.body }, { where: { id : req.connectedUser.User.id } , fields: ["name", "language", "newsletterOk", "timeDifference"], limit:1 }),
db["Subscription"].update({ ...req.body }, { where: { UserId : req.connectedUser.User.id }, fields: ["receiptDays", "noticeOk"], limit:1 })
]);
creaUserJson(req.connectedUser.User.id);
const messageRetour=[txt.updatedOkMessage];
if(req.body.newPassword || (req.body.email != req.connectedUser.User.email))
{
const newLogin={ userId: req.connectedUser.User.id };
req.body.email=tool.trimIfNotNull(req.body.email);
if(req.body.email != req.connectedUser.User.email)
{
if (! /[A-Za-z0-9_.-]+@[A-Za-z0-9_.-]+\.[A-Za-z]{2,6}/.test(req.body.email))
messageRetour.push(txt.updatedNeedGoodEmail);
else
{
const emailFree=await searchUserByEmail(req.body.email);
if(emailFree!==false)
messageRetour.push(txt.updatedNeedUniqueEmail.replace("NEW_EMAIL", req.body.email));
else
newLogin.email=req.body.email;
}
}
if(req.body.newPassword)
newLogin.password=await bcrypt.hash(req.body.newPassword, config.bcryptSaltRounds);
if(newLogin.email || newLogin.password)
{
const token=jwt.sign(newLogin, config.tokenPrivateKey, { expiresIn: config.tokenLoginChangingTimeInHours });
const mapMail =
{
USER_NAME: req.body.name,
LINK_URL : config.siteUrl+"/"+configTpl.newLoginLinkPage+token
};
const mailDatas=
{
mailSubject: txt.mailUpdateLoginSubject,
mailPreheader: txt.mailUpdateLoginSubject,
mailTitle: txt.mailUpdateLoginSubject,
mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.newLoginLinkPage+token,
mailHeaderLinkTxt: txt.mailUpdateLoginLinkTxt,
mailMainContent: tool.replaceAll(txt.mailUpdateLoginBodyHTML, mapMail),
linksCTA: [{ url:config.siteUrl+"/"+configTpl.newLoginLinkPage+token, txt:txt.mailUpdateLoginLinkTxt }],
mailRecipientAddress: req.body.email
}
await toolMail.sendMail(req.connectedUser.User.smtp, req.body.email, txt.mailUpdateLoginSubject, tool.replaceAll(txt.mailUpdateLoginBodyTxt, mapMail), "", mailDatas);
messageRetour.push(txt.mailUpdateLoginLinkMessage.replace("NEW_EMAIL", req.body.email));
}
}
res.status(200).json({ message: messageRetour });
}
else
{
let userToUpdating=await searchUserById(req.params.id);
if(!userToUpdating || !userToUpdating.Subscription)
res.status(400).json({ errors: [txt.updatedNeedValidatedUser] });
else
{
const messageRetour=[txtGeneral.updateOkMessage];
if(req.body.newPassword)
req.body.password=await bcrypt.hash(req.body.newPassword, config.bcryptSaltRounds);
if(req.body.newGodfatherId!=undefined)
{
const godFather=searchIdGodfather(req.body.newGodfatherId);
if(empty(godFather))
messageRetour.push(txt.updatedNeedGoodGodfather);
else
req.body.GodfatherId=godFather.id;
}
switch(req.connectedUser.User.status)
{
case "admin":
await Promise.all([
db["User"].update({ ...req.body }, { where: { id : req.params.id }, limit:1 }),
db["Subscription"].update({ ...req.body }, { where: { UserId : req.params.id }, limit:1 })
]);
break;
case "manager":
await Promise.all([
db["User"].update({ ...req.body }, { where: { id : req.params.id }, fields: ["name", "email", "password", "language", "adminComments", "smtp", "newsletterOk", "GodfatherId"], limit:1 }),
db["Subscription"].update({ ...req.body }, { where: { UserId : req.params.id }, fields: ["numberOfDays", "receiptDays", "noticeOk"], limit:1 })
]);
break;
case "creator":
await Promise.all([
db["User"].update({ ...req.body }, { where: { id : req.params.id } , fields: ["name", "email", "password", "language", "adminComments"], limit:1 }),
db["Subscription"].update({ ...req.body }, { where: { UserId : req.params.id }, fields: ["numberOfDays", "receiptDays"], limit:1 })
]);
break;
default:
throw { message: txtGeneral.serverError };
}
await creaUserJson(req.params.id);// besoin d'attendre car utiliser pour rafraîchir l'affichage
res.status(200).json({ message: messageRetour });
}
}
}
next();
}
catch(e)
{
const returnAPI=toolError.returnSequelize(e);
if(returnAPI.messages)
{
res.status(returnAPI.status).json({ errors : returnAPI.messages });
next();
}
else
next(e);
}
}
exports.checkNewLoginLink = async (req, res, next) =>
{
try
{
const db = require("../models/index");
const userDatas=await checkTokenUser(req.params.token);
if(userDatas)
{
if(userDatas.User.id!==req.connectedUser.User.id)
res.status(401).json({ errors: [txtGeneral.notAllowed] });
else
{
await db["User"].update({ ...userDatas.decodedToken }, { where: { id : userDatas.User.id } , fields: ["email", "password"], limit:1 });
creaUserJson(userDatas.User.id);
res.status(200).json({ message: txt.mailUpdateLoginOkMessage });
}
}
else
res.status(404).json({ errors: [txtGeneral.notAllowed] });
next();
}
catch(e)
{
next(e);
}
}
exports.create = async (req, res, next) =>
{
try
{
const db = require("../models/index");
const connectedUser=req.connectedUser;
if(connectedUser.User.status==="user")
res.status(403).json({ errors: [txtGeneral.notAllowed] });
else
{
if(!tool.isEmpty(req.body.GodfatherId) && ! await searchUserById(req.body.GodfatherId))
res.status(400).json({ errors: [txt.updatedFailedGodfatherNotFound] });
else
{
req.body.password=await bcrypt.hash(req.body.password, config.bcryptSaltRounds);
if(req.body.GodfatherId==="")
req.body.GodfatherId=null;
let user;
switch(connectedUser.User.status)
{
case "admin":
user=await db["User"].create({ ...req.body });
req.body.UserId=user.id;
await db["Subscription"].create({ ...req.body });
break;
case "manager":
user=await db["User"].create({ ...req.body }, { fields: ["name", "email", "password", "language", "adminComments", "smtp", "newsletterOk", "GodfatherId"] });
req.body.UserId=user.id;
await db["Subscription"].create({ ...req.body }, { fields: ["numberOfDays", "receiptDays", "noticeOk", "UserId"] });
break;
case "creator":
user=await db["User"].create({ ...req.body }, { fields: ["name", "email", "password", "language", "adminComments"] });
req.body.UserId=user.id;
await db["Subscription"].create({ ...req.body }, { fields: ["numberOfDays", "receiptDays", "UserId"] });
break;
default:
throw { message: [txtGeneral.serverError] };
}
creaUserJson(req.body.UserId);
res.status(201).json({ message: [txt.creationOkMessage], id:req.body.UserId });
}
}
next();
}
catch(e)
{
const returnAPI=toolError.returnSequelize(e);
if(returnAPI.messages)
{
res.status(returnAPI.status).json({ errors : [returnAPI.messages] });
next();
}
else
next(e);
}
}
exports.validate = async (req, res, next) =>
{
try
{
const db = require("../models/index");
const connectedUser=req.connectedUser;
if(["admin", "manager"].indexOf(connectedUser.User.status) === -1)
res.status(403).json({ errors: [txtGeneral.notAllowed] });
else
{
const userToValidating=await searchUserById(req.params.id);
if(!userToValidating)
res.status(404).json({ errors: [txt.notFound] });
else if(userToValidating.Subscription)
res.status(400).json({ errors: [txt.validationAlreadyMessageAdmin] });
else
{
await db["Subscription"].create({ numberOfDays: config.freeAccountTimingInDays, UserId: req.params.id });
creaUserJson(req.params.id);
res.status(200).json({ message: [txt.validationMessageAdmin] });
}
}
}
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 connectedUser=req.connectedUser;
if(connectedUser.User.status==="user")
{
const userDatas=await searchUserById(connectedUser.User.id);
const token=jwt.sign({ userId: connectedUser.User.id }, config.tokenPrivateKey, { expiresIn: config.tokenDeleteUserTimeInHours });
const mapMail =
{
USER_NAME: userDatas.User.name,
LINK_URL : config.siteUrl+"/"+configTpl.deleteLinkPage+token
};
const mailDatas=
{
mailSubject: txt.mailDeleteSubject,
mailPreheader: txt.mailDeleteSubject,
mailTitle: txt.mailDeleteSubject,
mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.deleteLinkPage+token,
mailHeaderLinkTxt: txt.mailDeleteLinkTxt,
mailMainContent: tool.replaceAll(txt.mailDeleteBodyHTML, mapMail),
linksCTA: [{ url:config.siteUrl+"/"+configTpl.deleteLinkPage+token, txt:txt.mailDeleteLinkTxt }],
mailRecipientAddress: connectedUser.User.email
}
await toolMail.sendMail(connectedUser.User.smtp, connectedUser.User.email, txt.mailDeleteSubject, tool.replaceAll(txt.mailDeleteBodyTxt, mapMail), "", mailDatas);
res.status(200).json({ message: txt.mailDeleteLinkMessage });
}
else if (["admin","manager","creator"].indexOf(connectedUser.User.status) !== -1)
{
const userDeleted=await searchUserById(req.params.id);
if(!userDeleted)
throw { message: txt.notFound };
const nb=await db["User"].destroy({ where: { id : req.params.id }, limit:1 });
if(nb===1)
{
await toolFile.deleteJSON(config.dirCacheUsers, req.params.id);
toolFile.deleteJSON(config.dirCacheUsersQuestionnaires+"/without", req.params.id);
toolFile.deleteJSON(config.dirCacheUsersQuestionnaires+"/answers", req.params.id);
toolFile.deleteJSON(config.dirCacheUsersQuestionnaires+"/answers", "stats"+req.params.id);
let now=new Date(), wasValided=false, wasPremium=false;
if(userDeleted.Subscription)
{
wasValided=true;
if(userDeleted.Subscription.numberOfDay > config.freeAccountTimingInDays)
wasPremium=true;
}
db["UserDeleted"].create({ createdAt: userDeleted.User.createdAt, deletedAt: now, wasValided: wasValided, wasPremium: wasPremium });
res.status(200).json({ message: [txt.deleteOkMessage] });
}
else
{
res.status(400).json({ errors: txtGeneral.serverError });
throw { message: txt.deleteFailMessage+req.params.id };
}
}
next();
}
catch(e)
{
next(e);
}
}
exports.checkDeleteLink = async (req, res, next) =>
{
try
{
const db = require("../models/index");
const connectedUser=req.connectedUser;
const userDatas=await checkTokenUser(req.params.token);
if(userDatas)
{
if(connectedUser.User.id!=userDatas.User.id)
res.status(403).json({ errors: [txtGeneral.notAllowed] });
else
{
const nb=await db["User"].destroy({ where: { id : connectedUser.User.id }, limit:1 });
if(nb===1)
{
await toolFile.deleteJSON(config.dirCacheUsers, connectedUser.User.id);
res.status(200).json({ message: txt.mailDeleteLinkOkMessage });
toolFile.deleteJSON(config.dirCacheUsersQuestionnaires+"/without", connectedUser.User.id);
toolFile.deleteJSON(config.dirCacheUsersQuestionnaires+"/answers", connectedUser.User.id);
toolFile.deleteJSON(config.dirCacheUsersQuestionnaires+"/answers", "stats"+connectedUser.User.id);
const now=new Date(); wasPremium=false;
if(userDatas.Subscription.numberOfDay > config.freeAccountTimingInDays)
wasPremium=true;
db["UserDeleted"].create({ createdAt: userDatas.User.createdAt, deletedAt: now, wasValided:true, wasPremium: wasPremium });
}
else
res.status(400).json({ errors: [txt.mailDeleteLinkAlreadyMessage] });
}
}
else
res.status(400).json({ errors: [txt.mailDeleteLinkFailMessage] });
next();
}
catch(e)
{
next(e);
}
}
exports.getOneUserById = async (req, res, next) =>
{
try
{
const connectedUser=req.connectedUser;
if(connectedUser.User.status==="user")
{
if(connectedUser.User.id!=req.params.id)
res.status(403).json({ errors: txtGeneral.notAllowed });
else
{
const datas=await searchUserById(connectedUser.User.id);
if(datas)
res.status(200).json(datas);
else
res.status(404).json({ message:txt.notFound });
}
}
else if (["admin","manager","creator"].indexOf(connectedUser.User.status) !== -1)
{// dans le cas des "creator" il faudra peut-être limité l'accès à "ses" utilisateurs ?
const datas=await searchUserById(req.params.id);
if(datas)
res.status(200).json(datas);
else
res.status(404).json({ message:txt.notFound });
}
next();
}
catch(e)
{
next(e);
}
}
exports.getOneUserGodChilds = async (req, res, next) =>
{
try
{
const db = require("../models/index");
const connectedUser=req.connectedUser;
let godchilds;
if(["admin","manager","creator"].indexOf(connectedUser.User.status) === -1)
godchilds=await db["User"].findAll({ where: { GodfatherId: connectedUser.User.id }, attributes: ["id", "name", "email"] });
else
godchilds=await db["User"].findAll({ where: { GodfatherId: req.params.id }, attributes: ["id", "name", "email"] });
res.status(200).json(godchilds);
next();
}
catch(e)
{
next(e);
}
}
exports.searchUsers = async (req, res, next) =>
{
try
{
const connectedUser=req.connectedUser;
if(["admin", "manager", "creator"].indexOf(connectedUser.User.status) === -1)
res.status(403).json({ errors: [txtGeneral.notAllowed] });
else
{
const db = require("../models/index");
const Users=await db.sequelize.query("SELECT `id`,`name`,`email` FROM `Users` WHERE `name` LIKE :search OR `adminComments` LIKE :search OR `email` LIKE :search OR `id` = :id", { replacements: { search: "%"+req.body.search+"%", id: req.body.search }, type: QueryTypes.SELECT });
res.status(200).json(Users);
}
next();
}
catch(e)
{
next(e);
}
}
// Retourne des statistiques concernant les utilisateurs
// Utile tableau de bord gestionnaires
exports.getStats= async (req, res, next) =>
{
try
{
let stats=await getStatsUsers();
stats.Answers=await answerCtrl.getStatsAnswers();
stats.Subscriptions=await subscriptionCtrl.getStatsSubscriptions();
res.status(200).json(stats);
next();
}
catch(e)
{
next(e);
}
}
// CRONS
// Supprime les fichiers stockant les erreurs de connexion ayant expirés
exports.deleteLoginFail = async (req, res, next) =>
{
try
{
const fileExpiration=new Date().getTime()-config.loginFailTimeInMinutes*60*1000;
const deleteFiles = await toolFile.deleteOldFilesInDirectory(config.dirTmpLogin, fileExpiration);
res.status(200).json(deleteFiles);
next();
}
catch(e)
{
next(e);
}
}
// Suppression des fichiers pour des utilisateurs n'existant plus
exports.deleteJsonFiles= async (req, res, next) =>
{
try
{
const db = require("../models/index");
const users=await db["User"].findAll({ attributes: ["id"] });
let saveFiles=[];
for(let i in users)
{
saveFiles.push(users[i].id+".json");
saveFiles.push("stats"+users[i].id+".json");
}
const deleteFiles = await Promise.all([
toolFile.deleteFilesInDirectory(configUsers.dirCacheUsers, saveFiles),
toolFile.deleteFilesInDirectory(configUsers.dirCacheUsersWithoutAnswers, saveFiles),
toolFile.deleteFilesInDirectory(configUsers.dirCacheUsersAnswers, saveFiles)
]);
res.status(200).json(deleteFiles);
next();
}
catch(e)
{
next(e);
}
}
// Suppression des comptes n'ayant pas été validé passé un certain délai
exports.deleteUnvalided= async (req, res, next) =>
{
try
{
const db = require("../models/index");
const nowTS=Date.now();
const timeExpiration=nowTS-parseInt(config.tokenSignupValidationTimeInHours,10)*3600*1000;
const userUnvalided= await db.sequelize.query("SELECT createdAt FROM `Users` WHERE UNIX_TIMESTAMP(createdAt) < "+timeExpiration+" AND `id` NOT IN (SELECT `UserId` FROM `Subscriptions`)", { type: QueryTypes.SELECT });
if(userUnvalided.length!==0)
{
const [results, metadata] = await db.sequelize.query("DELETE FROM `Users` WHERE UNIX_TIMESTAMP(createdAt) < "+timeExpiration+" AND `id` NOT IN (SELECT `UserId` FROM `Subscriptions`)");
const now=new Date();
for(let i in userUnvalided)
await db["UserDeleted"].create({ createdAt: userUnvalided[i].createdAt, deletedAt: now, wasValided: false });
res.message=metadata.affectedRows+txt.cronDeleteUnvalidedUsersMessage;
}
res.status(200).json(true);
next();
}
catch(e)
{
next(e);
}
}
// Suppression des comptes dont l'abonnement a expiré et ne s'étant pas connecté depuis à certains temps
exports.deleteInactiveAccounts= async(req, res, next) =>
{
try
{
const db = require("../models/index");
const usersInactive= await db.sequelize.query("SELECT createdAt FROM `Users` WHERE status='user' AND (ADDDATE(`connectedAt`, "+config.inactiveAccountTimeToDeleteInDays+") < NOW() OR `connectedAt` IS NULL) AND `id` IN (SELECT `UserId` FROM `Subscriptions` WHERE ADDDATE(`createdAt`, `numberOfDays`)< NOW())", { type: QueryTypes.SELECT });
if(usersInactive.length!==0)
{
const [results, metadata] = await db.sequelize.query("DELETE FROM `Users` WHERE (ADDDATE(`connectedAt`, "+config.inactiveAccountTimeToDeleteInDays+") < NOW() OR `connectedAt` IS NULL) AND status='user' AND `id` IN (SELECT `UserId` FROM `Subscriptions` WHERE ADDDATE(`createdAt`, `numberOfDays`)< NOW())");
if(metadata.affectedRows!==0)
res.message=metadata.affectedRows+txt.deleteInactiveUsersMessage;
const now=new Date();
for(let i in usersInactive)
await db["UserDeleted"].create({ createdAt: usersInactive[i].createdAt, deletedAt: now, wasValided: true });
}
res.status(200).json(true);
next();
}
catch(e)
{
next(e);
}
}
// FONCTIONS UTILITAIRES
// Création du fichier des données de l'utilisateur + son abonnement et ses pauses
const creaUserJson = async (id) =>
{
id=tool.trimIfNotNull(id);
if(id===null)
return false;
const db = require("../models/index");
const User=await db["User"].findByPk(id, { attributes: { exclude: ['password'] } });
if(User)
{
let datas={ User };
const Subscription=await db["Subscription"].findOne({ where: { UserId: id } });
if(Subscription) // pas de cache pour utilisateurs non validés
{
datas.Subscription=Subscription;
const Pauses=await db["Pause"].findAll({ where: { SubscriptionId: Subscription.id } });
if(Pauses)
datas.Pauses=Pauses;
await toolFile.createJSON(config.dirCacheUsers, id, datas);
}
return datas;
}
else
return false;
}
exports.creaUserJson = creaUserJson;
const searchUserById = async (id) =>
{
id=tool.trimIfNotNull(id);
if(id===null)
return false;
const user=await toolFile.readJSON(config.dirCacheUsers, id);
if(user)
return user;
else
return await creaUserJson(id);
}
exports.searchUserById = searchUserById;
const searchUserByEmail = async (email) =>
{
const db = require("../models/index");
const emailSend=tool.trimIfNotNull(email);
const userEmail=await db["User"].findOne({ attributes: ["id"], where: { email : emailSend } });
if(userEmail)
return await searchUserById(userEmail.id);// permet de récupérer les autres infos (Subscription, etc.)
else
return false;
}
// Recherche le parrain dont le "code" fourni peut être l'identifiant préfixé ou bien l'adresse e-mail
const searchIdGodfather = async (code) =>
{
let godfatherInfos="";
code=tool.trimIfNotNull(code);
if(code===null)
return false;
else if(code.indexOf("@")!==-1) // tester en premier, car une adresse peut débuter par le préfixe de l'appli...
godfatherInfos=await searchUserByEmail(code);
else if (code.startsWith(config.beginCodeGodfather))
godfatherInfos=await searchUserById(code.substring(config.beginCodeGodfather.length));
if(godfatherInfos.Subscription && godfatherInfos.User.status==="user") // le parrain doit avoir validé son compte et doit être un simple "user"
return godfatherInfos.User;
else
return null;
}
// Envoi le lien permettant au nouvel utilisateur de valider son compte
const sendValidationLink = async (user) =>
{
const token=jwt.sign({ userId: user.id }, config.tokenPrivateKey, { expiresIn: config.tokenSignupValidationTimeInHours });
const mapMail =
{
USER_NAME: user.name,
LINK_URL: config.siteUrl+"/"+configTpl.validationLinkPage+token,
};
const mailDatas=
{
mailSubject: txt.mailValidationLinkSubject,
mailPreheader: txt.mailValidationLinkSubject,
mailTitle: txt.mailValidationLinkSubject,
mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.validationLinkPage+token,
mailHeaderLinkTxt: txt.mailValidationLinkTxt,
mailMainContent: tool.replaceAll(txt.mailValidationLinkSBodyHTML, mapMail),
linksCTA: [{ url:config.siteUrl+"/"+configTpl.validationLinkPage+token, txt:txt.mailValidationLinkTxt }],
mailRecipientAddress: user.email
}
await toolMail.sendMail(0, user.email, txt.mailValidationLinkSubject, tool.replaceAll(txt.mailValidationLinkSBodyTxt, mapMail), "", mailDatas);
return true;
}
// Permet de vérifier un token de connexion : est-il valide et est-ce qu'il correspond à un utilisateur ?
// Si oui, on peut aussi enregistrer une nouvelle connexion pour l'utilisateur (une fois / jour)
const checkTokenUser = async (token) =>
{
const db = require("../models/index");
const decodedToken=jwt.verify(token, config.tokenPrivateKey);
const userId=decodedToken.userId;
const datas=await searchUserById(userId);
if(datas.User)
{
const now=new Date();
const nowTS=Date.now()
const lastConnection = Date.parse(datas.User.connectedAt);
if(lastConnection+24*3600*1000 < nowTS) // une fois par jour suffit.
{
await db["User"].update({ connectedAt: now }, { where: { id : userId }, limit:1 });
creaUserJson(userId);
}
datas.decodedToken=decodedToken;
return datas;
}
else
return false;
}
exports.checkTokenUser = checkTokenUser;
// Stats sur les créations de compte, suppression.. durant les dernières 24H ou depuis le début
const getStatsUsers = async () =>
{
const db = require("../models/index");
const getNewUsers24H = await db.sequelize.query("SELECT `id` FROM `Users` WHERE `createdAt` > ADDDATE(NOW(), -1)", { type: QueryTypes.SELECT });
const getNewUsersTot = await db.sequelize.query("SELECT `id` FROM `Users`", { type: QueryTypes.SELECT });
const getDeletedUsers24H = await db.sequelize.query("SELECT `id` FROM `UserDeleteds` WHERE `deletedAt` > ADDDATE(NOW(), -1)", { type: QueryTypes.SELECT });
const getDeletedUsersTot = await db.sequelize.query("SELECT `id` FROM `UserDeleteds`", { type: QueryTypes.SELECT });
const getDeletedUsersWasValided = await db.sequelize.query("SELECT `id` FROM `UserDeleteds` WHERE `wasValided`= 1", { type: QueryTypes.SELECT });
const getDeletedUsersTotWasPremium = await db.sequelize.query("SELECT `id` FROM `UserDeleteds` WHERE `wasPremium`= 1", { type: QueryTypes.SELECT });
if(getNewUsers24H && getNewUsersTot && getDeletedUsers24H && getDeletedUsersWasValided && getDeletedUsersTotWasPremium && getDeletedUsersTot )
{
const stats =
{
nbNewUsers24H : getNewUsers24H.length,
nbNewUsersTot : getNewUsersTot.length,
nbDeletedUsers24H : getDeletedUsers24H.length,
nbDeletedUsersTot : getDeletedUsersTot.length,
nbDeletedUsersWasValided : getDeletedUsersWasValided.length,
nbDeletedUsersTotWasPremium : getDeletedUsersTotWasPremium.length
}
return stats;
}
else
return false;
}
exports.getStatsUsers = getStatsUsers;