First version of the API server with some TODOs to solve
This commit is contained in:
parent
a5656441cc
commit
159f72fe3d
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
logs/
|
||||
pids/
|
||||
*.sock
|
||||
test/tmp/*
|
11
Makefile
Normal file
11
Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
test-tmp:
|
||||
@rm -rf test/tmp
|
||||
@mkdir -p test/tmp
|
||||
|
||||
test:
|
||||
expresso -I lib test/unit/*.js test/acceptance/*.js
|
||||
|
||||
test-cov:
|
||||
expresso -I lib --cov test/unit/*.js test/acceptance/*.js
|
||||
|
||||
.PHONY: test test-cov
|
82
app.js
Executable file
82
app.js
Executable file
@ -0,0 +1,82 @@
|
||||
#! /Users/fernando/local/node/bin/node
|
||||
|
||||
var app = require('express').createServer()
|
||||
, pg = require('pg').native
|
||||
, Step = require('./step')
|
||||
, _ = require('underscore')
|
||||
, redis = require("redis")
|
||||
, redisClient = redis.createClient();
|
||||
|
||||
redisClient.select(3);
|
||||
_.mixin(require('underscore.string'));
|
||||
|
||||
function authenticate(req){
|
||||
var oauth_token;
|
||||
// Parameter oauth_token with the OAuth token
|
||||
if (!_.isUndefined(req.query.oauth_token)){
|
||||
oauth_token = _.trim(req.query.oauth_token);
|
||||
} else if (!_.isUndefined(req.headers.authorization)) {
|
||||
// OAuth token is in the header Authorization
|
||||
oauth_token = req.headers.authorization.match(/oauth_token=\"([^\"]+)\"/)[1]
|
||||
}
|
||||
if (!_.isUndefined(oauth_token)) {
|
||||
return oauth_token;
|
||||
}
|
||||
}
|
||||
|
||||
app.get('/v1/', function(req, res){
|
||||
if (!_.isUndefined(req.query.sql)){
|
||||
var sql = _.trim(req.query.sql);
|
||||
var conString;
|
||||
Step(
|
||||
function getUser(err) {
|
||||
var oauth_token = authenticate(req);
|
||||
console.log("oauth_token: " + oauth_token);
|
||||
if (!_.isUndefined(oauth_token)) {
|
||||
redisClient.hget("rails:oauth_tokens:" + oauth_token, "user_id", this);
|
||||
} else {
|
||||
connectDb(undefined,undefined);
|
||||
}
|
||||
console.log("next step!");
|
||||
},
|
||||
function connectDb(error, reply){
|
||||
if(!_.isUndefined(reply)) {
|
||||
var user_id = reply.toString();
|
||||
// TODO:
|
||||
// - database_username: depending on the environment is different:
|
||||
// - development: dev_cartodb_user_33
|
||||
// - test: test_cartodb_user_33
|
||||
// - production: cartodb_user_33
|
||||
// - database_name: depending on the environment is different:
|
||||
// - development: cartodb_dev_user_33_db
|
||||
// - test: cartodb_test_user_33_db
|
||||
// - production: cartodb_user_33_db
|
||||
var database_username = "cartodb_user_" + user_id;
|
||||
var database_name = "cartodb_dev_user_" + user_id + "_db"
|
||||
conString = "tcp://" + database_username + "@localhost/" + database_name;
|
||||
} else {
|
||||
// TODO:
|
||||
// - if the user doesn't identify who is how do we now to which database to connect?
|
||||
// I think it's impossible to know unless each request goes to a different subdomain
|
||||
// depending on the user
|
||||
conString = "tcp://publicuser@localhost/cartodb_dev_user_2_db";
|
||||
}
|
||||
pg.connect(conString, this);
|
||||
},
|
||||
function queryDb(err, client){
|
||||
if (err) throw err;
|
||||
client.query(sql, this);
|
||||
},
|
||||
function setResultToBrowser(err, result){
|
||||
if (err)
|
||||
res.send({error:[err.message]}, 400);
|
||||
else
|
||||
res.send(result.rows);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
res.send({error:["You must indicate a sql query"]}, 400);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = app;
|
13
cluster.js
Executable file
13
cluster.js
Executable file
@ -0,0 +1,13 @@
|
||||
#! /Users/fernando/local/node/bin/node
|
||||
|
||||
var cluster = require('cluster');
|
||||
|
||||
cluster('./app')
|
||||
.use(cluster.logger('logs'))
|
||||
.set('workers', 2)
|
||||
.use(cluster.stats())
|
||||
.use(cluster.pidfiles('pids'))
|
||||
.use(cluster.cli())
|
||||
.use(cluster.repl(8888))
|
||||
.use(cluster.debug())
|
||||
.listen(3000);
|
154
step.js
Normal file
154
step.js
Normal file
@ -0,0 +1,154 @@
|
||||
/*
|
||||
Copyright (c) 2011 Tim Caswell <tim@creationix.com>
|
||||
|
||||
MIT License
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
// Inspired by http://github.com/willconant/flow-js, but reimplemented and
|
||||
// modified to fit my taste and the node.JS error handling system.
|
||||
function Step() {
|
||||
var steps = Array.prototype.slice.call(arguments),
|
||||
pending, counter, results, lock;
|
||||
|
||||
// Define the main callback that's given as `this` to the steps.
|
||||
function next() {
|
||||
|
||||
// Check if there are no steps left
|
||||
if (steps.length === 0) {
|
||||
// Throw uncaught errors
|
||||
if (arguments[0]) {
|
||||
throw arguments[0];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the next step to execute
|
||||
var fn = steps.shift();
|
||||
counter = pending = 0;
|
||||
results = [];
|
||||
|
||||
// Run the step in a try..catch block so exceptions don't get out of hand.
|
||||
try {
|
||||
lock = true;
|
||||
var result = fn.apply(next, arguments);
|
||||
} catch (e) {
|
||||
// Pass any exceptions on through the next callback
|
||||
next(e);
|
||||
}
|
||||
|
||||
|
||||
// If a syncronous return is used, pass it to the callback
|
||||
if (result !== undefined) {
|
||||
next(undefined, result);
|
||||
}
|
||||
lock = false;
|
||||
}
|
||||
|
||||
// Add a special callback generator `this.parallel()` that groups stuff.
|
||||
next.parallel = function () {
|
||||
var index = 1 + counter++;
|
||||
pending++;
|
||||
|
||||
function check() {
|
||||
if (pending === 0) {
|
||||
// When they're all done, call the callback
|
||||
next.apply(null, results);
|
||||
}
|
||||
}
|
||||
process.nextTick(check); // Ensures that check is called at least once
|
||||
|
||||
return function () {
|
||||
pending--;
|
||||
// Compress the error from any result to the first argument
|
||||
if (arguments[0]) {
|
||||
results[0] = arguments[0];
|
||||
}
|
||||
// Send the other results as arguments
|
||||
results[index] = arguments[1];
|
||||
if (!lock) { check(); }
|
||||
};
|
||||
};
|
||||
|
||||
// Generates a callback generator for grouped results
|
||||
next.group = function () {
|
||||
var localCallback = next.parallel();
|
||||
var counter = 0;
|
||||
var pending = 0;
|
||||
var result = [];
|
||||
var error = undefined;
|
||||
|
||||
function check() {
|
||||
if (pending === 0) {
|
||||
// When group is done, call the callback
|
||||
localCallback(error, result);
|
||||
}
|
||||
}
|
||||
process.nextTick(check); // Ensures that check is called at least once
|
||||
|
||||
// Generates a callback for the group
|
||||
return function () {
|
||||
var index = counter++;
|
||||
pending++;
|
||||
return function () {
|
||||
pending--;
|
||||
// Compress the error from any result to the first argument
|
||||
if (arguments[0]) {
|
||||
error = arguments[0];
|
||||
}
|
||||
// Send the other results as arguments
|
||||
result[index] = arguments[1];
|
||||
if (!lock) { check(); }
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// Start the engine an pass nothing to the first step.
|
||||
next();
|
||||
}
|
||||
|
||||
// Tack on leading and tailing steps for input and output and return
|
||||
// the whole thing as a function. Basically turns step calls into function
|
||||
// factories.
|
||||
Step.fn = function StepFn() {
|
||||
var steps = Array.prototype.slice.call(arguments);
|
||||
return function () {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
|
||||
// Insert a first step that primes the data stream
|
||||
var toRun = [function () {
|
||||
this.apply(null, args);
|
||||
}].concat(steps);
|
||||
|
||||
// If the last arg is a function add it as a last step
|
||||
if (typeof args[args.length-1] === 'function') {
|
||||
toRun.push(args.pop());
|
||||
}
|
||||
|
||||
|
||||
Step.apply(null, toRun);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Hook into commonJS module systems
|
||||
if (typeof module !== 'undefined' && "exports" in module) {
|
||||
module.exports = Step;
|
||||
}
|
22
test/acceptance/app.test.js
Normal file
22
test/acceptance/app.test.js
Normal file
@ -0,0 +1,22 @@
|
||||
var app = require('../../app')
|
||||
, assert = require('assert');
|
||||
|
||||
module.exports = {
|
||||
'GET /v1/': function(){
|
||||
assert.response(app, {
|
||||
url: '/v1/',
|
||||
method: 'GET'
|
||||
},{
|
||||
body: '{"error":["You must indicate a sql query"]}',
|
||||
status: 400
|
||||
});
|
||||
},
|
||||
'GET /v1/ with SQL parameter': function(){
|
||||
assert.response(app, {
|
||||
url: '/v1/?sql=bla',
|
||||
method: 'GET'
|
||||
},{
|
||||
status: 200
|
||||
});
|
||||
}
|
||||
};
|
1
test/unit/app.test.js
Normal file
1
test/unit/app.test.js
Normal file
@ -0,0 +1 @@
|
||||
// Empty file but necessary to make `make test` won't fail
|
Loading…
Reference in New Issue
Block a user