Merge branch 'release/api_key'

This commit is contained in:
Simon Tokumine 2012-04-10 13:12:13 +01:00
commit 28a35064a7
17 changed files with 985 additions and 15 deletions

View File

@ -12,18 +12,21 @@ core requirements
-------------
* postgres
* redis
* node v0.4.8+
* node >v0.4.8 && < 0.7.0>
* npm
usage
-----
Edit config/environments/<environment>.js
Make sure redis is running and knows about active cartodb user.
``` bash
node [cluster.js|app.js] [developement|test|production]
node [cluster.js|app.js] <environment>
```
Supported <environment> values are developement, test, production
for examples of use, see /tests
@ -38,3 +41,8 @@ npm install
tests
------
see test/README.md
note on 0.4.x
--------------
output of large result sets is slow under node 0.4. Recommend running under 0.6 where possible.

View File

@ -19,10 +19,11 @@ var express= require('express')
buffer: true,
format: '[:date] :req[X-Real-IP] \033[90m:method\033[0m \033[36m:req[Host]:url\033[0m \033[90m:status :response-time ms -> :res[Content-Type]\033[0m'
}))
, Step = require('step')
, Meta = require(global.settings.app_root + '/app/models/metadata')
, oAuth = require(global.settings.app_root + '/app/models/oauth')
, PSQL = require(global.settings.app_root + '/app/models/psql')
, Step = require('step')
, Meta = require(global.settings.app_root + '/app/models/metadata')
, oAuth = require(global.settings.app_root + '/app/models/oauth')
, PSQL = require(global.settings.app_root + '/app/models/psql')
, ApiKeyAuth = require(global.settings.app_root + '/app/models/apikey_auth')
, _ = require('underscore');
app.use(express.bodyParser());
@ -35,6 +36,7 @@ function handleQuery(req, res){
// sanitize input
var body = (req.body) ? req.body : {};
var sql = req.query.q || body.q; // get and post
var api_key = req.query.api_key || body.api_key;
var database = req.query.database; // deprecate this in future
var limit = parseInt(req.query.rows_per_page);
var offset = parseInt(req.query.page);
@ -68,7 +70,11 @@ function handleQuery(req, res){
function setDBGetUser(err, data) {
if (err) throw err;
database = (data == "" || _.isNull(data)) ? database : data;
oAuth.verifyRequest(req, this);
if(api_key) {
ApiKeyAuth.verifyRequest(req, this);
} else {
oAuth.verifyRequest(req, this);
}
},
function querySql(err, user_id){
if (err) throw err;

114
app/models/apikey_auth.js Normal file
View File

@ -0,0 +1,114 @@
/**
* this module allows to auth user using an pregenerated api key
*/
var RedisPool = require("./redis_pool")
, _ = require('underscore')
, Step = require('step');
module.exports = (function() {
var me = {
user_metadata_db: 5,
table_metadata_db: 0,
user_key: "rails:users:<%= username %>",
map_key: "rails:users:<%= username %>:map_key",
table_key: "rails:<%= database_name %>:<%= table_name %>"
};
me.retrieve = function(db, redisKey, hashKey, callback) {
this.redisCmd(db,'HGET',[redisKey, hashKey], callback);
};
me.inSet = function(db, setKey, member, callback) {
this.redisCmd(db,'SISMEMBER',[setKey, member], callback);
};
/**
* Use Redis
*
* @param db - redis database number
* @param redisFunc - the redis function to execute
* @param redisArgs - the arguments for the redis function in an array
* @param callback - function to pass results too.
*/
me.redisCmd = function(db, redisFunc, redisArgs, callback) {
var redisClient;
Step(
function() {
var step = this;
RedisPool.acquire(db, function(_redisClient) {
redisClient = _redisClient;
redisArgs.push(step);
redisClient[redisFunc.toUpperCase()].apply(redisClient, redisArgs);
});
},
function releaseRedisClient(err, data) {
if (err) throw err;
RedisPool.release(db, redisClient);
callback(err, data);
}
);
};
/**
* Get the user id for this particular subdomain/username
*
* @param req - standard express req object. importantly contains host information
* @param callback
*/
me.getId = function(req, callback) {
// strip subdomain from header host
var username = req.headers.host.split('.')[0];
var redisKey = _.template(this.user_key, {username: username});
this.retrieve(this.user_metadata_db, redisKey, 'id', callback);
};
/**
* Get the user map key for this particular subdomain/username
*
* @param req - standard express req object. importantly contains host information
* @param callback
*/
me.checkAPIKey= function(req, callback) {
// strip subdomain from header host
var username = req.headers.host.split('.')[0];
var redisKey = _.template(this.map_key, {username: username});
var api_key = req.query.api_key || req.body.api_key;
this.inSet(this.user_metadata_db, redisKey, api_key, callback);
};
/**
* Get privacy for cartodb table
*
* @param req - standard req object. Importantly contains table and host information
* @param callback - user_id if ok, null if auth fails
*/
me.verifyRequest = function(req, callback) {
var that = this;
Step(
// check api key
function(){
that.checkAPIKey(req, this);
},
// get user id or fail
function (err, apikey_valid) {
if (apikey_valid) {
that.getId(req, this);
} else {
// no auth
callback(false, null);
}
},
function (err, user_id){
if (err) throw err;
callback(false, user_id);
}
);
};
return me;
})();

View File

@ -65,7 +65,7 @@ var oAuth = function(){
me.verifyRequest = function(req, callback){
var that = this;
//TODO: review this
var http = arguments['2'];
var http = true;//arguments['2'];
var passed_tokens;
var ohash;
var signature;

View File

@ -2,7 +2,7 @@
"private": true,
"name": "cartodb_api",
"description": "high speed SQL api for cartodb",
"version": "0.0.1",
"version": "0.0.2",
"author": {
"name": "Simon Tokumine, Vizzuality",
"url": "http://vizzuality.com",
@ -10,14 +10,16 @@
},
"dependencies": {
"cluster": "0.6.4",
"express": "2.4.3",
"express": "2.5.8",
"underscore" : "1.1.x",
"underscore.string": "1.1.5",
"pg": "0.5.6",
"pg": "0.6.14",
"generic-pool": "1.0.x",
"redis": "0.6.1",
"redis": "0.7.1",
"hiredis": "*",
"step": "0.0.x",
"oauth-client": "0.2.0"
"oauth-client": "0.2.0",
"node-uuid":"1.3.3"
},
"devDependencies": {
"expresso": "0.8.x"

56
spike/app.js Normal file
View File

@ -0,0 +1,56 @@
var express = require('express')
, app = express.createServer(
express.logger({
buffer: true,
format: '[:date] :req[X-Real-IP] \033[90m:method\033[0m \033[36m:req[Host]:url\033[0m \033[90m:status :response-time ms -> :res[Content-Type]\033[0m'
}))
, Step = require('step')
, _ = require('underscore');
app.use(express.bodyParser());
app.use(express.static(__dirname + '/public'));
app.enable('jsonp callback');
var io = require('socket.io');
io = io.listen(app);
io.configure('development', function(){
io.set('log level', 1);
io.set('origins', '*:*');
});
app.listen(8080);
// hacked postgres setup
//var pg = require('pg');
var pg = require('pg').native //native libpq bindings = `
var conString = "tcp://postgres@localhost/cartodb_dev_user_2_db";
var client = new pg.Client(conString);
client.connect();
io.sockets.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
});
socket.on('sql_query', function(data){
var query = client.query(data.sql);
var id = data.id;
query.on('row', function(row) {
socket.emit("sql_result", {r:row, id:id, state:1})
});
query.on('end',function(){
socket.emit("sql_result", {id:id, state:0});
});
query.on('error', function(row){
socket.emit("sql_result", {r:row, id:id, state:-1})
});
});
});

View File

@ -0,0 +1,150 @@
function Point(x, y) {
this.x = x;
this.y = y;
}
/** return a copy of this point with coordinates as int */
Point.prototype.floor = function() {
return new Point(this.x>>0, this.y>>0);
}
function LatLng(lat, lng) {
this.lat = lat;
this.lng = lng;
}
LatLng.prototype.clone = function() {
return new LatLng(this.lat, this.lng);
}
var TILE_SIZE = 256;
MercatorProjection.prototype.TILE_SIZE = TILE_SIZE;
function bound(value, opt_min, opt_max) {
if (opt_min != null) value = Math.max(value, opt_min);
if (opt_max != null) value = Math.min(value, opt_max);
return value;
}
function degreesToRadians(deg) {
return deg * (Math.PI / 180);
}
function radiansToDegrees(rad) {
return rad / (Math.PI / 180);
}
function MercatorProjection() {
this.pixelOrigin_ = new Point(TILE_SIZE / 2,
TILE_SIZE / 2);
this.pixelsPerLonDegree_ = TILE_SIZE / 360;
this.pixelsPerLonRadian_ = TILE_SIZE / (2 * Math.PI);
}
MercatorProjection.prototype.fromLatLngToPixel = function(latLng, zoom) {
var p = this.fromLatLngToPoint(latLng);
return this.toPixelCoordinate(p, zoom);
};
MercatorProjection.prototype.fromLatLngToPoint = function(latLng,
opt_point) {
var me = this;
var point = opt_point || new Point(0, 0);
var origin = me.pixelOrigin_;
point.x = origin.x + latLng.lng * me.pixelsPerLonDegree_;
// NOTE(appleton): Truncating to 0.9999 effectively limits latitude to
// 89.189. This is about a third of a tile past the edge of the world
// tile.
var siny = bound(Math.sin(degreesToRadians(latLng.lat)), -0.9999,
0.9999);
point.y = origin.y + 0.5 * Math.log((1 + siny) / (1 - siny)) *
-me.pixelsPerLonRadian_;
return point;
};
MercatorProjection.prototype.fromPointToLatLng = function(point) {
var me = this;
var origin = me.pixelOrigin_;
var lng = (point.x - origin.x) / me.pixelsPerLonDegree_;
var latRadians = (point.y - origin.y) / -me.pixelsPerLonRadian_;
var lat = radiansToDegrees(2 * Math.atan(Math.exp(latRadians)) -
Math.PI / 2);
return new LatLng(lat, lng);
};
MercatorProjection.prototype.tileBBox = function(x, y, zoom) {
var numTiles = 1 << zoom;
var inc = TILE_SIZE/numTiles;
var px = x*TILE_SIZE/numTiles;
var py = y*TILE_SIZE/numTiles;
return [
this.fromPointToLatLng(new Point(px, py + inc)),
this.fromPointToLatLng(new Point(px + inc, py))
];
};
MercatorProjection.prototype.tilePoint = function(x, y, zoom) {
var numTiles = 1 << zoom;
var px = x*TILE_SIZE;
var py = y*TILE_SIZE;
return [px, py];
}
MercatorProjection.prototype.fromPixelToLatLng = function(pixel, zoom) {
var numTiles = 1 << zoom;
var p = new Point(
pixel.x/numTiles,
pixel.y/numTiles);
return this.fromPointToLatLng(p);
}
MercatorProjection.prototype.toPixelCoordinate = function(worldCoordinate, zoom) {
var numTiles = 1 << zoom;
return new Point(
worldCoordinate.x * numTiles,
worldCoordinate.y * numTiles);
}
MercatorProjection.prototype.latLngToTilePoint = function(latLng, x, y, zoom) {
var numTiles = 1 << zoom;
var projection = this;
var worldCoordinate = projection.fromLatLngToPoint(latLng);
var pixelCoordinate = new Point(
worldCoordinate.x * numTiles,
worldCoordinate.y * numTiles);
var tp = this.tilePoint(x, y, zoom);
return new Point(
Math.floor(pixelCoordinate.x - tp[0]),
Math.floor(pixelCoordinate.y - tp[1]));
}
MercatorProjection.prototype.pixelToTile = function(pixelCoordinate) {
return new Point(
Math.floor(pixelCoordinate.x / TILE_SIZE),
Math.floor(pixelCoordinate.y / TILE_SIZE));
};
MercatorProjection.prototype.pointToTile = function(point, zoom) {
var numTiles = 1 << zoom;
var pixelCoordinate = new Point(
point.x * numTiles,
point.y * numTiles);
return this.pixelToTile(pixelCoordinate);
};
MercatorProjection.prototype.latLngToTile = function(latLng, zoom) {
var numTiles = 1 << zoom;
var projection = this;
var worldCoordinate = projection.fromLatLngToPoint(latLng);
var pixelCoordinate = new Point(
worldCoordinate.x * numTiles,
worldCoordinate.y * numTiles);
return new Point(
Math.floor(pixelCoordinate.x / TILE_SIZE),
Math.floor(pixelCoordinate.y / TILE_SIZE));
}

59
spike/public/index.html Normal file
View File

@ -0,0 +1,59 @@
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="http://localhost:8080/socket.io/socket.io.js"></script>
<link rel="stylesheet" href="http://code.leafletjs.com/leaflet-0.3.1/leaflet.css" />
<!--[if lte IE 8]>
<link rel="stylesheet" href="http://code.leafletjs.com/leaflet-0.3.1/leaflet.ie.css" />
<![endif]-->
<script src="http://code.leafletjs.com/leaflet-0.3.1/leaflet.js"></script>
<script>
$(document).ready(function () {
var map = new L.Map('map');
var cloudmade = new L.TileLayer('http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png', {
attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="http://cloudmade.com">CloudMade</a>',
maxZoom: 18
});
var london = new L.LatLng(51.505, -0.09);
map.setView(london, 3).addLayer(cloudmade);
var geojson = new L.GeoJSON();
map.addLayer(geojson);
var socket = io.connect('http://localhost:8080');
socket.on('news', function (data) {
console.log(data);
socket.emit('my other event', { my:'data' });
});
$('#go').click(function () {
var sql = $("#sql_input").val();
socket.emit("sql_query", {sql:sql, id:'TUMADRE'});
});
socket.on('sql_result', function(data){
if (data.state == 1){
//console.log(data.r['the_geom']);
geojson.addGeoJSON(JSON.parse(data.r['the_geom']));
}
//$('#sql_result').append(JSON.stringify(data));
});
});
</script>
</head>
<body>
<input type="text" id="sql_input"></input>
<button type="submit" id="go">go</button>
<div id="map" style="height: 100%;width:100%;"></div>
</body>
</html>

266
spike/public/map.js Normal file
View File

@ -0,0 +1,266 @@
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
function Event() {}
Event.prototype.on = function(evt, callback) {
var cb = this.callbacks = this.callbacks || {};
var l = cb[evt] || (cb[evt] = []);
l.push(callback);
};
Event.prototype.emit = function(evt) {
var c = this.callbacks && this.callbacks[evt];
for(var i = 0; c && i < c.length; ++i) {
c[i].apply(this, Array.prototype.slice.call(arguments, 1));
}
};
function MapModel(opts) {
opts = opts || {};
this.projection = new MercatorProjection();
this.setCenter(opts.center || new LatLng(0,0));
this.setZoom(opts.zoom || 1);
}
MapModel.prototype = new Event();
MapModel.prototype.setCenter = function(center) {
this.center = new LatLng(center.lat, center.lng);
this.center_pixel = this.projection.fromLatLngToPixel(this.center, this.zoom).floor();
this.emit('center_changed', this.center);
};
MapModel.prototype.setZoom = function(zoom) {
this.zoom = zoom;
this.center_pixel = this.projection.fromLatLngToPixel(this.center, this.zoom).floor();
this.emit('zoom_changed', this.center);
};
MapModel.prototype.getCenterPixel = function() {
var center_point = this.projection.fromLatLngToPixel(this.center, this.zoom);
return center_point;
}
MapModel.prototype.getTopLeft = function(width, height) {
var center_point = this.projection.fromLatLngToPixel(this.center, this.zoom);
var widthHalf = width / 2;
var heightHalf = height / 2;
center_point.x -= widthHalf;
center_point.y -= heightHalf;
return center_point;
}
MapModel.prototype.getBBox = function(width, height) {
var center_point = this.projection.fromLatLngToPixel(this.center, this.zoom);
var widthHalf = width / 2;
var heightHalf = height / 2;
center_point.x -= widthHalf;
center_point.y += heightHalf;
var bottomleft = this.projection.fromPixelToLatLng(center_point, this.zoom);
center_point.x += width;
center_point.y -= height;
var topRight = this.projection.fromPixelToLatLng(center_point, this.zoom);
return [bottomleft, topRight]
}
/**
* return a list of tiles inside the spcified zone
* the center will be placed on the center of that zone
*/
MapModel.prototype.visibleTiles = function(width, height) {
var self = this;
var widthHalf = width / 2;
var heightHalf = height / 2;
var center_point = self.projection.fromLatLngToPixel(self.center, self.zoom);
center_point.x -= widthHalf;
center_point.y -= heightHalf;
var tile = this.projection.pixelToTile(center_point, self.zoom);
var offset_x = center_point.x%this.projection.TILE_SIZE;
var offset_y = center_point.y%this.projection.TILE_SIZE;
var num_tiles_x = Math.ceil((width + offset_x)/this.projection.TILE_SIZE);
var num_tiles_y = Math.ceil((height + offset_y)/this.projection.TILE_SIZE);
var tiles = [];
for(var i = 0; i < num_tiles_x; ++i) {
for(var j = 0; j < num_tiles_y; ++j) {
var tile_x = tile.x + i;
var tile_y = tile.y + j;
tiles.push({
x: tile_x * this.projection.TILE_SIZE,
y: tile_y * this.projection.TILE_SIZE,
zoom: self.zoom,
i: tile_x,
j: tile_y
});
}
}
return tiles;
}
function dragger(el) {
var self = this;
var dragging = false;
var x, y;
el.ontouchstart = el.onmousedown = function(e) {
dragging = true;
if (e.touches) {
var p = e.touches[0];
x = p.pageX;
y = p.pageY;
} else {
x = e.clientX;
y = e.clientY;
}
self.emit('startdrag', x, y);
};
el.ontouchmove = el.onmousemove = function(e) {
var xx, yy;
if(!dragging) return;
if (e.touches) {
var p = e.touches[0];
xx = p.pageX;
yy = p.pageY;
} else {
xx = e.clientX;
yy = e.clientY;
}
self.emit('move', xx - x, yy - y);
return false;
};
el.ontouchend = el.onmouseup = function(e) {
dragging = false;
self.emit('enddrag', x, y);
};
}
dragger.prototype = new Event();
function CanvasRenderer(el, map) {
var self = this;
this.el = el;
this.tiles = {};
this.width = el.offsetWidth >> 0;
this.height = el.offsetHeight >> 0;
var widthHalf = (this.width / 2) >> 0;
var heightHalf = (this.height / 2) >> 0;
var canvas = this.canvas = document.createElement('canvas');
canvas.style.padding = '0';
canvas.style.margin= '0';
canvas.style.position = 'absolute';
canvas.width = this.width;
canvas.height = this.height;
var context = canvas.getContext( '2d' );
context.translate( widthHalf, heightHalf );
this.context = context;
var div = document.createElement('div');
div.style.width = this.width + "px";
div.style.height= this.height + "px";
div.style.position = 'relative';
div.appendChild(canvas);
el.appendChild(div);
this.center_init = null;
this.target_center = new LatLng();
this.drag = new dragger(div);
this.drag.on('startdrag', function() {
self.center_init = map.center.clone();
});
this.drag.on('enddrag', function() {
map.emit('end_move');
});
function go_to_target() {
var c = map.center;
var t = self.target_center;
var dlat = t.lat - c.lat;
var dlon = t.lng - c.lng;
t.lat += dlat*0.0001;
t.lng += dlon*0.0001;
map.setCenter(t);
if(Math.abs(dlat) + Math.abs(dlon) > 0.001) {
requestAnimFrame(go_to_target);
} else {
//map.emit('end_move');
}
}
this.drag.on('move', function(dx, dy) {
var t = 1 << map.zoom;
var s = 1/t;
s = s/map.projection.pixelsPerLonDegree_;
self.target_center.lat = self.center_init.lat + dy*s;
self.target_center.lng = self.center_init.lng - dx*s;
requestAnimFrame(go_to_target);
});
}
CanvasRenderer.prototype.renderTile = function(tile, at) {
var self = this;
var key = at.x + '_' + at.y
if(a=self.tiles[key]) {
self.context.drawImage(a, at.x, at.y);
return;
}
//var layer = 'http://a.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{{z}}/{{x}}/{{y}}.png';
var layer = 'http://b.tiles.mapbox.com/v3/mapbox.mapbox-light/{{z}}/{{x}}/{{y}}.png64';
var url = layer.replace('{{z}}', tile.zoom).replace('{{x}}', tile.i).replace('{{y}}', tile.j);
var img = new Image();
img.src = url;
img.onload = function() {
self.context.drawImage(img, at.x, at.y);
self.tiles[key] = img;
};
}
CanvasRenderer.prototype.renderTiles = function(tiles, center) {
for(var i = 0; i < tiles.length; ++i) {
var tile = tiles[i];
var p = new Point(tile.x, tile.y);
p.x -= center.x;
p.y -= center.y;
this.renderTile(tile, p);
}
}
function Map(el, opts) {
opts = opts || {};
var self = this;
this.model = new MapModel({
center: opts.center || new LatLng(41.69, -4.83),
zoom: opts.zoom || 1
});
this.view = new CanvasRenderer(el, this.model);
/*function render() {
var tiles = self.model.visibleTiles(self.view.width, self.view.height);
self.view.renderTiles(tiles, this.center_pixel);
}
this.model.on('center_changed', render);
this.model.on('zoom_changed', render);
this.model.emit('center_changed');
*/
}

1
spike/public/run_server.sh Executable file
View File

@ -0,0 +1 @@
python -m SimpleHTTPServer 8000

111
spike/public/test.html Normal file
View File

@ -0,0 +1,111 @@
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no">
<style>
html, body, #map {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
#map {
width: 100%;
height: 100%;
}
.button {
position: absolute;
background-color: #888;
font: bold 16px Helvetica;
padding: 5px 10px;
width: 10px;
color: white;
text-decoration: none;
border-radius: 3px;
display: inline-block;
text-align: center;
}
#plus {
top: 40px;
left: 40px;
}
#minus {
top: 40px;
left: 73px;
}
.button:hover {
background-color: #BBB;
}
</style>
</head>
<body>
<div id="map"></div>
<a id="plus" href="#" class="button">+</a>
<a id="minus" href="#" class="button">-</a>
</body>
<script src="gmaps_mercator.js"></script>
<script src="map.js"></script>
<script src="http://localhost:8080/socket.io/socket.io.js"></script>
<script>
map = new Map(document.getElementById('map'), {
center: new LatLng(41.69, -4.83),
zoom: 4
});
document.getElementById('plus').onclick = function() {
map.model.setZoom(map.model.zoom + 1);
return false;
}
document.getElementById('minus').onclick = function() {
map.model.setZoom(map.model.zoom - 1);
return false;
}
function render() {
//var tiles = self.model.visibleTiles(self.view.width, self.view.height);
//self.view.renderTiles(tiles, this.center_pixel);
}
var socket = io.connect('http://localhost:8080');
function get_sql() {
var bbox = map.model.getBBox(map.view.width, map.view.height);
var sql = "select st_asgeojson(the_geom,5) as the_geom_geojson from places WHERE the_geom && ST_SetSRID(ST_MakeBox2D(";
sql += "ST_Point(" + bbox[0].lng + "," + bbox[0].lat +"),";
sql += "ST_Point(" + bbox[1].lng + "," + bbox[1].lat +")), 4326)";
return sql ;
}
map.model.on('end_move', jajaja=function() {
var s = get_sql();
//console.log(s);
//setTimeout(function() {
socket.emit("sql_query", {sql: s, id:'LOVELY'});
//}, 1000);
var tiles = map.model.visibleTiles(map.view.width, map.view.height);
map.view.renderTiles(tiles, this.center_pixel);
});
function add_point(p) {
var p = new LatLng(p[1], p[0]);
var px = map.model.projection.fromLatLngToPixel(p, map.model.zoom);
var centerpx= map.model.getCenterPixel(map.view.width, map.view.height);
map.view.context.fillStyle = '#000';
map.view.context.fillRect(px.x - centerpx.x , px.y - centerpx.y, 2, 2);
}
socket.on('sql_result', function(data){
if (data.state == 1){
geo = JSON.parse(data.r['the_geom_geojson']);
add_point(geo.coordinates);
}
});
map.model.on('zoom_changed', jajaja);
map.model.emit('center_changed');
</script>
</html>

