From daa39660b74ac9b926ae1ac2371843728ed42e4d Mon Sep 17 00:00:00 2001 From: Will White Date: Wed, 29 Jul 2015 15:43:53 -0400 Subject: [PATCH 1/8] Add uploads. --- lib/client.js | 4 +- lib/constants.js | 3 + lib/services/uploads.js | 143 ++++++++++++++++ package.json | 2 + test/fixtures/valid-onlytiles.mbtiles | Bin 0 -> 19456 bytes test/uploads.js | 227 ++++++++++++++++++++++++++ 6 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 lib/services/uploads.js create mode 100644 test/fixtures/valid-onlytiles.mbtiles create mode 100644 test/uploads.js diff --git a/lib/client.js b/lib/client.js index 29a7cd8f..d40f5695 100644 --- a/lib/client.js +++ b/lib/client.js @@ -5,6 +5,7 @@ var xtend = require('xtend/mutable'); var MapboxGeocoder = require('./services/geocoder'); var MapboxSurface = require('./services/surface'); var MapboxDirections = require('./services/directions'); +var MapboxUploads = require('./services/uploads'); var MapboxMatching = require('./services/matching'); var MapboxDatasets = require('./services/datasets'); @@ -32,6 +33,7 @@ xtend(MapboxClient.prototype, MapboxSurface.prototype, MapboxDirections.prototype, MapboxMatching.prototype, - MapboxDatasets.prototype); + MapboxDatasets.prototype, + MapboxUploads.prototype); module.exports = MapboxClient; diff --git a/lib/constants.js b/lib/constants.js index 23b6bb9e..313f17ec 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -5,6 +5,9 @@ module.exports.API_GEOCODER_FORWARD = compile('${endpoint}/v4/geocode/${dataset} module.exports.API_GEOCODER_REVERSE = compile('${endpoint}/v4/geocode/${dataset}/${location.longitude},${location.latitude}.json?${query}'); module.exports.API_DIRECTIONS = compile('${endpoint}/v4/directions/${profile}/${encodedWaypoints}.json?${query}'); module.exports.API_SURFACE = compile('${endpoint}/v4/surface/${mapid}.json?${query}'); +module.exports.API_UPLOADS = compile('${endpoint}/uploads/v1/${owner}?${query}'); +module.exports.API_UPLOAD = compile('${endpoint}/uploads/v1/${owner}/${upload}?${query}'); +module.exports.API_UPLOAD_CREDENTIALS = compile('${endpoint}/uploads/v1/${owner}/credentials?${query}'); module.exports.API_MATCHING = compile('${endpoint}/matching/v4/${profile}.json?${query}'); module.exports.API_DATASET_DATASETS = compile('${endpoint}/datasets/v1/${owner}?${query}'); module.exports.API_DATASET_DATASET = compile('${endpoint}/datasets/v1/${owner}/${dataset}?${query}'); diff --git a/lib/services/uploads.js b/lib/services/uploads.js new file mode 100644 index 00000000..084e9ffd --- /dev/null +++ b/lib/services/uploads.js @@ -0,0 +1,143 @@ +'use strict'; + +var invariant = require('invariant'), + request = require('superagent'), + makeService = require('../make_service'), + constants = require('../constants'), + makeURL = require('../make_url'); + +var Uploads = module.exports = makeService('MapboxUploads'); + +/** + * To retrieve a listing of uploads for a particular account. + * This request requires an access token with the uploads:list scope. + * + * @param {Function} callback called with (err, uploads) + * @returns {undefined} nothing, calls callback + * @example + * var mapboxClient = new MapboxClient('ACCESSTOKEN'); + * mapboxClient.listUploads(function(err, uploads) { + * console.log(uploads); + * // [ + * // { + * // "complete": true, + * // "tileset": "example.mbtiles", + * // "error": null, + * // "id": "abc123", + * // "modified": "2014-11-21T19:41:10.000Z", + * // "created": "2014-11-21T19:41:10.000Z", + * // "owner": "example", + * // "progress": 1 + * // }, + * // { + * // "complete": false, + * // "tileset": "example.foo", + * // "error": null, + * // "id": "xyz789", + * // "modified": "2014-11-21T19:41:10.000Z", + * // "created": "2014-11-21T19:41:10.000Z", + * // "owner": "example", + * // "progress": 0 + * // } + * // ] + * }); + */ +Uploads.prototype.listUploads = function(owner, callback) { + // defaults to the owner of the provided token if omitted + if (callback === undefined && typeof owner === 'function') { + callback = owner; + owner = this.user; + } + + invariant(typeof callback === 'function', 'callback must be a function'); + invariant(typeof owner === 'string', 'owner must be a string'); + + var url = makeURL(this, constants.API_UPLOADS, { owner: owner }); + + request(url, function(err, res) { + callback(err, res.body); + }); +}; + +Uploads.prototype.createUploadCredentials = function(owner, callback) { + // defaults to the owner of the provided token if omitted + if (callback === undefined && typeof owner === 'function') { + callback = owner; + owner = this.user; + } + + invariant(typeof callback === 'function', 'callback must be a function'); + invariant(typeof owner === 'string', 'owner must be a string'); + + var url = makeURL(this, constants.API_UPLOAD_CREDENTIALS, { owner: owner }); + + request + .get(url) + .end(function(err, res) { + callback(err, res.body); + }); +}; + +Uploads.prototype.createUpload = function(options, owner, callback) { + // defaults to the owner of the provided token if omitted + if (callback === undefined && typeof owner === 'function') { + callback = owner; + owner = this.user; + } + + invariant(typeof options === 'object', 'options must be an object'); + invariant(typeof owner === 'string', 'owner must be a string'); + invariant(typeof callback === 'function', 'callback must be a function'); + + var url = makeURL(this, constants.API_UPLOADS, { owner: owner }); + + request + .post(url) + .send(options) + .end(function(err, res) { + callback(err, res.body); + }); +}; + +Uploads.prototype.readUpload = function(upload, owner, callback) { + // defaults to the owner of the provided token if omitted + if (callback === undefined && typeof owner === 'function') { + callback = owner; + owner = this.user; + } + + invariant(typeof upload === 'string', 'upload must be a string'); + invariant(typeof callback === 'function', 'callback must be a function'); + + var url = makeURL(this, constants.API_UPLOAD, { + owner: owner, + upload: upload + }); + + request(url, function(err, res) { + callback(err, res.body); + }); +}; + +Uploads.prototype.deleteUpload = function(upload, owner, callback) { + // defaults to the owner of the provided token if omitted + if (callback === undefined && typeof owner === 'function') { + callback = owner; + owner = this.user; + } + + invariant(typeof upload === 'string', 'upload must be a string'); + invariant(typeof owner === 'string', 'owner must be a string'); + invariant(typeof callback === 'function', 'callback must be a function'); + + var url = makeURL(this, constants.API_UPLOAD, { + owner: owner, + upload: upload + }); + + request + .del(url) + .end(function(err) { + callback(err); + }); +}; diff --git a/package.json b/package.json index da7091b4..7aed8b5c 100644 --- a/package.json +++ b/package.json @@ -28,11 +28,13 @@ }, "homepage": "https://github.com/mapbox/mapbox-sdk-js", "devDependencies": { + "aws-sdk": "^2.1.41", "browserify": "^11.0.0", "documentation": "^2.1.0-alpha2", "eslint": "^0.24.1", "geojson-random": "^0.2.2", "geojsonhint": "^1.1.0", + "hat": "0.0.3", "polyline": "^0.1.0", "tap": "^1.3.1", "uglifyjs": "^2.4.10" diff --git a/test/fixtures/valid-onlytiles.mbtiles b/test/fixtures/valid-onlytiles.mbtiles new file mode 100644 index 0000000000000000000000000000000000000000..546148d7a99db16fc918cb70ffdfd46580aeb07b GIT binary patch literal 19456 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 x%k=59fC(xaOs0yy 10) throw new Error('Upload did not complete in time'); + // we are waiting for mapbox to process the upload + if (!upload.complete) return setTimeout(poll, Math.pow(2, attempts++) * 1000); + completedUpload = upload; + assert.end(); + }); + } + poll(); + }); + + readUpload.test('with owner', function(assert) { + var client = new MapboxClient(process.env.MapboxAccessToken); + assert.ok(client, 'created upload client'); + var upload = testUploads.shift(); + client.readUpload(upload.id, upload.owner, function(err, upload) { + assert.ifError(err, 'success'); + assert.end(); + }); + }); + + readUpload.end(); + }); + + uploadClient.test('#listUploads', function(listUploads) { + listUploads.test('typecheck', function(assert) { + var client = new MapboxClient(process.env.MapboxAccessToken); + assert.ok(client, 'created upload client'); + assert.throws(function() { + client.listUploads(100, function() {}); + }, 'throws owner must be a string error'); + assert.throws(function() { + client.listUploads(); + }, 'throws no callback function error'); + assert.end(); + }); + + listUploads.test('without owner', function(assert) { + var client = new MapboxClient(process.env.MapboxAccessToken); + assert.ok(client, 'created upload client'); + client.listUploads(function(err, uploads) { + assert.ifError(err, 'success'); + assert.end(); + }); + }); + + listUploads.end(); + }); + + uploadClient.test('#deleteUpload', function(deleteUpload) { + deleteUpload.test('typecheck', function(assert) { + var client = new MapboxClient(process.env.MapboxAccessToken); + assert.ok(client, 'created upload client'); + assert.throws(function() { + client.deleteUpload(100, function() {}); + }, 'throws owner must be a string error'); + assert.throws(function() { + client.deleteUpload(); + }, 'throws no callback function error'); + assert.end(); + }); + + deleteUpload.test('without owner', function(assert) { + var client = new MapboxClient(process.env.MapboxAccessToken); + assert.ok(client, 'created upload client'); + var upload = testUploads.shift(); + client.deleteUpload(completedUpload.id, function(err, uploads) { + assert.ifError(err, 'success'); + assert.end(); + }); + }); + + deleteUpload.end(); + }); + + uploadClient.end(); +}); From 4788b3b33346bc8fedd4ff274a98237f0d889f9f Mon Sep 17 00:00:00 2001 From: Will White Date: Wed, 29 Jul 2015 23:13:30 -0400 Subject: [PATCH 2/8] Remove owner override. Better way of handling this in: https://github.com/mapbox/mapbox-sdk-js/pull/27 --- lib/services/uploads.js | 54 +++++++---------------------------- test/uploads.js | 62 ++++------------------------------------- 2 files changed, 15 insertions(+), 101 deletions(-) diff --git a/lib/services/uploads.js b/lib/services/uploads.js index 084e9ffd..ad6acce9 100644 --- a/lib/services/uploads.js +++ b/lib/services/uploads.js @@ -42,34 +42,20 @@ var Uploads = module.exports = makeService('MapboxUploads'); * // ] * }); */ -Uploads.prototype.listUploads = function(owner, callback) { - // defaults to the owner of the provided token if omitted - if (callback === undefined && typeof owner === 'function') { - callback = owner; - owner = this.user; - } - +Uploads.prototype.listUploads = function(callback) { invariant(typeof callback === 'function', 'callback must be a function'); - invariant(typeof owner === 'string', 'owner must be a string'); - var url = makeURL(this, constants.API_UPLOADS, { owner: owner }); + var url = makeURL(this, constants.API_UPLOADS, { owner: this.user }); request(url, function(err, res) { callback(err, res.body); }); }; -Uploads.prototype.createUploadCredentials = function(owner, callback) { - // defaults to the owner of the provided token if omitted - if (callback === undefined && typeof owner === 'function') { - callback = owner; - owner = this.user; - } - +Uploads.prototype.createUploadCredentials = function(callback) { invariant(typeof callback === 'function', 'callback must be a function'); - invariant(typeof owner === 'string', 'owner must be a string'); - var url = makeURL(this, constants.API_UPLOAD_CREDENTIALS, { owner: owner }); + var url = makeURL(this, constants.API_UPLOAD_CREDENTIALS, { owner: this.user }); request .get(url) @@ -78,18 +64,11 @@ Uploads.prototype.createUploadCredentials = function(owner, callback) { }); }; -Uploads.prototype.createUpload = function(options, owner, callback) { - // defaults to the owner of the provided token if omitted - if (callback === undefined && typeof owner === 'function') { - callback = owner; - owner = this.user; - } - +Uploads.prototype.createUpload = function(options, callback) { invariant(typeof options === 'object', 'options must be an object'); - invariant(typeof owner === 'string', 'owner must be a string'); invariant(typeof callback === 'function', 'callback must be a function'); - var url = makeURL(this, constants.API_UPLOADS, { owner: owner }); + var url = makeURL(this, constants.API_UPLOADS, { owner: this.user }); request .post(url) @@ -99,18 +78,12 @@ Uploads.prototype.createUpload = function(options, owner, callback) { }); }; -Uploads.prototype.readUpload = function(upload, owner, callback) { - // defaults to the owner of the provided token if omitted - if (callback === undefined && typeof owner === 'function') { - callback = owner; - owner = this.user; - } - +Uploads.prototype.readUpload = function(upload, callback) { invariant(typeof upload === 'string', 'upload must be a string'); invariant(typeof callback === 'function', 'callback must be a function'); var url = makeURL(this, constants.API_UPLOAD, { - owner: owner, + owner: this.user, upload: upload }); @@ -119,19 +92,12 @@ Uploads.prototype.readUpload = function(upload, owner, callback) { }); }; -Uploads.prototype.deleteUpload = function(upload, owner, callback) { - // defaults to the owner of the provided token if omitted - if (callback === undefined && typeof owner === 'function') { - callback = owner; - owner = this.user; - } - +Uploads.prototype.deleteUpload = function(upload, callback) { invariant(typeof upload === 'string', 'upload must be a string'); - invariant(typeof owner === 'string', 'owner must be a string'); invariant(typeof callback === 'function', 'callback must be a function'); var url = makeURL(this, constants.API_UPLOAD, { - owner: owner, + owner: this.user, upload: upload }); diff --git a/test/uploads.js b/test/uploads.js index 23eb57bf..2e0e089b 100644 --- a/test/uploads.js +++ b/test/uploads.js @@ -25,34 +25,7 @@ test('UploadClient', function(uploadClient) { assert.end(); }); - createUploadCredentials.test('with owner', function(assert) { - var client = new MapboxClient(process.env.MapboxAccessToken); - assert.ok(client, 'created upload client'); - - client.createUploadCredentials(client.user, function(err, credentials) { - assert.ifError(err, 'success'); - var s3 = new AWS.S3({ - accessKeyId: credentials.accessKeyId, - secretAccessKey: credentials.secretAccessKey, - sessionToken: credentials.sessionToken, - region: 'us-east-1' - }); - s3.putObject({ - Bucket: credentials.bucket, - Key: credentials.key, - Body: fs.createReadStream(__dirname + '/fixtures/valid-onlytiles.mbtiles') - }, function(err, resp) { - assert.ifError(err, 'success'); - testStagedFiles.push({ - bucket: credentials.bucket, - key: credentials.key - }); - assert.end(); - }); - }); - }); - - createUploadCredentials.test('without owner', function(assert) { + createUploadCredentials.test('valid request', function(assert) { var client = new MapboxClient(process.env.MapboxAccessToken); assert.ok(client, 'created upload client'); @@ -95,7 +68,7 @@ test('UploadClient', function(uploadClient) { assert.end(); }); - createUpload.test('without owner', function(assert) { + createUpload.test('valid request', function(assert) { var client = new MapboxClient(process.env.MapboxAccessToken); assert.ok(client, 'created upload client'); var staged = testStagedFiles.shift(); @@ -110,21 +83,6 @@ test('UploadClient', function(uploadClient) { }); }); - createUpload.test('with owner', function(assert) { - var client = new MapboxClient(process.env.MapboxAccessToken); - assert.ok(client, 'created upload client'); - var staged = testStagedFiles.shift(); - var url = 'http://' + staged.bucket + '.s3.amazonaws.com/' + staged.key; - client.createUpload({ - tileset: [client.user, hat()].join('.'), - url: url - }, client.user, function(err, upload) { - assert.ifError(err, 'success'); - testUploads.push(upload); - assert.end(); - }); - }); - createUpload.end(); }); @@ -141,7 +99,7 @@ test('UploadClient', function(uploadClient) { assert.end(); }); - readUpload.test('without owner', function(assert) { + readUpload.test('valid request', function(assert) { var client = new MapboxClient(process.env.MapboxAccessToken); assert.ok(client, 'created upload client'); var upload = testUploads.shift(); @@ -159,16 +117,6 @@ test('UploadClient', function(uploadClient) { poll(); }); - readUpload.test('with owner', function(assert) { - var client = new MapboxClient(process.env.MapboxAccessToken); - assert.ok(client, 'created upload client'); - var upload = testUploads.shift(); - client.readUpload(upload.id, upload.owner, function(err, upload) { - assert.ifError(err, 'success'); - assert.end(); - }); - }); - readUpload.end(); }); @@ -185,7 +133,7 @@ test('UploadClient', function(uploadClient) { assert.end(); }); - listUploads.test('without owner', function(assert) { + listUploads.test('valid request', function(assert) { var client = new MapboxClient(process.env.MapboxAccessToken); assert.ok(client, 'created upload client'); client.listUploads(function(err, uploads) { @@ -210,7 +158,7 @@ test('UploadClient', function(uploadClient) { assert.end(); }); - deleteUpload.test('without owner', function(assert) { + deleteUpload.test('valid request', function(assert) { var client = new MapboxClient(process.env.MapboxAccessToken); assert.ok(client, 'created upload client'); var upload = testUploads.shift(); From f4b3a71ac698e8153556dfd62e2bcebcfea302fd Mon Sep 17 00:00:00 2001 From: Will White Date: Wed, 29 Jul 2015 23:44:16 -0400 Subject: [PATCH 3/8] Documentation for uploads. --- API.md | 190 ++++++++++++++++++++++++++++++++++++++++ lib/services/uploads.js | 117 ++++++++++++++++++++++++- 2 files changed, 306 insertions(+), 1 deletion(-) diff --git a/API.md b/API.md index ee5a4615..a35aa5c7 100644 --- a/API.md +++ b/API.md @@ -138,6 +138,98 @@ mapboxClient.createDataset({ name: 'foo', description: 'bar' }, function(err, da Returns nothing, calls callback +## `createUpload` + +Create an new upload with a file previously staged on Amazon S3. + +This request requires an access token with the uploads:write scope. + +### Parameters + +* `options` **`Object`** an object that defines the upload's properties + * `options.tileset` **`String`** id of the tileset to create or replace. This must consist of an account id and a unique key separated by a period. Reuse of a tileset value will overwrite existing data. To avoid overwriting existing data, you must ensure that you are using unique tileset ids. + * `options.url` **`String`** https url of a file staged on Amazon S3. +* `callback` **`Function`** called with (err, upload) + + +### Examples + +```js +var mapboxClient = new MapboxClient('ACCESSTOKEN'); +// Response from a call to createUploadCredentials +var credentials = { + "accessKeyId": "{accessKeyId}", + "bucket": "somebucket", + "key": "hij456", + "secretAccessKey": "{secretAccessKey}", + "sessionToken": "{sessionToken}" +}; +mapboxClient.createUpload({ + tileset: [accountid, 'mytileset'].join('.'), + url: 'http://' + credentials.bucket + '.s3.amazonaws.com/' + credentials.key +}, function(err, upload) { + console.log(upload); + // { + // "complete": false, + // "tileset": "example.markers", + // "error": null, + // "id": "hij456", + // "modified": "2014-11-21T19:41:10.000Z", + // "created": "2014-11-21T19:41:10.000Z", + // "owner": "example", + // "progress": 0 + // } +}); +``` + +Returns nothing, calls callback + +## `createUploadCredentials` + +Retrieve credentials that allow a new file to be staged on Amazon S3 +while an upload is processed. All uploads must be staged using these +credentials before being uploaded to Mapbox. + +This request requires an access token with the uploads:write scope. + +### Parameters + +* `callback` **`Function`** called with (err, credentials) + + +### Examples + +```js +var mapboxClient = new MapboxClient('ACCESSTOKEN'); +mapboxClient.createUploadCredentials(function(err, credentials) { + console.log(credentials); + // { + // "accessKeyId": "{accessKeyId}", + // "bucket": "somebucket", + // "key": "hij456", + // "secretAccessKey": "{secretAccessKey}", + // "sessionToken": "{sessionToken}" + // } + + // Use aws-sdk to stage the file on Amazon S3 + var AWS = require('aws-sdk'); + var s3 = new AWS.S3({ + accessKeyId: credentials.accessKeyId, + secretAccessKey: credentials.secretAccessKey, + sessionToken: credentials.sessionToken, + region: 'us-east-1' + }); + s3.putObject({ + Bucket: credentials.bucket, + Key: credentials.key, + Body: fs.createReadStream('/path/to/file.mbtiles') + }, function(err, resp) { + }); +}); +``` + +Returns nothing, calls callback + ## `deleteDataset` To delete a particular dataset. @@ -183,6 +275,27 @@ mapboxClient.deleteFeature('feature-id', 'dataset-id', function(err, feature) { Returns nothing, calls callback +## `deleteUpload` + +Delete a completed upload. In-progress uploads cannot be deleted. + +This request requires an access token with the uploads:delete scope. + +### Parameters + +* `callback` **`Function`** called with (err) + + +### Examples + +```js +var mapboxClient = new MapboxClient('ACCESSTOKEN'); +mapboxClient.deleteUpload('hij456', function(err) { +}); +``` + +Returns nothing, calls callback + ## `geocodeForward` Search for a location with a string, using the @@ -447,6 +560,50 @@ mapboxClient.listFeatures('dataset-id', function(err, collection) { Returns nothing, calls callback +## `listUploads` + +Retrieve a listing of uploads for a particular account. + +This request requires an access token with the uploads:list scope. + +### Parameters + +* `callback` **`Function`** called with (err, uploads) + + +### Examples + +```js +var mapboxClient = new MapboxClient('ACCESSTOKEN'); +mapboxClient.listUploads(function(err, uploads) { + console.log(uploads); + // [ + // { + // "complete": true, + // "tileset": "example.mbtiles", + // "error": null, + // "id": "abc123", + // "modified": "2014-11-21T19:41:10.000Z", + // "created": "2014-11-21T19:41:10.000Z", + // "owner": "example", + // "progress": 1 + // }, + // { + // "complete": false, + // "tileset": "example.foo", + // "error": null, + // "id": "xyz789", + // "modified": "2014-11-21T19:41:10.000Z", + // "created": "2014-11-21T19:41:10.000Z", + // "owner": "example", + // "progress": 0 + // } + // ] +}); +``` + +Returns nothing, calls callback + ## `matching` Snap recorded location traces to roads and paths from OpenStreetMap. @@ -560,6 +717,39 @@ mapboxClient.readFeature('feature-id', 'dataset-id', function(err, feature) { Returns nothing, calls callback +## `readUpload` + +Retrieve state of an upload. + +This request requires an access token with the uploads:read scope. + +### Parameters + +* `upload` **`String`** id of the upload to read +* `callback` **`Function`** called with (err, upload) + + +### Examples + +```js +var mapboxClient = new MapboxClient('ACCESSTOKEN'); +mapboxClient.readUpload('hij456', function(err, upload) { + console.log(upload); + // { + // "complete": true, + // "tileset": "example.markers", + // "error": null, + // "id": "hij456", + // "modified": "2014-11-21T19:41:10.000Z", + // "created": "2014-11-21T19:41:10.000Z", + // "owner": "example", + // "progress": 1 + // } +}); +``` + +Returns nothing, calls callback + ## `surface` Given a list of locations, retrieve vector tiles, find the nearest diff --git a/lib/services/uploads.js b/lib/services/uploads.js index ad6acce9..4d0f83b1 100644 --- a/lib/services/uploads.js +++ b/lib/services/uploads.js @@ -9,7 +9,8 @@ var invariant = require('invariant'), var Uploads = module.exports = makeService('MapboxUploads'); /** - * To retrieve a listing of uploads for a particular account. + * Retrieve a listing of uploads for a particular account. + * * This request requires an access token with the uploads:list scope. * * @param {Function} callback called with (err, uploads) @@ -52,6 +53,43 @@ Uploads.prototype.listUploads = function(callback) { }); }; +/** + * Retrieve credentials that allow a new file to be staged on Amazon S3 + * while an upload is processed. All uploads must be staged using these + * credentials before being uploaded to Mapbox. + * + * This request requires an access token with the uploads:write scope. + * + * @param {Function} callback called with (err, credentials) + * @returns {undefined} nothing, calls callback + * @example + * var mapboxClient = new MapboxClient('ACCESSTOKEN'); + * mapboxClient.createUploadCredentials(function(err, credentials) { + * console.log(credentials); + * // { + * // "accessKeyId": "{accessKeyId}", + * // "bucket": "somebucket", + * // "key": "hij456", + * // "secretAccessKey": "{secretAccessKey}", + * // "sessionToken": "{sessionToken}" + * // } + * + * // Use aws-sdk to stage the file on Amazon S3 + * var AWS = require('aws-sdk'); + * var s3 = new AWS.S3({ + * accessKeyId: credentials.accessKeyId, + * secretAccessKey: credentials.secretAccessKey, + * sessionToken: credentials.sessionToken, + * region: 'us-east-1' + * }); + * s3.putObject({ + * Bucket: credentials.bucket, + * Key: credentials.key, + * Body: fs.createReadStream('/path/to/file.mbtiles') + * }, function(err, resp) { + * }); + * }); + */ Uploads.prototype.createUploadCredentials = function(callback) { invariant(typeof callback === 'function', 'callback must be a function'); @@ -64,6 +102,47 @@ Uploads.prototype.createUploadCredentials = function(callback) { }); }; +/** + * Create an new upload with a file previously staged on Amazon S3. + * + * This request requires an access token with the uploads:write scope. + * + * @param {Object} options an object that defines the upload's properties + * @param {String} options.tileset id of the tileset to create or + * replace. This must consist of an account id and a unique key + * separated by a period. Reuse of a tileset value will overwrite + * existing data. To avoid overwriting existing data, you must ensure + * that you are using unique tileset ids. + * @param {String} options.url https url of a file staged on Amazon S3. + * @param {Function} callback called with (err, upload) + * @returns {undefined} nothing, calls callback + * @example + * var mapboxClient = new MapboxClient('ACCESSTOKEN'); + * // Response from a call to createUploadCredentials + * var credentials = { + * "accessKeyId": "{accessKeyId}", + * "bucket": "somebucket", + * "key": "hij456", + * "secretAccessKey": "{secretAccessKey}", + * "sessionToken": "{sessionToken}" + * }; + * mapboxClient.createUpload({ + * tileset: [accountid, 'mytileset'].join('.'), + * url: 'http://' + credentials.bucket + '.s3.amazonaws.com/' + credentials.key + * }, function(err, upload) { + * console.log(upload); + * // { + * // "complete": false, + * // "tileset": "example.markers", + * // "error": null, + * // "id": "hij456", + * // "modified": "2014-11-21T19:41:10.000Z", + * // "created": "2014-11-21T19:41:10.000Z", + * // "owner": "example", + * // "progress": 0 + * // } + * }); + */ Uploads.prototype.createUpload = function(options, callback) { invariant(typeof options === 'object', 'options must be an object'); invariant(typeof callback === 'function', 'callback must be a function'); @@ -78,6 +157,30 @@ Uploads.prototype.createUpload = function(options, callback) { }); }; +/** + * Retrieve state of an upload. + * + * This request requires an access token with the uploads:read scope. + * + * @param {String} upload id of the upload to read + * @param {Function} callback called with (err, upload) + * @returns {undefined} nothing, calls callback + * @example + * var mapboxClient = new MapboxClient('ACCESSTOKEN'); + * mapboxClient.readUpload('hij456', function(err, upload) { + * console.log(upload); + * // { + * // "complete": true, + * // "tileset": "example.markers", + * // "error": null, + * // "id": "hij456", + * // "modified": "2014-11-21T19:41:10.000Z", + * // "created": "2014-11-21T19:41:10.000Z", + * // "owner": "example", + * // "progress": 1 + * // } + * }); + */ Uploads.prototype.readUpload = function(upload, callback) { invariant(typeof upload === 'string', 'upload must be a string'); invariant(typeof callback === 'function', 'callback must be a function'); @@ -92,6 +195,18 @@ Uploads.prototype.readUpload = function(upload, callback) { }); }; +/** + * Delete a completed upload. In-progress uploads cannot be deleted. + * + * This request requires an access token with the uploads:delete scope. + * + * @param {Function} callback called with (err) + * @returns {undefined} nothing, calls callback + * @example + * var mapboxClient = new MapboxClient('ACCESSTOKEN'); + * mapboxClient.deleteUpload('hij456', function(err) { + * }); + */ Uploads.prototype.deleteUpload = function(upload, callback) { invariant(typeof upload === 'string', 'upload must be a string'); invariant(typeof callback === 'function', 'callback must be a function'); From dfee6b93376b65d1ca7b041d0227da05c26d7859 Mon Sep 17 00:00:00 2001 From: Will White Date: Thu, 30 Jul 2015 00:15:42 -0400 Subject: [PATCH 4/8] build out tests. --- test/uploads.js | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/test/uploads.js b/test/uploads.js index 2e0e089b..1f7c9981 100644 --- a/test/uploads.js +++ b/test/uploads.js @@ -31,6 +31,12 @@ test('UploadClient', function(uploadClient) { client.createUploadCredentials(function(err, credentials) { assert.ifError(err, 'success'); + assert.ok(credentials, 'has credentials'); + assert.ok(credentials.accessKeyId, 'has accessKeyId'); + assert.ok(credentials.bucket, 'has bucket'); + assert.ok(credentials.key, 'has key'); + assert.ok(credentials.secretAccessKey, 'has secretAccessKey'); + assert.ok(credentials.sessionToken, 'has sessionToken'); var s3 = new AWS.S3({ accessKeyId: credentials.accessKeyId, secretAccessKey: credentials.secretAccessKey, @@ -78,11 +84,31 @@ test('UploadClient', function(uploadClient) { url: url }, function(err, upload) { assert.ifError(err, 'success'); + assert.ok(upload.id, 'has id'); + assert.ok(upload.complete === false, 'has complete'); + assert.ok(upload.tileset, 'has tileset'); + assert.ok(upload.error === null, 'has error'); + assert.ok(upload.modified, 'has modified'); + assert.ok(upload.created, 'has created'); + assert.ok(upload.owner, 'has owner'); + assert.ok(upload.progress === 0, 'has progress'); testUploads.push(upload); assert.end(); }); }); + createUpload.test('invalid request', function(assert) { + var client = new MapboxClient(process.env.MapboxAccessToken); + assert.ok(client, 'created upload client'); + client.createUpload({ + tileset: 'blah' + }, function(err, upload) { + assert.equal(err.status, 422); + assert.equal(upload.message, 'Missing property "url"'); + assert.end(); + }); + }); + createUpload.end(); }); @@ -107,7 +133,7 @@ test('UploadClient', function(uploadClient) { function poll() { client.readUpload(upload.id, function(err, upload) { assert.ifError(err, 'success'); - if (attempts > 10) throw new Error('Upload did not complete in time'); + if (attempts > 3) throw new Error('Upload did not complete in time'); // we are waiting for mapbox to process the upload if (!upload.complete) return setTimeout(poll, Math.pow(2, attempts++) * 1000); completedUpload = upload; @@ -117,6 +143,16 @@ test('UploadClient', function(uploadClient) { poll(); }); + readUpload.test('does not exist', function(assert) { + var client = new MapboxClient(process.env.MapboxAccessToken); + assert.ok(client, 'created upload client'); + client.readUpload('fakeo', function(err, upload) { + assert.equal(err.status, 404); + assert.equal(upload.message, 'Not Found'); + assert.end(); + }); + }); + readUpload.end(); }); From 4fc8152e704b9dcd3c4c25d986b1be45b6038e0b Mon Sep 17 00:00:00 2001 From: Will White Date: Thu, 30 Jul 2015 00:17:43 -0400 Subject: [PATCH 5/8] Increase allowed time for upload. --- test/uploads.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/uploads.js b/test/uploads.js index 1f7c9981..eca6595a 100644 --- a/test/uploads.js +++ b/test/uploads.js @@ -133,7 +133,7 @@ test('UploadClient', function(uploadClient) { function poll() { client.readUpload(upload.id, function(err, upload) { assert.ifError(err, 'success'); - if (attempts > 3) throw new Error('Upload did not complete in time'); + if (attempts > 4) throw new Error('Upload did not complete in time'); // we are waiting for mapbox to process the upload if (!upload.complete) return setTimeout(poll, Math.pow(2, attempts++) * 1000); completedUpload = upload; From 82564bca71a9464ef04cc053a164b24da4b2da03 Mon Sep 17 00:00:00 2001 From: Will White Date: Thu, 30 Jul 2015 00:20:33 -0400 Subject: [PATCH 6/8] Add uploads to README. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 953f1408..a50b77a9 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ to Mapbox services. [OpenStreetMap](https://www.openstreetmap.org/) data * [Surface API](https://www.mapbox.com/developers/api/surface/) * Interpolates values along lines. Useful for elevation traces. +* [Upload API](https://www.mapbox.com/developers/api/uploads/) + * Upload data to be processed and hosted by Mapbox. ## Installation From aa84892215f56d3b6849cde69aaa963ae210fa5a Mon Sep 17 00:00:00 2001 From: Will White Date: Thu, 30 Jul 2015 02:20:36 -0400 Subject: [PATCH 7/8] Use url property from credentials endpoint. --- lib/services/uploads.js | 8 +++++--- test/uploads.js | 8 ++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/services/uploads.js b/lib/services/uploads.js index 4d0f83b1..c423f2e1 100644 --- a/lib/services/uploads.js +++ b/lib/services/uploads.js @@ -71,7 +71,8 @@ Uploads.prototype.listUploads = function(callback) { * // "bucket": "somebucket", * // "key": "hij456", * // "secretAccessKey": "{secretAccessKey}", - * // "sessionToken": "{sessionToken}" + * // "sessionToken": "{sessionToken}", + * // "url": "{s3 url}" * // } * * // Use aws-sdk to stage the file on Amazon S3 @@ -124,11 +125,12 @@ Uploads.prototype.createUploadCredentials = function(callback) { * "bucket": "somebucket", * "key": "hij456", * "secretAccessKey": "{secretAccessKey}", - * "sessionToken": "{sessionToken}" + * "sessionToken": "{sessionToken}", + * "url": "{s3 url}" * }; * mapboxClient.createUpload({ * tileset: [accountid, 'mytileset'].join('.'), - * url: 'http://' + credentials.bucket + '.s3.amazonaws.com/' + credentials.key + * url: credentials.url * }, function(err, upload) { * console.log(upload); * // { diff --git a/test/uploads.js b/test/uploads.js index eca6595a..aad067c4 100644 --- a/test/uploads.js +++ b/test/uploads.js @@ -49,10 +49,7 @@ test('UploadClient', function(uploadClient) { Body: fs.createReadStream(__dirname + '/fixtures/valid-onlytiles.mbtiles') }, function(err, resp) { assert.ifError(err, 'success'); - testStagedFiles.push({ - bucket: credentials.bucket, - key: credentials.key - }); + testStagedFiles.push(credentials); assert.end(); }); }); @@ -78,10 +75,9 @@ test('UploadClient', function(uploadClient) { var client = new MapboxClient(process.env.MapboxAccessToken); assert.ok(client, 'created upload client'); var staged = testStagedFiles.shift(); - var url = 'http://' + staged.bucket + '.s3.amazonaws.com/' + staged.key; client.createUpload({ tileset: [client.user, hat()].join('.'), - url: url + url: staged.url }, function(err, upload) { assert.ifError(err, 'success'); assert.ok(upload.id, 'has id'); From 652761d4ec5c9bd15c65b7b4fc21abb8400d0583 Mon Sep 17 00:00:00 2001 From: Will White Date: Thu, 30 Jul 2015 13:22:20 -0400 Subject: [PATCH 8/8] Renamed user -> owner. --- lib/services/uploads.js | 10 +++++----- test/uploads.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/services/uploads.js b/lib/services/uploads.js index c423f2e1..92ffef25 100644 --- a/lib/services/uploads.js +++ b/lib/services/uploads.js @@ -46,7 +46,7 @@ var Uploads = module.exports = makeService('MapboxUploads'); Uploads.prototype.listUploads = function(callback) { invariant(typeof callback === 'function', 'callback must be a function'); - var url = makeURL(this, constants.API_UPLOADS, { owner: this.user }); + var url = makeURL(this, constants.API_UPLOADS, { owner: this.owner }); request(url, function(err, res) { callback(err, res.body); @@ -94,7 +94,7 @@ Uploads.prototype.listUploads = function(callback) { Uploads.prototype.createUploadCredentials = function(callback) { invariant(typeof callback === 'function', 'callback must be a function'); - var url = makeURL(this, constants.API_UPLOAD_CREDENTIALS, { owner: this.user }); + var url = makeURL(this, constants.API_UPLOAD_CREDENTIALS, { owner: this.owner }); request .get(url) @@ -149,7 +149,7 @@ Uploads.prototype.createUpload = function(options, callback) { invariant(typeof options === 'object', 'options must be an object'); invariant(typeof callback === 'function', 'callback must be a function'); - var url = makeURL(this, constants.API_UPLOADS, { owner: this.user }); + var url = makeURL(this, constants.API_UPLOADS, { owner: this.owner }); request .post(url) @@ -188,7 +188,7 @@ Uploads.prototype.readUpload = function(upload, callback) { invariant(typeof callback === 'function', 'callback must be a function'); var url = makeURL(this, constants.API_UPLOAD, { - owner: this.user, + owner: this.owner, upload: upload }); @@ -214,7 +214,7 @@ Uploads.prototype.deleteUpload = function(upload, callback) { invariant(typeof callback === 'function', 'callback must be a function'); var url = makeURL(this, constants.API_UPLOAD, { - owner: this.user, + owner: this.owner, upload: upload }); diff --git a/test/uploads.js b/test/uploads.js index aad067c4..8ab1ed4d 100644 --- a/test/uploads.js +++ b/test/uploads.js @@ -76,7 +76,7 @@ test('UploadClient', function(uploadClient) { assert.ok(client, 'created upload client'); var staged = testStagedFiles.shift(); client.createUpload({ - tileset: [client.user, hat()].join('.'), + tileset: [client.owner, hat()].join('.'), url: staged.url }, function(err, upload) { assert.ifError(err, 'success');