├── .eslintrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── bin ├── import.bin.js └── sql.bin.js ├── examples ├── config.sample.js ├── import-file │ ├── all_week.csv │ ├── file.js │ └── invalid.mdb ├── import-url │ └── url.js ├── maps-named │ ├── named.js │ ├── template.json │ └── updateTemplate.json ├── sql-json │ └── json.js └── sql-pipe │ ├── output.json │ └── pipe.js ├── index.js ├── lib ├── cartodb.js ├── import.js ├── maps.js ├── promise.js ├── resources.json ├── sql.js └── util │ ├── cli.js │ └── request.js ├── package.json └── test ├── functional └── sql.func.test.js ├── secret.js.example └── unit ├── import.unit.test.js ├── maps.unit.test.js └── sql.unit.test.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | /*"extends": "airbnb/legacy",*/ 3 | "env": { 4 | "browser": false, 5 | "node": true, 6 | }, 7 | "rules": { 8 | "quotes": [2, "single"], 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | examples/config.js 3 | test/secret.js 4 | 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/secret.js 2 | examples/secret.js 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Vizzuality 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 3. All advertising materials mentioning features or use of this software 12 | must display the following acknowledgement: 13 | This product includes software developed by Vizzuality. 14 | 4. Neither the name of Vizzuality nor the 15 | names of its contributors may be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY 19 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY 22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CARTO (formerly CartoDB) client for node.js 2 | ================================= 3 | 4 | This library abstracts calls to CARTO's SQL, Import, and Maps APIs. 5 | 6 | NB! It is not official and not maintained. The version in NPM is outdated and does not work. If you want to use a bit updated version, use one directly from GH repo as: 7 | ``` 8 | "cartodb": "github:cartodb/cartodb-nodejs#master", 9 | ``` 10 | 11 | Install 12 | ------- 13 | 14 | ```bash 15 | npm install github:cartodb/cartodb-nodejs#master 16 | ``` 17 | 18 | 19 | Usage 20 | ----- 21 | 22 | Here's a simple example to make a SQL API call. 23 | 24 | ``` 25 | var CartoDB = require('cartodb'); 26 | 27 | var sql = new CartoDB.SQL({user:{USERNAME}}) 28 | 29 | sql.execute("SELECT * FROM mytable LIMIT 10") 30 | //you can listen for 'done' and 'error' promise events 31 | .done(function(data) { 32 | console.log(data) //data.rows is an array with an object for each row in your result set 33 | }); 34 | 35 | ``` 36 | 37 | SQL Module 38 | ---------- 39 | 40 | ### Methods 41 | 42 | #### constructor 43 | 44 | ``` 45 | var sql = new CartoDB.SQL({ 46 | user: {USERNAME}, 47 | api_key: {APIKEY} 48 | }); 49 | ``` 50 | Note: `api_key` is only required for a write query. 51 | 52 | #### SQL.execute 53 | 54 | `SQL.execute(sql [,vars][, options][, callback])` 55 | 56 | `vars` - An object of variables to be rendered with `sql` using Mustache 57 | 58 | `options` - An object for options for the API call. Default is `json`. 59 | ``` 60 | { 61 | format: 'json'|'csv'|'geojson'|'shp'|'svg'|'kml'|'SpatiaLite' 62 | } 63 | ``` 64 | More details about formats : [CARTO SQL API docs](https://carto.com/docs/carto-engine/sql-api/making-calls/) 65 | 66 | `callback` - A function that the `data` object will be passed to if the API call is successful. 67 | 68 | This will return a promise. `.done()` and `.error()` can be chained after `SQL.execute()`. 69 | 70 | `.done()` first argument will be either : 71 | an object containing (when using json format) or a raw string (when using all other formats). 72 | 73 | ``` 74 | var sql = new CartoDB.SQL({user:{USERNAME}); 75 | sql.execute("SELECT * from {{table}} LIMIT 5") 76 | .done(function(data) { 77 | console.log(`Executed in ${data.time}, total rows: ${data.total_rows}`); 78 | console.log(data.rows[0].cartodb_id) 79 | }) 80 | .error(function(error) { 81 | //error contains the error message 82 | }); 83 | ``` 84 | 85 | ``` 86 | var sql = new CartoDB.SQL({user:{USERNAME}}); 87 | sql.execute("SELECT * from {{table}} LIMIT 5", { format: 'csv' }) 88 | .done(function(data) { 89 | console.log('raw CSV data :'); 90 | console.log(data); 91 | }); 92 | ``` 93 | 94 | ### Piping 95 | 96 | You can pipe data from the SQL.execute() 97 | 98 | ``` 99 | var file = require('fs').createWriteStream(__dirname + '/output.json'); 100 | 101 | var sql = new CartoDB.SQL({user:{USERNAME}, api_key:{APIKEY}}); 102 | 103 | sql.execute("SELECT * from {{table}} LIMIT 5", {table: 'all_month'}) 104 | 105 | 106 | sql.pipe(file); 107 | ``` 108 | 109 | 110 | Import Module 111 | ------------- 112 | 113 | ### Methods 114 | 115 | `file` - Import a file from the filesystem - This method is the same as dragging a file (CSV,ZIP,XLS,KML) into the CartoDB GUI. The end result is a table in your account. 116 | 117 | This method takes the path to your file and results in a table_name for the newly-created table. 118 | 119 | `Import.file(filePath, options)` 120 | 121 | 122 | `.done()` and `.error()` can be chained after `Import.file()`. 123 | 124 | `url` - Import a file from URL - This method is the same as specifying a publicly accessible url to import in the CartoDB GUI. The end result is a table in your account. 125 | 126 | This method takes the URL to your file and results in a table_name for the newly-created table. 127 | 128 | `Import.url(url, options)` 129 | 130 | `.done()` and `.error()` can be chained after `Import.url()`. 131 | 132 | ``` 133 | var importer = new CartoDB.Import({user:{USERNAME}, api_key:{APIKEY}}); 134 | var path = require('path'); 135 | 136 | importer 137 | .file(path.join(__dirname, 'all_week.csv'), { 138 | privacy: 'public' 139 | }) 140 | .done(function(table_name) { 141 | console.log('Table ' + table_name + ' has been created!'); 142 | }); 143 | 144 | ``` 145 | 146 | Named Maps Module 147 | ----------- 148 | 149 | ### Constructor 150 | 151 | Pass in a config object with `user` and `api_key` 152 | 153 | ``` 154 | var namedMaps = new CartoDB.Maps.Named({ 155 | user: 'username', 156 | api_key: 'yourreallylongcartodbapikey' 157 | }); 158 | ``` 159 | 160 | ### Methods 161 | 162 | All methods create a promise, and you can listen for `done` and `_error` events. 163 | 164 | #### `Named.create()` - Create a named map by providing a JSON template 165 | 166 | Named Map Template: 167 | ``` 168 | { 169 | "version": "0.0.1", 170 | "name": "world_borders", 171 | "auth": { 172 | "method": "token", 173 | "valid_tokens": [ 174 | "auth_token1", 175 | "auth_token2" 176 | ] 177 | }, 178 | "placeholders": { 179 | "color": { 180 | "type": "css_color", 181 | "default": "red" 182 | }, 183 | "cartodb_id": { 184 | "type": "number", 185 | "default": 1 186 | } 187 | }, 188 | "layergroup": { 189 | "version": "1.0.1", 190 | "layers": [ 191 | { 192 | "type": "cartodb", 193 | "options": { 194 | "cartocss_version": "2.1.1", 195 | "cartocss": "/** category visualization */ #world_borders { polygon-opacity: 1; line-color: #FFF; line-width: 0; line-opacity: 1; } #world_borders[cat=0] { polygon-fill: #A6CEE3; } #world_borders[cat=1] { polygon-fill: #1F78B4; } #world_borders[cat=2] { polygon-fill: #2167AB; } #world_borders[cat=3] { polygon-fill: #5077b5; }", 196 | "sql": "SELECT *, mod(cartodb_id,4) as cat FROM world_borders" 197 | } 198 | } 199 | ] 200 | }, 201 | "view": { 202 | "zoom": 4, 203 | "center": { 204 | "lng": 0, 205 | "lat": 0 206 | }, 207 | "bounds": { 208 | "west": -45, 209 | "south": -45, 210 | "east": 45, 211 | "north": 45 212 | } 213 | } 214 | } 215 | ``` 216 | 217 | 218 | ``` 219 | namedMaps.create({ 220 | template: template 221 | }) 222 | .on('done', function(res) { 223 | console.log(res) 224 | }) 225 | ``` 226 | Response: 227 | ``` 228 | { template_id: 'world_borders' } 229 | ``` 230 | 231 | 232 | #### `Named.instantiate()` - Instantiate a named map to get a layergroupid, passing an options object with the `template_id`, `auth_token` (if required), and placeholder `params` (if needed by your named map template) 233 | ``` 234 | namedMaps.instantiate({ 235 | template_id: 'world_borders', 236 | auth_token: 'auth_token1', 237 | params: { 238 | color: '#ff0000', 239 | cartodb_id: 3 240 | } 241 | }) 242 | .on('done', function(res) { 243 | console.log(res) 244 | }) 245 | ``` 246 | Response: 247 | ``` 248 | { layergroupid: 'chriswhong@72f19e2f@28aa9b31a7147f1d370f0f6322e16de6:1453321153152', 249 | metadata: { layers: [ [Object] ] }, 250 | cdn_url: 251 | { http: 'ashbu.cartocdn.com', 252 | https: 'cartocdn-ashbu.global.ssl.fastly.net' }, 253 | last_updated: '2016-01-20T20:19:13.152Z' } 254 | ``` 255 | 256 | #### `Named.update()` - Update a Named Map template 257 | 258 | ``` 259 | namedMaps.update({ 260 | template: template 261 | }) 262 | .on('done', function(res) { 263 | console.log(res) 264 | }) 265 | ``` 266 | Response: 267 | ``` 268 | { template_id: 'world_borders' } 269 | ``` 270 | 271 | #### `Named.delete()` - Delete a named map - pass it an options object with `template_id` 272 | 273 | ``` 274 | namedMaps.delete({ 275 | template_id: 'world_borders' 276 | }) 277 | .on('done', function(template_id) { 278 | console.log(template_id); 279 | }) 280 | ``` 281 | Response is the `template_id` that was just deleted: 282 | ``` 283 | world_borders 284 | ``` 285 | #### `Named.list()` - Get a list of all named maps in your account 286 | ``` 287 | namedMaps.list() 288 | .on('done', function(res) { 289 | console.log(res); 290 | }); 291 | ``` 292 | #### `Named.definition()` - Get the current template for a named map 293 | 294 | ``` 295 | namedMaps.definition({ 296 | template_id: 'world_borders' 297 | }) 298 | .on('done', function(res) { 299 | console.log(res); 300 | }); 301 | ``` 302 | 303 | Command-line access 304 | ------------- 305 | 306 | ### SQL Module: `cartodb` / `cartodb-sql` 307 | 308 | ``` 309 | Options 310 | 311 | -s, --sql string A SQL query (required). 312 | -u, --user string Your CartoDB username. 313 | -a, --api_key string Your CartoDB API Key (only needed for write operations). 314 | -f, --format string Output format json|csv|geojson|shp|svg|kml|SpatiaLite 315 | -o, --output string Output file. If omitted will use stdout. 316 | -c, --config string Config file. Use a JSON file as a way to input these arguments. If no username nor config file is provided, it will look for "config.json" by default. 317 | --sqlFile string A file containing a SQL query (you can then omit -s). 318 | -h, --help 319 | ``` 320 | 321 | #### Examples 322 | 323 | ``` 324 | $ cartodb -u nerik -f svg 'SELECT * FROM europe' > europe.svg 325 | $ cartodb -u nerik -f csv 'SELECT cartodb_id, admin, adm_code FROM europe LIMIT 5' -o europe.csv 326 | $ cartodb -c config.json 'UPDATE ...' #hide your api_key in this file ! 327 | $ cartodb 'UPDATE ...' # "config.json" will be used for credentials by default 328 | 329 | ``` 330 | 331 | ### Import Module: `cartodb-import` 332 | 333 | 334 | ``` 335 | Options 336 | 337 | -f, --file string Path to a local file to import. 338 | -l, --url string URL to import. 339 | -p, --privacy string Privacy of the generated table (public|private) 340 | -u, --user string Your CartoDB username 341 | -a, --api_key string Your CartoDB API Key (only needed for write operations) 342 | -c, --config string Config file. Use a JSON file as a way to input these arguments. 343 | -h, --help 344 | ``` 345 | 346 | #### Examples 347 | 348 | ``` 349 | $ cartodb-import -u nerik --api_key XXX test.csv 350 | $ cartodb-import -c config.json test.csv 351 | $ cartodb-import test.csv # "config.json" will be used for credentials by default 352 | $ cartodb-import --url http://sig.pilote41.fr/urbanisme/aleas_inondation/aleas_sauldre_shp.zip 353 | ``` 354 | -------------------------------------------------------------------------------- /bin/import.bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var CartoDB = require('../'); 4 | var path = require('path'); 5 | var Mustache = require('mustache'); 6 | var openurl = require('openurl'); 7 | 8 | var args = [ 9 | { name: 'file', alias: 'f', type: String, defaultOption: true, description: 'Path to a local file to import.' }, 10 | { name: 'url', alias: 'l', type: String, description: 'URL to import.' }, 11 | { name: 'privacy', alias: 'p', type: String, description: 'Privacy of the generated table (public|private)' }, 12 | { name: 'openMap', type: Boolean, description: 'Open map view in CartoDB' }, 13 | { name: 'openTable', type: Boolean, description: 'Open table/data view in CartoDB' }, 14 | ]; 15 | 16 | var options = require('../lib/util/cli.js').getCommandLineArgs(args); 17 | 18 | if (options.error) { 19 | console.log(options.error); 20 | return; 21 | } 22 | 23 | if (options.help || (!options.file && !options.url)) { 24 | console.log(options.usage); 25 | return; 26 | } 27 | 28 | var importer = new CartoDB.Import({ 29 | user: options.user, 30 | api_key: options.api_key 31 | }); 32 | 33 | var opts = { 34 | privacy: options.privacy || 'public' 35 | } 36 | 37 | var importing; 38 | if (options.file) { 39 | importing = importer.file(options.file, opts); 40 | } else if (options.url) { 41 | importing = importer.url(options.url, opts); 42 | } 43 | 44 | importing.done(function(table_name) { 45 | console.log('Table ' + table_name + ' has been created!'); 46 | if (options.openMap || options.openTable) { 47 | var url = 'https://{{user}}.carto.com/tables/{{table_name}}'; 48 | if (options.openMap) url += '/map'; 49 | openurl.open(Mustache.render(url, { 50 | user: options.user, 51 | table_name: table_name, 52 | })); 53 | } 54 | }); 55 | -------------------------------------------------------------------------------- /bin/sql.bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var CartoDB = require('../'); 4 | var fs = require('fs'); 5 | 6 | var args = [ 7 | { name: 'sql', alias: 's', type: String, defaultOption: true, description: 'A SQL query (required).' }, 8 | { name: 'sqlFile', type: String, description: 'A file containing a SQL query.' }, 9 | { name: 'format', alias: 'f', type: String, description: 'Output format json|csv|geojson|shp|svg|kml|SpatiaLite' }, 10 | { name: 'output', alias: 'o', type: String, description: 'Output file. If omitted will use stdout.' }, 11 | ]; 12 | 13 | var options = require('../lib/util/cli.js').getCommandLineArgs(args); 14 | 15 | if (options.error) { 16 | console.log(options.error); 17 | console.log(options.usage); 18 | return; 19 | } 20 | 21 | if (options.help || (!options.sql && !options.sqlFile)) { 22 | console.log(options.usage); 23 | return; 24 | } 25 | 26 | if (options.sqlFile) { 27 | var sql = fs.readFileSync(options.sqlFile).toString(); 28 | options.sql = sql; 29 | } 30 | 31 | var sql = new CartoDB.SQL({ 32 | user: options.user, 33 | api_key: options.api_key 34 | }); 35 | 36 | if (options.output) { 37 | var file = require('fs').createWriteStream(options.output); 38 | sql.pipe(file); 39 | } 40 | 41 | sql.execute(options.sql, { 42 | format: options.format 43 | }).done(function (data) { 44 | if (!options.output) console.log(data) 45 | }).error(function (error) { 46 | console.log(error) 47 | }); 48 | -------------------------------------------------------------------------------- /examples/config.sample.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | user: 'username', 3 | api_key: 'yourreallyeallyeallyeallyeallylongapikey' //available at https://{yourusername}.carto.com/your_apps 4 | }; -------------------------------------------------------------------------------- /examples/import-file/file.js: -------------------------------------------------------------------------------- 1 | //uses the cartoDB import API to import a file 2 | 3 | var CartoDB = require('../..'); 4 | var cdb_config = require('../config.js'); 5 | 6 | var importer = new CartoDB.Import(cdb_config); 7 | 8 | importer 9 | .file(__dirname + '/' + 'all_week.csv', {}) 10 | .done(function(table_name) { 11 | console.log('Table ' + table_name + ' has been created!'); 12 | }) 13 | 14 | importer 15 | .file(__dirname + '/' + 'invalid.mdb', {}) 16 | .error(function(error) { 17 | console.log(error); 18 | }) -------------------------------------------------------------------------------- /examples/import-file/invalid.mdb: -------------------------------------------------------------------------------- 1 | this is not a valid csv this is not a valid csv this is not a valid csv this is not a valid csv this is not a valid csv this is not a valid csv this is not a valid csv this is not a valid csv this is not a valid csv this is not a valid csv this is not a valid csv -------------------------------------------------------------------------------- /examples/import-url/url.js: -------------------------------------------------------------------------------- 1 | //uses the cartoDB import API to import a file 2 | 3 | var CartoDB = require('../..'); 4 | var cdb_config = require('../config.js'); 5 | 6 | var importer = new CartoDB.Import(cdb_config); 7 | 8 | importer 9 | .url('http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_week.csv', {}) 10 | .done(function(table_name) { 11 | console.log('Table ' + table_name + ' has been created!'); 12 | }) 13 | 14 | // importer 15 | // .url('http://chriswhong.com', {}) 16 | // .error(function(error) { 17 | // console.log(error); 18 | // }) -------------------------------------------------------------------------------- /examples/maps-named/named.js: -------------------------------------------------------------------------------- 1 | var CartoDB = require('../..'); 2 | var cdb_config = require('../config.js'); 3 | var fs = require('fs'); 4 | 5 | var namedMaps = new CartoDB.Maps.Named(cdb_config); 6 | 7 | //create a named map from template.json 8 | 9 | var filePath = __dirname + '/' + 'template.json'; 10 | var template = JSON.parse(fs.readFileSync(filePath, 'utf8')) 11 | 12 | namedMaps.create({ 13 | template: template 14 | }) 15 | .on('done', function(res) { 16 | console.log(res) 17 | }) 18 | .on('_error', function(res) { 19 | console.log(res) 20 | }); 21 | 22 | //instantiate a named map with auth_token and params 23 | 24 | namedMaps.instantiate({ 25 | template_id: 'world_borders', 26 | auth_token: 'auth_token1', 27 | params: { 28 | color: '#ff0000', 29 | cartodb_id: 3 30 | } 31 | }) 32 | .on('done', function(res) { 33 | console.log(res) 34 | }) 35 | .on('_error', function(res) { 36 | console.log(res) 37 | }); 38 | 39 | // //instantiate a named map with auth_token 40 | 41 | namedMaps.instantiate({ 42 | template_id: 'world_borders', 43 | auth_token: 'auth_token1' 44 | }) 45 | .on('done', function(res) { 46 | console.log(res) 47 | }) 48 | .on('_error', function(res) { 49 | console.log(res) 50 | });; 51 | 52 | 53 | //update a named map 54 | 55 | var filePath = __dirname + '/' + 'updateTemplate.json'; 56 | var template = JSON.parse(fs.readFileSync(filePath, 'utf8')) 57 | 58 | namedMaps.update({ 59 | template: template 60 | }) 61 | .on('done', function(res) { 62 | console.log(res) 63 | }) 64 | .on('_error', function(res) { 65 | console.log(res) 66 | }); 67 | 68 | // delete a named map 69 | 70 | namedMaps.delete({ 71 | template_id: 'world_borders' 72 | }) 73 | .on('done', function(res) { 74 | console.log(res); 75 | }) 76 | .on('_error', function(error) { 77 | console.log(error); 78 | }); 79 | 80 | //get a list of all named maps in your account 81 | 82 | namedMaps.list() 83 | .on('done', function(res) { 84 | console.log(res); 85 | }); 86 | 87 | //get the definition of a named map 88 | namedMaps.definition({ 89 | template_id: 'world_borders' 90 | }) 91 | .on('done', function(res) { 92 | console.log(res); 93 | }); 94 | 95 | -------------------------------------------------------------------------------- /examples/maps-named/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.1", 3 | "name": "world_borders", 4 | "auth": { 5 | "method": "token", 6 | "valid_tokens": [ 7 | "auth_token1", 8 | "auth_token2" 9 | ] 10 | }, 11 | "placeholders": { 12 | "color": { 13 | "type": "css_color", 14 | "default": "red" 15 | }, 16 | "cartodb_id": { 17 | "type": "number", 18 | "default": 1 19 | } 20 | }, 21 | "layergroup": { 22 | "version": "1.0.1", 23 | "layers": [ 24 | { 25 | "type": "cartodb", 26 | "options": { 27 | "cartocss_version": "2.1.1", 28 | "cartocss": "/** category visualization */ #world_borders { polygon-opacity: 1; line-color: #FFF; line-width: 0; line-opacity: 1; } #world_borders[cat=0] { polygon-fill: #A6CEE3; } #world_borders[cat=1] { polygon-fill: #1F78B4; } #world_borders[cat=2] { polygon-fill: #2167AB; } #world_borders[cat=3] { polygon-fill: #5077b5; }", 29 | "sql": "SELECT *, mod(cartodb_id,4) as cat FROM world_borders" 30 | } 31 | } 32 | ] 33 | }, 34 | "view": { 35 | "zoom": 4, 36 | "center": { 37 | "lng": 0, 38 | "lat": 0 39 | }, 40 | "bounds": { 41 | "west": -45, 42 | "south": -45, 43 | "east": 45, 44 | "north": 45 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /examples/maps-named/updateTemplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.1", 3 | "name": "world_borders", 4 | "auth": { 5 | "method": "token", 6 | "valid_tokens": [ 7 | "auth_token1", 8 | "auth_token2" 9 | ] 10 | }, 11 | "placeholders": { 12 | "color": { 13 | "type": "css_color", 14 | "default": "red" 15 | }, 16 | "cartodb_id": { 17 | "type": "number", 18 | "default": 1 19 | } 20 | }, 21 | "layergroup": { 22 | "version": "1.0.1", 23 | "layers": [ 24 | { 25 | "type": "cartodb", 26 | "options": { 27 | "cartocss_version": "2.1.1", 28 | "cartocss": "/** category visualization */ #world_borders { polygon-opacity: 1; line-color: #FFF; line-width: 0; line-opacity: 1; } #world_borders[cat=0] { polygon-fill: #A6CEE3; } #world_borders[cat=1] { polygon-fill: #1F78B4; } #world_borders[cat=2] { polygon-fill: #2167AB; } #world_borders[cat=3] { polygon-fill: #5077b5; }", 29 | "sql": "SELECT *, mod(cartodb_id,4) as cat, 1 as test FROM world_borders" 30 | } 31 | } 32 | ] 33 | }, 34 | "view": { 35 | "zoom": 4, 36 | "center": { 37 | "lng": 0, 38 | "lat": 0 39 | }, 40 | "bounds": { 41 | "west": -45, 42 | "south": -45, 43 | "east": 45, 44 | "north": 45 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /examples/sql-json/json.js: -------------------------------------------------------------------------------- 1 | var CartoDB = require('../..'); 2 | var cdb_config = require('../config.js'); 3 | 4 | var sql = new CartoDB.SQL(cdb_config); 5 | 6 | // 1 - query only 7 | 8 | // 2 - query,vars 9 | // 2 - query,options 10 | // 2 - query,callback 11 | 12 | // 3 - query,vars,options 13 | // 3 - query,vars,callback 14 | // 3 - query,options,callback 15 | 16 | // 4 - query, vars, options, callback 17 | 18 | // //query only 19 | // sql.execute("SELECT * from all_month LIMIT 5") 20 | // .done(function(data) { 21 | // console.log(data); 22 | // }); 23 | 24 | // //query, vars 25 | // sql.execute("SELECT * from {{table}} LIMIT 5", {table: 'all_month'}) 26 | // .done(function(data) { 27 | // console.log(data); 28 | // }); 29 | 30 | // //query, options 31 | // sql.execute("SELECT * from all_month LIMIT 5", {format:'GeoJSON'}) 32 | // .done(function(data) { 33 | // console.log(data); 34 | // }); 35 | 36 | // //query, callback 37 | // sql.execute("SELECT * from all_month LIMIT 5", function(data) { 38 | // console.log(data); 39 | // }) 40 | 41 | // //query, vars, options 42 | // sql.execute("SELECT * from {{table}} LIMIT 5", {table: 'all_month'},{format:'GeoJSON'}) 43 | // .done(function(data) { 44 | // console.log(data); 45 | // }); 46 | 47 | 48 | // //query, vars, callback 49 | // sql.execute("SELECT * from {{table}} LIMIT 5", {table: 'all_month'},function(data) { 50 | // console.log(data); 51 | // }) 52 | 53 | // //query, options, callback 54 | // sql.execute("SELECT * from all_month LIMIT 5", {format:'GeoJSON'},function(data) { 55 | // console.log(data); 56 | // }) 57 | 58 | // query, vars, options, callback 59 | sql.execute("SELECT * from {{table}} LIMIT 5", {table: 'all_month'}, {format:'GeoJSON'},function(data) { 60 | console.log(data); 61 | }) 62 | 63 | 64 | 65 | 66 | // //INSERT 67 | // sql.execute("INSERT INTO all_month (depth) VALUES (27)") 68 | // .done(function(data) { 69 | // console.log(data); 70 | // }) 71 | // .error(function(error){ 72 | // console.log(error); 73 | // }); 74 | 75 | // //should throw an error 76 | // sql.execute("THIS IS NOT VALID SQL") 77 | // .error(function(error) { 78 | // console.log(error); 79 | // }); 80 | -------------------------------------------------------------------------------- /examples/sql-pipe/output.json: -------------------------------------------------------------------------------- 1 | {"rows":[{"cartodb_id":7764,"the_geom":"0101000020E61000002FBB270F0BB962C0780B24287E144F40","the_geom_webmercator":"0101000020110F00001BC95F5877CD6FC174B320AA56F86041","time":"2015-09-11T13:59:30Z","latitude":62.1601,"longitude":-149.7826,"depth":7.3,"mag":2.3,"magtype":"ml","nst":null,"gap":null,"dmin":null,"rms":0.62,"net":"ak","id":"ak11698047","updated":"2015-09-19T09:20:15Z","place":"3km E of Y, Alaska","type":"earthquake"},{"cartodb_id":918,"the_geom":"0101000020E61000007958A835CD2D63C0BB74931804AE4D40","the_geom_webmercator":"0101000020110F000023060163E54970C1B3904B6CF0805F41","time":"2015-10-05T05:56:50Z","latitude":59.3595,"longitude":-153.4313,"depth":0.1,"mag":0.7,"magtype":"ml","nst":null,"gap":null,"dmin":null,"rms":0.3,"net":"ak","id":"ak11731017","updated":"2015-10-05T06:49:36Z","place":"94km ESE of Old Iliamna, Alaska","type":"earthquake"},{"cartodb_id":1696,"the_geom":"0101000020E6100000984C158C4ABC62C00F4FAF9465C04E40","the_geom_webmercator":"0101000020110F0000DB8B14B6FBD26FC156081E96AFAC6041","time":"2015-10-01T19:11:29Z","latitude":61.5031,"longitude":-149.8841,"depth":36.7,"mag":1.8,"magtype":"ml","nst":null,"gap":null,"dmin":null,"rms":0.47,"net":"ak","id":"ak11728140","updated":"2015-10-08T09:08:02Z","place":"4km ESE of Big Lake, Alaska","type":"earthquake"},{"cartodb_id":1840,"the_geom":"0101000020E6100000DAACFA5C6D6762C00D4FAF9465B84E40","the_geom_webmercator":"0101000020110F0000B246D04CD5426FC12C509E9A92A56041","time":"2015-10-01T05:11:55Z","latitude":61.4406,"longitude":-147.2321,"depth":33.6,"mag":1.1,"magtype":"ml","nst":null,"gap":null,"dmin":null,"rms":0.14,"net":"ak","id":"ak11733951","updated":"2015-10-08T20:58:46Z","place":"58km NW of Valdez, Alaska","type":"earthquake"},{"cartodb_id":2118,"the_geom":"0101000020E6100000D7F0F44A596C62C0C1A8A44E403F5040","the_geom_webmercator":"0101000020110F000092C8D16A314B6FC11846504B15526241","time":"2015-09-30T01:37:16Z","latitude":64.9883,"longitude":-147.3859,"depth":0,"mag":1.3,"magtype":"ml","nst":null,"gap":null,"dmin":null,"rms":0.19,"net":"ak","id":"ak11727041","updated":"2015-10-05T22:32:30Z","place":"22km NE of Fairbanks, Alaska","type":"earthquake"}],"time":0.001,"fields":{"cartodb_id":{"type":"number"},"the_geom":{"type":"geometry"},"the_geom_webmercator":{"type":"geometry"},"time":{"type":"date"},"latitude":{"type":"number"},"longitude":{"type":"number"},"depth":{"type":"number"},"mag":{"type":"number"},"magtype":{"type":"string"},"nst":{"type":"number"},"gap":{"type":"number"},"dmin":{"type":"number"},"rms":{"type":"number"},"net":{"type":"string"},"id":{"type":"string"},"updated":{"type":"date"},"place":{"type":"string"},"type":{"type":"string"}},"total_rows":5} -------------------------------------------------------------------------------- /examples/sql-pipe/pipe.js: -------------------------------------------------------------------------------- 1 | var CartoDB = require('../..'); 2 | var cdb_config = require('../config.js'); 3 | 4 | var file = require('fs').createWriteStream(__dirname + '/output.json'); 5 | 6 | var sql = new CartoDB.SQL(cdb_config); 7 | 8 | sql.execute("SELECT * from {{table}} LIMIT 5", {table: 'all_month'}) 9 | 10 | 11 | sql.pipe(file); 12 | 13 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/cartodb'); 2 | -------------------------------------------------------------------------------- /lib/cartodb.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var SQL = require('./sql') 4 | , Import = require('./import') 5 | , Maps = require('./maps') 6 | // , _Promise = require('./promise') 7 | ; 8 | 9 | module.exports = { 10 | SQL: SQL, 11 | Import: Import, 12 | Maps: Maps 13 | // _Promise: _Promise 14 | }; 15 | 16 | -------------------------------------------------------------------------------- /lib/import.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Stream = require('stream') 4 | , util = require('util') 5 | , request = require('request') 6 | , debug = require('debug')('cartodb:import') 7 | , resources = require('./resources.json') 8 | , Mustache = require('mustache') 9 | , fs = require('fs') 10 | , _Promise = require('./promise') 11 | , requestUtil = require('./util/request') 12 | ; 13 | 14 | 15 | /* 16 | * Constructor 17 | * 18 | * @config {Object} Connection params 19 | * 20 | * @api public 21 | */ 22 | 23 | function Import(config) { 24 | 25 | for(var i in config){ 26 | this[i] = config[i]; 27 | } 28 | 29 | this.import_api_url = this.import_api_url || Mustache.render(resources.import_api_url,config); 30 | 31 | return this; 32 | } 33 | 34 | /* 35 | * Inheritance 36 | */ 37 | 38 | util.inherits(Import, Stream); 39 | 40 | Import.prototype.file = function(filePath, options) { 41 | 42 | var self = this; 43 | 44 | var promise = new _Promise(); 45 | if(!filePath) { 46 | throw new TypeError('filePath should not be null'); 47 | } 48 | 49 | if (!options) options = {}; 50 | options.filePath = filePath; 51 | options.promise = promise; 52 | this._post('file',options); 53 | 54 | return promise; 55 | }; 56 | 57 | 58 | Import.prototype.url = function(url, options) { 59 | 60 | var self = this; 61 | 62 | var promise = new _Promise(); 63 | if(!url) { 64 | throw new TypeError('url should not be null'); 65 | } 66 | 67 | if (!options) options = {}; 68 | options.url = url; 69 | options.promise = promise; 70 | this._post('url',options); 71 | 72 | return promise; 73 | }; 74 | 75 | 76 | Import.prototype._post = function(type, options) { 77 | 78 | var self = this, 79 | req, 80 | reqOptions, 81 | defaultReqOptions = { 82 | url: self.import_api_url, 83 | json: true, 84 | qs: { 85 | api_key: self.api_key, 86 | privacy: options.privacy 87 | } 88 | }; 89 | 90 | reqOptions = options.reqOptions || defaultReqOptions; 91 | 92 | debug(reqOptions) 93 | debug('POSTing your file to %s', self.import_api_url); 94 | 95 | req = reqOptions.method ? request(reqOptions, processResponse) 96 | : request.post(reqOptions, processResponse); 97 | 98 | if(type === 'file') { 99 | req.form().append('file', fs.createReadStream(options.filePath)); 100 | } 101 | 102 | if(type === 'url') { 103 | req.form().append('url', options.url); 104 | } 105 | 106 | function processResponse(err, res, body) { 107 | if (err) { 108 | debug('processResponse error defined') 109 | debug(err, true) 110 | options.promise.emit('_error', err); 111 | } else { 112 | body = requestUtil.fixBody(body); 113 | 114 | debug(body); 115 | debug('Success, the item_queue_id is %s', body.item_queue_id); 116 | if(body.item_queue_id) { 117 | poll(body.item_queue_id); 118 | } 119 | else 120 | options.promise.emit('_error', 'body.item_queue_id undefined'); 121 | } 122 | } 123 | 124 | 125 | function poll(queue_id) { 126 | debug('Polling to see if this file is finished importing...') 127 | setTimeout(function() { 128 | var pollUrl = self.import_api_url + '/' + queue_id; 129 | 130 | var getOptions = { 131 | url: pollUrl, 132 | json: true, 133 | qs: { 134 | api_key: self.api_key 135 | } 136 | }; 137 | 138 | debug(getOptions); 139 | 140 | request.get(getOptions, processGetResponsefunction); 141 | 142 | 143 | function processGetResponsefunction(err, res, body) { 144 | 145 | body = requestUtil.fixBody(body); 146 | 147 | debug('statusCode: %s', res.statusCode) 148 | 149 | debug('Processing Import Status Response') 150 | debug(body); 151 | 152 | try { 153 | 154 | if( body.state == 'complete' ) { 155 | debug('It\'s finished! The table name is %s', body.table_name); 156 | options.promise.emit('done', body.table_name) 157 | 158 | } else if( body.state == 'failure') { 159 | debug('import failure') 160 | if(!body.get_error_text && !body.error_code) 161 | debug('body.get_error_text and body.error_code is undefined'); 162 | 163 | var msg = body.get_error_text? JSON.stringify(body.get_error_text) : body.error_code.toString(); 164 | options.promise.emit('_error', new Error(msg)); 165 | 166 | } else { 167 | debug('Not finished, trying again...'); 168 | poll(queue_id); //recurse 169 | } 170 | } 171 | catch (err) { 172 | debug('processGetResponse: try catch error') 173 | debug(err) 174 | options.promise.emit('_error', err); 175 | } 176 | }; 177 | 178 | },3000); 179 | } 180 | } 181 | 182 | 183 | /* 184 | * Exports the constructor 185 | */ 186 | 187 | 188 | module.exports = Import; 189 | -------------------------------------------------------------------------------- /lib/maps.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Stream = require('stream') 4 | , util = require('util') 5 | , request = require('request') 6 | , debug = require('debug')('cartodb:map') 7 | , resources = require('./resources.json') 8 | , Mustache = require('mustache') 9 | , _Promise = require('./promise') 10 | , fs = require('fs') 11 | , requestUtil = require('./util/request') 12 | ; 13 | 14 | /* 15 | * Inheritance 16 | */ 17 | 18 | util.inherits(Named, Stream); 19 | 20 | 21 | function Named(config) { 22 | 23 | for(var i in config){ 24 | this[i] = config[i]; 25 | } 26 | 27 | this.maps_api_url = this.maps_api_url || Mustache.render(resources.maps_api_url, config); 28 | 29 | return this; 30 | } 31 | 32 | Named.prototype.create = function(options) { 33 | debug('POSTing your template to %s', this.maps_api_url); 34 | 35 | var self = this; 36 | var promise = new _Promise(); 37 | 38 | var requestOptions = { 39 | url: this.maps_api_url, 40 | qs: { 41 | api_key: this.api_key 42 | }, 43 | body: options.template 44 | } 45 | 46 | this._request('post', requestOptions, promise) 47 | 48 | return promise; 49 | } 50 | 51 | Named.prototype.instantiate = function(options) { 52 | debug('Instantiating named map: %s', options.template_id); 53 | 54 | var self = this; 55 | var promise = new _Promise(); 56 | 57 | var url = this.maps_api_url + '/' + options.template_id; 58 | debug(url); 59 | 60 | var formData = (options.params) ? options.params : JSON.parse('{}'); 61 | 62 | var requestOptions = { 63 | url: url, 64 | qs: { 65 | auth_token: options.auth_token 66 | }, 67 | body: formData 68 | } 69 | 70 | this._request('post', requestOptions, promise) 71 | 72 | return promise; 73 | 74 | } 75 | 76 | Named.prototype.update = function(options) { 77 | debug('Updating Named Map %s', this.maps_api_url); 78 | 79 | var self = this; 80 | var promise = new _Promise(); 81 | 82 | var requestOptions = { 83 | url: this.maps_api_url + '/' + options.template.name, 84 | json: true, 85 | qs: { 86 | api_key: this.api_key 87 | }, 88 | body: options.template 89 | } 90 | 91 | this._request('put', requestOptions, promise) 92 | 93 | return promise; 94 | } 95 | 96 | 97 | Named.prototype.delete = function(options) { 98 | debug('Attempting to delete named map: %s', options.template_id); 99 | 100 | var self = this; 101 | var promise = new _Promise(); 102 | 103 | var requestOptions = { 104 | url: this.maps_api_url + '/' + options.template_id, 105 | qs: { 106 | api_key: this.api_key 107 | } 108 | }; 109 | 110 | this._request('del', requestOptions, promise) 111 | 112 | return promise; 113 | } 114 | 115 | 116 | 117 | Named.prototype.list = function(options) { 118 | 119 | var promise = new _Promise(); 120 | var self = this; 121 | 122 | var requestOptions = { 123 | url: this.maps_api_url, 124 | qs: { 125 | api_key: this.api_key 126 | } 127 | } 128 | 129 | this._request('get',requestOptions, promise); 130 | 131 | return promise; 132 | } 133 | 134 | Named.prototype.definition = function(options) { 135 | 136 | var promise = new _Promise(); 137 | var self = this; 138 | 139 | var requestOptions = { 140 | url: this.maps_api_url + '/' + options.template_id, 141 | qs: { 142 | api_key: this.api_key 143 | } 144 | }; 145 | 146 | this._request('get', requestOptions, promise); 147 | 148 | return promise; 149 | } 150 | 151 | Named.prototype._request = function( method, options, promise ) { 152 | var self = this; 153 | 154 | options.json = true 155 | 156 | request[method](options, function (err, res, body) { 157 | self._processResponse(err, res, body, method, promise); 158 | }); 159 | } 160 | 161 | 162 | Named.prototype._processResponse = function(err, res, body, method, promise) { 163 | debug('Processing response', res.statusCode); 164 | body = requestUtil.fixBody(body); 165 | 166 | if(method != 'del') { 167 | if(!err && res.statusCode === 200) { 168 | debug('Success!'); 169 | promise.emit('done', body); 170 | } else { 171 | debug('Error, %s', body.errors); 172 | promise.emit('_error', body.errors); 173 | } 174 | } else { //handle delete differently 175 | if(!err && res.statusCode === 204) { 176 | debug('Success!'); 177 | promise.emit('done', {success: true}); 178 | } else { 179 | debug('Error, %s', body.errors); 180 | promise.emit('_error', body.errors); 181 | } 182 | } 183 | } 184 | 185 | 186 | module.exports = { 187 | Named: Named 188 | }; 189 | -------------------------------------------------------------------------------- /lib/promise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Stream = require('stream') 4 | , util = require('util') 5 | 6 | 7 | function _Promise() {} 8 | 9 | util.inherits(_Promise, Stream); 10 | 11 | //_Promise.prototype = Events; 12 | 13 | _Promise.prototype.done = function(fn) { 14 | return this.on('done', fn); 15 | } 16 | 17 | //if the event is actually named 'error', will lead to Uncaught error if .error() is not called 18 | //so name it _error and it doesn't matter if .error() is ever called or not 19 | _Promise.prototype.error = function(fn) { 20 | return this.on('_error', fn); 21 | } 22 | 23 | module.exports = _Promise; 24 | -------------------------------------------------------------------------------- /lib/resources.json: -------------------------------------------------------------------------------- 1 | { 2 | "sql_api_url" : "https://{{user}}.carto.com/api/v2/sql", 3 | "import_api_url" : "https://{{user}}.carto.com/api/v1/imports", 4 | "maps_api_url" : "https://{{user}}.carto.com/api/v1/map/named" 5 | } 6 | -------------------------------------------------------------------------------- /lib/sql.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Stream = require('stream') 4 | , util = require('util') 5 | , request = require('request') 6 | , debug = require('debug')('cartodb:sql') 7 | , resources = require('./resources.json') 8 | , Mustache = require('mustache') 9 | , _Promise = require('./promise') 10 | , requestUtil = require('./util/request') 11 | ; 12 | 13 | 14 | /* 15 | * Constructor 16 | * 17 | * @config {Object} Connection params 18 | * 19 | * @api public 20 | */ 21 | 22 | function SQL(config) { 23 | 24 | for(var i in config){ 25 | this[i] = config[i]; 26 | } 27 | 28 | this.sql_api_url = this.sql_api_url || Mustache.render(resources.sql_api_url, config); 29 | 30 | return this; 31 | } 32 | 33 | /* 34 | * Inheritance 35 | */ 36 | 37 | util.inherits(SQL, Stream); 38 | 39 | 40 | //sql.execute(sql [,vars][, options][, callback])Permalink 41 | SQL.prototype.execute = function(sql, vars, options, cb) { 42 | 43 | var self = this; 44 | 45 | var MAX_LENGTH_GET_QUERY = 1024; 46 | 47 | var promise = new _Promise(); 48 | 49 | if(!sql) { 50 | throw new Error('sql should not be null'); 51 | } 52 | 53 | if(!sql.match) { 54 | debug(sql) 55 | throw new Error('sql match does not exist'); 56 | } 57 | 58 | //check if last argument is a function 59 | var args = arguments, 60 | fn = args[args.length -1]; 61 | if(typeof(fn) == 'function') cb = fn; 62 | 63 | //if no brackets in sql, 2nd argument should be options 64 | if(sql.match(/({{|}})/) === null) { 65 | options = vars; 66 | } 67 | 68 | var query = Mustache.render(sql, vars); 69 | 70 | // check method: if we are going to send by get or by post 71 | var isGetRequest = query.length < MAX_LENGTH_GET_QUERY; 72 | 73 | debug('About to perform a query to %s', self.sql_api_url); 74 | 75 | var params = { 76 | q: query, 77 | api_key: this.api_key 78 | } 79 | 80 | if(options) { 81 | params = util._extend(params, options); 82 | } 83 | 84 | if (!params.format) { 85 | params.format = 'json'; 86 | } 87 | params.format = params.format.toLowerCase(); 88 | 89 | //TODO: why use POST if it's a write query? 90 | if (isGetRequest && !isWriteQuery(query)) { 91 | request.get({ 92 | url: this.sql_api_url, 93 | qs: params 94 | }, function(err, res, body) { 95 | processResponse(err, res, body, params.format); 96 | }); 97 | } else { 98 | request.post({url: this.sql_api_url, form: params 99 | }, function(err, res, body) { 100 | processResponse(err, res, body, params.format); 101 | }); 102 | } 103 | 104 | function processResponse(err, res, body, format) { 105 | body = requestUtil.fixBody(body); 106 | 107 | if(!err && res.statusCode === 200) { 108 | debug('Successful response from the server'); 109 | self.emit('data', body); 110 | 111 | promise.emit('done', body); 112 | if(cb) cb(body); 113 | } else { 114 | var error; 115 | if (err) { 116 | error = err.code; 117 | } else if (body) { 118 | error = (body.error) ? body.error : JSON.stringify(body); 119 | debug('There was an error with the request %s', error); 120 | } 121 | promise.emit('_error', new Error(error)); 122 | } 123 | } 124 | 125 | return promise; 126 | }; 127 | 128 | /* 129 | * Utilities 130 | */ 131 | 132 | 133 | /* 134 | * check if the user wants to write to the db 135 | */ 136 | 137 | var isWriteQuery = SQL.prototype.isWriteQuery = function(sql) { 138 | return /insert|delete|update|alter/gi.test(sql); 139 | }; 140 | 141 | module.exports = SQL; 142 | -------------------------------------------------------------------------------- /lib/util/cli.js: -------------------------------------------------------------------------------- 1 | var commonArgs = [ 2 | { name: 'user', alias: 'u', type: String, description: 'Your CartoDB username' }, 3 | { name: 'api_key', alias: 'a', type: String, description: 'Your CartoDB API Key (only needed for write operations)' }, 4 | { name: 'config', alias: 'c', type: String, description: 'Config file. Use a JSON file as a way to input these arguments. If no username nor config file is provided, it will look for "config.json" by default' }, 5 | { name: 'help', alias: 'h' } 6 | ]; 7 | var commandLineArgs = require('command-line-args'); 8 | 9 | var readConfigFile = function(path, options) { 10 | var config = require('fs').readFileSync(path, {encoding: 'utf-8'}); 11 | var configJSON = JSON.parse(config); 12 | options = require('util')._extend(options, configJSON); 13 | } 14 | 15 | module.exports = { 16 | getCommandLineArgs: function (customArgs) { 17 | var args = customArgs.concat(commonArgs); 18 | var cli = commandLineArgs(args); 19 | var options = cli.parse(); 20 | 21 | if (options.config) { 22 | try { 23 | readConfigFile(options.config, options); 24 | } catch (e) { 25 | options.error = e.message; 26 | } 27 | } else if (!options.user) { 28 | try { 29 | readConfigFile('config.json', options); 30 | } catch (e) { 31 | options.error = 'No user name nor config file was provided. Defaulting to "config.json" also failed.'; 32 | } 33 | } 34 | 35 | options.usage = cli.getUsage(args); 36 | 37 | return options; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/util/request.js: -------------------------------------------------------------------------------- 1 | function fixBody(body){ 2 | if(typeof(body) === 'string') 3 | body = JSON.parse(body); 4 | return body; 5 | } 6 | 7 | module.exports = { 8 | fixBody: fixBody 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cartodb", 3 | "description": "CartoDB Node.js library", 4 | "private": false, 5 | "version": "0.5.1", 6 | "main": "index", 7 | "url": "https://github.com/CartoDB/cartodb-nodejs", 8 | "license": "BSD-3-Clause", 9 | "repository": [ 10 | { 11 | "type": "git", 12 | "url": "git://github.com/CartoDB/cartodb-nodejs.git" 13 | } 14 | ], 15 | "author": { 16 | "name": "Chris Whong, CartoDB", 17 | "url": "https://github.com/CartoDB/cartodb-nodejs", 18 | "email": "cwhong@cartodb.com" 19 | }, 20 | "contributors": [ 21 | "Dan Zajdband ", 22 | "Javi Santana ", 23 | "Chris Whong ", 24 | "Erik Escoffier " 25 | ], 26 | "dependencies": { 27 | "command-line-args": "2.X", 28 | "debug": "2.X", 29 | "mustache": "2.X", 30 | "openurl": "1.X", 31 | "request": "2.X" 32 | }, 33 | "devDependencies": { 34 | "eslint": "^2.2.0", 35 | "eslint-config-airbnb": "^6.0.2", 36 | "mocha": "^1.8.2" 37 | }, 38 | "scripts": { 39 | "test": "npm run test:unit && npm run test:functional", 40 | "test:unit": "mocha test/unit", 41 | "test:functional": "mocha -t 10000 test/functional", 42 | "lint": "eslint -c .eslintrc lib" 43 | }, 44 | "bin": { 45 | "cartodb": "bin/sql.bin.js", 46 | "cartodb-sql": "bin/sql.bin.js", 47 | "carto-sql": "bin/sql.bin.js", 48 | "cartodb-import": "bin/import.bin.js", 49 | "carto-import": "bin/import.bin.js" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/functional/sql.func.test.js: -------------------------------------------------------------------------------- 1 | var CartoDB = require('../../'); 2 | var assert = require('assert'); 3 | 4 | var credentials = require('../secret.js'); 5 | 6 | beforeEach(function(){ 7 | this.SQL = new CartoDB.SQL(credentials); 8 | this.Import = new CartoDB.Import(credentials); 9 | }); 10 | 11 | describe('SQL', function() { 12 | describe('execute', function(){ 13 | it('should return some results', function(done) { 14 | var sql = "select * from {{table}} limit 1"; 15 | this.SQL.execute(sql, {table: credentials.EXISTING_TABLE}).done(function(data) { 16 | assert.strictEqual(data.rows.length, 1); 17 | done(); 18 | }).error(function(e){ 19 | throw new Error(e); 20 | }); 21 | }); 22 | }); 23 | }); 24 | 25 | 26 | describe('Import', function() { 27 | this.timeout(300000); 28 | describe('file', function(){ 29 | it('should create a new table', function(done) { 30 | this.Import.file(__dirname + '/../../examples/import-file/all_week.csv',{}).done(function(table_name) { 31 | assert.notEqual(typeof table_name, undefined); 32 | done(); 33 | }).error(function(e){ 34 | throw new Error(e); 35 | }); 36 | }) 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/secret.js.example: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | EXISTING_TABLE: '', 4 | user: '', 5 | password: '', 6 | CONSUMER_KEY: '', 7 | CONSUMER_SECRET: '', 8 | api_key: '' 9 | }; 10 | -------------------------------------------------------------------------------- /test/unit/import.unit.test.js: -------------------------------------------------------------------------------- 1 | var CartoDB = require('../../'); 2 | var assert = require('assert'); 3 | 4 | var dummyCredentials = {user: 'someuser', api_key: 'somelongstring'}; 5 | 6 | beforeEach(function(){ 7 | this.dummyImport = new CartoDB.Import(dummyCredentials); 8 | }); 9 | 10 | describe('Import', function() { 11 | 12 | describe('constructor', function() { 13 | context('when not providing import_api_url', function() { 14 | it('should have a correct import_api_url defined', function () { 15 | assert.strictEqual(this.dummyImport.import_api_url, 'https://someuser.carto.com/api/v1/imports'); 16 | }); 17 | 18 | }); 19 | context('when providing import_api_url', function() { 20 | it('should have a correct import_api_url defined', function () { 21 | var options = dummyCredentials; 22 | options.import_api_url = 'https://someuser.carto.com/api/v1/imports'; 23 | var cdbImport = new CartoDB.Import(options); 24 | //assert.strictEqual(cdbImport.import_api_url, 'https://someuser.cartodb.com/api/v1/imports'); 25 | }); 26 | }); 27 | 28 | }); 29 | 30 | describe('file', function() { 31 | it('should throw an error when user does not provide filePath', function () { 32 | assert.throws(function() { 33 | this.dummyImport.file(null); 34 | }); 35 | }); 36 | }); 37 | 38 | describe('url', function() { 39 | it('should throw an error when user does not provide url', function () { 40 | assert.throws(function() { 41 | this.dummyImport.file(null); 42 | }); 43 | }); 44 | }); 45 | 46 | describe('stream', function() { 47 | it('should throw an error when user does not provide a stream', function () { 48 | assert.throws(function() { 49 | this.dummyImport.stream(null); 50 | }); 51 | }); 52 | }); 53 | 54 | 55 | }) 56 | -------------------------------------------------------------------------------- /test/unit/maps.unit.test.js: -------------------------------------------------------------------------------- 1 | var CartoDB = require('../../'); 2 | var assert = require('assert'); 3 | 4 | var dummyCredentials = {user: 'someuser', api_key: 'somelongstring'}; 5 | 6 | beforeEach(function(){ 7 | this.namedMaps = new CartoDB.Maps.Named(dummyCredentials); 8 | }); 9 | 10 | describe('Maps', function() { 11 | 12 | describe('Named', function() { 13 | 14 | describe('constructor', function() { 15 | context('when not providing maps_api_url', function() { 16 | it('should have a correct maps_api_url defined', function () { 17 | assert.strictEqual(this.namedMaps.maps_api_url, 'https://someuser.carto.com/api/v1/map/named'); 18 | }); 19 | 20 | }); 21 | context('when providing maps_api_url', function() { 22 | it('should have a correct maps_api_url defined', function () { 23 | var options = dummyCredentials; 24 | options.maps_api_url = 'https://someuser.carto.com/api/v1/map/named'; 25 | var namedMaps = new CartoDB.Maps.Named(options); 26 | assert.strictEqual(namedMaps.maps_api_url, 'https://someuser.carto.com/api/v1/map/named'); 27 | }); 28 | }); 29 | 30 | }); 31 | 32 | // describe('isWriteQuery', function() { 33 | // it('should detect an INSERT as a write query', function () { 34 | // assert.strictEqual(this.dummySQL.isWriteQuery('InSerT into table (bla) values (\'meh\')'), true); 35 | // }); 36 | // it('should detect an UPDATE as a write query', function () { 37 | // assert.strictEqual(this.dummySQL.isWriteQuery('UpDaTe table set bla=1'), true); 38 | // }); 39 | // it('should detect an DELETE as a write query', function () { 40 | // assert.strictEqual(this.dummySQL.isWriteQuery('DeLeTe from table'), true); 41 | // }); 42 | // it('should detect a SELECT as a read query', function () { 43 | // assert.strictEqual(this.dummySQL.isWriteQuery('select * from table'), false); 44 | // }); 45 | 46 | // }); 47 | 48 | // describe('execute', function() { 49 | // it('should throw an error when user does not provide query', function () { 50 | // assert.throws(function() { 51 | // this.dummySQL.execute(''); 52 | // }); 53 | // }); 54 | // }); 55 | 56 | }) 57 | }); 58 | -------------------------------------------------------------------------------- /test/unit/sql.unit.test.js: -------------------------------------------------------------------------------- 1 | var CartoDB = require('../../'); 2 | var assert = require('assert'); 3 | 4 | var dummyCredentials = {user: 'someuser', api_key: 'somelongstring'}; 5 | 6 | beforeEach(function(){ 7 | this.dummySQL = new CartoDB.SQL(dummyCredentials); 8 | }); 9 | 10 | describe('SQL', function() { 11 | 12 | 13 | describe('constructor', function() { 14 | context('when not providing sql_api_url', function() { 15 | it('should have a correct sql_api_url defined', function () { 16 | assert.strictEqual(this.dummySQL.sql_api_url, 'https://someuser.carto.com/api/v2/sql'); 17 | }); 18 | 19 | }); 20 | context('when providing sql_api_url', function() { 21 | it('should have a correct sql_api_url defined', function () { 22 | var options = dummyCredentials; 23 | options.sql_api_url = 'https://someuser.carto.com/api/v2/sql'; 24 | var sql = new CartoDB.SQL(options); 25 | assert.strictEqual(sql.sql_api_url, 'https://someuser.carto.com/api/v2/sql'); 26 | }); 27 | }); 28 | 29 | }); 30 | 31 | describe('isWriteQuery', function() { 32 | it('should detect an INSERT as a write query', function () { 33 | assert.strictEqual(this.dummySQL.isWriteQuery('InSerT into table (bla) values (\'meh\')'), true); 34 | }); 35 | it('should detect an UPDATE as a write query', function () { 36 | assert.strictEqual(this.dummySQL.isWriteQuery('UpDaTe table set bla=1'), true); 37 | }); 38 | it('should detect an DELETE as a write query', function () { 39 | assert.strictEqual(this.dummySQL.isWriteQuery('DeLeTe from table'), true); 40 | }); 41 | it('should detect a SELECT as a read query', function () { 42 | assert.strictEqual(this.dummySQL.isWriteQuery('select * from table'), false); 43 | }); 44 | 45 | }); 46 | 47 | describe('execute', function() { 48 | it('should throw an error when user does not provide query', function () { 49 | assert.throws(function() { 50 | this.dummySQL.execute(''); 51 | }); 52 | }); 53 | }); 54 | 55 | }); 56 | --------------------------------------------------------------------------------