Checks for dependencies during deletion.
This commit is contained in:
parent
0a08088893
commit
efbfa2b366
20 changed files with 246 additions and 121 deletions
|
@ -105,8 +105,15 @@ export class DeleteModalDialog extends Component {
|
|||
const t = props.t;
|
||||
|
||||
this.entityTypeLabels = {
|
||||
'namespace': t('Namespace'),
|
||||
'list': t('List'),
|
||||
'customForm': t('Custom forms'),
|
||||
'campaign': t('Campaign'),
|
||||
'template': t('Template')
|
||||
'template': t('Template'),
|
||||
'sendConfiguration': t('Send configuration'),
|
||||
'report': t('Report'),
|
||||
'reportTemplate': t('Report template'),
|
||||
'mosaicoTemplate': t('Mosaico template')
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -141,8 +148,12 @@ export class DeleteModalDialog extends Component {
|
|||
<p>{t('Cannote delete "{{name}}" due to the following dependencies:', {name, nsSeparator: '|'})}</p>
|
||||
<ul className={styles.dependenciesList}>
|
||||
{err.data.dependencies.map(dep =>
|
||||
dep.link ?
|
||||
<li key={dep.link}><Link to={dep.link}>{this.entityTypeLabels[dep.entityTypeId]}: {dep.name}</Link></li>
|
||||
: // if no dep.link is present, it means the user has no permission to view the entity, thus only id without the link is shown
|
||||
<li key={dep.id}>{this.entityTypeLabels[dep.entityTypeId]}: [{dep.id}]</li>
|
||||
)}
|
||||
{err.data.andMore && <li>{t('... and more')}</li>}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -26,7 +26,8 @@
|
|||
|
||||
.mt-treetable-container.mt-treetable-inactivable>table.fancytree-ext-table.fancytree-container>tbody>tr.fancytree-active>td span.fancytree-title,
|
||||
.mt-treetable-container.mt-treetable-inactivable>table.fancytree-ext-table.fancytree-container>tbody>tr.fancytree-active>td span.fancytree-expander,
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title {
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title,
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container>tbody>tr.fancytree-active>td {
|
||||
outline: 0px none;
|
||||
color: #333333;
|
||||
}
|
||||
|
|
|
@ -104,6 +104,7 @@ class TreeTable extends Component {
|
|||
const data = [];
|
||||
for (const unsafeEntry of unsafeData) {
|
||||
const entry = Object.assign({}, unsafeEntry);
|
||||
entry.unsanitizedTitle = entry.title;
|
||||
entry.title = ReactDOMServer.renderToStaticMarkup(<div>{entry.title}</div>);
|
||||
entry.description = ReactDOMServer.renderToStaticMarkup(<div>{entry.description}</div>);
|
||||
if (entry.children) {
|
||||
|
@ -137,15 +138,32 @@ class TreeTable extends Component {
|
|||
const linksContainer = jQuery(`<span class="${styles.actionLinks}"/>`);
|
||||
|
||||
const actions = this.props.actions(node);
|
||||
for (const {label, link} of actions) {
|
||||
const lnkHtml = ReactDOMServer.renderToStaticMarkup(<a href={link}>{label}</a>);
|
||||
const lnk = jQuery(lnkHtml);
|
||||
lnk.click((evt) => {
|
||||
evt.preventDefault();
|
||||
this.navigateTo(link)
|
||||
});
|
||||
linksContainer.append(lnk);
|
||||
|
||||
for (const action of actions) {
|
||||
if (action.action) {
|
||||
const html = ReactDOMServer.renderToStaticMarkup(<a href="">{action.label}</a>);
|
||||
const elem = jQuery(html);
|
||||
elem.click((evt) => { evt.preventDefault(); action.action(this) });
|
||||
linksContainer.append(elem);
|
||||
|
||||
} else if (action.link) {
|
||||
const html = ReactDOMServer.renderToStaticMarkup(<a href={action.link}>{action.label}</a>);
|
||||
const elem = jQuery(html);
|
||||
elem.click((evt) => { evt.preventDefault(); this.navigateTo(action.link) });
|
||||
linksContainer.append(elem);
|
||||
|
||||
} else if (action.href) {
|
||||
const html = ReactDOMServer.renderToStaticMarkup(<a href={action.href}>{action.label}</a>);
|
||||
const elem = jQuery(html);
|
||||
linksContainer.append(elem);
|
||||
|
||||
} else {
|
||||
const html = ReactDOMServer.renderToStaticMarkup(<span>{action.label}</span>);
|
||||
const elem = jQuery(html);
|
||||
linksContainer.append(elem);
|
||||
}
|
||||
}
|
||||
|
||||
tdList.eq(tdIdx).html(linksContainer);
|
||||
tdIdx += 1;
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ export default class CUD extends Component {
|
|||
this.state = {};
|
||||
|
||||
this.initForm();
|
||||
this.hasChildren = false;
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
|
@ -42,10 +41,6 @@ export default class CUD extends Component {
|
|||
const entry = data[idx];
|
||||
|
||||
if (entry.key === this.props.entity.id) {
|
||||
if (entry.children.length > 0) {
|
||||
this.hasChildren = true;
|
||||
}
|
||||
|
||||
data.splice(idx, 1);
|
||||
return true;
|
||||
}
|
||||
|
@ -158,25 +153,10 @@ export default class CUD extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
async onDeleteError(error) {
|
||||
if (error instanceof interoperableErrors.ChildDetectedError) {
|
||||
this.disableForm();
|
||||
this.setFormStatusMessage('danger',
|
||||
<span>
|
||||
<strong>{t('The namespace cannot be deleted.')}</strong>{' '}
|
||||
{t('There has been a child namespace found. This is most likely because someone else has changed the parent of some namespace in the meantime. Refresh your page to start anew with fresh data.')}
|
||||
</span>
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
const isEdit = !!this.props.entity;
|
||||
const canDelete = isEdit && !this.isEditGlobal() && !this.hasChildren && this.props.entity.permissions.includes('delete');
|
||||
const canDelete = isEdit && !this.isEditGlobal() && this.props.entity.permissions.includes('delete');
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -188,8 +168,7 @@ export default class CUD extends Component {
|
|||
backUrl={`/namespaces/${this.props.entity.id}/edit`}
|
||||
successUrl="/namespaces"
|
||||
deletingMsg={t('Deleting namespace ...')}
|
||||
deletedMsg={t('Namespace deleted')}
|
||||
onErrorAsync={::this.onDeleteError}/>
|
||||
deletedMsg={t('Namespace deleted')} />
|
||||
}
|
||||
|
||||
<Title>{isEdit ? t('Edit Namespace') : t('Create Namespace')}</Title>
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
tableDeleteDialogInit,
|
||||
tableDeleteDialogRender
|
||||
} from "../lib/modals";
|
||||
import {getGlobalNamespaceId} from "../../../shared/namespaces";
|
||||
|
||||
@translate()
|
||||
@withErrorHandling
|
||||
|
@ -50,7 +51,6 @@ export default class List extends Component {
|
|||
|
||||
const actions = node => {
|
||||
const actions = [];
|
||||
console.log(node);
|
||||
|
||||
if (node.data.permissions.includes('edit')) {
|
||||
actions.push({
|
||||
|
@ -66,7 +66,9 @@ export default class List extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
tableDeleteDialogAddDeleteButton(actions, this, node.data.permissions, node.key, node.key);
|
||||
if (Number.parseInt(node.key) !== getGlobalNamespaceId()) {
|
||||
tableDeleteDialogAddDeleteButton(actions, this, node.data.permissions, node.key, node.data.unsanitizedTitle);
|
||||
}
|
||||
|
||||
return actions;
|
||||
};
|
||||
|
|
58
lib/dependency-helpers.js
Normal file
58
lib/dependency-helpers.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
'use strict';
|
||||
|
||||
const knex = require('./knex');
|
||||
const interoperableErrors = require('../shared/interoperable-errors');
|
||||
const entitySettings = require('./entity-settings');
|
||||
const shares = require('../models/shares');
|
||||
const { enforce } = require('./helpers');
|
||||
|
||||
const defaultNoOfDependenciesReported = 20;
|
||||
|
||||
async function ensureNoDependencies(tx, context, id, depSpecs) {
|
||||
|
||||
const deps = [];
|
||||
let andMore = false;
|
||||
|
||||
for (const depSpec of depSpecs) {
|
||||
const entityType = entitySettings.getEntityType(depSpec.entityTypeId);
|
||||
|
||||
let rows;
|
||||
|
||||
if (depSpec.query) {
|
||||
rows = await depSpec.query(tx).limit(defaultNoOfDependenciesReported + 1);
|
||||
} else if (depSpec.column) {
|
||||
rows = await tx(entityType.entitiesTable).where(depSpec.column, id).select(['id', 'name']).limit(defaultNoOfDependenciesReported + 1);
|
||||
} else if (depSpec.rows) {
|
||||
rows = await depSpec.rows(tx, defaultNoOfDependenciesReported + 1)
|
||||
}
|
||||
|
||||
for (const row of rows) {
|
||||
if (deps.length === defaultNoOfDependenciesReported) {
|
||||
andMore = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (await shares.checkEntityPermissionTx(tx, context, depSpec.entityTypeId, row.id, 'view')) {
|
||||
deps.push({
|
||||
entityTypeId: depSpec.entityTypeId,
|
||||
name: row.name,
|
||||
link: entityType.clientLink(row.id)
|
||||
});
|
||||
} else {
|
||||
deps.push({
|
||||
entityTypeId: depSpec.entityTypeId,
|
||||
id: row.id
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (deps.length > 0) {
|
||||
throw new interoperableErrors.DependencyPresentError('', {
|
||||
dependencies: deps,
|
||||
andMore
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.ensureNoDependencies = ensureNoDependencies
|
|
@ -10,17 +10,20 @@ const entityTypes = {
|
|||
namespace: {
|
||||
entitiesTable: 'namespaces',
|
||||
sharesTable: 'shares_namespace',
|
||||
permissionsTable: 'permissions_namespace'
|
||||
permissionsTable: 'permissions_namespace',
|
||||
clientLink: id => `/namespaces/${id}`
|
||||
},
|
||||
list: {
|
||||
entitiesTable: 'lists',
|
||||
sharesTable: 'shares_list',
|
||||
permissionsTable: 'permissions_list'
|
||||
permissionsTable: 'permissions_list',
|
||||
clientLink: id => `/lists/${id}`
|
||||
},
|
||||
customForm: {
|
||||
entitiesTable: 'custom_forms',
|
||||
sharesTable: 'shares_custom_form',
|
||||
permissionsTable: 'permissions_custom_form'
|
||||
permissionsTable: 'permissions_custom_form',
|
||||
clientLink: id => `/lists/forms/${id}`
|
||||
},
|
||||
campaign: {
|
||||
entitiesTable: 'campaigns',
|
||||
|
@ -43,7 +46,8 @@ const entityTypes = {
|
|||
},
|
||||
defaultReplacementBehavior: ReplacementBehavior.NONE
|
||||
}
|
||||
}
|
||||
},
|
||||
clientLink: id => `/campaigns/${id}`
|
||||
},
|
||||
template: {
|
||||
entitiesTable: 'templates',
|
||||
|
@ -58,22 +62,26 @@ const entityTypes = {
|
|||
},
|
||||
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
||||
}
|
||||
}
|
||||
},
|
||||
clientLink: id => `/templates/${id}`
|
||||
},
|
||||
sendConfiguration: {
|
||||
entitiesTable: 'send_configurations',
|
||||
sharesTable: 'shares_send_configuration',
|
||||
permissionsTable: 'permissions_send_configuration'
|
||||
permissionsTable: 'permissions_send_configuration',
|
||||
clientLink: id => `/send-configurations/${id}`
|
||||
},
|
||||
report: {
|
||||
entitiesTable: 'reports',
|
||||
sharesTable: 'shares_report',
|
||||
permissionsTable: 'permissions_report'
|
||||
permissionsTable: 'permissions_report',
|
||||
clientLink: id => `/reports/${id}`
|
||||
},
|
||||
reportTemplate: {
|
||||
entitiesTable: 'report_templates',
|
||||
sharesTable: 'shares_report_template',
|
||||
permissionsTable: 'permissions_report_template'
|
||||
permissionsTable: 'permissions_report_template',
|
||||
clientLink: id => `/reports/templates/${id}`
|
||||
},
|
||||
mosaicoTemplate: {
|
||||
entitiesTable: 'mosaico_templates',
|
||||
|
@ -96,14 +104,31 @@ const entityTypes = {
|
|||
},
|
||||
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
||||
}
|
||||
}
|
||||
},
|
||||
clientLink: id => `/templates/mosaico/${id}`
|
||||
},
|
||||
user: {
|
||||
entitiesTable: 'users',
|
||||
clientLink: id => `/users/${id}`
|
||||
}
|
||||
};
|
||||
|
||||
const entityTypesWithPermissions = {};
|
||||
for (const key in entityTypes) {
|
||||
if (entityTypes[key].permissionsTable) {
|
||||
entityTypesWithPermissions[key] = entityTypes[key];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getEntityTypes() {
|
||||
return entityTypes;
|
||||
}
|
||||
|
||||
function getEntityTypesWithPermissions() {
|
||||
return entityTypesWithPermissions;
|
||||
}
|
||||
|
||||
function getEntityType(entityTypeId) {
|
||||
const entityType = entityTypes[entityTypeId];
|
||||
|
||||
|
@ -116,6 +141,7 @@ function getEntityType(entityTypeId) {
|
|||
|
||||
module.exports = {
|
||||
getEntityTypes,
|
||||
getEntityTypesWithPermissions,
|
||||
getEntityType,
|
||||
ReplacementBehavior
|
||||
}
|
|
@ -467,7 +467,12 @@ async function remove(context, id) {
|
|||
return new interoperableErrors.InvalidStateError;
|
||||
}
|
||||
|
||||
// FIXME - deal with deletion of dependent entities (files)
|
||||
await files.removeAllTx(tx, context, 'campaign', 'file', id);
|
||||
await files.removeAllTx(tx, context, 'campaign', 'attachment', id);
|
||||
|
||||
await tx('campaign_lists').where('campaign', id).del();
|
||||
await tx('campaign_messages').where('campaign', id).del();
|
||||
await tx('campaign_links').where('campaign', id).del();
|
||||
|
||||
await triggers.removeAllByCampaignIdTx(tx, context, id);
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ const path = require('path');
|
|||
const mjml = require('mjml');
|
||||
const _ = require('../lib/translate')._;
|
||||
const lists = require('./lists');
|
||||
const dependencyHelpers = require('../lib/dependency-helpers');
|
||||
|
||||
const formAllowedKeys = new Set([
|
||||
'name',
|
||||
|
@ -173,7 +174,9 @@ async function remove(context, id) {
|
|||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'customForm', id, 'delete');
|
||||
|
||||
await lists.removeFormFromAllTx(tx, context, id);
|
||||
await dependencyHelpers.ensureNoDependencies(tx, context, id, [
|
||||
{ entityTypeId: 'list', column: 'default_form' }
|
||||
]);
|
||||
|
||||
await tx('custom_forms_data').where('form', id).del();
|
||||
await tx('custom_forms').where('id', id).del();
|
||||
|
|
|
@ -10,7 +10,9 @@ const shares = require('./shares');
|
|||
const namespaceHelpers = require('../lib/namespace-helpers');
|
||||
const fields = require('./fields');
|
||||
const segments = require('./segments');
|
||||
const imports = require('./imports');
|
||||
const entitySettings = require('../lib/entity-settings');
|
||||
const dependencyHelpers = require('../lib/dependency-helpers');
|
||||
|
||||
const UnsubscriptionMode = require('../shared/lists').UnsubscriptionMode;
|
||||
|
||||
|
@ -176,23 +178,23 @@ async function remove(context, id) {
|
|||
|
||||
await fields.removeAllByListIdTx(tx, context, id);
|
||||
await segments.removeAllByListIdTx(tx, context, id);
|
||||
await imports.removeAllByListIdTx(tx, context, id);
|
||||
|
||||
await dependencyHelpers.ensureNoDependencies(tx, context, id, [
|
||||
{
|
||||
entityTypeId: 'campaign',
|
||||
query: tx => tx('campaign_lists')
|
||||
.where('campaign_lists.list', id)
|
||||
.innerJoin('campaigns', 'campaign_lists.campaign', 'campaigns.id')
|
||||
.select(['campaigns.id', 'campaigns.name'])
|
||||
}
|
||||
]);
|
||||
|
||||
await tx('lists').where('id', id).del();
|
||||
await knex.schema.dropTableIfExists('subscription__' + id);
|
||||
});
|
||||
}
|
||||
|
||||
async function removeFormFromAllTx(tx, context, formId) {
|
||||
await knex.transaction(async tx => {
|
||||
const entities = tx('lists').where('default_form', formId).select(['id']);
|
||||
|
||||
for (const entity of entities) {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', entity.id, 'edit');
|
||||
await tx('lists').where('id', entity.id).update({default_form: null});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function getMergeTags(context, id) {
|
||||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', id, ['view']);
|
||||
|
@ -224,5 +226,4 @@ module.exports.getByCid = getByCid;
|
|||
module.exports.create = create;
|
||||
module.exports.updateWithConsistencyCheck = updateWithConsistencyCheck;
|
||||
module.exports.remove = remove;
|
||||
module.exports.removeFormFromAllTx = removeFormFromAllTx;
|
||||
module.exports.getMergeTags = getMergeTags;
|
||||
|
|
|
@ -7,6 +7,8 @@ const dtHelpers = require('../lib/dt-helpers');
|
|||
const interoperableErrors = require('../shared/interoperable-errors');
|
||||
const namespaceHelpers = require('../lib/namespace-helpers');
|
||||
const shares = require('./shares');
|
||||
const files = require('./files');
|
||||
const dependencyHelpers = require('../lib/dependency-helpers');
|
||||
|
||||
const allowedKeys = new Set(['name', 'description', 'type', 'data', 'namespace']);
|
||||
|
||||
|
@ -84,22 +86,34 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
async function remove(context, id) {
|
||||
await knex.transaction(async tx => {
|
||||
const deps = [];
|
||||
const tmpls = await tx('templates').where('type', 'mosaico').select(['id', 'name', 'data']);
|
||||
for (const row of tmpls) {
|
||||
const data = JSON.parse(row.data);
|
||||
if (data.mosaicoTemplate === id) {
|
||||
deps.push({ entityTypeId: 'template', name: row.name, link: `templates/${row.id}` });
|
||||
}
|
||||
}
|
||||
|
||||
if (deps.length > 0) {
|
||||
throw new interoperableErrors.DependencyPresentError('', {
|
||||
dependencies: deps
|
||||
});
|
||||
}
|
||||
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'mosaicoTemplate', id, 'delete');
|
||||
|
||||
await dependencyHelpers.ensureNoDependencies(tx, context, id, [
|
||||
{
|
||||
entityTypeId: 'template',
|
||||
rows: async (tx, limit) => {
|
||||
const result = [];
|
||||
|
||||
const tmpls = await tx('templates').where('type', 'mosaico').select(['id', 'name', 'data']);
|
||||
for (const tmpl of tmpls) {
|
||||
const data = JSON.parse(tmpl.data);
|
||||
if (data.mosaicoTemplate === id) {
|
||||
result.push(tmpl);
|
||||
}
|
||||
|
||||
limit -= 1;
|
||||
if (limit <= 0) break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
await files.removeAllTx(tx, context, 'mosaicoTemplate', 'file', id);
|
||||
await files.removeAllTx(tx, context, 'mosaicoTemplate', 'block', id);
|
||||
|
||||
await tx('mosaico_templates').where('id', id).del();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ const interoperableErrors = require('../shared/interoperable-errors');
|
|||
const shares = require('./shares');
|
||||
const entitySettings = require('../lib/entity-settings');
|
||||
const namespaceHelpers = require('../lib/namespace-helpers');
|
||||
const dependencyHelpers = require('../lib/dependency-helpers');
|
||||
|
||||
|
||||
const allowedKeys = new Set(['name', 'description', 'namespace']);
|
||||
|
@ -176,12 +177,8 @@ async function remove(context, id) {
|
|||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', id, 'delete');
|
||||
|
||||
const childNs = await tx('namespaces').where('namespace', id).first();
|
||||
if (childNs) {
|
||||
throw new interoperableErrors.ChildDetectedError();
|
||||
}
|
||||
|
||||
// FIXME - Remove all contained entities first
|
||||
const entityTypesWithNamespace = Object.keys(entitySettings.getEntityTypes());
|
||||
await dependencyHelpers.ensureNoDependencies(tx, context, id, entityTypesWithNamespace.map(entityTypeId => ({ entityTypeId: entityTypeId, column: 'namespace' })));
|
||||
|
||||
await tx('namespaces').where('id', id).del();
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@ const interoperableErrors = require('../shared/interoperable-errors');
|
|||
const namespaceHelpers = require('../lib/namespace-helpers');
|
||||
const shares = require('./shares');
|
||||
const reports = require('./reports');
|
||||
const dependencyHelpers = require('../lib/dependency-helpers');
|
||||
|
||||
const allowedKeys = new Set(['name', 'description', 'mime_type', 'user_fields', 'js', 'hbs', 'namespace']);
|
||||
|
||||
|
@ -77,7 +78,9 @@ async function remove(context, id) {
|
|||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'reportTemplate', id, 'delete');
|
||||
|
||||
await reports.removeAllByReportTemplateIdTx(tx, context, id);
|
||||
await dependencyHelpers.ensureNoDependencies(tx, context, id, [
|
||||
{ entityTypeId: 'report', column: 'report_template' }
|
||||
]);
|
||||
|
||||
await tx('report_templates').where('id', id).del();
|
||||
});
|
||||
|
|
|
@ -8,6 +8,8 @@ const interoperableErrors = require('../shared/interoperable-errors');
|
|||
const fields = require('./fields');
|
||||
const namespaceHelpers = require('../lib/namespace-helpers');
|
||||
const shares = require('./shares');
|
||||
const reportHelpers = require('../lib/report-helpers');
|
||||
const fs = require('fs-extra-promise');
|
||||
|
||||
const ReportState = require('../shared/reports').ReportState;
|
||||
|
||||
|
@ -114,9 +116,12 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
async function removeTx(tx, context, id) {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'report', id, 'delete');
|
||||
|
||||
await tx('reports').where('id', id).del();
|
||||
const report = tx('reports').where('id', id).first();
|
||||
|
||||
// FIXME: Remove generated files
|
||||
await fs.removeAsync(reportHelpers.getReportContentFile(report));
|
||||
await fs.removeAsync(reportHelpers.getReportOutputFile(report));
|
||||
|
||||
await tx('reports').where('id', id).del();
|
||||
}
|
||||
|
||||
async function remove(context, id) {
|
||||
|
@ -125,14 +130,6 @@ async function remove(context, id) {
|
|||
});
|
||||
}
|
||||
|
||||
async function removeAllByReportTemplateIdTx(tx, context, templateId) {
|
||||
const entities = await tx('reports').where('report_template', templateId).select(['id']);
|
||||
for (const entity of entities) {
|
||||
await removeTx(tx, context, entity.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function updateFields(id, fields) {
|
||||
return await knex('reports').where('id', id).update(fields);
|
||||
}
|
||||
|
@ -203,7 +200,6 @@ module.exports.listDTAjax = listDTAjax;
|
|||
module.exports.create = create;
|
||||
module.exports.updateWithConsistencyCheck = updateWithConsistencyCheck;
|
||||
module.exports.remove = remove;
|
||||
module.exports.removeAllByReportTemplateIdTx = removeAllByReportTemplateIdTx;
|
||||
module.exports.updateFields = updateFields;
|
||||
module.exports.listByState = listByState;
|
||||
module.exports.bulkChangeState = bulkChangeState;
|
||||
|
|
|
@ -9,6 +9,7 @@ const hasher = require('node-object-hash')();
|
|||
const moment = require('moment');
|
||||
const fields = require('./fields');
|
||||
const subscriptions = require('./subscriptions');
|
||||
const dependencyHelpers = require('../lib/dependency-helpers');
|
||||
|
||||
const { parseDate, parseBirthday, DateFormat } = require('../shared/date');
|
||||
|
||||
|
@ -331,7 +332,15 @@ async function updateWithConsistencyCheck(context, listId, entity) {
|
|||
async function removeTx(tx, context, listId, id) {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'manageSegments');
|
||||
|
||||
// FIXME - check dependencies: campaigns
|
||||
await dependencyHelpers.ensureNoDependencies(tx, context, id, [
|
||||
{
|
||||
entityTypeId: 'campaign',
|
||||
query: tx => tx('campaign_lists')
|
||||
.where('campaign_lists.segment', id)
|
||||
.innerJoin('campaigns', 'campaign_lists.campaign', 'campaigns.id')
|
||||
.select(['campaigns.id', 'campaigns.name'])
|
||||
}
|
||||
]);
|
||||
|
||||
// The listId "where" is here to prevent deleting segment of a list for which a user does not have permission
|
||||
await tx('segments').where({list: listId, id}).del();
|
||||
|
|
|
@ -12,6 +12,7 @@ const {MailerType, getSystemSendConfigurationId} = require('../shared/send-confi
|
|||
const contextHelpers = require('../lib/context-helpers');
|
||||
const mailers = require('../lib/mailers');
|
||||
const senders = require('../lib/senders');
|
||||
const dependencyHelpers = require('../lib/dependency-helpers');
|
||||
|
||||
const allowedKeys = new Set(['name', 'description', 'from_email', 'from_email_overridable', 'from_name', 'from_name_overridable', 'reply_to', 'reply_to_overridable', 'subject', 'subject_overridable', 'x_mailer', 'verp_hostname', 'mailer_type', 'mailer_settings', 'namespace']);
|
||||
|
||||
|
@ -147,9 +148,11 @@ async function remove(context, id) {
|
|||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'sendConfiguration', id, 'delete');
|
||||
|
||||
await tx('lists').update({send_configuration: null}).where('send_configuration', id);
|
||||
await dependencyHelpers.ensureNoDependencies(tx, context, id, [
|
||||
{ entityTypeId: 'campaign', column: 'send_configuration' },
|
||||
{ entityTypeId: 'list', column: 'send_configuration' }
|
||||
]);
|
||||
|
||||
// If any campaign with the send configuration exists, this fails due to sql foreign key
|
||||
await tx('send_configurations').where('id', id).del();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -140,7 +140,7 @@ async function rebuildPermissionsTx(tx, restriction) {
|
|||
[restriction.entityTypeId]: entityType
|
||||
};
|
||||
} else {
|
||||
restrictedEntityTypes = entitySettings.getEntityTypes();
|
||||
restrictedEntityTypes = entitySettings.getEntityTypesWithPermissions();
|
||||
}
|
||||
|
||||
|
||||
|
@ -375,7 +375,7 @@ async function regenerateRoleNamesTable() {
|
|||
await knex.transaction(async tx => {
|
||||
await tx('generated_role_names').del();
|
||||
|
||||
const entityTypeIds = ['global', ...Object.keys(entitySettings.getEntityTypes())];
|
||||
const entityTypeIds = ['global', ...Object.keys(entitySettings.getEntityTypesWithPermissions())];
|
||||
|
||||
for (const entityTypeId of entityTypeIds) {
|
||||
const roles = config.roles[entityTypeId];
|
||||
|
@ -539,6 +539,14 @@ async function checkEntityPermission(context, entityTypeId, entityId, requiredOp
|
|||
});
|
||||
}
|
||||
|
||||
async function checkEntityPermissionTx(tx, context, entityTypeId, entityId, requiredOperations) {
|
||||
if (!entityId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return await _checkPermissionTx(tx, context, entityTypeId, entityId, requiredOperations);
|
||||
}
|
||||
|
||||
async function checkTypePermission(context, entityTypeId, requiredOperations) {
|
||||
return await knex.transaction(async tx => {
|
||||
return await _checkPermissionTx(tx, context, entityTypeId, null, requiredOperations);
|
||||
|
@ -626,6 +634,7 @@ module.exports.enforceEntityPermission = enforceEntityPermission;
|
|||
module.exports.enforceEntityPermissionTx = enforceEntityPermissionTx;
|
||||
module.exports.enforceTypePermission = enforceTypePermission;
|
||||
module.exports.enforceTypePermissionTx = enforceTypePermissionTx;
|
||||
module.exports.checkEntityPermissionTx = checkEntityPermissionTx;
|
||||
module.exports.checkEntityPermission = checkEntityPermission;
|
||||
module.exports.checkTypePermission = checkTypePermission;
|
||||
module.exports.enforceGlobalPermission = enforceGlobalPermission;
|
||||
|
|
|
@ -9,6 +9,7 @@ const namespaceHelpers = require('../lib/namespace-helpers');
|
|||
const shares = require('./shares');
|
||||
const reports = require('./reports');
|
||||
const files = require('./files');
|
||||
const dependencyHelpers = require('../lib/dependency-helpers');
|
||||
|
||||
const allowedKeys = new Set(['name', 'description', 'type', 'data', 'html', 'text', 'namespace']);
|
||||
|
||||
|
@ -97,17 +98,15 @@ async function remove(context, id) {
|
|||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'template', id, 'delete');
|
||||
|
||||
const depCampaigns = await tx('template_dep_campaigns')
|
||||
await dependencyHelpers.ensureNoDependencies(tx, context, id, [
|
||||
{
|
||||
entityTypeId: 'campaign',
|
||||
query: tx => tx('template_dep_campaigns')
|
||||
.where('template_dep_campaigns.template', id)
|
||||
.innerJoin('campaigns', 'template_dep_campaigns.campaign', 'campaigns.id')
|
||||
.limit(interoperableErrors.defaultNoOfDependenciesReported)
|
||||
.select(['campaigns.id', 'campaigns.name']);
|
||||
|
||||
if (depCampaigns.length > 0) {
|
||||
throw new interoperableErrors.DependencyPresentError('', {
|
||||
dependencies: depCampaigns.map(row => ({ entityTypeId: 'campaign', name: row.name, link: `campaigns/${row.id}` }))
|
||||
});
|
||||
.select(['campaigns.id', 'campaigns.name'])
|
||||
}
|
||||
]);
|
||||
|
||||
await files.removeAllTx(tx, context, 'template', 'file', id);
|
||||
|
||||
|
|
|
@ -4,14 +4,15 @@ const contextHelpers = require('../../../lib/context-helpers');
|
|||
const mosaicoTemplates = require('../../../shared/mosaico-templates');
|
||||
const {getGlobalNamespaceId} = require('../../../shared/namespaces');
|
||||
const {getAdminId} = require('../../../shared/users');
|
||||
const entityTypesAddNamespace = ['list', 'custom_form', 'template', 'campaign', 'report', 'report_template', 'user'];
|
||||
const shareableEntityTypes = ['list', 'custom_form', 'template', 'campaign', 'report', 'report_template', 'namespace', 'send_configuration', 'mosaico_template'];
|
||||
const { MailerType, getSystemSendConfigurationId, getSystemSendConfigurationCid } = require('../../../shared/send-configurations');
|
||||
const { enforce } = require('../../../lib/helpers');
|
||||
const { EntityVals: TriggerEntityVals, EventVals: TriggerEventVals } = require('../../../shared/triggers');
|
||||
const { SubscriptionSource } = require('../../../shared/lists');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const entityTypesAddNamespace = ['list', 'custom_form', 'template', 'campaign', 'report', 'report_template', 'user'];
|
||||
const shareableEntityTypes = ['list', 'custom_form', 'template', 'campaign', 'report', 'report_template', 'namespace', 'send_configuration', 'mosaico_template'];
|
||||
|
||||
const entityTypesWithFiles = {
|
||||
campaign: {
|
||||
file: 'files_campaign_file',
|
||||
|
@ -895,9 +896,9 @@ async function migrateCampaigns(knex) {
|
|||
|
||||
await knex.schema.createTable('campaign_lists', table => {
|
||||
table.increments('id').primary();
|
||||
table.integer('campaign').unsigned().notNullable().references('campaigns.id').onDelete('CASCADE');
|
||||
table.integer('list').unsigned().notNullable().references('lists.id').onDelete('CASCADE');
|
||||
table.integer('segment').unsigned().references('segments.id').onDelete('CASCADE');
|
||||
table.integer('campaign').unsigned().notNullable().references('campaigns.id');
|
||||
table.integer('list').unsigned().notNullable().references('lists.id');
|
||||
table.integer('segment').unsigned().references('segments.id');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('template_dep_campaigns', table => {
|
||||
|
|
|
@ -33,12 +33,6 @@ class LoopDetectedError extends InteroperableError {
|
|||
}
|
||||
}
|
||||
|
||||
class ChildDetectedError extends InteroperableError {
|
||||
constructor(msg, data) {
|
||||
super('ChildDetectedError', msg, data);
|
||||
}
|
||||
}
|
||||
|
||||
class DuplicitNameError extends InteroperableError {
|
||||
constructor(msg, data) {
|
||||
super('DuplicitNameError', msg, data);
|
||||
|
@ -106,8 +100,6 @@ class InvalidConfirmationForUnsubscriptionError extends InteroperableError {
|
|||
}
|
||||
}
|
||||
|
||||
const defaultNoOfDependenciesReported = 20;
|
||||
|
||||
class DependencyPresentError extends InteroperableError {
|
||||
constructor(msg, data) {
|
||||
super('DependencyPresentError', msg, data);
|
||||
|
@ -127,7 +119,6 @@ const errorTypes = {
|
|||
ChangedError,
|
||||
NotFoundError,
|
||||
LoopDetectedError,
|
||||
ChildDetectedError,
|
||||
DuplicitNameError,
|
||||
DuplicitEmailError,
|
||||
DuplicitKeyError,
|
||||
|
@ -156,6 +147,5 @@ function deserialize(errorObj) {
|
|||
}
|
||||
|
||||
module.exports = Object.assign({}, errorTypes, {
|
||||
defaultNoOfDependenciesReported,
|
||||
deserialize
|
||||
});
|
Loading…
Reference in a new issue