WikiLerni/controllers/user.js

1118 lines
47 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);
}
}
// Contrôleur traitant les données envoyées pour une inscription
// Les CGU doivent être acceptées et une adresse e-mail envoyée.
// Le reste peut être adapté sur la page de validation de l'inscription.
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(tool.isEmpty(req.body.email))
res.status(400).json({ errors: [txt.needEmail] });
else
{
// Un mot de passe temporaire et non communiqué est généré :
req.body.passwordTemp=tool.getPassword(8, 12);
req.body.password=await bcrypt.hash(req.body.passwordTemp, config.bcryptSaltRounds);
// Un pseudo temporaire est créé en utilisant la partie de l'e-mail précédant le "@" :
const lastIndex=req.body.email.indexOf("@");
if(lastIndex === -1)// possible car validité de l'e-mail testé par le modèle lors de l'enregistrement
lastIndex=1;
req.body.name=req.body.email.substring(0, lastIndex);
const user=await db["User"].create({ ...req.body }, { fields: ["name", "email", "password", "timeDifference"] });
req.body.UserId=user.id;
// Si l'utilisateur a répondu à un quiz (ou groupe de quizs) avant de créer son compte, on enregistre son résultat :
if(req.body.QuestionnaireId)
await answerCtrl.saveAnswerToQuestionnaire(req.body);
else if(req.body.GroupId)
await answerCtrl.saveAnswerToGroup(req.body);
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);
}
}
// Contrôleur testant le lien de validation du compte et envoyant un message de bienvenue si tout est ok
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.replace("#URL", configTpl.connectionPage)] });
else
{
const now=new Date();
await Promise.all([
db["Subscription"].create({ numberOfDays: config.freeAccountTimingInDays, receiptDays: config.defaultReceiptDays, UserId: datas.User.id }),
db["User"].update({ connectedAt: now }, { where: { id : datas.User.id }, limit:1 })
]);
const newUser=await 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);
res.status(200).json(
{
newUser: newUser,
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);
}
}
// Contrôleur recevant les données envoyées par l'utilisateur lorsqu'il lui est demandé de complèter ses informations, juste après avoir validé son abonnement
// Il peut fournir ici un pseudo et un mot de passe de son choix ou encore un code parrain
// Il peut asussi choisir les jours de réception de son abonnement au site
exports.signUpCompletion = async (req, res, next) =>
{
try
{
const db = require("../models/index");
if(!tool.isEmpty(req.body.newPassword) && req.body.newPassword.length < config.passwordMinLength)
res.status(400).json({ errors: txt.needLongPassWord.replace("MIN_LENGTH", config.passwordMinLength) });
else
{
if(!tool.isEmpty(req.body.newPassword))// dans ce cas, l'utilisateur n'a pas choisi de mot de passe, mais pourra se connecter via les tokens de connexion
req.body.password=await bcrypt.hash(req.body.newPassword, config.bcryptSaltRounds);
req.body.GodfatherId=null;
if(req.body.codeGodfather !== "")
{
const godfather=await searchIdGodfather(req.body.codeGodfather);
if(godfather)
req.body.GodfatherId=godfather.id;
}
await Promise.all([
db["User"].update({ ...req.body }, { where: { id : req.connectedUser.User.id } , fields: ["name", "password", "GodfatherId"], limit:1 }),
db["Subscription"].update({ ...req.body }, { where: { UserId : req.connectedUser.User.id }, fields: ["receiptDays"], limit:1 })
]);
const user=await creaUserJson(req.connectedUser.User.id);
// Si un parrain a été désigné, on prévient l'heureux élu :) :
if(user!==false && req.body.GodfatherId !== null)
{
const godfather=await searchUserById(req.body.GodfatherId);
if(godfather)
{
const mapMail =
{
USER_NAME: godfather.User.name,
NOM_SITE : config.siteName,
EMAIL : user.User.email,
LINK_URL : config.siteUrl+"/"+configTpl.connectionPage
};
const mailDatas=
{
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), "", mailDatas);
}
}
const messageRetour=[txt.updatedOkMessage];
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.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 });
await 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 l'utilisateur a répondu à un quiz (ou groupe de quizs) avant de se connecter, on enregistre son résultat.
// Uniquement pour les utilisateurs de base.
req.body.UserId=user.id;
if(req.body.QuestionnaireId && user.status === "user")
await answerCtrl.saveAnswerToQuestionnaire(req.body);
else if(req.body.GroupId && user.status === "user")
await answerCtrl.saveAnswerToGroup(req.body);
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 l'utilisateur a répondu à un quiz (ou groupe de quizs) avant de se connecter, on enregistre son résultat :
req.body.UserId=userDatas.User.id;
if(req.body.QuestionnaireId)
await answerCtrl.saveAnswerToQuestionnaire(req.body);
else if(req.body.GroupId)
await answerCtrl.saveAnswerToGroup(req.body);
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", "timeDifference"], limit:1 }),
db["Subscription"].update({ ...req.body }, { where: { UserId : req.connectedUser.User.id }, fields: ["receiptDays"], 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", "GodfatherId"], limit:1 }),
db["Subscription"].update({ ...req.body }, { where: { UserId : req.params.id }, fields: ["numberOfDays", "receiptDays"], 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", "GodfatherId"] });
req.body.UserId=user.id;
await db["Subscription"].create({ ...req.body }, { fields: ["numberOfDays", "receiptDays", "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;