Work in progress on refactoring all mail sending to use the message sender an sender workers. No yet finished.

This commit is contained in:
Tomas Bures 2019-06-29 23:19:56 +02:00
parent 355e03900a
commit 4e9f6bd57b
22 changed files with 811 additions and 444 deletions

View file

@ -17,11 +17,11 @@ const {SubscriptionStatus} = require('../../shared/lists');
const subscriptions = require('./subscriptions');
const segments = require('./segments');
const senders = require('../lib/senders');
const {LinkId} = require('./links');
const links = require('./links');
const feedcheck = require('../lib/feedcheck');
const contextHelpers = require('../lib/context-helpers');
const {convertFileURLs} = require('../lib/campaign-content');
const {CampaignSender, MessageType} = require('../lib/campaign-sender');
const messageSender = require('../lib/message-sender');
const lists = require('./lists');
const {EntityActivityType, CampaignActivityType} = require('../../shared/activity-log');
@ -298,7 +298,7 @@ async function listOpensDTAjax(context, campaignId, params) {
return this.from('campaign_links')
.where('campaign_links.campaign', campaignId)
.where('campaign_links.list', cpgList.list)
.where('campaign_links.link', LinkId.OPEN)
.where('campaign_links.link', links.LinkId.OPEN)
.as('related_campaign_links');
},
'related_campaign_links.subscription', subsTable + '.id')
@ -705,16 +705,12 @@ async function getMessageByCid(messageCid, withVerpHostname = false) { // withVe
}
async function getMessageByResponseId(responseId) {
return await knex.transaction(async tx => {
const message = await tx('campaign_messages')
.where('campaign_messages.response_id', responseId)
.select([
'campaign_messages.id', 'campaign_messages.campaign', 'campaign_messages.list', 'campaign_messages.subscription', 'campaign_messages.status'
])
.first();
return message;
});
return await knex('campaign_messages')
.where('campaign_messages.response_id', responseId)
.select([
'campaign_messages.id', 'campaign_messages.campaign', 'campaign_messages.list', 'campaign_messages.subscription', 'campaign_messages.status'
])
.first();
}
const statusFieldMapping = {
@ -747,7 +743,6 @@ async function _changeStatusByMessageTx(tx, context, message, subscriptionStatus
}
async function changeStatusByCampaignCidAndSubscriptionIdTx(tx, context, campaignCid, listId, subscriptionId, subscriptionStatus) {
const campaign = await tx('campaigns').where('cid', campaignCid);
const message = await tx('campaign_messages')
.innerJoin('campaigns', 'campaign_messages.campaign', 'campaigns.id')
.where('campaigns.cid', campaignCid)
@ -856,7 +851,7 @@ async function getSubscribersQueryGeneratorTx(tx, campaignId) {
}
}
async function _changeStatus(context, campaignId, permittedCurrentStates, newState, invalidStateMessage, scheduled = null) {
async function _changeStatus(context, campaignId, permittedCurrentStates, newState, invalidStateMessage, startAt) {
await knex.transaction(async tx => {
const entity = await getByIdTx(tx, context, campaignId, false);
@ -866,10 +861,18 @@ async function _changeStatus(context, campaignId, permittedCurrentStates, newSta
throw new interoperableErrors.InvalidStateError(invalidStateMessage);
}
await tx('campaigns').where('id', campaignId).update({
const updateData = {
status: newState,
scheduled
});
};
if (startAt !== undefined) {
updateData.scheduled = startAt;
if (!startAt || startAt.valueOf() < Date.now()) {
updateData.start_at = new Date();
}
}
await tx('campaigns').where('id', campaignId).update(updateData);
await activityLog.logEntityActivity('campaign', CampaignActivityType.STATUS_CHANGE, campaignId, {status: newState});
});
@ -929,8 +932,8 @@ async function getStatisticsOpened(context, id) {
return await knex.transaction(async tx => {
await shares.enforceEntityPermissionTx(tx, context, 'campaign', id, 'viewStats');
const devices = await tx('campaign_links').where('campaign', id).where('link', LinkId.OPEN).groupBy('device_type').select('device_type AS key').count('* as count');
const countries = await tx('campaign_links').where('campaign', id).where('link', LinkId.OPEN).groupBy('country').select('country AS key').count('* as count');
const devices = await tx('campaign_links').where('campaign', id).where('link', links.LinkId.OPEN).groupBy('device_type').select('device_type AS key').count('* as count');
const countries = await tx('campaign_links').where('campaign', id).where('link', links.LinkId.OPEN).groupBy('country').select('country AS key').count('* as count');
return {
devices,
@ -959,7 +962,7 @@ async function testSend(context, data) {
await knex.transaction(async tx => {
const processSubscriber = async (sendConfigurationId, listId, subscriptionId, messageData) => {
await CampaignSender.queueMessageTx(tx, sendConfigurationId, listId, subscriptionId, MessageType.TEST, messageData);
await messageSender.queueCampaignMessageTx(tx, sendConfigurationId, listId, subscriptionId, messageSender.MessageType.TEST, messageData);
await activityLog.logEntityActivity('campaign', CampaignActivityType.TEST_SEND, campaignId, {list: listId, subscription: subscriptionId});
};

View file

@ -21,7 +21,7 @@ async function addConfirmation(listId, action, ip, data) {
*/
async function takeConfirmation(cid) {
return await knex.transaction(async tx => {
const entry = await tx('confirmations').select(['cid', 'list', 'action', 'ip', 'data']).where('cid', cid).first();
const entry = await tx('confirmations').select(['cid', 'list', 'action', 'ip', 'data']).where('cid', cid).forUpdate().first();
if (!entry) {
return false;

View file

@ -504,7 +504,7 @@ async function serverValidate(context, listId, data) {
async function _validateAndPreprocess(tx, listId, groupedFieldsMap, entity, meta, isCreate) {
enforce(entity.email, 'Email must be set');
const existingWithKeyQuery = tx(getSubscriptionTableName(listId)).where('hash_email', hashEmail(entity.email));
const existingWithKeyQuery = tx(getSubscriptionTableName(listId)).where('hash_email', hashEmail(entity.email)).forUpdate();
if (!isCreate) {
existingWithKeyQuery.whereNot('id', entity.id);

View file

@ -153,7 +153,7 @@ async function remove(context, id) {
const MAX_EMAIL_COUNT = 100;
async function sendAsTransactionalEmail(context, templateId, sendConfigurationId, emails, subject, mergeTags) {
// TODO - Update this to use CampaignSender.queueMessageTx (with renderedHtml and renderedText)
// TODO - Update this to use MessageSender.queueMessageTx (with renderedHtml and renderedText)
if (emails.length > MAX_EMAIL_COUNT) {
throw new Error(`Cannot send more than ${MAX_EMAIL_COUNT} emails at once`);

View file

@ -12,6 +12,8 @@ const crypto = require('crypto');
const settings = require('./settings');
const {getTrustedUrl} = require('../lib/urls');
const { tUI } = require('../lib/translate');
const messageSender = require('../lib/message-sender');
const {getSystemSendConfigurationId} = require('../../shared/send-configurations');
const bluebird = require('bluebird');
@ -19,8 +21,6 @@ const bcrypt = require('bcrypt-nodejs');
const bcryptHash = bluebird.promisify(bcrypt.hash.bind(bcrypt));
const bcryptCompare = bluebird.promisify(bcrypt.compare.bind(bcrypt));
const mailers = require('../lib/mailers');
const passport = require('../lib/passport');
const namespaceHelpers = require('../lib/namespace-helpers');
@ -297,26 +297,31 @@ async function resetAccessToken(userId) {
async function sendPasswordReset(locale, usernameOrEmail) {
enforce(passport.isAuthMethodLocal, 'Local user management is required');
const resetToken = crypto.randomBytes(16).toString('base64').replace(/[^a-z0-9]/gi, '');
let user;
await knex.transaction(async tx => {
const user = await tx('users').where('username', usernameOrEmail).orWhere('email', usernameOrEmail).select(['id', 'username', 'email', 'name']).first();
user = await tx('users').where('username', usernameOrEmail).orWhere('email', usernameOrEmail).select(['id', 'username', 'email', 'name']).forUpdate().first();
if (user) {
const resetToken = crypto.randomBytes(16).toString('base64').replace(/[^a-z0-9]/gi, '');
await tx('users').where('id', user.id).update({
reset_token: resetToken,
reset_expire: new Date(Date.now() + 60 * 60 * 1000)
});
}
// We intentionally silently ignore the situation when user is not found. This is not to reveal if a user exists in the system.
const { adminEmail } = await settings.get(contextHelpers.getAdminContext(), ['adminEmail']);
});
const mailer = await mailers.getOrCreateMailer();
await mailer.sendTransactionalMailBasedOnTemplate({
to: {
address: user.email
},
subject: tUI('mailerPasswordChangeRequest', locale)
}, {
if (user) {
await messageSender.queueSubscriptionMessage(
getSystemSendConfigurationId(),
{
address: user.email
},
tUI('mailerPasswordChangeRequest', locale),
null,
{
html: 'users/password-reset-html.hbs',
text: 'users/password-reset-text.hbs',
locale,
@ -326,10 +331,9 @@ async function sendPasswordReset(locale, usernameOrEmail) {
name: user.name,
confirmUrl: getTrustedUrl(`login/reset/${encodeURIComponent(user.username)}/${encodeURIComponent(resetToken)}`)
}
});
}
// We intentionally silently ignore the situation when user is not found. This is not to reveal if a user exists in the system.
});
}
);
}
}
async function isPasswordResetTokenValid(username, resetToken) {