View File

@ -0,0 +1,57 @@
describe("Event", function() {
before_each(function() {
this.evt = new Event();
});
it("call event binded", function() {
var c = 0;
function callback() {
c++;
}
this.evt.on('test', callback);
this.evt.emit('test');
assert(c == 1);
});
it("should works when call non existing event", function() {
this.evt.emit('test_no_exists');
});
});
describe("MapModel", function() {
before_each(function() {
this.map_model = new MapModel(new LatLng(0, 0));
});
it("center_changed should be called", function() {
var c = 0;
this.map_model.on('center_changed', function() {
++c;
});
this.map_model.setCenter(new LatLng(0, 0));
assert(c == 1);
});
it("zoom_changed should be called", function() {
var c = 0;
this.map_model.on('zoom_changed', function() {
++c;
});
this.map_model.setZoom(2);
assert(c == 1);
});
it("visibleTiles", function() {
var ts = this.map_model.projection.TILE_SIZE;
this.map_model.setZoom(10);
var tiles = this.map_model.visibleTiles(ts, ts);
assert(tiles.length == 4);
this.map_model.setCenter(new LatLng(0.3, 1.2));
tiles = this.map_model.visibleTiles(ts, ts);
assert(tiles.length == 4);
});
});

View File

@ -0,0 +1,62 @@
<!doctype html>
<html lang=en>
<head>
<title>quick'n'dirty js testing framework</title>
<meta charset=utf-8>
<style type="text/css" media="screen">
h1{ font: 1.5em Helvetica; }
p { font: .9em courier; padding: .5em 1em; margin: 0;}
.result { margin-top: 1px; }
.pass { color: #4F8A10; background: #DFF2BF }
.fail { color: #D8000C; background: #FFBABA }
.error{ color: white; background: red; font-weight: bold; }
.describe { border-bottom: 1px solid #ccc; padding: 20px; }
</style>
<script>
var setup_fn = null;
function describe(desc, fn) {
document.write('<div class="describe">');
document.write('<h1>' + desc +'</h1>');
fn();
document.write('</div>');
setup_fn = null;
}
function before_each(be) {
setup_fn = be;
}
var result = true;
function assert(cond, msg) {
if(result) {
if(!cond) {
result = false;
}
}
}
function it(desc, fn) {
var output = function(klass, msg){
document.write('<p class="result ' + klass + '">' + msg + '</p>')
}
try{
if (setup_fn != null)
setup_fn();
result = true;
fn()
} catch(err){ result = err }
if (typeof result === 'boolean'){
result ? output('pass', desc) : output('fail', desc)
}else{
output('error', ['ERROR:',result.message,'when testing',desc].join(' '))
}
}
</script>
<script src="../gmaps_mercator.js"> </script>
<script src="../map.js"> </script>
<script src="map.test.js"> </script>
</head>
<body></body>
</html>

View File

@ -0,0 +1,32 @@
require('../helper');
var app = require(global.settings.app_root + '/app/controllers/app')
, assert = require('assert')
, tests = module.exports = {}
, querystring = require('querystring');
tests['valid api key should allow insert in protected tables'] = function(){
assert.response(app, {
// view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&q=INSERT%20INTO%20private_table%20(name)%20VALUES%20('test')&database=cartodb_dev_user_1_db",
headers: {host: 'vizzuality.cartodb.com' },
method: 'GET'
},{
status: 200
});
}
tests['invalid api key should NOT allow insert in protected tables'] = function(){
assert.response(app, {
// view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=RAMBO&q=INSERT%20INTO%20private_table%20(name)%20VALUES%20('test')&database=cartodb_dev_user_1_db",
headers: {host: 'vizzuality.cartodb.com' },
method: 'GET'
},{
status: 400
});
}

View File

@ -18,8 +18,10 @@ var app = require(global.settings.app_root + '/app/controllers/app')
, tests = module.exports = {}
, querystring = require('querystring');
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"';
// allow lots of emitters to be set to silence warning
app.setMaxListeners(0);
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['GET /api/v1/sql'] = function(){

View File

@ -5,6 +5,7 @@
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 "preparing postgres..."
dropdb -Upostgres -hlocalhost cartodb_test_user_1_db

View File

@ -62,8 +62,51 @@ ALTER TABLE ONLY untitle_table_4 ADD CONSTRAINT test_table_pkey PRIMARY KEY (car
CREATE INDEX test_table_the_geom_idx ON untitle_table_4 USING gist (the_geom);
CREATE INDEX test_table_the_geom_webmercator_idx ON untitle_table_4 USING gist (the_geom_webmercator);
CREATE TABLE private_table (
updated_at timestamp without time zone DEFAULT now(),
created_at timestamp without time zone DEFAULT now(),
cartodb_id integer NOT NULL,
name character varying,
address character varying,
the_geom geometry,
the_geom_webmercator geometry,
CONSTRAINT enforce_dims_the_geom CHECK ((st_ndims(the_geom) = 2)),
CONSTRAINT enforce_dims_the_geom_webmercator CHECK ((st_ndims(the_geom_webmercator) = 2)),
CONSTRAINT enforce_geotype_the_geom CHECK (((geometrytype(the_geom) = 'POINT'::text) OR (the_geom IS NULL))),
CONSTRAINT enforce_geotype_the_geom_webmercator CHECK (((geometrytype(the_geom_webmercator) = 'POINT'::text) OR (the_geom_webmercator IS NULL))),
CONSTRAINT enforce_srid_the_geom CHECK ((st_srid(the_geom) = 4326)),
CONSTRAINT enforce_srid_the_geom_webmercator CHECK ((st_srid(the_geom_webmercator) = 3857))
);
CREATE SEQUENCE test_table_cartodb_id_seq_p
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE test_table_cartodb_id_seq_p OWNED BY private_table.cartodb_id;
SELECT pg_catalog.setval('test_table_cartodb_id_seq_p', 60, true);
ALTER TABLE private_table ALTER COLUMN cartodb_id SET DEFAULT nextval('test_table_cartodb_id_seq_p'::regclass);
INSERT INTO private_table VALUES ('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.314252', 1, 'Hawai', 'Calle de Pérez Galdós 9, Madrid, Spain', '0101000020E6100000A6B73F170D990DC064E8D84125364440', '0101000020110F000076491621312319C122D4663F1DCC5241');
INSERT INTO private_table VALUES ('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.319101', 2, 'El Estocolmo', 'Calle de la Palma 72, Madrid, Spain', '0101000020E6100000C90567F0F7AB0DC0AB07CC43A6364440', '0101000020110F0000C4356B29423319C15DD1092DADCC5241');
INSERT INTO private_table VALUES ('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.324', 3, 'El Rey del Tallarín', 'Plaza Conde de Toreno 2, Madrid, Spain', '0101000020E610000021C8410933AD0DC0CB0EF10F5B364440', '0101000020110F000053E71AC64D3419C10F664E4659CC5241');
INSERT INTO private_table VALUES ('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.329509', 4, 'El Lacón', 'Manuel Fernández y González 8, Madrid, Spain', '0101000020E6100000BC5983F755990DC07D923B6C22354440', '0101000020110F00005DACDB056F2319C1EC41A980FCCA5241');
INSERT INTO private_table VALUES ('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.334931', 5, 'El Pico', 'Calle Divino Pastor 12, Madrid, Spain', '0101000020E61000003B6D8D08C6A10DC0371B2B31CF364440', '0101000020110F00005F716E91992A19C17DAAA4D6DACC5241');
ALTER TABLE ONLY private_table ADD CONSTRAINT test_table_pkey_p PRIMARY KEY (cartodb_id);
CREATE INDEX test_table_the_geom_idx_p ON private_table USING gist (the_geom);
CREATE INDEX test_table_the_geom_webmercator_idx_p ON private_table USING gist (the_geom_webmercator);
CREATE USER publicuser WITH PASSWORD '';
CREATE USER test_cartodb_user_1 WITH PASSWORD '';
GRANT SELECT ON TABLE untitle_table_4 TO publicuser;
GRANT ALL ON TABLE private_table TO test_cartodb_user_1;
GRANT ALL ON SEQUENCE test_table_cartodb_id_seq_p TO test_cartodb_user_1