Added an API endpoint that triggers an RSS campaign.

This commit is contained in:
Tomas Bures 2018-12-29 15:12:42 +01:00
parent e786964411
commit 428fb9db7b
8 changed files with 88 additions and 7 deletions

View file

@ -348,6 +348,26 @@ export default class API extends Component {
</p> </p>
<pre>curl -XGET '{getUrl(`api/lists/test@example.com?access_token=${accessToken}`)}'</pre> <pre>curl -XGET '{getUrl(`api/lists/test@example.com?access_token=${accessToken}`)}'</pre>
<h4>GET /api/rss/fetch/:campaignCid {t('Trigger fetch of a campaign')}</h4>
<p>
{t('Forces the RSS feed check to immediately check the campaign with the given CID (in :campaignCid). It works only for RSS campaigns.')}
</p>
<p>
<strong>GET</strong> {t('arguments')}
</p>
<ul>
<li><strong>access_token</strong> {t('yourPersonalAccessToken')}</li>
</ul>
<p>
<strong>{t('example')}</strong>
</p>
<pre>curl -XGET '{getUrl(`api/rss/fetch/5OOnZKrp0?access_token=${accessToken}`)}'</pre>
</div> </div>
); );
} }

View file

@ -230,6 +230,10 @@ roles:
description: All permissions description: All permissions
permissions: [rebuildPermissions, createJavascriptWithROAccess, manageBlacklist, manageSettings, setupAutomation] permissions: [rebuildPermissions, createJavascriptWithROAccess, manageBlacklist, manageSettings, setupAutomation]
rootNamespaceRole: master rootNamespaceRole: master
nobody:
name: None
description: No permissions
permissions: []
namespace: namespace:
master: master:
@ -240,7 +244,7 @@ roles:
sendConfiguration: [viewPublic, viewPrivate, edit, delete, share, sendWithoutOverrides, sendWithAllowedOverrides, sendWithAnyOverrides] sendConfiguration: [viewPublic, viewPrivate, edit, delete, share, sendWithoutOverrides, sendWithAllowedOverrides, sendWithAnyOverrides]
list: [view, edit, delete, share, viewFields, manageFields, viewSubscriptions, manageSubscriptions, viewSegments, manageSegments, viewImports, manageImports] list: [view, edit, delete, share, viewFields, manageFields, viewSubscriptions, manageSubscriptions, viewSegments, manageSegments, viewImports, manageImports]
customForm: [view, edit, delete, share] customForm: [view, edit, delete, share]
campaign: [view, edit, delete, share, viewFiles, manageFiles, viewAttachments, manageAttachments, viewTriggers, manageTriggers, send, viewStats] campaign: [view, edit, delete, share, viewFiles, manageFiles, viewAttachments, manageAttachments, viewTriggers, manageTriggers, send, viewStats, fetchRss]
template: [view, edit, delete, share, viewFiles, manageFiles] template: [view, edit, delete, share, viewFiles, manageFiles]
report: [view, edit, delete, share, execute, viewContent, viewOutput] report: [view, edit, delete, share, execute, viewContent, viewOutput]
reportTemplate: [view, edit, delete, share, execute] reportTemplate: [view, edit, delete, share, execute]
@ -269,7 +273,11 @@ roles:
master: master:
name: Master name: Master
description: All permissions description: All permissions
permissions: [view, edit, delete, share, viewFiles, manageFiles, viewAttachments, manageAttachments, viewTriggers, manageTriggers, send, viewStats, manageMessages] permissions: [view, edit, delete, share, viewFiles, manageFiles, viewAttachments, manageAttachments, viewTriggers, manageTriggers, send, viewStats, manageMessages, fetchRss]
rssTrigger:
name: RSS Campaign Trigger
description: Allows triggering a fetch of an RSS campaign
permissions: [fetchRss]
template: template:
master: master:

View file

@ -312,7 +312,6 @@ class CampaignSender {
const sendConfiguration = this.sendConfiguration; const sendConfiguration = this.sendConfiguration;
const {html, text, attachments} = await this._getMessage(campaign, list, subscriptionGrouped, mergeTags, true); const {html, text, attachments} = await this._getMessage(campaign, list, subscriptionGrouped, mergeTags, true);
console.log(html);
const campaignAddress = [campaign.cid, list.cid, subscriptionGrouped.cid].join('.'); const campaignAddress = [campaign.cid, list.cid, subscriptionGrouped.cid].join('.');

View file

@ -5,10 +5,12 @@ const log = require('./log');
const path = require('path'); const path = require('path');
const senders = require('./senders'); const senders = require('./senders');
let messageTid = 0;
let feedcheckProcess; let feedcheckProcess;
module.exports = { module.exports = {
spawn spawn,
scheduleCheck
}; };
function spawn(callback) { function spawn(callback) {
@ -34,3 +36,13 @@ function spawn(callback) {
log.error('Feed', 'Feedcheck process exited with code %s signal %s', code, signal); log.error('Feed', 'Feedcheck process exited with code %s signal %s', code, signal);
}); });
} }
function scheduleCheck() {
feedcheckProcess.send({
type: 'scheduleCheck',
tid: messageTid
});
messageTid++;
}

