From 572f58ef4f10a10e6784b33b69f218c567428e4b Mon Sep 17 00:00:00 2001 From: Sacha Greif Date: Thu, 4 Jul 2013 12:59:39 +0900 Subject: [PATCH] refactoring API/RSS to use server-side router --- packages/rss/.npm/.gitignore | 1 + packages/rss/.npm/README | 7 + packages/rss/.npm/npm-shrinkwrap.json | 12 ++ packages/rss/package.js | 7 + packages/rss/rss.js | 1 + server/api.js | 29 --- server/helpers/rss/_node-xml.js | 299 -------------------------- server/helpers/rss/node-rss.js | 111 ---------- server/helpers/serve.js | 18 -- server/router.js | 58 +++++ server/rss.js | 23 -- 11 files changed, 86 insertions(+), 480 deletions(-) create mode 100644 packages/rss/.npm/.gitignore create mode 100644 packages/rss/.npm/README create mode 100644 packages/rss/.npm/npm-shrinkwrap.json create mode 100644 packages/rss/package.js create mode 100644 packages/rss/rss.js delete mode 100644 server/api.js delete mode 100644 server/helpers/rss/_node-xml.js delete mode 100644 server/helpers/rss/node-rss.js delete mode 100644 server/helpers/serve.js create mode 100644 server/router.js delete mode 100644 server/rss.js diff --git a/packages/rss/.npm/.gitignore b/packages/rss/.npm/.gitignore new file mode 100644 index 000000000..3c3629e64 --- /dev/null +++ b/packages/rss/.npm/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/packages/rss/.npm/README b/packages/rss/.npm/README new file mode 100644 index 000000000..3d492553a --- /dev/null +++ b/packages/rss/.npm/README @@ -0,0 +1,7 @@ +This directory and the files immediately inside it are automatically generated +when you change this package's NPM dependencies. Commit the files in this +directory (npm-shrinkwrap.json, .gitignore, and this README) to source control +so that others run the same versions of sub-dependencies. + +You should NOT check in the node_modules directory that Meteor automatically +creates; if you are using git, the .gitignore file tells git to ignore it. diff --git a/packages/rss/.npm/npm-shrinkwrap.json b/packages/rss/.npm/npm-shrinkwrap.json new file mode 100644 index 000000000..0724877b4 --- /dev/null +++ b/packages/rss/.npm/npm-shrinkwrap.json @@ -0,0 +1,12 @@ +{ + "dependencies": { + "rss": { + "version": "0.0.4", + "dependencies": { + "xml": { + "version": "0.0.7" + } + } + } + } +} diff --git a/packages/rss/package.js b/packages/rss/package.js new file mode 100644 index 000000000..f25b1d273 --- /dev/null +++ b/packages/rss/package.js @@ -0,0 +1,7 @@ +Package.describe("RSS feed generator"); + +Npm.depends({rss: '0.0.4'}); + +Package.on_use(function (api) { + api.add_files('rss.js', 'server'); +}); \ No newline at end of file diff --git a/packages/rss/rss.js b/packages/rss/rss.js new file mode 100644 index 000000000..7be98168a --- /dev/null +++ b/packages/rss/rss.js @@ -0,0 +1 @@ +RSS = Npm.require('rss'); \ No newline at end of file diff --git a/server/api.js b/server/api.js deleted file mode 100644 index afaa3f367..000000000 --- a/server/api.js +++ /dev/null @@ -1,29 +0,0 @@ -// serve up api at the right url -Meteor.serve('api', function(request) { - var posts = []; - var limit = parseInt(request.query['limit']); - limit = limit ? limit : 100; - Posts.find({status: STATUS_APPROVED}, {sort: {submitted: -1}, limit: limit}).forEach(function(post) { - var url = (post.url ? post.url : getPostUrl(post._id)); - var properties = { - headline: post.headline, - author: post.author, - date: post.submitted, - url: url, - guid: post._id - }; - - if(post.body) - properties['body'] = post.body; - - if(post.url) - properties['domain'] = getDomain(url); - - if(twitterName = getTwitterNameById(post.userId)) - properties['twitterName'] = twitterName; - - posts.push(properties); - }); - - return JSON.stringify(posts); -}); \ No newline at end of file diff --git a/server/helpers/rss/_node-xml.js b/server/helpers/rss/_node-xml.js deleted file mode 100644 index 51b83db73..000000000 --- a/server/helpers/rss/_node-xml.js +++ /dev/null @@ -1,299 +0,0 @@ -// code lifted from: https://github.com/dylang/node-xml -// note: streaming stuff isn't going to work. -// -// (The MIT License) -// -// Copyright (c) 2011 Dylan Greene -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// 'Software'), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -XML = (function() { - -var XML_CHARACTER_MAP = { - '&': '&', - '"': '"', - "'": ''', - '<': '<', - '>': '>' -}; - -var util = { - xml_safe: function(string) { - return string && string.replace ? string.replace(/([&"<>'])/g, function(str, item) { - return XML_CHARACTER_MAP[item]; - }) - : string; - } -} - -var DEFAULT_INDENT = ' '; - -function xml (input, options) { - - if (typeof options != 'object') { - options = { - indent: options - }; - } - - var stream = options.stream ? new Stream() : null, - output = "", - interrupted = false, - indent = !options.indent ? '' - : options.indent === true ? DEFAULT_INDENT - : options.indent, - instant = true; - - - function delay (func) { - if (!instant) { - func(); - } else { - process.nextTick(func); - } - } - - function append (interrupt, out) { - if (out !== undefined) { - output += out; - } - if (interrupt && !interrupted) { - stream = stream || new Stream(); - interrupted = true; - } - if (interrupt && interrupted) { - var data = output; - delay(function () { stream.emit('data', data) }); - output = ""; - } - } - - function add (value, last) { - format(append, resolve(value, indent, indent ? 1 : 0), last); - } - - function end() { - if (stream) { - var data = output; - delay(function () { stream.emit('data', data) }); - - stream.emit('end'); - stream.readable = false; - stream.emit('close'); - } - } - - // disable delay delayed - delay(function () { instant = false }); - - if (input && input.forEach) { - input.forEach(function (value, i) { - var last; - if (i + 1 === input.length) - last = end; - add(value, last); - }); - } else { - add(input, end); - } - - if (stream) { - stream.readable = true; - return stream; - } - return output; -} - -function element (/*input, …*/) { - var input = Array.prototype.slice.call(arguments), - self = { - _elem: resolve(input) - }; - - self.push = function (input) { - if (!this.append) { - throw new Error("not assigned to a parent!"); - } - var that = this; - var indent = this._elem.indent; - format(this.append, resolve( - input, indent, this._elem.icount + (indent ? 1 : 0)), - function () { that.append(true) }); - }; - - self.close = function (input) { - if (input !== undefined) { - this.push(input); - } - if (this.end) { - this.end(); - } - }; - - return self; -} - -function create_indent(character, count) { - return (new Array(count || 0).join(character || '')) -} - -function resolve(data, indent, indent_count) { - indent_count = indent_count || 0; - var indent_spaces = create_indent(indent, indent_count); - var name; - var values = data; - var interrupt = false; - - if (typeof data == 'object') { - var keys = Object.keys(data); - name = keys[0]; - values = data[name]; - - if (values._elem) { - values._elem.name = name; - values._elem.icount = indent_count; - values._elem.indent = indent; - values._elem.indents = indent_spaces; - values._elem.interrupt = values; - return values._elem; - } - } - - var attributes = [], - content = []; - - function get_attributes(obj){ - var keys = Object.keys(obj); - keys.forEach(function(key){ - attributes.push(attribute(key, obj[key])); - }); - } - - switch(typeof values) { - case 'object': - if (values === null) break; - - if (values._attr) { - get_attributes(values._attr); - } - - if (values._cdata) { - content.push(''); - } - - if (values.forEach) { - content.push(''); - values.forEach(function(value) { - if (typeof value == 'object') { - var _name = Object.keys(value)[0]; - - if (_name == '_attr') { - get_attributes(value._attr); - } else { - content.push(resolve( - value, indent, indent_count + 1)); - } - } else { - //string - content.push(create_indent( - indent, indent_count + 1) + util.xml_safe(value)); - } - - }); - content.push(''); - } - break; - - default: - //string - content.push(util.xml_safe(values)); - - } - - return { - name: name, - interrupt: interrupt, - attributes: attributes, - content: content, - icount: indent_count, - indents: indent_spaces, - indent: indent - }; -} - -function format(append, elem, end) { - - if (typeof elem != 'object') { - return append(false, elem); - } - - var len = elem.interrupt ? 1 : elem.content.length; - - function proceed () { - while (elem.content.length) { - var value = elem.content.shift(); - - if (value === undefined) continue; - if (interrupt(value)) return; - - format(append, value); - } - - append(false, (len > 1 ? elem.indents : '') - + (elem.name ? '' : '') - + (elem.indent && !end ? '\n' : '')); - - if (end) { - end(); - } - } - - function interrupt(value) { - if (value.interrupt) { - value.interrupt.append = append; - value.interrupt.end = proceed; - value.interrupt = false; - append(true); - return true; - } - return false; - } - - append(false, elem.indents - + (elem.name ? '<' + elem.name : '') - + (elem.attributes.length ? ' ' + elem.attributes.join(' ') : '') - + (len ? (elem.name ? '>' : '') : (elem.name ? '/>' : '')) - + (elem.indent && len > 1 ? '\n' : '')); - - if (!len) return append(false, elem.indent ? '\n' : ''); - - if (!interrupt(elem)) { - proceed(); - } -} - -function attribute(key, value) { - return key + '=' + '"' + util.xml_safe(value) + '"'; -} - -xml.Element = element; - -return xml; -}()); \ No newline at end of file diff --git a/server/helpers/rss/node-rss.js b/server/helpers/rss/node-rss.js deleted file mode 100644 index 147684562..000000000 --- a/server/helpers/rss/node-rss.js +++ /dev/null @@ -1,111 +0,0 @@ -// code lifted from: https://github.com/dylang/node-rss -// -// (The MIT License) -// -// Copyright (c) 2011 Dylan Greene -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// 'Software'), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -RSS = (function() { - -function RSS (options, items) { - options = options || {}; - - this.title = options.title || 'Untitled RSS Feed'; - this.description = options.description || ''; - this.feed_url = options.feed_url; - this.site_url = options.site_url; - this.image_url = options.image_url; - this.author = options.author; - this.items = items || []; - - this.item = function (options) { - options = options || {}; - var item = { - title: options.title || 'No title', - description: options.description || '', - url: options.url, - guid: options.guid, - categories: options.categories || [], - author: options.author, - date: options.date - }; - - this.items.push(item); - return this; - }; - - this.xml = function(indent) { - return '\n' - + XML(generateXML(this), indent); - } - -} - -function ifTruePush(bool, array, data) { - if (bool) { - array.push(data); - } -} - -function generateXML (data){ - // todo: xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" - - var channel = []; - channel.push({ title: { _cdata: data.title } }); - channel.push({ description: { _cdata: data.description || data.title } }); - channel.push({ link: data.site_url || 'http://github.com/dylan/node-rss' }); - // image_url set? - if (data.image_url) { - channel.push({ image: [ {url: data.image_url}, {title: data.title}, {link: data.site_url} ] }); - } - channel.push({ generator: 'NodeJS RSS Module' }); - channel.push({ lastBuildDate: new Date().toGMTString() }); - - ifTruePush(data.feed_url, channel, { 'atom:link': { _attr: { href: data.feed_url, rel: 'self', type: 'application/rss+xml' } } }); - // { updated: new Date().toGMTString() } - - - data.items.forEach(function(item) { - var item_values = [ - { title: { _cdata: item.title } } - ]; - ifTruePush(item.description, item_values, { description: { _cdata: item.description } }); - ifTruePush(item.url, item_values, { link: item.url }); - ifTruePush(item.link || item.guid || item.title, item_values, { guid: [ { _attr: { isPermaLink: !item.guid && !!item.url } }, item.guid || item.url || item.title ] }); - - ifTruePush(item.author || data.author, item_values, { 'dc:creator': { _cdata: item.author || data.author } }); - ifTruePush(item.date, item_values, { pubDate: new Date(item.date).toGMTString() }); - channel.push({ item: item_values }); - }); - - return { rss: [ - { _attr: { - 'xmlns:dc': 'http://purl.org/dc/elements/1.1/', - 'xmlns:content': 'http://purl.org/rss/1.0/modules/content/', - 'xmlns:atom': 'http://www.w3.org/2005/Atom', - version: '2.0' - } }, - { channel: channel } - ] }; -} - -return RSS; -}()); \ No newline at end of file diff --git a/server/helpers/serve.js b/server/helpers/serve.js deleted file mode 100644 index a8731b922..000000000 --- a/server/helpers/serve.js +++ /dev/null @@ -1,18 +0,0 @@ -// Very simple function to serve a particular function at a particular URL - -// The request's path has to be _exactly_ path. -// Sure, we could make it smarter, but I'm guessing this isn't the way things -// will work long term. -Meteor.serve = function(path, fn) { -var connect = (typeof(Npm) == "undefined") ? __meteor_bootstrap__.require("connect") : Npm.require("connect"); - __meteor_bootstrap__.app - .use(connect.query()) // <- XXX: we can probably assume accounts did this - .use(function(req, res, next) { - var test = ('/' + path); - if (req.url.substring(0, test.length) !== test) - return next(); - - // just run fn() and return it to the requester - res.end(fn(req)) - }); -} diff --git a/server/router.js b/server/router.js new file mode 100644 index 000000000..065da4c1b --- /dev/null +++ b/server/router.js @@ -0,0 +1,58 @@ +serveAPI = function(limit){ + var posts = []; + limit = typeof limit == 'undefined' ? 100 : limit; + console.log(limit) + Posts.find({status: STATUS_APPROVED}, {sort: {submitted: -1}, limit: limit}).forEach(function(post) { + var url = (post.url ? post.url : getPostUrl(post._id)); + var properties = { + headline: post.headline, + author: post.author, + date: post.submitted, + url: url, + guid: post._id + }; + + if(post.body) + properties['body'] = post.body; + + if(post.url) + properties['domain'] = getDomain(url); + + if(twitterName = getTwitterNameById(post.userId)) + properties['twitterName'] = twitterName; + + posts.push(properties); + }); + + return JSON.stringify(posts); +} + +Meteor.Router.add({ + '/feed.xml': function() { + var feed = new RSS({ + title: getSetting('title'), + description: getSetting('tagline'), + feed_url: Meteor.absoluteUrl()+'feed.xml', + site_url: Meteor.absoluteUrl(), + image_url: Meteor.absoluteUrl()+'img/favicon.png', + }); + + Posts.find({status: STATUS_APPROVED}, {sort: {submitted: -1}}).forEach(function(post) { + feed.item({ + title: post.headline, + description: post.body+'

Comments', + author: post.author, + date: post.submitted, + url: (post.url ? post.url : getPostUrl(post._id)), + guid: post._id + }); + }); + + return feed.xml(); + }, + + '/api/': serveAPI, + + '/api/:limit': serveAPI + +}); diff --git a/server/rss.js b/server/rss.js deleted file mode 100644 index d36826afa..000000000 --- a/server/rss.js +++ /dev/null @@ -1,23 +0,0 @@ -// serve up RSS at the right url -Meteor.serve('feed.xml', function() { - var feed = new RSS({ - title: getSetting('title'), - description: getSetting('tagline'), - feed_url: Meteor.absoluteUrl()+'feed.xml', - site_url: Meteor.absoluteUrl(), - image_url: Meteor.absoluteUrl()+'img/favicon.png', - }); - - Posts.find({status: STATUS_APPROVED}, {sort: {submitted: -1}}).forEach(function(post) { - feed.item({ - title: post.headline, - description: post.body+'

Comments', - author: post.author, - date: post.submitted, - url: (post.url ? post.url : getPostUrl(post._id)), - guid: post._id - }); - }); - - return feed.xml(); -}); \ No newline at end of file