WikiLerni/controllers/subscription.js

301 lines
17 KiB
JavaScript

// Contrôleurs gérant l'abonnement de l'utilisateur mais aussi les questionnaires auxquels il a accès
const { Op, QueryTypes } = require("sequelize");// pour certaines requêtes sql
const jwt = require("jsonwebtoken");
const config = require("../config/main");
const configQuestionnaires = require("../config/questionnaires");
const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js");
const tool = require("../tools/main");
const toolFile = require("../tools/file");
const toolMail = require("../tools/mail");
const txt = require("../lang/"+config.adminLang+"/subscription");
const txtQuestionnaire= require("../lang/"+config.adminLang+"/questionnaire");
const txtQuestionnaireAccess= require("../lang/"+config.adminLang+"/questionnaireaccess");
const txtGeneral= require("../lang/"+config.adminLang+"/general");
const answerCtrl = require("./answer");
const questionnaireCtrl = require("./questionnaire");
const userCtrl = require("./user");
// Clic sur lien de désabonnement aux alertes email
exports.unsubscribeLink = async (req, res, next) =>
{
try
{
const db = require("../models/index");
const userDatas= await userCtrl.checkTokenUser(req.params.token);
await db["User"].update({ newsletterOk: false }, { where: { id : userDatas.User.id }, limit:1 });
await db["Subscription"].update({ noticeOk: false }, { where: { UserId : userDatas.User.id }, limit:1 });
userCtrl.creaUserJson(userDatas.User.id);
res.status(200).json({ message: txt.unsubscriptionOk });
next();
}
catch(e)
{
next(e);
}
}
// CRONS
// Envoi des notifications aux abonnés arrivés à quelques jours de la fin de leur période d'abonnement gratuit.
exports.notifyExpirationFreeAccount= async(req, res, next) =>
{
try
{
const db = require("../models/index");
const dateExpirationMin=new Date(new Date().getTime()-(config.freeAccountTimingInDays-config.freeAccountExpirationNotificationInDays+1)*24*3600*1000);
const dateExpirationMax=new Date(new Date().getTime()-(config.freeAccountTimingInDays-config.freeAccountExpirationNotificationInDays)*24*3600*1000);
const users=await db["Subscription"].findAll(
{
where:
{
[Op.and]:
[
{ numberOfDays: config.freeAccountTimingInDays },
{ createdAt: { [Op.gte]: dateExpirationMin } },
{ createdAt: { [Op.lt]: dateExpirationMax } }
]
},
attributes: ["UserId"]
});
const sendNotification = async (user) =>
{
let userInfos=await userCtrl.searchUserById(user.UserId);
if(userInfos && userInfos.User.status=="user")
{
const mapMail =
{
SITE_NAME: config.siteName,
USER_NAME: userInfos.User.name,
LINK_URL : config.siteUrl+"/"+configTpl.updateAccountPage+"#subscribe"
};
const mailDatas=
{
mailSubject: txt.mailEndFreeTimeSubject,
mailPreheader: txt.mailEndFreeTimeSubject,
mailTitle: txt.mailEndFreeTimeSubject,
mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.updateAccountPage+"#subscribe",
mailHeaderLinkTxt: txt.mailEndFreeTimeLinkTxt,
mailMainContent: tool.replaceAll(txt.mailEndFreeTimeBodyHTML, mapMail),
linksCTA: [{ url:config.siteUrl+"/"+configTpl.updateAccountPage+"#subscribe", txt:txt.mailEndFreeTimeLinkTxt }],
mailRecipientAddress: userInfos.User.email
}
await toolMail.sendMail(userInfos.User.smtp, userInfos.User.email, txt.mailEndFreeTimeSubject, tool.replaceAll(txt.mailEndFreeTimeBodyTxt, mapMail), "", mailDatas);
}
}
for(let i in users)
sendNotification(users[i]);
if(res.message)
res.message+="\n"+users.length+txt.mailEndFreeTimeMessage;
else
res.message=users.length+txt.mailEndFreeTimeMessage;
res.status(200).json(true);
next();
}
catch(e)
{
next(e);
}
}
// Envoi des notifications aux abonnés arrivés à quelques jours de la fin de leur période d'abonnement prémium.
exports.notifyExpirationAccount= async(req, res, next) =>
{
try
{
const db = require("../models/index");
const getUsers= async (nbDays) =>
{
const dateExpirationMin=new Date(new Date().getTime()+nbDays*24*3600*1000);
const dateExpirationMax=new Date(new Date().getTime()+(nbDays+1)*24*3600*1000);
const users=await db["Subscription"].findAll(
{
where:
{
[Op.and]:
[
{ numberOfDays: { [Op.gt]: config.freeAccountTimingInDays } },
db.sequelize.where(db.sequelize.fn('ADDDATE', db.sequelize.col('createdAt'), db.sequelize.literal('INTERVAL `numberOfDays` DAY')), { [Op.gte]: dateExpirationMin }),
db.sequelize.where(db.sequelize.fn('ADDDATE', db.sequelize.col('createdAt'), db.sequelize.literal('INTERVAL `numberOfDays` DAY')), { [Op.lt]: dateExpirationMax })
]
},
attributes: ["UserId"],
});
return users;
}
const sendNotification= async (user, mail) =>
{
let userInfos=await userCtrl.searchUserById(user.UserId);
if(userInfos && userInfos.User.status=="user")
{
const mapMail =
{
SITE_NAME: config.siteName,
USER_NAME: userInfos.User.name,
LINK_URL : config.siteUrl+"/"+configTpl.updateAccountPage+"#subscribe"
};
const mailDatas=
{
mailSubject: txt.mailExpirationSubject,
mailPreheader: txt.mailExpirationSubject,
mailTitle: txt.mailExpirationSubject,
mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.updateAccountPage+"#subscribe",
mailHeaderLinkTxt: txt.mailExpirationLinkTxt,
mailMainContent: tool.replaceAll(txt.mailExpirationBodyHTML, mapMail),
linksCTA: [{ url:config.siteUrl+"/"+configTpl.updateAccountPage+"#subscribe", txt:txt.mailExpirationLinkTxt }],
mailRecipientAddress: userInfos.User.email
}
await toolMail.sendMail(userInfos.User.smtp, userInfos.User.email, txt.mailExpirationSubject, tool.replaceAll(txt.mailExpirationBodyTxt, mapMail), "", mailDatas);
}
}
// première salve :
let users1=await getUsers(config.accountExpirationFirstNotificationInDays);
for(let i in users1)
sendNotification(users1[i], "first");
// deuxième relance :
let users2=await getUsers(config.accountExpirationSecondNotificationInDays);
for(let i in users2)
sendNotification(users2[i], "second");
if(res.message)
res.message+="\n"+tool.replaceAll(txt.mailExpirationMessage, { FIRST:users1.length , SECOND:users2.length });
else
res.message=tool.replaceAll(txt.mailExpirationMessage, { FIRST:users1.length , SECOND:users2.length });
res.status(200).json(true);
next();
}
catch(e)
{
next(e);
}
}
// CRON donnant accès à l'utilisateur à nouveau quiz suivant son choix
// si il n'a plus de nouveaux quizs et que l'utilisateur souhaite recevoir un email, on lui passe un quiz au hasard à retester.
exports.addNewQuestionnaireUsers = async(req, res, next) =>
{
try
{
const db = require("../models/index");
// Utilisateurs dont l'abonnement est toujours actif et souhaitant recevoir un nouveau quiz le jour de l'appel de cette méthode.
// Le tout en heure locale et en ignorant ceux qui ont déjà été traités ce jour.
const subscriptionsOk = await db.sequelize.query("SELECT `Subscriptions`.`id` as SubscriptionId, `Subscriptions`.`lastProcessingAt`, `UserId`, `name`, `email`, `smtp`, `language`, `noticeOk`, `receiptDays`, ADDDATE(UTC_TIMESTAMP, INTERVAL `timeDifference` MINUTE) AS localDate FROM `Subscriptions` INNER JOIN `Users` ON `Subscriptions`.`UserId`=`Users`.`id` WHERE `status`='user' AND ADDDATE(`Subscriptions`.`createdAt`, `numberOfDays`) > UTC_TIMESTAMP HAVING HOUR(localDate) > "+config.hourGiveNewQuestionnaireBegin+" AND HOUR(localDate) < "+config.hourGiveNewQuestionnaireEnd+" AND LOCATE(DAYOFWEEK(localDate),receiptDays)!=0 AND SubscriptionId NOT IN (SELECT DISTINCT `SubscriptionId` FROM `Pauses` WHERE ADDDATE(`startingAt`, INTERVAL `timeDifference` MINUTE) <= localDate AND ADDDATE(`endingAT`, INTERVAL `timeDifference` MINUTE) > localDate) AND DATEDIFF(NOW(),`Subscriptions`.`lastProcessingAt`) >= 1 LIMIT "+config.numberNewQuestionnaireAtSameTime, { type: QueryTypes.SELECT });
if(subscriptionsOk.length===0)
res.status(200).json({ message: txt.allSubscriptionProcessed });
else
{
let newQuestionnaire, access, questionnaire, token, now=new Date();
for (let i in subscriptionsOk)
{
newQuestionnaire=await db.sequelize.query("SELECT `Questionnaires`.`id`, `title`, `slug`, `introduction`, `url`, `anchor`,`estimatedTime` FROM `Questionnaires` INNER JOIN `Links` ON `Links`.`QuestionnaireId`=`Questionnaires`.`id` WHERE `isPublished`=1 AND `language`='"+subscriptionsOk[i].language+"' AND `Questionnaires`.`id` NOT IN (SELECT `QuestionnaireId` FROM `QuestionnaireAccesses` WHERE `UserId`="+subscriptionsOk[i].UserId+") ORDER BY `id` LIMIT 1", { type: QueryTypes.SELECT });
if(newQuestionnaire.length!==0)
{
access=await db["QuestionnaireAccess"].create({ QuestionnaireId: newQuestionnaire[0].id, UserId: subscriptionsOk[i].UserId, selfCreatedOk: false });
if(access)
{
answerCtrl.creaUserQuestionnairesWithoutAnswerJson(subscriptionsOk[i].UserId);
if(subscriptionsOk[i].noticeOk)
{
token=jwt.sign({ userId: subscriptionsOk[i].UserId }, config.tokenPrivateKey, { expiresIn: config.tokenUnsubscribeLinkTimeInDays });
const mapMail =
{
USER_NAME: subscriptionsOk[i].name,
QUESTIONNAIRE_URL: config.siteUrl+"/"+configQuestionnaires.dirWebQuestionnaire+"/"+newQuestionnaire[0].slug+".html",
UNSUBSCRIBE_URL: config.siteUrl+"/"+configTpl.stopMailPage+token
};
const mailDatas=
{
mailSubject: newQuestionnaire[0].title,
mailPreheader: newQuestionnaire[0].title,
mailTitle: newQuestionnaire[0].title,
mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.stopMailPage+token,
mailHeaderLinkTxt: txt.mailStopMailLinkTxt,
mailMainContent: newQuestionnaire[0].introduction+"<p><b>"+txtQuestionnaire.estimatedTime+"</b> "+txtQuestionnaire.estimatedTimeOption[newQuestionnaire[0].estimatedTime]+".</p>",
linksCTA: [{ url:newQuestionnaire[0].url, txt:newQuestionnaire[0].anchor }, { url:config.siteUrl+"/"+configQuestionnaires.dirWebQuestionnaire+"/"+newQuestionnaire[0].slug+".html#questionnaire", txt:txtQuestionnaire.btnShowQuestionnaire }],
mailRecipientAddress: subscriptionsOk[i].email
}
toolMail.sendMail(subscriptionsOk[i].smtp, subscriptionsOk[i].email, newQuestionnaire[0].title, tool.replaceAll(txt.mailNewQuestionnaireBodyTxt, mapMail), "", mailDatas);
}
}
db["Subscription"].update({ lastProcessingAt: now }, { where: { id : subscriptionsOk[i].SubscriptionId }, limit:1 });
}
else
{
// L'utilisateur a déjà accès à tous les quizs enregistés.
// Si il est abonné par email, on en tire un au hasard pour le lui envoyer, sinon on ne fait rien
if(subscriptionsOk[i].noticeOk)
{
getRandomQuestionnaire=await db.sequelize.query("SELECT `Questionnaires`.`id`, `title`, `slug`, `introduction`, `url`, `anchor`, `estimatedTime` FROM `Questionnaires` INNER JOIN `Links` ON `Links`.`QuestionnaireId`=`Questionnaires`.`id` WHERE `isPublished`=1 AND `language`='"+subscriptionsOk[i].language+"' ORDER BY RAND() LIMIT 1", { type: QueryTypes.SELECT });
token=jwt.sign({ userId: subscriptionsOk[i].UserId }, config.tokenPrivateKey, { expiresIn: config.tokenUnsubscribeLinkTimeInDays });
const mapMail =
{
USER_NAME: subscriptionsOk[i].name,
QUESTIONNAIRE_URL: config.siteUrl+"/"+configQuestionnaires.dirWebQuestionnaire+"/"+getRandomQuestionnaire[0].slug+".html",
UNSUBSCRIBE_URL: config.siteUrl+"/"+configTpl.stopMailPage+token
};
const mailDatas=
{
mailSubject: getRandomQuestionnaire[0].title,
mailPreheader: getRandomQuestionnaire[0].title,
mailTitle: getRandomQuestionnaire[0].title,
mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.stopMailPage+token,
mailHeaderLinkTxt: txt.mailStopMailLinkTxt,
mailMainContent: "<h4>"+txtQuestionnaireAccess.questionnaireRetryInfo+"</h4>"+getRandomQuestionnaire[0].introduction+"<p><b>"+txtQuestionnaire.estimatedTime+"</b> "+txtQuestionnaire.estimatedTimeOption[getRandomQuestionnaire[0].estimatedTime]+".</p>",
linksCTA: [{ url:getRandomQuestionnaire[0].url, txt:getRandomQuestionnaire[0].anchor }, { url:config.siteUrl+"/"+configQuestionnaires.dirWebQuestionnaire+"/"+getRandomQuestionnaire[0].slug+".html#questionnaire", txt:txtQuestionnaire.btnShowQuestionnaire }],
mailRecipientAddress: subscriptionsOk[i].email
}
toolMail.sendMail(subscriptionsOk[i].smtp, subscriptionsOk[i].email, getRandomQuestionnaire[0].title, tool.replaceAll(txtQuestionnaireAccess.questionnaireRetryInfoTxt, mapMail), "", mailDatas);
db["Subscription"].update({ lastProcessingAt: now }, { where: { id : subscriptionsOk[i].SubscriptionId }, limit:1 });
}
}
}
res.status(200).json(subscriptionsOk);
}
next();
}
catch(e)
{
next(e);
}
}
// FONCTIONS UTILITAIRES
// Retourne un booléen suivant si l'utilisateur a accès ou non à un questionnaire
const checkQuestionnaireAccess = async (UserId, QuestionnaireId) =>
{
const db = require("../models/index");
const checkQuestionnaireAccess=await db["QuestionnaireAccess"].findOne({ where: { UserId : UserId, QuestionnaireId : QuestionnaireId }, attributes: ["createdAt"] });
if(checkQuestionnaireAccess)
return true;
else
return false;
}
exports.checkQuestionnaireAccess = checkQuestionnaireAccess;
// Combien d'abonnements ont été enregistrés ces dernières 24 H ? depuis le début ? combien sont en premium ?
// Revoir : chercher les dates de paiement WP pour avoir stats 24 H ?
const getStatsSubscriptions = async () =>
{
const db = require("../models/index");
const getSubscriptions24H = await db.sequelize.query("SELECT `id` FROM `Subscriptions` WHERE `createdAt` > ADDDATE(NOW(), -1)", { type: QueryTypes.SELECT });
const getSubscriptionsTot = await db.sequelize.query("SELECT `id` FROM `Subscriptions`", { type: QueryTypes.SELECT });
const getSubscriptionsPremium = await db.sequelize.query("SELECT `id` FROM `Subscriptions` WHERE `numberOfDays` > :nb", { replacements: { nb: config.freeAccountTimingInDays }, type: QueryTypes.SELECT });
if(getSubscriptions24H && getSubscriptionsTot && getSubscriptionsPremium)
{
const stats =
{
nbSubscriptions24H : getSubscriptions24H.length,
nbSubscriptionsTot : getSubscriptionsTot.length,
nbSubscriptionsPremium : getSubscriptionsPremium.length
}
return stats;
}
else
return false;
}
exports.getStatsSubscriptions = getStatsSubscriptions;