View file

@ -86,15 +86,18 @@ module.exports.loggedIn = (req, res, next) => {
}; };
module.exports.authByAccessToken = (req, res, next) => { module.exports.authByAccessToken = (req, res, next) => {
if (!req.query.access_token) { const accessToken = req.get('access-token') || req.query.access_token
if (!accessToken) {
res.status(403); res.status(403);
res.json({ res.json({
error: 'Missing access_token', error: 'Missing access_token',
data: [] data: []
}); });
return;
} }
users.getByAccessToken(req.query.access_token).then(user => { users.getByAccessToken(accessToken).then(user => {
req.user = user; req.user = user;
next(); next();
}).catch(err => { }).catch(err => {

View file

@ -18,6 +18,7 @@ const subscriptions = require('./subscriptions');
const segments = require('./segments'); const segments = require('./segments');
const senders = require('../lib/senders'); const senders = require('../lib/senders');
const {LinkId} = require('./links'); const {LinkId} = require('./links');
const feedcheck = require('../lib/feedcheck');
const allowedKeysCommon = ['name', 'description', 'segment', 'namespace', const allowedKeysCommon = ['name', 'description', 'segment', 'namespace',
'send_configuration', 'from_name_override', 'from_email_override', 'reply_to_override', 'subject_override', 'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url']; 'send_configuration', 'from_name_override', 'from_email_override', 'reply_to_override', 'subject_override', 'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url'];
@ -921,6 +922,21 @@ async function getStatisticsOpened(context, id) {
}); });
} }
async function fetchRssCampaign(context, cid) {
return await knex.transaction(async tx => {
const campaign = await tx('campaigns').where('cid', cid).select(['id', 'type']).first();
await shares.enforceEntityPermissionTx(tx, context, 'campaign', campaign.id, 'fetchRss');
enforce(campaign.type === CampaignType.RSS, 'Invalid campaign type');
await tx('campaigns').where('id', campaign.id).update('last_check', null);
feedcheck.scheduleCheck();
});
}
module.exports.Content = Content; module.exports.Content = Content;
module.exports.hash = hash; module.exports.hash = hash;
@ -961,3 +977,5 @@ module.exports.disable = disable;
module.exports.rawGetByTx = rawGetByTx; module.exports.rawGetByTx = rawGetByTx;
module.exports.getTrackingSettingsByCidTx = getTrackingSettingsByCidTx; module.exports.getTrackingSettingsByCidTx = getTrackingSettingsByCidTx;
module.exports.getStatisticsOpened = getStatisticsOpened; module.exports.getStatisticsOpened = getStatisticsOpened;
module.exports.fetchRssCampaign = fetchRssCampaign;

View file

@ -16,6 +16,7 @@ const contextHelpers = require('../lib/context-helpers');
const shares = require('../models/shares'); const shares = require('../models/shares');
const slugify = require('slugify'); const slugify = require('slugify');
const passport = require('../lib/passport'); const passport = require('../lib/passport');
const campaigns = require('../models/campaigns');
class APIError extends Error { class APIError extends Error {
constructor(msg, status) { constructor(msg, status) {
@ -279,5 +280,10 @@ router.getAsync('/blacklist/get', passport.loggedIn, async (req, res) => {
}); });
}); });
router.getAsync('/rss/fetch/:campaignCid', passport.loggedIn, async (req, res) => {
await campaigns.fetchRssCampaign(req.context, req.params.campaignCid);
return res.json();
});
module.exports = router; module.exports = router;

View file

@ -17,6 +17,8 @@ const dbCheckInterval = 60 * 1000;
let running = false; let running = false;
let periodicTimeout = null;
async function fetch(url) { async function fetch(url) {
const httpOptions = { const httpOptions = {
uri: url, uri: url,
@ -161,9 +163,22 @@ async function run() {
running = false; running = false;
setTimeout(run, dbCheckInterval); periodicTimeout = setTimeout(run, dbCheckInterval);
} }
process.on('message', msg => {
if (msg) {
const type = msg.type;
if (type === 'scheduleCheck') {
if (periodicTimeout) {
clearTimeout(periodicTimeout);
setImmediate(run);
}
}
}
});
if (config.title) { if (config.title) {
process.title = config.title + ': feedcheck'; process.title = config.title + ': feedcheck';
} }