633 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			633 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| let tools = require('../tools');
 | |
| let db = require('../db');
 | |
| let fields = require('./fields');
 | |
| 
 | |
| module.exports.defaultColumns = [{
 | |
|     column: 'email',
 | |
|     name: 'Email address',
 | |
|     type: 'string'
 | |
| }, {
 | |
|     column: 'opt_in_country',
 | |
|     name: 'Signup country',
 | |
|     type: 'string'
 | |
| }, {
 | |
|     column: 'created',
 | |
|     name: 'Sign up date',
 | |
|     type: 'date'
 | |
| }, {
 | |
|     column: 'latest_open',
 | |
|     name: 'Latest open',
 | |
|     type: 'date'
 | |
| }, {
 | |
|     column: 'latest_click',
 | |
|     name: 'Latest click',
 | |
|     type: 'date'
 | |
| }, {
 | |
|     column: 'first_name',
 | |
|     name: 'First name',
 | |
|     type: 'string'
 | |
| }, {
 | |
|     column: 'last_name',
 | |
|     name: 'Last name',
 | |
|     type: 'string'
 | |
| }];
 | |
| 
 | |
| module.exports.list = (listId, callback) => {
 | |
|     listId = Number(listId) || 0;
 | |
| 
 | |
|     if (listId < 1) {
 | |
|         return callback(new Error('Missing List ID'));
 | |
|     }
 | |
| 
 | |
| 
 | |
|     db.getConnection((err, connection) => {
 | |
|         if (err) {
 | |
|             return callback(err);
 | |
|         }
 | |
| 
 | |
|         let query = 'SELECT * FROM segments WHERE list=? ORDER BY name';
 | |
|         connection.query(query, [listId], (err, rows) => {
 | |
|             connection.release();
 | |
|             if (err) {
 | |
|                 return callback(err);
 | |
|             }
 | |
| 
 | |
|             let segments = (rows || []).map(tools.convertKeys);
 | |
|             return callback(null, segments);
 | |
|         });
 | |
|     });
 | |
| };
 | |
| 
 | |
