2017-07-11 09:28:44 +00:00
'use strict' ;
const knex = require ( '../lib/knex' ) ;
2018-08-01 10:00:20 +00:00
const hasher = require ( 'node-object-hash' ) ( ) ;
2017-07-11 09:28:44 +00:00
const dtHelpers = require ( '../lib/dt-helpers' ) ;
2018-11-18 14:38:52 +00:00
const interoperableErrors = require ( '../../shared/interoperable-errors' ) ;
2018-07-31 04:34:28 +00:00
const shortid = require ( 'shortid' ) ;
2018-08-01 10:00:20 +00:00
const { enforce , filterObject } = require ( '../lib/helpers' ) ;
2017-08-13 18:11:58 +00:00
const shares = require ( './shares' ) ;
2018-08-01 10:00:20 +00:00
const namespaceHelpers = require ( '../lib/namespace-helpers' ) ;
2018-07-31 04:34:28 +00:00
const files = require ( './files' ) ;
2018-08-02 10:19:27 +00:00
const templates = require ( './templates' ) ;
2018-12-16 12:47:08 +00:00
const { CampaignStatus , CampaignSource , CampaignType , getSendConfigurationPermissionRequiredForSend } = require ( '../../shared/campaigns' ) ;
2018-08-03 16:07:46 +00:00
const sendConfigurations = require ( './send-configurations' ) ;
const triggers = require ( './triggers' ) ;
2018-11-18 14:38:52 +00:00
const { SubscriptionStatus } = require ( '../../shared/lists' ) ;
2018-09-09 22:55:44 +00:00
const subscriptions = require ( './subscriptions' ) ;
const segments = require ( './segments' ) ;
const senders = require ( '../lib/senders' ) ;
2018-12-16 12:47:08 +00:00
const { LinkId } = require ( './links' ) ;
2017-07-11 09:28:44 +00:00
2018-09-02 18:17:42 +00:00
const allowedKeysCommon = [ 'name' , 'description' , 'segment' , 'namespace' ,
2018-08-06 14:54:51 +00:00
'send_configuration' , 'from_name_override' , 'from_email_override' , 'reply_to_override' , 'subject_override' , 'data' , 'click_tracking_disabled' , 'open_tracking_disabled' , 'unsubscribe_url' ] ;
2018-07-31 04:34:28 +00:00
2018-08-01 10:00:20 +00:00
const allowedKeysCreate = new Set ( [ 'type' , 'source' , ... allowedKeysCommon ] ) ;
2018-11-17 01:54:23 +00:00
const allowedKeysCreateRssEntry = new Set ( [ 'type' , 'source' , 'parent' , ... allowedKeysCommon ] ) ;
2018-07-31 04:34:28 +00:00
const allowedKeysUpdate = new Set ( [ ... allowedKeysCommon ] ) ;
2018-08-03 11:35:55 +00:00
const Content = {
ALL : 0 ,
WITHOUT _SOURCE _CUSTOM : 1 ,
2018-09-02 12:59:02 +00:00
ONLY _SOURCE _CUSTOM : 2 ,
2018-09-09 22:55:44 +00:00
RSS _ENTRY : 3 ,
SETTINGS _WITH _STATS : 4
2018-08-03 11:35:55 +00:00
} ;
function hash ( entity , content ) {
let filteredEntity ;
if ( content === Content . ALL ) {
filteredEntity = filterObject ( entity , allowedKeysUpdate ) ;
2018-09-02 18:17:42 +00:00
filteredEntity . lists = entity . lists ;
2018-08-03 11:35:55 +00:00
} else if ( content === Content . WITHOUT _SOURCE _CUSTOM ) {
filteredEntity = filterObject ( entity , allowedKeysUpdate ) ;
2018-09-02 18:17:42 +00:00
filteredEntity . lists = entity . lists ;
2018-08-03 11:35:55 +00:00
filteredEntity . data = { ... filteredEntity . data } ;
delete filteredEntity . data . sourceCustom ;
} else if ( content === Content . ONLY _SOURCE _CUSTOM ) {
filteredEntity = {
data : {
sourceCustom : entity . data . sourceCustom
}
} ;
}
return hasher . hash ( filteredEntity ) ;
2018-08-01 10:00:20 +00:00
}
2018-07-31 04:34:28 +00:00
async function listDTAjax ( context , params ) {
2017-08-13 18:11:58 +00:00
return await dtHelpers . ajaxListWithPermissions (
context ,
[ { entityTypeId : 'campaign' , requiredOperations : [ 'view' ] } ] ,
params ,
builder => builder . from ( 'campaigns' )
2018-11-17 01:54:23 +00:00
. innerJoin ( 'namespaces' , 'namespaces.id' , 'campaigns.namespace' )
. whereNull ( 'campaigns.parent' ) ,
[ 'campaigns.id' , 'campaigns.name' , 'campaigns.cid' , 'campaigns.description' , 'campaigns.type' , 'campaigns.status' , 'campaigns.scheduled' , 'campaigns.source' , 'campaigns.created' , 'namespaces.name' ]
) ;
}
async function listChildrenDTAjax ( context , campaignId , params ) {
return await dtHelpers . ajaxListWithPermissions (
context ,
[ { entityTypeId : 'campaign' , requiredOperations : [ 'view' ] } ] ,
params ,
builder => builder . from ( 'campaigns' )
. innerJoin ( 'namespaces' , 'namespaces.id' , 'campaigns.namespace' )
. where ( 'campaigns.parent' , campaignId ) ,
2018-09-27 10:34:54 +00:00
[ 'campaigns.id' , 'campaigns.name' , 'campaigns.cid' , 'campaigns.description' , 'campaigns.type' , 'campaigns.status' , 'campaigns.scheduled' , 'campaigns.source' , 'campaigns.created' , 'namespaces.name' ]
2017-08-13 18:11:58 +00:00
) ;
}
2017-07-13 11:27:03 +00:00
2018-11-17 01:54:23 +00:00
2018-08-03 11:35:55 +00:00
async function listWithContentDTAjax ( context , params ) {
return await dtHelpers . ajaxListWithPermissions (
context ,
[ { entityTypeId : 'campaign' , requiredOperations : [ 'view' ] } ] ,
params ,
builder => builder . from ( 'campaigns' )
. innerJoin ( 'namespaces' , 'namespaces.id' , 'campaigns.namespace' )
. whereIn ( 'campaigns.source' , [ CampaignSource . CUSTOM , CampaignSource . CUSTOM _FROM _TEMPLATE , CampaignSource . CUSTOM _FROM _CAMPAIGN ] ) ,
2018-09-27 10:34:54 +00:00
[ 'campaigns.id' , 'campaigns.name' , 'campaigns.cid' , 'campaigns.description' , 'campaigns.type' , 'campaigns.created' , 'namespaces.name' ]
2018-08-03 11:35:55 +00:00
) ;
}
2018-09-02 18:17:42 +00:00
async function listOthersWhoseListsAreIncludedDTAjax ( context , campaignId , listIds , params ) {
2018-08-04 09:30:37 +00:00
return await dtHelpers . ajaxListWithPermissions (
context ,
[ { entityTypeId : 'campaign' , requiredOperations : [ 'view' ] } ] ,
params ,
builder => builder . from ( 'campaigns' )
. innerJoin ( 'namespaces' , 'namespaces.id' , 'campaigns.namespace' )
. whereNot ( 'campaigns.id' , campaignId )
2018-09-02 18:17:42 +00:00
. whereNotExists ( qry => qry . from ( 'campaign_lists' ) . whereRaw ( 'campaign_lists.campaign = campaigns.id' ) . whereNotIn ( 'campaign_lists.list' , listIds ) ) ,
2018-09-27 10:34:54 +00:00
[ 'campaigns.id' , 'campaigns.name' , 'campaigns.cid' , 'campaigns.description' , 'campaigns.type' , 'campaigns.created' , 'namespaces.name' ]
2018-08-04 09:30:37 +00:00
) ;
}
2018-09-09 22:55:44 +00:00
async function listTestUsersDTAjax ( context , campaignId , params ) {
return await knex . transaction ( async tx => {
await shares . enforceEntityPermissionTx ( tx , context , 'campaign' , campaignId , 'view' ) ;
2018-09-10 18:15:59 +00:00
/ *
This is supposed to produce queries like this :
select * from (
2018-09-27 16:30:23 +00:00
( select ` subscription__1 ` . ` email ` , ` subscription__1 ` . ` cid ` , 1 AS list , NULL AS segment from ` subscription__1 ` where ` subscription__1 ` . ` status ` = 1 and ` subscription__1 ` . ` is_test ` = true )
2018-09-10 18:15:59 +00:00
UNION ALL
2018-09-27 16:30:23 +00:00
( select ` subscription__2 ` . ` email ` , ` subscription__2 ` . ` cid ` , 2 AS list , NULL AS segment from ` subscription__2 ` where ` subscription__2 ` . ` status ` = 1 and ` subscription__2 ` . ` is_test ` = true )
2018-09-10 18:15:59 +00:00
) as ` test_subscriptions ` inner join ` lists ` on ` test_subscriptions ` . ` list ` = ` lists ` . ` id ` inner join ` segments ` on ` test_subscriptions ` . ` segment ` = ` segments ` . ` id `
inner join ` namespaces ` on ` lists ` . ` namespace ` = ` namespaces ` . ` id `
This was too much for Knex , so we partially construct these queries directly as strings ;
* /
const subsQrys = [ ] ;
2018-09-09 22:55:44 +00:00
const cpgLists = await tx ( 'campaign_lists' ) . where ( 'campaign' , campaignId ) ;
for ( const cpgList of cpgLists ) {
2018-09-10 18:15:59 +00:00
const addSegmentQuery = cpgList . segment ? await segments . getQueryGeneratorTx ( tx , cpgList . list , cpgList . segment ) : ( ) => { } ;
2018-09-09 22:55:44 +00:00
const subsTable = subscriptions . getSubscriptionTableName ( cpgList . list ) ;
2018-09-10 18:15:59 +00:00
const sqlQry = knex . from ( subsTable )
. where ( subsTable + '.status' , SubscriptionStatus . SUBSCRIBED )
. where ( subsTable + '.is_test' , true )
. where ( function ( ) {
addSegmentQuery ( this ) ;
} )
2018-09-27 16:30:23 +00:00
. select ( [ subsTable + '.email' , subsTable + '.cid' , knex . raw ( '? AS list' , [ cpgList . list ] ) , knex . raw ( '? AS segment' , [ cpgList . segment ] ) ] )
2018-09-10 18:15:59 +00:00
. toSQL ( ) . toNative ( ) ;
subsQrys . push ( sqlQry ) ;
2018-09-09 22:55:44 +00:00
}
2018-09-10 18:15:59 +00:00
if ( subsQrys . length > 0 ) {
let subsQry ;
if ( subsQrys . length === 1 ) {
const subsUnionSql = '(' + subsQrys [ 0 ] . sql + ') as `test_subscriptions`'
subsQry = knex . raw ( subsUnionSql , subsQrys [ 0 ] . bindings ) ;
} else {
const subsUnionSql = '(' +
subsQrys . map ( qry => '(' + qry . sql + ')' ) . join ( ' UNION ALL ' ) +
') as `test_subscriptions`' ;
const subsUnionBindings = Array . prototype . concat ( ... subsQrys . map ( qry => qry . bindings ) ) ;
subsQry = knex . raw ( subsUnionSql , subsUnionBindings ) ;
}
2018-12-16 12:47:08 +00:00
return await dtHelpers . ajaxListWithPermissionsTx (
tx ,
2018-09-09 22:55:44 +00:00
context ,
2018-09-27 16:30:23 +00:00
[ { entityTypeId : 'list' , requiredOperations : [ 'viewSubscriptions' ] , column : 'subs.list_id' } ] ,
2018-09-09 22:55:44 +00:00
params ,
builder => {
2018-09-27 16:30:23 +00:00
return builder . from ( function ( ) {
return this . from ( subsQry )
. innerJoin ( 'lists' , 'test_subscriptions.list' , 'lists.id' )
. innerJoin ( 'namespaces' , 'lists.namespace' , 'namespaces.id' )
. select ( [
knex . raw ( 'CONCAT_WS(":", lists.cid, test_subscriptions.cid) AS cid' ) ,
'test_subscriptions.email' , 'test_subscriptions.cid AS subscription_cid' , 'lists.cid AS list_cid' ,
'lists.name as list_name' , 'namespaces.name AS namespace_name' , 'lists.id AS list_id'
] )
. as ( 'subs' ) ;
} ) ;
2018-09-09 22:55:44 +00:00
} ,
2018-09-27 16:30:23 +00:00
[ 'subs.cid' , 'subs.email' , 'subs.subscription_cid' , 'subs.list_cid' , 'subs.list_name' , 'subs.namespace_name' ]
2018-09-09 22:55:44 +00:00
) ;
} else {
const result = {
draw : params . draw ,
recordsTotal : 0 ,
recordsFiltered : 0 ,
data : [ ]
} ;
return result ;
}
} ) ;
}
2018-12-16 12:47:08 +00:00
async function _listSubscriberResultsDTAjax ( context , campaignId , getSubsQrys , columns , params ) {
return await knex . transaction ( async tx => {
await shares . enforceEntityPermissionTx ( tx , context , 'campaign' , campaignId , 'view' ) ;
const subsQrys = [ ] ;
const cpgLists = await tx ( 'campaign_lists' ) . where ( 'campaign' , campaignId ) ;
for ( const cpgList of cpgLists ) {
const subsTable = subscriptions . getSubscriptionTableName ( cpgList . list ) ;
subsQrys . push ( getSubsQrys ( subsTable , cpgList ) ) ;
}
if ( subsQrys . length > 0 ) {
let subsSql , subsBindings ;
if ( subsQrys . length === 1 ) {
subsSql = '(' + subsQrys [ 0 ] . sql + ') as `subs`'
subsBindings = subsQrys [ 0 ] . bindings ;
} else {
subsSql = '(' +
subsQrys . map ( qry => '(' + qry . sql + ')' ) . join ( ' UNION ALL ' ) +
') as `subs`' ;
subsBindings = Array . prototype . concat ( ... subsQrys . map ( qry => qry . bindings ) ) ;
}
return await dtHelpers . ajaxListWithPermissionsTx (
tx ,
context ,
[ { entityTypeId : 'list' , requiredOperations : [ 'viewSubscriptions' ] , column : 'lists.id' } ] ,
params ,
( builder , tx ) => builder . from ( knex . raw ( subsSql , subsBindings ) )
. innerJoin ( 'lists' , 'subs.list' , 'lists.id' )
. innerJoin ( 'namespaces' , 'lists.namespace' , 'namespaces.id' )
,
columns
) ;
} else {
const result = {
draw : params . draw ,
recordsTotal : 0 ,
recordsFiltered : 0 ,
data : [ ]
} ;
return result ;
}
} ) ;
}
async function listSentByStatusDTAjax ( context , campaignId , status , params ) {
return await _listSubscriberResultsDTAjax (
context ,
campaignId ,
( subsTable , cpgList ) => knex . from ( subsTable )
. innerJoin (
function ( ) {
return this . from ( 'campaign_messages' )
. where ( 'campaign_messages.campaign' , campaignId )
. where ( 'campaign_messages.list' , cpgList . list )
. where ( 'campaign_messages.status' , status )
. as ( 'related_campaign_messages' ) ;
} ,
'related_campaign_messages.subscription' , subsTable + '.id' )
. select ( [ subsTable + '.email' , subsTable + '.cid' , knex . raw ( '? AS list' , [ cpgList . list ] ) ] )
. toSQL ( ) . toNative ( ) ,
[ 'subs.email' , 'subs.cid' , 'lists.cid' , 'lists.name' , 'namespaces.name' ] ,
params
) ;
}
async function listOpensDTAjax ( context , campaignId , params ) {
return await _listSubscriberResultsDTAjax (
context ,
campaignId ,
( subsTable , cpgList ) => knex . from ( subsTable )
. innerJoin (
function ( ) {
return this . from ( 'campaign_links' )
. where ( 'campaign_links.campaign' , campaignId )
. where ( 'campaign_links.list' , cpgList . list )
. where ( 'campaign_links.link' , LinkId . OPEN )
. as ( 'related_campaign_links' ) ;
} ,
'related_campaign_links.subscription' , subsTable + '.id' )
. select ( [ subsTable + '.email' , subsTable + '.cid' , knex . raw ( '? AS list' , [ cpgList . list ] ) , 'related_campaign_links.count' ] )
. toSQL ( ) . toNative ( ) ,
[ 'subs.email' , 'subs.cid' , 'lists.cid' , 'lists.name' , 'namespaces.name' , 'subs.count' ] ,
params
) ;
}
async function listLinkClicksDTAjax ( context , campaignId , params ) {
return await knex . transaction ( async ( tx ) => {
await shares . enforceEntityPermissionTx ( tx , context , 'campaign' , campaignId , 'viewStats' ) ;
return await dtHelpers . ajaxListTx (
tx ,
params ,
builder => builder . from ( 'links' )
. where ( 'links.campaign' , campaignId ) ,
[ 'links.url' , 'links.visits' , 'links.hits' ]
) ;
} ) ;
}
2018-09-23 20:28:58 +00:00
async function getTrackingSettingsByCidTx ( tx , cid ) {
const entity = await tx ( 'campaigns' ) . where ( 'campaigns.cid' , cid )
. select ( [
'campaigns.id' , 'campaigns.click_tracking_disabled' , 'campaigns.open_tracking_disabled'
] )
. first ( ) ;
if ( ! entity ) {
throw new interoperableErrors . NotFoundError ( ) ;
}
return entity ;
}
2018-09-22 16:12:22 +00:00
async function rawGetByTx ( tx , key , id ) {
const entity = await tx ( 'campaigns' ) . where ( 'campaigns.' + key , id )
2018-09-10 18:15:59 +00:00
. leftJoin ( 'campaign_lists' , 'campaigns.id' , 'campaign_lists.campaign' )
2018-09-02 18:17:42 +00:00
. groupBy ( 'campaigns.id' )
. select ( [
2018-09-27 10:34:54 +00:00
'campaigns.id' , 'campaigns.cid' , 'campaigns.name' , 'campaigns.description' , 'campaigns.namespace' , 'campaigns.status' , 'campaigns.type' , 'campaigns.source' ,
2018-09-02 18:17:42 +00:00
'campaigns.send_configuration' , 'campaigns.from_name_override' , 'campaigns.from_email_override' , 'campaigns.reply_to_override' , 'campaigns.subject_override' ,
2018-09-27 21:37:50 +00:00
'campaigns.data' , 'campaigns.click_tracking_disabled' , 'campaigns.open_tracking_disabled' , 'campaigns.unsubscribe_url' , 'campaigns.scheduled' ,
2018-09-02 18:17:42 +00:00
knex . raw ( ` GROUP_CONCAT(CONCAT_WS( \' : \' , campaign_lists.list, campaign_lists.segment) ORDER BY campaign_lists.id SEPARATOR \' ; \' ) as lists ` )
] )
. first ( ) ;
if ( ! entity ) {
throw new interoperableErrors . NotFoundError ( ) ;
}
2018-09-10 18:15:59 +00:00
if ( entity . lists ) {
entity . lists = entity . lists . split ( ';' ) . map ( x => {
const entries = x . split ( ':' ) ;
const list = Number . parseInt ( entries [ 0 ] ) ;
const segment = entries [ 1 ] ? Number . parseInt ( entries [ 1 ] ) : null ;
return { list , segment } ;
} ) ;
} else {
entity . lists = [ ] ;
}
2018-09-02 18:17:42 +00:00
entity . data = JSON . parse ( entity . data ) ;
return entity ;
}
2018-08-03 11:35:55 +00:00
async function getByIdTx ( tx , context , id , withPermissions = true , content = Content . ALL ) {
2018-08-02 10:19:27 +00:00
await shares . enforceEntityPermissionTx ( tx , context , 'campaign' , id , 'view' ) ;
2018-08-03 11:35:55 +00:00
2018-09-22 16:12:22 +00:00
let entity = await rawGetByTx ( tx , 'id' , id ) ;
2018-08-03 11:35:55 +00:00
2018-09-09 22:55:44 +00:00
if ( content === Content . ALL || content === Content . RSS _ENTRY ) {
// Return everything
} else if ( content === Content . SETTINGS _WITH _STATS ) {
delete entity . data . sourceCustom ;
await shares . enforceEntityPermissionTx ( tx , context , 'campaign' , id , 'viewStats' ) ;
const unsentQryGen = await getSubscribersQueryGeneratorTx ( tx , id , true ) ;
if ( unsentQryGen ) {
const res = await unsentQryGen ( tx ) . count ( '* AS subscriptionsToSend' ) . first ( ) ;
entity . subscriptionsToSend = res . subscriptionsToSend ;
}
const totalQryGen = await getSubscribersQueryGeneratorTx ( tx , id , false ) ;
if ( totalQryGen ) {
const res = await totalQryGen ( tx ) . count ( '* AS subscriptionsTotal' ) . first ( ) ;
entity . subscriptionsTotal = res . subscriptionsTotal ;
}
} else if ( content === Content . WITHOUT _SOURCE _CUSTOM ) {
2018-08-03 11:35:55 +00:00
delete entity . data . sourceCustom ;
} else if ( content === Content . ONLY _SOURCE _CUSTOM ) {
entity = {
id : entity . id ,
2018-11-14 21:29:31 +00:00
send _configuration : entity . send _configuration ,
2018-08-03 11:35:55 +00:00
data : {
sourceCustom : entity . data . sourceCustom
}
} ;
}
2018-08-01 10:00:20 +00:00
2018-08-02 10:19:27 +00:00
if ( withPermissions ) {
2017-08-13 18:11:58 +00:00
entity . permissions = await shares . getPermissionsTx ( tx , context , 'campaign' , id ) ;
2018-08-02 10:19:27 +00:00
}
return entity ;
}
2018-08-01 10:00:20 +00:00
2018-08-03 11:35:55 +00:00
async function getById ( context , id , withPermissions = true , content = Content . ALL ) {
2018-08-02 10:19:27 +00:00
return await knex . transaction ( async tx => {
2018-08-03 11:35:55 +00:00
return await getByIdTx ( tx , context , id , withPermissions , content ) ;
2017-08-13 18:11:58 +00:00
} ) ;
2017-07-13 11:27:03 +00:00
}
2017-07-11 09:28:44 +00:00
2018-08-03 11:35:55 +00:00
async function _validateAndPreprocess ( tx , context , entity , isCreate , content ) {
2018-09-02 12:59:02 +00:00
if ( content === Content . ALL || content === Content . WITHOUT _SOURCE _CUSTOM || content === Content . RSS _ENTRY ) {
2018-08-03 11:35:55 +00:00
await namespaceHelpers . validateEntity ( tx , entity ) ;
2018-07-31 04:34:28 +00:00
2018-08-03 11:35:55 +00:00
if ( isCreate ) {
2018-09-02 12:59:02 +00:00
enforce ( entity . type === CampaignType . REGULAR || entity . type === CampaignType . RSS || entity . type === CampaignType . TRIGGERED ||
( content === Content . RSS _ENTRY && entity . type === CampaignType . RSS _ENTRY ) ,
'Unknown campaign type' ) ;
2018-08-01 10:00:20 +00:00
2018-08-03 11:35:55 +00:00
if ( entity . source === CampaignSource . TEMPLATE || entity . source === CampaignSource . CUSTOM _FROM _TEMPLATE ) {
await shares . enforceEntityPermissionTx ( tx , context , 'template' , entity . data . sourceTemplate , 'view' ) ;
}
enforce ( Number . isInteger ( entity . source ) ) ;
enforce ( entity . source >= CampaignSource . MIN && entity . source <= CampaignSource . MAX , 'Unknown campaign source' ) ;
2018-08-01 10:00:20 +00:00
}
2018-08-03 11:35:55 +00:00
2018-09-02 18:17:42 +00:00
for ( const lstSeg of entity . lists ) {
await shares . enforceEntityPermissionTx ( tx , context , 'list' , lstSeg . list , 'view' ) ;
2018-08-03 11:35:55 +00:00
2018-09-02 18:17:42 +00:00
if ( lstSeg . segment ) {
// Check that the segment under the list exists
await segments . getByIdTx ( tx , context , lstSeg . list , lstSeg . segment ) ;
}
2018-08-03 11:35:55 +00:00
}
await shares . enforceEntityPermissionTx ( tx , context , 'sendConfiguration' , entity . send _configuration , 'viewPublic' ) ;
2018-07-31 04:34:28 +00:00
}
2018-08-03 11:35:55 +00:00
}
function convertFileURLs ( sourceCustom , fromEntityType , fromEntityId , toEntityType , toEntityId ) {
2018-07-31 04:34:28 +00:00
2018-08-03 11:35:55 +00:00
function convertText ( text ) {
if ( text ) {
const fromUrl = ` /files/ ${ fromEntityType } /file/ ${ fromEntityId } ` ;
const toUrl = ` /files/ ${ toEntityType } /file/ ${ toEntityId } ` ;
2018-07-31 04:34:28 +00:00
2018-08-03 11:35:55 +00:00
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 ) ;
}
2018-07-31 04:34:28 +00:00
2018-08-03 11:35:55 +00:00
return text ;
2018-07-31 04:34:28 +00:00
}
2018-08-03 11:35:55 +00:00
sourceCustom . html = convertText ( sourceCustom . html ) ;
sourceCustom . text = convertText ( sourceCustom . text ) ;
2018-08-02 10:19:27 +00:00
2018-08-03 11:35:55 +00:00
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 ) ;
}
2018-07-31 04:34:28 +00:00
}
2018-09-02 12:59:02 +00:00
async function _createTx ( tx , context , entity , content ) {
2018-07-31 04:34:28 +00:00
return await knex . transaction ( async tx => {
await shares . enforceEntityPermissionTx ( tx , context , 'namespace' , entity . namespace , 'createCampaign' ) ;
2018-08-02 10:19:27 +00:00
let copyFilesFrom = null ;
2018-07-31 04:34:28 +00:00
if ( entity . source === CampaignSource . CUSTOM _FROM _TEMPLATE ) {
2018-08-02 10:19:27 +00:00
copyFilesFrom = {
entityType : 'template' ,
entityId : entity . data . sourceTemplate
} ;
const template = await templates . getByIdTx ( tx , context , entity . data . sourceTemplate , false ) ;
entity . data . sourceCustom = {
type : template . type ,
data : template . data ,
html : template . html ,
text : template . text
} ;
2018-08-03 11:35:55 +00:00
2018-08-02 10:19:27 +00:00
} else if ( entity . source === CampaignSource . CUSTOM _FROM _CAMPAIGN ) {
copyFilesFrom = {
entityType : 'campaign' ,
entityId : entity . data . sourceCampaign
} ;
const sourceCampaign = await getByIdTx ( tx , context , entity . data . sourceCampaign , false ) ;
2018-08-03 11:35:55 +00:00
enforce ( sourceCampaign . source === CampaignSource . CUSTOM || sourceCampaign . source === CampaignSource . CUSTOM _FROM _TEMPLATE || sourceCampaign . source === CampaignSource . CUSTOM _FROM _CAMPAIGN , 'Incorrect source type of the source campaign.' ) ;
2018-08-02 10:19:27 +00:00
entity . data . sourceCustom = sourceCampaign . data . sourceCustom ;
2018-07-31 04:34:28 +00:00
}
2018-09-02 12:59:02 +00:00
await _validateAndPreprocess ( tx , context , entity , true , content ) ;
2018-07-31 04:34:28 +00:00
2018-11-17 01:54:23 +00:00
const filteredEntity = filterObject ( entity , entity . type === CampaignType . RSS _ENTRY ? allowedKeysCreateRssEntry : allowedKeysCreate ) ;
2018-07-31 04:34:28 +00:00
filteredEntity . cid = shortid . generate ( ) ;
2018-08-03 11:35:55 +00:00
const data = filteredEntity . data ;
filteredEntity . data = JSON . stringify ( filteredEntity . data ) ;
2018-11-17 01:54:23 +00:00
if ( filteredEntity . type === CampaignType . RSS || filteredEntity . type === CampaignType . TRIGGERED ) {
filteredEntity . status = CampaignStatus . ACTIVE ;
} else if ( filteredEntity . type === CampaignType . RSS _ENTRY ) {
filteredEntity . status = CampaignStatus . SCHEDULED ;
} else {
filteredEntity . status = CampaignStatus . IDLE ;
}
2018-07-31 04:34:28 +00:00
const ids = await tx ( 'campaigns' ) . insert ( filteredEntity ) ;
const id = ids [ 0 ] ;
2018-09-02 18:17:42 +00:00
await tx ( 'campaign_lists' ) . insert ( entity . lists . map ( x => ( { campaign : id , ... x } ) ) ) ;
2018-09-29 11:30:29 +00:00
if ( entity . source === CampaignSource . TEMPLATE ) {
await tx ( 'template_dep_campaigns' ) . insert ( {
campaign : id ,
template : entity . data . sourceTemplate
} ) ;
}
2018-11-17 01:54:23 +00:00
if ( filteredEntity . parent ) {
await shares . rebuildPermissionsTx ( tx , { entityTypeId : 'campaign' , entityId : id , parentId : filteredEntity . parent } ) ;
} else {
await shares . rebuildPermissionsTx ( tx , { entityTypeId : 'campaign' , entityId : id } ) ;
}
2018-07-31 04:34:28 +00:00
2018-08-02 10:19:27 +00:00
if ( copyFilesFrom ) {
2018-08-03 11:35:55 +00:00
await files . copyAllTx ( tx , context , copyFilesFrom . entityType , 'file' , copyFilesFrom . entityId , 'campaign' , 'file' , id ) ;
convertFileURLs ( data . sourceCustom , copyFilesFrom . entityType , copyFilesFrom . entityId , 'campaign' , id ) ;
await tx ( 'campaigns' )
. update ( {
data : JSON . stringify ( data )
} ) . where ( 'id' , id ) ;
2018-07-31 04:34:28 +00:00
}
return id ;
} ) ;
}
2018-09-02 12:59:02 +00:00
async function create ( context , entity ) {
return await knex . transaction ( async tx => {
return await _createTx ( tx , context , entity , Content . ALL ) ;
} ) ;
}
async function createRssTx ( tx , context , entity ) {
return await _createTx ( tx , context , entity , Content . RSS _ENTRY ) ;
}
2018-08-03 11:35:55 +00:00
async function updateWithConsistencyCheck ( context , entity , content ) {
2018-07-31 04:34:28 +00:00
await knex . transaction ( async tx => {
await shares . enforceEntityPermissionTx ( tx , context , 'campaign' , entity . id , 'edit' ) ;
2018-09-22 16:12:22 +00:00
const existing = await rawGetByTx ( tx , 'id' , entity . id ) ;
2018-07-31 04:34:28 +00:00
2018-08-03 11:35:55 +00:00
const existingHash = hash ( existing , content ) ;
2018-07-31 04:34:28 +00:00
if ( existingHash !== entity . originalHash ) {
throw new interoperableErrors . ChangedError ( ) ;
}
2018-08-03 11:35:55 +00:00
await _validateAndPreprocess ( tx , context , entity , false , content ) ;
2018-07-31 04:34:28 +00:00
2018-08-03 11:35:55 +00:00
let filteredEntity = filterObject ( entity , allowedKeysUpdate ) ;
if ( content === Content . ALL ) {
await namespaceHelpers . validateMove ( context , entity , existing , 'campaign' , 'createCampaign' , 'delete' ) ;
} else if ( content === Content . WITHOUT _SOURCE _CUSTOM ) {
filteredEntity . data . sourceCustom = existing . data . sourceCustom ;
await namespaceHelpers . validateMove ( context , filteredEntity , existing , 'campaign' , 'createCampaign' , 'delete' ) ;
} else if ( content === Content . ONLY _SOURCE _CUSTOM ) {
const data = existing . data ;
data . sourceCustom = filteredEntity . data . sourceCustom ;
filteredEntity = {
data
} ;
}
2018-07-31 04:34:28 +00:00
2018-09-27 10:34:54 +00:00
if ( content === Content . ALL || content === Content . WITHOUT _SOURCE _CUSTOM ) {
await tx ( 'campaign_lists' ) . where ( 'campaign' , entity . id ) . del ( ) ;
await tx ( 'campaign_lists' ) . insert ( entity . lists . map ( x => ( { campaign : entity . id , ... x } ) ) ) ;
2018-09-29 11:30:29 +00:00
if ( existing . source === CampaignSource . TEMPLATE ) {
await tx ( 'template_dep_campaigns' )
. where ( 'campaign' , entity . id )
. update ( 'template' , entity . data . sourceTemplate ) ;
}
2018-09-27 10:34:54 +00:00
}
2018-08-03 11:35:55 +00:00
filteredEntity . data = JSON . stringify ( filteredEntity . data ) ;
await tx ( 'campaigns' ) . where ( 'id' , entity . id ) . update ( filteredEntity ) ;
2018-07-31 04:34:28 +00:00
await shares . rebuildPermissionsTx ( tx , { entityTypeId : 'campaign' , entityId : entity . id } ) ;
} ) ;
}
2018-11-17 01:54:23 +00:00
async function _removeTx ( tx , context , id , existing = null ) {
await shares . enforceEntityPermissionTx ( tx , context , 'campaign' , id , 'delete' ) ;
2018-07-31 04:34:28 +00:00
2018-11-17 01:54:23 +00:00
if ( ! existing ) {
existing = await tx ( 'campaigns' ) . where ( 'id' , id ) . select ( [ 'id' , 'status' , 'type' ] ) . first ( ) ;
}
if ( existing . status === CampaignStatus . SENDING ) {
return new interoperableErrors . InvalidStateError ;
}
enforce ( existing . type === CampaignType . REGULAR || existing . type === CampaignType . RSS || existing . type === CampaignType . TRIGGERED , 'This campaign cannot be removed by user.' ) ;
const childCampaigns = await tx ( 'campaigns' ) . where ( 'parent' , id ) . select ( [ 'id' , 'status' , 'type' ] ) ;
for ( const childCampaign of childCampaigns ) {
await _removeTx ( tx , contect , childCampaign . id , childCampaign ) ;
}
await files . removeAllTx ( tx , context , 'campaign' , 'file' , id ) ;
await files . removeAllTx ( tx , context , 'campaign' , 'attachment' , id ) ;
await tx ( 'campaign_lists' ) . where ( 'campaign' , id ) . del ( ) ;
await tx ( 'campaign_messages' ) . where ( 'campaign' , id ) . del ( ) ;
await tx ( 'campaign_links' ) . where ( 'campaign' , id ) . del ( ) ;
2018-09-09 22:55:44 +00:00
2018-12-16 12:47:08 +00:00
await tx ( 'links' ) . where ( 'campaign' , id ) . del ( ) ;
2018-11-17 01:54:23 +00:00
await triggers . removeAllByCampaignIdTx ( tx , context , id ) ;
2018-09-29 18:08:49 +00:00
2018-11-17 01:54:23 +00:00
await tx ( 'template_dep_campaigns' )
. where ( 'campaign' , id )
. del ( ) ;
2018-08-01 10:00:20 +00:00
2018-11-17 01:54:23 +00:00
await tx ( 'campaigns' ) . where ( 'id' , id ) . del ( ) ;
}
2018-08-03 16:07:46 +00:00
2018-09-29 11:30:29 +00:00
2018-11-17 01:54:23 +00:00
async function remove ( context , id ) {
await knex . transaction ( async tx => {
await _removeTx ( tx , context , id ) ;
2018-07-31 04:34:28 +00:00
} ) ;
}
2018-08-03 16:07:46 +00:00
async function enforceSendPermissionTx ( tx , context , campaignId ) {
const campaign = await getByIdTx ( tx , context , campaignId , false ) ;
const sendConfiguration = await sendConfigurations . getByIdTx ( tx , context , campaign . send _configuration , false , false ) ;
const requiredPermission = getSendConfigurationPermissionRequiredForSend ( campaign , sendConfiguration ) ;
2018-08-04 09:30:37 +00:00
await shares . enforceEntityPermissionTx ( tx , context , 'sendConfiguration' , campaign . send _configuration , requiredPermission ) ;
await shares . enforceEntityPermissionTx ( tx , context , 'campaign' , campaignId , 'send' ) ;
2018-08-03 16:07:46 +00:00
}
2018-07-31 04:34:28 +00:00
2018-09-09 22:55:44 +00:00
// Message API
function getMessageCid ( campaignCid , listCid , subscriptionCid ) {
return [ campaignCid , listCid , subscriptionCid ] . join ( '.' )
}
async function getMessageByCid ( messageCid ) {
2018-11-14 21:29:31 +00:00
const messageCidElems = messageCid . split ( '.' ) ;
2018-09-09 22:55:44 +00:00
2018-11-14 21:29:31 +00:00
if ( messageCidElems . length !== 3 ) {
2018-09-09 22:55:44 +00:00
return null ;
}
2018-11-14 21:29:31 +00:00
const [ campaignCid , listCid , subscriptionCid ] = messageCidElems ;
2018-09-09 22:55:44 +00:00
2018-12-16 21:35:21 +00:00
return await knex . transaction ( async tx => {
2018-09-09 22:55:44 +00:00
const list = await tx ( 'lists' ) . where ( 'cid' , listCid ) . select ( 'id' ) ;
const subscrTblName = subscriptions . getSubscriptionTableName ( list . id ) ;
const message = await tx ( 'campaign_messages' )
. innerJoin ( 'campaigns' , 'campaign_messages.campaign' , 'campaigns.id' )
. innerJoin ( subscrTblName , subscrTblName + '.id' , 'campaign_messages.subscription' )
. where ( subscrTblName + '.cid' , subscriptionCid )
. where ( 'campaigns.cid' , campaignCid )
. select ( [
2018-12-16 21:35:21 +00:00
'campaign_messages.id' , 'campaign_messages.campaign' , 'campaign_messages.list' , 'campaign_messages.subscription' , 'campaign_messages.status'
] )
. first ( ) ;
2018-09-09 22:55:44 +00:00
return message ;
} ) ;
}
async function getMessageByResponseId ( responseId ) {
2018-12-16 21:35:21 +00:00
return await knex . transaction ( async tx => {
console . log ( responseId ) ;
2018-09-09 22:55:44 +00:00
const message = await tx ( 'campaign_messages' )
. where ( 'campaign_messages.response_id' , responseId )
. select ( [
2018-12-16 21:35:21 +00:00
'campaign_messages.id' , 'campaign_messages.campaign' , 'campaign_messages.list' , 'campaign_messages.subscription' , 'campaign_messages.status'
] )
. first ( ) ;
2018-09-09 22:55:44 +00:00
return message ;
} ) ;
}
const statusFieldMapping = {
[ SubscriptionStatus . UNSUBSCRIBED ] : 'unsubscribed' ,
[ SubscriptionStatus . BOUNCED ] : 'bounced' ,
[ SubscriptionStatus . COMPLAINED ] : 'complained'
} ;
async function _changeStatusByMessageTx ( tx , context , message , subscriptionStatus ) {
enforce ( subscriptionStatus !== SubscriptionStatus . SUBSCRIBED ) ;
if ( message . status === SubscriptionStatus . SUBSCRIBED ) {
await shares . enforceEntityPermissionTx ( tx , context , 'campaign' , message . campaign , 'manageMessages' ) ;
if ( ! subscriptionStatus in statusFieldMapping ) {
throw new Error ( 'Unrecognized message status' ) ;
}
const statusField = statusFieldMapping [ subscriptionStatus ] ;
if ( message . status === SubscriptionStatus . SUBSCRIBED ) {
await tx ( 'campaigns' ) . increment ( statusField , 1 ) . where ( 'id' , message . campaign ) ;
}
await tx ( 'campaign_messages' )
. where ( 'id' , message . id )
. update ( {
status : subscriptionStatus ,
updated : knex . fn . now ( )
} ) ;
}
}
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 )
. where ( { subscription : subscriptionId , list : listId } ) ;
if ( ! message ) {
throw new Error ( 'Invalid campaign.' )
}
await _changeStatusByMessageTx ( tx , context , message , subscriptionStatus ) ;
}
async function changeStatusByMessage ( context , message , subscriptionStatus , updateSubscription ) {
await knex . transaction ( async tx => {
if ( updateSubscription ) {
await subscriptions . changeStatusTx ( tx , context , message . list , message . subscription , subscriptionStatus ) ;
}
await _changeStatusByMessageTx ( tx , context , message , subscriptionStatus ) ;
2018-12-16 21:35:21 +00:00
2018-09-09 22:55:44 +00:00
} ) ;
}
2018-09-18 09:03:36 +00:00
async function updateMessageResponse ( context , message , response , responseId ) {
await knex . transaction ( async tx => {
await shares . enforceEntityPermissionTx ( tx , context , 'campaign' , message . campaign , 'manageMessages' ) ;
await tx ( 'campaign_messages' ) . where ( 'id' , message . id ) . update ( {
response ,
response _id : responseId
} ) ;
} ) ;
}
2018-09-27 19:32:35 +00:00
async function getSubscribersQueryGeneratorTx ( tx , campaignId , onlyUnsent ) {
2018-09-10 18:15:59 +00:00
/ *
This is supposed to produce queries like this :
2018-09-11 08:07:00 +00:00
select ... from ` campaign_lists ` inner join (
2018-09-10 18:15:59 +00:00
select ` email ` , min ( ` campaign_list_id ` ) as ` campaign_list_id ` , max ( ` sent ` ) as ` sent ` from (
2018-09-11 08:07:00 +00:00
( select ` subscription__2 ` . ` email ` , 8 AS campaign _list _id , related _campaign _messages . id IS NOT NULL AS sent from ` subscription__2 ` left join
( select * from ` campaign_messages ` where ` campaign_messages ` . ` campaign ` = 1 and ` campaign_messages ` . ` list ` = 2 )
as ` related_campaign_messages ` on ` related_campaign_messages ` . ` subscription ` = ` subscription__2 ` . ` id ` where ` subscription__2 ` . ` status ` = 1 )
UNION ALL
( select ` subscription__1 ` . ` email ` , 9 AS campaign _list _id , related _campaign _messages . id IS NOT NULL AS sent from ` subscription__1 ` left join
( select * from ` campaign_messages ` where ` campaign_messages ` . ` campaign ` = 1 and ` campaign_messages ` . ` list ` = 1 )
as ` related_campaign_messages ` on ` related_campaign_messages ` . ` subscription ` = ` subscription__1 ` . ` id ` where ` subscription__1 ` . ` status ` = 1 )
) as ` pending_subscriptions_all ` where ` sent ` = false group by ` email ` )
as ` pending_subscriptions ` on ` campaign_lists ` . ` id ` = ` pending_subscriptions ` . ` campaign_list_id ` where ` campaign_lists ` . ` campaign ` = '1'
2018-09-10 18:15:59 +00:00
This was too much for Knex , so we partially construct these queries directly as strings ;
* /
const subsQrys = [ ] ;
2018-09-09 22:55:44 +00:00
const cpgLists = await tx ( 'campaign_lists' ) . where ( 'campaign' , campaignId ) ;
for ( const cpgList of cpgLists ) {
const addSegmentQuery = cpgList . segment ? await segments . getQueryGeneratorTx ( tx , cpgList . list , cpgList . segment ) : ( ) => { } ;
const subsTable = subscriptions . getSubscriptionTableName ( cpgList . list ) ;
2018-09-10 18:15:59 +00:00
const sqlQry = knex . from ( subsTable )
2018-09-11 08:07:00 +00:00
. leftJoin (
function ( ) {
return this . from ( 'campaign_messages' )
2018-09-22 13:59:05 +00:00
. where ( 'campaign_messages.campaign' , campaignId )
2018-09-11 08:07:00 +00:00
. where ( 'campaign_messages.list' , cpgList . list )
. as ( 'related_campaign_messages' ) ;
} ,
'related_campaign_messages.subscription' , subsTable + '.id' )
2018-09-10 18:15:59 +00:00
. where ( subsTable + '.status' , SubscriptionStatus . SUBSCRIBED )
. where ( function ( ) {
addSegmentQuery ( this ) ;
} )
2018-09-11 08:07:00 +00:00
. select ( [ subsTable + '.email' , knex . raw ( '? AS campaign_list_id' , [ cpgList . id ] ) , knex . raw ( 'related_campaign_messages.id IS NOT NULL AS sent' ) ] )
2018-09-10 18:15:59 +00:00
. toSQL ( ) . toNative ( ) ;
subsQrys . push ( sqlQry ) ;
2018-09-09 22:55:44 +00:00
}
2018-09-10 18:15:59 +00:00
if ( subsQrys . length > 0 ) {
let subsQry ;
const unsentWhere = onlyUnsent ? ' where `sent` = false' : '' ;
if ( subsQrys . length === 1 ) {
const subsUnionSql = '(select `email`, `campaign_list_id`, `sent` from (' + subsQrys [ 0 ] . sql + ') as `pending_subscriptions_all`' + unsentWhere + ') as `pending_subscriptions`'
subsQry = knex . raw ( subsUnionSql , subsQrys [ 0 ] . bindings ) ;
} else {
const subsUnionSql = '(select `email`, min(`campaign_list_id`) as `campaign_list_id`, max(`sent`) as `sent` from (' +
subsQrys . map ( qry => '(' + qry . sql + ')' ) . join ( ' UNION ALL ' ) +
') as `pending_subscriptions_all`' + unsentWhere + ' group by `email`) as `pending_subscriptions`' ;
const subsUnionBindings = Array . prototype . concat ( ... subsQrys . map ( qry => qry . bindings ) ) ;
subsQry = knex . raw ( subsUnionSql , subsUnionBindings ) ;
}
2018-09-09 22:55:44 +00:00
return knx => knx . from ( 'campaign_lists' )
. where ( 'campaign_lists.campaign' , campaignId )
2018-09-10 18:15:59 +00:00
. innerJoin ( subsQry , 'campaign_lists.id' , 'pending_subscriptions.campaign_list_id' ) ;
2018-09-09 22:55:44 +00:00
} else {
return null ;
}
}
async function _changeStatus ( context , campaignId , permittedCurrentStates , newState , invalidStateMessage , scheduled = null ) {
await knex . transaction ( async tx => {
await shares . enforceEntityPermissionTx ( tx , context , 'campaign' , campaignId , 'send' ) ;
const entity = await tx ( 'campaigns' ) . where ( 'id' , campaignId ) . first ( ) ;
if ( ! entity ) {
throw new interoperableErrors . NotFoundError ( ) ;
}
if ( ! permittedCurrentStates . includes ( entity . status ) ) {
throw new interoperableErrors . InvalidStateError ( invalidStateMessage ) ;
}
await tx ( 'campaigns' ) . where ( 'id' , campaignId ) . update ( {
status : newState ,
scheduled
} ) ;
} ) ;
senders . scheduleCheck ( ) ;
}
async function start ( context , campaignId , startAt ) {
2018-09-27 21:37:50 +00:00
await _changeStatus ( context , campaignId , [ CampaignStatus . IDLE , CampaignStatus . SCHEDULED , CampaignStatus . PAUSED , CampaignStatus . FINISHED ] , CampaignStatus . SCHEDULED , 'Cannot start campaign until it is in IDLE or PAUSED state' , startAt ) ;
2018-09-09 22:55:44 +00:00
}
async function stop ( context , campaignId ) {
await _changeStatus ( context , campaignId , [ CampaignStatus . SCHEDULED ] , CampaignStatus . PAUSED , 'Cannot stop campaign until it is in SCHEDULED state' ) ;
}
async function reset ( context , campaignId ) {
await knex . transaction ( async tx => {
await shares . enforceEntityPermissionTx ( tx , context , 'campaign' , campaignId , 'send' ) ;
const entity = await tx ( 'campaigns' ) . where ( 'id' , campaignId ) . first ( ) ;
if ( ! entity ) {
throw new interoperableErrors . NotFoundError ( ) ;
}
if ( entity . status !== CampaignStatus . FINISHED && entity . status !== CampaignStatus . PAUSED ) {
throw new interoperableErrors . InvalidStateError ( 'Cannot reset campaign until it is FINISHED or PAUSED state' ) ;
}
await tx ( 'campaigns' ) . where ( 'id' , campaignId ) . update ( {
2018-12-16 12:47:08 +00:00
status : CampaignStatus . IDLE ,
delivered : 0 ,
unsubscribed : 0 ,
bounced : 0 ,
complained : 0 ,
blacklisted : 0 ,
opened : 0 ,
clicks : 0
2018-09-09 22:55:44 +00:00
} ) ;
await tx ( 'campaign_messages' ) . where ( 'campaign' , campaignId ) . del ( ) ;
await tx ( 'campaign_links' ) . where ( 'campaign' , campaignId ) . del ( ) ;
2018-12-16 12:47:08 +00:00
await tx ( 'links' ) . where ( 'campaign' , campaignId ) . del ( ) ;
2018-09-09 22:55:44 +00:00
} ) ;
}
2018-11-17 01:54:23 +00:00
async function enable ( context , campaignId ) {
await _changeStatus ( context , campaignId , [ CampaignStatus . INACTIVE ] , CampaignStatus . ACTIVE , 'Cannot enable campaign unless it is in INACTIVE state' ) ;
}
async function disable ( context , campaignId ) {
await _changeStatus ( context , campaignId , [ CampaignStatus . ACTIVE ] , CampaignStatus . INACTIVE , 'Cannot disable campaign unless it is in ACTIVE state' ) ;
}
2018-12-16 12:47:08 +00:00
async function getStatisticsOverview ( context , id ) {
return await knex . transaction ( async tx => {
await shares . enforceEntityPermissionTx ( tx , context , 'campaign' , id , 'viewStats' ) ;
const stats = await tx ( 'campaigns' ) . where ( 'id' , id ) . select ( [ 'delivered' , 'unsubscribed' , 'bounced' , 'complained' , 'blacklisted' , 'opened' , 'clicks' ] ) . first ( ) ;
const totalQryGen = await getSubscribersQueryGeneratorTx ( tx , id , false ) ;
if ( totalQryGen ) {
const res = await totalQryGen ( tx ) . count ( '* AS subscriptionsTotal' ) . first ( ) ;
stats . total = res . subscriptionsTotal ;
} else {
stats . total = 0 ;
}
return stats ;
} ) ;
}
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 ) . groupBy ( 'device_type' ) . select ( 'device_type AS key' ) . count ( '* as count' ) ;
const countries = await tx ( 'campaign_links' ) . where ( 'campaign' , id ) . groupBy ( 'country' ) . select ( 'country AS key' ) . count ( '* as count' ) ;
return {
devices ,
countries
} ;
} ) ;
}
2018-11-17 01:54:23 +00:00
2018-09-09 22:55:44 +00:00
module . exports . Content = Content ;
module . exports . hash = hash ;
2018-12-16 12:47:08 +00:00
2018-09-09 22:55:44 +00:00
module . exports . listDTAjax = listDTAjax ;
2018-11-17 01:54:23 +00:00
module . exports . listChildrenDTAjax = listChildrenDTAjax ;
2018-09-09 22:55:44 +00:00
module . exports . listWithContentDTAjax = listWithContentDTAjax ;
module . exports . listOthersWhoseListsAreIncludedDTAjax = listOthersWhoseListsAreIncludedDTAjax ;
module . exports . listTestUsersDTAjax = listTestUsersDTAjax ;
2018-12-16 12:47:08 +00:00
module . exports . listSentByStatusDTAjax = listSentByStatusDTAjax ;
module . exports . listOpensDTAjax = listOpensDTAjax ;
module . exports . listLinkClicksDTAjax = listLinkClicksDTAjax ;
2018-09-09 22:55:44 +00:00
module . exports . getByIdTx = getByIdTx ;
module . exports . getById = getById ;
module . exports . create = create ;
module . exports . createRssTx = createRssTx ;
module . exports . updateWithConsistencyCheck = updateWithConsistencyCheck ;
module . exports . remove = remove ;
module . exports . enforceSendPermissionTx = enforceSendPermissionTx ;
module . exports . getMessageCid = getMessageCid ;
module . exports . getMessageByCid = getMessageByCid ;
module . exports . getMessageByResponseId = getMessageByResponseId ;
module . exports . changeStatusByCampaignCidAndSubscriptionIdTx = changeStatusByCampaignCidAndSubscriptionIdTx ;
module . exports . changeStatusByMessage = changeStatusByMessage ;
2018-09-18 09:03:36 +00:00
module . exports . updateMessageResponse = updateMessageResponse ;
2018-09-09 22:55:44 +00:00
module . exports . getSubscribersQueryGeneratorTx = getSubscribersQueryGeneratorTx ;
module . exports . start = start ;
module . exports . stop = stop ;
2018-09-22 16:12:22 +00:00
module . exports . reset = reset ;
2018-11-17 01:54:23 +00:00
module . exports . enable = enable ;
module . exports . disable = disable ;
2018-09-22 16:12:22 +00:00
2018-09-23 20:28:58 +00:00
module . exports . rawGetByTx = rawGetByTx ;
2018-12-16 12:47:08 +00:00
module . exports . getTrackingSettingsByCidTx = getTrackingSettingsByCidTx ;
module . exports . getStatisticsOverview = getStatisticsOverview ;
module . exports . getStatisticsOpened = getStatisticsOpened ;