Merge branch 'development' of https://github.com/Mailtrain-org/mailtrain into transactional-mail-v2

This commit is contained in:
Alexey Zinkevych 2019-03-31 11:52:42 +03:00
commit e3e1e7a086
153 changed files with 10570 additions and 9267 deletions

View file

@ -0,0 +1,54 @@
'use strict';
async function _logActivity(typeId, data) {
// TODO
}
/*
Extra data:
campaign:
- status : CampaignStatus
list:
- subscriptionId
- subscriptionStatus : SubscriptionStatus
- fieldId
- segmentId
- importId
- importStatus : ImportStatus
*/
async function logEntityActivity(entityTypeId, activityType, entityId, extraData = {}) {
const data = {
...extraData,
type: activityType,
entity: entityId
};
await _logActivity(entityTypeId, data);
}
async function logCampaignTrackerActivity(activityType, campaignId, listId, subscriptionId, extraData = {}) {
const data = {
...extraData,
type: activityType,
campaign: campaignId,
list: listId,
subscription: subscriptionId
};
await _logActivity('campaign_tracker', data);
}
async function logBlacklistActivity(activityType, email) {
const data = {
type: activityType,
email
};
await _logActivity('blacklist', data);
}
module.exports.logEntityActivity = logEntityActivity;
module.exports.logBlacklistActivity = logBlacklistActivity;
module.exports.logCampaignTrackerActivity = logCampaignTrackerActivity;

View file

@ -108,8 +108,8 @@ async function createConfig() {
default: {
preferIPv6: false,
ignoreIPv6: true,
processes: 1,
connections: 5,
processes: config.builtinZoneMTA.processes,
connections: config.builtinZoneMTA.connections,
pool: 'default'
}
}

View file

@ -0,0 +1,32 @@
'use strict';
function convertFileURLs(sourceCustom, fromEntityType, fromEntityId, toEntityType, toEntityId) {
function convertText(text) {
if (text) {
const fromUrl = `/files/${fromEntityType}/file/${fromEntityId}`;
const toUrl = `/files/${toEntityType}/file/${toEntityId}`;
const encodedFromUrl = encodeURIComponent(fromUrl);
const encodedToUrl = encodeURIComponent(toUrl);
text = text.split('[URL_BASE]' + fromUrl).join('[URL_BASE]' + toUrl);
text = text.split('[SANDBOX_URL_BASE]' + fromUrl).join('[SANDBOX_URL_BASE]' + toUrl);
text = text.split('[ENCODED_URL_BASE]' + encodedFromUrl).join('[ENCODED_URL_BASE]' + encodedToUrl);
text = text.split('[ENCODED_SANDBOX_URL_BASE]' + encodedFromUrl).join('[ENCODED_SANDBOX_URL_BASE]' + encodedToUrl);
}
return text;
}
sourceCustom.html = convertText(sourceCustom.html);
sourceCustom.text = convertText(sourceCustom.text);
if (sourceCustom.type === 'mosaico' || sourceCustom.type === 'mosaicoWithFsTemplate') {
sourceCustom.data.model = convertText(sourceCustom.data.model);
sourceCustom.data.model = convertText(sourceCustom.data.model);
sourceCustom.data.metadata = convertText(sourceCustom.data.metadata);
}
}
module.exports.convertFileURLs = convertFileURLs;

View file

