From 0180ac7b507c24cd2ed088cfa3dd37fec12e0e1a Mon Sep 17 00:00:00 2001 From: Kevin Delisle Date: Wed, 23 Aug 2017 08:27:24 -0400 Subject: [PATCH 1/8] Add stalebot configuration --- .github/stale.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/stale.yml 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. From 7cf4d883ef02dd6096e7f42aa5a024bcb45bc6b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Wed, 30 Aug 2017 15:09:02 +0200 Subject: [PATCH 2/8] Mark HTTP path parameters as required Per Swagger spec 2.0, parameters coming from the path must be marked as required. Before this change, the Swagger spec produced by loopback-swagger was not valid because path parameters were optional. Note that this commit does not fix the problem of the "uploaded" method which does not have any swagger-supported parameters now, and therefore the swagger spec will remain invalid (but with less errors). --- lib/storage-service.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/storage-service.js b/lib/storage-service.js index 021e16a..322b518 100644 --- a/lib/storage-service.js +++ b/lib/storage-service.js @@ -282,7 +282,7 @@ StorageService.prototype.getContainers.http = StorageService.prototype.getContainer.shared = true; StorageService.prototype.getContainer.accepts = [ - {arg: 'container', type: 'string'}, + {arg: 'container', type: 'string', required: true}, ]; StorageService.prototype.getContainer.returns = { arg: 'container', @@ -312,7 +312,7 @@ StorageService.prototype.destroyContainer.http = StorageService.prototype.getFiles.shared = true; StorageService.prototype.getFiles.accepts = [ - {arg: 'container', type: 'string'}, + {arg: 'container', type: 'string', required: true}, ]; StorageService.prototype.getFiles.returns = {arg: 'files', type: 'array', root: true}; StorageService.prototype.getFiles.http = @@ -320,8 +320,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}, + {arg: 'file', type: 'string', required: true}, ]; StorageService.prototype.getFile.returns = {arg: 'file', type: 'object', root: true}; StorageService.prototype.getFile.http = @@ -329,8 +329,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}, + {arg: 'file', type: 'string', required: true}, ]; StorageService.prototype.removeFile.returns = {}; StorageService.prototype.removeFile.http = @@ -347,8 +347,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'}}, ]; From 03a57672ea9c8305c53fcc2fe4f2eb36d957b16f Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 30 Aug 2017 08:30:15 -0700 Subject: [PATCH 3/8] 3.3.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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) --- CHANGES.md | 18 ++++++++++++++++++ package.json | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index d7ba551..eb13fa5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,21 @@ +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/package.json b/package.json index 90739b5..fa11bfd 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "engines": { "node": ">=4" }, - "version": "3.2.0", + "version": "3.3.0", "main": "index.js", "scripts": { "lint": "eslint .", From 47d555798cf189577326369a3355e4068287f5d3 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 30 Aug 2017 13:13:25 -0700 Subject: [PATCH 4/8] Declare container parameter for swagger spec Without this change, generated Swagger spec for the upload operation does not have `container` parameter even it's a variable on the path. As a result, the sepc fails validations. An optional `container` is added to the remote method. Conditional check is added to ensure backward compatibility. --- lib/storage-service.js | 29 +++++++++++++++++++++-------- test/images/album1/.gitignore | 3 ++- test/upload-download.test.js | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/lib/storage-service.js b/lib/storage-service.js index 322b518..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', required: true}, + {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', required: true}, + {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', required: true}, - {arg: 'file', type: 'string', required: true}, + {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', required: true}, - {arg: 'file', type: 'string', required: true}, + {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'}}, ]; 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(); + }); + }); }); From 3508b036d8e37288ddde752fd9e3bf3e48d3b371 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 30 Aug 2017 13:54:54 -0700 Subject: [PATCH 5/8] 3.3.1 * Declare container parameter for swagger spec (Raymond Feng) --- CHANGES.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index eb13fa5..61912bb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,9 @@ +2017-08-30, Version 3.3.1 +========================= + + * Declare container parameter for swagger spec (Raymond Feng) + + 2017-08-30, Version 3.3.0 ========================= diff --git a/package.json b/package.json index fa11bfd..0ddd90a 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "engines": { "node": ">=4" }, - "version": "3.3.0", + "version": "3.3.1", "main": "index.js", "scripts": { "lint": "eslint .", From c2d5a5429e43c761ebae15f0e392be4fc6a4a854 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Tue, 12 Sep 2017 10:11:20 -0700 Subject: [PATCH 6/8] Update README.md Add Google Cloud support to README --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ea8a88e..cdb082b 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/). ## Examples From e1109e39dd1260d1c4030bc972d5cd0039a7bd57 Mon Sep 17 00:00:00 2001 From: Timo Wolf Date: Sun, 8 Oct 2017 18:00:46 +0200 Subject: [PATCH 7/8] add AWS S3 options for server side encryption Now, the AWS S3 options for server side encryptions are passed to the upload handler. Thus, the client can specify the AWS options and use AWS Server Side Encryption. --- lib/storage-handler.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/storage-handler.js b/lib/storage-handler.js index cc7a444..a84ff50 100644 --- a/lib/storage-handler.js +++ b/lib/storage-handler.js @@ -141,6 +141,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) { From ac4a14a55a2ba61367a28f3aff86e6f49a9070f6 Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Fri, 10 Nov 2017 17:54:11 -0500 Subject: [PATCH 8/8] chore: update license --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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.