| module.exports.get = (id, callback) => {
 | |
|     id = Number(id) || 0;
 | |
| 
 | |
|     if (id < 1) {
 | |
|         return callback(new Error('Missing Segment ID'));
 | |
|     }
 | |
| 
 | |
|     db.getConnection((err, connection) => {
 | |
|         if (err) {
 | |
|             return callback(err);
 | |
|         }
 | |
| 
 | |
|         let query = 'SELECT * FROM segments WHERE id=? LIMIT 1';
 | |
|         connection.query(query, [id], (err, rows) => {
 | |
|             if (err) {
 | |
|                 connection.release();
 | |
|                 return callback(err);
 | |
|             }
 | |
|             if (!rows || !rows.length) {
 | |
|                 connection.release();
 | |
|                 return callback(new Error('Segment not found'));
 | |
|             }
 | |
| 
 | |
|             let segment = tools.convertKeys(rows[0]);
 | |
| 
 | |
|             let query = 'SELECT * FROM segment_rules WHERE segment=? ORDER BY id ASC';
 | |
|             connection.query(query, [id], (err, rows) => {
 | |
|                 connection.release();
 | |
|                 if (err) {
 | |
|                     return callback(err);
 | |
|                 }
 | |
| 
 | |
|                 fields.list(segment.list, (err, fieldList) => {
 | |
|                     if (err || !fieldList) {
 | |
|                         fieldList = [];
 | |
|                     }
 | |
| 
 | |
|                     segment.columns = [].concat(module.exports.defaultColumns);
 | |
|                     fieldList.forEach(field => {
 | |
|                         if (fields.genericTypes[field.type] === 'textarea') {
 | |
|                             return;
 | |
|                         }
 | |
|                         if (field.column) {
 | |
|                             segment.columns.push({
 | |
|                                 column: field.column,
 | |
|                                 name: field.name,
 | |
|                                 type: fields.genericTypes[field.type] || 'string'
 | |
|                             });
 | |
|                         }
 | |
|                         if (field.options) {
 | |
|                             field.options.forEach(subField => {
 | |
|                                 if (subField.column) {
 | |
|                                     segment.columns.push({
 | |
|                                         column: subField.column,
 | |
|                                         name: field.name + ': ' + subField.name,
 | |
|                                         type: fields.genericTypes[subField.type] || 'string'
 | |
|                                     });
 | |
|                                 }
 | |
|                             });
 | |
|                         }
 | |
|                     });
 | |
| 
 | |
|                     segment.rules = (rows || []).map(rule => {
 | |
|                         rule = tools.convertKeys(rule);
 | |
|                         if (rule.value) {
 | |
|                             try {
 | |
|                                 rule.value = JSON.parse(rule.value);
 | |
|                             } catch (E) {
 | |
|                                 // ignore
 | |
|                             }
 | |
|                         }
 | |
|                         if (!rule.value) {
 | |
|                             rule.value = {};
 | |
|                         }
 | |
|                         rule.columnType = segment.columns.filter(column => rule.column === column.column).pop() || {};
 | |
|                         rule.name = rule.columnType.name || '';
 | |
|                         switch (rule.columnType.type) {
 | |
|                             case 'number':
 | |
|                             case 'date':
 | |
|                             case 'birthday':
 | |
|                                 if (rule.value.range) {
 | |
|                                     rule.formatted = (rule.value.start || '') + ' … ' + (rule.value.end || '');
 | |
|                                 } else {
 | |
|                                     rule.formatted = rule.value.value || '';
 | |
|                                 }
 | |
|                                 break;
 | |
|                             case 'boolean':
 | |
|                                 rule.formatted = rule.value.value ? 'Selected' : 'Not selected';
 | |
|                                 break;
 | |
|                             default:
 | |
|                                 rule.formatted = rule.value.value || '';
 | |
|                         }
 | |
| 
 | |
|                         return rule;
 | |
|                     });
 | |
| 
 | |
|                     return callback(null, segment);
 | |
|                 });
 | |
|             });
 | |
|         });
 | |
|     });
 | |
| };
 | |
| 
 | |
| module.exports.create = (listId, segment, callback) => {
 | |
|     listId = Number(listId) || 0;
 | |
| 
 | |
|     if (listId < 1) {
 | |
|         return callback(new Error('Missing List ID'));
 | |
|     }
 | |
| 
 | |
|     segment = tools.convertKeys(segment);
 | |
| 
 | |
|     segment.name = (segment.name || '').toString().trim();
 | |
|     segment.type = Number(segment.type) || 0;
 | |
| 
 | |
|     if (!segment.name) {
 | |
|         return callback(new Error('Field Name must be set'));
 | |
|     }
 | |
| 
 | |
|     if (segment.type <= 0) {
 | |
|         return callback(new Error('Invalid segment rule type'));
 | |
|     }
 | |
| 
 | |
|     let keys = ['list', 'name', 'type'];
 | |
|     let values = [listId, segment.name, segment.type];
 | |
| 
 | |
|     db.getConnection((err, connection) => {
 | |
|         if (err) {
 | |
|             return callback(err);
 | |
|         }
 | |
| 
 | |
|         let query = 'INSERT INTO segments (`' + keys.join('`, `') + '`) VALUES (' + values.map(() => '?').join(',') + ')';
 | |
|         connection.query(query, values, (err, result) => {
 | |
|             connection.release();
 | |
|             if (err) {
 | |
|                 return callback(err);
 | |
|             }
 | |
|             return callback(null, result && result.insertId || false);
 | |
|         });
 | |
|     });
 | |
| };
 | |
| 
 | |
