- Fix for #890
- "Channels" feature - Shoutout config param rendered on the homepage - "Clone" feature for campaigns
This commit is contained in:
parent
00432e6cfe
commit
d170548cfa
25 changed files with 1009 additions and 525 deletions
|
@ -357,7 +357,7 @@ async function rawGetByTx(tx, key, id) {
|
|||
.leftJoin('campaign_lists', 'campaigns.id', 'campaign_lists.campaign')
|
||||
.groupBy('campaigns.id')
|
||||
.select([
|
||||
'campaigns.id', 'campaigns.cid', 'campaigns.name', 'campaigns.description', 'campaigns.namespace', 'campaigns.status', 'campaigns.type', 'campaigns.source',
|
||||
'campaigns.id', 'campaigns.cid', 'campaigns.name', 'campaigns.description', 'campaigns.channel', 'campaigns.namespace', 'campaigns.status', 'campaigns.type', 'campaigns.source',
|
||||
'campaigns.send_configuration', 'campaigns.from_name_override', 'campaigns.from_email_override', 'campaigns.reply_to_override', 'campaigns.subject',
|
||||
'campaigns.data', 'campaigns.click_tracking_disabled', 'campaigns.open_tracking_disabled', 'campaigns.unsubscribe_url', 'campaigns.scheduled',
|
||||
'campaigns.delivered', 'campaigns.unsubscribed', 'campaigns.bounced', 'campaigns.complained', 'campaigns.blacklisted', 'campaigns.opened', 'campaigns.clicks',
|
||||
|
@ -412,6 +412,7 @@ async function getByIdTx(tx, context, id, withPermissions = true, content = Cont
|
|||
} else if (content === Content.ONLY_SOURCE_CUSTOM) {
|
||||
entity = {
|
||||
id: entity.id,
|
||||
channel: entity.channel,
|
||||
send_configuration: entity.send_configuration,
|
||||
|
||||
data: {
|
||||
|
@ -454,6 +455,8 @@ async function _validateAndPreprocess(tx, context, entity, isCreate, content) {
|
|||
|
||||
if (entity.source === CampaignSource.TEMPLATE || entity.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'template', entity.data.sourceTemplate, 'view');
|
||||
} else if (entity.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'campaign', entity.data.sourceCampaign, 'view');
|
||||
}
|
||||
|
||||
enforce(Number.isInteger(entity.source));
|
||||
|
@ -481,6 +484,10 @@ async function _createTx(tx, context, entity, content) {
|
|||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createCampaign');
|
||||
|
||||
if (entity.channel) {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'channel', entity.channel, 'createCampaign');
|
||||
}
|
||||
|
||||
let copyFilesFrom = null;
|
||||
if (entity.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
copyFilesFrom = {
|
||||
|
@ -570,6 +577,13 @@ async function createRssTx(tx, context, entity) {
|
|||
return await _createTx(tx, context, entity, Content.RSS_ENTRY);
|
||||
}
|
||||
|
||||
async function _validateChannelMoveTx(tx, context, entity, existing) {
|
||||
if (existing.channel !== entity.channel) {
|
||||
await shares.enforceEntityPermission(context, 'channel', entity.channel, 'createCampaign');
|
||||
await shares.enforceEntityPermission(context, 'campaign', entity.id, 'delete');
|
||||
}
|
||||
}
|
||||
|
||||
async function updateWithConsistencyCheck(context, entity, content) {
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'campaign', entity.id, 'edit');
|
||||
|
@ -585,11 +599,13 @@ async function updateWithConsistencyCheck(context, entity, content) {
|
|||
|
||||
let filteredEntity = filterObject(entity, allowedKeysUpdate);
|
||||
if (content === Content.ALL) {
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'campaign', 'createCampaign', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'campaign', 'createCampaign', 'delete');
|
||||
await _validateChannelMoveTx(tx, context, entity, existing);
|
||||
|
||||
} else if (content === Content.WITHOUT_SOURCE_CUSTOM) {
|
||||
filteredEntity.data.sourceCustom = existing.data.sourceCustom;
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'campaign', 'createCampaign', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'campaign', 'createCampaign', 'delete');
|
||||
await _validateChannelMoveTx(tx, context, entity, existing);
|
||||
|
||||
} else if (content === Content.ONLY_SOURCE_CUSTOM) {
|
||||
const data = existing.data;
|
||||
|
|
|
@ -16,8 +16,8 @@ const dependencyHelpers = require('../lib/dependency-helpers');
|
|||
const {EntityActivityType, CampaignActivityType} = require('../../shared/activity-log');
|
||||
const activityLog = require('../lib/activity-log');
|
||||
|
||||
const allowedKeys = ['name', 'description', 'namespace', 'cpg_name', 'cpg_description',
|
||||
'send_configuration', 'from_name_override', 'from_email_override', 'reply_to_override', 'subject', 'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url', 'source'];
|
||||
const allowedKeys = new Set(['name', 'description', 'namespace', 'cpg_name', 'cpg_description',
|
||||
'send_configuration', 'from_name_override', 'from_email_override', 'reply_to_override', 'subject', 'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url', 'source']);
|
||||
|
||||
|
||||
function hash(entity) {
|
||||
|
@ -43,12 +43,27 @@ async function listDTAjax(context, params) {
|
|||
);
|
||||
}
|
||||
|
||||
async function _getByTx(tx, key, id, withPermissions = true) {
|
||||
async function listWithCreateCampaignPermissionDTAjax(context, params) {
|
||||
return await dtHelpers.ajaxListWithPermissions(
|
||||
context,
|
||||
[{ entityTypeId: 'channel', requiredOperations: ['createCampaign'] }],
|
||||
params,
|
||||
builder => {
|
||||
builder = builder.from('channels')
|
||||
.innerJoin('namespaces', 'namespaces.id', 'channels.namespace');
|
||||
return builder;
|
||||
},
|
||||
['channels.id', 'channels.name', 'channels.cid', 'channels.description', 'namespaces.name']
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
async function _getByTx(tx, context, key, id, withPermissions = true) {
|
||||
const entity = await tx('channels').where('channels.' + key, id)
|
||||
.leftJoin('channel_lists', 'channels.id', 'channel_lists.channel')
|
||||
.groupBy('channels.id')
|
||||
.select([
|
||||
'channels.id', 'channels.name', 'channels.cid', 'channels.description', 'channels.namespace', 'channels.source',
|
||||
'channels.id', 'channels.name', 'channels.cid', 'channels.description', 'channels.namespace', 'channels.cpg_name', 'channels.cpg_description', 'channels.source',
|
||||
'channels.send_configuration', 'channels.from_name_override', 'channels.from_email_override', 'channels.reply_to_override', 'channels.subject',
|
||||
'channels.data', 'channels.click_tracking_disabled', 'channels.open_tracking_disabled', 'channels.unsubscribe_url',
|
||||
knex.raw(`GROUP_CONCAT(CONCAT_WS(\':\', channel_lists.list, channel_lists.segment) ORDER BY channel_lists.id SEPARATOR \';\') as lists`)
|
||||
|
@ -82,7 +97,7 @@ async function _getByTx(tx, key, id, withPermissions = true) {
|
|||
async function getByIdTx(tx, context, id, withPermissions = true) {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'channel', id, 'view');
|
||||
|
||||
return await _getByTx(tx, 'id', id, withPermissions);
|
||||
return await _getByTx(tx, context, 'id', id, withPermissions);
|
||||
}
|
||||
|
||||
async function getById(context, id, withPermissions = true) {
|
||||
|
@ -97,12 +112,13 @@ async function _validateAndPreprocess(tx, context, entity, isCreate) {
|
|||
if (entity.source !== null) {
|
||||
enforce(Number.isInteger(entity.source));
|
||||
|
||||
if (entity.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
if (entity.source === CampaignSource.TEMPLATE || entity.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'template', entity.data.sourceTemplate, 'view');
|
||||
} else if (entity.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'campaign', entity.data.sourceCampaign, 'view');
|
||||
} else if (entity.source === CampaignSource.CUSTOM) {
|
||||
enforce(allTagLanguages.includes(entity.data.sourceCustom.tag_language), `Invalid tag language '${entity.data.sourceCustom.tag_language}'`);
|
||||
} else if (entity.source === CampaignSource.URL) {
|
||||
} else {
|
||||
enforce(false, 'Unknown channel source');
|
||||
}
|
||||
|
@ -156,7 +172,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'channel', entity.id, 'edit');
|
||||
|
||||
const existing = await _getByTx(tx, 'id', entity.id, false);
|
||||
const existing = await _getByTx(tx, context, 'id', entity.id, false);
|
||||
|
||||
const existingHash = hash(existing);
|
||||
if (existingHash !== entity.originalHash) {
|
||||
|
@ -166,7 +182,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
await _validateAndPreprocess(tx, context, entity, false);
|
||||
|
||||
let filteredEntity = filterObject(entity, allowedKeys);
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'channel', 'createCampaign', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'channel', 'createCampaign', 'delete');
|
||||
|
||||
await tx('channel_lists').where('channel', entity.id).del();
|
||||
await tx('channel_lists').insert(entity.lists.map(x => ({channel: entity.id, ...x})));
|
||||
|
@ -197,8 +213,8 @@ async function remove(context, id) {
|
|||
|
||||
|
||||
module.exports.hash = hash;
|
||||
|
||||
module.exports.listDTAjax = listDTAjax;
|
||||
module.exports.listWithCreateCampaignPermissionDTAjax = listWithCreateCampaignPermissionDTAjax;
|
||||
module.exports.getByIdTx = getByIdTx;
|
||||
module.exports.getById = getById;
|
||||
module.exports.create = create;
|
||||
|
|
|
@ -169,7 +169,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
}
|
||||
|
||||
await namespaceHelpers.validateEntity(tx, entity);
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'customForm', 'createCustomForm', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'customForm', 'createCustomForm', 'delete');
|
||||
|
||||
const form = filterObject(entity, allowedFormKeys);
|
||||
enforce(!Object.keys(checkForMjmlErrors(form)).length, 'Error(s) in form templates');
|
||||
|
|
|
@ -263,7 +263,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
|
||||
await _validateAndPreprocess(tx, entity);
|
||||
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'list', 'createList', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'list', 'createList', 'delete');
|
||||
|
||||
await tx('lists').where('id', entity.id).update(filterObject(entity, allowedKeys));
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
|
||||
await _validateAndPreprocess(tx, entity);
|
||||
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'mosaicoTemplate', 'createMosaicoTemplate', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'mosaicoTemplate', 'createMosaicoTemplate', 'delete');
|
||||
|
||||
await tx('mosaico_templates').where('id', entity.id).update(filterObject(entity, allowedKeys));
|
||||
|
||||
|
|
|
@ -198,7 +198,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
}
|
||||
|
||||
// namespaceHelpers.validateEntity is not needed here because it is part of the tree traversal check below
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'namespace', 'createNamespace', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'namespace', 'createNamespace', 'delete');
|
||||
|
||||
let iter = entity;
|
||||
while (iter.namespace != null) {
|
||||
|
|
|
@ -66,7 +66,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
}
|
||||
|
||||
await namespaceHelpers.validateEntity(tx, entity);
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'reportTemplate', 'createReportTemplate', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'reportTemplate', 'createReportTemplate', 'delete');
|
||||
|
||||
await tx('report_templates').where('id', entity.id).update(filterObject(entity, allowedKeys));
|
||||
|
||||
|
|
|
@ -102,7 +102,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
}
|
||||
|
||||
await namespaceHelpers.validateEntity(tx, entity);
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'report', 'createReport', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'report', 'createReport', 'delete');
|
||||
|
||||
entity.params = JSON.stringify(entity.params);
|
||||
|
||||
|
|
|
@ -155,7 +155,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
|
||||
await _validateAndPreprocess(tx, entity);
|
||||
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'sendConfiguration', 'createSendConfiguration', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'sendConfiguration', 'createSendConfiguration', 'delete');
|
||||
|
||||
await tx('send_configurations').where('id', entity.id).update(filterObject(entity, allowedKeys));
|
||||
|
||||
|
|
|
@ -132,7 +132,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
await _validateAndPreprocess(tx, entity);
|
||||
entity.data = JSON.stringify(entity.data);
|
||||
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'template', 'createTemplate', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'template', 'createTemplate', 'delete');
|
||||
|
||||
const filteredEntity = filterObject(entity, allowedKeys);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue