Subscription/unsubscription seems to work.
This commit is contained in:
parent
d8ee364a4b
commit
e9165838dc
22 changed files with 14939 additions and 196 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,7 +3,6 @@
|
|||
|
||||
node_modules
|
||||
npm-debug.log
|
||||
package-lock.json
|
||||
.DS_Store
|
||||
config/development.*
|
||||
config/production.*
|
||||
|
|
6159
client/package-lock.json
generated
Normal file
6159
client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -24,7 +24,6 @@
|
|||
"immutable": "^3.8.1",
|
||||
"moment": "^2.18.1",
|
||||
"moment-timezone": "^0.5.13",
|
||||
"owasp-password-strength-test": "github:bures/owasp-password-strength-test",
|
||||
"prop-types": "^15.5.10",
|
||||
"querystringify": "^1.0.0",
|
||||
"react": "^15.6.1",
|
||||
|
|
|
@ -56,7 +56,7 @@ export default class API extends Component {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<Title>{t('Sign in')}</Title>
|
||||
<Title>{t('API')}</Title>
|
||||
|
||||
|
||||
<div className="panel panel-default">
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
Dropdown, Form,
|
||||
withForm
|
||||
} from '../../lib/form';
|
||||
import {Icon} from "../../lib/bootstrap-components";
|
||||
import {Icon, Button} from "../../lib/bootstrap-components";
|
||||
import axios from '../../lib/axios';
|
||||
import {getFieldTypes, getSubscriptionStatusLabels} from './helpers';
|
||||
|
||||
|
@ -154,6 +154,7 @@ export default class List extends Component {
|
|||
return (
|
||||
<div>
|
||||
<Toolbar>
|
||||
<a href={`/subscription/${this.props.list.cid}`} className="btn-default"><Button label={t('Subscription Form')} className="btn-default"/></a>
|
||||
<NavButton linkTo={`/lists/${this.props.list.id}/subscriptions/create`} className="btn-primary" icon="plus" label={t('Add Subscriber')}/>
|
||||
</Toolbar>
|
||||
|
||||
|
|
|
@ -25,7 +25,10 @@ export function getFieldTypes(t) {
|
|||
|
||||
const stringFieldType = long => ({
|
||||
form: groupedField => long ? <TextArea key={getFieldKey(groupedField)} id={getFieldKey(groupedField)} label={groupedField.name}/> : <InputField key={getFieldKey(groupedField)} id={getFieldKey(groupedField)} label={groupedField.name}/>,
|
||||
assignFormData: (groupedField, data) => {},
|
||||
assignFormData: (groupedField, data) => {
|
||||
const value = data[getFieldKey(groupedField)];
|
||||
data[getFieldKey(groupedField)] = value || '';
|
||||
},
|
||||
initFormData: (groupedField, data) => {
|
||||
data[getFieldKey(groupedField)] = '';
|
||||
},
|
||||
|
@ -109,7 +112,10 @@ export function getFieldTypes(t) {
|
|||
|
||||
const jsonFieldType = {
|
||||
form: groupedField => <ACEEditor key={getFieldKey(groupedField)} id={getFieldKey(groupedField)} label={groupedField.name} mode="json" height="300px"/>,
|
||||
assignFormData: (groupedField, data) => {},
|
||||
assignFormData: (groupedField, data) => {
|
||||
const value = data[getFieldKey(groupedField)];
|
||||
data[getFieldKey(groupedField)] = value || '';
|
||||
},
|
||||
initFormData: (groupedField, data) => {
|
||||
data[getFieldKey(groupedField)] = '';
|
||||
},
|
||||
|
|
|
@ -10,18 +10,18 @@ function getRequestContext(req) {
|
|||
return context;
|
||||
}
|
||||
|
||||
function getAdminContext() {
|
||||
const context = {
|
||||
user: {
|
||||
admin: true,
|
||||
id: 0,
|
||||
username: '',
|
||||
name: '',
|
||||
email: ''
|
||||
}
|
||||
};
|
||||
const adminContext = {
|
||||
user: {
|
||||
admin: true,
|
||||
id: 0,
|
||||
username: '',
|
||||
name: '',
|
||||
email: ''
|
||||
}
|
||||
};
|
||||
|
||||
return context;
|
||||
function getAdminContext() {
|
||||
return adminContext;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -166,13 +166,13 @@ async function ajaxListWithPermissionsTx(tx, context, fetchSpecs, params, queryF
|
|||
|
||||
async function ajaxList(params, queryFun, columns, options) {
|
||||
return await knex.transaction(async tx => {
|
||||
return ajaxListTx(tx, params, queryFun, columns, options)
|
||||
return await ajaxListTx(tx, params, queryFun, columns, options)
|
||||
});
|
||||
}
|
||||
|
||||
async function ajaxListWithPermissions(context, fetchSpecs, params, queryFun, columns, options) {
|
||||
return await knex.transaction(async tx => {
|
||||
return ajaxListWithPermissionsTx(tx, context, fetchSpecs, params, queryFun, columns, options)
|
||||
return await ajaxListWithPermissionsTx(tx, context, fetchSpecs, params, queryFun, columns, options)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ const knex = require('knex')({
|
|||
migrations: {
|
||||
directory: __dirname + '/../setup/knex/migrations'
|
||||
}
|
||||
, debug: true
|
||||
//, debug: true
|
||||
});
|
||||
|
||||
module.exports = knex;
|
||||
|
|
|
@ -117,7 +117,7 @@ async function _sendMail(list, email, template, subject, relativeUrls, mailOpts,
|
|||
|
||||
const encryptionKeys = [];
|
||||
for (const fld of flds) {
|
||||
if (fld.type === 'gpg' && field.value) {
|
||||
if (fld.type === 'gpg' && fld.value) {
|
||||
encryptionKeys.push(subscription[getFieldKey(fld)].value.trim());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,10 +6,13 @@ const isemail = require('isemail');
|
|||
|
||||
const bluebird = require('bluebird');
|
||||
const mergeTemplateIntoLayout = bluebird.promisify(require('./tools').mergeTemplateIntoLayout);
|
||||
const queryParams = require('./tools').queryParams;
|
||||
|
||||
module.exports = {
|
||||
validateEmail,
|
||||
mergeTemplateIntoLayout
|
||||
validateEmailGetMessage,
|
||||
mergeTemplateIntoLayout,
|
||||
queryParams
|
||||
};
|
||||
|
||||
async function validateEmail(address, checkBlocked) {
|
||||
|
@ -29,3 +32,21 @@ async function validateEmail(address, checkBlocked) {
|
|||
return result;
|
||||
}
|
||||
|
||||
function validateEmailGetMessage(result, address) {
|
||||
if (result !== 0) {
|
||||
let message = util.format(_('Invalid email address "%s".'), address);
|
||||
switch (result) {
|
||||
case 5:
|
||||
message += ' ' + _('MX record not found for domain');
|
||||
break;
|
||||
case 6:
|
||||
message += ' ' + _('Address domain not found');
|
||||
break;
|
||||
case 12:
|
||||
message += ' ' + _('Address domain name is required');
|
||||
break;
|
||||
}
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ async function _getById(tx, id) {
|
|||
|
||||
async function getById(context, id) {
|
||||
return await knex.transaction(async tx => {
|
||||
shares.enforceEntityPermissionTx(tx, context, 'customForm', id, 'view');
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'customForm', id, 'view');
|
||||
const entity = await _getById(tx, id);
|
||||
entity.permissions = await shares.getPermissionsTx(tx, context, 'customForm', id);
|
||||
return entity;
|
||||
|
@ -171,9 +171,9 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
|
||||
async function remove(context, id) {
|
||||
await knex.transaction(async tx => {
|
||||
shares.enforceEntityPermissionTx(tx, context, 'customForm', id, 'delete');
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'customForm', id, 'delete');
|
||||
|
||||
lists.removeFormFromAllTx(tx, context, id);
|
||||
await lists.removeFormFromAllTx(tx, context, id);
|
||||
|
||||
await tx('custom_forms_data').where('form', id).del();
|
||||
await tx('custom_forms').where('id', id).del();
|
||||
|
|
|
@ -33,21 +33,22 @@ async function listDTAjax(context, params) {
|
|||
}
|
||||
|
||||
async function _getByIdTx(tx, context, id) {
|
||||
shares.enforceEntityPermissionTx(tx, context, 'list', id, 'view');
|
||||
await 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 => {
|
||||
return _getByIdTx(tx, context, id);
|
||||
// note that permissions are not obtained here as this methods is used only with synthetic admin context
|
||||
return await _getByIdTx(tx, context, id);
|
||||
});
|
||||
}
|
||||
|
||||
async function getByIdWithListFields(context, id) {
|
||||
return await knex.transaction(async tx => {
|
||||
const entity = _getByIdTx(tx, context, id);
|
||||
const entity = await _getByIdTx(tx, context, id);
|
||||
entity.permissions = await shares.getPermissionsTx(tx, context, 'list', id);
|
||||
entity.listFields = await fields.listByOrderListTx(tx, id);
|
||||
return entity;
|
||||
});
|
||||
|
@ -60,8 +61,7 @@ async function getByCid(context, cid) {
|
|||
shares.throwPermissionDenied();
|
||||
}
|
||||
|
||||
shares.enforceEntityPermissionTx(tx, context, 'list', entity.id, 'view');
|
||||
entity.permissions = await shares.getPermissionsTx(tx, context, 'list', entity.id);
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', entity.id, 'view');
|
||||
return entity;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -31,11 +31,16 @@ function getOptionsMap(groupedField) {
|
|||
return result;
|
||||
}
|
||||
|
||||
fieldTypes.text = fieldTypes.website = fieldTypes.longtext = fieldTypes.gpg = fieldTypes.number = {
|
||||
fieldTypes.text = fieldTypes.website = fieldTypes.longtext = fieldTypes.gpg = {
|
||||
afterJSON: (groupedField, entity) => {},
|
||||
listRender: (groupedField, value) => value
|
||||
};
|
||||
|
||||
fieldTypes.number = {
|
||||
afterJSON: (groupedField, entity) => {},
|
||||
listRender: (groupedField, value) => Number(value)
|
||||
};
|
||||
|
||||
fieldTypes.json = {
|
||||
afterJSON: (groupedField, entity) => {},
|
||||
listRender: (groupedField, value) => value
|
||||
|
@ -59,14 +64,20 @@ fieldTypes['radio-enum'] = fieldTypes['dropdown-enum'] = fieldTypes['radio-group
|
|||
|
||||
fieldTypes.date = {
|
||||
afterJSON: (groupedField, entity) => {
|
||||
entity[getFieldKey(groupedField)] = moment(entity[getFieldKey(groupedField)]).toDate();
|
||||
const key = getFieldKey(groupedField);
|
||||
if (key in entity) {
|
||||
entity[key] = entity[key] ? moment(entity[key]).toDate() : null;
|
||||
}
|
||||
},
|
||||
listRender: (groupedField, value) => formatDate(groupedField.settings.dateFormat, value)
|
||||
};
|
||||
|
||||
fieldTypes.birthday = {
|
||||
afterJSON: (groupedField, entity) => {
|
||||
entity[getFieldKey(groupedField)] = moment(entity[getFieldKey(groupedField)]).toDate();
|
||||
const key = getFieldKey(groupedField);
|
||||
if (key in entity) {
|
||||
entity[key] = entity[key] ? moment(entity[key]).toDate() : null;
|
||||
}
|
||||
},
|
||||
listRender: (groupedField, value) => formatBirthday(groupedField.settings.dateFormat, value)
|
||||
};
|
||||
|
@ -153,7 +164,7 @@ function ungroupSubscription(groupedFieldsMap, entity) {
|
|||
}
|
||||
|
||||
} else {
|
||||
const values = entity[fldKey];
|
||||
const values = entity[fldKey] || []; // The default (empty array) is here because create may be called with an entity that has some fields not filled in
|
||||
for (const optionKey in fld.groupedOptions) {
|
||||
const option = fld.groupedOptions[optionKey];
|
||||
entity[option.column] = values.includes(option.column);
|
||||
|
@ -210,11 +221,6 @@ async function _getStatusBy(context, listId, key, value) {
|
|||
});
|
||||
}
|
||||
|
||||
async function getStatusByCid(context, listId, cid) {
|
||||
return await _getStatusBy(context, listId, 'cid', cid);
|
||||
}
|
||||
|
||||
|
||||
async function _getBy(context, listId, key, value, grouped) {
|
||||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'viewSubscriptions');
|
||||
|
@ -387,8 +393,9 @@ async function serverValidate(context, listId, data) {
|
|||
});
|
||||
}
|
||||
|
||||
async function _validateAndPreprocess(tx, listId, groupedFieldsMap, entity, isCreate) {
|
||||
async function _validateAndPreprocess(tx, listId, groupedFieldsMap, entity, meta, isCreate) {
|
||||
enforce(entity.email, 'Email must be set');
|
||||
enforce(entity.status > 0 && entity.status < SubscriptionStatus.MAX, 'Subscription status is invalid');
|
||||
|
||||
const existingWithKeyQuery = tx(getSubscriptionTableName(listId)).where('email', entity.email);
|
||||
|
||||
|
@ -397,7 +404,12 @@ async function _validateAndPreprocess(tx, listId, groupedFieldsMap, entity, isCr
|
|||
}
|
||||
const existingWithKey = await existingWithKeyQuery.first();
|
||||
if (existingWithKey) {
|
||||
throw new interoperableErrors.DuplicitEmailError();
|
||||
if (meta && meta.replaceOfUnsubscribedAllowed && existingWithKey.status === SubscriptionStatus.UNSUBSCRIBED) {
|
||||
meta.updateNeeded = true;
|
||||
meta.existing = existingWithKey;
|
||||
} else {
|
||||
throw new interoperableErrors.DuplicitEmailError();
|
||||
}
|
||||
}
|
||||
|
||||
enforce(entity.status >= 0 && entity.status < SubscriptionStatus.MAX, 'Invalid status');
|
||||
|
@ -409,33 +421,68 @@ async function _validateAndPreprocess(tx, listId, groupedFieldsMap, entity, isCr
|
|||
}
|
||||
}
|
||||
|
||||
async function create(context, listId, entity, meta = {}) {
|
||||
async function _update(tx, listId, existing, filteredEntity) {
|
||||
if (existing.status !== filteredEntity.status) {
|
||||
filteredEntity.status_change = new Date();
|
||||
}
|
||||
|
||||
await tx(getSubscriptionTableName(listId)).where('id', existing.id).update(filteredEntity);
|
||||
|
||||
|
||||
let countIncrement = 0;
|
||||
if (existing.status === SubscriptionStatus.SUBSCRIBED && filteredEntity.status !== SubscriptionStatus.SUBSCRIBED) {
|
||||
countIncrement = -1;
|
||||
} else if (existing.status !== SubscriptionStatus.SUBSCRIBED && filteredEntity.status === SubscriptionStatus.SUBSCRIBED) {
|
||||
countIncrement = 1;
|
||||
}
|
||||
|
||||
if (countIncrement) {
|
||||
await tx('lists').where('id', listId).increment('subscribers', countIncrement);
|
||||
}
|
||||
}
|
||||
|
||||
async function _create(tx, listId, filteredEntity) {
|
||||
const ids = await tx(getSubscriptionTableName(listId)).insert(filteredEntity);
|
||||
const id = ids[0];
|
||||
|
||||
if (filteredEntity.status === SubscriptionStatus.SUBSCRIBED) {
|
||||
await tx('lists').where('id', listId).increment('subscribers', 1);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
async function create(context, listId, entity, meta /* meta is provided when called from /confirm/subscribe/:cid */) {
|
||||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'manageSubscriptions');
|
||||
|
||||
const groupedFieldsMap = await getGroupedFieldsMap(tx, listId);
|
||||
const allowedKeys = getAllowedKeys(groupedFieldsMap);
|
||||
|
||||
await _validateAndPreprocess(tx, listId, groupedFieldsMap, entity, true);
|
||||
await _validateAndPreprocess(tx, listId, groupedFieldsMap, entity, meta, true);
|
||||
|
||||
const filteredEntity = filterObject(entity, allowedKeys);
|
||||
filteredEntity.cid = shortid.generate();
|
||||
filteredEntity.status_change = new Date();
|
||||
|
||||
ungroupSubscription(groupedFieldsMap, filteredEntity);
|
||||
|
||||
filteredEntity.opt_in_ip = meta.ip;
|
||||
filteredEntity.opt_in_country = meta.country;
|
||||
filteredEntity.imported = meta.imported || false;
|
||||
filteredEntity.opt_in_ip = meta && meta.ip;
|
||||
filteredEntity.opt_in_country = meta && meta.country;
|
||||
filteredEntity.imported = meta && !!meta.imported;
|
||||
|
||||
const ids = await tx(getSubscriptionTableName(listId)).insert(filteredEntity);
|
||||
const id = ids[0];
|
||||
if (meta && meta.updateNeeded) {
|
||||
await _update(tx, listId, meta.existing, filteredEntity);
|
||||
meta.cid = meta.existing.cid; // The cid is needed by /confirm/subscribe/:cid
|
||||
return meta.existing.id;
|
||||
} else {
|
||||
filteredEntity.cid = shortid.generate();
|
||||
|
||||
if (entity.status === SubscriptionStatus.SUBSCRIBED) {
|
||||
await tx('lists').where('id', listId).increment('subscribers', 1);
|
||||
if (meta) {
|
||||
meta.cid = filteredEntity.cid; // The cid is needed by /confirm/subscribe/:cid
|
||||
}
|
||||
|
||||
return await _create(tx, listId, filteredEntity);
|
||||
}
|
||||
|
||||
return id;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -458,29 +505,13 @@ async function updateWithConsistencyCheck(context, listId, entity) {
|
|||
throw new interoperableErrors.ChangedError();
|
||||
}
|
||||
|
||||
await _validateAndPreprocess(tx, listId, groupedFieldsMap, entity, false);
|
||||
await _validateAndPreprocess(tx, listId, groupedFieldsMap, entity, null, false);
|
||||
|
||||
const filteredEntity = filterObject(entity, allowedKeys);
|
||||
|
||||
ungroupSubscription(groupedFieldsMap, filteredEntity);
|
||||
|
||||
if (existing.status !== entity.status) {
|
||||
filteredEntity.status_change = new Date();
|
||||
}
|
||||
|
||||
await tx(getSubscriptionTableName(listId)).where('id', entity.id).update(filteredEntity);
|
||||
|
||||
|
||||
let countIncrement = 0;
|
||||
if (existing.status === SubscriptionStatus.SUBSCRIBED && entity.status !== SubscriptionStatus.SUBSCRIBED) {
|
||||
countIncrement = -1;
|
||||
} else if (existing.status !== SubscriptionStatus.SUBSCRIBED && entity.status === SubscriptionStatus.SUBSCRIBED) {
|
||||
countIncrement = 1;
|
||||
}
|
||||
|
||||
if (countIncrement) {
|
||||
await tx('lists').where('id', listId).increment('subscribers', countIncrement);
|
||||
}
|
||||
await _update(tx, listId, existing, filteredEntity);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -505,40 +536,52 @@ async function remove(context, listId, id) {
|
|||
});
|
||||
}
|
||||
|
||||
async function _unsubscribeAndGetTx(tx, context, listId, existingSubscription, campaignCid) {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'manageSubscriptions');
|
||||
|
||||
if (!(existingSubscription && existingSubscription.status === SubscriptionStatus.SUBSCRIBED)) {
|
||||
throw new interoperableErrors.NotFoundError();
|
||||
}
|
||||
|
||||
existingSubscription.status = SubscriptionStatus.UNSUBSCRIBED;
|
||||
|
||||
await tx(getSubscriptionTableName(listId)).where('id', existingSubscription.id).update({
|
||||
status: SubscriptionStatus.UNSUBSCRIBED
|
||||
});
|
||||
|
||||
await tx('lists').where('id', listId).decrement('subscribers', 1);
|
||||
|
||||
if (campaignCid) {
|
||||
const campaign = await tx('campaigns').where('cid', campaignCid);
|
||||
const subscriptionInCampaign = await tx(getCampaignTableName(campaign.id)).where({subscription: existingSubscription.id, list: listId});
|
||||
|
||||
if (!subscriptionInCampaign) {
|
||||
throw new Error('Invalid campaign.')
|
||||
}
|
||||
|
||||
if (subscriptionInCampaign.status === SubscriptionStatus.SUBSCRIBED) {
|
||||
await tx('campaigns').where('id', campaign.id).increment('unsubscribed', 1);
|
||||
await tx(getCampaignTableName(campaign.id)).where({subscription: existingSubscription.id, list: listId}).update({
|
||||
status: SubscriptionStatus.UNSUBSCRIBED
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return existingSubscription;
|
||||
}
|
||||
|
||||
|
||||
async function unsubscribeByIdAndGet(context, listId, subscriptionId) {
|
||||
return await knex.transaction(async tx => {
|
||||
const existing = await tx(getSubscriptionTableName(listId)).where('id', subscriptionId).first();
|
||||
return _unsubscribeAndGetTx(tx, context, listId, existing);
|
||||
});
|
||||
}
|
||||
|
||||
async function unsubscribeByCidAndGet(context, listId, subscriptionCid, campaignCid) {
|
||||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'manageSubscriptions');
|
||||
|
||||
const existing = await tx(getSubscriptionTableName(listId)).where('cid', subscriptionCid).first();
|
||||
if (!(existing && existing.status === SubscriptionStatus.SUBSCRIBED)) {
|
||||
throw new interoperableErrors.NotFoundError();
|
||||
}
|
||||
|
||||
existing.status = SubscriptionStatus.UNSUBSCRIBED;
|
||||
|
||||
await tx(getSubscriptionTableName(listId)).where('cid', subscriptionCid).update({
|
||||
status: SubscriptionStatus.UNSUBSCRIBED
|
||||
});
|
||||
|
||||
await tx('lists').where('id', listId).decrement('subscribers', 1);
|
||||
|
||||
if (campaignCid) {
|
||||
const campaign = await tx('campaigns').where('cid', campaignCid);
|
||||
const subscriptionInCampaign = await tx(getCampaignTableName(campaign.id)).where({subscription: existing.id, list: listId});
|
||||
|
||||
if (!subscriptionInCampaign) {
|
||||
throw new Error('Invalid campaign.')
|
||||
}
|
||||
|
||||
if (subscriptionInCampaign.status === SubscriptionStatus.SUBSCRIBED) {
|
||||
await tx('campaigns').where('id', campaign.id).increment('unsubscribed', 1);
|
||||
await tx(getCampaignTableName(campaign.id)).where({subscription: existing.id, list: listId}).update({
|
||||
status: SubscriptionStatus.UNSUBSCRIBED
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return existing;
|
||||
return _unsubscribeAndGetTx(tx, context, listId, existing, campaignCid);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -564,8 +607,8 @@ async function updateManagedUngrouped(context, listId, entity) {
|
|||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'manageSubscriptions');
|
||||
|
||||
const existing = await tx(getSubscriptionTableName(listId)).where('id', entity.id).first();
|
||||
if (!existing) {
|
||||
const existing = await tx(getSubscriptionTableName(listId)).where('cid', entity.cid).first();
|
||||
if (!existing || existing.status !== SubscriptionStatus.SUBSCRIBED) {
|
||||
throw new interoperableErrors.NotFoundError();
|
||||
}
|
||||
|
||||
|
@ -583,7 +626,7 @@ async function updateManagedUngrouped(context, listId, entity) {
|
|||
}
|
||||
}
|
||||
|
||||
await tx(getSubscriptionTableName(listId)).where('id', entity.id).update(update);
|
||||
await tx(getSubscriptionTableName(listId)).where('cid', entity.cid).update(update);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -592,7 +635,6 @@ module.exports = {
|
|||
getById,
|
||||
getByCid,
|
||||
getByEmail,
|
||||
getStatusByCid,
|
||||
list,
|
||||
listDTAjax,
|
||||
serverValidate,
|
||||
|
@ -600,6 +642,7 @@ module.exports = {
|
|||
updateWithConsistencyCheck,
|
||||
remove,
|
||||
unsubscribeByCidAndGet,
|
||||
unsubscribeByIdAndGet,
|
||||
updateAddressAndGet,
|
||||
updateManagedUngrouped
|
||||
};
|
8447
package-lock.json
generated
Normal file
8447
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"name": "mailtrain",
|
||||
"private": true,
|
||||
"version": "1.23.2",
|
||||
"description": "Self hosted email newsletter app",
|
||||
"main": "index.js",
|
||||
|
@ -29,7 +28,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^8.1.2",
|
||||
"bluebird": "^3.5.0",
|
||||
"chai": "^4.1.2",
|
||||
"eslint-config-nodemailer": "^1.2.0",
|
||||
"grunt": "^1.0.1",
|
||||
|
@ -91,6 +89,7 @@
|
|||
"memory-cache": "^0.2.0",
|
||||
"mjml": "3.3.5",
|
||||
"mkdirp": "^0.5.1",
|
||||
"moment": "^2.18.1",
|
||||
"moment-timezone": "^0.5.13",
|
||||
"morgan": "^1.8.2",
|
||||
"multer": "^1.3.0",
|
||||
|
@ -105,7 +104,6 @@
|
|||
"npmlog": "^4.1.2",
|
||||
"object-hash": "^1.1.8",
|
||||
"openpgp": "^2.6.1",
|
||||
"owasp-password-strength-test": "github:bures/owasp-password-strength-test",
|
||||
"passport": "^0.4.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"premailer-api": "^1.0.4",
|
||||
|
|
|
@ -39,7 +39,7 @@ router.postAsync('/subscriptions-validate/:listId', passport.loggedIn, async (re
|
|||
});
|
||||
|
||||
router.postAsync('/subscriptions-unsubscribe/:listId/:subscriptionId', passport.loggedIn, passport.csrfProtection, async (req, res) => {
|
||||
await subscriptions.unsubscribeAndGet(req.context, req.params.listId, req.params.subscriptionId);
|
||||
await subscriptions.unsubscribeByIdAndGet(req.context, req.params.listId, req.params.subscriptionId);
|
||||
return res.json();
|
||||
});
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ const confirmations = require('../models/confirmations');
|
|||
const subscriptions = require('../models/subscriptions');
|
||||
const lists = require('../models/lists');
|
||||
const fields = require('../models/fields');
|
||||
const shares = require('../models/shares');
|
||||
const settings = require('../models/settings');
|
||||
const _ = require('../lib/translate')._;
|
||||
const contextHelpers = require('../lib/context-helpers');
|
||||
|
@ -127,32 +128,40 @@ async function getMjmlTemplate(template) {
|
|||
return renderer;
|
||||
}
|
||||
|
||||
function captureFlashMessages(req, res) {
|
||||
return new Promise((resolve, reject) => {
|
||||
res.render('subscription/capture-flash-messages', { layout: null }, (err, flash) => {
|
||||
reject(err);
|
||||
resolve(flash);
|
||||
});
|
||||
})
|
||||
async function captureFlashMessages(res) {
|
||||
const renderAsync = bluebird.promisify(res.render.bind(res));
|
||||
return await renderAsync('subscription/capture-flash-messages', { layout: null });
|
||||
}
|
||||
|
||||
|
||||
|
||||
router.getAsync('/confirm/subscribe/:cid', async (req, res) => {
|
||||
const confirmation = await takeConfirmationAndValidate(req, 'subscribe', () => new interoperableErrors.InvalidConfirmationForSubscriptionError('Request invalid or already completed. If your subscription request is still pending, please subscribe again.'));
|
||||
const subscription = confirmation.data;
|
||||
const data = confirmation.data;
|
||||
|
||||
const meta = {
|
||||
cid: req.params.cid,
|
||||
ip: confirmation.ip,
|
||||
country: geoip.lookupCountry(confirmation.ip) || null
|
||||
country: geoip.lookupCountry(confirmation.ip) || null,
|
||||
replaceOfUnsubscribedAllowed: true
|
||||
};
|
||||
|
||||
const subscription = data.subscriptionData;
|
||||
subscription.email = data.email;
|
||||
subscription.status = SubscriptionStatus.SUBSCRIBED;
|
||||
|
||||
await subscriptions.create(contextHelpers.getAdminContext(), confirmation.list, subscription, meta);
|
||||
try {
|
||||
await subscriptions.create(contextHelpers.getAdminContext(), confirmation.list, subscription, meta);
|
||||
} catch (err) {
|
||||
if (err instanceof interoperableErrors.DuplicitEmailError) {
|
||||
throw new interoperableErrors.DuplicitEmailError('Subscription already present'); // This is here to provide some meaningful error message.
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(subscription);
|
||||
|
||||
const list = await lists.getById(contextHelpers.getAdminContext(), confirmation.list);
|
||||
subscription.cid = meta.cid;
|
||||
await mailHelpers.sendSubscriptionConfirmed(list, subscription.email, subscription);
|
||||
|
||||
res.redirect('/subscription/' + encodeURIComponent(list.cid) + '/subscribed-notice');
|
||||
|
@ -187,15 +196,15 @@ router.getAsync('/confirm/unsubscribe/:cid', async (req, res) => {
|
|||
|
||||
|
||||
router.getAsync('/:cid', passport.csrfProtection, async (req, res) => {
|
||||
const list = await lists.getByCid(req.params.cid);
|
||||
const list = await lists.getByCid(contextHelpers.getAdminContext(), req.params.cid);
|
||||
|
||||
if (!list.publicSubscribe) {
|
||||
throw new interoperableErrors.SubscriptionNotAllowedError('The list does not allow public subscriptions.');
|
||||
if (!list.public_subscribe) {
|
||||
shares.throwPermissionDenied();
|
||||
}
|
||||
|
||||
const ucid = req.query.cid;
|
||||
|
||||
const data = {};
|
||||
const data = req.query;
|
||||
data.layout = 'subscription/layout';
|
||||
data.title = list.name;
|
||||
data.cid = list.cid;
|
||||
|
@ -203,10 +212,14 @@ router.getAsync('/:cid', passport.csrfProtection, async (req, res) => {
|
|||
|
||||
let subscription;
|
||||
if (ucid) {
|
||||
subscription = await subscriptions.getById(contextHelpers.getAdminContext(), list.id, ucid, false);
|
||||
subscription = await subscriptions.getByCid(contextHelpers.getAdminContext(), list.id, ucid, false);
|
||||
|
||||
if (subscription) {
|
||||
data.email = subscription.email;
|
||||
}
|
||||
}
|
||||
|
||||
data.customFields = fields.getRow(contextHelpers.getAdminContext(), list.id, subscription);
|
||||
data.customFields = await fields.getRow(contextHelpers.getAdminContext(), list.id, subscription);
|
||||
data.useEditor = true;
|
||||
|
||||
const configItems = await settings.get(['pgpPrivateKey', 'defaultAddress', 'defaultPostaddress']);
|
||||
|
@ -227,7 +240,8 @@ router.getAsync('/:cid', passport.csrfProtection, async (req, res) => {
|
|||
data.needsJsWarning = true;
|
||||
data.flashMessages = await captureFlashMessages(res);
|
||||
|
||||
res.send(htmlRenderer(data));
|
||||
const result = htmlRenderer(data);
|
||||
res.send(result);
|
||||
});
|
||||
|
||||
|
||||
|
@ -241,7 +255,7 @@ router.getAsync('/:cid/widget', cors(corsOptions), async (req, res) => {
|
|||
return res.status(200).json(cached);
|
||||
}
|
||||
|
||||
const list = await lists.getByCid(req.params.cid);
|
||||
const list = await lists.getByCid(contextHelpers.getAdminContext(), req.params.cid);
|
||||
|
||||
const configItems = await settings.get(['serviceUrl', 'pgpPrivateKey']);
|
||||
|
||||
|
@ -250,7 +264,7 @@ router.getAsync('/:cid/widget', cors(corsOptions), async (req, res) => {
|
|||
cid: list.cid,
|
||||
serviceUrl: configItems.serviceUrl,
|
||||
hasPubkey: !!configItems.pgpPrivateKey,
|
||||
customFields: fields.getRow(contextHelpers.getAdminContext(), list.id),
|
||||
customFields: await fields.getRow(contextHelpers.getAdminContext(), list.id),
|
||||
template: {},
|
||||
layout: null,
|
||||
};
|
||||
|
@ -293,11 +307,13 @@ router.postAsync('/:cid/subscribe', passport.parseForm, corsOrCsrfProtection, as
|
|||
|
||||
const emailErr = await tools.validateEmail(email);
|
||||
if (emailErr) {
|
||||
const errMsg = tools.validateEmailGetMessage(emailErr, email);
|
||||
|
||||
if (req.xhr) {
|
||||
throw new Error(emailErr.message);
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
|
||||
req.flash('danger', emailErr.message);
|
||||
req.flash('danger', errMsg);
|
||||
return res.redirect('/subscription/' + encodeURIComponent(req.params.cid) + '?' + tools.queryParams(req.body));
|
||||
}
|
||||
|
||||
|
@ -310,20 +326,20 @@ router.postAsync('/:cid/subscribe', passport.parseForm, corsOrCsrfProtection, as
|
|||
let addressTest = !req.body.address;
|
||||
let testsPass = subTimeTest && addressTest;
|
||||
|
||||
const list = await lists.getByCid(req.params.cid);
|
||||
const list = await lists.getByCid(contextHelpers.getAdminContext(), req.params.cid);
|
||||
|
||||
if (!list.publicSubscribe) {
|
||||
throw new interoperableErrors.SubscriptionNotAllowedError('The list does not allow public subscriptions.');
|
||||
if (!list.public_subscribe) {
|
||||
shares.throwPermissionDenied();
|
||||
}
|
||||
|
||||
let subscriptionData = {};
|
||||
const subscriptionData = {};
|
||||
Object.keys(req.body).forEach(key => {
|
||||
if (key !== 'email' && key.charAt(0) !== '_') {
|
||||
subscriptionData[key] = (req.body[key] || '').toString().trim();
|
||||
}
|
||||
});
|
||||
|
||||
const subscription = subscriptions.getByEmail(list.id, email, false)
|
||||
const subscription = await subscriptions.getByEmail(contextHelpers.getAdminContext(), list.id, email, false);
|
||||
|
||||
if (subscription && subscription.status === SubscriptionStatus.SUBSCRIBED) {
|
||||
await mailHelpers.sendAlreadySubscribed(list, email, subscription);
|
||||
|
@ -353,33 +369,36 @@ router.postAsync('/:cid/subscribe', passport.parseForm, corsOrCsrfProtection, as
|
|||
});
|
||||
|
||||
router.getAsync('/:lcid/manage/:ucid', passport.csrfProtection, async (req, res) => {
|
||||
const list = await lists.getByCid(req.params.lcid);
|
||||
const list = await lists.getByCid(contextHelpers.getAdminContext(), req.params.lcid);
|
||||
const subscription = await subscriptions.getByCid(contextHelpers.getAdminContext(), list.id, req.params.ucid, false);
|
||||
|
||||
if (!subscription || subscription.status !== SubscriptionStatus.SUBSCRIBED) {
|
||||
throw new interoperableErrors.NotFoundError('Subscription not found in this list');
|
||||
}
|
||||
|
||||
subscription.lcid = req.params.lcid;
|
||||
subscription.title = list.name;
|
||||
subscription.csrfToken = req.csrfToken();
|
||||
subscription.layout = 'subscription/layout';
|
||||
const data = {};
|
||||
data.email = subscription.email;
|
||||
data.cid = subscription.cid;
|
||||
data.lcid = req.params.lcid;
|
||||
data.title = list.name;
|
||||
data.csrfToken = req.csrfToken();
|
||||
data.layout = 'data/layout';
|
||||
|
||||
subscription.customFields = await fields.getRow(contextHelpers.getAdminContext(), list.id, subscription);
|
||||
data.customFields = await fields.getRow(contextHelpers.getAdminContext(), list.id, subscription);
|
||||
|
||||
subscription.useEditor = true;
|
||||
data.useEditor = true;
|
||||
|
||||
const configItems = await settings.get(['pgpPrivateKey', 'defaultAddress', 'defaultPostaddress']);
|
||||
subscription.hasPubkey = !!configItems.pgpPrivateKey;
|
||||
subscription.defaultAddress = configItems.defaultAddress;
|
||||
subscription.defaultPostaddress = configItems.defaultPostaddress;
|
||||
data.hasPubkey = !!configItems.pgpPrivateKey;
|
||||
data.defaultAddress = configItems.defaultAddress;
|
||||
data.defaultPostaddress = configItems.defaultPostaddress;
|
||||
|
||||
subscription.template = {
|
||||
data.template = {
|
||||
template: 'subscription/web-manage.mjml.hbs',
|
||||
layout: 'subscription/layout.mjml.hbs'
|
||||
};
|
||||
|
||||
await injectCustomFormData(req.query.fid || list.default_form, 'subscription/web-manage', subscription);
|
||||
await injectCustomFormData(req.query.fid || list.default_form, 'data/web-manage', data);
|
||||
|
||||
const htmlRenderer = await getMjmlTemplate(data.template);
|
||||
|
||||
|
@ -392,20 +411,23 @@ router.getAsync('/:lcid/manage/:ucid', passport.csrfProtection, async (req, res)
|
|||
});
|
||||
|
||||
router.postAsync('/:lcid/manage', passport.parseForm, passport.csrfProtection, async (req, res) => {
|
||||
const list = await lists.getByCid(req.params.lcid);
|
||||
const status = await subscriptions.getStatusByCid(contextHelpers.getAdminContext(), list.id, req.body.cid);
|
||||
const list = await lists.getByCid(contextHelpers.getAdminContext(), req.params.lcid);
|
||||
|
||||
if (status !== SubscriptionStatus.SUBSCRIBED) {
|
||||
throw new interoperableErrors.NotFoundError('Subscription not found in this list');
|
||||
try {
|
||||
await subscriptions.updateManagedUngrouped(contextHelpers.getAdminContext(), list.id, req.body);
|
||||
} catch (err) {
|
||||
if (err instanceof interoperableErrors.NotFoundError) {
|
||||
throw new interoperableErrors.NotFoundError('Subscription not found in this list');
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
await subscriptions.updateManagedUngrouped(contextHelpers.getAdminContext(), list.id, req.body)
|
||||
|
||||
res.redirect('/subscription/' + encodeURIComponent(req.params.lcid) + '/updated-notice');
|
||||
});
|
||||
|
||||
router.getAsync('/:lcid/manage-address/:ucid', passport.csrfProtection, async (req, res) => {
|
||||
const list = await lists.getByCid(req.params.lcid);
|
||||
const list = await lists.getByCid(contextHelpers.getAdminContext(), req.params.lcid);
|
||||
const subscription = await subscriptions.getByCid(contextHelpers.getAdminContext(), list.id, req.params.ucid, false);
|
||||
|
||||
if (!subscription || subscription.status !== SubscriptionStatus.SUBSCRIBED) {
|
||||
|
@ -414,18 +436,21 @@ router.getAsync('/:lcid/manage-address/:ucid', passport.csrfProtection, async (r
|
|||
|
||||
const configItems = await settings.get(['defaultAddress', 'defaultPostaddress']);
|
||||
|
||||
subscription.lcid = req.params.lcid;
|
||||
subscription.title = list.name;
|
||||
subscription.csrfToken = req.csrfToken();
|
||||
subscription.defaultAddress = configItems.defaultAddress;
|
||||
subscription.defaultPostaddress = configItems.defaultPostaddress;
|
||||
const data = {};
|
||||
data.email = subscription.email;
|
||||
data.cid = subscription.cid;
|
||||
data.lcid = req.params.lcid;
|
||||
data.title = list.name;
|
||||
data.csrfToken = req.csrfToken();
|
||||
data.defaultAddress = configItems.defaultAddress;
|
||||
data.defaultPostaddress = configItems.defaultPostaddress;
|
||||
|
||||
subscription.template = {
|
||||
data.template = {
|
||||
template: 'subscription/web-manage-address.mjml.hbs',
|
||||
layout: 'subscription/layout.mjml.hbs'
|
||||
};
|
||||
|
||||
await injectCustomFormData(req.query.fid || list.default_form, 'subscription/web-manage-address', subscription);
|
||||
await injectCustomFormData(req.query.fid || list.default_form, 'data/web-manage-address', subscription);
|
||||
|
||||
const htmlRenderer = await getMjmlTemplate(data.template);
|
||||
|
||||
|
@ -439,13 +464,13 @@ router.getAsync('/:lcid/manage-address/:ucid', passport.csrfProtection, async (r
|
|||
|
||||
|
||||
router.postAsync('/:lcid/manage-address', passport.parseForm, passport.csrfProtection, async (req, res) => {
|
||||
const list = await lists.getByCid(req.params.lcid);
|
||||
const list = await lists.getByCid(contextHelpers.getAdminContext(), req.params.lcid);
|
||||
|
||||
const emailNew = (req.body['email-new'] || '').toString().trim();
|
||||
|
||||
const subscription = await subscriptions.getByCid(contextHelpers.getAdminContext(), list.id, req.body.cid, false);
|
||||
|
||||
if (status !== SubscriptionStatus.SUBSCRIBED) {
|
||||
if (subscription.status !== SubscriptionStatus.SUBSCRIBED) {
|
||||
throw new interoperableErrors.NotFoundError('Subscription not found in this list');
|
||||
}
|
||||
|
||||
|
@ -455,7 +480,9 @@ router.postAsync('/:lcid/manage-address', passport.parseForm, passport.csrfProte
|
|||
} else {
|
||||
const emailErr = await tools.validateEmail(emailNew);
|
||||
if (emailErr) {
|
||||
req.flash('danger', emailErr.message);
|
||||
const errMsg = tools.validateEmailGetMessage(emailErr, email);
|
||||
|
||||
req.flash('danger', errMsg);
|
||||
|
||||
} else {
|
||||
const newSubscription = await subscriptions.getByEmail(contextHelpers.getAdminContext(), list.id, emailNew, false);
|
||||
|
@ -463,8 +490,13 @@ router.postAsync('/:lcid/manage-address', passport.parseForm, passport.csrfProte
|
|||
if (newSubscription && newSubscription.status === SubscriptionStatus.SUBSCRIBED) {
|
||||
await mailHelpers.sendAlreadySubscribed(list, emailNew, subscription);
|
||||
} else {
|
||||
const data = {
|
||||
subscriptionId: subscription.id,
|
||||
emailNew
|
||||
};
|
||||
|
||||
const confirmCid = await confirmations.addConfirmation(list.id, 'change-address', req.ip, data);
|
||||
await mailHelpers.sendConfirmAddressChange(list, emailNew, confirmCid, subscription, sendWebResponse);
|
||||
await mailHelpers.sendConfirmAddressChange(list, emailNew, confirmCid, subscription);
|
||||
}
|
||||
|
||||
req.flash('info', _('An email with further instructions has been sent to the provided address'));
|
||||
|
@ -476,7 +508,7 @@ router.postAsync('/:lcid/manage-address', passport.parseForm, passport.csrfProte
|
|||
|
||||
|
||||
router.getAsync('/:lcid/unsubscribe/:ucid', passport.csrfProtection, async (req, res) => {
|
||||
const list = await lists.getByCid(req.params.lcid);
|
||||
const list = await lists.getByCid(contextHelpers.getAdminContext(), req.params.lcid);
|
||||
|
||||
const configItems = await settings.get(['defaultAddress', 'defaultPostaddress']);
|
||||
|
||||
|
@ -486,8 +518,8 @@ router.getAsync('/:lcid/unsubscribe/:ucid', passport.csrfProtection, async (req,
|
|||
handleUnsubscribe(list, req.params.ucid, autoUnsubscribe, req.query.c, req.ip, res, next);
|
||||
|
||||
} else if (req.query.formTest ||
|
||||
list.unsubscriptionMode === lists.UnsubscriptionMode.ONE_STEP_WITH_FORM ||
|
||||
list.unsubscriptionMode === lists.UnsubscriptionMode.TWO_STEP_WITH_FORM) {
|
||||
list.unsubscription_mode === lists.UnsubscriptionMode.ONE_STEP_WITH_FORM ||
|
||||
list.unsubscription_mode === lists.UnsubscriptionMode.TWO_STEP_WITH_FORM) {
|
||||
|
||||
const subscription = await subscriptions.getByCid(contextHelpers.getAdminContext(), list.id, req.params.ucid, false);
|
||||
|
||||
|
@ -495,20 +527,22 @@ router.getAsync('/:lcid/unsubscribe/:ucid', passport.csrfProtection, async (req,
|
|||
throw new interoperableErrors.NotFoundError('Subscription not found in this list');
|
||||
}
|
||||
|
||||
subscription.lcid = req.params.lcid;
|
||||
subscription.ucid = req.params.ucid;
|
||||
subscription.title = list.name;
|
||||
subscription.csrfToken = req.csrfToken();
|
||||
subscription.campaign = req.query.c;
|
||||
subscription.defaultAddress = configItems.defaultAddress;
|
||||
subscription.defaultPostaddress = configItems.defaultPostaddress;
|
||||
const data = {};
|
||||
data.email = subscription.email;
|
||||
data.lcid = req.params.lcid;
|
||||
data.ucid = req.params.ucid;
|
||||
data.title = list.name;
|
||||
data.csrfToken = req.csrfToken();
|
||||
data.campaign = req.query.c;
|
||||
data.defaultAddress = configItems.defaultAddress;
|
||||
data.defaultPostaddress = configItems.defaultPostaddress;
|
||||
|
||||
subscription.template = {
|
||||
data.template = {
|
||||
template: 'subscription/web-unsubscribe.mjml.hbs',
|
||||
layout: 'subscription/layout.mjml.hbs'
|
||||
};
|
||||
|
||||
await injectCustomFormData(req.query.fid || list.default_form, 'subscription/web-unsubscribe', subscription);
|
||||
await injectCustomFormData(req.query.fid || list.default_form, 'subscription/web-unsubscribe', data);
|
||||
|
||||
const htmlRenderer = await getMjmlTemplate(data.template);
|
||||
|
||||
|
@ -526,7 +560,7 @@ router.getAsync('/:lcid/unsubscribe/:ucid', passport.csrfProtection, async (req,
|
|||
|
||||
|
||||
router.postAsync('/:lcid/unsubscribe', passport.parseForm, passport.csrfProtection, async (req, res) => {
|
||||
const list = await lists.getByCid(req.params.lcid);
|
||||
const list = await lists.getByCid(contextHelpers.getAdminContext(), req.params.lcid);
|
||||
|
||||
const campaignCid = (req.body.campaign || '').toString().trim() || false;
|
||||
|
||||
|
@ -535,8 +569,8 @@ router.postAsync('/:lcid/unsubscribe', passport.parseForm, passport.csrfProtecti
|
|||
|
||||
|
||||
async function handleUnsubscribe(list, subscriptionCid, autoUnsubscribe, campaignCid, ip, res) {
|
||||
if ((list.unsubscriptionMode === lists.UnsubscriptionMode.ONE_STEP || list.unsubscriptionMode === lists.UnsubscriptionMode.ONE_STEP_WITH_FORM) ||
|
||||
(autoUnsubscribe && (list.unsubscriptionMode === lists.UnsubscriptionMode.TWO_STEP || list.unsubscriptionMode === lists.UnsubscriptionMode.TWO_STEP_WITH_FORM)) ) {
|
||||
if ((list.unsubscription_mode === lists.UnsubscriptionMode.ONE_STEP || list.unsubscription_mode === lists.UnsubscriptionMode.ONE_STEP_WITH_FORM) ||
|
||||
(autoUnsubscribe && (list.unsubscription_mode === lists.UnsubscriptionMode.TWO_STEP || list.unsubscription_mode === lists.UnsubscriptionMode.TWO_STEP_WITH_FORM)) ) {
|
||||
|
||||
try {
|
||||
const subscription = await subscriptions.unsubscribeByCidAndGet(contextHelpers.getAdminContext(), list.id, subscriptionCid, campaignCid);
|
||||
|
@ -558,7 +592,7 @@ async function handleUnsubscribe(list, subscriptionCid, autoUnsubscribe, campaig
|
|||
throw new interoperableErrors.NotFoundError('Subscription not found in this list');
|
||||
}
|
||||
|
||||
if (list.unsubscriptionMode === lists.UnsubscriptionMode.TWO_STEP || list.unsubscriptionMode === lists.UnsubscriptionMode.TWO_STEP_WITH_FORM) {
|
||||
if (list.unsubscription_mode === lists.UnsubscriptionMode.TWO_STEP || list.unsubscription_mode === lists.UnsubscriptionMode.TWO_STEP_WITH_FORM) {
|
||||
|
||||
const data = {
|
||||
subscriptionCid,
|
||||
|
@ -638,7 +672,7 @@ router.postAsync('/publickey', passport.parseForm, async (req, res) => {
|
|||
|
||||
|
||||
async function webNotice(type, req, res) {
|
||||
const list = await lists.getByCid(req.params.cid);
|
||||
const list = await lists.getByCid(contextHelpers.getAdminContext(), req.params.cid);
|
||||
|
||||
const configItems = await settings.get(['defaultHomepage', 'serviceUrl', 'defaultAddress', 'defaultPostaddress', 'adminEmail']);
|
||||
|
||||
|
|
|
@ -105,13 +105,6 @@ class InvalidConfirmationForUnsubscriptionError extends InteroperableError {
|
|||
}
|
||||
}
|
||||
|
||||
class SubscriptionNotAllowedError extends InteroperableError {
|
||||
constructor(msg, data) {
|
||||
super('SubscriptionNotAllowedError', msg, data);
|
||||
this.status = 403;
|
||||
}
|
||||
}
|
||||
|
||||
const errorTypes = {
|
||||
InteroperableError,
|
||||
NotLoggedInError,
|
||||
|
@ -129,8 +122,7 @@ const errorTypes = {
|
|||
PermissionDeniedError,
|
||||
InvalidConfirmationForSubscriptionError,
|
||||
InvalidConfirmationForAddressChangeError,
|
||||
InvalidConfirmationForUnsubscriptionError,
|
||||
SubscriptionNotAllowedError
|
||||
InvalidConfirmationForUnsubscriptionError
|
||||
};
|
||||
|
||||
function deserialize(errorObj) {
|
||||
|
|
24
shared/package-lock.json
generated
Normal file
24
shared/package-lock.json
generated
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "mailtrain-shared",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"moment": {
|
||||
"version": "2.20.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz",
|
||||
"integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg=="
|
||||
},
|
||||
"moment-timezone": {
|
||||
"version": "0.5.14",
|
||||
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.14.tgz",
|
||||
"integrity": "sha1-TrOP+VOLgBCLpGekWPPtQmjM/LE=",
|
||||
"requires": {
|
||||
"moment": "2.20.1"
|
||||
}
|
||||
},
|
||||
"owasp-password-strength-test": {
|
||||
"version": "github:bures/owasp-password-strength-test#50bfcf0035b1468b9d03a00eaf561d4fed4973eb"
|
||||
}
|
||||
}
|
||||
}
|
20
shared/package.json
Normal file
20
shared/package.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "mailtrain-shared",
|
||||
"version": "1.0.0",
|
||||
"description": "Self hosted email newsletter app - shared lib",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/Mailtrain-org/mailtrain.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "GPL-3.0",
|
||||
"homepage": "https://mailtrain.org/",
|
||||
"engines": {
|
||||
"node": ">=5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"moment": "^2.18.1",
|
||||
"moment-timezone": "^0.5.13",
|
||||
"owasp-password-strength-test": "github:bures/owasp-password-strength-test"
|
||||
}
|
||||
}
|
|
@ -181,7 +181,7 @@
|
|||
|
||||
<footer class="footer">
|
||||
<div class="container-fluid">
|
||||
<p class="text-muted">© 2016 Kreata OÜ <a href="https://mailtrain.org">Mailtrain.org</a>, <a href="mailto:info@mailtrain.org">info@mailtrain.org</a>. <a href="https://github.com/Mailtrain-org/mailtrain">{{#translate}}Source on GitHub{{/translate}}</a></p>
|
||||
<p class="text-muted">© 2018 <a href="https://mailtrain.org">Mailtrain.org</a>, <a href="mailto:info@mailtrain.org">info@mailtrain.org</a>. <a href="https://github.com/Mailtrain-org/mailtrain">{{#translate}}Source on GitHub{{/translate}}</a></p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue