From 9e7469e3eae191c440ffa78c7931316d646bcc6b Mon Sep 17 00:00:00 2001 From: Fabian Becker Date: Fri, 14 Mar 2014 22:10:50 +0000 Subject: [PATCH] Add XML-RPC ping closes #2148 - Added core/server/xmlrpc.js - Hook into post::saved to ping when a published post gets saved - Added node package to hook into http requests --- core/server/models/post.js | 8 ++++- core/server/xmlrpc.js | 65 +++++++++++++++++++++++++++++++++++ core/test/unit/xmlrpc_spec.js | 46 +++++++++++++++++++++++++ package.json | 4 ++- 4 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 core/server/xmlrpc.js create mode 100644 core/test/unit/xmlrpc_spec.js diff --git a/core/server/models/post.js b/core/server/models/post.js index efc4732b5f..450cc21b28 100644 --- a/core/server/models/post.js +++ b/core/server/models/post.js @@ -11,6 +11,7 @@ var _ = require('lodash'), Tags = require('./tag').Tags, ghostBookshelf = require('./base'), validation = require('../data/validation'), + xmlrpc = require('../xmlrpc'), Post, Posts; @@ -29,7 +30,12 @@ Post = ghostBookshelf.Model.extend({ initialize: function () { var self = this; this.on('creating', this.creating, this); - this.on('saved', this.updateTags, this); + this.on('saved', function (model, attributes, options) { + if (model.get('status') === 'published') { + xmlrpc.ping(model.attributes); + } + return self.updateTags(model, attributes, options); + }); this.on('saving', function (model, attributes, options) { return when(self.saving(model, attributes, options)).then(function () { return self.validate(model, attributes, options); diff --git a/core/server/xmlrpc.js b/core/server/xmlrpc.js new file mode 100644 index 0000000000..0b70520a98 --- /dev/null +++ b/core/server/xmlrpc.js @@ -0,0 +1,65 @@ +var _ = require('lodash'), + config = require('./config'), + errors = require('./errorHandling'), + http = require('http'), + xml = require('xml'), + pingList; + +// ToDo: Make this configurable +pingList = [ + { host: 'blogsearch.google.com', path: '/ping/RPC2' }, + { host: 'rpc.pingomatic.com', path: '/' } +]; + +function ping(post) { + var pingXML, + title = post.title; + + // Only ping when in production and not a page + if (process.env.NODE_ENV !== 'production' || post.page) { + return; + } + + // Need to require here because of circular dependency + return config.urlForPost(require('./api').settings, post, true).then(function (url) { + + // Build XML object. + pingXML = xml({ + methodCall: [ + { methodName: 'weblogUpdate.ping' }, + { + params: [{ + param: [{ value: [{ string: title }]}], + }, { + param: [{ value: [{ string: url }]}], + }] + } + ] + }, {declaration: true}); + + // Ping each of the defined services. + _.each(pingList, function (pingHost) { + var options = { + hostname: pingHost.host, + path: pingHost.path, + method: 'POST' + }, + req; + + req = http.request(options); + req.write(pingXML); + req.on('error', function (error) { + errors.logError( + error, + "Pinging services for updates on your blog failed, your blog will continue to function.", + "If you get this error repeatedly, please seek help from https://ghost.org/forum." + ); + }); + req.end(); + }); + }); +} + +module.exports = { + ping: ping +}; \ No newline at end of file diff --git a/core/test/unit/xmlrpc_spec.js b/core/test/unit/xmlrpc_spec.js new file mode 100644 index 0000000000..312d885e65 --- /dev/null +++ b/core/test/unit/xmlrpc_spec.js @@ -0,0 +1,46 @@ +/*globals describe, beforeEach, afterEach, it*/ +var assert = require('assert'), + http = require('http'), + nock = require('nock'), + settings = require('../../server/api').settings; + should = require('should'), + sinon = require('sinon'), + testUtils = require('../utils'), + when = require('when'), + xmlrpc = require('../../server/xmlrpc'), + // storing current environment + currentEnv = process.env.NODE_ENV; + +describe('XMLRPC', function () { + var sandbox; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + // give environment a value that will ping + process.env.NODE_ENV = "production"; + }); + + afterEach(function () { + sandbox.restore(); + // reset the environment + process.env.NODE_ENV = currentEnv; + }); + + + it('should execute two pings', function (done) { + var ping1 = nock('http://blogsearch.google.com').post('/ping/RPC2').reply(200), + ping2 = nock('http://rpc.pingomatic.com').post('/').reply(200), + testPost = testUtils.DataGenerator.Content.posts[2], + settingsStub = sandbox.stub(settings, 'read', function () { + return when({value: '/:slug/'}); + }); + + xmlrpc.ping(testPost).then(function () { + ping1.isDone().should.be.true; + ping2.isDone().should.be.true; + + done(); + }); + }); + +}); diff --git a/package.json b/package.json index e7064beaa1..0752a2286b 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,8 @@ "sqlite3": "2.2.0", "unidecode": "0.1.3", "validator": "3.4.0", - "when": "2.7.0" + "when": "2.7.0", + "xml": "0.0.12" }, "optionalDependencies": { "mysql": "2.1.1" @@ -77,6 +78,7 @@ "grunt-update-submodules": "~0.2.1", "matchdep": "~0.3.0", "mocha": "~1.15.1", + "nock": "0.27.2", "rewire": "~2.0.0", "request": "~2.29.0", "require-dir": "~0.1.0",