diff --git a/lib/cartodb/carto_data.js b/lib/cartodb/carto_data.js index e43b7233..a5c5939d 100644 --- a/lib/cartodb/carto_data.js +++ b/lib/cartodb/carto_data.js @@ -19,7 +19,8 @@ module.exports = function() { user_metadata_db: 5, table_metadata_db: 0, user_key: "rails:users:<%= username %>", - table_key: "rails:<%= database_name %>:<%= table_name %>" + table_key: "rails:<%= database_name %>:<%= table_name %>", + user_stats_key: "tiler:users:<%= username %>" }; @@ -43,6 +44,24 @@ module.exports = function() { }); }; + me.userFromHostname = function(hostname) { + return hostname.split('.')[0]; + } + + /** + * Increment mapview count for a user + * + * @param username + * @param callback will be called with the new value + */ + me.incMapviewCount = function(username, callback) { + var redisKey = _.template(this.user_stats_key, {username: username}); + + this.redisCmd(me.user_metadata_db, 'HINCRBY', [redisKey, 'mapviews', 1], function(err, val) { + if ( err ) callback(err, null); + else callback(err, val); + }); + }; /** @@ -207,6 +226,11 @@ module.exports = function() { this.redisCmd(db,'SISMEMBER',[setKey, member], callback); }; + // Redis INCREMENT + me.increment = function(db, key, callback) { + this.redisCmd(db,'INCR', key, callback); + }; + /** * Use Redis * diff --git a/lib/cartodb/server_options.js b/lib/cartodb/server_options.js index d39fcead..cc4c1f7e 100644 --- a/lib/cartodb/server_options.js +++ b/lib/cartodb/server_options.js @@ -220,6 +220,29 @@ module.exports = function(){ me.afterLayergroupCreate = function(req, mapconfig, response, callback) { var token = response.layergroupid; + var username = cartoData.userFromHostname(req.headers.host); + + var tasksleft = 2; // redis key and affectedTables + var errors = []; + + var done = function(err) { + if ( err ) { + errors.push('' + err); + } + if ( ! --tasksleft ) { + err = errors.length ? new Error(errors.join('\n')) : null; + callback(err); + } + } + + // Don't wait for the mapview count increment to + // take place before proceeding. Error will be logged + // asyncronously + cartoData.incMapviewCount(username, function(err) { + if ( err ) console.log("ERROR: failed to increment mapview count for user '" + username + "': " + err); + done(); + }); + var sql = []; _.each(mapconfig.layers, function(lyr) { sql.push(lyr.options.sql); @@ -234,14 +257,14 @@ module.exports = function(){ me.affectedTables(usr, key, sql, function(err, tableNames) { - if ( err ) { callback(err); return; } + if ( err ) { done(err); return; } var cacheChannel = me.buildCacheChannel(dbName,tableNames); me.channelCache[cacheKey] = cacheChannel; // store for caching // find last updated me.findLastUpdated(usr, key, tableNames, function(err, lastUpdated) { - if ( err ) { callback(err); return; } + if ( err ) { done(err); return; } response.last_updated = lastUpdated; - callback(null); + done(null); }); }); }; diff --git a/test/acceptance/multilayer.js b/test/acceptance/multilayer.js index 259c0c6e..35e0027e 100644 --- a/test/acceptance/multilayer.js +++ b/test/acceptance/multilayer.js @@ -329,6 +329,87 @@ suite('multilayer', function() { ); }); + test("layergroup creation raises mapviews counter", function(done) { + var layergroup = { + version: '1.0.0', + layers: [ + { options: { + sql: 'select 1 as cartodb_id, ' + + 'ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator', + cartocss: '#layer { polygon-fill:red; }', + cartocss_version: '2.0.1' + } } + ] + }; + var statskey = "tiler:users:localhost"; + var mapviews_field = 'mapviews'; + var redis_stats_client = redis.createClient(global.environment.redis.port); + var redis_stats_db = 5; + var expected_token; // will be set on first post and checked on second + Step( + function clean_stats() + { + var next = this; + redis_stats_client.select(redis_stats_db, function(err) { + if ( err ) next(err); + else redis_stats_client.del(statskey, next); + }); + }, + function do_post_1(err) + { + if ( err ) throw err; + var next = this; + assert.response(server, { + url: '/tiles/layergroup', + method: 'POST', + headers: {host: 'localhost', 'Content-Type': 'application/json' }, + data: JSON.stringify(layergroup) + }, {}, function(res) { + assert.equal(res.statusCode, 200, res.body); + expected_token = JSON.parse(res.body).layergroupid; + redis_stats_client.hget(statskey, mapviews_field, next); + }); + }, + function check_stats_1_do_post_2(err, val) { + if ( err ) throw err; + assert.equal(val, 1); + var next = this; + assert.response(server, { + url: '/tiles/layergroup', + method: 'POST', + headers: {host: 'localhost', 'Content-Type': 'application/json' }, + data: JSON.stringify(layergroup) + }, {}, function(res) { + assert.equal(res.statusCode, 200, res.body); + assert.equal(JSON.parse(res.body).layergroupid, expected_token); + redis_stats_client.hget(statskey, mapviews_field, next); + }); + }, + function check_stats_2(err, val) + { + if ( err ) throw err; + assert.equal(val, 2); + return 1; + }, + function finish(err) { + var errors = []; + if ( err ) { + errors.push(err.message); + console.log("Error: " + err); + } + redis_client.keys("map_style|cartodb_test_user_1_db|~" + expected_token, function(err, matches) { + if ( err ) errors.push(err.message); + assert.equal(matches.length, 1, "Missing expected token " + expected_token + " from redis: " + matches); + redis_client.del(matches, function(err) { + if ( err ) errors.push(err.message); + if ( errors.length ) done(new Error(errors)); + else done(null); + }); + }); + } + ); + }); + suiteTeardown(function(done) { // This test will add map_style records, like // 'map_style|null|publicuser|my_table',