Before renaming imports to tasks
This commit is contained in:
parent
a494dc6482
commit
86efa11994
18 changed files with 81 additions and 58 deletions
12
TODO.md
12
TODO.md
|
@ -1,3 +1,6 @@
|
|||
### Front page
|
||||
- Some dashboard
|
||||
|
||||
### Deletion
|
||||
- Delete button in Lists
|
||||
- Check/delete dependencies
|
||||
|
@ -19,4 +22,13 @@
|
|||
- Add X-Mailer header option in settings to override or disable it - 44fe8882b876bdfd9990110496d16f819dc64ac3
|
||||
- Add custom unsubscribe option in a campaign - 68cb8384f7dfdbcaf2932293ec5a2f1ec0a1554e
|
||||
|
||||
### API
|
||||
- Add API extensions
|
||||
|
||||
### GDPR
|
||||
- Removal of personal data upon unsubscribe (settable per list)
|
||||
- Refuse editing subscriptions which have been anonymized
|
||||
- Add field to subscriptions which says till when the consent has been given
|
||||
- Provide a link (and merge tag) that will update the consent date to now
|
||||
- Add campaign trigger that triggers if the consent for specific subscription field is about to expire (i.e. it is greater than now - seconds)
|
||||
- Removal of personal data upon consent expiration (settable per list)
|
|
@ -13,4 +13,4 @@ The migration should happen almost automatically. There are however the followin
|
|||
4. Imports are not migrated. If you have any pending imports, complete them before migration to v2.
|
||||
|
||||
5. Zone MTA configuration endpoint (webhooks/zone-mta/sender-config) has changed. The send-configuration CID has to be
|
||||
part of the URL - e.g. webhooks/zone-mta/sender-config/default.
|
||||
part of the URL - e.g. webhooks/zone-mta/sender-config/system.
|
|
@ -141,7 +141,7 @@ function createApp(appType) {
|
|||
app.disable('x-powered-by');
|
||||
|
||||
app.use(compression());
|
||||
app.use(favicon(path.join(__dirname, 'client', 'public', 'favicon.ico')));
|
||||
app.use(favicon(path.join(__dirname, 'client', 'static', 'favicon.ico')));
|
||||
|
||||
app.use(logger(config.www.log, {
|
||||
stream: {
|
||||
|
@ -182,7 +182,7 @@ function createApp(appType) {
|
|||
app.use(passport.tryAuthByRestrictedAccessToken);
|
||||
}
|
||||
|
||||
useWith404Fallback('/public', express.static(path.join(__dirname, 'client', 'public')));
|
||||
useWith404Fallback('/static', express.static(path.join(__dirname, 'client', 'static')));
|
||||
useWith404Fallback('/mailtrain', express.static(path.join(__dirname, 'client', 'dist')));
|
||||
useWith404Fallback('/locales', express.static(path.join(__dirname, 'client', 'locales')));
|
||||
|
||||
|
|
|
@ -559,10 +559,11 @@ export default class CUD extends Component {
|
|||
|
||||
const sendConfigurationsColumns = [
|
||||
{ data: 1, title: t('Name') },
|
||||
{ data: 2, title: t('Description') },
|
||||
{ data: 3, title: t('Type'), render: data => this.mailerTypes[data].typeName },
|
||||
{ data: 4, title: t('Created'), render: data => moment(data).fromNow() },
|
||||
{ data: 5, title: t('Namespace') }
|
||||
{ data: 2, title: t('ID'), render: data => <code>{data}</code> },
|
||||
{ data: 3, title: t('Description') },
|
||||
{ data: 4, title: t('Type'), render: data => this.mailerTypes[data].typeName },
|
||||
{ data: 5, title: t('Created'), render: data => moment(data).fromNow() },
|
||||
{ data: 6, title: t('Namespace') }
|
||||
];
|
||||
|
||||
let sendSettings;
|
||||
|
|
|
@ -84,7 +84,7 @@ export default class CUD extends Component {
|
|||
componentDidMount() {
|
||||
if (this.props.entity) {
|
||||
this.getFormValuesFromEntity(this.props.entity, data => {
|
||||
data.daysAfter = (Math.round(data.seconds_after / (3600 * 24))).toString();
|
||||
data.daysAfter = (Math.round(data.seconds / (3600 * 24))).toString();
|
||||
|
||||
if (data.entity === Entity.SUBSCRIPTION) {
|
||||
data.subscriptionEvent = data.event;
|
||||
|
@ -157,7 +157,7 @@ export default class CUD extends Component {
|
|||
this.setFormStatusMessage('info', t('Saving ...'));
|
||||
|
||||
const submitSuccessful = await this.validateAndSendFormValuesToURL(sendMethod, url, data => {
|
||||
data.seconds_after = Number.parseInt(data.daysAfter) * 3600 * 24;
|
||||
data.seconds = Number.parseInt(data.daysAfter) * 3600 * 24;
|
||||
|
||||
if (data.entity === Entity.SUBSCRIPTION) {
|
||||
data.event = data.subscriptionEvent;
|
||||
|
|
|
@ -156,9 +156,10 @@ export default class CUD extends Component {
|
|||
|
||||
const sendConfigurationsColumns = [
|
||||
{ data: 1, title: t('Name') },
|
||||
{ data: 2, title: t('Description') },
|
||||
{ data: 3, title: t('Type'), render: data => this.mailerTypes[data].typeName },
|
||||
{ data: 5, title: t('Namespace') }
|
||||
{ data: 2, title: t('ID'), render: data => <code>{data}</code> },
|
||||
{ data: 3, title: t('Description') },
|
||||
{ data: 4, title: t('Type'), render: data => this.mailerTypes[data].typeName },
|
||||
{ data: 6, title: t('Namespace') }
|
||||
];
|
||||
|
||||
return (
|
||||
|
|
|
@ -96,7 +96,7 @@ export default class List extends Component {
|
|||
|
||||
if (perms.includes('viewImports')) {
|
||||
actions.push({
|
||||
label: <Icon icon="sort" title={t('Imports')}/>,
|
||||
label: <Icon icon="sort" title={t('Imports & Tasks')}/>,
|
||||
link: `/lists/${data[0]}/imports`
|
||||
});
|
||||
}
|
||||
|
|
|
@ -386,7 +386,7 @@ export default class CUD extends Component {
|
|||
|
||||
let saveButtonLabel;
|
||||
if (!isEdit) {
|
||||
saveButtonLabel = t('Save and edit mapping');
|
||||
saveButtonLabel = t('Save and edit settings');
|
||||
} else {
|
||||
saveButtonLabel = t('Save');
|
||||
}
|
||||
|
@ -404,7 +404,7 @@ export default class CUD extends Component {
|
|||
deletedMsg={t('Field deleted')}/>
|
||||
}
|
||||
|
||||
<Title>{isEdit ? t('Edit Import') : t('Create Import')}</Title>
|
||||
<Title>{isEdit ? t('Edit Import/Task') : t('Create Import/Task')}</Title>
|
||||
|
||||
<Form stateOwner={this} onSubmitAsync={::this.submitHandler}>
|
||||
<InputField id="name" label={t('Name')}/>
|
||||
|
|
|
@ -81,11 +81,11 @@ export default class List extends Component {
|
|||
<div>
|
||||
{mailtrainConfig.globalPermissions.includes('setupAutomation') && this.props.list.permissions.includes('manageImports') &&
|
||||
<Toolbar>
|
||||
<NavButton linkTo={`/lists/${this.props.list.id}/imports/create`} className="btn-primary" icon="plus" label={t('Create Import')}/>
|
||||
<NavButton linkTo={`/lists/${this.props.list.id}/imports/create`} className="btn-primary" icon="plus" label={t('Create Import/Task')}/>
|
||||
</Toolbar>
|
||||
}
|
||||
|
||||
<Title>{t('Imports')}</Title>
|
||||
<Title>{t('Imports & Tasks')}</Title>
|
||||
|
||||
<Table withHeader dataUrl={`rest/imports-table/${this.props.list.id}`} columns={columns} />
|
||||
</div>
|
||||
|
|
|
@ -132,7 +132,7 @@ function getMenus(t) {
|
|||
}
|
||||
},
|
||||
imports: {
|
||||
title: t('Imports'),
|
||||
title: t('Imports & Tasks'),
|
||||
link: params => `/lists/${params.listId}/imports/`,
|
||||
visible: resolved => resolved.list.permissions.includes('viewImports'),
|
||||
panelRender: props => <ImportsList list={props.resolved.list} />,
|
||||
|
|
11
index.js
11
index.js
|
@ -18,6 +18,7 @@ const executor = require('./lib/executor');
|
|||
const privilegeHelpers = require('./lib/privilege-helpers');
|
||||
const knex = require('./lib/knex');
|
||||
const shares = require('./models/shares');
|
||||
const { AppType } = require('./shared/app');
|
||||
|
||||
const trustedPort = config.www.trustedPort;
|
||||
const sandboxPort = config.www.sandboxPort;
|
||||
|
@ -31,7 +32,7 @@ if (config.title) {
|
|||
log.level = config.log.level;
|
||||
|
||||
|
||||
function startHTTPServer(appType, port, callback) {
|
||||
function startHTTPServer(appType, appName, port, callback) {
|
||||
const app = appBuilder.createApp(appType);
|
||||
app.set('port', port);
|
||||
|
||||
|
@ -60,7 +61,7 @@ function startHTTPServer(appType, port, callback) {
|
|||
server.on('listening', () => {
|
||||
const addr = server.address();
|
||||
const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
|
||||
log.info('Express', 'WWW server listening on %s', bind);
|
||||
log.info('Express', 'WWW server [%s] listening on %s', appName, bind);
|
||||
});
|
||||
|
||||
server.listen({port, host}, callback);
|
||||
|
@ -85,9 +86,9 @@ dbcheck(err => { // Check if database needs upgrading before starting the server
|
|||
executor.spawn(() => {
|
||||
testServer(() => {
|
||||
verpServer(() => {
|
||||
startHTTPServer(AppType.TRUSTED, trustedPort, () => {
|
||||
startHTTPServer(AppType.SANDBOXED, sandboxPort, () => {
|
||||
startHTTPServer(AppType.PUBLIC, publicPort, () => {
|
||||
startHTTPServer(AppType.TRUSTED, 'trusted', trustedPort, () => {
|
||||
startHTTPServer(AppType.SANDBOXED, 'sandbox', sandboxPort, () => {
|
||||
startHTTPServer(AppType.PUBLIC, 'public', publicPort, () => {
|
||||
privilegeHelpers.dropRootPrivileges();
|
||||
|
||||
tzupdate.start();
|
||||
|
|
|
@ -172,6 +172,20 @@ async function listTestUsersDTAjax(context, campaignId, params) {
|
|||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
async function rawGetByTx(tx, key, id) {
|
||||
const entity = await tx('campaigns').where('campaigns.' + key, id)
|
||||
.leftJoin('campaign_lists', 'campaigns.id', 'campaign_lists.campaign')
|
||||
|
@ -709,8 +723,6 @@ module.exports.listOthersWhoseListsAreIncludedDTAjax = listOthersWhoseListsAreIn
|
|||
module.exports.listTestUsersDTAjax = listTestUsersDTAjax;
|
||||
module.exports.getByIdTx = getByIdTx;
|
||||
module.exports.getById = getById;
|
||||
module.exports.getByCidTx = getByCidTx;
|
||||
module.exports.getByCid = getByCid;
|
||||
module.exports.create = create;
|
||||
module.exports.createRssTx = createRssTx;
|
||||
module.exports.updateWithConsistencyCheck = updateWithConsistencyCheck;
|
||||
|
@ -731,4 +743,5 @@ module.exports.start = start;
|
|||
module.exports.stop = stop;
|
||||
module.exports.reset = reset;
|
||||
|
||||
module.exports.rawGetBy = rawGetBy;
|
||||
module.exports.rawGetByTx = rawGetByTx;
|
||||
module.exports.getTrackingSettingsByCidTx = getTrackingSettingsByCidTx;
|
|
@ -4,7 +4,6 @@ const log = require('npmlog');
|
|||
const knex = require('../lib/knex');
|
||||
const dtHelpers = require('../lib/dt-helpers');
|
||||
const shares = require('./shares');
|
||||
const tools = require('../lib/tools');
|
||||
const campaigns = require('./campaigns');
|
||||
const lists = require('./lists');
|
||||
const subscriptions = require('./subscriptions');
|
||||
|
@ -28,7 +27,7 @@ async function resolve(linkCid) {
|
|||
async function countLink(remoteIp, userAgent, campaignCid, listCid, subscriptionCid, linkId) {
|
||||
await knex.transaction(async tx => {
|
||||
const list = await lists.getByCidTx(tx, contextHelpers.getAdminContext(), listCid);
|
||||
const campaign = await campaigns.getByCidTx(tx, contextHelpers.getAdminContext(), campaignCid);
|
||||
const campaign = await campaigns.getTrackingSettingsByCidTx(tx, campaignCid);
|
||||
const subscription = await subscriptions.getByCidTx(tx, contextHelpers.getAdminContext(), subscriptionCid);
|
||||
|
||||
const country = geoip.lookupCountry(remoteIp) || null;
|
||||
|
|
|
@ -9,7 +9,7 @@ const shares = require('./shares');
|
|||
const {EntityVals, EventVals, Entity} = require('../shared/triggers');
|
||||
const campaigns = require('./campaigns');
|
||||
|
||||
const allowedKeys = new Set(['name', 'description', 'entity', 'event', 'seconds_after', 'enabled', 'source_campaign']);
|
||||
const allowedKeys = new Set(['name', 'description', 'entity', 'event', 'seconds', 'enabled', 'source_campaign']);
|
||||
|
||||
function hash(entity) {
|
||||
return hasher.hash(filterObject(entity, allowedKeys));
|
||||
|
@ -36,7 +36,7 @@ async function listByCampaignDTAjax(context, campaignId, params) {
|
|||
.from('triggers')
|
||||
.innerJoin('campaigns', 'campaigns.id', 'triggers.campaign')
|
||||
.where('triggers.campaign', campaignId),
|
||||
[ 'triggers.id', 'triggers.name', 'triggers.description', 'triggers.entity', 'triggers.event', 'triggers.seconds_after', 'triggers.enabled' ]
|
||||
[ 'triggers.id', 'triggers.name', 'triggers.description', 'triggers.entity', 'triggers.event', 'triggers.seconds', 'triggers.enabled' ]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -51,13 +51,13 @@ async function listByListDTAjax(context, listId, params) {
|
|||
.innerJoin('campaigns', 'campaigns.id', 'triggers.campaign')
|
||||
.innerJoin('campaign_lists', 'campaign_lists.campaign', 'campaigns.id')
|
||||
.where('campaign_lists.list', listId),
|
||||
[ 'triggers.id', 'triggers.name', 'triggers.description', 'campaigns.name', 'triggers.entity', 'triggers.event', 'triggers.seconds_after', 'triggers.enabled', 'triggers.campaign' ]
|
||||
[ 'triggers.id', 'triggers.name', 'triggers.description', 'campaigns.name', 'triggers.entity', 'triggers.event', 'triggers.seconds', 'triggers.enabled', 'triggers.campaign' ]
|
||||
);
|
||||
}
|
||||
|
||||
async function _validateAndPreprocess(tx, context, campaignId, entity) {
|
||||
enforce(Number.isInteger(entity.seconds_after));
|
||||
enforce(entity.seconds_after >= 0, 'Seconds after must not be negative');
|
||||
enforce(Number.isInteger(entity.seconds));
|
||||
enforce(entity.seconds >= 0, 'Seconds must not be negative');
|
||||
enforce(entity.entity in EntityVals, 'Invalid entity');
|
||||
enforce(entity.event in EventVals[entity.entity], 'Invalid event');
|
||||
|
||||
|
|
|
@ -154,25 +154,23 @@ router.postAsync('/mailgun', uploads.any(), async (req, res) => {
|
|||
const evt = req.body;
|
||||
|
||||
const message = await campaigns.getMessageByCid([].concat(evt && evt.campaign_id || []).shift());
|
||||
if (!message) {
|
||||
continue;
|
||||
}
|
||||
if (message) {
|
||||
switch (evt.event) {
|
||||
case 'bounced':
|
||||
await campaigns.changeStatusByMessage(contextHelpers.getAdminContext(), message, SubscriptionStatus.BOUNCED, true);
|
||||
log.verbose('Mailgun', 'Marked message %s as bounced', evt.campaign_id);
|
||||
break;
|
||||
|
||||
switch (evt.event) {
|
||||
case 'bounced':
|
||||
await campaigns.changeStatusByMessage(contextHelpers.getAdminContext(), message, SubscriptionStatus.BOUNCED, true);
|
||||
log.verbose('Mailgun', 'Marked message %s as bounced', evt.campaign_id);
|
||||
break;
|
||||
case 'complained':
|
||||
await campaigns.changeStatusByMessage(contextHelpers.getAdminContext(), message, SubscriptionStatus.COMPLAINED, true);
|
||||
log.verbose('Mailgun', 'Marked message %s as complaint', evt.campaign_id);
|
||||
break;
|
||||
|
||||
case 'complained':
|
||||
await campaigns.changeStatusByMessage(contextHelpers.getAdminContext(), message, SubscriptionStatus.COMPLAINED, true);
|
||||
log.verbose('Mailgun', 'Marked message %s as complaint', evt.campaign_id);
|
||||
break;
|
||||
|
||||
case 'unsubscribed':
|
||||
await campaigns.changeStatusByMessage(contextHelpers.getAdminContext(), message, SubscriptionStatus.UNSUBSCRIBED, true);
|
||||
log.verbose('Mailgun', 'Marked message %s as unsubscribed', evt.campaign_id);
|
||||
break;
|
||||
case 'unsubscribed':
|
||||
await campaigns.changeStatusByMessage(contextHelpers.getAdminContext(), message, SubscriptionStatus.UNSUBSCRIBED, true);
|
||||
log.verbose('Mailgun', 'Marked message %s as unsubscribed', evt.campaign_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return res.json({
|
||||
|
@ -188,12 +186,11 @@ router.postAsync('/zone-mta', async (req, res) => {
|
|||
|
||||
if (req.body.id) {
|
||||
const message = await campaigns.getMessageByCid(req.body.id);
|
||||
if (!message) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await campaigns.changeStatusByMessage(contextHelpers.getAdminContext(), message, SubscriptionStatus.BOUNCED, true);
|
||||
log.verbose('ZoneMTA', 'Marked message %s as bounced', req.body.id);
|
||||
if (message) {
|
||||
await campaigns.changeStatusByMessage(contextHelpers.getAdminContext(), message, SubscriptionStatus.BOUNCED, true);
|
||||
log.verbose('ZoneMTA', 'Marked message %s as bounced', req.body.id);
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
|
|
|
@ -131,7 +131,7 @@ async function start() {
|
|||
}
|
||||
|
||||
|
||||
sqlQry = sqlQry.where(column, '<=', currentTs - trigger.seconds_after);
|
||||
sqlQry = sqlQry.where(column, '<=', currentTs - trigger.seconds);
|
||||
|
||||
if (trigger.last_check !== null) {
|
||||
sqlQry = sqlQry.where(column, '>', trigger.last_check);
|
||||
|
|
|
@ -1065,7 +1065,6 @@ async function migrateTriggers(knex) {
|
|||
table.renameColumn('rule', 'entity');
|
||||
table.renameColumn('column', 'event');
|
||||
table.renameColumn('dest_campaign', 'campaign');
|
||||
table.renameColumn('seconds', 'seconds_after');
|
||||
});
|
||||
|
||||
const triggers = await knex('triggers');
|
||||
|
|
|
@ -11,7 +11,7 @@ function getSystemSendConfigurationId() {
|
|||
}
|
||||
|
||||
function getSystemSendConfigurationCid() {
|
||||
return 'default';
|
||||
return 'system';
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
Loading…
Reference in a new issue