Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
jhuckabee committed Aug 11, 2011
1 parent 8cb5e28 commit 7f6beea
Show file tree
Hide file tree
Showing 340 changed files with 31,830 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README.md
@@ -0,0 +1,3 @@
Hacker News Reader App

** More to come ***
47 changes: 47 additions & 0 deletions app.js
@@ -0,0 +1,47 @@

/**
* Module dependencies.
*/

var express = require('express'),
hn = require('./lib/hn');

var app = module.exports = express.createServer(),
PORT;

// Configuration

app.configure(function(){
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(express.logger());
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(__dirname + '/public'));
});

app.configure('development', function(){
PORT = 8000;
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
PORT = 80;
app.use(express.errorHandler());
});

// Routes

app.get('/top.json', function(req, res){
hn.topFeed.getJSON(function(json) {
res.send(json);
});
});

// Only listen on $ node app.js

if (!module.parent) {
app.listen(PORT);
console.log("Express server listening on port %d", app.address().port);
}
136 changes: 136 additions & 0 deletions lib/hn/feeds/top.js
@@ -0,0 +1,136 @@
var fs = require('fs'),
libxmljs = require('libxmljs'),
request = require('request'),
NewsItem = require('../news_item').NewsItem;

function TopFeed(config) {
this.config = config;
this.cacheFile = './cache/top.json'
}

/*
* Public API
*/

/*
* Returns top news items as an object array
*/
TopFeed.prototype.get = function(next) {
var that = this;

that._isCached({
yes: function() {
// Load news items from cache
that._fromCache(next);
},
no: function() {
that._getItems(function(items) {
that._respondWith(items, items, next);
});
}
});
};

/*
* Returns top news items as a JSON string
*/
TopFeed.prototype.getJSON = function(next) {
var that = this;

that._isCached({
yes: function() {
// Load news items from cache
that._fromCache(next, 'json');
},
no: function() {
that._getItems(function(items) {
that._respondWith(JSON.stringify(items), items, next);
});
}
});
};

/*
* Private helper methods
*/

TopFeed.prototype._getItems = function(next) {
var that = this;
that._getRSSXML(function(rss) {
that._itemsFromRSS(rss, function(newsItems) {
next(newsItems);
});
});
};

TopFeed.prototype._getRSSXML = function(next) {
request('http://news.ycombinator.com/rss', function(err, resp, body) {
next(libxmljs.parseXmlString(body));
});
};

TopFeed.prototype._itemsFromRSS = function(rss, next) {
var newsItem,
newsItems = [];

rss.find("//rss/channel/item").forEach(function(item) {
newsItem = new NewsItem({
title: item.get('title').text(),
link: item.get('link').text(),
comments: item.get('comments').text()
});
newsItems.push(newsItem);
});

next(newsItems);
};

TopFeed.prototype._respondWith = function(toReturn, toCache, next) {
next(toReturn);
this._cache(toCache);
};

TopFeed.prototype._isCached = function(callbacks) {
var that = this;
fs.stat(that.cacheFile, function(err, stat) {
// Cache miss or cache has expired, load it from RSS feed
if((err && err.code === 'ENOENT') ||
(stat.mtime.getTime() < ((new Date()).getTime() - (that.config.cache_expiration_time*1000*60)))) {
callbacks.no();
}
else {
callbacks.yes();
}
});
};

TopFeed.prototype._fromCache = function(next, returnAs) {
var newsItem,
newsItems = [];

fs.readFile(this.cacheFile, 'utf8', function(err, data) {
if(returnAs === 'json') {
next(data);
}
else {
var items = JSON.parse(data)
items.forEach(function(item) {
newsItem = new NewsItem({
title: item.title,
link: item.link,
comments: item.comments
});
newsItems.push(newsItem);
});
next(newsItems);
}
});
};

TopFeed.prototype._cache = function(newsItems) {
fs.writeFile(this.cacheFile, JSON.stringify(newsItems));
};

module.exports = function(config) {
return new TopFeed(config);
}
5 changes: 5 additions & 0 deletions lib/hn/index.js
@@ -0,0 +1,5 @@
var config = {
cache_expiration_time: 3 // Minutes to keep news items cached
}

exports.topFeed = require('./feeds/top')(config);
16 changes: 16 additions & 0 deletions lib/hn/news_item.js
@@ -0,0 +1,16 @@
var url = require('url');

function NewsItem(defaults) {
this.title = defaults.title || '';
this.link = defaults.link || '';
this.comments = defaults.comments || '';
this.host = this.link === '' ? '' : url.parse(this.link).host;
};

NewsItem.prototype.print = function() {
console.log(this.title);
console.log(this.link);
console.log('');
}

exports.NewsItem = NewsItem;
1 change: 1 addition & 0 deletions node_modules/.bin/express

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions node_modules/connect/.npmignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions node_modules/connect/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions node_modules/connect/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

106 changes: 106 additions & 0 deletions node_modules/connect/lib/connect.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7f6beea

Please sign in to comment.