| module.exports.update = (id, updates, callback) => {
 | |
|     updates = updates || {};
 | |
|     id = Number(id) || 0;
 | |
| 
 | |
|     if (id < 1) {
 | |
|         return callback(new Error('Missing Segment ID'));
 | |
|     }
 | |
| 
 | |
|     let segment = tools.convertKeys(updates);
 | |
| 
 | |
|     segment.name = (segment.name || '').toString().trim();
 | |
|     segment.type = Number(segment.type) || 0;
 | |
| 
 | |
|     if (!segment.name) {
 | |
|         return callback(new Error('Field Name must be set'));
 | |
|     }
 | |
| 
 | |
|     if (segment.type <= 0) {
 | |
|         return callback(new Error('Invalid segment rule type'));
 | |
|     }
 | |
| 
 | |
|     let keys = ['name', 'type'];
 | |
|     let values = [segment.name, segment.type];
 | |
| 
 | |
|     db.getConnection((err, connection) => {
 | |
|         if (err) {
 | |
|             return callback(err);
 | |
|         }
 | |
| 
 | |
|         values.push(id);
 | |
| 
 | |
|         connection.query('UPDATE segments SET ' + keys.map(key => '`' + key + '`=?').join(', ') + ' WHERE id=? LIMIT 1', values, (err, result) => {
 | |
|             connection.release();
 | |
|             if (err) {
 | |
|                 return callback(err);
 | |
|             }
 | |
|             return callback(null, result && result.affectedRows || false);
 | |
|         });
 | |
|     });
 | |
| };
 | |
| 
 | |
| module.exports.delete = (id, callback) => {
 | |
|     id = Number(id) || 0;
 | |
| 
 | |
|     if (id < 1) {
 | |
|         return callback(new Error('Missing Segment ID'));
 | |
|     }
 | |
| 
 | |
|     db.getConnection((err, connection) => {
 | |
|         if (err) {
 | |
|             return callback(err);
 | |
|         }
 | |
| 
 | |
|         connection.query('DELETE FROM segments WHERE id=? LIMIT 1', [id], err => {
 | |
|             connection.release();
 | |
|             if (err) {
 | |
|                 return callback(err);
 | |
|             }
 | |
|             return callback(null, true);
 | |
|         });
 | |
|     });
 | |
| };
 | |
| 
 | |
| module.exports.createRule = (segmentId, rule, callback) => {
 | |
|     segmentId = Number(segmentId) || 0;
 | |
| 
 | |
|     if (segmentId < 1) {
 | |
|         return callback(new Error('Missing Segment ID'));
 | |
|     }
 | |
| 
 | |
|     rule = tools.convertKeys(rule);
 | |
| 
 | |
|     module.exports.get(segmentId, (err, segment) => {
 | |
|         if (err) {
 | |
|             return callback(err);
 | |
|         }
 | |
| 
 | |
|         if (!segment) {
 | |
|             return callback(new Error('Selected segment not found'));
 | |
|         }
 | |
| 
 | |
|         let column = segment.columns.filter(column => column.column === rule.column).pop();
 | |
|         if (!column) {
 | |
|             return callback(new Error('Invalid rule type'));
 | |
|         }
 | |
| 
 | |
|         let value;
 | |
| 
 | |
|         switch (column.type) {
 | |
|             case 'date':
 | |
|             case 'birthday':
 | |
|             case 'number':
 | |
|                 if (rule.range) {
 | |
|                     value = {
 | |
|                         range: true,
 | |
|                         start: rule.start,
 | |
|                         end: rule.end
 | |
|                     };
 | |
|                 } else {
 | |
|                     value = {
 | |
|                         value: rule.value
 | |
|                     };
 | |
|                 }
 | |
|                 break;
 | |
|             case 'boolean':
 | |
|                 value = {
 | |
|                     value: rule.value ? 1 : 0
 | |
|                 };
 | |
|                 break;
 | |
|             default:
 | |
|                 value = {
 | |
|                     value: rule.value
 | |
|                 };
 | |
|         }
 | |
| 
 | |
|         let keys = ['segment', 'column', 'value'];
 | |
|         let values = [segment.id, rule.column, JSON.stringify(value)];
 | |
| 
 | |
|         db.getConnection((err, connection) => {
 | |
|             if (err) {
 | |
|                 return callback(err);
 | |
|             }
 | |
| 
 | |
|             let query = 'INSERT INTO segment_rules (`' + keys.join('`, `') + '`) VALUES (' + values.map(() => '?').join(',') + ')';
 | |
|             connection.query(query, values, (err, result) => {
 | |
|                 connection.release();
 | |
|                 if (err) {
 | |
|                     return callback(err);
 | |
|                 }
 | |
|                 return callback(null, result && result.insertId || false);
 | |
|             });
 | |
|         });
 | |
|     });
 | |
| };
 | |
