Merge branch 'release/minibump'

This commit is contained in:
Simon Tokumine 2012-06-06 19:48:32 +01:00
commit 6edc510bfe
20 changed files with 179 additions and 179 deletions

View File

@ -1,11 +1,10 @@
SQL API for cartodb.com
========================
Provides a nodejs based API for running SQL queries against CartoDB.
* Users are authenticated over OAuth. Also provides ability to make public
"SELECT" only calls.
* OAuth requests to this API should always be made over SSL.
* Users are authenticated over OAuth or via an API KEY.
* Authenticated requests to this API should always be made over SSL.
core requirements

View File

@ -1,6 +0,0 @@
update error messages if a query goes wrong
add locking cache as per tilelive-mapnik to the queries
one day
-------
move to fully chunked row by row evented pg model (maybe)

View File

@ -39,23 +39,23 @@ app.get('/api/v1/cachestatus', function(req, res) { handleCacheStatus(req, res)
// request handlers
function handleQuery(req, res){
// sanitize input
// extract input
var body = (req.body) ? req.body : {};
var sql = req.query.q || body.q; // get and post
var sql = req.query.q || body.q; // HTTP GET and POST store in different vars
var api_key = req.query.api_key || body.api_key;
var database = req.query.database; // deprecate this in future
var database = req.query.database; // TODO: Depricate
var limit = parseInt(req.query.rows_per_page);
var offset = parseInt(req.query.page);
var format = (req.query.format) ? req.query.format : null;
var dp = (req.query.dp) ? req.query.dp: '15';
var format = req.query.format;
var dp = req.query.dp;
// validate input slightly
dp = (dp === "") ? '15' : dp;
format = (format === "") ? null : format;
sql = (sql === "") ? null : sql;
database = (database === "") ? null : database;
// sanitize and apply defaults to input
dp = (dp === "" || _.isUndefined(dp)) ? '6' : dp;
format = (format === "" || _.isUndefined(format)) ? null : format;
sql = (sql === "" || _.isUndefined(sql)) ? null : sql;
database = (database === "" || _.isUndefined(database)) ? null : database;
limit = (_.isNumber(limit)) ? limit : null;
offset = (_.isNumber(offset)) ? offset * limit : null
offset = (_.isNumber(offset)) ? offset * limit : null;
// setup step run
var start = new Date().getTime();
@ -80,7 +80,15 @@ function handleQuery(req, res){
},
function setDBGetUser(err, data) {
if (err) throw err;
database = (data == "" || _.isNull(data)) ? database : data;
database = (data == "" || _.isNull(data) || _.isUndefined(data)) ? database : data;
// If the database could not be found, the user is non-existant
if (_.isNull(database)) {
var msg = "Sorry, we can't find this CartoDB. Please check that you have entered the correct domain.";
err = new Error(msg);
err.http_status = 404;
throw err;
}
if(api_key) {
ApiKeyAuth.verifyRequest(req, this);
@ -265,7 +273,7 @@ function handleException(err, res){
// allow cross site post
setCrossDomain(res);
// if the exception defines a http status code, use that, else a 500
// if the exception defines a http status code, use that, else a 400
if (!_.isUndefined(err.http_status)){
res.send(msg, err.http_status);
} else {

View File

@ -4,6 +4,9 @@
* SQL API loader
* ===============
*
* Builds a cluster of node processes managed by cluster library.
* Only compatible with node 0.4.x
*
* node app [environment]
*
* environments: [development, test, production]

View File

@ -1,12 +1,18 @@
cartodb-sql-api tests
--
---------------------
Tests require you create a test database and set some redis keys before,
you can execute prepare_db.sh script, it will create database, users
and redis stuff for you. Be sure postgres and redis are running
> cd test && ./prepare_db.sh
once database is configured, run the tests with expresso:
Acceptance tests (need ctrl-C to exit)
--------------------------------------
> expresso test/acceptance/app.test.js
> expresso test/acceptance/app.auth.test.js
Unit tests (need ctrl-C to exit)
--------------------------------
> expresso test/unit/*.js (or run the tests individually)

View File

@ -56,7 +56,6 @@ tests['GET /api/v1/sql with SQL parameter on SELECT only. no database param, jus
};
tests['POST /api/v1/sql with SQL parameter on SELECT only. no database param, just id using headers'] = function(){
assert.response(app, {
url: '/api/v1/sql',
@ -158,6 +157,19 @@ tests['GET /api/v1/sql as geojson limiting decimal places'] = function(){
});
};
tests['GET /api/v1/sql as geojson with default dp as 6'] = function(){
assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{
status: 200
}, function(res){
var result = JSON.parse(res.body);
assert.equal(6, checkDecimals(result.features[0].geometry.coordinates[0], '.'));
});
};
tests['GET /api/v1/sql as csv'] = function(){
assert.response(app, {
url: '/api/v1/sql?q=SELECT%20cartodb_id,ST_AsEWKT(the_geom)%20as%20geom%20FROM%20untitle_table_4%20LIMIT%201&format=csv',
@ -184,7 +196,7 @@ tests['GET /api/v1/sql as csv, properly escaped'] = function(){
});
};
tests['GET system tables'] = function(){
tests['cannot GET system tables'] = function(){
assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20pg_attribute',
headers: {host: 'vizzuality.cartodb.com'},
@ -194,6 +206,19 @@ tests['GET system tables'] = function(){
});
};
tests['GET decent error if domain is incorrect'] = function(){
assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson',
headers: {host: 'vizzualinot.cartodb.com'},
method: 'GET'
},{
status: 404
}, function(res){
var result = JSON.parse(res.body);
assert.equal(result.error[0],"Sorry, we can't find this CartoDB. Please check that you have entered the correct domain.");
});
};
// use dec_sep for internationalization
function checkDecimals(x, dec_sep){
tmp='' + x;

View File

@ -1,17 +1,24 @@
#!/bin/sh
# this script prepare database and redis instance to run accpetance test
echo "preparing redis..."
echo "HSET rails:users:vizzuality id 1" | redis-cli -n 5
echo "HSET rails:users:vizzuality database_name cartodb_test_user_1_db" | redis-cli -n 5
echo "SADD rails:users:vizzuality:map_key 1234" | redis-cli -n 5
echo "hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR consumer_key fZeNGv5iYayvItgDYHUbot1Ukb5rVyX6QAg8GaY2" | redis-cli -n 3
echo "hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR consumer_secret IBLCvPEefxbIiGZhGlakYV4eM8AbVSwsHxwEYpzx" | redis-cli -n 3
echo "hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR access_token_token l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR" | redis-cli -n 3
echo "hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR access_token_secret 22zBIek567fMDEebzfnSdGe8peMFVFqAreOENaDK" | redis-cli -n 3
echo "hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR user_id 1" | redis-cli -n 3
echo "hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR time sometime" | redis-cli -n 3
echo "preparing postgres..."
dropdb -Upostgres -hlocalhost cartodb_test_user_1_db
createdb -Upostgres -hlocalhost -Ttemplate_postgis -Opostgres -EUTF8 cartodb_test_user_1_db
psql -Upostgres -hlocalhost cartodb_test_user_1_db < test.sql
echo "ok, you can run test now"

View File

@ -1,37 +1,23 @@
/**
* User: simon
* Date: 24/08/2011
* Time: 13:03
* Desc: Tests for the metadata model
*
* in order to run this test, please ensure you have set the following in Redis:
*
* SELECT 5
* HSET rails:users:simon id 5
* HSET rails:users:simon database_name simons_database
*/
require('../helper');
var _ = require('underscore')
, redis = require("redis")
var _ = require('underscore')
, redis = require("redis")
, MetaData = require('../../app/models/metadata')
, assert = require('assert')
, assert = require('assert')
, tests = module.exports = {};
tests['test can retrieve database name from header and redis'] = function(){
var req = {headers: {host: 'simon.cartodb.com'}};
var req = {headers: {host: 'vizzuality.cartodb.com'}};
MetaData.getDatabase(req, function(err, data){
assert.equal(data, 'simons_database');
assert.equal(data, 'cartodb_test_user_1_db');
});
};
tests['test can retrieve id from header and redis'] = function(){
var req = {headers: {host: 'simon.cartodb.com'}};
var req = {headers: {host: 'vizzuality.cartodb.com'}};
MetaData.getId(req, function(err, data){
assert.equal(data, '5');
assert.equal(data, '1');
});
};

View File

@ -1,117 +1,99 @@
require('../helper');
var _ = require('underscore')
, redis = require("redis")
, oAuth = require('../../app/models/oauth')
, assert = require('assert')
, tests = module.exports = {};
var oauth_data_1 = {
oauth_consumer_key: "dpf43f3p2l4k3l03",
oauth_token: "nnch734d00sl2jdk",
oauth_signature_method: "HMAC-SHA1",
oauth_signature: "tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D",
oauth_timestamp:"1191242096",
oauth_nonce:"kllo9940pd9333jh"
}
var oauth_data_2 = { oauth_version:"1.0" }
var oauth_data = _.extend(oauth_data_1, oauth_data_2);
var oauth_header_tokens = 'oauth_consumer_key="dpf43f3p2l4k3l03",oauth_token="nnch734d00sl2jdk",oauth_signature_method="HMAC-SHA1", oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D",oauth_timestamp="1191242096",oauth_nonce="kllo9940pd9333jh",oauth_version="1.0"'
var full_oauth_header = 'OAuth realm="http://photos.example.net/"' + oauth_header_tokens;
var part_oauth_header = 'oauth_token="ad180jjd733klru7",oauth_signature_method="HMAC-SHA1"'
var _ = require('underscore')
, redis = require("redis")
, oAuth = require('../../app/models/oauth')
, assert = require('assert')
, tests = module.exports = {}
, oauth_data_1 = {
oauth_consumer_key: "dpf43f3p2l4k3l03",
oauth_token: "nnch734d00sl2jdk",
oauth_signature_method: "HMAC-SHA1",
oauth_signature: "tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D",
oauth_timestamp:"1191242096",
oauth_nonce:"kllo9940pd9333jh"
}
, oauth_data_2 = { oauth_version:"1.0" }
, oauth_data = _.extend(oauth_data_1, oauth_data_2)
, real_oauth_header = 'OAuth realm="http://vizzuality.testhost.lan/",oauth_consumer_key="fZeNGv5iYayvItgDYHUbot1Ukb5rVyX6QAg8GaY2",oauth_token="l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR",oauth_signature_method="HMAC-SHA1", oauth_signature="o4hx4hWP6KtLyFwggnYB4yPK8xI%3D",oauth_timestamp="1313581372",oauth_nonce="W0zUmvyC4eVL8cBd4YwlH1nnPTbxW0QBYcWkXTwe4",oauth_version="1.0"'
, oauth_header_tokens = 'oauth_consumer_key="dpf43f3p2l4k3l03",oauth_token="nnch734d00sl2jdk",oauth_signature_method="HMAC-SHA1", oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D",oauth_timestamp="1191242096",oauth_nonce="kllo9940pd9333jh",oauth_version="1.0"'
, full_oauth_header = 'OAuth realm="http://photos.example.net/"' + oauth_header_tokens;
tests['test database number'] = function(){
assert.equal(oAuth.oauth_database, 3);
assert.equal(oAuth.oauth_database, 3);
};
tests['test oauth database key'] = function(){
assert.equal(oAuth.oauth_user_key, "rails:oauth_access_tokens:<%= oauth_access_key %>");
assert.equal(oAuth.oauth_user_key, "rails:oauth_access_tokens:<%= oauth_access_key %>");
};
tests['test parse tokens from full headers does not raise exception'] = function(){
var req = {query:{}, headers:{authorization:full_oauth_header}}
assert.doesNotThrow(function(){ oAuth.parseTokens(req) }, /incomplete oauth tokens in request/);
var req = {query:{}, headers:{authorization:full_oauth_header}};
assert.doesNotThrow(function(){ oAuth.parseTokens(req) }, /incomplete oauth tokens in request/);
};
tests['test parse all normal tokens raises no exception'] = function(){
var req = {query:oauth_data, headers:{}}
assert.doesNotThrow(function(){ oAuth.parseTokens(req) }, /incomplete oauth tokens in request/);
var req = {query:oauth_data, headers:{}};
assert.doesNotThrow(function(){ oAuth.parseTokens(req) }, /incomplete oauth tokens in request/);
};
tests['test headers take presedence over query parameters'] = function(){
var req = {query:{oauth_signature_method: "MY_HASH"}, headers:{authorization:full_oauth_header}}
var tokens = oAuth.parseTokens(req);
assert.equal(tokens.oauth_signature_method, "HMAC-SHA1");
var req = {query:{oauth_signature_method: "MY_HASH"}, headers:{authorization:full_oauth_header}};
var tokens = oAuth.parseTokens(req);
assert.equal(tokens.oauth_signature_method, "HMAC-SHA1");
};
// before this, you must embed the test OAUTH hash in redis so everything works.
// Request url: http://vizzuality.testhost.lan/api/v1/tables
// hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR consumer_key fZeNGv5iYayvItgDYHUbot1Ukb5rVyX6QAg8GaY2
// hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR consumer_secret IBLCvPEefxbIiGZhGlakYV4eM8AbVSwsHxwEYpzx
// hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR access_token_token l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR
// hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR access_token_secret 22zBIek567fMDEebzfnSdGe8peMFVFqAreOENaDK
// hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR user_id 1
// hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR time sometime
//the headers for this are:
var real_oauth_header = 'OAuth realm="http://vizzuality.testhost.lan/",oauth_consumer_key="fZeNGv5iYayvItgDYHUbot1Ukb5rVyX6QAg8GaY2",oauth_token="l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR",oauth_signature_method="HMAC-SHA1", oauth_signature="o4hx4hWP6KtLyFwggnYB4yPK8xI%3D",oauth_timestamp="1313581372",oauth_nonce="W0zUmvyC4eVL8cBd4YwlH1nnPTbxW0QBYcWkXTwe4",oauth_version="1.0"';
tests['test can access oauth hash for a user based on access token (oauth_token)'] = function(){
var req = {query:{}, headers:{authorization:real_oauth_header}};
var tokens = oAuth.parseTokens(req);
oAuth.getOAuthHash(tokens.oauth_token, function(err, data){
assert.equal(tokens.oauth_consumer_key, data.consumer_key)
});
var req = {query:{}, headers:{authorization:real_oauth_header}};
var tokens = oAuth.parseTokens(req);
oAuth.getOAuthHash(tokens.oauth_token, function(err, data){
assert.equal(tokens.oauth_consumer_key, data.consumer_key);
});
};
tests['test non existant oauth hash for a user based on oauth_token returns empty hash'] = function(){
var req = {query:{}, headers:{authorization:full_oauth_header}};
var tokens = oAuth.parseTokens(req);
oAuth.getOAuthHash(tokens.oauth_token, function(err, data){
assert.eql(data, {})
});
var req = {query:{}, headers:{authorization:full_oauth_header}};
var tokens = oAuth.parseTokens(req);
oAuth.getOAuthHash(tokens.oauth_token, function(err, data){
assert.eql(data, {});
});
};
tests['can return user for verified signature'] = function(){
var req = {query:{},
headers:{authorization:real_oauth_header, host: 'vizzuality.testhost.lan' },
method: 'GET',
route: {path: '/api/v1/tables'}
};
oAuth.verifyRequest(req, function(err, data){
assert.eql(data, 1);
}, true)
var req = {query:{},
headers:{authorization:real_oauth_header, host: 'vizzuality.testhost.lan' },
method: 'GET',
route: {path: '/api/v1/tables'}
};
oAuth.verifyRequest(req, function(err, data){
assert.eql(data, 1);
}, true);
};
tests['returns null user for unverified signatures'] = function(){
var req = {query:{},
headers:{authorization:real_oauth_header, host: 'vizzuality.testyhost.lan' },
method: 'GET',
route: {path: '/api/v1/tables'}
};
oAuth.verifyRequest(req, function(err, data){
assert.eql(data, null);
}, true)
var req = {query:{},
headers:{authorization:real_oauth_header, host: 'vizzuality.testyhost.lan' },
method: 'GET',
route: {path: '/api/v1/tables'}
};
oAuth.verifyRequest(req, function(err, data){
assert.eql(data, null);
}, true);
};
tests['returns null user for no oauth'] = function(){
var req = {
query:{},
headers:{},
method: 'GET',
route: {path: '/api/v1/tables'}
};
oAuth.verifyRequest(req,function(err,data){
assert.eql(data, null);
});
};
var req = {
query:{},
headers:{},
method: 'GET',
route: {path: '/api/v1/tables'}
};
oAuth.verifyRequest(req,function(err,data){
assert.eql(data, null);
});
};

View File

@ -6,7 +6,7 @@ var _ = require('underscore')
exports['test throws error if no args passed to constructor'] = function(){
try{
var pg = new PSQL();
var pg = new PSQL();
} catch (err){
assert.equal(err.message, "Incorrect access parameters. If you are accessing via OAuth, please check your tokens are correct. For public users, please ensure your table is published.");
}
@ -42,18 +42,9 @@ exports['test username returns interpolated db if user set'] = function(){
assert.equal(pg.database(), "cartodb_test_user_simon_db");
};
// TODO fix
//exports['test can connect to db'] = function(){
// var pg = new PSQL('simon');
// pg.connect(function(err, client){
// assert.equal(client.connected, true);
// pg.end();
// });
//};
exports['test private user can execute SELECTS on db'] = function(){
var pg = new PSQL('simon');
var sql = "SELECT 1 as test_sum"
var pg = new PSQL('1');
var sql = "SELECT 1 as test_sum";
pg.query(sql, function(err, result){
assert.equal(result.rows[0].test_sum, 1);
pg.end();
@ -61,8 +52,8 @@ exports['test private user can execute SELECTS on db'] = function(){
};
exports['test private user can execute CREATE on db'] = function(){
var pg = new PSQL('simon');
var sql = "DROP TABLE IF EXISTS distributors; CREATE TABLE distributors (id integer, name varchar(40), UNIQUE(name))"
var pg = new PSQL('1');
var sql = "DROP TABLE IF EXISTS distributors; CREATE TABLE distributors (id integer, name varchar(40), UNIQUE(name))";
pg.query(sql, function(err, result){
assert.isNull(err);
pg.end();
@ -70,10 +61,10 @@ exports['test private user can execute CREATE on db'] = function(){
};
exports['test private user can execute INSERT on db'] = function(){
var pg = new PSQL('simon');
var sql = "DROP TABLE IF EXISTS distributors1; CREATE TABLE distributors1 (id integer, name varchar(40), UNIQUE(name))"
var pg = new PSQL('1');
var sql = "DROP TABLE IF EXISTS distributors1; CREATE TABLE distributors1 (id integer, name varchar(40), UNIQUE(name))";
pg.query(sql, function(err, result){
sql = "INSERT INTO distributors1 (id, name) VALUES (1, 'fish')"
sql = "INSERT INTO distributors1 (id, name) VALUES (1, 'fish')";
pg.query(sql,function(err, result){
assert.eql(result.rows, []);
pg.end();
@ -82,29 +73,29 @@ exports['test private user can execute INSERT on db'] = function(){
};
exports['test publicuser can execute SELECT on enabled tables'] = function(){
var pg = new PSQL("simon");
var sql = "DROP TABLE IF EXISTS distributors2; CREATE TABLE distributors2 (id integer, name varchar(40), UNIQUE(name)); GRANT SELECT ON distributors2 TO publicuser;"
var pg = new PSQL("1");
var sql = "DROP TABLE IF EXISTS distributors2; CREATE TABLE distributors2 (id integer, name varchar(40), UNIQUE(name)); GRANT SELECT ON distributors2 TO publicuser;";
pg.query(sql, function(err, result){
pg.end();
pg = new PSQL(null, 'cartodb_test_user_simon_db');
pg = new PSQL(null, 'cartodb_test_user_1_db');
pg.query("SELECT count(*) FROM distributors2", function(err, result){
assert.equal(result.rows[0].count, 0);
pg.end();
});
});
}
};
exports['test publicuser cannot execute INSERT on db'] = function(){
var pg = new PSQL("simon");
var sql = "DROP TABLE IF EXISTS distributors3; CREATE TABLE distributors3 (id integer, name varchar(40), UNIQUE(name)); GRANT SELECT ON distributors3 TO publicuser;"
var pg = new PSQL("1");
var sql = "DROP TABLE IF EXISTS distributors3; CREATE TABLE distributors3 (id integer, name varchar(40), UNIQUE(name)); GRANT SELECT ON distributors3 TO publicuser;";
pg.query(sql, function(err, result){
pg.end();
pg = new PSQL(null, 'cartodb_test_user_simon_db'); //anonymous user
pg = new PSQL(null, 'cartodb_test_user_1_db'); //anonymous user
pg.query("INSERT INTO distributors3 (id, name) VALUES (1, 'fishy')", function(err, result){
assert.eql(err.message, 'permission denied for relation distributors3')
assert.eql(err.message, 'permission denied for relation distributors3');
pg.end();
});
});
}
};

View File

@ -1,45 +1,43 @@
require('../helper');
var _ = require('underscore')
require('../helper');
var _ = require('underscore')
, redis_pool = require('../../app/models/redis_pool')
, assert = require('assert');
, assert = require('assert');
exports['test truth'] = function(){
assert.ok(true, 'it is');
assert.ok(true, 'it is');
};
exports['test can instantiate a RedisPool object'] = function(){
assert.ok(redis_pool);
}
};
exports['test pool object has an aquire function'] = function(){
assert.includes(_.functions(redis_pool), 'acquire');
}
};
exports['test calling aquire returns a redis client object that can get/set'] = function(beforeExit){
redis_pool.acquire(0, function(client){
client.set("key","value")
client.set("key","value");
client.get("key", function(err,data){
assert.eql(data, "value");
redis_pool.release(0, client); // needed to exit tests
})
redis_pool.release(0, client);
});
});
}
};
exports['test calling aquire on another DB returns a redis client object that can get/set'] = function(beforeExit){
redis_pool.acquire("MYDATABASE", function(client){
client.set("key","value")
client.set("key","value");
client.get("key", function(err,data){
assert.eql(data, "value");
redis_pool.release("MYDATABASE", client); // needed to exit tests
redis_pool.release("MYDATABASE", client);
})
});
redis_pool.acquire("MYDATABASE", function(client){
client.get("key", function(err,data){
assert.eql(data, "value");
redis_pool.release("MYDATABASE", client); // needed to exit tests
})
redis_pool.release("MYDATABASE", client);
});
});
}
};

View File

@ -1,3 +1,4 @@
// this is a test to understand accessing sql api via websockets
var express = require('express')
, app = express.createServer(
express.logger({