Client's public folder renamed to static

Regular campaign sender seems to have most of the code in place. (Not tested.)
This commit is contained in:
Tomas Bures 2018-09-18 10:30:13 +02:00
parent 89eabea0de
commit 63765f7222
354 changed files with 836 additions and 324 deletions

13
.gitignore vendored
View file

@ -15,19 +15,6 @@ dump.rdb
# generate POT file every time you want to update your PO file # generate POT file every time you want to update your PO file
languages/mailtrain.pot languages/mailtrain.pot
public/mosaico/uploads/*
!public/mosaico/uploads/README.md
public/mosaico/custom/*
!public/mosaico/custom/README.md
public/mosaico/templates/*
!public/mosaico/templates/versafix-1
public/grapejs/uploads/*
!public/grapejs/uploads/README.md
public/grapejs/templates/*
!public/grapejs/templates/demo
!public/grapejs/templates/aves
config/production.toml config/production.toml
workers/reports/config/production.toml workers/reports/config/production.toml
docker-compose.override.yml docker-compose.override.yml

View file

@ -6,8 +6,8 @@ The migration should almost happen automatically. There are however the followin
and update your configs accordingly. and update your configs accordingly.
2. Images uploaded in a template editor (Mosaico, GrapeJS, etc.) need to be manually moved to a new destination (under `client`). 2. Images uploaded in a template editor (Mosaico, GrapeJS, etc.) need to be manually moved to a new destination (under `client`).
For Mosaico, this means to move folders named by a number from `public/mosaico` to `client/public/mosaico`. For Mosaico, this means to move folders named by a number from `public/mosaico` to `client/static/mosaico`.
3. Directory for custom Mosaico templates has changed from `public/mosaico/templates` to `client/public/mosaico/templates`. 3. Directory for custom Mosaico templates has changed from `public/mosaico/templates` to `client/static/mosaico/templates`.
4. Imports are not migrated. If you have any pending imports, complete them before migration to v2. 4. Imports are not migrated. If you have any pending imports, complete them before migration to v2.

View file

@ -54,6 +54,9 @@ const index = require('./routes/index');
const interoperableErrors = require('./shared/interoperable-errors'); const interoperableErrors = require('./shared/interoperable-errors');
const { getTrustedUrl } = require('./lib/urls');
const { AppType } = require('./shared/app');
hbs.registerPartials(__dirname + '/views/partials'); hbs.registerPartials(__dirname + '/views/partials');
hbs.registerPartials(__dirname + '/views/subscription/partials/'); hbs.registerPartials(__dirname + '/views/subscription/partials/');
@ -104,7 +107,8 @@ hbs.registerHelper('flash_messages', function () { // eslint-disable-line prefer
handlebarsHelpers.registerHelpers(hbs.handlebars); handlebarsHelpers.registerHelpers(hbs.handlebars);
function createApp(trusted) {
function createApp(appType) {
const app = express(); const app = express();
function install404Fallback(url) { function install404Fallback(url) {
@ -171,9 +175,9 @@ function createApp(trusted) {
limit: config.www.postSize limit: config.www.postSize
})); }));
if (trusted) { if (appType === AppType.TRUSTED) {
passport.setupRegularAuth(app); passport.setupRegularAuth(app);
} else { } else if (appType === AppType.SANDBOXED) {
app.use(passport.tryAuthByRestrictedAccessToken); app.use(passport.tryAuthByRestrictedAccessToken);
} }
@ -189,52 +193,6 @@ function createApp(trusted) {
next(); next();
}); });
/* FIXME - can we remove this???
app.use((req, res, next) => {
res.locals.flash = req.flash.bind(req);
res.locals.user = req.user;
res.locals.admin = req.user && req.user.id == 1; // FIXME, this should verify the admin privileges and set this accordingly
res.locals.ldap = {
enabled: config.ldap.enabled,
passwordresetlink: config.ldap.passwordresetlink
};
let menu = [{
title: _('Home'),
url: '/',
selected: true
}];
res.setSelectedMenu = key => {
menu.forEach(item => {
item.selected = (item.key === key);
});
};
res.locals.menu = menu;
tools.updateMenu(res);
res.locals.customStyles = config.customstyles || [];
res.locals.customScripts = config.customscripts || [];
let bodyClasses = [];
if (req.user) {
bodyClasses.push('logged-in user-' + req.user.username);
}
res.locals.bodyClass = bodyClasses.join(' ');
getSettings(['uaCode', 'shoutout'], (err, configItems) => {
if (err) {
return next(err);
}
Object.keys(configItems).forEach(key => {
res.locals[key] = configItems[key];
});
next();
});
});
*/
// Endpoint under /api are authenticated by access token // Endpoint under /api are authenticated by access token
app.all('/api/*', passport.authByAccessToken); app.all('/api/*', passport.authByAccessToken);
@ -244,62 +202,64 @@ function createApp(trusted) {
next(); next();
}); });
app.all('/rest/*', (req, res, next) => { app.all('/rest/*', (req, res, next) => {
req.needsRESTJSONResponse = true; req.needsRESTJSONResponse = true;
next(); next();
}); });
// Initializes the request context to be used for authorization // Initializes the request context to be used for authorization
app.use((req, res, next) => { app.use((req, res, next) => {
req.context = contextHelpers.getRequestContext(req); req.context = contextHelpers.getRequestContext(req);
next(); next();
}); });
if (appType === AppType.PUBLIC) {
// Regular endpoints useWith404Fallback('/subscription', subscription);
useWith404Fallback('/subscription', subscription);
useWith404Fallback('/files', files);
useWith404Fallback('/mosaico', mosaico.getRouter(trusted));
if (config.reports && config.reports.enabled === true) {
useWith404Fallback('/reports', reports);
} }
if (appType === AppType.TRUSTED || appType === AppType.SANDBOXED) {
// Regular endpoints
useWith404Fallback('/files', files);
useWith404Fallback('/mosaico', mosaico.getRouter(appType));
// API endpoints if (config.reports && config.reports.enabled === true) {
useWith404Fallback('/api', api); useWith404Fallback('/reports', reports);
}
// REST endpoints
app.use('/rest', namespacesRest);
app.use('/rest', sendConfigurationsRest);
app.use('/rest', usersRest);
app.use('/rest', accountRest);
app.use('/rest', campaignsRest);
app.use('/rest', triggersRest);
app.use('/rest', listsRest);
app.use('/rest', formsRest);
app.use('/rest', fieldsRest);
app.use('/rest', importsRest);
app.use('/rest', importRunsRest);
app.use('/rest', sharesRest);
app.use('/rest', segmentsRest);
app.use('/rest', subscriptionsRest);
app.use('/rest', templatesRest);
app.use('/rest', mosaicoTemplatesRest);
app.use('/rest', blacklistRest);
app.use('/rest', editorsRest);
app.use('/rest', filesRest);
app.use('/rest', settingsRest);
if (config.reports && config.reports.enabled === true) { // API endpoints
app.use('/rest', reportTemplatesRest); useWith404Fallback('/api', api);
app.use('/rest', reportsRest);
// REST endpoints
app.use('/rest', namespacesRest);
app.use('/rest', sendConfigurationsRest);
app.use('/rest', usersRest);
app.use('/rest', accountRest);
app.use('/rest', campaignsRest);
app.use('/rest', triggersRest);
app.use('/rest', listsRest);
app.use('/rest', formsRest);
app.use('/rest', fieldsRest);
app.use('/rest', importsRest);
app.use('/rest', importRunsRest);
app.use('/rest', sharesRest);
app.use('/rest', segmentsRest);
app.use('/rest', subscriptionsRest);
app.use('/rest', templatesRest);
app.use('/rest', mosaicoTemplatesRest);
app.use('/rest', blacklistRest);
app.use('/rest', editorsRest);
app.use('/rest', filesRest);
app.use('/rest', settingsRest);
if (config.reports && config.reports.enabled === true) {
app.use('/rest', reportTemplatesRest);
app.use('/rest', reportsRest);
}
install404Fallback('/rest');
} }
install404Fallback('/rest');
app.use('/', index.getRouter(trusted)); app.use('/', index.getRouter(appType));
// Error handlers // Error handlers
if (app.get('env') === 'development') { if (app.get('env') === 'development') {
@ -333,7 +293,7 @@ function createApp(trusted) {
} else { } else {
if (err instanceof interoperableErrors.NotLoggedInError) { if (err instanceof interoperableErrors.NotLoggedInError) {
return res.redirect('/account/login?next=' + encodeURIComponent(req.originalUrl)); return res.redirect(getTrustedUrl('/account/login?next=' + encodeURIComponent(req.originalUrl)));
} else { } else {
res.status(err.status || 500); res.status(err.status || 500);
res.render('error', { res.render('error', {
@ -378,7 +338,7 @@ function createApp(trusted) {
// TODO: Render interoperable errors using a special client that does internationalization of the error message // TODO: Render interoperable errors using a special client that does internationalization of the error message
if (err instanceof interoperableErrors.NotLoggedInError) { if (err instanceof interoperableErrors.NotLoggedInError) {
return res.redirect('/account/login?next=' + encodeURIComponent(req.originalUrl)); return res.redirect(getTrustedUrl('/account/login?next=' + encodeURIComponent(req.originalUrl)));
} else { } else {
res.status(err.status || 500); res.status(err.status || 500);
res.render('error', { res.render('error', {
@ -393,6 +353,4 @@ function createApp(trusted) {
return app; return app;
} }
module.exports = { module.exports.createApp = createApp;
createApp
};

View file

@ -40,7 +40,6 @@ export default class CustomContent extends Component {
const t = props.t; const t = props.t;
console.log(props);
this.templateTypes = getTemplateTypes(props.t, 'data_sourceCustom_', ResourceType.CAMPAIGN); this.templateTypes = getTemplateTypes(props.t, 'data_sourceCustom_', ResourceType.CAMPAIGN);
this.customTemplateTypeOptions = []; this.customTemplateTypeOptions = [];

View file

@ -29,3 +29,56 @@ export function getCampaignLabels(t) {
campaignTypeLabels campaignTypeLabels
}; };
} }
/* FIXME - this is not used at the moment, but it's kept here because it will be probably needed at some later point of time.
export function getDefaultMergeTags(t) {
return [{
key: 'LINK_UNSUBSCRIBE',
value: t('URL that points to the unsubscribe page')
}, {
key: 'LINK_PREFERENCES',
value: t('URL that points to the preferences page of the subscriber')
}, {
key: 'LINK_BROWSER',
value: t('URL to preview the message in a browser')
}, {
key: 'EMAIL',
value: t('Email address')
}, {
key: 'SUBSCRIPTION_ID',
value: t('Unique ID that identifies the recipient')
}, {
key: 'LIST_ID',
value: t('Unique ID that identifies the list used for this campaign')
}, {
key: 'CAMPAIGN_ID',
value: t('Unique ID that identifies current campaign')
}];
}
export function getRSSMergeTags(t) {
return [{
key: 'RSS_ENTRY',
value: t('content from an RSS entry')
}, {
key: 'RSS_ENTRY_TITLE',
value: t('RSS entry title')
}, {
key: 'RSS_ENTRY_DATE',
value: t('RSS entry date')
}, {
key: 'RSS_ENTRY_LINK',
value: t('RSS entry link')
}, {
key: 'RSS_ENTRY_CONTENT',
value: t('content from an RSS entry')
}, {
key: 'RSS_ENTRY_SUMMARY',
value: t('RSS entry summary')
}, {
key: 'RSS_ENTRY_IMAGE_URL',
value: t('RSS entry image URL')
}];
}
*/

View file

@ -70,7 +70,7 @@ export class MosaicoEditor extends Component {
return ( return (
<div className={this.state.fullscreen ? styles.editorFullscreen : styles.editor}> <div className={this.state.fullscreen ? styles.editorFullscreen : styles.editor}>
<div className={styles.navbar}> <div className={styles.navbar}>
{this.state.fullscreen && <img className={styles.logo} src={getTrustedUrl('public/mailtrain-notext.png')}/>} {this.state.fullscreen && <img className={styles.logo} src={getTrustedUrl('static/mailtrain-notext.png')}/>}
<div className={styles.title}>{this.props.title}</div> <div className={styles.title}>{this.props.title}</div>
<a className={styles.btn} onClick={::this.toggleFullscreenAsync}><Icon icon="fullscreen"/></a> <a className={styles.btn} onClick={::this.toggleFullscreenAsync}><Icon icon="fullscreen"/></a>
</div> </div>
@ -142,7 +142,7 @@ export class MosaicoSandbox extends Component {
plugins.unshift(vm => { plugins.unshift(vm => {
// This is an override of the default paths in Mosaico // This is an override of the default paths in Mosaico
vm.logoPath = getTrustedUrl('public/mosaico/img/mosaico32.png'); vm.logoPath = getTrustedUrl('static/mosaico/img/mosaico32.png');
vm.logoUrl = '#'; vm.logoUrl = '#';
}); });

View file

@ -1,6 +1,7 @@
'use strict'; 'use strict';
import {anonymousRestrictedAccessToken} from '../../../shared/urls'; import {anonymousRestrictedAccessToken} from '../../../shared/urls';
import {AppType} from '../../../shared/app';
import mailtrainConfig from "mailtrainConfig"; import mailtrainConfig from "mailtrainConfig";
let restrictedAccessToken = anonymousRestrictedAccessToken; let restrictedAccessToken = anonymousRestrictedAccessToken;
@ -17,25 +18,34 @@ function getSandboxUrl(path) {
return mailtrainConfig.sandboxUrlBase + restrictedAccessToken + '/' + (path || ''); return mailtrainConfig.sandboxUrlBase + restrictedAccessToken + '/' + (path || '');
} }
function getPublicUrl(path) {
return mailtrainConfig.publicUrlBase + (path || '');
}
function getUrl(path) { function getUrl(path) {
if (mailtrainConfig.trusted) { if (mailtrainConfig.appType === AppType.TRUSTED) {
return getTrustedUrl(path); return getTrustedUrl(path);
} else { } else if (mailtrainConfig.appType === AppType.SANDBOXED) {
return getSandboxUrl(path); return getSandboxUrl(path);
} else if (mailtrainConfig.appType === AppType.PUBLIC) {
return getPublicUrl(path);
} }
} }
function getBaseDir() { function getBaseDir() {
if (mailtrainConfig.trusted) { if (mailtrainConfig.appType === AppType.TRUSTED) {
return mailtrainConfig.trustedUrlBaseDir; return mailtrainConfig.trustedUrlBaseDir;
} else { } else if (mailtrainConfig.appType === AppType.SANDBOXED) {
return mailtrainConfig.sandboxUrlBaseDir + anonymousRestrictedAccessToken; return mailtrainConfig.sandboxUrlBaseDir + anonymousRestrictedAccessToken;
} else if (mailtrainConfig.appType === AppType.PUBLIC) {
return mailtrainConfig.publicUrlBaseDir;
} }
} }
export { export {
getTrustedUrl, getTrustedUrl,
getSandboxUrl, getSandboxUrl,
getPublicUrl,
getUrl, getUrl,
getBaseDir, getBaseDir,
setRestrictedAccessToken setRestrictedAccessToken

View file

@ -52,7 +52,8 @@ export default class CUD extends Component {
contact_email: '', contact_email: '',
homepage: '', homepage: '',
unsubscription_mode: UnsubscriptionMode.ONE_STEP, unsubscription_mode: UnsubscriptionMode.ONE_STEP,
namespace: mailtrainConfig.user.namespace namespace: mailtrainConfig.user.namespace,
to_name: '[FIRST_NAME] [LAST_NAME]'
}); });
} }
} }
@ -186,6 +187,7 @@ export default class CUD extends Component {
<InputField id="contact_email" label={t('Contact email')} help={t('Contact email used in subscription forms and emails that are sent out. If not filled in, the admin email from the global settings will be used.')}/> <InputField id="contact_email" label={t('Contact email')} help={t('Contact email used in subscription forms and emails that are sent out. If not filled in, the admin email from the global settings will be used.')}/>
<InputField id="homepage" label={t('Homepage')} help={t('Homepage URL used in subscription forms and emails that are sent out. If not filled in, the default homepage from global settings will be used.')}/> <InputField id="homepage" label={t('Homepage')} help={t('Homepage URL used in subscription forms and emails that are sent out. If not filled in, the default homepage from global settings will be used.')}/>
<InputField id="to_name" label={t('Recipients name template')} help={t('Specify using merge tags of this list how to construct full name of the recipient. This full name is used as "To" header when sending emails.')}/>
<TableSelect id="send_configuration" label={t('Send configuration')} withHeader dropdown dataUrl='rest/send-configurations-table' columns={sendConfigurationsColumns} selectionLabelIndex={1} help={t('Send configuration that will be used for sending out subscription-related emails.')}/> <TableSelect id="send_configuration" label={t('Send configuration')} withHeader dropdown dataUrl='rest/send-configurations-table' columns={sendConfigurationsColumns} selectionLabelIndex={1} help={t('Send configuration that will be used for sending out subscription-related emails.')}/>
<NamespaceSelect/> <NamespaceSelect/>

View file

@ -15,7 +15,7 @@ import {
import {Icon, Button} from "../../lib/bootstrap-components"; import {Icon, Button} from "../../lib/bootstrap-components";
import axios from '../../lib/axios'; import axios from '../../lib/axios';
import {getFieldTypes, getSubscriptionStatusLabels} from './helpers'; import {getFieldTypes, getSubscriptionStatusLabels} from './helpers';
import {getUrl} from "../../lib/urls"; import {getUrl, getPublicUrl} from "../../lib/urls";
@translate() @translate()
@withForm @withForm
@ -158,7 +158,7 @@ export default class List extends Component {
return ( return (
<div> <div>
<Toolbar> <Toolbar>
<a href={`/subscription/${this.props.list.cid}`} className="btn-default"><Button label={t('Subscription Form')} className="btn-default"/></a> <a href={getPublicUrl(`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')}/> <NavButton linkTo={`/lists/${this.props.list.id}/subscriptions/create`} className="btn-primary" icon="plus" label={t('Add Subscriber')}/>
</Toolbar> </Toolbar>

View file

@ -134,7 +134,7 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
entity={owner.props.entity} entity={owner.props.entity}
initialModel={owner.getFormValue(prefix + 'mosaicoData').model} initialModel={owner.getFormValue(prefix + 'mosaicoData').model}
initialMetadata={owner.getFormValue(prefix + 'mosaicoData').metadata} initialMetadata={owner.getFormValue(prefix + 'mosaicoData').metadata}
templatePath={getSandboxUrl(`public/mosaico/templates/${owner.getFormValue(prefix + 'mosaicoFsTemplate')}/index.html`)} templatePath={getSandboxUrl(`static/mosaico/templates/${owner.getFormValue(prefix + 'mosaicoFsTemplate')}/index.html`)}
entityTypeId={entityTypeId} entityTypeId={entityTypeId}
title={t('Mosaico Template Designer')} title={t('Mosaico Template Designer')}
onFullscreenAsync={::owner.setElementInFullscreen}/> onFullscreenAsync={::owner.setElementInFullscreen}/>
@ -285,6 +285,14 @@ export function getEditForm(owner, typeKey, prefix = '') {
<Trans>Email address</Trans> <Trans>Email address</Trans>
</td> </td>
</tr> </tr>
<tr>
<th scope="row">
[TO_NAME]
</th>
<td>
<Trans>Recipient name as it appears in email's 'To' header</Trans>
</td>
</tr>
<tr> <tr>
<th scope="row"> <th scope="row">
[SUBSCRIPTION_ID] [SUBSCRIPTION_ID]

View file

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 434 KiB

After

Width:  |  Height:  |  Size: 434 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4 KiB

After

Width:  |  Height:  |  Size: 4 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 965 B

After

Width:  |  Height:  |  Size: 965 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 215 B

After

Width:  |  Height:  |  Size: 215 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 169 B

After

Width:  |  Height:  |  Size: 169 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

Some files were not shown because too many files have changed in this diff Show more