Implemented and working assets (todo: asset convenience methods)
diff --git a/helpers/build.js b/helpers/build.js
index 67fa0c9..0991638 100644
--- a/helpers/build.js
+++ b/helpers/build.js
@@ -6,6 +6,7 @@
UsergridQuery = require('../lib/query'),
UsergridEntity = require('../lib/entity'),
UsergridAuth = require('../lib/auth'),
+ UsergridAsset = require('../lib/asset'),
util = require('util'),
version = require('../package.json').version,
ok = require('objectkit'),
@@ -13,12 +14,44 @@
var assignPrefabOptions = function(args) {
// if a preformatted options argument passed, assign it to options
- if (_.isObject(args[0]) && !_.isFunction(args[0]) && args.length <= 2) {
+ if (_.isObject(args[0]) && !_.isFunction(args[0]) && ok(this).has("method")) {
_.assign(this, args[0])
}
return this
}
+var setEntity = function(args) {
+ this.entity = _.first([this.entity, args[0]].filter(function(property) {
+ return (property instanceof UsergridEntity)
+ }))
+ if (this.entity !== undefined) {
+ this.type = this.entity.type
+ }
+ return this
+}
+
+var setAsset = function(args) {
+ this.asset = _.first([this.asset, ok(this).getIfExists('entity.asset'), args[1], args[0]].filter(function(property) {
+ return (property instanceof UsergridAsset)
+ }))
+ return this
+}
+
+var setUuidOrName = function(args) {
+ this.uuidOrName = _.first([
+ this.uuidOrName,
+ this.uuid,
+ this.name,
+ ok(this).getIfExists('entity.uuid'),
+ ok(this).getIfExists('body.uuid'),
+ ok(this).getIfExists('entity.name'),
+ ok(this).getIfExists('body.name'),
+ ok(args).getIfExists('2'),
+ ok(args).getIfExists('1')
+ ].filter(_.isString))
+ return this
+}
+
var setPathOrType = function(args) {
var pathOrType = _.first([
this.type,
@@ -48,34 +81,14 @@
var setBody = function(args) {
this.body = _.first([this.entity, this.body, args[2], args[1], args[0]].filter(function(property) {
- return _.isObject(property) && !_.isFunction(property) && !(property instanceof UsergridQuery)
+ return _.isObject(property) && !_.isFunction(property) && !(property instanceof UsergridQuery) && !(property instanceof UsergridAsset)
}))
- if (this.body === undefined) {
+ if (this.body === undefined && this.asset === undefined) {
throw new Error(util.format('"body" is required when making a %s request', this.method))
}
return this
}
-var setUuidOrName = function(args) {
- this.uuidOrName = _.first([
- this.uuidOrName,
- this.uuid,
- this.name,
- ok(this).getIfExists('entity.uuid'),
- ok(this).getIfExists('body.uuid'),
- _.isArray(args) ? args[2] : undefined,
- _.isArray(args) ? args[1] : undefined
- ].filter(_.isString))
- return this
-}
-
-var setEntity = function(args) {
- this.entity = _.first([this.entity, args[0]].filter(function(property) {
- return (property instanceof UsergridEntity)
- }))
- return this
-}
-
module.exports = {
uri: function(client, options) {
return urljoin(
@@ -83,7 +96,14 @@
client.orgId,
client.appId,
options.path || options.type,
- _.first([options.uuidOrName, options.uuid, options.name, ""].filter(_.isString))
+ options.method !== "POST" ? _.first([
+ options.uuidOrName,
+ options.uuid,
+ options.name,
+ ok(options).getIfExists('entity.uuid'),
+ ok(options).getIfExists('entity.name'),
+ ""
+ ].filter(_.isString)) : ""
)
},
headers: function(client) {
@@ -158,11 +178,11 @@
callback: helpers.cb(args)
}
assignPrefabOptions.call(options, args)
+ setEntity.call(options, args)
setUuidOrName.call(options, args)
setPathOrType.call(options, args)
setQs.call(options, args)
setQuery.call(options, args)
- setEntity.call(options, args)
return options
},
PUT: function(client, args) {
@@ -191,12 +211,12 @@
callback: helpers.cb(args)
}
assignPrefabOptions.call(options, args)
+ setEntity.call(options, args)
+ setAsset.call(options, args)
setBody.call(options, args)
setUuidOrName.call(options, args)
setPathOrType.call(options, args)
setQuery.call(options, args)
- setEntity.call(options, args)
-
return options
},
POST: function(client, args) {
@@ -220,6 +240,8 @@
callback: helpers.cb(args)
}
assignPrefabOptions.call(options, args)
+ setEntity.call(options, args)
+ setAsset.call(options, args)
setBody.call(options, args)
setPathOrType.call(options, args)
return options
@@ -246,14 +268,11 @@
callback: helpers.cb(args)
}
assignPrefabOptions.call(options, args)
+ setEntity.call(options, args)
setUuidOrName.call(options, args)
setPathOrType.call(options, args)
setQs.call(options, args)
- setEntity.call(options, args)
setQuery.call(options, args)
- if (!_.isString(options.uuidOrName) && options.query === undefined && options.path === undefined) {
- throw new Error('"uuidOrName", "query", or "path" is required when making a DELETE request')
- }
return options
},
connection: function(client, method, args) {
@@ -414,5 +433,30 @@
)
return options
+ },
+ qs: function(options) {
+ return (options.query instanceof UsergridQuery) ? {
+ ql: options.query._ql || undefined,
+ limit: options.query._limit,
+ cursor: options.query._cursor
+ } : options.qs
+ },
+ formData: function(options) {
+ if (ok(options).getIfExists('asset.data')) {
+ var formData = {}
+ formData.file = {
+ value: options.asset.data,
+ options: {
+ filename: ok(options).getIfExists('asset.filename') || UsergridAsset.DEFAULT_FILE_NAME,
+ contentType: ok(options).getIfExists('asset.contentType') || 'application/octet-stream'
+ }
+ }
+ if (ok(options).has('asset.name')) {
+ formData.name = options.asset.name
+ }
+ return formData
+ } else {
+ return undefined
+ }
}
}
\ No newline at end of file
diff --git a/lib/asset.js b/lib/asset.js
index e69de29..d5f25b2 100644
--- a/lib/asset.js
+++ b/lib/asset.js
@@ -0,0 +1,75 @@
+'use strict'
+
+var Usergrid = require('../usergrid'),
+ validator = require('validator'),
+ readChunk = require('read-chunk'),
+ fileType = require('file-type'),
+ helpers = require('../helpers'),
+ stream = require('stream'),
+ util = require('util'),
+ ok = require('objectkit'),
+ _ = require('lodash')
+
+var UsergridAsset = function() {
+ var self = this
+ var args = helpers.args(arguments)
+
+ var __contentType
+ var __binaryData = []
+
+ if (args.length === 0) {
+ throw new Error('A UsergridAsset object was initialized using an empty argument')
+ }
+
+ if (_.isObject(args[0])) {
+ _.assign(self, args[0])
+ } else {
+ self.filename = _.isString(args[0]) ? args[0] : undefined
+ self.data = validator.isBase64(args[1]) || Buffer.isBuffer(args[1]) ? args[1] : []
+ self.originalLocation = _.first([args[2], args[1]].filter(function(property) {
+ return (_.isString(property) && !validator.isBase64(property))
+ }))
+ self.contentType = _.isString(args[3]) ? args[3] : undefined
+
+ stream.PassThrough.call(self)
+ self._write = function(chunk, encoding, done) {
+ __binaryData.push(chunk)
+ done()
+ }
+ self.on('finish', function() {
+ self.data = Buffer.concat(__binaryData)
+ })
+ }
+
+ Object.defineProperty(self, 'contentLength', {
+ get: function() {
+ return (self.data) ? self.data.byteLength : 0
+ }
+ })
+
+ Object.defineProperty(self, 'contentType', {
+ get: function() {
+ if (__contentType) {
+ return __contentType
+ } else if (Buffer.isBuffer(self.data)) {
+ __contentType = fileType(self.data).mime
+ return __contentType
+ }
+ },
+ set: function(contentType) {
+ if (contentType) {
+ __contentType = contentType
+ } else if (Buffer.isBuffer(self.data)) {
+ __contentType = fileType(self.data).mime
+ }
+ }
+ })
+
+ return self
+}
+
+util.inherits(UsergridAsset, stream.PassThrough)
+
+
+module.exports = UsergridAsset
+module.exports.DEFAULT_FILE_NAME = 'file'
\ No newline at end of file
diff --git a/lib/entity.js b/lib/entity.js
index cc81e17..cc17872 100644
--- a/lib/entity.js
+++ b/lib/entity.js
@@ -9,7 +9,7 @@
var self = this
var args = helpers.args(arguments)
- if (!args[0]) {
+ if (args.length === 0) {
throw new Error('A UsergridEntity object was initialized using an empty argument')
}
@@ -42,6 +42,8 @@
}
})
+ self.asset
+
helpers.setReadOnly(self, ['uuid', 'name', 'type', 'created'])
return self
@@ -121,7 +123,9 @@
callback(error, usergridResponse, this)
}.bind(this))
},
- attachAsset: function() {},
+ attachAsset: function(asset) {
+ this.asset = asset
+ },
uploadAsset: function() {},
downloadAsset: function() {},
connect: function() {
diff --git a/lib/request.js b/lib/request.js
index 65dad4a..28e549e 100644
--- a/lib/request.js
+++ b/lib/request.js
@@ -4,33 +4,34 @@
helpers = require('../helpers'),
UsergridResponse = require('../lib/response'),
UsergridQuery = require('../lib/query'),
- util = require('util'),
+ UsergridAsset = require('../lib/asset'),
ok = require('objectkit'),
_ = require('lodash')
var UsergridRequest = function(options) {
+ var self = this
var client = helpers.client.validate(options.client)
var callback = helpers.cb(helpers.args(arguments))
if (!_.isString(options.type) && !_.isString(options.path) && !_.isString(options.uri)) {
throw new Error('one of "type" (collection name), "path", or "uri" parameters are required when initializing a UsergridRequest')
}
+
if (!_.includes(['GET', 'PUT', 'POST', 'DELETE'], options.method)) {
throw new Error('"method" parameter is required when initializing a UsergridRequest')
}
var uri = options.uri || helpers.build.uri(client, options)
+ var formData = helpers.build.formData(options)
+ var body = ((formData !== undefined) ? undefined : options.body)
- request(uri, {
+ var req = request(uri, {
headers: helpers.build.headers(client),
- body: options.body,
+ body: body,
json: true,
method: options.method,
- qs: (options.query instanceof UsergridQuery) ? {
- ql: options.query._ql || undefined,
- limit: options.query._limit,
- cursor: options.query._cursor
- } : options.qs
+ qs: helpers.build.qs(options),
+ formData: formData
}, function(error, response) {
var usergridResponse = new UsergridResponse(response)
var returnBody = _.first([usergridResponse.user, usergridResponse.users, usergridResponse.entity, usergridResponse.entities, usergridResponse.body].filter(_.isObject))
diff --git a/lib/response.js b/lib/response.js
index 63a4b20..3cd21e1 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -26,7 +26,6 @@
}
return entity
})
-
_.assign(self, {
metadata: _.cloneDeep(response.body),
entities: entities
diff --git a/package.json b/package.json
index bfd5a32..d949ff1 100644
--- a/package.json
+++ b/package.json
@@ -2,12 +2,16 @@
"author": "Brandon Shelley",
"dependencies": {
"async": "latest",
+ "file-type": "^3.4.0",
"lodash": "~4.0",
"lodash-inflection": "latest",
"lodash-uuid": "latest",
"objectkit": "latest",
+ "read-chunk": "^1.0.1",
"request": "latest",
- "url-join": "latest"
+ "through2": "^2.0.0",
+ "url-join": "latest",
+ "validator": "^4.5.0"
},
"description": "The official Node.js SDK for Usergrid",
"devDependencies": {
diff --git a/tests/lib/asset.test.js b/tests/lib/asset.test.js
new file mode 100644
index 0000000..0911ea8
--- /dev/null
+++ b/tests/lib/asset.test.js
@@ -0,0 +1,122 @@
+'use strict'
+
+var should = require('should'),
+ urljoin = require('url-join'),
+ config = require('../../helpers').config,
+ UsergridEntity = require('../../lib/entity'),
+ UsergridAsset = require('../../lib/asset'),
+ UsergridClient = require('../../lib/client'),
+ util = require('util'),
+ request = require('request'),
+ fs = require('fs'),
+ _ = require('lodash')
+
+var _slow = 6000,
+ _timeout = 12000,
+ filename = 'old_man',
+ file = __dirname + '/image.jpg',
+ testFile = __dirname + '/image_test.jpg',
+ expectedContentLength = 109055
+
+describe('init from fs.readFile()', function() {
+ var asset = new UsergridAsset(filename, file)
+
+ before(function(done) {
+ fs.readFile(file, function(err, data) {
+ asset.data = data
+ done()
+ })
+ })
+
+ it('asset.data should be a binary Buffer', function() {
+ asset.data.should.be.a.buffer
+ })
+
+ it('asset.contentType should be inferred from Buffer', function() {
+ asset.contentType.should.equal('image/jpeg')
+ })
+
+ it(util.format('asset.contentLength should be %s bytes', expectedContentLength), function() {
+ asset.contentLength.should.equal(expectedContentLength)
+ })
+})
+
+describe('init from piped writable stream', function() {
+ var asset = new UsergridAsset(filename, file)
+ var writeTestAsset = new UsergridAsset('image_test', testFile)
+ before(function(done) {
+ var stream = fs.createReadStream(file).pipe(asset),
+ writeTest
+ stream.on('finish', function() {
+ fs.writeFile(testFile, asset.data)
+ writeTest = fs.createReadStream(file).pipe(writeTestAsset)
+ writeTest.on('finish', function() {
+ done()
+ })
+ })
+ })
+
+ it('asset.data should be a binary Buffer', function() {
+ asset.data.should.be.a.buffer
+ })
+
+ it('asset.contentType should be inferred from Buffer', function() {
+ asset.contentType.should.equal('image/jpeg')
+ })
+
+ it(util.format('asset.contentLength should be %s bytes', expectedContentLength), function() {
+ asset.contentLength.should.equal(expectedContentLength)
+ })
+
+ it('should write an identical asset to the filesystem', function() {
+ writeTestAsset.contentType.should.equal('image/jpeg')
+ writeTestAsset.contentLength.should.equal(expectedContentLength)
+ })
+})
+
+describe('upload via client.POST to a specific entity', function() {
+
+ this.slow(_slow)
+ this.timeout(_timeout)
+
+ var client = new UsergridClient()
+ it('should upload a binary asset and create a new entity', function(done) {
+ var asset = new UsergridAsset(filename, file)
+ fs.createReadStream(file).pipe(asset).on('finish', function() {
+ client.POST(config.test.collection, asset, function(err, assetResponse, entityWithAsset) {
+ assetResponse.statusCode.should.equal(200)
+ entityWithAsset.should.have.property('file-metadata')
+ entityWithAsset['file-metadata'].should.have.property('content-type').equal('image/jpeg')
+ entityWithAsset['file-metadata'].should.have.property('content-length').equal(expectedContentLength)
+ entityWithAsset.remove(client)
+ done()
+ })
+ })
+ })
+})
+
+describe('upload via client.PUT to a specific entity', function() {
+
+ this.slow(_slow)
+ this.timeout(_timeout)
+
+ var client = new UsergridClient()
+ it('should upload a binary asset to an existing entity', function(done) {
+ var entity = new UsergridEntity({
+ type: config.test.collection,
+ name: "AssetTestPUT",
+ })
+ var asset = new UsergridAsset(filename, file)
+ client.PUT(entity, function(err, entityResponse, createdEntity) {
+ fs.createReadStream(file).pipe(asset).on('finish', function() {
+ client.PUT(createdEntity, asset, function(err, assetResponse, entityWithAsset) {
+ assetResponse.statusCode.should.equal(200)
+ entityWithAsset.should.have.property('file-metadata')
+ entityWithAsset['file-metadata'].should.have.property('content-type').equal('image/jpeg')
+ entityWithAsset['file-metadata'].should.have.property('content-length').equal(expectedContentLength)
+ done()
+ })
+ })
+ })
+ })
+})
\ No newline at end of file
diff --git a/tests/lib/client.rest.test.js b/tests/lib/client.rest.test.js
index 75f17d2..7cf7074 100644
--- a/tests/lib/client.rest.test.js
+++ b/tests/lib/client.rest.test.js
@@ -3,6 +3,7 @@
var should = require('should'),
urljoin = require('url-join'),
config = require('../../helpers').config,
+ chance = new require('chance').Chance(),
UsergridClient = require('../../lib/client'),
UsergridEntity = require('../../lib/entity'),
UsergridQuery = require('../../lib/query'),
@@ -135,6 +136,22 @@
})
})
+ it('should support creating an entity by passing a UsergridEntity object with a unique name', function(done) {
+
+ this.slow(_slow)
+ this.timeout(_timeout)
+
+ var entity = new UsergridEntity({
+ type: config.test.collection,
+ name: chance.word()
+ })
+ client.POST(entity, function(err, usergridResponse) {
+ usergridResponse.entity.should.be.an.Object().with.property('name').equal(entity.name)
+ usergridResponse.entity.remove(client)
+ done()
+ })
+ })
+
it('should support creating an entity by passing type and a body object', function(done) {
this.slow(_slow)
@@ -203,9 +220,7 @@
}
client.POST(options, function(err, usergridResponse) {
- usergridResponse.entities.forEach(function(entity) {
- entity.should.be.an.Object().with.property('restaurant').equal(entity.restaurant)
- })
+ usergridResponse.entity.should.be.an.Object().with.property('restaurant').equal(usergridResponse.entity.restaurant)
done()
})
})
diff --git a/tests/lib/image.jpg b/tests/lib/image.jpg
new file mode 100644
index 0000000..32f4fa3
--- /dev/null
+++ b/tests/lib/image.jpg
Binary files differ
diff --git a/tests/lib/image_test.jpg b/tests/lib/image_test.jpg
new file mode 100644
index 0000000..32f4fa3
--- /dev/null
+++ b/tests/lib/image_test.jpg
Binary files differ
diff --git a/tests/main.test.js b/tests/main.test.js
index 87a2d05..581411e 100644
--- a/tests/main.test.js
+++ b/tests/main.test.js
@@ -2,6 +2,7 @@
// module config
var should = require('should'),
+ validator = require('validator'),
_ = require('lodash')
_.mixin(require('lodash-uuid'))
@@ -9,9 +10,17 @@
should.Assertion.add('uuid', function() {
this.params = {
operator: 'to be a valid uuid'
- };
+ }
this.assert(_.isUuid(this.obj))
})
+
+should.Assertion.add('buffer', function() {
+ this.params = {
+ operator: 'to be buffer data'
+ }
+ this.assert(Buffer.isBuffer(this.obj))
+})
+
// end module config
describe('Usergrid initialization', function() {
@@ -60,4 +69,8 @@
describe('UsergridUser', function() {
return require('./lib/user.test')
+})
+
+describe('UsergridAsset', function() {
+ return require('./lib/asset.test')
})
\ No newline at end of file
diff --git a/usergrid.js b/usergrid.js
index 330fffa..d4fe3cd 100644
--- a/usergrid.js
+++ b/usergrid.js
@@ -1,7 +1,5 @@
'use strict'
-var UsergridClient = require('./lib/client')
-
var Usergrid = {
isInitialized: false,
isSharedInstance: true,
@@ -10,6 +8,7 @@
if (self.isInitialized) {
return self
}
+ var UsergridClient = require('./lib/client')
Object.setPrototypeOf(Usergrid, new UsergridClient(options))
UsergridClient.call(self)
self.isInitialized = true