'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 (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) => { connection.release(); 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) => { if (err) { return callback(err); } let count = rows && rows[0] && rows[0].count || 0; return callback(null, count); }); }); }); }); };