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
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
workers/reports/config/production.toml
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.
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.

View file

@ -54,6 +54,9 @@ const index = require('./routes/index');
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/subscription/partials/');
@ -104,7 +107,8 @@ hbs.registerHelper('flash_messages', function () { // eslint-disable-line prefer
handlebarsHelpers.registerHelpers(hbs.handlebars);
function createApp(trusted) {
function createApp(appType) {
const app = express();
function install404Fallback(url) {
@ -171,9 +175,9 @@ function createApp(trusted) {
limit: config.www.postSize
}));
if (trusted) {
if (appType === AppType.TRUSTED) {
passport.setupRegularAuth(app);
} else {
} else if (appType === AppType.SANDBOXED) {
app.use(passport.tryAuthByRestrictedAccessToken);
}
@ -189,52 +193,6 @@ function createApp(trusted) {
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
app.all('/api/*', passport.authByAccessToken);
@ -244,62 +202,64 @@ function createApp(trusted) {
next();
});
app.all('/rest/*', (req, res, next) => {
req.needsRESTJSONResponse = true;
next();
});
// Initializes the request context to be used for authorization
app.use((req, res, next) => {
req.context = contextHelpers.getRequestContext(req);
next();
});
// Regular endpoints
useWith404Fallback('/subscription', subscription);
useWith404Fallback('/files', files);
useWith404Fallback('/mosaico', mosaico.getRouter(trusted));
if (config.reports && config.reports.enabled === true) {
useWith404Fallback('/reports', reports);
if (appType === AppType.PUBLIC) {
useWith404Fallback('/subscription', subscription);
}
if (appType === AppType.TRUSTED || appType === AppType.SANDBOXED) {
// Regular endpoints
useWith404Fallback('/files', files);
useWith404Fallback('/mosaico', mosaico.getRouter(appType));
// API endpoints
useWith404Fallback('/api', api);
if (config.reports && config.reports.enabled === true) {
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) {
app.use('/rest', reportTemplatesRest);
app.use('/rest', reportsRest);
// API endpoints
useWith404Fallback('/api', api);
// 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
if (app.get('env') === 'development') {
@ -333,7 +293,7 @@ function createApp(trusted) {
} else {
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 {
res.status(err.status || 500);
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
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 {
res.status(err.status || 500);
res.render('error', {
@ -393,6 +353,4 @@ function createApp(trusted) {
return app;
}
module.exports = {
createApp
};
module.exports.createApp = createApp;

View file

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

View file

@ -29,3 +29,56 @@ export function getCampaignLabels(t) {
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 (
<div className={this.state.fullscreen ? styles.editorFullscreen : styles.editor}>
<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>
<a className={styles.btn} onClick={::this.toggleFullscreenAsync}><Icon icon="fullscreen"/></a>
</div>
@ -142,7 +142,7 @@ export class MosaicoSandbox extends Component {
plugins.unshift(vm => {
// 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 = '#';
});

View file

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

View file

@ -52,7 +52,8 @@ export default class CUD extends Component {
contact_email: '',
homepage: '',
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="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.')}/>
<NamespaceSelect/>

View file

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

View file

@ -134,7 +134,7 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
entity={owner.props.entity}
initialModel={owner.getFormValue(prefix + 'mosaicoData').model}
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}
title={t('Mosaico Template Designer')}
onFullscreenAsync={::owner.setElementInFullscreen}/>
@ -285,6 +285,14 @@ export function getEditForm(owner, typeKey, prefix = '') {
<Trans>Email address</Trans>
</td>
</tr>
<tr>
<th scope="row">
[TO_NAME]
</th>
<td>
<Trans>Recipient name as it appears in email's 'To' header</Trans>
</td>
</tr>
<tr>
<th scope="row">
[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