// 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 groupCtrl = require("./group"); const questionnaireCtrl = require("./questionnaire"); const userCtrl = require("./user"); // Clic sur lien de désabonnement aux email exports.unsubscribeLink = async (req, res, next) => { try { const db = require("../models/index"); const userDatas= await userCtrl.checkTokenUser(req.params.token); await db["Subscription"].update({ receiptDays: "" }, { 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" }; let mailSubject; if(mail!=="first") mailSubject=txt.mailExpirationRelaunchTxt+txt.mailExpirationSubject; else mailSubject=txt.mailExpirationSubject; const mailDatas = { mailSubject: mailSubject, mailPreheader: mailSubject, mailTitle: mailSubject, 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"); // 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); } } // Diffuse aux abonnés un nouvel élément de quiz, en parcourant ceux d'un même groupe, dans l'ordre où ils ont été enregistrés. // Quand tous les éléments ont été envoyés, l'abonné reçoit en plus le quiz regroupant les questions des éléments du groupe. // Si il n'y plus de nouveautés pour l'utilisateur, on lui envoie un quiz au hasard parmi ceux déjà envoyés pour le retester. exports.addNewQuestionnaireUsers = async(req, res, next) => { try { const db = require("../models/index"); // Utilisateurs dont l'abonnement est toujours actif et souhaitant recevoir des quizs le jour de la semaine en cours. // Le tout en heure locale et en ignorant ceux qui ont déjà été traités ces dernières 24H. const subscriptionsOk = await db.sequelize.query("SELECT `Subscriptions`.`id` as SubscriptionId, `Subscriptions`.`lastProcessingAt`, `UserId`, `name`, `email`, `smtp`, `language`, `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.maxQuestionnaireSendedAtSameTime, { type: QueryTypes.SELECT }); if(subscriptionsOk.length === 0) res.status(200).json({ message: txt.allSubscriptionProcessed }); else { let lastSended, lastSendedGroup, lastSendedGroupNbElements, elementToSend, getElementToSend, accessSave, token, textMail, now=new Date(); for (let i in subscriptionsOk) { // On commence à chercher le dernier élément auquel il a été donné accès à l'utilisateur pour comparer sa date à celle du dernier envoi lastSended = await db.sequelize.query("SELECT DATE_FORMAT(`QuestionnaireAccesses`.`createdAt`, \"%Y-%m-%d\") as `dateSended`, `QuestionnaireId`, `GroupId`, `rankInGroup` FROM `QuestionnaireAccesses` INNER JOIN `Questionnaires` WHERE `Questionnaires`.`id`=`QuestionnaireAccesses`.`QuestionnaireId` AND `UserId`="+subscriptionsOk[i].UserId+" AND `selfCreatedOk`= 0 AND `GroupId` IS NOT NULL ORDER BY `QuestionnaireAccesses`.`CreatedAt` DESC LIMIT 1", { type: QueryTypes.SELECT }); elementToSend="", lastSendedGroup=""; // Il y a déjà eu au moins un envoi et le dernier envoi était l'élément d'un quiz groupé : if(lastSended.length !==0 && lastSended[0].dateSended == subscriptionsOk[i].lastProcessingAt) { // Si l'élément envoyé était le dernier de son groupe, on va lui envoyer le lien du quiz du groupe : lastSendedGroup = await groupCtrl.searchGroupById(lastSended[0].GroupId); lastSendedGroupNbElements = lastSendedGroup.Questionnaires.length; if(!tool.isEmpty(lastSendedGroupNbElements) && lastSendedGroup.Questionnaires[(lastSendedGroupNbElements-1)].id == lastSended[0].QuestionnaireId) elementToSend = lastSendedGroup.Group; else { // dans ce cas, on va envoyer l'élément suivant du groupe : for(let j in lastSendedGroup.Questionnaires) if(lastSendedGroup.Questionnaires[j].id == lastSended[0].QuestionnaireId) elementToSend = await questionnaireCtrl.searchQuestionnaireById(lastSendedGroup.Questionnaires[(parseInt(j)+1)].id, true); } } else { // Soit il s'agit du premier envoi d'un abonnement, soit le dernier envoi était un quiz groupé. // Dans ces deux cas, on va envoyer le premier élément non encore envoyé d'un groupe : getElementToSend = await db.sequelize.query("SELECT `Questionnaires`.`id` FROM `Questionnaires` INNER JOIN `Groups` WHERE `Questionnaires`.`GroupId`=`Groups`.`id` AND `Questionnaires`.`isPublished`=1 AND `Groups`.`language`='"+subscriptionsOk[i].language+"' AND `Groups`.`publishingAt` < NOW() AND `Questionnaires`.`id` NOT IN (SELECT `QuestionnaireId` FROM `QuestionnaireAccesses` where `UserId`="+subscriptionsOk[i].UserId+") ORDER BY `Groups`.`publishingAt`,`rankInGroup` ASC", { type: QueryTypes.SELECT }); if(getElementToSend.length !== 0) elementToSend = await questionnaireCtrl.searchQuestionnaireById(getElementToSend[0].id, true); } // console.log(elementToSend); if(!tool.isEmpty(elementToSend)) { token=jwt.sign({ userId: subscriptionsOk[i].UserId }, config.tokenPrivateKey, { expiresIn: config.tokenUnsubscribeLinkTimeInDays }); if(elementToSend.Questionnaire !== undefined)// il s'agit de l'élément d'un group { const mapMail = { USER_NAME: subscriptionsOk[i].name, QUESTIONNAIRE_URL: config.siteUrl+"/"+configQuestionnaires.dirWebQuestionnaires+"/"+elementToSend.Questionnaire.slug+".html", UNSUBSCRIBE_URL: config.siteUrl+"/"+configTpl.stopMailPage+token }; const mailDatas = { mailSubject: elementToSend.Questionnaire.title, mailPreheader: elementToSend.Questionnaire.title, mailTitle: elementToSend.Questionnaire.title, mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.stopMailPage+token, mailHeaderLinkTxt: txt.mailStopMailLinkTxt, mailMainContent: elementToSend.Questionnaire.introduction, mailMainContentLinks : elementToSend.Questionnaire.Links, linksCTA: [{ url:config.siteUrl+"/"+configQuestionnaires.dirWebQuestionnaires+"/"+elementToSend.Questionnaire.slug+".html", txt:txtGeneral.btnShowOnWebSite.replace("#SITE_NAME", config.siteName) }], mailRecipientAddress: subscriptionsOk[i].email } toolMail.sendMail(subscriptionsOk[i].smtp, subscriptionsOk[i].email, mailDatas.mailSubject, tool.replaceAll(txt.mailNewElementForGroupTxt, mapMail), "", mailDatas); // on enregistre le fait que cet élément a déjà été envoyé : accessSave = await db["QuestionnaireAccess"].create({ QuestionnaireId: elementToSend.Questionnaire.id, UserId: subscriptionsOk[i].UserId, selfCreatedOk: false }); /// à remplacer pour fonction listant les derniers éléments "lus" par l'utilisateur : //answerCtrl.creaUserQuestionnairesWithoutAnswerJson(subscriptionsOk[i].UserId); } else // envoi du quiz du groupe { const mapMail = { USER_NAME: subscriptionsOk[i].name, QUESTIONNAIRE_URL: config.siteUrl+"/"+configQuestionnaires.dirWebGroups+"/"+elementToSend.slug+".html", UNSUBSCRIBE_URL: config.siteUrl+"/"+configTpl.stopMailPage+token }; const mailDatas = { mailSubject: elementToSend.title+" ("+txtQuestionnaire.questionnairesName+")", mailPreheader: elementToSend.title, mailTitle: elementToSend.title, mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.stopMailPage+token, mailHeaderLinkTxt: txt.mailStopMailLinkTxt, mailMainContent: elementToSend.introduction, linksCTA: [{ url:config.siteUrl+"/"+configQuestionnaires.dirWebGroups+"/"+elementToSend.slug+".html", txt:txtQuestionnaire.btnShowQuestionnaire }], mailRecipientAddress: subscriptionsOk[i].email } toolMail.sendMail(subscriptionsOk[i].smtp, subscriptionsOk[i].email, mailDatas.mailSubject, tool.replaceAll(txt.mailNewQuestionnaireBodyTxt, mapMail), "", mailDatas); } } else { // L'utilisateur a déjà reçu tous les élements et quizs des groupes publiés. Dans ces cas, on tire un au hasard un quiz groupé pour le lui renvoyer getElementToSend = await db.sequelize.query("SELECT `id`, `title`, `slug`, `introduction` FROM `Groups` WHERE `publishingAt` < NOW() 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.dirWebQuestionnaires+"/"+getElementToSend[0].slug+".html", UNSUBSCRIBE_URL: config.siteUrl+"/"+configTpl.stopMailPage+token }; const mailDatas= { mailSubject: getElementToSend[0].title+" ("+txtQuestionnaire.questionnairesName+")", mailPreheader: getElementToSend[0].title, mailTitle: getElementToSend[0].title, mailHeaderLinkUrl: config.siteUrl+"/"+configTpl.stopMailPage+token, mailHeaderLinkTxt: txt.mailStopMailLinkTxt, mailMainContent: "

"+txtQuestionnaireAccess.questionnaireRetryInfo+"

"+getElementToSend[0].introduction, linksCTA: [{ url:config.siteUrl+"/"+configQuestionnaires.dirWebGroups+"/"+getElementToSend[0].slug+".html", txt:txtQuestionnaire.btnShowQuestionnaire }], mailRecipientAddress: subscriptionsOk[i].email } toolMail.sendMail(subscriptionsOk[i].smtp, subscriptionsOk[i].email, mailDatas.mailSubject, tool.replaceAll(txtQuestionnaireAccess.questionnaireRetryInfoTxt, mapMail), "", mailDatas); } // dans tout les cas, on enregistre l'envoi : //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;