diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..bebe60a --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,23 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 14 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security + - critical + - p1 + - major +# Label to use when marking an issue as stale +staleLabel: stale +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: > + This issue has been closed due to continued inactivity. Thank you for your understanding. + If you believe this to be in error, please contact one of the code owners, + listed in the `CODEOWNERS` file at the top-level of this repository. diff --git a/CHANGES.md b/CHANGES.md index d7ba551..61912bb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,27 @@ +2017-08-30, Version 3.3.1 +========================= + + * Declare container parameter for swagger spec (Raymond Feng) + + +2017-08-30, Version 3.3.0 +========================= + + * Mark HTTP path parameters as required (Miroslav Bajtoš) + + * Add stalebot configuration (Kevin Delisle) + + * Create Issue and PR Templates (#218) (Sakib Hasan) + + * Update translated strings Q3 2017 (Allen Boone) + + * Add CODEOWNER file (Diana Lau) + + * update messages.json (Diana Lau) + + * add .travis.yml (Diana Lau) + + 2017-03-09, Version 3.2.0 ========================= diff --git a/LICENSE.md b/LICENSE.md index a7161d5..3474cfc 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) IBM Corp. 2013,2016. All Rights Reserved. +Copyright (c) IBM Corp. 2013,2017. All Rights Reserved. Node module: loopback-component-storage This project is licensed under the Artistic License 2.0, full text below. diff --git a/README.md b/README.md index d139e3f..03555be 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,20 @@ **NOTE: The loopback-component-storage module supersedes [loopback-storage-service](https://www.npmjs.org/package/loopback-storage-service). Please update your package.json accordingly.** -LoopBack storage component provides Node.js and REST APIs to manage binary contents +LoopBack storage component provides Node.js and REST APIs to manage binary file contents using pluggable storage providers, such as local file systems, Amazon S3, or -Rackspace cloud files. We use [pkgcloud](https://github.com/pkgcloud/pkgcloud) to support the cloud based +Rackspace cloud files. It uses [pkgcloud](https://github.com/pkgcloud/pkgcloud) to support cloud-based storage services including: - Amazon -- Rackspace -- Openstack - Azure +- Google Cloud +- Openstack +- Rackspace -> Please see the [Storage Service Documentaion](http://loopback.io/doc/en/lb2/Storage-component.html). +> Please see the [Storage Service Documentaion](http://loopback.io/doc/en/lb3/Storage-component.html). -For more details on the architecture of the module, please see the introduction section of the [blog post](https://strongloop.com/strongblog/managing-nodejs-loopback-storage-service-provider/) written up its launch. +For more details on the architecture of the module, please see the introduction section of the [blog post](https://strongloop.com/strongblog/managing-nodejs-loopback-storage-service-provider/). ## Use Now you can use Container's name with slash! If you want to create a directory, like `this/isMy/newContainer`, you have to use the char `%2F` instead of `/`, so your Container's name going to be `this%2FisMy%2FnewContainer`. diff --git a/lib/storage-handler.js b/lib/storage-handler.js index bd66ad9..aef9664 100644 --- a/lib/storage-handler.js +++ b/lib/storage-handler.js @@ -166,6 +166,30 @@ exports.upload = function(provider, req, res, options, cb) { uploadParams.acl = file.acl; } + // add AWS specific options + // See http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property + if (options.StorageClass) { + uploadParams.StorageClass = options.StorageClass; + } + if (options.CacheControl) { + uploadParams.CacheControl = options.CacheControl; + } + if (options.ServerSideEncryption) { + uploadParams.ServerSideEncryption = options.ServerSideEncryption; + } + if (options.SSEKMSKeyId) { + uploadParams.SSEKMSKeyId = options.SSEKMSKeyId; + } + if (options.SSECustomerAlgorithm) { + uploadParams.SSECustomerAlgorithm = options.SSECustomerAlgorithm; + } + if (options.SSECustomerKey) { + uploadParams.SSECustomerKey = options.SSECustomerKey; + } + if (options.SSECustomerKeyMD5) { + uploadParams.SSECustomerKeyMD5 = options.SSECustomerKeyMD5; + } + var writer = provider.upload(uploadParams); writer.on('error', function(err) { diff --git a/lib/storage-service.js b/lib/storage-service.js index 021e16a..dbb1d50 100644 --- a/lib/storage-service.js +++ b/lib/storage-service.js @@ -225,12 +225,21 @@ StorageService.prototype.removeFile = function(container, file, cb) { /** * Upload middleware for the HTTP request/response + * @param {String} [container] Container name * @param {Request} req Request object * @param {Response} res Response object * @param {Object} [options] Options for upload * @param {Function} cb Callback function */ -StorageService.prototype.upload = function(req, res, options, cb) { +StorageService.prototype.upload = function(container, req, res, options, cb) { + // Test if container is req for backward compatibility + if (typeof container === 'object' && container.url && container.method) { + // First argument is req, shift all args + cb = options; + options = res; + res = req; + req = container; + } if (!cb && 'function' === typeof options) { cb = options; options = {}; @@ -253,6 +262,9 @@ StorageService.prototype.upload = function(req, res, options, cb) { if (this.maxFieldsSize && !options.maxFieldsSize) { options.maxFieldsSize = this.maxFieldsSize; } + if (typeof container === 'string') { + options.container = container; + } return handler.upload(this.client, req, res, options, cb); }; @@ -282,7 +294,7 @@ StorageService.prototype.getContainers.http = StorageService.prototype.getContainer.shared = true; StorageService.prototype.getContainer.accepts = [ - {arg: 'container', type: 'string'}, + {arg: 'container', type: 'string', required: true, 'http': {source: 'path'}}, ]; StorageService.prototype.getContainer.returns = { arg: 'container', @@ -304,7 +316,7 @@ StorageService.prototype.createContainer.http = StorageService.prototype.destroyContainer.shared = true; StorageService.prototype.destroyContainer.accepts = [ - {arg: 'container', type: 'string'}, + {arg: 'container', type: 'string', required: true, 'http': {source: 'path'}}, ]; StorageService.prototype.destroyContainer.returns = {}; StorageService.prototype.destroyContainer.http = @@ -312,7 +324,7 @@ StorageService.prototype.destroyContainer.http = StorageService.prototype.getFiles.shared = true; StorageService.prototype.getFiles.accepts = [ - {arg: 'container', type: 'string'}, + {arg: 'container', type: 'string', required: true, 'http': {source: 'path'}}, ]; StorageService.prototype.getFiles.returns = {arg: 'files', type: 'array', root: true}; StorageService.prototype.getFiles.http = @@ -320,8 +332,8 @@ StorageService.prototype.getFiles.http = StorageService.prototype.getFile.shared = true; StorageService.prototype.getFile.accepts = [ - {arg: 'container', type: 'string'}, - {arg: 'file', type: 'string'}, + {arg: 'container', type: 'string', required: true, 'http': {source: 'path'}}, + {arg: 'file', type: 'string', required: true, 'http': {source: 'path'}}, ]; StorageService.prototype.getFile.returns = {arg: 'file', type: 'object', root: true}; StorageService.prototype.getFile.http = @@ -329,8 +341,8 @@ StorageService.prototype.getFile.http = StorageService.prototype.removeFile.shared = true; StorageService.prototype.removeFile.accepts = [ - {arg: 'container', type: 'string'}, - {arg: 'file', type: 'string'}, + {arg: 'container', type: 'string', required: true, 'http': {source: 'path'}}, + {arg: 'file', type: 'string', required: true, 'http': {source: 'path'}}, ]; StorageService.prototype.removeFile.returns = {}; StorageService.prototype.removeFile.http = @@ -338,6 +350,7 @@ StorageService.prototype.removeFile.http = StorageService.prototype.upload.shared = true; StorageService.prototype.upload.accepts = [ + {arg: 'container', type: 'string', required: true, 'http': {source: 'path'}}, {arg: 'req', type: 'object', 'http': {source: 'req'}}, {arg: 'res', type: 'object', 'http': {source: 'res'}}, ]; @@ -347,8 +360,8 @@ StorageService.prototype.upload.http = StorageService.prototype.download.shared = true; StorageService.prototype.download.accepts = [ - {arg: 'container', type: 'string', 'http': {source: 'path'}}, - {arg: 'file', type: 'string', 'http': {source: 'path'}}, + {arg: 'container', type: 'string', required: true, 'http': {source: 'path'}}, + {arg: 'file', type: 'string', required: true, 'http': {source: 'path'}}, {arg: 'req', type: 'object', 'http': {source: 'req'}}, {arg: 'res', type: 'object', 'http': {source: 'res'}}, ]; diff --git a/package.json b/package.json index b8bfec5..bed17b8 100755 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "engines": { "node": ">=4" }, - "version": "3.2.0", + "version": "3.3.1", "main": "index.js", "scripts": { "lint": "eslint .", diff --git a/test/images/album1/.gitignore b/test/images/album1/.gitignore index 9084277..ea550c4 100644 --- a/test/images/album1/.gitignore +++ b/test/images/album1/.gitignore @@ -1,3 +1,4 @@ test.jpg image-*.jpg -customimagefield_test.jpg \ No newline at end of file +customimagefield_test.jpg +customimagefield1_test.jpg diff --git a/test/upload-download.test.js b/test/upload-download.test.js index 18c041b..7fb6025 100644 --- a/test/upload-download.test.js +++ b/test/upload-download.test.js @@ -32,6 +32,23 @@ app.post('/custom/upload', function(req, res, next) { }); }); +// custom route with renamer +app.post('/custom/uploadWithContainer', function(req, res, next) { + var options = { + getFilename: function(file, req, res) { + return file.field + '_' + file.name; + }, + }; + ds.connector.upload('album1', req, res, options, function(err, result) { + if (!err) { + res.setHeader('Content-Type', 'application/json'); + res.status(200).send({result: result}); + } else { + res.status(500).send(err); + } + }); +}); + // expose a rest api app.use(loopback.rest()); @@ -420,4 +437,20 @@ describe('storage service', function() { done(); }); }); + + it('should upload a file with container param', function(done) { + request('http://localhost:' + app.get('port')) + .post('/custom/uploadWithContainer') + .attach('customimagefield1', path.join(__dirname, './fixtures/test.jpg')) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200, function(err, res) { + assert.deepEqual(res.body, {'result': {'files': {'customimagefield1': [ + {'container': 'album1', 'name': 'customimagefield1_test.jpg', + 'originalFilename': 'test.jpg', 'type': 'image/jpeg', + 'field': 'customimagefield1', 'size': 60475}, + ]}, 'fields': {}}}); + done(); + }); + }); });