Work in progress on subscriptions
This commit is contained in:
parent
eecb3cd067
commit
b22a87e712
18 changed files with 1729 additions and 884 deletions
|
@ -16,6 +16,53 @@ async function listDTAjax(context, params) {
|
|||
);
|
||||
}
|
||||
|
||||
/*
|
||||
module.exports.get = (start, limit, search, callback) => {
|
||||
db.getConnection((err, connection) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
search = '%' + search + '%';
|
||||
connection.query('SELECT SQL_CALC_FOUND_ROWS `email` FROM blacklist WHERE `email` LIKE ? ORDER BY `email` LIMIT ? OFFSET ?', [search, limit, start], (err, rows) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
connection.query('SELECT FOUND_ROWS() AS total', (err, total) => {
|
||||
connection.release();
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
let emails = [];
|
||||
rows.forEach(email => {
|
||||
emails.push(email.email);
|
||||
});
|
||||
return callback(null, emails, total && total[0] && total[0].total);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
*/
|
||||
|
||||
async function search(context, start, limit, search) {
|
||||
return await knex.transaction(async tx => {
|
||||
shares.enforceGlobalPermission(context, 'manageBlacklist');
|
||||
|
||||
search = '%' + search + '%';
|
||||
|
||||
const count = await tx('blacklist').where('email', 'like', search).count();
|
||||
// FIXME - the count won't likely work;
|
||||
console.log(count);
|
||||
|
||||
const rows = await tx('blacklist').where('email', 'like', search).offset(start).limit(limit);
|
||||
|
||||
return {
|
||||
emails: rows.map(row => row.email),
|
||||
total: count
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function add(context, email) {
|
||||
return await knex.transaction(async tx => {
|
||||
shares.enforceGlobalPermission(context, 'manageBlacklist');
|
||||
|
@ -56,6 +103,7 @@ module.exports = {
|
|||
listDTAjax,
|
||||
add,
|
||||
remove,
|
||||
search,
|
||||
isBlacklisted,
|
||||
serverValidate
|
||||
};
|
51
models/confirmations.js
Normal file
51
models/confirmations.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
'use strict';
|
||||
|
||||
const knex = require('../lib/knex');
|
||||
const shortid = require('shortid');
|
||||
|
||||
async function addConfirmation(listId, action, ip, data) {
|
||||
const cid = shortid.generate();
|
||||
await knex('confirmations').insert({
|
||||
cid,
|
||||
list: listId,
|
||||
action,
|
||||
ip,
|
||||
data: JSON.stringify(data || {})
|
||||
});
|
||||
|
||||
return cid;
|
||||
}
|
||||
|
||||
/*
|
||||
Atomically retrieves confirmation from the database, removes it from the database and returns it.
|
||||
*/
|
||||
async function takeConfirmation(cid) {
|
||||
return await knex.transaction(async tx => {
|
||||
const entry = await tx('confirmations').select(['cid', 'list', 'action', 'ip', 'data']).where('cid', cid).first();
|
||||
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await tx('confirmations').where('cid', cid).del();
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(entry.data);
|
||||
} catch (err) {
|
||||
data = {};
|
||||
}
|
||||
|
||||
return {
|
||||
list: entry.list,
|
||||
action: entry.action,
|
||||
ip: entry.ip,
|
||||
data
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
addConfirmation,
|
||||
takeConfirmation
|
||||
};
|
|
@ -7,7 +7,6 @@ const { enforce, filterObject } = require('../lib/helpers');
|
|||
const dtHelpers = require('../lib/dt-helpers');
|
||||
const interoperableErrors = require('../shared/interoperable-errors');
|
||||
const shares = require('./shares');
|
||||
const bluebird = require('bluebird');
|
||||
const validators = require('../shared/validators');
|
||||
const shortid = require('shortid');
|
||||
const segments = require('./segments');
|
||||
|
@ -154,7 +153,7 @@ async function listTx(tx, listId) {
|
|||
|
||||
async function list(context, listId) {
|
||||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, ['manageFields', 'manageSegments']);
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, ['viewSubscriptions', 'manageFields', 'manageSegments']);
|
||||
return await listTx(tx, listId);
|
||||
});
|
||||
}
|
||||
|
@ -193,6 +192,7 @@ async function listGroupedTx(tx, listId) {
|
|||
|
||||
async function listGrouped(context, listId) {
|
||||
return await knex.transaction(async tx => {
|
||||
// It may seem odd why there is not 'manageFields' here. But it's just a result of strictly apply the "need-to-know" principle. Simply, at this point this function is needed only in managing subscriptions.
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, ['manageSubscriptions']);
|
||||
return await listGroupedTx(tx, listId);
|
||||
});
|
||||
|
@ -474,6 +474,32 @@ async function removeAllByListIdTx(tx, context, listId) {
|
|||
}
|
||||
}
|
||||
|
||||
async function getRow(context, listId, subscription) {
|
||||
const customFields = [{
|
||||
name: 'Email Address',
|
||||
column: 'email',
|
||||
typeSubscriptionEmail: true,
|
||||
value: subscription ? subscription.email : '',
|
||||
order_subscribe: -1,
|
||||
order_manage: -1
|
||||
}];
|
||||
|
||||
const flds = await list(context, listId);
|
||||
|
||||
for (const fld of flds) {
|
||||
if (fld.column) {
|
||||
customFields.push({
|
||||
name: fld.name,
|
||||
column: fld.column,
|
||||
['type' + fld.type.replace(/(?:^|-)([a-z])/g, (m, c) => c.toUpperCase())]: true,
|
||||
value: subscription ? subscription[fld.column] : ''
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return customFields;
|
||||
}
|
||||
|
||||
// This is to handle circular dependency with segments.js
|
||||
Object.assign(module.exports, {
|
||||
Cardinality,
|
||||
|
@ -491,5 +517,6 @@ Object.assign(module.exports, {
|
|||
updateWithConsistencyCheck,
|
||||
remove,
|
||||
removeAllByListIdTx,
|
||||
serverValidate
|
||||
serverValidate,
|
||||
getRow
|
||||
});
|
|
@ -32,16 +32,40 @@ async function listDTAjax(context, params) {
|
|||
);
|
||||
}
|
||||
|
||||
async function _getByIdTx(tx, context, id) {
|
||||
shares.enforceEntityPermissionTx(tx, context, 'list', id, 'view');
|
||||
const entity = await tx('lists').where('id', id).first();
|
||||
entity.permissions = await shares.getPermissionsTx(tx, context, 'list', id);
|
||||
return entity;
|
||||
}
|
||||
|
||||
async function getById(context, id) {
|
||||
return await knex.transaction(async tx => {
|
||||
shares.enforceEntityPermissionTx(tx, context, 'list', id, 'view');
|
||||
const entity = await tx('lists').where('id', id).first();
|
||||
entity.permissions = await shares.getPermissionsTx(tx, context, 'list', id);
|
||||
return _getByIdTx(tx, context, id);
|
||||
});
|
||||
}
|
||||
|
||||
async function getByIdWithListFields(context, id) {
|
||||
return await knex.transaction(async tx => {
|
||||
const entity = _getByIdTx(tx, context, id);
|
||||
entity.listFields = await fields.listByOrderListTx(tx, id);
|
||||
return entity;
|
||||
});
|
||||
}
|
||||
|
||||
async function getByCid(context, cid) {
|
||||
return await knex.transaction(async tx => {
|
||||
const entity = await tx('lists').where('cid', cid).first();
|
||||
if (!entity) {
|
||||
shares.throwPermissionDenied();
|
||||
}
|
||||
|
||||
shares.enforceEntityPermissionTx(tx, context, 'list', entity.id, 'view');
|
||||
entity.permissions = await shares.getPermissionsTx(tx, context, 'list', entity.id);
|
||||
return entity;
|
||||
});
|
||||
}
|
||||
|
||||
async function create(context, entity) {
|
||||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createList');
|
||||
|
@ -116,6 +140,8 @@ module.exports = {
|
|||
hash,
|
||||
listDTAjax,
|
||||
getById,
|
||||
getByIdWithListFields,
|
||||
getByCid,
|
||||
create,
|
||||
updateWithConsistencyCheck,
|
||||
remove,
|
||||
|
|
|
@ -157,14 +157,14 @@ const campaignFieldsMapping = {
|
|||
};
|
||||
|
||||
async function getCampaignResults(context, campaign, select, extra) {
|
||||
const fieldList = await fields.list(context, campaign.list);
|
||||
const flds = await fields.list(context, campaign.list);
|
||||
|
||||
const fieldsMapping = Object.assign({}, campaignFieldsMapping);
|
||||
for (const field of fieldList) {
|
||||
/* Dropdowns and checkboxes are aggregated. As such, they have field.column == null
|
||||
TODO - For the time being, we ignore groupped fields. */
|
||||
if (field.column) {
|
||||
fieldsMapping[field.key.toLowerCase()] = 'subscribers.' + field.column;
|
||||
for (const fld of flds) {
|
||||
/* Dropdown and checkbox groups have field.column == null
|
||||
TODO - For the time being, we don't group options and we don't expand enums. We just provide it as it is in the DB. */
|
||||
if (fld.column) {
|
||||
fieldsMapping[fld.key.toLowerCase()] = 'subscribers.' + fld.column;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -191,11 +191,11 @@ async function hashByList(listId, entity) {
|
|||
});
|
||||
}
|
||||
|
||||
async function getById(context, listId, id) {
|
||||
async function _getBy(context, listId, key, value) {
|
||||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'viewSubscriptions');
|
||||
|
||||
const entity = await tx(getTableName(listId)).where('id', id).first();
|
||||
const entity = await tx(getTableName(listId)).where(key, value).first();
|
||||
|
||||
const groupedFieldsMap = await getGroupedFieldsMap(tx, listId);
|
||||
groupSubscription(groupedFieldsMap, entity);
|
||||
|
@ -204,6 +204,19 @@ async function getById(context, listId, id) {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
async function getById(context, listId, id) {
|
||||
return await _getBy(context, listId, 'id', id);
|
||||
}
|
||||
|
||||
async function getByEmail(context, listId, email) {
|
||||
return await _getBy(context, listId, 'email', email);
|
||||
}
|
||||
|
||||
async function getByCid(context, listId, cid) {
|
||||
return await _getBy(context, listId, 'cid', cid);
|
||||
}
|
||||
|
||||
async function listDTAjax(context, listId, segmentId, params) {
|
||||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'viewSubscriptions');
|
||||
|
@ -369,7 +382,7 @@ async function _validateAndPreprocess(tx, listId, groupedFieldsMap, entity, isCr
|
|||
}
|
||||
}
|
||||
|
||||
async function create(context, listId, entity) {
|
||||
async function create(context, listId, entity, meta = {}) {
|
||||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'manageSubscriptions');
|
||||
|
||||
|
@ -384,10 +397,9 @@ async function create(context, listId, entity) {
|
|||
|
||||
ungroupSubscription(groupedFieldsMap, filteredEntity);
|
||||
|
||||
// FIXME - process:
|
||||
// filteredEntity.opt_in_ip =
|
||||
// filteredEntity.opt_in_country =
|
||||
// filteredEntity.imported =
|
||||
filteredEntity.opt_in_ip = meta.ip;
|
||||
filteredEntity.opt_in_country = meta.country;
|
||||
filteredEntity.imported = meta.imported || false;
|
||||
|
||||
const ids = await tx(getTableName(listId)).insert(filteredEntity);
|
||||
const id = ids[0];
|
||||
|
@ -466,35 +478,58 @@ async function remove(context, listId, id) {
|
|||
});
|
||||
}
|
||||
|
||||
async function unsubscribe(context, listId, id) {
|
||||
await knex.transaction(async tx => {
|
||||
async function unsubscribeAndGet(context, listId, subscriptionId) {
|
||||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'manageSubscriptions');
|
||||
|
||||
const existing = await tx(getTableName(listId)).where('id', id).first();
|
||||
const existing = await tx(getTableName(listId)).where('id', subscriptionId).first();
|
||||
if (!existing) {
|
||||
throw new interoperableErrors.NotFoundError();
|
||||
}
|
||||
|
||||
if (existing.status === SubscriptionStatus.SUBSCRIBED) {
|
||||
await tx(getTableName(listId)).where('id', id).update({
|
||||
existing.status = SubscriptionStatus.UNSUBSCRIBED;
|
||||
|
||||
await tx(getTableName(listId)).where('id', subscriptionId).update({
|
||||
status: SubscriptionStatus.UNSUBSCRIBED
|
||||
});
|
||||
|
||||
await tx('lists').where('id', listId).decrement('subscribers', 1);
|
||||
}
|
||||
|
||||
return existing;
|
||||
});
|
||||
}
|
||||
|
||||
async function updateAddressAndGet(context, listId, subscriptionId, emailNew) {
|
||||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'manageSubscriptions');
|
||||
|
||||
const existing = await tx(getTableName(listId)).where('id', subscriptionId).first();
|
||||
if (!existing) {
|
||||
throw new interoperableErrors.NotFoundError();
|
||||
}
|
||||
|
||||
await tx(getTableName(listId)).where('id', subscriptionId).update({
|
||||
email: emailNew
|
||||
});
|
||||
|
||||
existing.email = emailNew;
|
||||
return existing;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
hashByList,
|
||||
getById,
|
||||
getByCid,
|
||||
getByEmail,
|
||||
list,
|
||||
listDTAjax,
|
||||
serverValidate,
|
||||
create,
|
||||
updateWithConsistencyCheck,
|
||||
remove,
|
||||
unsubscribe
|
||||
unsubscribeAndGet,
|
||||
updateAddressAndGet
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue