Fix for #660
Campaign preview and campaign test send pulls the first entry in the RSS feed and substitutes its data in `[RSS_ENTRY_*]`
This commit is contained in:
parent
588cf34810
commit
8cb24feca1
11 changed files with 262 additions and 57 deletions
|
@ -10,7 +10,7 @@ import {getCampaignLabels} from './helpers';
|
||||||
import {Table} from "../lib/table";
|
import {Table} from "../lib/table";
|
||||||
import {Button, Icon, ModalDialog} from "../lib/bootstrap-components";
|
import {Button, Icon, ModalDialog} from "../lib/bootstrap-components";
|
||||||
import axios from "../lib/axios";
|
import axios from "../lib/axios";
|
||||||
import {getPublicUrl, getUrl} from "../lib/urls";
|
import {getPublicUrl, getSandboxUrl, getUrl} from "../lib/urls";
|
||||||
import interoperableErrors from '../../../shared/interoperable-errors';
|
import interoperableErrors from '../../../shared/interoperable-errors';
|
||||||
import {CampaignStatus, CampaignType} from "../../../shared/campaigns";
|
import {CampaignStatus, CampaignType} from "../../../shared/campaigns";
|
||||||
import moment from 'moment-timezone';
|
import moment from 'moment-timezone';
|
||||||
|
@ -58,10 +58,29 @@ class PreviewForTestUserModalDialog extends Component {
|
||||||
|
|
||||||
async previewAsync() {
|
async previewAsync() {
|
||||||
if (this.isFormWithoutErrors()) {
|
if (this.isFormWithoutErrors()) {
|
||||||
const campaignCid = this.props.entity.cid;
|
const entity = this.props.entity;
|
||||||
|
const campaignCid = entity.cid;
|
||||||
const [listCid, subscriptionCid] = this.getFormValue('testUser').split(':');
|
const [listCid, subscriptionCid] = this.getFormValue('testUser').split(':');
|
||||||
|
|
||||||
|
if (entity.type === CampaignType.RSS) {
|
||||||
|
const result = await axios.post(getUrl('rest/restricted-access-token'), {
|
||||||
|
method: 'rssPreview',
|
||||||
|
params: {
|
||||||
|
campaignCid,
|
||||||
|
listCid
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const accessToken = result.data;
|
||||||
|
window.open(getSandboxUrl(`campaigns/rss-preview/${campaignCid}/${listCid}/${subscriptionCid}`, accessToken, {withLocale: true}), '_blank');
|
||||||
|
|
||||||
|
} else if (entity.type === CampaignType.REGULAR) {
|
||||||
window.open(getPublicUrl(`archive/${campaignCid}/${listCid}/${subscriptionCid}`, {withLocale: true}), '_blank');
|
window.open(getPublicUrl(`archive/${campaignCid}/${listCid}/${subscriptionCid}`, {withLocale: true}), '_blank');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new Error('Preview not supported');
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.showFormValidation();
|
this.showFormValidation();
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,15 @@ function getTrustedUrl(path) {
|
||||||
return mailtrainConfig.trustedUrlBase + (path || '');
|
return mailtrainConfig.trustedUrlBase + (path || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSandboxUrl(path, customRestrictedAccessToken) {
|
function getSandboxUrl(path, customRestrictedAccessToken, opts) {
|
||||||
const localRestrictedAccessToken = customRestrictedAccessToken || restrictedAccessToken;
|
const localRestrictedAccessToken = customRestrictedAccessToken || restrictedAccessToken;
|
||||||
return mailtrainConfig.sandboxUrlBase + localRestrictedAccessToken + '/' + (path || '');
|
const url = new URL(localRestrictedAccessToken + '/' + (path || ''), mailtrainConfig.sandboxUrlBase);
|
||||||
|
|
||||||
|
if (opts && opts.withLocale) {
|
||||||
|
url.searchParams.append('locale', i18n.language);
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPublicUrl(path, opts) {
|
function getPublicUrl(path, opts) {
|
||||||
|
|
|
@ -683,6 +683,14 @@ export function getEditForm(owner, typeKey, prefix = '') {
|
||||||
<Trans i18nKey="rssEntryImageUrl">RSS entry image URL</Trans>
|
<Trans i18nKey="rssEntryImageUrl">RSS entry image URL</Trans>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
{tg('RSS_ENTRY_CUSTOM_TAGS')}
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<Trans>Mailtrain custom tags. The custom tags can be passed in via <code>mt:entries-json</code> element in RSS entry. The text contents of the elements is interpreted as JSON-formatted object..</Trans>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>}
|
</div>}
|
||||||
|
|
|
@ -23,6 +23,7 @@ const api = require('./routes/api');
|
||||||
const reports = require('./routes/reports');
|
const reports = require('./routes/reports');
|
||||||
const quickReports = require('./routes/quick-reports');
|
const quickReports = require('./routes/quick-reports');
|
||||||
const subscriptions = require('./routes/subscriptions');
|
const subscriptions = require('./routes/subscriptions');
|
||||||
|
const campaigns = require('./routes/campaigns');
|
||||||
const subscription = require('./routes/subscription');
|
const subscription = require('./routes/subscription');
|
||||||
const sandboxedMosaico = require('./routes/sandboxed-mosaico');
|
const sandboxedMosaico = require('./routes/sandboxed-mosaico');
|
||||||
const sandboxedCKEditor = require('./routes/sandboxed-ckeditor');
|
const sandboxedCKEditor = require('./routes/sandboxed-ckeditor');
|
||||||
|
@ -60,7 +61,7 @@ const index = require('./routes/index');
|
||||||
|
|
||||||
const interoperableErrors = require('../shared/interoperable-errors');
|
const interoperableErrors = require('../shared/interoperable-errors');
|
||||||
|
|
||||||
const { getTrustedUrl } = require('./lib/urls');
|
const { getTrustedUrl, getSandboxUrl, getPublicUrl } = require('./lib/urls');
|
||||||
const { AppType } = require('../shared/app');
|
const { AppType } = require('../shared/app');
|
||||||
|
|
||||||
|
|
||||||
|
@ -274,6 +275,8 @@ async function createApp(appType) {
|
||||||
useWith404Fallback('/files', files);
|
useWith404Fallback('/files', files);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useWith404Fallback('/campaigns', await campaigns.getRouter(appType));
|
||||||
|
|
||||||
useWith404Fallback('/mosaico', await sandboxedMosaico.getRouter(appType));
|
useWith404Fallback('/mosaico', await sandboxedMosaico.getRouter(appType));
|
||||||
useWith404Fallback('/ckeditor', await sandboxedCKEditor.getRouter(appType));
|
useWith404Fallback('/ckeditor', await sandboxedCKEditor.getRouter(appType));
|
||||||
useWith404Fallback('/grapesjs', await sandboxedGrapesJS.getRouter(appType));
|
useWith404Fallback('/grapesjs', await sandboxedGrapesJS.getRouter(appType));
|
||||||
|
@ -357,11 +360,21 @@ async function createApp(appType) {
|
||||||
if (err instanceof interoperableErrors.NotLoggedInError) {
|
if (err instanceof interoperableErrors.NotLoggedInError) {
|
||||||
return res.redirect(getTrustedUrl('/login?next=' + encodeURIComponent(req.originalUrl)));
|
return res.redirect(getTrustedUrl('/login?next=' + encodeURIComponent(req.originalUrl)));
|
||||||
} else {
|
} else {
|
||||||
|
let publicPath;
|
||||||
|
if (appType === AppType.TRUSTED) {
|
||||||
|
publicPath = getTrustedUrl();
|
||||||
|
} else if (appType === AppType.SANDBOXED) {
|
||||||
|
publicPath = getSandboxUrl();
|
||||||
|
} else if (appType === AppType.PUBLIC) {
|
||||||
|
publicPath = getPublicUrl();
|
||||||
|
}
|
||||||
|
|
||||||
log.verbose('HTTP', err);
|
log.verbose('HTTP', err);
|
||||||
res.status(err.status || 500);
|
res.status(err.status || 500);
|
||||||
res.render('error', {
|
res.render('error', {
|
||||||
message: err.message,
|
message: err.message,
|
||||||
error: config.sendStacktracesToClient ? err : {}
|
error: config.sendStacktracesToClient ? err : {},
|
||||||
|
publicPath
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ const log = require('./log');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const senders = require('./senders');
|
const senders = require('./senders');
|
||||||
const bluebird = require('bluebird');
|
const bluebird = require('bluebird');
|
||||||
|
const feedparser = require('feedparser-promised');
|
||||||
|
const {getPublicUrl} = require('./urls');
|
||||||
|
|
||||||
let messageTid = 0;
|
let messageTid = 0;
|
||||||
let feedcheckProcess;
|
let feedcheckProcess;
|
||||||
|
@ -42,5 +44,79 @@ function scheduleCheck() {
|
||||||
messageTid++;
|
messageTid++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetch(url) {
|
||||||
|
const httpOptions = {
|
||||||
|
uri: url,
|
||||||
|
headers: {
|
||||||
|
'user-agent': 'Mailtrain',
|
||||||
|
'accept': 'text/html,application/xhtml+xml'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const items = await feedparser.parse(httpOptions);
|
||||||
|
|
||||||
|
const entries = [];
|
||||||
|
for (const item of items) {
|
||||||
|
const entry = {
|
||||||
|
title: item.title,
|
||||||
|
date: item.date || item.pubdate || item.pubDate || new Date(),
|
||||||
|
guid: item.guid || item.link,
|
||||||
|
link: item.link,
|
||||||
|
content: item.description || item.summary,
|
||||||
|
summary: item.summary || item.description,
|
||||||
|
imageUrl: item.image.url,
|
||||||
|
};
|
||||||
|
|
||||||
|
if ('mt:entries-json' in item) {
|
||||||
|
entry.customTags = JSON.parse(item['mt:entries-json']['#'])
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getEntryForPreview(url) {
|
||||||
|
const entries = await fetch(url);
|
||||||
|
|
||||||
|
let entry;
|
||||||
|
if (entries.length === 0) {
|
||||||
|
entry = {
|
||||||
|
title: "Lorem Ipsum",
|
||||||
|
date: new Date(),
|
||||||
|
guid: "c21bc6c8-d351-4000-aa1f-e7ff928084cd",
|
||||||
|
link: "http://www.example.com/sample-item.html",
|
||||||
|
content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer gravida a purus in commodo. Sed risus eros, pharetra sit amet sagittis vel, porta nec magna. Sed sollicitudin blandit ornare. Pellentesque a lacinia dui. Etiam ullamcorper, nisl at pharetra fringilla, enim nunc blandit quam, nec vestibulum purus lorem in urna.",
|
||||||
|
summary: "Aliquam malesuada nibh eget arcu egestas, id pellentesque urna egestas. Phasellus lacus est, viverra in dolor quis, aliquet elementum nisi. Donec hendrerit elit pretium vehicula pharetra. Pellentesque aliquam elit id rutrum imperdiet. Phasellus ac enim at lacus sodales condimentum vitae quis sapien.",
|
||||||
|
imageUrl: getPublicUrl('static/mailtrain-notext.png'),
|
||||||
|
customTags: {
|
||||||
|
placerat: "Ligula at consequat",
|
||||||
|
accumsan: {
|
||||||
|
mauris: "Placerat nec justo",
|
||||||
|
ornare: "Nunc egestas"
|
||||||
|
},
|
||||||
|
fringilla: 42,
|
||||||
|
purus: [
|
||||||
|
{
|
||||||
|
consequat: "Vivamus",
|
||||||
|
enim: "volutpat blandit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
consequat: "Phasellus",
|
||||||
|
enim: "sed semper"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
entry = entries[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports.spawn = bluebird.promisify(spawn);
|
module.exports.spawn = bluebird.promisify(spawn);
|
||||||
module.exports.scheduleCheck = scheduleCheck;
|
module.exports.scheduleCheck = scheduleCheck;
|
||||||
|
module.exports.fetch = fetch;
|
||||||
|
module.exports.getEntryForPreview = getEntryForPreview;
|
|
@ -43,7 +43,7 @@ class MessageSender {
|
||||||
|
|
||||||
Option #1
|
Option #1
|
||||||
- settings.type in [MessageType.REGULAR, MessageType.TRIGGERED, MessageType.TEST]
|
- settings.type in [MessageType.REGULAR, MessageType.TRIGGERED, MessageType.TEST]
|
||||||
- campaignCid / campaignId
|
- campaign / campaignCid / campaignId
|
||||||
- listId / listCid [optional if campaign is provided]
|
- listId / listCid [optional if campaign is provided]
|
||||||
- sendConfigurationId [optional if campaign is provided]
|
- sendConfigurationId [optional if campaign is provided]
|
||||||
- attachments [optional]
|
- attachments [optional]
|
||||||
|
@ -68,7 +68,9 @@ class MessageSender {
|
||||||
if (this.type === MessageType.REGULAR || this.type === MessageType.TRIGGERED || this.type === MessageType.TEST) {
|
if (this.type === MessageType.REGULAR || this.type === MessageType.TRIGGERED || this.type === MessageType.TEST) {
|
||||||
this.isMassMail = true;
|
this.isMassMail = true;
|
||||||
|
|
||||||
if (settings.campaignCid) {
|
if (settings.campaign) {
|
||||||
|
this.campaign = settings.campaign;
|
||||||
|
} else if (settings.campaignCid) {
|
||||||
this.campaign = await campaigns.rawGetByTx(tx, 'cid', settings.campaignCid);
|
this.campaign = await campaigns.rawGetByTx(tx, 'cid', settings.campaignCid);
|
||||||
} else if (settings.campaignId) {
|
} else if (settings.campaignId) {
|
||||||
this.campaign = await campaigns.rawGetByTx(tx, 'id', settings.campaignId);
|
this.campaign = await campaigns.rawGetByTx(tx, 'id', settings.campaignId);
|
||||||
|
@ -155,6 +157,12 @@ class MessageSender {
|
||||||
this.tagLanguage = this.campaign.data.sourceCustom.tag_language;
|
this.tagLanguage = this.campaign.data.sourceCustom.tag_language;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (settings.rssEntry !== undefined) {
|
||||||
|
this.rssEntry = settings.rssEntry;
|
||||||
|
} else if (this.campaign && this.campaign.data.rssEntry) {
|
||||||
|
this.rssEntry = this.campaign.data.rssEntry;
|
||||||
|
}
|
||||||
|
|
||||||
enforce(this.renderedHtml || (this.campaign && this.campaign.source === CampaignSource.URL) || this.tagLanguage);
|
enforce(this.renderedHtml || (this.campaign && this.campaign.source === CampaignSource.URL) || this.tagLanguage);
|
||||||
|
|
||||||
if (settings.subject !== undefined) {
|
if (settings.subject !== undefined) {
|
||||||
|
@ -245,11 +253,11 @@ class MessageSender {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_getExtraTags(campaign) {
|
_getExtraTags() {
|
||||||
const tags = {};
|
const tags = {};
|
||||||
|
|
||||||
if (campaign && campaign.type === CampaignType.RSS_ENTRY) {
|
if (this.rssEntry) {
|
||||||
const rssEntry = campaign.data.rssEntry;
|
const rssEntry = this.rssEntry;
|
||||||
tags['RSS_ENTRY_TITLE'] = rssEntry.title;
|
tags['RSS_ENTRY_TITLE'] = rssEntry.title;
|
||||||
tags['RSS_ENTRY_DATE'] = rssEntry.date;
|
tags['RSS_ENTRY_DATE'] = rssEntry.date;
|
||||||
tags['RSS_ENTRY_LINK'] = rssEntry.link;
|
tags['RSS_ENTRY_LINK'] = rssEntry.link;
|
||||||
|
@ -311,7 +319,7 @@ class MessageSender {
|
||||||
const flds = this.listsFieldsGrouped.get(list.id);
|
const flds = this.listsFieldsGrouped.get(list.id);
|
||||||
|
|
||||||
if (!mergeTags) {
|
if (!mergeTags) {
|
||||||
mergeTags = fields.getMergeTags(flds, subscriptionGrouped, this._getExtraTags(campaign));
|
mergeTags = fields.getMergeTags(flds, subscriptionGrouped, this._getExtraTags());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const fld of flds) {
|
for (const fld of flds) {
|
||||||
|
@ -539,7 +547,8 @@ async function sendQueuedMessage(queuedMessage) {
|
||||||
subject: msgData.subject,
|
subject: msgData.subject,
|
||||||
tagLanguage: msgData.tagLanguage,
|
tagLanguage: msgData.tagLanguage,
|
||||||
renderedHtml: msgData.renderedHtml,
|
renderedHtml: msgData.renderedHtml,
|
||||||
renderedText: msgData.renderedText
|
renderedText: msgData.renderedText,
|
||||||
|
rssEntry: msgData.rssEntry
|
||||||
});
|
});
|
||||||
|
|
||||||
const campaign = cs.campaign;
|
const campaign = cs.campaign;
|
||||||
|
@ -696,9 +705,9 @@ async function queueSubscriptionMessage(sendConfigurationId, to, subject, encryp
|
||||||
senders.scheduleCheck();
|
senders.scheduleCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMessage(campaignCid, listCid, subscriptionCid) {
|
async function getMessage(campaignCid, listCid, subscriptionCid, settings) {
|
||||||
const cs = new MessageSender();
|
const cs = new MessageSender();
|
||||||
await cs._init({type: MessageType.REGULAR, campaignCid, listCid});
|
await cs._init({type: MessageType.REGULAR, campaignCid, listCid, ...settings});
|
||||||
|
|
||||||
const campaign = cs.campaign;
|
const campaign = cs.campaign;
|
||||||
const list = cs.listsByCid.get(listCid);
|
const list = cs.listsByCid.get(listCid);
|
||||||
|
@ -732,7 +741,7 @@ async function getMessage(campaignCid, listCid, subscriptionCid) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const flds = cs.listsFieldsGrouped.get(list.id);
|
const flds = cs.listsFieldsGrouped.get(list.id);
|
||||||
const mergeTags = fields.getMergeTags(flds, subscriptionGrouped, cs._getExtraTags(campaign));
|
const mergeTags = fields.getMergeTags(flds, subscriptionGrouped, cs._getExtraTags());
|
||||||
|
|
||||||
return await cs._getMessage(mergeTags, list, subscriptionGrouped, false);
|
return await cs._getMessage(mergeTags, list, subscriptionGrouped, false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -358,7 +358,7 @@ async function rawGetByTx(tx, key, id) {
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
if (!entity) {
|
if (!entity) {
|
||||||
throw new interoperableErrors.NotFoundError();
|
throw new shares.throwPermissionDenied();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity.lists) {
|
if (entity.lists) {
|
||||||
|
@ -425,6 +425,16 @@ async function getById(context, id, withPermissions = true, content = Content.AL
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getByCid(context, cid) {
|
||||||
|
return await knex.transaction(async tx => {
|
||||||
|
const entity = await rawGetByTx(tx,'cid', cid);
|
||||||
|
|
||||||
|
await shares.enforceEntityPermissionTx(tx, context, 'campaign', entity.id, 'view');
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function _validateAndPreprocess(tx, context, entity, isCreate, content) {
|
async function _validateAndPreprocess(tx, context, entity, isCreate, content) {
|
||||||
if (content === Content.ALL || content === Content.WITHOUT_SOURCE_CUSTOM || content === Content.RSS_ENTRY) {
|
if (content === Content.ALL || content === Content.WITHOUT_SOURCE_CUSTOM || content === Content.RSS_ENTRY) {
|
||||||
await namespaceHelpers.validateEntity(tx, entity);
|
await namespaceHelpers.validateEntity(tx, entity);
|
||||||
|
@ -991,6 +1001,10 @@ async function testSend(context, data) {
|
||||||
attachments: []
|
attachments: []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (campaign.type === CampaignType.RSS) {
|
||||||
|
messageData.rssEntry = await feedcheck.getEntryForPreview(campaign.data.feedUrl);
|
||||||
|
}
|
||||||
|
|
||||||
const attachments = await files.listTx(tx, contextHelpers.getAdminContext(), 'campaign', 'attachment', campaignId);
|
const attachments = await files.listTx(tx, contextHelpers.getAdminContext(), 'campaign', 'attachment', campaignId);
|
||||||
for (const attachment of attachments) {
|
for (const attachment of attachments) {
|
||||||
messageData.attachments.push({
|
messageData.attachments.push({
|
||||||
|
@ -1057,6 +1071,30 @@ async function testSend(context, data) {
|
||||||
senders.scheduleCheck();
|
senders.scheduleCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getRssPreview(context, campaignCid, listCid, subscriptionCid) {
|
||||||
|
const campaign = await getByCid(context, campaignCid);
|
||||||
|
await shares.enforceEntityPermission(context, 'campaign', campaign.id, 'view');
|
||||||
|
|
||||||
|
enforce(campaign.type === CampaignType.RSS);
|
||||||
|
|
||||||
|
const list = await lists.getByCid(context, listCid);
|
||||||
|
await shares.enforceEntityPermission(context, 'list', list.id, 'viewTestSubscriptions');
|
||||||
|
|
||||||
|
const subscription = await subscriptions.getByCid(context, list.id, subscriptionCid);
|
||||||
|
|
||||||
|
if (!subscription.is_test) {
|
||||||
|
shares.throwPermissionDenied();
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = {
|
||||||
|
campaign, // this prevents message sender from fetching the campaign again
|
||||||
|
rssEntry: await feedcheck.getEntryForPreview(campaign.data.feedUrl)
|
||||||
|
};
|
||||||
|
|
||||||
|
return await messageSender.getMessage(campaignCid, listCid, subscriptionCid, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports.Content = Content;
|
module.exports.Content = Content;
|
||||||
module.exports.hash = hash;
|
module.exports.hash = hash;
|
||||||
|
|
||||||
|
@ -1073,6 +1111,7 @@ module.exports.listLinkClicksDTAjax = listLinkClicksDTAjax;
|
||||||
|
|
||||||
module.exports.getByIdTx = getByIdTx;
|
module.exports.getByIdTx = getByIdTx;
|
||||||
module.exports.getById = getById;
|
module.exports.getById = getById;
|
||||||
|
module.exports.getByCid = getByCid;
|
||||||
module.exports.create = create;
|
module.exports.create = create;
|
||||||
module.exports.createRssTx = createRssTx;
|
module.exports.createRssTx = createRssTx;
|
||||||
module.exports.updateWithConsistencyCheck = updateWithConsistencyCheck;
|
module.exports.updateWithConsistencyCheck = updateWithConsistencyCheck;
|
||||||
|
@ -1102,3 +1141,4 @@ module.exports.getStatisticsOpened = getStatisticsOpened;
|
||||||
module.exports.fetchRssCampaign = fetchRssCampaign;
|
module.exports.fetchRssCampaign = fetchRssCampaign;
|
||||||
|
|
||||||
module.exports.testSend = testSend;
|
module.exports.testSend = testSend;
|
||||||
|
module.exports.getRssPreview = getRssPreview;
|
|
@ -420,7 +420,7 @@ async function list(context, listId, grouped, offset, limit) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listTestUsersTx(tx, context, listId, segmentId, grouped) {
|
async function listTestUsersTx(tx, context, listId, segmentId, grouped) {
|
||||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'viewSubscriptions');
|
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'viewTestSubscriptions');
|
||||||
|
|
||||||
let entitiesQry = tx(getSubscriptionTableName(listId)).orderBy('id', 'asc').where('is_test', true).limit(TEST_USERS_LIST_LIMIT);
|
let entitiesQry = tx(getSubscriptionTableName(listId)).orderBy('id', 'asc').where('is_test', true).limit(TEST_USERS_LIST_LIMIT);
|
||||||
|
|
||||||
|
|
69
server/routes/campaigns.js
Normal file
69
server/routes/campaigns.js
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const passport = require('../lib/passport');
|
||||||
|
const routerFactory = require('../lib/router-async');
|
||||||
|
const campaigns = require('../models/campaigns');
|
||||||
|
const lists = require('../models/lists');
|
||||||
|
const users = require('../models/users');
|
||||||
|
const contextHelpers = require('../lib/context-helpers');
|
||||||
|
const { AppType } = require('../../shared/app');
|
||||||
|
|
||||||
|
|
||||||
|
users.registerRestrictedAccessTokenMethod('rssPreview', async ({campaignCid, listCid}) => {
|
||||||
|
|
||||||
|
const campaign = await campaigns.getByCid(contextHelpers.getAdminContext(), campaignCid);
|
||||||
|
const list = await lists.getByCid(contextHelpers.getAdminContext(), listCid);
|
||||||
|
|
||||||
|
return {
|
||||||
|
permissions: {
|
||||||
|
'campaign': {
|
||||||
|
[campaign.id]: new Set(['view'])
|
||||||
|
},
|
||||||
|
'list': {
|
||||||
|
[list.id]: new Set(['view', 'viewTestSubscriptions'])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getRouter(appType) {
|
||||||
|
const router = routerFactory.create();
|
||||||
|
|
||||||
|
if (appType === AppType.SANDBOXED) {
|
||||||
|
|
||||||
|
router.get('/rss-preview/:campaign/:list/:subscription', passport.loggedIn, (req, res, next) => {
|
||||||
|
campaigns.getRssPreview(req.context, req.params.campaign, req.params.list, req.params.subscription)
|
||||||
|
.then(result => {
|
||||||
|
const {html} = result;
|
||||||
|
|
||||||
|
if (html.match(/<\/body\b/i)) {
|
||||||
|
res.render('partials/tracking-scripts', {
|
||||||
|
layout: 'archive/layout-raw'
|
||||||
|
}, (err, scripts) => {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
const htmlWithScripts = scripts ? html.replace(/<\/body\b/i, match => scripts + match) : html;
|
||||||
|
|
||||||
|
res.render('archive/view', {
|
||||||
|
layout: 'archive/layout-raw',
|
||||||
|
message: htmlWithScripts
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
res.render('archive/view', {
|
||||||
|
layout: 'archive/layout-wrapped',
|
||||||
|
message: html
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch(err => next(err));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.getRouter = getRouter;
|
|
@ -1,8 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const passport = require('../lib/passport');
|
const passport = require('../lib/passport');
|
||||||
const shares = require('../models/shares');
|
|
||||||
const contextHelpers = require('../lib/context-helpers');
|
|
||||||
const router = require('../lib/router-async').create();
|
const router = require('../lib/router-async').create();
|
||||||
const subscriptions = require('../models/subscriptions');
|
const subscriptions = require('../models/subscriptions');
|
||||||
const {castToInteger} = require('../lib/helpers');
|
const {castToInteger} = require('../lib/helpers');
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
const config = require('../lib/config');
|
const config = require('../lib/config');
|
||||||
const log = require('../lib/log');
|
const log = require('../lib/log');
|
||||||
const knex = require('../lib/knex');
|
const knex = require('../lib/knex');
|
||||||
const feedparser = require('feedparser-promised');
|
|
||||||
const { CampaignType, CampaignStatus, CampaignSource } = require('../../shared/campaigns');
|
const { CampaignType, CampaignStatus, CampaignSource } = require('../../shared/campaigns');
|
||||||
const campaigns = require('../models/campaigns');
|
const campaigns = require('../models/campaigns');
|
||||||
const contextHelpers = require('../lib/context-helpers');
|
const contextHelpers = require('../lib/context-helpers');
|
||||||
|
const {fetch} = require('../lib/feedcheck');
|
||||||
require('../lib/fork');
|
require('../lib/fork');
|
||||||
|
|
||||||
const { tLog } = require('../lib/translate');
|
const { tLog } = require('../lib/translate');
|
||||||
|
@ -19,39 +19,6 @@ let running = false;
|
||||||
|
|
||||||
let periodicTimeout = null;
|
let periodicTimeout = null;
|
||||||
|
|
||||||
async function fetch(url) {
|
|
||||||
const httpOptions = {
|
|
||||||
uri: url,
|
|
||||||
headers: {
|
|
||||||
'user-agent': 'Mailtrain',
|
|
||||||
'accept': 'text/html,application/xhtml+xml'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const items = await feedparser.parse(httpOptions);
|
|
||||||
|
|
||||||
const entries = [];
|
|
||||||
for (const item of items) {
|
|
||||||
const entry = {
|
|
||||||
title: item.title,
|
|
||||||
date: item.date || item.pubdate || item.pubDate || new Date(),
|
|
||||||
guid: item.guid || item.link,
|
|
||||||
link: item.link,
|
|
||||||
content: item.description || item.summary,
|
|
||||||
summary: item.summary || item.description,
|
|
||||||
imageUrl: item.image.url,
|
|
||||||
};
|
|
||||||
|
|
||||||
if ('mt:entries-json' in item) {
|
|
||||||
entry.customTags = JSON.parse(item['mt:entries-json']['#'])
|
|
||||||
}
|
|
||||||
|
|
||||||
entries.push(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
if (running) {
|
if (running) {
|
||||||
return;
|
return;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue