Editing of campaigns seems to work
This commit is contained in:
parent
b1c667d13d
commit
7b46c4b4b0
27 changed files with 335 additions and 130 deletions
|
@ -217,6 +217,7 @@ export default class CUD extends Component {
|
|||
|
||||
localValidateFormValues(state) {
|
||||
const t = this.props.t;
|
||||
const isEdit = !!this.props.entity;
|
||||
|
||||
if (!state.getIn(['name', 'value'])) {
|
||||
state.setIn(['name', 'error'], t('Name must not be empty'));
|
||||
|
@ -259,17 +260,17 @@ export default class CUD extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
if (sourceTypeKey === CampaignSource.TEMPLATE || sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
if (sourceTypeKey === CampaignSource.TEMPLATE || (!isEdit && sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE)) {
|
||||
if (!state.getIn(['data_sourceTemplate', 'value'])) {
|
||||
state.setIn(['data_sourceTemplate', 'error'], t('Template must be selected'));
|
||||
}
|
||||
|
||||
} else if (sourceTypeKey === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
} else if (!isEdit && sourceTypeKey === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
if (!state.getIn(['data_sourceCampaign', 'value'])) {
|
||||
state.setIn(['data_sourceCampaign', 'error'], t('Campaign must be selected'));
|
||||
}
|
||||
|
||||
} else if (sourceTypeKey === CampaignSource.CUSTOM) {
|
||||
} else if (!isEdit && sourceTypeKey === CampaignSource.CUSTOM) {
|
||||
// The type is used only in create form. In case of CUSTOM_FROM_TEMPLATE or CUSTOM_FROM_CAMPAIGN, it is determined by the source template, so no need to check it here
|
||||
const customTemplateTypeKey = state.getIn(['data_sourceCustom_type', 'value']);
|
||||
if (!customTemplateTypeKey) {
|
||||
|
@ -302,7 +303,7 @@ export default class CUD extends Component {
|
|||
let sendMethod, url;
|
||||
if (this.props.entity) {
|
||||
sendMethod = FormSendMethod.PUT;
|
||||
url = `rest/campaigns/${this.props.entity.id}`
|
||||
url = `rest/campaigns-settings/${this.props.entity.id}`;
|
||||
} else {
|
||||
sendMethod = FormSendMethod.POST;
|
||||
url = 'rest/campaigns'
|
||||
|
@ -312,6 +313,8 @@ export default class CUD extends Component {
|
|||
this.setFormStatusMessage('info', t('Saving ...'));
|
||||
|
||||
const submitResponse = await this.validateAndSendFormValuesToURL(sendMethod, url, data => {
|
||||
data.source = Number.parseInt(data.source);
|
||||
|
||||
if (!data.useSegmentation) {
|
||||
data.segment = null;
|
||||
}
|
||||
|
@ -460,18 +463,20 @@ export default class CUD extends Component {
|
|||
help = t('Selecting a template creates a campaign specific copy from it.');
|
||||
}
|
||||
|
||||
templateEdit = <TableSelect id="data_sourceTemplate" label={t('Template')} withHeader dropdown dataUrl='rest/templates-table' columns={templatesColumns} selectionLabelIndex={1} help={help}/>;
|
||||
// The "key" property here and in the TableSelect below is to tell React that these tables are different and should be rendered by different instances. Otherwise, React will use
|
||||
// only one instance, which fails because Table does not handle updates in "columns" property
|
||||
templateEdit = <TableSelect key="templateSelect" id="data_sourceTemplate" label={t('Template')} withHeader dropdown dataUrl='rest/templates-table' columns={templatesColumns} selectionLabelIndex={1} help={help}/>;
|
||||
|
||||
} else if (!isEdit && sourceTypeKey === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
const campaignsColumns = [
|
||||
{ data: 1, title: t('Name') },
|
||||
{ data: 2, title: t('Description') },
|
||||
{ data: 3, title: t('Type'), render: data => this.campaignTypes[data] },
|
||||
{ data: 7, title: t('Created'), render: data => moment(data).fromNow() },
|
||||
{ data: 8, title: t('Namespace') }
|
||||
{ data: 4, title: t('Created'), render: data => moment(data).fromNow() },
|
||||
{ data: 5, title: t('Namespace') }
|
||||
];
|
||||
|
||||
templateEdit = <TableSelect id="data_sourceCampaign" label={t('Campaign')} withHeader dropdown dataUrl='rest/campaigns-table' columns={campaignsColumns} selectionLabelIndex={1} help={t('Content of the selected campaign will be copied into this campaign.')}/>;
|
||||
templateEdit = <TableSelect key="campaignSelect" id="data_sourceCampaign" label={t('Campaign')} withHeader dropdown dataUrl='rest/campaigns-with-content-table' columns={campaignsColumns} selectionLabelIndex={1} help={t('Content of the selected campaign will be copied into this campaign.')}/>;
|
||||
|
||||
} else if (!isEdit && sourceTypeKey === CampaignSource.CUSTOM) {
|
||||
const customTemplateTypeKey = this.getFormValue('data_sourceCustom_type');
|
||||
|
|
|
@ -88,14 +88,8 @@ export default class CustomContent extends Component {
|
|||
const customTemplateTypeKey = this.getFormValue('data_sourceCustom_type');
|
||||
await this.templateTypes[customTemplateTypeKey].exportHTMLEditorData(this);
|
||||
|
||||
let sendMethod, url;
|
||||
if (this.props.entity) {
|
||||
sendMethod = FormSendMethod.PUT;
|
||||
url = `rest/campaigns/${this.props.entity.id}`
|
||||
} else {
|
||||
sendMethod = FormSendMethod.POST;
|
||||
url = 'rest/campaigns'
|
||||
}
|
||||
const sendMethod = FormSendMethod.PUT;
|
||||
const url = `rest/campaigns-content/${this.props.entity.id}`;
|
||||
|
||||
this.disableForm();
|
||||
this.setFormStatusMessage('info', t('Saving ...'));
|
||||
|
|
|
@ -20,7 +20,7 @@ function getMenus(t) {
|
|||
':campaignId([0-9]+)': {
|
||||
title: resolved => t('Campaign "{{name}}"', {name: resolved.campaign.name}),
|
||||
resolve: {
|
||||
campaign: params => `rest/campaigns/${params.campaignId}`
|
||||
campaign: params => `rest/campaigns-settings/${params.campaignId}`
|
||||
},
|
||||
link: params => `/campaigns/${params.campaignId}/edit`,
|
||||
navs: {
|
||||
|
@ -33,20 +33,23 @@ function getMenus(t) {
|
|||
content: {
|
||||
title: t('Content'),
|
||||
link: params => `/campaigns/${params.campaignId}/content`,
|
||||
resolve: {
|
||||
campaignContent: params => `rest/campaigns-content/${params.campaignId}`
|
||||
},
|
||||
visible: resolved => resolved.campaign.permissions.includes('edit') && (resolved.campaign.source === CampaignSource.CUSTOM || resolved.campaign.source === CampaignSource.CUSTOM_FROM_TEMPLATE || resolved.campaign.source === CampaignSource.CUSTOM_FROM_CAMPAIGN),
|
||||
panelRender: props => <Content entity={props.resolved.campaign} />
|
||||
panelRender: props => <Content entity={props.resolved.campaignContent} />
|
||||
},
|
||||
files: {
|
||||
title: t('Files'),
|
||||
link: params => `/campaigns/${params.campaignId}/files`,
|
||||
visible: resolved => resolved.campaign.permissions.includes('viewFiles') && (resolved.campaign.source === CampaignSource.CUSTOM || resolved.campaign.source === CampaignSource.CUSTOM_FROM_TEMPLATE || resolved.campaign.source === CampaignSource.CUSTOM_FROM_CAMPAIGN),
|
||||
panelRender: props => <Files title={t('Files')} entity={props.resolved.campaign} entityTypeId="campaign" entitySubTypeId="file" managePermission="manageFiles"/>
|
||||
panelRender: props => <Files title={t('Files')} help={t('These files are publicly available via HTTP so that they can be linked to from the content of the campaign.')} entity={props.resolved.campaign} entityTypeId="campaign" entitySubTypeId="file" managePermission="manageFiles"/>
|
||||
},
|
||||
attachments: {
|
||||
title: t('Attachments'),
|
||||
link: params => `/campaigns/${params.campaignId}/attachments`,
|
||||
visible: resolved => resolved.campaign.permissions.includes('viewAttachments'),
|
||||
panelRender: props => <Files title={t('Attachments')} entity={props.resolved.campaign} entityTypeId="campaign" entitySubTypeId="attachment" managePermission="manageAttachments"/>
|
||||
panelRender: props => <Files title={t('Attachments')} help={t('These files will be attached to the campaign emails as proper attachments. This means they count towards to resulting eventual size of the email.')} entity={props.resolved.campaign} entityTypeId="campaign" entitySubTypeId="attachment" managePermission="manageAttachments"/>
|
||||
},
|
||||
share: {
|
||||
title: t('Share'),
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
import React, {Component} from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {translate} from "react-i18next";
|
||||
import {requiresAuthenticatedUser} from "./page";
|
||||
import {
|
||||
requiresAuthenticatedUser,
|
||||
Title
|
||||
} from "./page";
|
||||
import {withErrorHandling} from "./error-handling";
|
||||
import {Table} from "./table";
|
||||
import Dropzone from "react-dropzone";
|
||||
|
@ -32,6 +35,7 @@ export default class Files extends Component {
|
|||
|
||||
static propTypes = {
|
||||
title: PropTypes.string,
|
||||
help: PropTypes.string,
|
||||
entity: PropTypes.object.isRequired,
|
||||
entityTypeId: PropTypes.string.isRequired,
|
||||
entitySubTypeId: PropTypes.string.isRequired,
|
||||
|
@ -151,6 +155,10 @@ export default class Files extends Component {
|
|||
{t('Are you sure you want to delete file "{{name}}"?', {name: this.state.fileToDeleteName})}
|
||||
</ModalDialog>
|
||||
|
||||
{this.props.title && <Title>{this.props.title}</Title>}
|
||||
|
||||
{this.props.help && <p>{this.props.help}</p>}
|
||||
|
||||
{
|
||||
this.props.entity.permissions.includes(this.props.managePermission) &&
|
||||
<Dropzone onDrop={::this.onDrop} className={styles.dropZone} activeClassName="dropZoneActive">
|
||||
|
|
|
@ -147,8 +147,8 @@ export class MosaicoSandbox extends Component {
|
|||
});
|
||||
|
||||
const config = {
|
||||
imgProcessorBackend: getTrustedUrl(`mosaico/img/${this.props.entityTypeId}/${this.props.entityId}`),
|
||||
emailProcessorBackend: getSandboxUrl('mosaico/dl/'),
|
||||
imgProcessorBackend: getTrustedUrl('mosaico/img'),
|
||||
emailProcessorBackend: getSandboxUrl('mosaico/dl'),
|
||||
fileuploadConfig: {
|
||||
url: getSandboxUrl(`mosaico/upload/${this.props.entityTypeId}/${this.props.entityId}`)
|
||||
},
|
||||
|
|
|
@ -20,6 +20,11 @@
|
|||
:global .ace_editor {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.buttonRow:last-child {
|
||||
// This is to move Save/Delete buttons a bit down
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.dayPickerWrapper {
|
||||
|
@ -27,7 +32,6 @@
|
|||
}
|
||||
|
||||
.buttonRow {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.buttonRow > * {
|
||||
|
|
|
@ -78,13 +78,20 @@ export default class List extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
if (perms.includes('manageFields')) {
|
||||
if (perms.includes('viewFields')) {
|
||||
actions.push({
|
||||
label: <Icon icon="th-list" title={t('Manage Fields')}/>,
|
||||
label: <Icon icon="th-list" title={t('Fields')}/>,
|
||||
link: `/lists/${data[0]}/fields`
|
||||
});
|
||||
}
|
||||
|
||||
if (perms.includes('viewSegments')) {
|
||||
actions.push({
|
||||
label: <Icon icon="tag" title={t('Segments')}/>,
|
||||
link: `/lists/${data[0]}/segments`
|
||||
});
|
||||
}
|
||||
|
||||
if (perms.includes('share')) {
|
||||
actions.push({
|
||||
label: <Icon icon="share-alt" title={t('Share')}/>,
|
||||
|
|
|
@ -40,18 +40,28 @@ export default class List extends Component {
|
|||
{ data: 2, title: t('Type'), render: data => this.fieldTypes[data].label, sortable: false, searchable: false },
|
||||
{ data: 3, title: t('Merge Tag') },
|
||||
{
|
||||
actions: data => [{
|
||||
actions: data => {
|
||||
const actions = [];
|
||||
|
||||
if (this.props.list.permissions.includes('manageFields')) {
|
||||
actions.push({
|
||||
label: <Icon icon="edit" title={t('Edit')}/>,
|
||||
link: `/lists/${this.props.list.id}/fields/${data[0]}/edit`
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.props.list.permissions.includes('manageFields') &&
|
||||
<Toolbar>
|
||||
<NavButton linkTo={`/lists/${this.props.list.id}/fields/create`} className="btn-primary" icon="plus" label={t('Create Field')}/>
|
||||
</Toolbar>
|
||||
}
|
||||
|
||||
<Title>{t('Fields')}</Title>
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ function getMenus(t) {
|
|||
fields: {
|
||||
title: t('Fields'),
|
||||
link: params => `/lists/${params.listId}/fields/`,
|
||||
visible: resolved => resolved.list.permissions.includes('manageFields'),
|
||||
visible: resolved => resolved.list.permissions.includes('viewFields'),
|
||||
panelRender: props => <FieldsList list={props.resolved.list} />,
|
||||
children: {
|
||||
':fieldId([0-9]+)': {
|
||||
|
@ -100,7 +100,7 @@ function getMenus(t) {
|
|||
segments: {
|
||||
title: t('Segments'),
|
||||
link: params => `/lists/${params.listId}/segments`,
|
||||
visible: resolved => resolved.list.permissions.includes('manageSegments'),
|
||||
visible: resolved => resolved.list.permissions.includes('viewSegments'),
|
||||
panelRender: props => <SegmentsList list={props.resolved.list} />,
|
||||
children: {
|
||||
':segmentId([0-9]+)': {
|
||||
|
|
|
@ -32,18 +32,28 @@ export default class List extends Component {
|
|||
const columns = [
|
||||
{ data: 1, title: t('Name') },
|
||||
{
|
||||
actions: data => [{
|
||||
actions: data => {
|
||||
const actions = [];
|
||||
|
||||
if (this.props.list.permissions.includes('manageSegments')) {
|
||||
actions.push({
|
||||
label: <Icon icon="edit" title={t('Edit')}/>,
|
||||
link: `/lists/${this.props.list.id}/segments/${data[0]}/edit`
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.props.list.permissions.includes('manageSegments') &&
|
||||
<Toolbar>
|
||||
<NavButton linkTo={`/lists/${this.props.list.id}/segments/create`} className="btn-primary" icon="plus" label={t('Create Segment')}/>
|
||||
</Toolbar>
|
||||
}
|
||||
|
||||
<Title>{t('Segment')}</Title>
|
||||
|
||||
|
|
|
@ -37,8 +37,8 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
|
|||
const initVals = templateTypes[templateType].initData();
|
||||
|
||||
for (const key in initVals) {
|
||||
if (!mutState.hasIn([prefix + key])) {
|
||||
mutState.setIn([prefix + key, 'value'], initVals[key]);
|
||||
if (!mutState.hasIn([key])) {
|
||||
mutState.setIn([key, 'value'], initVals[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -97,6 +97,7 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
|
|||
};
|
||||
},
|
||||
beforeSave: data => {
|
||||
console.log(data);
|
||||
data[prefix + 'data'] = {
|
||||
mosaicoTemplate: data[prefix + 'mosaicoTemplate'],
|
||||
metadata: data[prefix + 'mosaicoData'].metadata,
|
||||
|
@ -150,8 +151,8 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
|
|||
});
|
||||
},
|
||||
initData: () => ({
|
||||
mosaicoFsTemplate: mailtrainConfig.mosaico.fsTemplates[0][0],
|
||||
mosaicoData: {}
|
||||
[prefix + 'mosaicoFsTemplate']: mailtrainConfig.mosaico.fsTemplates[0][0],
|
||||
[prefix + 'mosaicoData']: {}
|
||||
}),
|
||||
afterLoad: data => {
|
||||
data[prefix + 'mosaicoFsTemplate'] = data[prefix + 'data'].mosaicoFsTemplate;
|
||||
|
@ -169,7 +170,7 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
|
|||
clearBeforeSave(data);
|
||||
},
|
||||
afterTypeChange: mutState => {
|
||||
initFieldsIfMissing(mutState, 'mosaico');
|
||||
initFieldsIfMissing(mutState, 'mosaicoWithFsTemplate');
|
||||
},
|
||||
validate: state => {}
|
||||
};
|
||||
|
|
|
@ -34,7 +34,7 @@ function getMenus(t) {
|
|||
title: t('Files'),
|
||||
link: params => `/templates/${params.templateId}/files`,
|
||||
visible: resolved => resolved.template.permissions.includes('viewFiles'),
|
||||
panelRender: props => <Files title={t('Files')} entity={props.resolved.template} entityTypeId="template" entitySubTypeId="file" managePermission="manageFiles"/>
|
||||
panelRender: props => <Files title={t('Files')} help={t('These files are publicly available via HTTP so that they can be linked to from the content of the campaign.')} entity={props.resolved.template} entityTypeId="template" entitySubTypeId="file" managePermission="manageFiles"/>
|
||||
},
|
||||
share: {
|
||||
title: t('Share'),
|
||||
|
@ -70,13 +70,13 @@ function getMenus(t) {
|
|||
title: t('Files'),
|
||||
link: params => `/templates/mosaico/${params.mosaiceTemplateId}/files`,
|
||||
visible: resolved => resolved.mosaicoTemplate.permissions.includes('viewFiles'),
|
||||
panelRender: props => <Files title={t('Files')} entity={props.resolved.mosaicoTemplate} entityTypeId="mosaicoTemplate" entitySubTypeId="file" managePermission="manageFiles" />
|
||||
panelRender: props => <Files title={t('Files')} help={t('These files are publicly available via HTTP so that they can be linked to from the Mosaico template.')} entity={props.resolved.mosaicoTemplate} entityTypeId="mosaicoTemplate" entitySubTypeId="file" managePermission="manageFiles" />
|
||||
},
|
||||
blocks: {
|
||||
title: t('Block thumbnails'),
|
||||
link: params => `/templates/mosaico/${params.mosaiceTemplateId}/blocks`,
|
||||
visible: resolved => resolved.mosaicoTemplate.permissions.includes('viewFiles'),
|
||||
panelRender: props => <Files title={t('Block thumbnails')} entity={props.resolved.mosaicoTemplate} entityTypeId="mosaicoTemplate" entitySubTypeId="block" managePermission="manageFiles" />
|
||||
panelRender: props => <Files title={t('Block thumbnails')} help={t('These files will be used by Mosaico to search for block thumbnails (the "edres" directory). Place here one file per block type that you have defined in the Mosaico template. Each file must have the same name as the block id. The file will be used as the thumbnail of the corresponding block.')}entity={props.resolved.mosaicoTemplate} entityTypeId="mosaicoTemplate" entitySubTypeId="block" managePermission="manageFiles" />
|
||||
},
|
||||
share: {
|
||||
title: t('Share'),
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
const passport = require('./passport');
|
||||
const config = require('config');
|
||||
const permissions = require('./permissions');
|
||||
const forms = require('../models/forms');
|
||||
const shares = require('../models/shares');
|
||||
const urls = require('./urls');
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const knex = require('../lib/knex');
|
||||
const permissions = require('../lib/permissions');
|
||||
const entitySettings = require('./entity-settings');
|
||||
|
||||
async function ajaxListTx(tx, params, queryFun, columns, options) {
|
||||
options = options || {};
|
||||
|
@ -109,7 +109,7 @@ async function ajaxListWithPermissionsTx(tx, context, fetchSpecs, params, queryF
|
|||
|
||||
const permCols = [];
|
||||
for (const fetchSpec of fetchSpecs) {
|
||||
const entityType = permissions.getEntityType(fetchSpec.entityTypeId);
|
||||
const entityType = entitySettings.getEntityType(fetchSpec.entityTypeId);
|
||||
permCols.push({
|
||||
name: `permissions_${fetchSpec.entityTypeId}`,
|
||||
query: builder => builder
|
||||
|
@ -128,7 +128,7 @@ async function ajaxListWithPermissionsTx(tx, context, fetchSpecs, params, queryF
|
|||
let query = queryFun(builder);
|
||||
|
||||
for (const fetchSpec of fetchSpecs) {
|
||||
const entityType = permissions.getEntityType(fetchSpec.entityTypeId);
|
||||
const entityType = entitySettings.getEntityType(fetchSpec.entityTypeId);
|
||||
|
||||
if (fetchSpec.requiredOperations) {
|
||||
query = query.innerJoin(
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
'use strict';
|
||||
|
||||
const ReplacementBehavior = {
|
||||
NONE: 1,
|
||||
REPLACE: 2,
|
||||
RENAME: 3
|
||||
};
|
||||
|
||||
const entityTypes = {
|
||||
namespace: {
|
||||
entitiesTable: 'namespaces',
|
||||
|
@ -26,14 +32,16 @@ const entityTypes = {
|
|||
permissions: {
|
||||
view: 'viewFiles',
|
||||
manage: 'manageFiles'
|
||||
}
|
||||
},
|
||||
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
||||
},
|
||||
attachment: {
|
||||
table: 'files_campaign_attachment',
|
||||
permissions: {
|
||||
view: 'viewAttachments',
|
||||
manage: 'manageAttachments'
|
||||
}
|
||||
},
|
||||
defaultReplacementBehavior: ReplacementBehavior.NONE
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -47,7 +55,8 @@ const entityTypes = {
|
|||
permissions: {
|
||||
view: 'viewFiles',
|
||||
manage: 'manageFiles'
|
||||
}
|
||||
},
|
||||
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -76,14 +85,16 @@ const entityTypes = {
|
|||
permissions: {
|
||||
view: 'viewFiles',
|
||||
manage: 'manageFiles'
|
||||
}
|
||||
},
|
||||
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
||||
},
|
||||
block: {
|
||||
table: 'files_mosaico_template_block',
|
||||
permissions: {
|
||||
view: 'viewFiles',
|
||||
manage: 'manageFiles'
|
||||
}
|
||||
},
|
||||
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -105,5 +116,6 @@ function getEntityType(entityTypeId) {
|
|||
|
||||
module.exports = {
|
||||
getEntityTypes,
|
||||
getEntityType
|
||||
getEntityType,
|
||||
ReplacementBehavior
|
||||
}
|
|
@ -10,7 +10,7 @@ const multer = require('multer')({
|
|||
dest: uploadedFilesDir
|
||||
});
|
||||
|
||||
function installUploadHandler(router, url, replacementBehavior, type = null, subType = null) {
|
||||
function installUploadHandler(router, url, replacementBehavior, type, subType) {
|
||||
router.postAsync(url, passport.loggedIn, multer.array('files[]'), async (req, res) => {
|
||||
return res.json(await files.createFiles(req.context, type || req.params.type, subType || req.params.subType, req.params.entityId, req.files, replacementBehavior));
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const knex = require('./knex');
|
||||
const { enforce } = require('./helpers');
|
||||
const shares = require('../models/shares');
|
||||
const interoperableErrors = require('../shared/interoperable-errors');
|
||||
|
||||
async function validateEntity(tx, entity) {
|
||||
|
|
|
@ -19,8 +19,32 @@ const allowedKeysCommon = ['name', 'description', 'list', 'segment', 'namespace'
|
|||
const allowedKeysCreate = new Set(['type', 'source', ...allowedKeysCommon]);
|
||||
const allowedKeysUpdate = new Set([...allowedKeysCommon]);
|
||||
|
||||
function hash(entity) {
|
||||
return hasher.hash(filterObject(entity, allowedKeysUpdate));
|
||||
const Content = {
|
||||
ALL: 0,
|
||||
WITHOUT_SOURCE_CUSTOM: 1,
|
||||
ONLY_SOURCE_CUSTOM: 2
|
||||
};
|
||||
|
||||
function hash(entity, content) {
|
||||
let filteredEntity;
|
||||
|
||||
if (content === Content.ALL) {
|
||||
filteredEntity = filterObject(entity, allowedKeysUpdate);
|
||||
|
||||
} else if (content === Content.WITHOUT_SOURCE_CUSTOM) {
|
||||
filteredEntity = filterObject(entity, allowedKeysUpdate);
|
||||
filteredEntity.data = {...filteredEntity.data};
|
||||
delete filteredEntity.data.sourceCustom;
|
||||
|
||||
} else if (content === Content.ONLY_SOURCE_CUSTOM) {
|
||||
filteredEntity = {
|
||||
data: {
|
||||
sourceCustom: entity.data.sourceCustom
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return hasher.hash(filteredEntity);
|
||||
}
|
||||
|
||||
async function listDTAjax(context, params) {
|
||||
|
@ -34,26 +58,52 @@ async function listDTAjax(context, params) {
|
|||
);
|
||||
}
|
||||
|
||||
async function getByIdTx(tx, context, id, withPermissions = true) {
|
||||
async function listWithContentDTAjax(context, params) {
|
||||
return await dtHelpers.ajaxListWithPermissions(
|
||||
context,
|
||||
[{ entityTypeId: 'campaign', requiredOperations: ['view'] }],
|
||||
params,
|
||||
builder => builder.from('campaigns')
|
||||
.innerJoin('namespaces', 'namespaces.id', 'campaigns.namespace')
|
||||
.whereIn('campaigns.source', [CampaignSource.CUSTOM, CampaignSource.CUSTOM_FROM_TEMPLATE, CampaignSource.CUSTOM_FROM_CAMPAIGN]),
|
||||
['campaigns.id', 'campaigns.name', 'campaigns.description', 'campaigns.type', 'campaigns.created', 'namespaces.name']
|
||||
);
|
||||
}
|
||||
|
||||
async function getByIdTx(tx, context, id, withPermissions = true, content = Content.ALL) {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'campaign', id, 'view');
|
||||
const entity = await tx('campaigns').where('id', id).first();
|
||||
let entity = await tx('campaigns').where('id', id).first();
|
||||
|
||||
entity.data = JSON.parse(entity.data);
|
||||
|
||||
if (content === Content.WITHOUT_SOURCE_CUSTOM) {
|
||||
delete entity.data.sourceCustom;
|
||||
|
||||
} else if (content === Content.ONLY_SOURCE_CUSTOM) {
|
||||
entity = {
|
||||
id: entity.id,
|
||||
|
||||
data: {
|
||||
sourceCustom: entity.data.sourceCustom
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (withPermissions) {
|
||||
entity.permissions = await shares.getPermissionsTx(tx, context, 'campaign', id);
|
||||
}
|
||||
|
||||
entity.data = JSON.parse(entity.data);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
async function getById(context, id, withPermissions = true) {
|
||||
async function getById(context, id, withPermissions = true, content = Content.ALL) {
|
||||
return await knex.transaction(async tx => {
|
||||
return await getByIdTx(tx, context, id, withPermissions);
|
||||
return await getByIdTx(tx, context, id, withPermissions, content);
|
||||
});
|
||||
}
|
||||
|
||||
async function _validateAndPreprocess(tx, context, entity, isCreate) {
|
||||
async function _validateAndPreprocess(tx, context, entity, isCreate, content) {
|
||||
if (content === Content.ALL || content === Content.WITHOUT_SOURCE_CUSTOM) {
|
||||
await namespaceHelpers.validateEntity(tx, entity);
|
||||
|
||||
if (isCreate) {
|
||||
|
@ -62,9 +112,10 @@ async function _validateAndPreprocess(tx, context, entity, isCreate) {
|
|||
if (entity.source === CampaignSource.TEMPLATE || entity.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'template', entity.data.sourceTemplate, 'view');
|
||||
}
|
||||
}
|
||||
|
||||
enforce(Number.isInteger(entity.source));
|
||||
enforce(entity.source >= CampaignSource.MIN && entity.source <= CampaignSource.MAX, 'Unknown campaign source');
|
||||
}
|
||||
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', entity.list, 'view');
|
||||
|
||||
|
@ -73,9 +124,37 @@ async function _validateAndPreprocess(tx, context, entity, isCreate) {
|
|||
await segments.getByIdTx(tx, context, entity.list, entity.segment);
|
||||
}
|
||||
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'send_configuration', entity.send_configuration, 'viewPublic');
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'sendConfiguration', entity.send_configuration, 'viewPublic');
|
||||
}
|
||||
}
|
||||
|
||||
entity.data = JSON.stringify(entity.data);
|
||||
function convertFileURLs(sourceCustom, fromEntityType, fromEntityId, toEntityType, toEntityId) {
|
||||
|
||||
function convertText(text) {
|
||||
if (text) {
|
||||
const fromUrl = `/files/${fromEntityType}/file/${fromEntityId}`;
|
||||
const toUrl = `/files/${toEntityType}/file/${toEntityId}`;
|
||||
|
||||
const encodedFromUrl = encodeURIComponent(fromUrl);
|
||||
const encodedToUrl = encodeURIComponent(toUrl);
|
||||
|
||||
text = text.split('[URL_BASE]' + fromUrl).join('[URL_BASE]' + toUrl);
|
||||
text = text.split('[SANDBOX_URL_BASE]' + fromUrl).join('[SANDBOX_URL_BASE]' + toUrl);
|
||||
text = text.split('[ENCODED_URL_BASE]' + encodedFromUrl).join('[ENCODED_URL_BASE]' + encodedToUrl);
|
||||
text = text.split('[ENCODED_SANDBOX_URL_BASE]' + encodedFromUrl).join('[ENCODED_SANDBOX_URL_BASE]' + encodedToUrl);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
sourceCustom.html = convertText(sourceCustom.html);
|
||||
sourceCustom.text = convertText(sourceCustom.text);
|
||||
|
||||
if (sourceCustom.type === 'mosaico' || sourceCustom.type === 'mosaicoWithFsTemplate') {
|
||||
sourceCustom.data.model = convertText(sourceCustom.data.model);
|
||||
sourceCustom.data.model = convertText(sourceCustom.data.model);
|
||||
sourceCustom.data.metadata = convertText(sourceCustom.data.metadata);
|
||||
}
|
||||
}
|
||||
|
||||
async function create(context, entity) {
|
||||
|
@ -97,6 +176,7 @@ async function create(context, entity) {
|
|||
html: template.html,
|
||||
text: template.text
|
||||
};
|
||||
|
||||
} else if (entity.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
copyFilesFrom = {
|
||||
entityType: 'campaign',
|
||||
|
@ -104,15 +184,19 @@ async function create(context, entity) {
|
|||
};
|
||||
|
||||
const sourceCampaign = await getByIdTx(tx, context, entity.data.sourceCampaign, false);
|
||||
enforce(sourceCampaign.source === CampaignSource.CUSTOM || sourceCampaign.source === CampaignSource.CUSTOM_FROM_TEMPLATE || sourceCampaign.source === CampaignSource.CUSTOM_FROM_CAMPAIGN, 'Incorrect source type of the source campaign.');
|
||||
|
||||
entity.data.sourceCustom = sourceCampaign.data.sourceCustom;
|
||||
}
|
||||
|
||||
await _validateAndPreprocess(tx, context, entity, true);
|
||||
await _validateAndPreprocess(tx, context, entity, true, Content.ALL);
|
||||
|
||||
const filteredEntity = filterObject(entity, allowedKeysCreate);
|
||||
filteredEntity.cid = shortid.generate();
|
||||
|
||||
const data = filteredEntity.data;
|
||||
|
||||
filteredEntity.data = JSON.stringify(filteredEntity.data);
|
||||
const ids = await tx('campaigns').insert(filteredEntity);
|
||||
const id = ids[0];
|
||||
|
||||
|
@ -150,14 +234,20 @@ async function create(context, entity) {
|
|||
await shares.rebuildPermissionsTx(tx, { entityTypeId: 'campaign', entityId: id });
|
||||
|
||||
if (copyFilesFrom) {
|
||||
await files.copyAllTx(tx, context, copyFilesFrom.entityType, copyFilesFrom.entityId, 'campaign', id);
|
||||
await files.copyAllTx(tx, context, copyFilesFrom.entityType, 'file', copyFilesFrom.entityId, 'campaign', 'file', id);
|
||||
|
||||
convertFileURLs(data.sourceCustom, copyFilesFrom.entityType, copyFilesFrom.entityId, 'campaign', id);
|
||||
await tx('campaigns')
|
||||
.update({
|
||||
data: JSON.stringify(data)
|
||||
}).where('id', id);
|
||||
}
|
||||
|
||||
return id;
|
||||
});
|
||||
}
|
||||
|
||||
async function updateWithConsistencyCheck(context, entity) {
|
||||
async function updateWithConsistencyCheck(context, entity, content) {
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'campaign', entity.id, 'edit');
|
||||
|
||||
|
@ -167,16 +257,31 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
}
|
||||
|
||||
existing.data = JSON.parse(existing.data);
|
||||
const existingHash = hash(existing);
|
||||
const existingHash = hash(existing, content);
|
||||
if (existingHash !== entity.originalHash) {
|
||||
throw new interoperableErrors.ChangedError();
|
||||
}
|
||||
|
||||
await _validateAndPreprocess(tx, context, entity, false);
|
||||
await _validateAndPreprocess(tx, context, entity, false, content);
|
||||
|
||||
let filteredEntity = filterObject(entity, allowedKeysUpdate);
|
||||
if (content === Content.ALL) {
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'campaign', 'createCampaign', 'delete');
|
||||
|
||||
await tx('campaigns').where('id', entity.id).update(filterObject(entity, allowedKeysUpdate));
|
||||
} else if (content === Content.WITHOUT_SOURCE_CUSTOM) {
|
||||
filteredEntity.data.sourceCustom = existing.data.sourceCustom;
|
||||
await namespaceHelpers.validateMove(context, filteredEntity, existing, 'campaign', 'createCampaign', 'delete');
|
||||
|
||||
} else if (content === Content.ONLY_SOURCE_CUSTOM) {
|
||||
const data = existing.data;
|
||||
data.sourceCustom = filteredEntity.data.sourceCustom;
|
||||
filteredEntity = {
|
||||
data
|
||||
};
|
||||
}
|
||||
|
||||
filteredEntity.data = JSON.stringify(filteredEntity.data);
|
||||
await tx('campaigns').where('id', entity.id).update(filteredEntity);
|
||||
|
||||
await shares.rebuildPermissionsTx(tx, { entityTypeId: 'campaign', entityId: entity.id });
|
||||
});
|
||||
|
@ -196,8 +301,10 @@ async function remove(context, id) {
|
|||
|
||||
|
||||
module.exports = {
|
||||
Content,
|
||||
hash,
|
||||
listDTAjax,
|
||||
listWithContentDTAjax,
|
||||
getByIdTx,
|
||||
getById,
|
||||
create,
|
||||
|
|
|
@ -7,22 +7,18 @@ const shares = require('./shares');
|
|||
const fs = require('fs-extra-promise');
|
||||
const path = require('path');
|
||||
const interoperableErrors = require('../shared/interoperable-errors');
|
||||
const permissions = require('../lib/permissions');
|
||||
const entitySettings = require('../lib/entity-settings');
|
||||
const {getTrustedUrl} = require('../lib/urls');
|
||||
|
||||
const crypto = require('crypto');
|
||||
const bluebird = require('bluebird');
|
||||
const cryptoPseudoRandomBytes = bluebird.promisify(crypto.pseudoRandomBytes);
|
||||
|
||||
const entityTypes = permissions.getEntityTypes();
|
||||
const entityTypes = entitySettings.getEntityTypes();
|
||||
|
||||
const filesDir = path.join(__dirname, '..', 'files');
|
||||
|
||||
const ReplacementBehavior = {
|
||||
NONE: 0,
|
||||
REPLACE: 1,
|
||||
RENAME: 2
|
||||
};
|
||||
const ReplacementBehavior = entitySettings.ReplacementBehavior;
|
||||
|
||||
function enforceTypePermitted(type, subType) {
|
||||
enforce(type in entityTypes && entityTypes[type].files && entityTypes[type].files[subType]);
|
||||
|
@ -108,10 +104,26 @@ async function getFileByFilename(context, type, subType, entityId, name) {
|
|||
return await _getFileBy(context, type, subType, entityId, 'filename', name)
|
||||
}
|
||||
|
||||
async function getFileByUrl(context, type, subType, entityId, url) {
|
||||
const urlPrefix = getTrustedUrl(`files/${type}/${subType}/${entityId}/`, context);
|
||||
async function getFileByUrl(context, url) {
|
||||
const urlPrefix = getTrustedUrl('files/', context);
|
||||
if (url.startsWith(urlPrefix)) {
|
||||
const name = url.substring(urlPrefix.length);
|
||||
const path = url.substring(urlPrefix.length);
|
||||
const pathElem = path.split('/');
|
||||
|
||||
if (pathElem.length !== 4) {
|
||||
throw new interoperableErrors.NotFoundError();
|
||||
}
|
||||
|
||||
const type = pathElem[0];
|
||||
const subType = pathElem[1];
|
||||
const entityId = Number.parseInt(pathElem[2]);
|
||||
|
||||
if (Number.isNaN(entityId)) {
|
||||
throw new interoperableErrors.NotFoundError();
|
||||
}
|
||||
|
||||
const name = pathElem[3];
|
||||
|
||||
return await getFileByFilename(context, type, subType, entityId, name);
|
||||
} else {
|
||||
throw new interoperableErrors.NotFoundError();
|
||||
|
@ -126,6 +138,10 @@ async function createFiles(context, type, subType, entityId, files, replacementB
|
|||
return {uploaded: 0};
|
||||
}
|
||||
|
||||
if (!replacementBehavior) {
|
||||
replacementBehavior = entityTypes[type].files[subType].defaultReplacementBehavior;
|
||||
}
|
||||
|
||||
const fileEntities = [];
|
||||
const filesToMove = [];
|
||||
const ignoredFiles = [];
|
||||
|
@ -280,8 +296,10 @@ async function copyAllTx(tx, context, fromType, fromSubType, fromEntityId, toTyp
|
|||
row.entity = toEntityId;
|
||||
}
|
||||
|
||||
if (rows.length > 0) {
|
||||
await tx(getFilesTable(toType, toSubType)).insert(rows);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -5,7 +5,7 @@ const hasher = require('node-object-hash')();
|
|||
const { enforce, filterObject } = require('../lib/helpers');
|
||||
const interoperableErrors = require('../shared/interoperable-errors');
|
||||
const shares = require('./shares');
|
||||
const permissions = require('../lib/permissions');
|
||||
const entitySettings = require('../lib/entity-settings');
|
||||
const namespaceHelpers = require('../lib/namespace-helpers');
|
||||
|
||||
|
||||
|
@ -14,7 +14,7 @@ const allowedKeys = new Set(['name', 'description', 'namespace']);
|
|||
async function listTree(context) {
|
||||
// FIXME - process permissions
|
||||
|
||||
const entityType = permissions.getEntityType('namespace');
|
||||
const entityType = entitySettings.getEntityType('namespace');
|
||||
|
||||
// This builds a forest of namespaces that contains only those namespace that the user has access to
|
||||
// This goes in three steps: 1) tree with all namespaces is built with parent-children links, 2) the namespaces that are not accessible
|
||||
|
|
|
@ -4,7 +4,7 @@ const knex = require('../lib/knex');
|
|||
const config = require('config');
|
||||
const { enforce } = require('../lib/helpers');
|
||||
const dtHelpers = require('../lib/dt-helpers');
|
||||
const permissions = require('../lib/permissions');
|
||||
const entitySettings = require('../lib/entity-settings');
|
||||
const interoperableErrors = require('../shared/interoperable-errors');
|
||||
const log = require('npmlog');
|
||||
const {getGlobalNamespaceId} = require('../shared/namespaces');
|
||||
|
@ -15,7 +15,7 @@ const {getGlobalNamespaceId} = require('../shared/namespaces');
|
|||
|
||||
async function listByEntityDTAjax(context, entityTypeId, entityId, params) {
|
||||
return await knex.transaction(async (tx) => {
|
||||
const entityType = permissions.getEntityType(entityTypeId);
|
||||
const entityType = entitySettings.getEntityType(entityTypeId);
|
||||
await enforceEntityPermissionTx(tx, context, entityTypeId, entityId, 'share');
|
||||
|
||||
return await dtHelpers.ajaxListTx(
|
||||
|
@ -41,7 +41,7 @@ async function listByUserDTAjax(context, entityTypeId, userId, params) {
|
|||
|
||||
await enforceEntityPermissionTx(tx, context, 'namespace', user.namespace, 'manageUsers');
|
||||
|
||||
const entityType = permissions.getEntityType(entityTypeId);
|
||||
const entityType = entitySettings.getEntityType(entityTypeId);
|
||||
|
||||
return await dtHelpers.ajaxListWithPermissionsTx(
|
||||
tx,
|
||||
|
@ -61,7 +61,7 @@ async function listByUserDTAjax(context, entityTypeId, userId, params) {
|
|||
|
||||
async function listUnassignedUsersDTAjax(context, entityTypeId, entityId, params) {
|
||||
return await knex.transaction(async (tx) => {
|
||||
const entityType = permissions.getEntityType(entityTypeId);
|
||||
const entityType = entitySettings.getEntityType(entityTypeId);
|
||||
|
||||
await enforceEntityPermissionTx(tx, context, entityTypeId, entityId, 'share');
|
||||
|
||||
|
@ -93,7 +93,7 @@ async function listRolesDTAjax(entityTypeId, params) {
|
|||
}
|
||||
|
||||
async function assign(context, entityTypeId, entityId, userId, role) {
|
||||
const entityType = permissions.getEntityType(entityTypeId);
|
||||
const entityType = entitySettings.getEntityType(entityTypeId);
|
||||
|
||||
await knex.transaction(async tx => {
|
||||
await enforceEntityPermissionTx(tx, context, entityTypeId, entityId, 'share');
|
||||
|
@ -129,17 +129,17 @@ async function assign(context, entityTypeId, entityId, userId, role) {
|
|||
async function rebuildPermissionsTx(tx, restriction) {
|
||||
restriction = restriction || {};
|
||||
|
||||
const namespaceEntityType = permissions.getEntityType('namespace');
|
||||
const namespaceEntityType = entitySettings.getEntityType('namespace');
|
||||
|
||||
// Collect entity types we care about
|
||||
let restrictedEntityTypes;
|
||||
if (restriction.entityTypeId) {
|
||||
const entityType = permissions.getEntityType(restriction.entityTypeId);
|
||||
const entityType = entitySettings.getEntityType(restriction.entityTypeId);
|
||||
restrictedEntityTypes = {
|
||||
[restriction.entityTypeId]: entityType
|
||||
};
|
||||
} else {
|
||||
restrictedEntityTypes = permissions.getEntityTypes();
|
||||
restrictedEntityTypes = entitySettings.getEntityTypes();
|
||||
}
|
||||
|
||||
|
||||
|
@ -374,7 +374,7 @@ async function regenerateRoleNamesTable() {
|
|||
await knex.transaction(async tx => {
|
||||
await tx('generated_role_names').del();
|
||||
|
||||
const entityTypeIds = ['global', ...Object.keys(permissions.getEntityTypes())];
|
||||
const entityTypeIds = ['global', ...Object.keys(entitySettings.getEntityTypes())];
|
||||
|
||||
for (const entityTypeId of entityTypeIds) {
|
||||
const roles = config.roles[entityTypeId];
|
||||
|
@ -397,7 +397,7 @@ function throwPermissionDenied() {
|
|||
}
|
||||
|
||||
async function removeDefaultShares(tx, user) {
|
||||
const namespaceEntityType = permissions.getEntityType('namespace');
|
||||
const namespaceEntityType = entitySettings.getEntityType('namespace');
|
||||
|
||||
const roleConf = config.roles.global[user.role];
|
||||
|
||||
|
@ -467,7 +467,7 @@ async function _checkPermissionTx(tx, context, entityTypeId, entityId, requiredO
|
|||
return false;
|
||||
}
|
||||
|
||||
const entityType = permissions.getEntityType(entityTypeId);
|
||||
const entityType = entitySettings.getEntityType(entityTypeId);
|
||||
|
||||
if (typeof requiredOperations === 'string') {
|
||||
requiredOperations = [ requiredOperations ];
|
||||
|
@ -603,7 +603,7 @@ async function getPermissionsTx(tx, context, entityTypeId, entityId) {
|
|||
|
||||
enforce(!context.user.admin, 'getPermissions is not supposed to be called by assumed admin');
|
||||
|
||||
const entityType = permissions.getEntityType(entityTypeId);
|
||||
const entityType = entitySettings.getEntityType(entityTypeId);
|
||||
|
||||
const rows = await tx(entityType.permissionsTable)
|
||||
.select('operation')
|
||||
|
|
|
@ -201,7 +201,7 @@ function getRouter(trusted) {
|
|||
});
|
||||
|
||||
} else {
|
||||
router.getAsync('/img/:type/:entityId', async (req, res) => {
|
||||
router.getAsync('/img', async (req, res) => {
|
||||
const method = req.query.method;
|
||||
const params = req.query.params;
|
||||
let [width, height] = params.split(',');
|
||||
|
@ -225,7 +225,7 @@ function getRouter(trusted) {
|
|||
if (url.startsWith(mosaicoLegacyUrlPrefix)) {
|
||||
filePath = path.join(__dirname, '..', 'client', 'public' , 'mosaico', 'uploads', url.substring(mosaicoLegacyUrlPrefix.length));
|
||||
} else {
|
||||
const file = await files.getFileByUrl(contextHelpers.getAdminContext(), req.params.type, 'file', req.params.entityId, url);
|
||||
const file = await files.getFileByUrl(contextHelpers.getAdminContext(), url);
|
||||
filePath = file.path;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,9 +10,19 @@ router.postAsync('/campaigns-table', passport.loggedIn, async (req, res) => {
|
|||
return res.json(await campaigns.listDTAjax(req.context, req.body));
|
||||
});
|
||||
|
||||
router.getAsync('/campaigns/:campaignId', passport.loggedIn, async (req, res) => {
|
||||
const campaign = await campaigns.getById(req.context, req.params.campaignId);
|
||||
campaign.hash = campaigns.hash(campaign);
|
||||
router.postAsync('/campaigns-with-content-table', passport.loggedIn, async (req, res) => {
|
||||
return res.json(await campaigns.listWithContentDTAjax(req.context, req.body));
|
||||
});
|
||||
|
||||
router.getAsync('/campaigns-settings/:campaignId', passport.loggedIn, async (req, res) => {
|
||||
const campaign = await campaigns.getById(req.context, req.params.campaignId, true, campaigns.Content.WITHOUT_SOURCE_CUSTOM);
|
||||
campaign.hash = campaigns.hash(campaign, campaigns.Content.WITHOUT_SOURCE_CUSTOM);
|
||||
return res.json(campaign);
|
||||
});
|
||||
|
||||
router.getAsync('/campaigns-content/:campaignId', passport.loggedIn, async (req, res) => {
|
||||
const campaign = await campaigns.getById(req.context, req.params.campaignId, true, campaigns.Content.ONLY_SOURCE_CUSTOM);
|
||||
campaign.hash = campaigns.hash(campaign, campaigns.Content.ONLY_SOURCE_CUSTOM);
|
||||
return res.json(campaign);
|
||||
});
|
||||
|
||||
|
@ -20,11 +30,19 @@ router.postAsync('/campaigns', passport.loggedIn, passport.csrfProtection, async
|
|||
return res.json(await campaigns.create(req.context, req.body));
|
||||
});
|
||||
|
||||
router.putAsync('/campaigns/:campaignId', passport.loggedIn, passport.csrfProtection, async (req, res) => {
|
||||
router.putAsync('/campaigns-settings/:campaignId', passport.loggedIn, passport.csrfProtection, async (req, res) => {
|
||||
const entity = req.body;
|
||||
entity.id = parseInt(req.params.campaignId);
|
||||
|
||||
await campaigns.updateWithConsistencyCheck(req.context, entity);
|
||||
await campaigns.updateWithConsistencyCheck(req.context, entity, campaigns.Content.WITHOUT_SOURCE_CUSTOM);
|
||||
return res.json();
|
||||
});
|
||||
|
||||
router.putAsync('/campaigns-content/:campaignId', passport.loggedIn, passport.csrfProtection, async (req, res) => {
|
||||
const entity = req.body;
|
||||
entity.id = parseInt(req.params.campaignId);
|
||||
|
||||
await campaigns.updateWithConsistencyCheck(req.context, entity, campaigns.Content.ONLY_SOURCE_CUSTOM);
|
||||
return res.json();
|
||||
});
|
||||
|
||||
|
|
|
@ -20,6 +20,6 @@ router.deleteAsync('/files/:type/:subType/:fileId', passport.loggedIn, async (re
|
|||
return res.json();
|
||||
});
|
||||
|
||||
fileHelpers.installUploadHandler(router, '/files/:type/:subType/:entityId', files.ReplacementBehavior.REPLACE);
|
||||
fileHelpers.installUploadHandler(router, '/files/:type/:subType/:entityId');
|
||||
|
||||
module.exports = router;
|
|
@ -3,7 +3,6 @@
|
|||
const passport = require('../../lib/passport');
|
||||
const _ = require('../../lib/translate')._;
|
||||
const shares = require('../../models/shares');
|
||||
const permissions = require('../../lib/permissions');
|
||||
|
||||
const router = require('../../lib/router-async').create();
|
||||
|
||||
|
|
|
@ -940,7 +940,7 @@ async function migrateAttachments(knex) {
|
|||
data: attachment.content
|
||||
});
|
||||
}
|
||||
await files.createFiles(contextHelpers.getAdminContext(), 'campaign', 'attachment', campaign.id, attachmentFiles, files.ReplacementBehavior.NONE);
|
||||
await files.createFiles(contextHelpers.getAdminContext(), 'campaign', 'attachment', campaign.id, attachmentFiles);
|
||||
}
|
||||
|
||||
await knex.schema.dropTableIfExists('attachments');
|
||||
|
|
|
@ -11,7 +11,12 @@ function base(text, trustedBaseUrl, sandboxBaseUrl) {
|
|||
sandboxBaseUrl = sandboxBaseUrl.substring(0, sandboxBaseUrl.length - 1);
|
||||
}
|
||||
|
||||
return text.split('[URL_BASE]').join(trustedBaseUrl).split('[SANDBOX_URL_BASE]').join(sandboxBaseUrl);
|
||||
text = text.split('[URL_BASE]').join(trustedBaseUrl);
|
||||
text = text.split('[SANDBOX_URL_BASE]').join(sandboxBaseUrl);
|
||||
text = text.split('[ENCODED_URL_BASE]').join(encodeURIComponent(trustedBaseUrl));
|
||||
text = text.split('[ENCODED_SANDBOX_URL_BASE]').join(encodeURIComponent(sandboxBaseUrl));
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
function unbase(text, trustedBaseUrl, sandboxBaseUrl, treatSandboxAsTrusted = false) {
|
||||
|
@ -23,7 +28,12 @@ function unbase(text, trustedBaseUrl, sandboxBaseUrl, treatSandboxAsTrusted = fa
|
|||
sandboxBaseUrl = sandboxBaseUrl.substring(0, sandboxBaseUrl.length - 1);
|
||||
}
|
||||
|
||||
return text.split(trustedBaseUrl).join('[URL_BASE]').split(sandboxBaseUrl).join(treatSandboxAsTrusted ? '[URL_BASE]' : '[SANDBOX_URL_BASE]');
|
||||
text = text.split(trustedBaseUrl).join('[URL_BASE]');
|
||||
text = text.split(sandboxBaseUrl).join(treatSandboxAsTrusted ? '[URL_BASE]' : '[SANDBOX_URL_BASE]');
|
||||
text = text.split(encodeURIComponent(trustedBaseUrl)).join('[ENCODED_URL_BASE]');
|
||||
text = text.split(encodeURIComponent(sandboxBaseUrl)).join(treatSandboxAsTrusted ? '[ENCODED_URL_BASE]' : '[ENCODED_SANDBOX_URL_BASE]');
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
Loading…
Reference in a new issue