@ -394,6 +394,18 @@ class CampaignSender {
try {
const info = await mailer.sendMassMail(mail);
status = SubscriptionStatus.SUBSCRIBED;
/*
ZoneMTA
info.response: 250 Message queued as 1691ad7f7ae00080fd
info.messageId: <e65c9386-e899-7d01-b21e-ec03c3a9d9b4@sathyasai.org>
Postal Mail Server
info.response: 250 OK
info.messageId: <xxxxxxxxx@xxx.xx> (postal messageId)
*/
console.log(`response: ${info.response} messageId: ${info.messageId}`);
response = info.response || info.messageId;
await knex('campaigns').where('id', campaign.id).increment('delivered');

View file

@ -1,151 +1,151 @@
'use strict';
const ReplacementBehavior = {
NONE: 1,
REPLACE: 2,
RENAME: 3
};
const entityTypes = {
namespace: {
entitiesTable: 'namespaces',
sharesTable: 'shares_namespace',
permissionsTable: 'permissions_namespace',
clientLink: id => `/namespaces/${id}`
},
list: {
entitiesTable: 'lists',
sharesTable: 'shares_list',
permissionsTable: 'permissions_list',
clientLink: id => `/lists/${id}`
},
customForm: {
entitiesTable: 'custom_forms',
sharesTable: 'shares_custom_form',
permissionsTable: 'permissions_custom_form',
clientLink: id => `/lists/forms/${id}`
},
campaign: {
entitiesTable: 'campaigns',
sharesTable: 'shares_campaign',
permissionsTable: 'permissions_campaign',
dependentPermissions: {
extraColumns: ['parent'],
getParent: entity => entity.parent
},
files: {
file: {
table: 'files_campaign_file',
permissions: {
view: 'viewFiles',
manage: 'manageFiles'
},
defaultReplacementBehavior: ReplacementBehavior.REPLACE
},
attachment: {
table: 'files_campaign_attachment',
permissions: {
view: 'viewAttachments',
manage: 'manageAttachments'
},
defaultReplacementBehavior: ReplacementBehavior.NONE
}
},
clientLink: id => `/campaigns/${id}`
},
template: {
entitiesTable: 'templates',
sharesTable: 'shares_template',
permissionsTable: 'permissions_template',
files: {
file: {
table: 'files_template_file',
permissions: {
view: 'viewFiles',
manage: 'manageFiles'
},
defaultReplacementBehavior: ReplacementBehavior.REPLACE
}
},
clientLink: id => `/templates/${id}`
},
sendConfiguration: {
entitiesTable: 'send_configurations',
sharesTable: 'shares_send_configuration',
permissionsTable: 'permissions_send_configuration',
clientLink: id => `/send-configurations/${id}`
},
report: {
entitiesTable: 'reports',
sharesTable: 'shares_report',
permissionsTable: 'permissions_report',
clientLink: id => `/reports/${id}`
},
reportTemplate: {
entitiesTable: 'report_templates',
sharesTable: 'shares_report_template',
permissionsTable: 'permissions_report_template',
clientLink: id => `/reports/templates/${id}`
},
mosaicoTemplate: {
entitiesTable: 'mosaico_templates',
sharesTable: 'shares_mosaico_template',
permissionsTable: 'permissions_mosaico_template',
files: {
file: {
table: 'files_mosaico_template_file',
permissions: {
view: 'viewFiles',
manage: 'manageFiles'
},
defaultReplacementBehavior: ReplacementBehavior.REPLACE
},
block: {
table: 'files_mosaico_template_block',
permissions: {
view: 'viewFiles',
manage: 'manageFiles'
},
defaultReplacementBehavior: ReplacementBehavior.REPLACE
}
},
clientLink: id => `/templates/mosaico/${id}`
},
user: {
entitiesTable: 'users',
clientLink: id => `/users/${id}`
}
};
const entityTypesWithPermissions = {};
for (const key in entityTypes) {
if (entityTypes[key].permissionsTable) {
entityTypesWithPermissions[key] = entityTypes[key];
}
}
function getEntityTypes() {
return entityTypes;
}
function getEntityTypesWithPermissions() {
return entityTypesWithPermissions;
}
function getEntityType(entityTypeId) {
const entityType = entityTypes[entityTypeId];
if (!entityType) {
throw new Error(`Unknown entity type ${entityTypeId}`);
}
return entityType
}
module.exports = {
getEntityTypes,
getEntityTypesWithPermissions,
getEntityType,
ReplacementBehavior
'use strict';
const ReplacementBehavior = {
NONE: 1,
REPLACE: 2,
RENAME: 3
};
const entityTypes = {
namespace: {
entitiesTable: 'namespaces',
sharesTable: 'shares_namespace',
permissionsTable: 'permissions_namespace',
clientLink: id => `/namespaces/${id}`
},
list: {
entitiesTable: 'lists',
sharesTable: 'shares_list',
permissionsTable: 'permissions_list',
clientLink: id => `/lists/${id}`
},
customForm: {
entitiesTable: 'custom_forms',
sharesTable: 'shares_custom_form',
permissionsTable: 'permissions_custom_form',
clientLink: id => `/lists/forms/${id}`
},
campaign: {
entitiesTable: 'campaigns',
sharesTable: 'shares_campaign',
permissionsTable: 'permissions_campaign',
dependentPermissions: {
extraColumns: ['parent'],
getParent: entity => entity.parent
},
files: {
file: {
table: 'files_campaign_file',
permissions: {
view: 'viewFiles',
manage: 'manageFiles'
},
defaultReplacementBehavior: ReplacementBehavior.REPLACE
},
attachment: {
table: 'files_campaign_attachment',
permissions: {
view: 'viewAttachments',
manage: 'manageAttachments'
},
defaultReplacementBehavior: ReplacementBehavior.NONE
}
},
clientLink: id => `/campaigns/${id}`
},
template: {
entitiesTable: 'templates',
sharesTable: 'shares_template',
permissionsTable: 'permissions_template',
files: {
file: {
table: 'files_template_file',
permissions: {
view: 'viewFiles',
manage: 'manageFiles'
},
defaultReplacementBehavior: ReplacementBehavior.REPLACE
}
},
clientLink: id => `/templates/${id}`
},
sendConfiguration: {
entitiesTable: 'send_configurations',
sharesTable: 'shares_send_configuration',
permissionsTable: 'permissions_send_configuration',
clientLink: id => `/send-configurations/${id}`
},
report: {
entitiesTable: 'reports',
sharesTable: 'shares_report',
permissionsTable: 'permissions_report',
clientLink: id => `/reports/${id}`
},
reportTemplate: {
entitiesTable: 'report_templates',
sharesTable: 'shares_report_template',
permissionsTable: 'permissions_report_template',
clientLink: id => `/reports/templates/${id}`
},
mosaicoTemplate: {
entitiesTable: 'mosaico_templates',
sharesTable: 'shares_mosaico_template',
permissionsTable: 'permissions_mosaico_template',
files: {
file: {
table: 'files_mosaico_template_file',
permissions: {
view: 'viewFiles',
manage: 'manageFiles'
},
defaultReplacementBehavior: ReplacementBehavior.REPLACE
},
block: {
table: 'files_mosaico_template_block',
permissions: {
view: 'viewFiles',
manage: 'manageFiles'
},
defaultReplacementBehavior: ReplacementBehavior.REPLACE
}
},
clientLink: id => `/templates/mosaico/${id}`
},
user: {
entitiesTable: 'users',
clientLink: id => `/users/${id}`
}
};
const entityTypesWithPermissions = {};
for (const key in entityTypes) {
if (entityTypes[key].permissionsTable) {
entityTypesWithPermissions[key] = entityTypes[key];
}
}
function getEntityTypes() {
return entityTypes;
}
function getEntityTypesWithPermissions() {
return entityTypesWithPermissions;
}
function getEntityType(entityTypeId) {
const entityType = entityTypes[entityTypeId];
if (!entityType) {
throw new Error(`Unknown entity type ${entityTypeId}`);
}
return entityType
}
module.exports = {
getEntityTypes,
getEntityTypesWithPermissions,
getEntityType,
ReplacementBehavior
}

View file

@ -5,6 +5,8 @@ const fork = require('child_process').fork;
const log = require('./log');
const path = require('path');
const {ImportStatus, RunStatus} = require('../../shared/imports');
const {ListActivityType} = require('../../shared/activity-log');
const activityLog = require('./activity-log');
let messageTid = 0;
let importerProcess;
@ -18,11 +20,17 @@ function spawn(callback) {
log.verbose('Importer', 'Spawning importer process');
knex.transaction(async tx => {
await tx('imports').where('status', ImportStatus.PREP_RUNNING).update({status: ImportStatus.PREP_SCHEDULED});
await tx('imports').where('status', ImportStatus.PREP_STOPPING).update({status: ImportStatus.PREP_FAILED});
const updateStatus = async (fromStatus, toStatus) => {
for (const impt of await tx('imports').where('status', fromStatus).select(['id', 'list'])) {
await tx('imports').where('id', impt.id).update({status: toStatus});
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, impt.list, {importId: impt.id, importStatus: toStatus});
}
}
await tx('imports').where('status', ImportStatus.RUN_RUNNING).update({status: ImportStatus.RUN_SCHEDULED});
await tx('imports').where('status', ImportStatus.RUN_STOPPING).update({status: ImportStatus.RUN_FAILED});
await updateStatus(ImportStatus.PREP_RUNNING, ImportStatus.PREP_SCHEDULED);
await updateStatus(ImportStatus.PREP_STOPPING, ImportStatus.PREP_FAILED);
await updateStatus(ImportStatus.RUN_RUNNING, ImportStatus.RUN_SCHEDULED);
await updateStatus(ImportStatus.RUN_STOPPING, ImportStatus.RUN_FAILED);
await tx('import_runs').where('status', RunStatus.RUNNING).update({status: RunStatus.SCHEDULED});
await tx('import_runs').where('status', RunStatus.STOPPING).update({status: RunStatus.FAILED});

View file

@ -1,8 +1,8 @@
'use strict';
const config = require('config');
const log = require('npmlog');
log.level = config.log.level;
'use strict';
const config = require('config');
const log = require('npmlog');
log.level = config.log.level;
module.exports = log;

View file

@ -173,7 +173,7 @@ async function _createTransport(sendConfiguration) {
}
};
if (mailerType === MailerType.ZONE_MTA || mailerSettings.zoneMTAType === ZoneMTAType.BUILTIN) {
if (mailerType === MailerType.ZONE_MTA && mailerSettings.zoneMtaType === ZoneMTAType.BUILTIN) {
transportOptions.host = config.builtinZoneMTA.host;
transportOptions.port = config.builtinZoneMTA.port;
transportOptions.secure = false;

View file

@ -1,24 +1,24 @@
'use strict';
const { enforce } = require('./helpers');
const interoperableErrors = require('../../shared/interoperable-errors');
const shares = require('../models/shares');
async function validateEntity(tx, entity) {
enforce(entity.namespace, 'Entity namespace not set');
if (!await tx('namespaces').where('id', entity.namespace).first()) {
throw new interoperableErrors.NamespaceNotFoundError();
}
}
async function validateMove(context, entity, existing, entityTypeId, createOperation, deleteOperation) {
if (existing.namespace !== entity.namespace) {
await shares.enforceEntityPermission(context, 'namespace', entity.namespace, createOperation);
await shares.enforceEntityPermission(context, entityTypeId, entity.id, deleteOperation);
}
}
module.exports = {
validateEntity,
validateMove
'use strict';
const { enforce } = require('./helpers');
const interoperableErrors = require('../../shared/interoperable-errors');
const shares = require('../models/shares');
async function validateEntity(tx, entity) {
enforce(entity.namespace, 'Entity namespace not set');
if (!await tx('namespaces').where('id', entity.namespace).first()) {
throw new interoperableErrors.NamespaceNotFoundError();
}
}
async function validateMove(context, entity, existing, entityTypeId, createOperation, deleteOperation) {
if (existing.namespace !== entity.namespace) {
await shares.enforceEntityPermission(context, 'namespace', entity.namespace, createOperation);
await shares.enforceEntityPermission(context, entityTypeId, entity.id, deleteOperation);
}
}
module.exports = {
validateEntity,
validateMove
};

View file

@ -1,15 +1,15 @@
'use strict';
const nodeify = require('nodeify');
module.exports.nodeifyPromise = nodeify;
module.exports.nodeifyFunction = (asyncFun) => {
return (...args) => {
const callback = args.pop();
const promise = asyncFun(...args);
return module.exports.nodeifyPromise(promise, callback);
};
};
'use strict';
const nodeify = require('nodeify');
module.exports.nodeifyPromise = nodeify;
module.exports.nodeifyFunction = (asyncFun) => {
return (...args) => {
const callback = args.pop();
const promise = asyncFun(...args);
return module.exports.nodeifyPromise(promise, callback);
};
};

View file

@ -129,7 +129,7 @@ async function _sendMail(list, email, template, locale, subjectKey, relativeUrls
};
if (list.default_form) {
const form = await forms.getById(contextHelpers.getAdminContext(), list.default_form);
const form = await forms.getById(contextHelpers.getAdminContext(), list.default_form, false);
text.template = form['mail_' + template + '_text'] || text.template;
html.template = form['mail_' + template + '_html'] || html.template;

View file

@ -13,6 +13,7 @@ function loadLanguage(longCode) {
}
loadLanguage('en-US');
loadLanguage('es-ES');
resourcesCommon['fk-FK'] = convertToFake(resourcesCommon['en-US']);
const resources = {};

View file

@ -1,72 +1,72 @@
'use strict';
const config = require('config');
const urllib = require('url');
const {anonymousRestrictedAccessToken} = require('../../shared/urls');
const {getLangCodeFromExpressLocale} = require('./translate');
function getTrustedUrlBase() {
return urllib.resolve(config.www.trustedUrlBase, '');
}
function getSandboxUrlBase() {
return urllib.resolve(config.www.sandboxUrlBase, '');
}
function getPublicUrlBase() {
return urllib.resolve(config.www.publicUrlBase, '');
}
function _getUrl(urlBase, path, opts) {
const url = new URL(path || '', urlBase);
if (opts && opts.locale) {
url.searchParams.append('locale', getLangCodeFromExpressLocale(opts.locale));
}
return url.toString();
}
function getTrustedUrl(path, opts) {
return _getUrl(config.www.trustedUrlBase, path || '', opts);
}
function getSandboxUrl(path, context, opts) {
if (context && context.user && context.user.restrictedAccessToken) {
return _getUrl(config.www.sandboxUrlBase, context.user.restrictedAccessToken + '/' + (path || ''), opts);
} else {
return _getUrl(config.www.sandboxUrlBase, anonymousRestrictedAccessToken + '/' + (path || ''), opts);
}
}
function getPublicUrl(path, opts) {
return _getUrl(config.www.publicUrlBase, path || '', opts);
}
function getTrustedUrlBaseDir() {
const mailtrainUrl = urllib.parse(config.www.trustedUrlBase);
return mailtrainUrl.pathname;
}
function getSandboxUrlBaseDir() {
const mailtrainUrl = urllib.parse(config.www.sandboxUrlBase);
return mailtrainUrl.pathname;
}
function getPublicUrlBaseDir() {
const mailtrainUrl = urllib.parse(config.www.publicUrlBase);
return mailtrainUrl.pathname;
}
module.exports = {
getTrustedUrl,
getSandboxUrl,
getPublicUrl,
getTrustedUrlBase,
getSandboxUrlBase,
getPublicUrlBase,
getTrustedUrlBaseDir,
getSandboxUrlBaseDir,
getPublicUrlBaseDir
'use strict';
const config = require('config');
const urllib = require('url');
const {anonymousRestrictedAccessToken} = require('../../shared/urls');
const {getLangCodeFromExpressLocale} = require('./translate');
function getTrustedUrlBase() {
return urllib.resolve(config.www.trustedUrlBase, '');
}
function getSandboxUrlBase() {
return urllib.resolve(config.www.sandboxUrlBase, '');
}
function getPublicUrlBase() {
return urllib.resolve(config.www.publicUrlBase, '');
}
function _getUrl(urlBase, path, opts) {
const url = new URL(path || '', urlBase);
if (opts && opts.locale) {
url.searchParams.append('locale', getLangCodeFromExpressLocale(opts.locale));
}
return url.toString();
}
function getTrustedUrl(path, opts) {
return _getUrl(config.www.trustedUrlBase, path || '', opts);
}
function getSandboxUrl(path, context, opts) {
if (context && context.user && context.user.restrictedAccessToken) {
return _getUrl(config.www.sandboxUrlBase, context.user.restrictedAccessToken + '/' + (path || ''), opts);
} else {
return _getUrl(config.www.sandboxUrlBase, anonymousRestrictedAccessToken + '/' + (path || ''), opts);
}
}
function getPublicUrl(path, opts) {
return _getUrl(config.www.publicUrlBase, path || '', opts);
}
function getTrustedUrlBaseDir() {
const mailtrainUrl = urllib.parse(config.www.trustedUrlBase);
return mailtrainUrl.pathname;
}
function getSandboxUrlBaseDir() {
const mailtrainUrl = urllib.parse(config.www.sandboxUrlBase);
return mailtrainUrl.pathname;
}
function getPublicUrlBaseDir() {
const mailtrainUrl = urllib.parse(config.www.publicUrlBase);
return mailtrainUrl.pathname;
}
module.exports = {
getTrustedUrl,
getSandboxUrl,
getPublicUrl,
getTrustedUrlBase,
getSandboxUrlBase,
getPublicUrlBase,
getTrustedUrlBaseDir,
getSandboxUrlBaseDir,
getPublicUrlBaseDir
};