diff --git a/controllers/subscription.js b/controllers/subscription.js index 80a9428..5c4b0bd 100644 --- a/controllers/subscription.js +++ b/controllers/subscription.js @@ -176,19 +176,20 @@ exports.notifyExpirationAccount= async(req, res, next) => } // 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é servis ce jour. - const subscriptionsOk = await db.sequelize.query("SELECT `Subscriptions`.`id` as SubscriptionId, `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 `UserId` NOT IN (SELECT DISTINCT `UserId` FROM `QuestionnaireAccesses` WHERE DATEDIFF(NOW(),`createdAt`) < 1 AND `selfCreatedOk` = false) LIMIT "+config.numberNewQuestionnaireAtSameTime, { type: QueryTypes.SELECT }); + // 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; + 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 }); @@ -204,7 +205,7 @@ exports.addNewQuestionnaireUsers = async(req, res, next) => const mapMail = { USER_NAME: subscriptionsOk[i].name, - QUESTIONNAIRE_URL: config.siteUrl+"/"+configQuestionnaires.dirWebQuestionnaire+"/"+newQuestionnaire[0].slug+".html#questionnaire", + QUESTIONNAIRE_URL: config.siteUrl+"/"+configQuestionnaires.dirWebQuestionnaire+"/"+newQuestionnaire[0].slug+".html", UNSUBSCRIBE_URL: config.siteUrl+"/"+configTpl.stopMailPage+token }; const mailDatas= @@ -221,14 +222,36 @@ exports.addNewQuestionnaireUsers = async(req, res, next) => 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 { - if(res.alerte) - res.alerte+="\n"+txt.noNewQuestionnaireForUser+subscriptionsOk[i].email; - else - res.alerte=txt.noNewQuestionnaireForUser+subscriptionsOk[i].UserId; - await toolMail.sendMail(0, config.adminEmail, "Abonné sans nouveau quiz", txt.noNewQuestionnaireForUser+subscriptionsOk[i].email, "

"+txt.noNewQuestionnaireForUser+subscriptionsOk[i].email+"

"); + // 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: "

"+txtQuestionnaireAccess.questionnaireRetryInfo+"

"+getRandomQuestionnaire[0].introduction+"

"+txtQuestionnaire.estimatedTime+" "+txtQuestionnaire.estimatedTimeOption[getRandomQuestionnaire[0].estimatedTime]+".