| 
 | |
| module.exports.getRule = (id, callback) => {
 | |
|     id = Number(id) || 0;
 | |
| 
 | |
|     if (id < 1) {
 | |
|         return callback(new Error('Missing Rule ID'));
 | |
|     }
 | |
| 
 | |
|     db.getConnection((err, connection) => {
 | |
|         if (err) {
 | |
|             return callback(err);
 | |
|         }
 | |
| 
 | |
|         let query = 'SELECT * FROM segment_rules WHERE id=? LIMIT 1';
 | |
|         connection.query(query, [id], (err, rows) => {
 | |
|             connection.release();
 | |
|             if (err) {
 | |
|                 return callback(err);
 | |
|             }
 | |
| 
 | |
|             if (!rows || !rows.length) {
 | |
|                 return callback(new Error('Specified rule not found'));
 | |
|             }
 | |
| 
 | |
|             let rule = tools.convertKeys(rows[0]);
 | |
| 
 | |
|             module.exports.get(rule.segment, (err, segment) => {
 | |
|                 if (err) {
 | |
|                     return callback(err);
 | |
|                 }
 | |
| 
 | |
|                 if (!segment) {
 | |
|                     return callback(new Error('Specified segment not found'));
 | |
|                 }
 | |
| 
 | |
|                 if (rule.value) {
 | |
|                     try {
 | |
|                         rule.value = JSON.parse(rule.value);
 | |
|                     } catch (E) {
 | |
|                         // ignore
 | |
|                     }
 | |
|                 }
 | |
|                 if (!rule.value) {
 | |
|                     rule.value = {};
 | |
|                 }
 | |
| 
 | |
|                 rule.columnType = segment.columns.filter(column => rule.column === column.column).pop() || {};
 | |
| 
 | |
|                 rule.name = rule.columnType.name || '';
 | |
|                 switch (rule.columnType.type) {
 | |
|                     case 'number':
 | |
|                     case 'date':
 | |
|                     case 'birthday':
 | |
|                         if (rule.value.range) {
 | |
|                             rule.formatted = (rule.value.start || '') + ' … ' + (rule.value.end || '');
 | |
|                         } else {
 | |
|                             rule.formatted = rule.value.value || '';
 | |
|                         }
 | |
|                         break;
 | |
|                     case 'boolean':
 | |
|                         rule.formatted = rule.value.value ? 'Selected' : 'Not selected';
 | |
|                         break;
 | |
|                     default:
 | |
|                         rule.formatted = rule.value.value || '';
 | |
|                 }
 | |
| 
 | |
|                 return callback(null, rule);
 | |
|             });
 | |
|         });
 | |
|     });
 | |
| };
 | |
| 
 | |
