diff --git a/client/src/account/API.js b/client/src/account/API.js index 9faa196c..a9d1ac9e 100644 --- a/client/src/account/API.js +++ b/client/src/account/API.js @@ -348,6 +348,26 @@ export default class API extends Component {

curl -XGET '{getUrl(`api/lists/test@example.com?access_token=${accessToken}`)}'
+ + +

GET /api/rss/fetch/:campaignCid – {t('Trigger fetch of a campaign')}

+ +

+ {t('Forces the RSS feed check to immediately check the campaign with the given CID (in :campaignCid). It works only for RSS campaigns.')} +

+ +

+ GET {t('arguments')} +

+ + +

+ {t('example')} +

+ +
curl -XGET '{getUrl(`api/rss/fetch/5OOnZKrp0?access_token=${accessToken}`)}'
); } diff --git a/server/config/default.yaml b/server/config/default.yaml index 027ca5ac..76fc16c1 100644 --- a/server/config/default.yaml +++ b/server/config/default.yaml @@ -230,6 +230,10 @@ roles: description: All permissions permissions: [rebuildPermissions, createJavascriptWithROAccess, manageBlacklist, manageSettings, setupAutomation] rootNamespaceRole: master + nobody: + name: None + description: No permissions + permissions: [] namespace: master: @@ -240,7 +244,7 @@ roles: sendConfiguration: [viewPublic, viewPrivate, edit, delete, share, sendWithoutOverrides, sendWithAllowedOverrides, sendWithAnyOverrides] list: [view, edit, delete, share, viewFields, manageFields, viewSubscriptions, manageSubscriptions, viewSegments, manageSegments, viewImports, manageImports] 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] report: [view, edit, delete, share, execute, viewContent, viewOutput] reportTemplate: [view, edit, delete, share, execute] @@ -269,7 +273,11 @@ roles: master: name: Master 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: master: diff --git a/server/lib/campaign-sender.js b/server/lib/campaign-sender.js index ba3b6067..09e92861 100644 --- a/server/lib/campaign-sender.js +++ b/server/lib/campaign-sender.js @@ -312,7 +312,6 @@ class CampaignSender { const sendConfiguration = this.sendConfiguration; const {html, text, attachments} = await this._getMessage(campaign, list, subscriptionGrouped, mergeTags, true); - console.log(html); const campaignAddress = [campaign.cid, list.cid, subscriptionGrouped.cid].join('.'); diff --git a/server/lib/feedcheck.js b/server/lib/feedcheck.js index 586382fb..f2839eed 100644 --- a/server/lib/feedcheck.js +++ b/server/lib/feedcheck.js @@ -5,10 +5,12 @@ const log = require('./log'); const path = require('path'); const senders = require('./senders'); +let messageTid = 0; let feedcheckProcess; module.exports = { - spawn + spawn, + scheduleCheck }; function spawn(callback) { @@ -34,3 +36,13 @@ function spawn(callback) { log.error('Feed', 'Feedcheck process exited with code %s signal %s', code, signal); }); } + +function scheduleCheck() { + feedcheckProcess.send({ + type: 'scheduleCheck', + tid: messageTid + }); + + messageTid++; +} + diff --git a/server/lib/passport.js b/server/lib/passport.js index 64d6b8d9..4128ae7c 100644 --- a/server/lib/passport.js +++ b/server/lib/passport.js @@ -86,15 +86,18 @@ module.exports.loggedIn = (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.json({ error: 'Missing access_token', data: [] }); + return; } - users.getByAccessToken(req.query.access_token).then(user => { + users.getByAccessToken(accessToken).then(user => { req.user = user; next(); }).catch(err => { diff --git a/server/models/campaigns.js b/server/models/campaigns.js index 18bd5bb3..8096f49f 100644 --- a/server/models/campaigns.js +++ b/server/models/campaigns.js @@ -18,6 +18,7 @@ const subscriptions = require('./subscriptions'); const segments = require('./segments'); const senders = require('../lib/senders'); const {LinkId} = require('./links'); +const feedcheck = require('../lib/feedcheck'); 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']; @@ -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.hash = hash; @@ -961,3 +977,5 @@ module.exports.disable = disable; module.exports.rawGetByTx = rawGetByTx; module.exports.getTrackingSettingsByCidTx = getTrackingSettingsByCidTx; module.exports.getStatisticsOpened = getStatisticsOpened; + +module.exports.fetchRssCampaign = fetchRssCampaign; \ No newline at end of file diff --git a/server/routes/api.js b/server/routes/api.js index cac50f76..2dc81884 100644 --- a/server/routes/api.js +++ b/server/routes/api.js @@ -16,6 +16,7 @@ const contextHelpers = require('../lib/context-helpers'); const shares = require('../models/shares'); const slugify = require('slugify'); const passport = require('../lib/passport'); +const campaigns = require('../models/campaigns'); class APIError extends Error { 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; diff --git a/server/services/feedcheck.js b/server/services/feedcheck.js index fe26326a..15f5749c 100644 --- a/server/services/feedcheck.js +++ b/server/services/feedcheck.js @@ -17,6 +17,8 @@ const dbCheckInterval = 60 * 1000; let running = false; +let periodicTimeout = null; + async function fetch(url) { const httpOptions = { uri: url, @@ -161,9 +163,22 @@ async function run() { 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) { process.title = config.title + ': feedcheck'; }