2017-07-11 09:28:44 +00:00
'use strict' ;
const knex = require ( '../lib/knex' ) ;
2017-07-29 19:42:07 +00:00
const hasher = require ( 'node-object-hash' ) ( ) ;
2017-07-11 09:28:44 +00:00
const dtHelpers = require ( '../lib/dt-helpers' ) ;
2017-07-29 19:42:07 +00:00
const shortid = require ( 'shortid' ) ;
const { enforce , filterObject } = require ( '../lib/helpers' ) ;
2018-11-18 14:38:52 +00:00
const interoperableErrors = require ( '../../shared/interoperable-errors' ) ;
2017-07-29 19:42:07 +00:00
const shares = require ( './shares' ) ;
const namespaceHelpers = require ( '../lib/namespace-helpers' ) ;
2017-08-13 18:11:58 +00:00
const fields = require ( './fields' ) ;
2017-08-14 20:53:29 +00:00
const segments = require ( './segments' ) ;
2018-09-29 18:08:49 +00:00
const imports = require ( './imports' ) ;
2018-08-04 09:30:37 +00:00
const entitySettings = require ( '../lib/entity-settings' ) ;
2018-09-29 18:08:49 +00:00
const dependencyHelpers = require ( '../lib/dependency-helpers' ) ;
2017-07-11 09:28:44 +00:00
2019-02-07 14:38:32 +00:00
const { EntityActivityType } = require ( '../../shared/activity-log' ) ;
const activityLog = require ( '../lib/activity-log' ) ;
2018-12-31 09:45:59 +00:00
const { UnsubscriptionMode , FieldWizard } = require ( '../../shared/lists' ) ;
2017-07-29 19:42:07 +00:00
2018-11-24 05:48:41 +00:00
const allowedKeys = new Set ( [ 'name' , 'description' , 'default_form' , 'public_subscribe' , 'unsubscription_mode' , 'contact_email' , 'homepage' , 'namespace' , 'to_name' , 'listunsubscribe_disabled' , 'send_configuration' ] ) ;
2017-07-29 19:42:07 +00:00
function hash ( entity ) {
return hasher . hash ( filterObject ( entity , allowedKeys ) ) ;
}
2019-03-26 23:41:18 +00:00
async function _listDTAjax ( context , namespaceId , params ) {
2018-08-04 09:30:37 +00:00
const campaignEntityType = entitySettings . getEntityType ( 'campaign' ) ;
2017-07-29 19:42:07 +00:00
return await dtHelpers . ajaxListWithPermissions (
context ,
[ { entityTypeId : 'list' , requiredOperations : [ 'view' ] } ] ,
params ,
2019-03-26 23:41:18 +00:00
builder => {
builder = builder
. from ( 'lists' )
. innerJoin ( 'namespaces' , 'namespaces.id' , 'lists.namespace' ) ;
if ( namespaceId ) {
builder = builder . where ( 'lists.namespace' , namespaceId ) ;
}
return builder ;
} ,
2018-08-04 09:30:37 +00:00
[ 'lists.id' , 'lists.name' , 'lists.cid' , 'lists.subscribers' , 'lists.description' , 'namespaces.name' ,
2019-01-12 10:21:38 +00:00
{
name : 'triggerCount' ,
query : builder =>
builder . from ( 'campaigns' )
. innerJoin ( 'campaign_lists' , 'campaigns.id' , 'campaign_lists.campaign' )
. innerJoin ( 'triggers' , 'campaigns.id' , 'triggers.campaign' )
. innerJoin ( campaignEntityType . permissionsTable , 'campaigns.id' , ` ${ campaignEntityType . permissionsTable } .entity ` )
. whereRaw ( 'campaign_lists.list = lists.id' )
. where ( ` ${ campaignEntityType . permissionsTable } .operation ` , 'viewTriggers' )
. count ( )
. as ( 'triggerCount' )
2018-08-04 09:30:37 +00:00
}
]
2017-07-29 19:42:07 +00:00
) ;
2017-07-11 09:28:44 +00:00
}
2019-03-26 23:41:18 +00:00
async function listDTAjax ( context , params ) {
return await _listDTAjax ( context , undefined , params ) ;
}
2019-03-13 08:52:02 +00:00
2019-03-26 23:41:18 +00:00
async function listByNamespaceDTAjax ( context , namespaceId , params ) {
return await _listDTAjax ( context , namespaceId , params ) ;
2019-03-13 08:52:02 +00:00
}
2018-09-09 22:55:44 +00:00
async function listWithSegmentByCampaignDTAjax ( context , campaignId , params ) {
return await dtHelpers . ajaxListWithPermissions (
context ,
[ { entityTypeId : 'list' , requiredOperations : [ 'view' ] } ] ,
params ,
builder => builder
. from ( 'lists' )
. innerJoin ( 'campaign_lists' , 'campaign_lists.list' , 'lists.id' )
. leftJoin ( 'segments' , 'segments.id' , 'campaign_lists.segment' )
. innerJoin ( 'namespaces' , 'namespaces.id' , 'lists.namespace' )
. where ( 'campaign_lists.campaign' , campaignId )
. orderBy ( 'campaign_lists.id' , 'asc' ) ,
[ 'lists.id' , 'lists.name' , 'lists.cid' , 'namespaces.name' , 'segments.name' ]
) ;
}
2018-09-18 08:30:13 +00:00
async function getByIdTx ( tx , context , id ) {
2018-01-27 15:37:14 +00:00
await shares . enforceEntityPermissionTx ( tx , context , 'list' , id , 'view' ) ;
2017-12-10 20:44:35 +00:00
const entity = await tx ( 'lists' ) . where ( 'id' , id ) . first ( ) ;
return entity ;
}
2017-07-29 19:42:07 +00:00
async function getById ( context , id ) {
2017-08-13 18:11:58 +00:00
return await knex . transaction ( async tx => {
2018-01-27 15:37:14 +00:00
// note that permissions are not obtained here as this methods is used only with synthetic admin context
2018-09-18 08:30:13 +00:00
return await getByIdTx ( tx , context , id ) ;
2017-12-10 20:44:35 +00:00
} ) ;
}
async function getByIdWithListFields ( context , id ) {
return await knex . transaction ( async tx => {
2018-09-18 08:30:13 +00:00
const entity = await getByIdTx ( tx , context , id ) ;
2018-01-27 15:37:14 +00:00
entity . permissions = await shares . getPermissionsTx ( tx , context , 'list' , id ) ;
2017-08-13 18:11:58 +00:00
entity . listFields = await fields . listByOrderListTx ( tx , id ) ;
return entity ;
2017-08-11 16:16:44 +00:00
} ) ;
2017-07-13 11:27:03 +00:00
}
2018-09-18 08:30:13 +00:00
async function getByCidTx ( tx , context , cid ) {
const entity = await tx ( 'lists' ) . where ( 'cid' , cid ) . first ( ) ;
if ( ! entity ) {
shares . throwPermissionDenied ( ) ;
}
await shares . enforceEntityPermissionTx ( tx , context , 'list' , entity . id , 'view' ) ;
return entity ;
}
2017-12-10 20:44:35 +00:00
async function getByCid ( context , cid ) {
return await knex . transaction ( async tx => {
2018-09-18 08:30:13 +00:00
return getByCidTx ( tx , context , cid ) ;
2017-12-10 20:44:35 +00:00
} ) ;
}
2018-07-31 04:34:28 +00:00
async function _validateAndPreprocess ( tx , entity ) {
await namespaceHelpers . validateEntity ( tx , entity ) ;
enforce ( entity . unsubscription _mode >= UnsubscriptionMode . MIN && entity . unsubscription _mode <= UnsubscriptionMode . MAX , 'Unknown unsubscription mode' ) ;
}
2017-07-29 19:42:07 +00:00
async function create ( context , entity ) {
2017-08-13 18:11:58 +00:00
return await knex . transaction ( async tx => {
2017-08-11 06:51:30 +00:00
await shares . enforceEntityPermissionTx ( tx , context , 'namespace' , entity . namespace , 'createList' ) ;
2018-07-31 04:34:28 +00:00
await _validateAndPreprocess ( tx , entity ) ;
2017-07-29 19:42:07 +00:00
2018-12-31 09:45:59 +00:00
const fieldsToAdd = [ ] ;
const fieldWizard = entity . fieldWizard ;
if ( fieldWizard === FieldWizard . FIRST _LAST _NAME ) {
if ( entity . to _name === null ) {
entity . to _name = '[MERGE_FIRST_NAME] [MERGE_LAST_NAME]' ;
}
fieldsToAdd . push ( {
name : 'First name' ,
key : 'MERGE_FIRST_NAME' ,
default _value : '' ,
type : 'text' ,
group : null ,
settings : { }
} ) ;
fieldsToAdd . push ( {
name : 'Last name' ,
key : 'MERGE_LAST_NAME' ,
default _value : '' ,
type : 'text' ,
group : null ,
settings : { }
} ) ;
} else if ( fieldWizard === FieldWizard . NAME ) {
if ( entity . to _name === null ) {
entity . to _name = '[MERGE_NAME]' ;
}
fieldsToAdd . push ( {
name : 'Name' ,
key : 'MERGE_NAME' ,
default _value : '' ,
type : 'text' ,
group : null ,
settings : { }
} ) ;
}
2017-07-29 19:42:07 +00:00
const filteredEntity = filterObject ( entity , allowedKeys ) ;
filteredEntity . cid = shortid . generate ( ) ;
const ids = await tx ( 'lists' ) . insert ( filteredEntity ) ;
2017-08-13 18:11:58 +00:00
const id = ids [ 0 ] ;
2017-07-29 19:42:07 +00:00
2018-07-31 04:34:28 +00:00
await knex . schema . raw ( 'CREATE TABLE `subscription__' + id + '` (\n' +
' `id` int(10) unsigned NOT NULL AUTO_INCREMENT,\n' +
' `cid` varchar(255) CHARACTER SET ascii NOT NULL,\n' +
2018-11-21 21:02:14 +00:00
' `email` varchar(255) CHARACTER SET utf8 DEFAULT NULL,\n' +
2018-08-06 14:54:51 +00:00
' `hash_email` varchar(255) CHARACTER SET ascii NOT NULL,\n' +
2018-11-24 05:58:14 +00:00
' `source_email` int(11) DEFAULT NULL,\n' + // Altough this is a reference to an import, it is represented as signed int(11). This is because we use negative values for constant from SubscriptionSource
2018-07-31 04:34:28 +00:00
' `opt_in_ip` varchar(100) DEFAULT NULL,\n' +
' `opt_in_country` varchar(2) DEFAULT NULL,\n' +
' `tz` varchar(100) CHARACTER SET ascii DEFAULT NULL,\n' +
' `status` tinyint(4) unsigned NOT NULL DEFAULT \'1\',\n' +
' `is_test` tinyint(4) unsigned NOT NULL DEFAULT \'0\',\n' +
' `status_change` timestamp NULL DEFAULT NULL,\n' +
2018-11-24 05:48:41 +00:00
' `unsubscribed` timestamp NULL DEFAULT NULL,\n' +
2018-07-31 04:34:28 +00:00
' `latest_open` timestamp NULL DEFAULT NULL,\n' +
' `latest_click` timestamp NULL DEFAULT NULL,\n' +
' `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n' +
' PRIMARY KEY (`id`),\n' +
2018-11-21 21:02:14 +00:00
' UNIQUE KEY `hash_email` (`hash_email`),\n' +
2018-07-31 04:34:28 +00:00
' UNIQUE KEY `cid` (`cid`),\n' +
2018-11-21 21:02:14 +00:00
' KEY `email` (`email`),\n' +
2018-07-31 04:34:28 +00:00
' KEY `status` (`status`),\n' +
' KEY `subscriber_tz` (`tz`),\n' +
' KEY `is_test` (`is_test`),\n' +
' KEY `latest_open` (`latest_open`),\n' +
' KEY `latest_click` (`latest_click`),\n' +
' KEY `created` (`created`)\n' +
') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;\n' ) ;
2017-07-29 19:42:07 +00:00
2018-11-24 05:48:41 +00:00
await shares . rebuildPermissionsTx ( tx , { entityTypeId : 'list' , entityId : id } ) ;
2018-12-31 09:45:59 +00:00
for ( const fld of fieldsToAdd ) {
await fields . createTx ( tx , context , id , fld ) ;
}
2019-02-07 14:38:32 +00:00
await activityLog . logEntityActivity ( 'list' , EntityActivityType . CREATE , id ) ;
2017-08-13 18:11:58 +00:00
return id ;
} ) ;
2017-07-29 19:42:07 +00:00
}
async function updateWithConsistencyCheck ( context , entity ) {
await knex . transaction ( async tx => {
2017-08-11 06:51:30 +00:00
await shares . enforceEntityPermissionTx ( tx , context , 'list' , entity . id , 'edit' ) ;
2017-07-29 19:42:07 +00:00
const existing = await tx ( 'lists' ) . where ( 'id' , entity . id ) . first ( ) ;
if ( ! existing ) {
throw new interoperableErrors . NotFoundError ( ) ;
}
const existingHash = hash ( existing ) ;
2017-08-11 16:16:44 +00:00
if ( existingHash !== entity . originalHash ) {
2017-07-29 19:42:07 +00:00
throw new interoperableErrors . ChangedError ( ) ;
}
2018-07-31 04:34:28 +00:00
await _validateAndPreprocess ( tx , entity ) ;
2017-07-29 19:42:07 +00:00
await namespaceHelpers . validateMove ( context , entity , existing , 'list' , 'createList' , 'delete' ) ;
await tx ( 'lists' ) . where ( 'id' , entity . id ) . update ( filterObject ( entity , allowedKeys ) ) ;
2017-08-14 20:53:29 +00:00
await shares . rebuildPermissionsTx ( tx , { entityTypeId : 'list' , entityId : entity . id } ) ;
2019-02-07 14:38:32 +00:00
await activityLog . logEntityActivity ( 'list' , EntityActivityType . UPDATE , entity . id ) ;
2017-07-29 19:42:07 +00:00
} ) ;
}
async function remove ( context , id ) {
await knex . transaction ( async tx => {
2017-08-11 06:51:30 +00:00
await shares . enforceEntityPermissionTx ( tx , context , 'list' , id , 'delete' ) ;
2018-09-29 18:08:49 +00:00
await dependencyHelpers . ensureNoDependencies ( tx , context , id , [
{
entityTypeId : 'campaign' ,
query : tx => tx ( 'campaign_lists' )
. where ( 'campaign_lists.list' , id )
. innerJoin ( 'campaigns' , 'campaign_lists.campaign' , 'campaigns.id' )
. select ( [ 'campaigns.id' , 'campaigns.name' ] )
}
] ) ;
2017-08-14 20:53:29 +00:00
2018-11-22 10:31:16 +00:00
await fields . removeAllByListIdTx ( tx , context , id ) ;
await segments . removeAllByListIdTx ( tx , context , id ) ;
await imports . removeAllByListIdTx ( tx , context , id ) ;
2017-07-29 19:42:07 +00:00
await tx ( 'lists' ) . where ( 'id' , id ) . del ( ) ;
await knex . schema . dropTableIfExists ( 'subscription__' + id ) ;
2019-02-07 14:38:32 +00:00
await activityLog . logEntityActivity ( 'list' , EntityActivityType . REMOVE , id ) ;
2017-07-29 19:42:07 +00:00
} ) ;
}
2017-07-11 09:28:44 +00:00
2018-09-09 22:55:44 +00:00
module . exports . UnsubscriptionMode = UnsubscriptionMode ;
module . exports . hash = hash ;
module . exports . listDTAjax = listDTAjax ;
2019-03-13 08:52:02 +00:00
module . exports . listByNamespaceDTAjax = listByNamespaceDTAjax ;
2018-09-09 22:55:44 +00:00
module . exports . listWithSegmentByCampaignDTAjax = listWithSegmentByCampaignDTAjax ;
2018-09-18 08:30:13 +00:00
module . exports . getByIdTx = getByIdTx ;
2018-09-09 22:55:44 +00:00
module . exports . getById = getById ;
module . exports . getByIdWithListFields = getByIdWithListFields ;
2018-09-18 08:30:13 +00:00
module . exports . getByCidTx = getByCidTx ;
2018-09-09 22:55:44 +00:00
module . exports . getByCid = getByCid ;
module . exports . create = create ;
module . exports . updateWithConsistencyCheck = updateWithConsistencyCheck ;
module . exports . remove = remove ;