| module.exports.updateRule = (id, rule, callback) => {
 | |
|     id = Number(id) || 0;
 | |
| 
 | |
|     if (id < 1) {
 | |
|         return callback(new Error('Missing Rule ID'));
 | |
|     }
 | |
| 
 | |
|     rule = tools.convertKeys(rule);
 | |
| 
 | |
|     module.exports.getRule(id, (err, existingRule) => {
 | |
|         if (err) {
 | |
|             return callback(err);
 | |
|         }
 | |
| 
 | |
|         if (!existingRule) {
 | |
|             return callback(new Error('Selected rule not found'));
 | |
|         }
 | |
| 
 | |
|         module.exports.get(existingRule.segment, (err, segment) => {
 | |
|             if (err) {
 | |
|                 return callback(err);
 | |
|             }
 | |
| 
 | |
|             if (!segment) {
 | |
|                 return callback(new Error('Selected segment not found'));
 | |
|             }
 | |
| 
 | |
|             let column = segment.columns.filter(column => column.column === existingRule.column).pop();
 | |
|             if (!column) {
 | |
|                 return callback(new Error('Invalid rule type'));
 | |
|             }
 | |
| 
 | |
|             let value;
 | |
|             switch (column.type) {
 | |
|                 case 'date':
 | |
|                 case 'birthday':
 | |
|                 case 'number':
 | |
|                     if (rule.range) {
 | |
|                         value = {
 | |
|                             range: true,
 | |
|                             start: rule.start,
 | |
|                             end: rule.end
 | |
|                         };
 | |
|                     } else {
 | |
|                         value = {
 | |
|                             value: rule.value
 | |
|                         };
 | |
|                     }
 | |
|                     break;
 | |
|                 case 'boolean':
 | |
|                     value = {
 | |
|                         value: rule.value ? 1 : 0
 | |
|                     };
 | |
|                     break;
 | |
|                 default:
 | |
|                     value = {
 | |
|                         value: rule.value
 | |
|                     };
 | |
|             }
 | |
| 
 | |
|             let keys = ['value'];
 | |
|             let values = [JSON.stringify(value)];
 | |
| 
 | |
|             db.getConnection((err, connection) => {
 | |
|                 if (err) {
 | |
|                     return callback(err);
 | |
|                 }
 | |
| 
 | |
|                 values.push(id);
 | |
| 
 | |
|                 connection.query('UPDATE segment_rules SET ' + keys.map(key => '`' + key + '`=?').join(', ') + ' WHERE id=? LIMIT 1', values, (err, result) => {
 | |
|                     connection.release();
 | |
|                     if (err) {
 | |
|                         return callback(err);
 | |
|                     }
 | |
|                     return callback(null, result && result.affectedRows || false);
 | |
|                 });
 | |
|             });
 | |
|         });
 | |
|     });
 | |
| };
 | |
| 
 | |
| module.exports.deleteRule = (id, callback) => {
 | |
|     id = Number(id) || 0;
 | |
| 
 | |
|     if (id < 1) {
 | |
|         return callback(new Error('Missing Rule ID'));
 | |
|     }
 | |
| 
 | |
|     db.getConnection((err, connection) => {
 | |
|         if (err) {
 | |
|             return callback(err);
 | |
|         }
 | |
| 
 | |
|         connection.query('DELETE FROM segment_rules WHERE id=? LIMIT 1', [id], err => {
 | |
|             connection.release();
 | |
|             if (err) {
 | |
|                 return callback(err);
 | |
|             }
 | |
|             return callback(null, true);
 | |
|         });
 | |
|     });
 | |
| };
 | |
| 
 | |