", + 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); @@ -277,109 +300,4 @@ const getStatsSubscriptions = async () => else return false; } -exports.getStatsSubscriptions = getStatsSubscriptions; - -/* Abandonné pour l'instant - * -// Recherche par mots-clés parmis les questionnaires auxquels l'utilisateur a accès !! où ceux auxquels il a répondu ? est-ce que cela a un intérêt ? -// Seul un certain nombre de résultats est renvoyé (pagination) -exports.searchQuestionnaires = async (req, res, next) => -{ - try - { - let search=tool.trimIfNotNull(req.body.searchQuestionnaires); - if(search === null || search === "" || search.length < config.minSearchQuestionnaires) - res.status(400).json(txtQuestionnaireAccess.searchIsNotLongEnough); - else - { - const db = require("../models/index"); - const getQuestionnaires=await db.sequelize.query("SELECT `Questionnaires`.`id` FROM `Questionnaires` INNER JOIN `QuestionnaireAccesses` WHERE `Questionnaires`.`id`=`QuestionnaireAccesses`.`QuestionnaireId` AND `QuestionnaireAccesses`.`UserId`=:id AND `Questionnaires`.`isPublished` = 1 AND (`Questionnaires`.`title` LIKE :search OR `Questionnaires`.`introduction` LIKE :search)", { replacements: { id: req.connectedUser.User.id, search: "%"+search+"%" }, type: QueryTypes.SELECT }); - let begin=0, end, output=""; - if(!tool.isEmpty(req.body.begin)) - begin=req.body.begin; - end=begin+configTpl.maxQuestionnairesByPage-1;// commence à 0 ! - if(req.body.output!==undefined) - output=req.body.output; - datas=await questionnaireCtrl.getListingsQuestionnairesOuput(getQuestionnaires, begin, end, output); - res.status(200).json(datas); - } - next(); - } - catch(e) - { - next(e); - } -} - -// Retourne un questionnaire au hasard parmi ceux auxquels l'utilisateur a accès -exports.getOneRandomQuestionnaire = async(req, res, next) => -{ - try - { - const questionnaires=await getUserQuestionnaires(req.params.id, false); - if(!questionnaires || questionnaires.ids.length===0) - res.status(404).json({ txtCode: "notFound" }); - else - { - const randomInt=tool.getRandomInt(0, questionnaires.ids.length); - const questionnaire=await questionnaireCtrl.searchQuestionnaireById(questionnaires.ids[randomInt]); - if(questionnaire) - res.status(200).json(questionnaire); - else - throw { message : txtQuestionnaireAccess.notFound+questionnaires.ids[randomInt], status : 404 }; - } - next(); - } - catch(e) - { - next(e); - } -} - -// Retourne la liste des questionnaires auxquels un utilisateur a accès. -// Ils sont listés par ordre de fraîcheur, les + récents étant en début de liste -// Si un questionnaire de début et un nombre de questionnaires sont fournis, l'ensemble des informations du questionnaire sont retournées -// Sinon, seulement les ids. -exports.getQuestionnairesByUser = async(req, res, next) => -{ - try - { - let datas; - if(req.params.begin && req.params.nb) - datas=await getUserQuestionnaires(req.params.id, true, req.params.begin, req.params.nb); - else - datas=await getUserQuestionnaires(req.params.id, false); - if(datas) - res.status(200).json(datas); - else - res.status(404).json(txtQuestionnaire.notFound); - next(); - } - catch(e) - { - next(e); - } -} -* -// Création du fichier des identifiants des questionnaires d'un utilisateur -// Les premiers étant les derniers -const creaUserQuestionnairesJson = async (UserId) => -{ - UserId=tool.trimIfNotNull(UserId); - if(UserId===null) - return false; - const db = require("../models/index"); - const userQuestionnaires=await db.sequelize.query("SELECT `QuestionnaireId` FROM `QuestionnaireAccesses` WHERE `UserId`=:id ORDER BY `createdAt` DESC", { replacements: { id: UserId }, type: QueryTypes.SELECT }); - if(userQuestionnaires) - { - const questionnairesId=[];// les ids suffisent et allègent le fichier - for(i in userQuestionnaires) - questionnairesId.push(userQuestionnaires[i].QuestionnaireId); - await toolFile.createJSON(config.dirCacheUsersQuestionnaires, UserId, { ids: questionnairesId }); - return { ids: questionnairesId }; - } - else - return false; -} -exports.creaUserQuestionnairesJson = creaUserQuestionnairesJson; -* */ \ No newline at end of file +exports.getStatsSubscriptions = getStatsSubscriptions; \ No newline at end of file diff --git a/lang/fr/questionnaireaccess.js b/lang/fr/questionnaireaccess.js index 8b6645a..5c675e2 100644 --- a/lang/fr/questionnaireaccess.js +++ b/lang/fr/questionnaireaccess.js @@ -1,5 +1,7 @@ module.exports = { - "notFound" : "Les informations d'un questionnaire attribué à un abonné n'ont pas été trouvées : ", - "searchIsNotLongEnough" : "Merci de fournir un mot-clés d'au moins deux caractères pour votre recherche." + notFound : "Les informations d'un questionnaire attribué à un abonné n'ont pas été trouvées : ", + searchIsNotLongEnough : "Merci de fournir un mot-clés d'au moins deux caractères pour votre recherche.", + questionnaireRetryInfo : "Vous avez déjà reçu tous les quizs publiés à ce jour ! En attendant la publication de nouveaux quizs, vous pouvez peut-être réessayer le suivant ?", + questionnaireRetryInfoTxt : "Bonjour USER_NAME,\n\nVous avez déjà reçu tous les quizs publiés à ce jour ! En attendant la publication de nouveaux quizs, vous pouvez peut-être réessayer le suivant ?\n\nQUESTIONNAIRE_URL\n\nBonne lecture !\n\nStopper les envois ?\nUNSUBSCRIBE_URL", }; \ No newline at end of file diff --git a/models/Subscription.js b/models/Subscription.js index b641beb..7b7ff48 100644 --- a/models/Subscription.js +++ b/models/Subscription.js @@ -65,6 +65,11 @@ module.exports = (sequelize, DataTypes) => } } }, + lastProcessingAt: + { + type: DataTypes.DATE, allowNull: false, defaultValue: "1970-01-01", + comment: "Date of last subscription processing (sending quiz, etc.)." + } }, { } diff --git a/models/wikilerni-crea.sql b/models/wikilerni-crea.sql index 58d1310..b9883ed 100644 --- a/models/wikilerni-crea.sql +++ b/models/wikilerni-crea.sql @@ -3,7 +3,7 @@ -- https://www.phpmyadmin.net/ -- -- Client : localhost:3306 --- Généré le : Ven 07 Août 2020 à 11:50 +-- Généré le : Mer 26 Août 2020 à 12:22 -- Version du serveur : 5.7.31-0ubuntu0.18.04.1 -- Version de PHP : 7.2.24-0ubuntu0.18.04.6 @@ -190,6 +190,7 @@ CREATE TABLE `Subscriptions` ( `noticeOk` tinyint(1) NOT NULL DEFAULT '0', `createdAt` datetime NOT NULL, `updatedAt` datetime NOT NULL, + `lastProcessingAt` date NOT NULL DEFAULT '1970-01-01' COMMENT 'Date of last subscription processing (sending quiz, etc.).', `UserId` int(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; @@ -359,22 +360,22 @@ ALTER TABLE `Users` -- AUTO_INCREMENT pour la table `Answers` -- ALTER TABLE `Answers` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=31; + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=29; -- -- AUTO_INCREMENT pour la table `Choices` -- ALTER TABLE `Choices` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=61; + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1007; -- -- AUTO_INCREMENT pour la table `Illustrations` -- ALTER TABLE `Illustrations` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=17; + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=37; -- -- AUTO_INCREMENT pour la table `Links` -- ALTER TABLE `Links` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=15; + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=35; -- -- AUTO_INCREMENT pour la table `Pauses` -- @@ -384,37 +385,37 @@ ALTER TABLE `Pauses` -- AUTO_INCREMENT pour la table `Payments` -- ALTER TABLE `Payments` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6; + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT pour la table `Questionnaires` -- ALTER TABLE `Questionnaires` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=30; + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=50; -- -- AUTO_INCREMENT pour la table `Questions` -- ALTER TABLE `Questions` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=43; + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=322; -- -- AUTO_INCREMENT pour la table `Subscriptions` -- ALTER TABLE `Subscriptions` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=18; + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=21; -- -- AUTO_INCREMENT pour la table `Tags` -- ALTER TABLE `Tags` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=20; + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=54; -- -- AUTO_INCREMENT pour la table `UserDeleteds` -- ALTER TABLE `UserDeleteds` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=10; + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT pour la table `Users` -- ALTER TABLE `Users` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=26; + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=29; -- -- Contraintes pour les tables exportées --