From bbcbe8d871fad47ded677c0e221c90dfaac8fca3 Mon Sep 17 00:00:00 2001 From: Jonathan Cremin Date: Tue, 6 Jan 2015 12:58:57 +0000 Subject: [PATCH] Refactor searching --- app.js | 13 ++++++-- lib/lookup.js | 29 ++++++++++++++++++ lib/services.js | 13 ++++++++ routes/index.js | 16 ++++++---- routes/search.js | 78 +++++++++++++++++------------------------------- routes/share.js | 76 +++++++++++++--------------------------------- test/lookup.js | 15 ++++++++++ views/home.jsx | 26 +++++++++++++++- views/share.jsx | 11 +++---- 9 files changed, 155 insertions(+), 122 deletions(-) create mode 100644 lib/lookup.js create mode 100644 lib/services.js create mode 100644 test/lookup.js diff --git a/app.js b/app.js index 58d69ec..8ee1f3b 100644 --- a/app.js +++ b/app.js @@ -2,7 +2,6 @@ var express = require('express'); var helmet = require('helmet'); var path = require('path'); -var url = require('url'); var favicon = require('serve-favicon'); var logger = require('morgan'); var session = require('express-session'); @@ -79,15 +78,22 @@ app.get('*', function(req,res,next) { app.get('/', index); app.post('/search', search); -app.get('/:service/:type/:id', share); +app.get('/:service/:type/:id.:format?', share); app.get('/itunes/*', itunesProxy); app.get('/recent', function(req, res, next) { req.db.matches.find().sort({created_at:-1}).limit(6).toArray().then(function(docs){ var recents = []; docs.forEach(function(doc) { - recents.push(doc.services[doc._id.split("$$")[0]]); + doc.services.some(function(item) { + if (item.service == doc._id.split("$$")[0]) { + recents.push(item); + return false; + } + }); }); res.json({recents:recents}); + }).catch(function (error) { + return next(error); }); }); @@ -104,6 +110,7 @@ app.use(function(req, res, next) { // will print stacktrace if (app.get('env') === 'development') { app.use(function(err, req, res, next) { + console.log(err) res.status(err.status || 500); var content = React.renderToString(ErrorView({status: err.status || 500, message: err.message, error: err})); diff --git a/lib/lookup.js b/lib/lookup.js new file mode 100644 index 0000000..7776710 --- /dev/null +++ b/lib/lookup.js @@ -0,0 +1,29 @@ +"use strict"; +var path = require('path'); + +var services = []; + +require("fs").readdirSync(path.join(__dirname, "services")).forEach(function(file) { + var service = require(path.join(__dirname, "services", file)); + if (service.search) { + services.push(service); + } +}); + +module.exports = function(url) { + + var matchedService; + services.some(function(service) { + matchedService = service.match(url) ? service : null; + return matchedService; + }); + + if (matchedService) { + return matchedService.parseUrl(url).timeout(10000).then(function(result) { + return matchedService.lookupId(result.id, result.type).then(function(item) { + return item; + }); + }); + } + return false; +}; diff --git a/lib/services.js b/lib/services.js new file mode 100644 index 0000000..4f30c9d --- /dev/null +++ b/lib/services.js @@ -0,0 +1,13 @@ +"use strict"; +var path = require('path'); +var Promise = require("bluebird"); + +module.exports = []; + +require("fs").readdirSync(path.join(__dirname, "services")).forEach(function(file) { + var service = require(path.join(__dirname, "services", file)); + if (service.search) { + module.exports.push(service); + } +}); + diff --git a/routes/index.js b/routes/index.js index 89b9ce4..0ee415e 100644 --- a/routes/index.js +++ b/routes/index.js @@ -10,17 +10,21 @@ module.exports = function(req, res, next) { req.db.matches.find().sort({created_at:-1}).limit(6).toArray().then(function(docs){ var recents = []; docs.forEach(function(doc) { - if (doc._id.indexOf("$$") > -1) { - recents.push(doc.services[doc._id.split("$$")[0]]); - } + var shares = Object.keys(doc.services).map(function (key) {return doc.services[key]}); + shares.some(function(item) { + if (item.service == doc._id.split("$$")[0]) { + recents.push(item); + return false; + } + }); }); Router.run(routes, req.url, function (Handler) { var App = React.createFactory(Handler); - var content = React.renderToString(App({recents: recents})); + var content = React.renderToString(new App({recents: recents})); res.send('\n' + content.replace("", "")); }); - }).catch(function(err) { - console.log(err) + }).catch(function(error) { + next(error); }); } \ No newline at end of file diff --git a/routes/search.js b/routes/search.js index a35921a..3102cb4 100644 --- a/routes/search.js +++ b/routes/search.js @@ -1,60 +1,36 @@ "use strict"; var parse = require('url').parse; var path = require('path'); - -var services = {}; - -require("fs").readdirSync(path.join(__dirname, "..", "lib", "services")).forEach(function(file) { - var service = require("../lib/services/" + file); - if (service.search) { - services[service.id] = service; - } -}); +var lookup = require('../lib/lookup'); +var services = require('../lib/services'); module.exports = function(req, res, next) { var url = parse(req.body.url); - var searching = false; - if (!url.host) { - res.json({error:{message:"You need to submit a url."}}); - } else { - var items = {}; - for (var id in services) { - items[id] = {service: id}; - } - for (var id in services) { - var matched = services[id].match(req.body.url); - if (matched) { - searching = true; - services[id].parseUrl(req.body.url).timeout(10000).then(function(result) { - if (!result.id) { - res.json({error:{message:"No match found for url"}}); - } else { - services[id].lookupId(result.id, result.type).then(function(item) { - items[id] = item; - req.db.matches.save({_id:id + "$$" + result.id, created_at: new Date(), services:items}).then(function() { - res.json(item); - }); - }); - } - }, function(error) { - if (error.code == "ETIMEDOUT") { - error = new Error("Error talking to music service"); - error.status = 502; - next(error); - } else if (!error || !error.status) { - error = new Error("An unexpected error happenend"); - error.status = 500; - next(error); - } - - res.json({error:{message:"No match found for url"}}); - }); - break; + return res.json({error:{message:"You need to submit a url."}}); + } + + lookup(req.body.url).then(function(item) { + item.matched_at = new Date(); + var matches = {}; + matches[item.service] = item; + services.forEach(function(service) { + if (service.id == item.service) { + return; } - } - } - if (url.host && !searching) { - res.json({error:{message:"No match found for url"}}); - } + matches[service.id] = {service: service.id}; + service.search(item).then(function(match) { + match.matched_at = new Date(); + var update = {}; + update["services." + match.service] = match; + req.db.matches.update({_id: item.service + "$$" + item.id}, {"$set": update}); + }); + }); + return req.db.matches.save({_id: item.service + "$$" + item.id, created_at: new Date(), services:matches}).then(function() { + res.json(item); + }); + }, function(error) { + console.log(error.stack) + res.json({error: "No matches found for url"}); + }); }; diff --git a/routes/share.js b/routes/share.js index 24b60db..c2e6ae3 100644 --- a/routes/share.js +++ b/routes/share.js @@ -9,72 +9,36 @@ var Router = require('react-router'); var nodejsx = require('node-jsx').install(); var routes = require('../views/app.jsx').routes; -var services = {}; - -require("fs").readdirSync(path.join(__dirname, "..", "lib", "services")).forEach(function(file) { - var service = require("../lib/services/" + file); - if (service.search) { - services[service.id] = service; - } -}); +var services = require('../lib/services'); module.exports = function(req, res, next) { var serviceId = req.params.service; var type = req.params.type; var itemId = req.params.id; - var promises = []; - if (!services[serviceId] || (type != "album" && type != "track")) { - next(); - return; + var matchedService; + services.some(function(service) { + matchedService = serviceId == service.id ? service : null; + return matchedService; + }); + + if (!matchedService || (type != "album" && type != "track")) { + return next(); } - req.db.matches.findOne({_id:serviceId + "$$" + itemId}, function(err, doc) { - if (err) { - return next(new Error()); - } else if (!doc) { - return next(); + return req.db.matches.findOne({_id:serviceId + "$$" + itemId}).then(function(doc) { + var shares = Object.keys(doc.services).map(function (key) {return doc.services[key]}); + if (req.params.format == "json") { + return res.json({shares:shares}); } - var shares = []; - for (var docService in Object.keys(services)) { - var loopServiceId = Object.keys(services)[docService]; - shares.push(doc.services[loopServiceId]); - if (doc.services[loopServiceId].id === undefined) { - services[loopServiceId].search(doc.services[serviceId]).timeout(15000).then(function(item) { - if (!item.id) { - item.id = null; - } - - var set = {}; - set["services." + item.service] = item; - req.db.matches.update({_id: serviceId + "$$" + itemId}, {$set: set}); - }).catch(function(err) { - console.log(err) - }); - } - } - - var shares = shares.filter(function(item) { - return item.service != serviceId; + Router.run(routes, req.url, function (Handler) { + var App = React.createFactory(Handler); + var content = React.renderToString(App({shares: shares})); + res.send('\n' + content.replace("", "")); }); - - shares.sort(function(a, b) { - return a.type == "video" && b.type != "video"; - }); - - shares.unshift(doc.services[serviceId]); - if (req.accepts(['html', 'json']) === 'json') { - req.db.matches.findOne({_id:serviceId + "$$" + itemId}, function(err, doc) { - res.json({shares:shares}); - }); - } else { - Router.run(routes, req.url, function (Handler) { - var App = React.createFactory(Handler); - var content = React.renderToString(App({shares: shares})); - res.send('\n' + content.replace("", "")); - }); - } - + }).catch(function (error) { + return next(error); }); + }; diff --git a/test/lookup.js b/test/lookup.js new file mode 100644 index 0000000..021c254 --- /dev/null +++ b/test/lookup.js @@ -0,0 +1,15 @@ +"use strict"; +require('should'); + +var search = require("../lib/search"); + +describe('Search with url', function(){ + + it('should find album by url', function(done){ + search("https://play.google.com/music/listen#/album/Bz6wrjczddcj5hurijsv6ohdoay").then(function(result) { + result.name.should.equal("Phase 5"); + done(); + }); + }); + +}); \ No newline at end of file diff --git a/views/home.jsx b/views/home.jsx index e36675c..15415b4 100644 --- a/views/home.jsx +++ b/views/home.jsx @@ -39,25 +39,49 @@ var SearchForm = React.createClass({ mixins: [ Router.Navigation, Router.State ], + getInitialState: function () { + return { + submitting: true + }; + }, + handleSubmit: function(e) { + this.setState({ + submitting: true + }); var that = this; e.preventDefault(); var url = this.refs.url.getDOMNode().value.trim(); if (!url) { + that.setState({ + submitting: false + }); return; } request.post('/search').send({url:url}).end(function(res) { + that.setState({ + submitting: false + }); + if (res.body.error) { + return alert(res.body.error.message) + } that.transitionTo("share", res.body); }); }, + componentDidMount: function () { + this.setState({ + submitting: false + }); + }, + render: function() { return (
- +
diff --git a/views/share.jsx b/views/share.jsx index f41324f..0a3eece 100644 --- a/views/share.jsx +++ b/views/share.jsx @@ -9,7 +9,7 @@ var Foot = require('./foot.jsx'); var MusicItem = React.createClass({ render: function() { - if (typeof this.props.item.id === "undefined") { + if (!this.props.item.matched_at) { return (
@@ -21,7 +21,7 @@ var MusicItem = React.createClass({
); - } else if (this.props.item.id === null) { + } else if (!this.props.item.id) { return (
@@ -102,17 +102,18 @@ module.exports = React.createClass({ var complete = this.state.shares.length > 0; this.state.shares.forEach(function(share) { - if (typeof share.id === "undefined") { + if (typeof share.matched_at === "undefined") { + console.log(share) complete = false; } }); var getShares = function() { - request.get(this.getPathname()).set('Accept', 'application/json').end(function(res) { + request.get(this.getPathname() + ".json").end(function(res) { var shares = res.body.shares; complete = true; shares.forEach(function(share) { - if (typeof share.id === "undefined") { + if (typeof share.matched_at === "undefined") { complete = false; } });