| module.exports.getQuery = (id, callback) => {
 | |
|     module.exports.get(id, (err, segment) => {
 | |
|         if (err) {
 | |
|             return callback(err);
 | |
|         }
 | |
| 
 | |
|         if (!segment) {
 | |
|             return callback(new Error('Segment not found'));
 | |
|         }
 | |
| 
 | |
|         let query = [];
 | |
|         let values = [];
 | |
| 
 | |
|         let getDate = (value, nextDay) => {
 | |
|             let parts = value.trim().split(/\D/);
 | |
|             let year = Number(parts.shift()) || 0;
 | |
|             let month = Number(parts.shift()) || 0;
 | |
|             let day = Number(parts.shift()) || 0;
 | |
|             if (!year || !month || !day) {
 | |
|                 return false;
 | |
|             }
 | |
|             return new Date(Date.UTC(year, month - 1, day + (nextDay ? 1 : 0)));
 | |
|         };
 | |
| 
 | |
|         segment.rules.forEach(rule => {
 | |
|             switch (rule.columnType.type) {
 | |
|                 case 'string':
 | |
|                     query.push('`' + rule.columnType.column + '` LIKE ?');
 | |
|                     values.push(rule.value.value);
 | |
|                     break;
 | |
|                 case 'boolean':
 | |
|                     query.push('`' + rule.columnType.column + '` = ?');
 | |
|                     values.push(rule.value.value);
 | |
|                     break;
 | |
|                 case 'number':
 | |
|                     if (rule.value.range) {
 | |
|                         query.push('`' + rule.columnType.column + '` >= ?');
 | |
|                         query.push('`' + rule.columnType.column + '` < ?');
 | |
|                         values.push(rule.value.start);
 | |
|                         values.push(rule.value.end);
 | |
|                     } else {
 | |
|                         query.push('`' + rule.columnType.column + '` = ?');
 | |
|                         values.push(rule.value.value);
 | |
|                     }
 | |
|                     break;
 | |
|                 case 'birthday':
 | |
|                     if (rule.value.range) {
 | |
|                         query.push('`' + rule.columnType.column + '` >= ?');
 | |
|                         query.push('`' + rule.columnType.column + '` < ?');
 | |
|                         values.push(getDate('2000-' + rule.value.start));
 | |
|                         values.push(getDate('2000-' + rule.value.end, true));
 | |
|                     } else {
 | |
|                         query.push('`' + rule.columnType.column + '` >= ?');
 | |
|                         query.push('`' + rule.columnType.column + '` < ?');
 | |
|                         values.push(getDate('2000-' + rule.value.value));
 | |
|                         values.push(getDate('2000-' + rule.value.value, true));
 | |
|                     }
 | |
|                     break;
 | |
|                 case 'date':
 | |
|                     if (rule.value.range) {
 | |
|                         query.push('`' + rule.columnType.column + '` >= ?');
 | |
|                         query.push('`' + rule.columnType.column + '` < ?');
 | |
|                         values.push(getDate(rule.value.start));
 | |
|                         values.push(getDate(rule.value.end, true));
 | |
|                     } else {
 | |
|                         query.push('`' + rule.columnType.column + '` >= ?');
 | |
|                         query.push('`' + rule.columnType.column + '` < ?');
 | |
|                         values.push(getDate(rule.value.value));
 | |
|                         values.push(getDate(rule.value.value, true));
 | |
|                     }
 | |
| 
 | |
|                     break;
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         return callback(null, {
 | |
|             where: query.join(segment.type === 1 ? ' AND ' : ' OR ') || '1',
 | |
|             values
 | |
|         });
 | |
|     });
 | |
| };
 | |
| 
 | |
| module.exports.subscribers = (id, onlySubscribed, callback) => {
 | |
|     module.exports.get(id, (err, segment) => {
 | |
|         if (err) {
 | |
|             return callback(err);
 | |
|         }
 | |
|         if (!segment) {
 | |
|             return callback(new Error('Segment not found'));
 | |
|         }
 | |
|         module.exports.getQuery(id, (err, queryData) => {
 | |
|             if (err) {
 | |
|                 return callback(err);
 | |
|             }
 | |
| 
 | |
|             db.getConnection((err, connection) => {
 | |
|                 if (err) {
 | |
|                     return callback(err);
 | |
|                 }
 | |
| 
 | |
|                 let query;
 | |
|                 if (!onlySubscribed) {
 | |
|                     query = 'SELECT COUNT(id) AS `count` FROM `subscription__' + segment.list + '` WHERE ' + queryData.where + ' LIMIT 1';
 | |
|                 } else {
 | |
|                     query = 'SELECT COUNT(id) AS `count` FROM `subscription__' + segment.list + '` WHERE `status`=1 ' + (queryData.where ? ' AND (' + queryData.where + ')' : '') + ' LIMIT 1';
 | |
|                 }
 | |
| 
 | |
|                 connection.query(query, queryData.values, (err, rows) => {
 | |
|                     connection.release();
 | |
|                     if (err) {
 | |
|                         return callback(err);
 | |
|                     }
 | |
|                     let count = rows && rows[0] && rows[0].count || 0;
 | |
|                     return callback(null, count);
 | |
|                 });
 | |
|             });
 | |
|         });
 | |
|     });
 | |
| };
 |