From 41657124dc34c0d5b9d1ebeb9bbf903e84bb532c Mon Sep 17 00:00:00 2001 From: Zachary Carter Date: Tue, 13 Nov 2012 09:44:11 -0800 Subject: [PATCH] WIP persistence layer and stage account api --- bin/api | 14 +++++- client/client.js | 102 ++++++++++++++++++++++++++++------------- lib/api/v1/account.js | 32 +++++++++++++ lib/api_loader.js | 4 +- lib/db.js | 3 ++ lib/db/couchbase.js | 29 ++++++++++++ lib/db/json.js | 23 ++++++++++ package.json | 1 + test/api.v1.account.js | 42 +++++++++++++++++ test/api.v1.context.js | 9 ++-- test/lib/runner.js | 5 +- 11 files changed, 223 insertions(+), 41 deletions(-) create mode 100644 lib/api/v1/account.js create mode 100644 lib/db.js create mode 100644 lib/db/couchbase.js create mode 100644 lib/db/json.js create mode 100644 test/api.v1.account.js diff --git a/bin/api b/bin/api index e546765..73cea0e 100755 --- a/bin/api +++ b/bin/api @@ -24,11 +24,21 @@ server.on('bound', function(host, port) { console.log("running on http://" + host + ":" + port); }); +// TODO seperate config +var dbConfig = { + hosts: [ 'localhost:8091' ], + username: 'admin', + password: null, + bucket: 'default' +}; + // now load up api handlers apiLoader(server, function(err) { if (err) fatal(err); - // Start the server - server.start(); + db.connect(dbConfig, function() { + // Start the server + server.start(); + }); }); process.on('SIGTERM', function() { diff --git a/client/client.js b/client/client.js index e551bc8..8a93026 100644 --- a/client/client.js +++ b/client/client.js @@ -1,39 +1,79 @@ -GombotClient = (function() { - var xhr = typeof jQuery !== 'undefined' ? jQuery.ajax : require('xhrequest'); +;(function() { - function request(args, cb) { - var url = args.scheme ? args.scheme : 'http'; - var method = args.method.toUpperCase(); - url += "://" + args.host; - if (args.port) url += ":" + args.port; - url += args.path; +GombotClient = function(host, port) { + this.host = host; + this.port = port; +}; - console.log('sending xhr'); - xhr(url, { - method: method, - success: function (data, res, status) { - try { - var body = JSON.parse(data); - body.session_context = {}; - cb(null, body); - } catch (e) { - cb('Invalid JSON response: '+e); - } - }, - error: function (data, res, status) { - cb('Error: '+data+'\nStatus: '+status); +var xhr = typeof jQuery !== 'undefined' ? jQuery.ajax : require('xhrequest'); + +if (typeof GombotCrypto === 'undefined') { + var GombotCrypto = require('./crypto.js'); +} + +function request(args, cb) { + var url = args.scheme ? args.scheme : 'http'; + var method = args.method.toUpperCase(); + url += '://' + args.host; + if (args.port) url += ':' + args.port; + url += args.path; + + var req = { + url: url, + method: method, + data: args.data, + headers: {}, + success: function(data, res, status) { + try { + var body = JSON.parse(data); + } catch (e) { + return cb('Invalid JSON response: ' + e); } - }); - } - - return { - // get "session context" from the server - context: function(args, cb) { - args.method = 'get'; - args.path = '/v1/context'; - request(args, cb); + body.session_context = {}; + cb(null, body); + }, + error: function(data, res, status) { + cb('Error: ' + data + '\nStatus: ' + status); } }; + if (method == 'PUT' || method == 'POST') { + req.headers['Content-Type'] = 'application/json'; + } + xhr(url, req); +} + +GombotClient.prototype = { + // get "session context" from the server + context: function(args, cb) { + if (typeof args === 'function') { + cb = args; + args = {}; + } + args.host = args.host || this.host; + args.port = args.port || this.port; + args.method = 'get'; + args.path = '/v1/context'; + + request(args, cb); + }, + account: function(args, cb) { + args.host = args.host || this.host; + args.port = args.port || this.port; + args.method = 'put'; + args.path = '/v1/account'; + + // compute the authKey + var keys = GombotCrypto.derive({ + email: args.email, + password: args.password + }, function(err, r) { + args.data = JSON.stringify({email: args.email, pass: r.authKey}); + // send request with authKey as the password + request(args, cb); + }); + } +}; + })(); if (typeof module != 'undefined' && module.exports) { diff --git a/lib/api/v1/account.js b/lib/api/v1/account.js new file mode 100644 index 0000000..b0e904f --- /dev/null +++ b/lib/api/v1/account.js @@ -0,0 +1,32 @@ +var crypto = require('crypto'), + Hapi = require('hapi'), + db = require('../../db'); + +var B = Hapi.Types.Boolean, + S = Hapi.Types.String; + +module.exports = { + method: 'PUT', + handler: handler, + config: { + auth: false, + description: 'Stage a new account', + schema: { + email: B(), + pass: S() + }, + response: { + success: B() + } + } +}; + +function handler(request) { + console.log('$$$$$$$$$$'); + db.stageAccount(request.payload, function(err) { + if (err) request.reply(Hapi.Error.internal("error staging account")); + request.reply({ + success: true + }); + }); +} diff --git a/lib/api_loader.js b/lib/api_loader.js index 97fd03f..7678d58 100644 --- a/lib/api_loader.js +++ b/lib/api_loader.js @@ -5,7 +5,7 @@ const API_BASE_PATH = path.join(__dirname, 'api'); function addRouteFromFile(hapiServer, apiPath) { // hack off ext - var route = apiPath.substr(0,apiPath.length - 3); + var route = apiPath.substr(0, apiPath.length - 3); var route = path.relative(API_BASE_PATH, route); var impl = require(apiPath); impl.path = '/' + route; @@ -23,7 +23,7 @@ module.exports = function(hapiServer, cb) { try { addRouteFromFile(hapiServer, apiPath); - } catch(e) { + } catch (e) { walker.end(); cb('error while loading API handler ("' + apiPath + '") ' + e.toString()); diff --git a/lib/db.js b/lib/db.js new file mode 100644 index 0000000..08bd4a1 --- /dev/null +++ b/lib/db.js @@ -0,0 +1,3 @@ + +// TODO select driver based on config +module.exports = require('./db/json'); diff --git a/lib/db/couchbase.js b/lib/db/couchbase.js new file mode 100644 index 0000000..5cb0ce7 --- /dev/null +++ b/lib/db/couchbase.js @@ -0,0 +1,29 @@ +var couchbase = require('couchbase'); + +var db; + +module.exports = { + connect: function(options, cb) { + console.error('options', options); + couchbase.connect(options, function(err, bucket) { + console.error('connection fail!!!', err); + if (err) cb(err); + db = bucket; + cb(null); + }); + return this; + }, + stageAccount: function(data, cb) { + var account = { + pass: data.pass, + email: data.email, + staged: true + }; + db.set(data.email, account, function (err, meta) { + console.log('meta', meta); + if (err) cb(err); + else cb(null, meta); + }); + return this; + } +}; diff --git a/lib/db/json.js b/lib/db/json.js new file mode 100644 index 0000000..6af972f --- /dev/null +++ b/lib/db/json.js @@ -0,0 +1,23 @@ +var db; + +module.exports = { + connect: function(options, cb) { + setTimeout(function() { + if (db) return; + db = {}; + cb(null); + }, 0); + }, + stageAccount: function(data, cb) { + var account = { + pass: data.pass, + email: data.email, + staged: true + }; + setTimeout(function() { + db[data.email] = account; + cb(null); + }, 0); + return this; + } +}; diff --git a/package.json b/package.json index d1077e0..e7c91f6 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "dependencies": { "hapi": "git://github.com/lloyd/hapi#b12270", "walkdir": "0.0.5", + "couchbase": "0.0.4", "express": "3.0.2", "nunjucks": "0.1.5" }, diff --git a/test/api.v1.account.js b/test/api.v1.account.js new file mode 100644 index 0000000..4660e2a --- /dev/null +++ b/test/api.v1.account.js @@ -0,0 +1,42 @@ +const +should = require('should'), +runner = require('./lib/runner.js'), +Client = require('../client/client.js'); + +var servers; +var client; + +describe('the servers', function() { + it('should start up', function(done) { + runner(function(err, r) { + should.not.exist(err); + should.exist(r); + servers = r; + client = new Client(servers.host, servers.port); + done(); + }); + }); +}); + +describe('/api/v1/account', function() { + it('staging should return success', function(done) { + client.account({ + email: 'foo', + password: 'bar' + }, function(err, r) { + should.not.exist(err); + should.exist(r); + (r.success).should.be.true; + done(); + }); + }); +}); + +describe('the servers', function() { + it('should stop', function(done) { + servers.stop(function(err) { + should.not.exist(err); + done(); + }); + }); +}); diff --git a/test/api.v1.context.js b/test/api.v1.context.js index 1ce0d25..36d1839 100644 --- a/test/api.v1.context.js +++ b/test/api.v1.context.js @@ -1,9 +1,10 @@ const should = require('should'), runner = require('./lib/runner.js'), -client = require('../client/client.js'); +Client = require('../client/client.js'); var servers; +var client; describe('the servers', function() { it('should start up', function(done) { @@ -11,6 +12,7 @@ describe('the servers', function() { should.not.exist(err); should.exist(r); servers = r; + client = new Client(servers.host, servers.port); done(); }); }); @@ -18,10 +20,7 @@ describe('the servers', function() { describe("/api/v1/context", function() { it ("should return an object with two keys", function(done) { - client.context({ - host: servers.host, - port: servers.port - }, function(err, r) { + client.context(function(err, r) { should.not.exist(err); should.exist(r); should.exist(r.session_context); diff --git a/test/lib/runner.js b/test/lib/runner.js index ca5c177..9a297bb 100644 --- a/test/lib/runner.js +++ b/test/lib/runner.js @@ -19,7 +19,7 @@ module.exports = function(cb) { if (!s.started) { // we parse app output to determine when the process has really data.toString().split("\n").forEach(function(line) { - var m = /^bound to ([a-zA-Z0-9_.]+):([0-9]+)$/.exec(line); + var m = /running on http:\/\/([a-zA-Z0-9_.]+):([0-9]+)$/.exec(line); if (m) { s.started = true; s.host = m[1]; @@ -28,6 +28,9 @@ module.exports = function(cb) { } }); } + if (process.env.LOG_TO_CONSOLE) { + process.stdout.write(data); + } }); s.server.stderr.on('data', function (data) {