Compare commits

...

66 Commits

Author SHA1 Message Date
Miroslav Bajtoš 3b1b432b13
3.7.0
* Update LTS status in README (Miroslav Bajtoš)
 * chore: update copyright year (Diana Lau)
 * Update README.md (Shaun)
 * chore: improve issue and PR templates (Nora)
 * chore: drop Node.js 6 and add Node.js 12 to travis (Nora)
2020-03-06 17:21:12 +01:00
Miroslav Bajtoš 11a3caf2d3
Merge pull request #288 from strongloop/feat/maintenance-lts
Update LTS status in README
2020-03-06 17:19:31 +01:00
Miroslav Bajtoš 479800e419
Update LTS status in README 2020-03-06 10:07:12 +01:00
Agnes Lin 3ada3ddf4e
Merge pull request #287 from strongloop/copyright
chore: update copyright year
2020-02-10 14:08:11 -05:00
Diana Lau 5668fe31f4 chore: update copyright year 2020-02-10 13:57:12 -05:00
Diana Lau c8f3b24141
Merge pull request #286 from syntheticgoo/patch-1
Update README.md
2020-01-29 22:08:03 -05:00
Shaun fd1a2527f2
Update README.md 2020-01-29 19:21:42 +00:00
Nora d020a178c4
Merge pull request #281 from strongloop/chore/improve-issue-templates
chore: improve issue and PR templates
2019-11-21 09:48:27 -05:00
Nora 6a18af7774 chore: improve issue and PR templates 2019-11-19 15:42:35 -05:00
Nora fee0855137
Merge pull request #282 from strongloop/drop-node-6
chore: drop Node.js 6 and add Node.js 12 to travis
2019-11-19 15:42:04 -05:00
Nora 02569bd3b5 chore: drop Node.js 6 and add Node.js 12 to travis 2019-11-19 15:19:39 -05:00
Diana Lau 60963c4415 3.6.3
* Rannig from other paths. Property files to array. (Diego A. Zapata Häntsch)
2019-07-25 13:37:24 -04:00
Diana Lau 963f334ee7
Merge pull request #279 from diegoazh/master
Fixes in PRs 252 and 242
2019-07-20 17:32:08 -04:00
Diego A. Zapata Häntsch 9205c9f079 Rannig from other paths. Property files to array. 2019-07-20 01:27:37 -03:00
Diana Lau 5608021675 3.6.2
* chore: update CODEOWNERS (Diana Lau)
 * Upgrade pkgcloud version (Diego A. Zapata Häntsch)
2019-07-12 11:30:47 -04:00
Diana Lau 351c3e8ebb
Merge pull request #277 from strongloop/codeowner
chore: update CODEOWNERS
2019-07-10 15:09:47 -04:00
Diana Lau 5c10529445 chore: update CODEOWNERS 2019-07-10 15:04:41 -04:00
Diana Lau 6ff4f2b234
Merge pull request #276 from diegoazh/master
Upgrade pkgcloud version
2019-07-10 14:56:40 -04:00
Diego A. Zapata Häntsch 3516cf642d Upgrade pkgcloud version
This upgrade fix the bug uploading files to google cloud storage.
This change solves the problem explained in issue #273.
2019-07-09 20:48:49 -03:00
Diana Lau 609b63c474 3.6.1
* update pkgcloud and use version 2.x (Anis)
 * chore: update copyrights years (Agnes Lin)
2019-05-23 15:56:37 -04:00
Diana Lau d2352bdc4c
Merge pull request #271 from rachedanis/update-pkgcloud-version
update pkgcloud and use version 2.x
2019-05-23 15:51:31 -04:00
Anis 1dc5a4dc81 update pkgcloud and use version 2.x 2019-05-14 10:33:15 +02:00
Agnes Lin a3c8509adf
Merge pull request #270 from strongloop/copyrights
chore: update copyrights years
2019-05-07 14:46:34 -04:00
Agnes Lin 6f8e4284f5 chore: update copyrights years 2019-05-07 09:41:29 -04:00
Diana Lau d8b7ebcd14 3.6.0
* Pass through AWS/S3 specific options (Alex Owen)
 * add support to promise (Matteo Padovano)
 * style: fix linting (virkt25)
 * {download,upload}Stream: removed callback from doc (Youcef Mammar)
2019-03-28 09:33:56 -04:00
Diana Lau 4eaec69fce
Merge pull request #267 from AlexOwen/master
Pass through AWS/S3 specific options
2019-03-28 09:31:37 -04:00
Alex Owen 30e8ee0a09 Pass through AWS/S3 specific options 2019-03-28 08:52:48 +00:00
Raymond Feng 00092b5129
Merge pull request #260 from mrbatista/feat/promise
add support to promise
2019-03-20 07:34:48 -07:00
Matteo Padovano 83a28273d4 add support to promise 2019-01-12 00:23:37 +01:00
Taranveer Virk e76d571a8c
Merge pull request #257 from strongloop/fix-linting
style: fix linting
2018-08-23 22:40:08 -04:00
virkt25 47002dceff style: fix linting 2018-08-23 22:09:45 -04:00
Taranveer Virk 7b4cf0b236
Merge pull request #255 from tkrugg/downloadStream
{download,upload}Stream: removed callback from doc
2018-08-23 21:57:32 -04:00
Youcef Mammar 0562cd03d4 {download,upload}Stream: removed callback from doc
closes #254
2018-08-23 06:27:33 +02:00
virkt25 21d6b0d60c 3.5.0
* [WebFM] cs/pl/ru translation (candytangnb)
2018-07-09 20:35:20 -04:00
Diana Lau a03ff9fe42
Merge pull request #250 from candytangnb/webfm-0629-000545-cs,pl,ru-translation
[WebFM] cs/pl/ru translation Check-in by YI TANG (tangyinb@cn.ibm.com)
2018-07-02 19:34:00 -04:00
candytangnb 726310edcf [WebFM] cs/pl/ru translation
cs/pl/ru translation check-in by YI TANG (tangyinb@cn.ibm.com) using
WebFM tool.
2018-06-29 00:05:45 -04:00
Taranveer Virk 7d58ef4758 3.4.0
* added missing tests (Cory Gottschalk)
 * Updated "getFile" to send a 404 for ENOENT errors (Cory Gottschalk)
 * chore: update node support and versions (Taranveer Virk)
 * Add some debug strings to handler and service (Remi Beges)
 * chore: update license (Diana Lau)
 * add AWS S3 options for server side encryption (Timo Wolf)
 * Update README.md (Rand McKinney)
2018-06-20 15:58:47 -04:00
Taranveer Virk 7fec4b8932
Merge pull request #244 from cory-newleaf/patch-1
Updated "getFile" to send 404 for ENOENT errors
2018-06-20 13:38:12 -04:00
Cory Gottschalk db669b1543 added missing tests
Added 2 tests to validate the error responses when a file is not found
2018-06-14 15:19:53 -07:00
Cory Gottschalk 970ecc7901 Updated "getFile" to send a 404 for ENOENT errors
getFile sends a 500 error response when the requested file cannot be
found, but the status code probably should be 404. This change sets
the status of the error to 404 when the error code is "ENOENT".
2018-06-14 15:19:53 -07:00
Taranveer Virk 537e1158d4
Merge pull request #248 from strongloop/drop-node
chore: update node support and versions
2018-06-14 15:47:19 -04:00
Taranveer Virk 1854769986 chore: update node support and versions 2018-06-14 15:42:15 -04:00
Biniam Admikew eb11b4d660
Merge pull request #229 from Overdrivr/implement-debug-strings
Implement debug strings
2018-02-12 16:56:33 -05:00
Remi Beges c105f8a9ae Add some debug strings to handler and service 2018-01-30 10:29:59 -05:00
Diana Lau bcd6965b8f
Merge pull request #230 from strongloop/license
chore: update license
2017-11-10 21:55:18 -05:00
Diana Lau ac4a14a55a chore: update license 2017-11-10 17:54:11 -05:00
Raymond Feng 1e71f7d8dd Merge pull request #228 from timowolf/master
feat(aws-options): add AWS S3 options for server side encryption
2017-10-09 09:07:48 -07:00
Timo Wolf e1109e39dd 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.
2017-10-09 09:51:20 +02:00
Rand McKinney 91f6268c87 Merge pull request #222 from strongloop/document-google-cloud-support
Update README.md to add Google Cloud
2017-09-19 13:56:03 -07:00
Rand McKinney c2d5a5429e Update README.md
Add Google Cloud support to README
2017-09-12 10:11:20 -07:00
Raymond Feng 3508b036d8 3.3.1
* Declare container parameter for swagger spec (Raymond Feng)
2017-08-30 13:54:54 -07:00
Raymond Feng e48a4b1a10 Merge pull request #220 from strongloop/fix-upload-args
Declare container parameter for swagger spec
2017-08-30 13:53:47 -07:00
Raymond Feng 47d555798c 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.
2017-08-30 13:45:19 -07:00
Raymond Feng 03a57672ea 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-08-30 08:30:15 -07:00
Raymond Feng 0ba1595d25 Merge pull request #219 from strongloop/fix/path-parameters
Mark HTTP path parameters as required
2017-08-30 08:29:09 -07:00
Miroslav Bajtoš 7cf4d883ef
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).
2017-08-30 15:09:02 +02:00
Kevin Delisle 0180ac7b50 Add stalebot configuration 2017-08-23 08:27:24 -04:00
Sakib Hasan ea5e9807a4 Create Issue and PR Templates (#218)
* create issue template

* create pr template
2017-08-16 14:27:54 -04:00
Allen Boone 007b9d5eeb Merge pull request #216 from strongloop/piiReturnQ32017
Update translated strings Q3 2017
2017-08-11 14:18:08 -04:00
Allen Boone 4d37716b58 Update translated strings Q3 2017 2017-08-10 15:27:31 -04:00
Diana Lau 361388bb1d Merge pull request #212 from strongloop/add-codeowner
Add CODEOWNERS file
2017-08-02 17:36:35 -04:00
Diana Lau d8b76ec0f6 Add CODEOWNER file 2017-08-02 16:24:32 -04:00
Diana Lau 4b87a28c15 Merge pull request #214 from strongloop/translate
update messages.json
2017-08-02 08:35:27 -04:00
Diana Lau ab71df42cc update messages.json 2017-08-01 21:51:04 -04:00
Diana Lau 4572305831 Merge pull request #213 from strongloop/fix-ci
Fix CI by add .travis.yml
2017-07-31 22:17:46 -04:00
Diana Lau 8189c5c991 add .travis.yml 2017-07-31 22:08:41 -04:00
43 changed files with 985 additions and 215 deletions

View File

@ -1,29 +0,0 @@
<!--
Questions:
https://groups.google.com/forum/#!forum/loopbackjs
https://gitter.im/strongloop/loopback
Immediate support:
https://strongloop.com/api-connect-faqs/
https://strongloop.com/node-js/subscription-plans/
-->
# Description/Steps to reproduce
<!--
If feature: A description of the feature
If bug: Steps to reproduce + link to sample repo
-->
# Expected result
<!--
Also include actual results if bug
-->
# Additional information
<!--
Copy+paste the output of these two commands:
node -e 'console.log(process.platform, process.arch, process.versions.node)'
npm ls --prod --depth 0 | grep loopback
-->

50
.github/ISSUE_TEMPLATE/Bug_report.md vendored Normal file
View File

@ -0,0 +1,50 @@
---
name: Bug report
about: Create a report to help us improve
labels: bug
---
<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨
HELP US HELP YOU, PLEASE
- Do a quick search to avoid duplicate issues
- Provide as much information as possible (reproduction sandbox, use case for features, etc.)
- Consider using a more suitable venue for questions such as Stack Overflow, Gitter, etc.
Please fill in the *entire* template below.
-->
## Steps to reproduce
<!-- Describe how to reproduce the issue -->
## Current Behavior
<!-- Describe the observed result -->
## Expected Behavior
<!-- Describe what did you expect instead, what is the desired outcome? -->
## Link to reproduction sandbox
<!--
See https://loopback.io/doc/en/contrib/Reporting-issues.html#loopback-3x-bugs
Note: Failure to provide a sandbox application for reproduction purposes will result in the issue being closed.
-->
## Additional information
<!--
Copy+paste the output of these two commands:
node -e 'console.log(process.platform, process.arch, process.versions.node)'
npm ls --prod --depth 0 | grep loopback
-->
## Related Issues
<!-- Did you find other bugs that looked similar? -->
_See [Reporting Issues](http://loopback.io/doc/en/contrib/Reporting-issues.html) for more tips on writing good issues_

View File

@ -0,0 +1,25 @@
---
name: Feature request
about: Suggest an idea for this project
labels: feature
---
## Suggestion
<!-- A summary of what you'd like to see added or changed -->
## Use Cases
<!--
What do you want to use this for?
What shortcomings exist with current approaches?
-->
## Examples
<!-- Show how this would be used and what the behavior would be -->
## Acceptance criteria
TBD - will be filled by the team.

27
.github/ISSUE_TEMPLATE/Question.md vendored Normal file
View File

@ -0,0 +1,27 @@
---
name: Question
about: The issue tracker is not for questions. Please use Stack Overflow or other resources for help.
labels: question
---
<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨
THE ISSUE TRACKER IS NOT FOR QUESTIONS.
DO NOT CREATE A NEW ISSUE TO ASK A QUESTION.
Please use one of the following resources for help:
**Questions**
- https://stackoverflow.com/tags/loopbackjs
- https://groups.google.com/forum/#!forum/loopbackjs
- https://gitter.im/strongloop/loopback
**Immediate support**
- https://strongloop.com/api-connect-faqs/
- https://strongloop.com/node-js/subscription-plans/
-->

11
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Report a security vulnerability
url: https://loopback.io/doc/en/contrib/Reporting-issues.html#security-issues
about: Do not report security vulnerabilities using GitHub issues. Please send an email to `reachsl@us.ibm.com` instead.
- name: Get help on StackOverflow
url: https://stackoverflow.com/tags/loopbackjs
about: Please ask and answer questions on StackOverflow.
- name: Join our mailing list
url: https://groups.google.com/forum/#!forum/loopbackjs
about: You can also post your question to our mailing list.

View File

@ -1,24 +1,18 @@
### Description
#### Related issues
<!--
Please use the following link syntaxes:
Please provide a high-level description of the changes made by your pull request.
- #49 (to reference issues in the current repository)
- strongloop/loopback#49 (to reference issues in another repository)
Include references to all related GitHub issues and other pull requests, for example:
Fixes #123
Implements #254
See also #23
-->
- None
## Checklist
### Checklist
<!--
Please mark your choice with an "x" (i.e. [x], see
https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments)
-->
👉 [Read and sign the CLA (Contributor License Agreement)](https://cla.strongloop.com/agreements/strongloop/loopback-component-storage) 👈
- [ ] `npm test` passes on your machine
- [ ] New tests added or existing tests modified to cover all changes
- [ ] Code conforms with the [style
guide](http://loopback.io/doc/en/contrib/style-guide.html)
- [ ] Code conforms with the [style guide](https://loopback.io/doc/en/contrib/style-guide-es6.html)
- [ ] Commit messages are following our [guidelines](https://loopback.io/doc/en/contrib/git-commit-messages.html)

23
.github/stale.yml vendored Normal file
View File

@ -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.

1
.npmrc Normal file
View File

@ -0,0 +1 @@
package-lock=false

5
.travis.yml Normal file
View File

@ -0,0 +1,5 @@
language: node_js
node_js:
- "8"
- "10"
- "12"

View File

@ -1,3 +1,99 @@
2020-03-06, Version 3.7.0
=========================
* Update LTS status in README (Miroslav Bajtoš)
* chore: update copyright year (Diana Lau)
* Update README.md (Shaun)
* chore: improve issue and PR templates (Nora)
* chore: drop Node.js 6 and add Node.js 12 to travis (Nora)
2019-07-25, Version 3.6.3
=========================
* Rannig from other paths. Property files to array. (Diego A. Zapata Häntsch)
2019-07-12, Version 3.6.2
=========================
* chore: update CODEOWNERS (Diana Lau)
* Upgrade pkgcloud version (Diego A. Zapata Häntsch)
2019-05-23, Version 3.6.1
=========================
* update pkgcloud and use version 2.x (Anis)
* chore: update copyrights years (Agnes Lin)
2019-03-28, Version 3.6.0
=========================
* Pass through AWS/S3 specific options (Alex Owen)
* add support to promise (Matteo Padovano)
* style: fix linting (virkt25)
* {download,upload}Stream: removed callback from doc (Youcef Mammar)
2018-07-09, Version 3.5.0
=========================
* [WebFM] cs/pl/ru translation (candytangnb)
2018-06-20, Version 3.4.0
=========================
* added missing tests (Cory Gottschalk)
* Updated "getFile" to send a 404 for ENOENT errors (Cory Gottschalk)
* chore: update node support and versions (Taranveer Virk)
* Add some debug strings to handler and service (Remi Beges)
* chore: update license (Diana Lau)
* add AWS S3 options for server side encryption (Timo Wolf)
* Update README.md (Rand McKinney)
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
=========================

9
CODEOWNERS Normal file
View File

@ -0,0 +1,9 @@
# Lines starting with '#' are comments.
# Each line is a file pattern followed by one or more owners,
# the last matching pattern has the most precedence.
# Alumni members
# @kjdelisle @loay @ssh24 @virkt25
# Core team members from IBM
* @jannyHou @b-admike @dhmlau @hacksparrow

View File

@ -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.

View File

@ -1,21 +1,43 @@
# LoopBack Storage Component
**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 3 is in Maintenance LTS mode, only critical bugs and critical
security fixes will be provided. (See
[Module Long Term Support Policy](#module-long-term-support-policy) below.)**
LoopBack storage component provides Node.js and REST APIs to manage binary contents
We urge all LoopBack 3 users to migrate their applications to LoopBack 4 as
soon as possible. Refer to our
[Migration Guide](https://loopback.io/doc/en/lb4/migration-overview.html)
for more information on how to upgrade.
## Overview
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 Documentation](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
See https://github.com/strongloop/loopback-example-storage.
## Module Long Term Support Policy
This module adopts the [
Module Long Term Support (LTS)](http://github.com/CloudNativeJS/ModuleLTS) policy,
with the following End Of Life (EOL) dates:
| Version | Status | Published | EOL |
| ------- | --------------- | --------- | -------- |
| 3.x | Maintenance LTS | Dec 2016 | Dec 2020 |
Learn more about our LTS plan in [docs](https://loopback.io/doc/en/contrib/Long-term-support.html).

View File

@ -1,7 +1,8 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback-component-storage
// This file is licensed under the Artistic License 2.0.
// License text available at https://opensource.org/licenses/Artistic-2.0
'use strict';
var SG = require('strong-globalize');

10
intl/cs/messages.json Normal file
View File

@ -0,0 +1,10 @@
{
"2eb418c4dc7f7a3e989bb71a8f5388d7": "{{FileSystemProvider}}: Cesta neexistuje: {0}",
"45c1c136e750c62179d75a1c99151281": "{{maxFileSize}} překročena, přijato {0} bajtů dat pole (maximum je {1})",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: Neplatný název: {0}",
"78f6f36e8300e15cff778496fb1dd178": "{{contentType}} \"{0}\" není povolen (musí být v [{1}])",
"b8a9e184534171cf66caf58d29ad76f5": "{{maxFieldsSize}} překročena, přijato {0} bajtů dat pole",
"c9fb0aba850059a14f4ed5e045e4ec3e": "Neplatný název: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: Neplatný adresář: {0}"
}

View File

@ -1,11 +1,10 @@
{
"2eb418c4dc7f7a3e989bb71a8f5388d7": "{{FileSystemProvider}}: Pfad ist nicht vorhanden: {0}",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: Ungültiger Name: {0}",
"95065f7f9499f75f49e3714aa4e2031d": "{{FileSystemProvider}}: Ungültiger Name: ",
"c9fb0aba850059a14f4ed5e045e4ec3e": "Ungültiger Name: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: Ungültiges Verzeichnis: {0}",
"45c1c136e750c62179d75a1c99151281": "{{maxFileSize}} überschritten, {0} Byte Felddaten erhalten (Maximum ist {1})",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: Ungültiger Name: {0}",
"78f6f36e8300e15cff778496fb1dd178": "{{contentType}} \"{0}\" ist nicht zulässig (muss in [{1}] sein)",
"b8a9e184534171cf66caf58d29ad76f5": "{{maxFieldsSize}} überschritten, {0} Byte Felddaten erhalten"
"b8a9e184534171cf66caf58d29ad76f5": "{{maxFieldsSize}} überschritten, {0} Byte Felddaten erhalten",
"c9fb0aba850059a14f4ed5e045e4ec3e": "Ungültiger Name: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: Ungültiges Verzeichnis: {0}"
}

View File

@ -1,10 +1,9 @@
{
"2eb418c4dc7f7a3e989bb71a8f5388d7": "{{FileSystemProvider}}: Path does not exist: {0}",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: Invalid name: {0}",
"95065f7f9499f75f49e3714aa4e2031d": "{{FileSystemProvider}}: Invalid name: ",
"c9fb0aba850059a14f4ed5e045e4ec3e": "Invalid name: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: Invalid directory: {0}",
"45c1c136e750c62179d75a1c99151281": "{{maxFileSize}} exceeded, received {0} bytes of field data (max is {1})",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: Invalid name: {0}",
"78f6f36e8300e15cff778496fb1dd178": "{{contentType}} \"{0}\" is not allowed (Must be in [{1}])",
"b8a9e184534171cf66caf58d29ad76f5": "{{maxFieldsSize}} exceeded, received {0} bytes of field data"
"b8a9e184534171cf66caf58d29ad76f5": "{{maxFieldsSize}} exceeded, received {0} bytes of field data",
"c9fb0aba850059a14f4ed5e045e4ec3e": "Invalid name: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: Invalid directory: {0}"
}

View File

@ -1,11 +1,10 @@
{
"2eb418c4dc7f7a3e989bb71a8f5388d7": "{{FileSystemProvider}}: La vía de acceso no existe: {0}",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: Nombre no válido: {0}",
"95065f7f9499f75f49e3714aa4e2031d": "{{FileSystemProvider}}: Nombre no válido: ",
"c9fb0aba850059a14f4ed5e045e4ec3e": "Nombre no válido: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: Directorio no válido: {0}",
"45c1c136e750c62179d75a1c99151281": "Se ha superado {{maxFileSize}}, se han recibido {0} bytes de datos de campo (el máximo es {1})",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: Nombre no válido: {0}",
"78f6f36e8300e15cff778496fb1dd178": "{{contentType}} \"{0}\" no está permitido (Debe estar en [{1}])",
"b8a9e184534171cf66caf58d29ad76f5": "Se ha superado {{maxFieldsSize}}, se han recibido {0} bytes de datos de campo"
"b8a9e184534171cf66caf58d29ad76f5": "Se ha superado {{maxFieldsSize}}, se han recibido {0} bytes de datos de campo",
"c9fb0aba850059a14f4ed5e045e4ec3e": "Nombre no válido: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: Directorio no válido: {0}"
}

View File

@ -1,11 +1,10 @@
{
"2eb418c4dc7f7a3e989bb71a8f5388d7": "{{FileSystemProvider}} : Le chemin n'existe pas : {0}",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}} : Nom invalide : {0}",
"95065f7f9499f75f49e3714aa4e2031d": "{{FileSystemProvider}} : Nom invalide : ",
"c9fb0aba850059a14f4ed5e045e4ec3e": "Nom invalide : {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}} : Répertoire non valide : {0}",
"45c1c136e750c62179d75a1c99151281": "{{maxFileSize}} dépassé, réception de {0} octets de données de zone (max est {1})",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}} : Nom invalide : {0}",
"78f6f36e8300e15cff778496fb1dd178": "{{contentType}} \"{0}\" n'est pas autorisé (Doit être dans [{1}])",
"b8a9e184534171cf66caf58d29ad76f5": "{{maxFieldsSize}} dépassé, réception de {0} octets de données de zone"
"b8a9e184534171cf66caf58d29ad76f5": "{{maxFieldsSize}} dépassé, réception de {0} octets de données de zone",
"c9fb0aba850059a14f4ed5e045e4ec3e": "Nom invalide : {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}} : Répertoire non valide : {0}"
}

View File

@ -1,11 +1,10 @@
{
"2eb418c4dc7f7a3e989bb71a8f5388d7": "{{FileSystemProvider}}: Il percorso non esiste: {0}",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: Nome non valido: {0}",
"95065f7f9499f75f49e3714aa4e2031d": "{{FileSystemProvider}}: Nome non valido: ",
"c9fb0aba850059a14f4ed5e045e4ec3e": "Nome non valido: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: Directory non valida: {0}",
"45c1c136e750c62179d75a1c99151281": "{{maxFileSize}} superata, sono stati ricevuti {0} byte di dati del campo (il valore massimo è {1})",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: Nome non valido: {0}",
"78f6f36e8300e15cff778496fb1dd178": "{{contentType}} \"{0}\" non è consentito (deve essere in [{1}])",
"b8a9e184534171cf66caf58d29ad76f5": "{{maxFieldsSize}} superata, sono stati ricevuti {0} byte di dati del campo"
"b8a9e184534171cf66caf58d29ad76f5": "{{maxFieldsSize}} superata, sono stati ricevuti {0} byte di dati del campo",
"c9fb0aba850059a14f4ed5e045e4ec3e": "Nome non valido: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: Directory non valida: {0}"
}

View File

@ -1,11 +1,10 @@
{
"2eb418c4dc7f7a3e989bb71a8f5388d7": "{{FileSystemProvider}}: パスが存在しません: {0}",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: 名前が無効です: {0}",
"95065f7f9499f75f49e3714aa4e2031d": "{{FileSystemProvider}}: 名前が無効です: ",
"c9fb0aba850059a14f4ed5e045e4ec3e": "名前が無効です: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: ディレクトリーが無効です: {0}",
"45c1c136e750c62179d75a1c99151281": "{{maxFileSize}} を超過しました。{0} バイトのフィールド・データを受信しました (最大 {1})",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: 名前が無効です: {0}",
"78f6f36e8300e15cff778496fb1dd178": "{{contentType}} \"{0}\" は許可されていません ([{1}] でなければなりません)",
"b8a9e184534171cf66caf58d29ad76f5": "{{maxFieldsSize}} を超過しました。{0} バイトのフィールド・データを受信しました"
"b8a9e184534171cf66caf58d29ad76f5": "{{maxFieldsSize}} を超過しました。{0} バイトのフィールド・データを受信しました",
"c9fb0aba850059a14f4ed5e045e4ec3e": "名前が無効です: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: ディレクトリーが無効です: {0}"
}

View File

@ -1,11 +1,10 @@
{
"2eb418c4dc7f7a3e989bb71a8f5388d7": "{{FileSystemProvider}}: 경로가 존재하지 않음: {0}",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: 올바르지 않은 이름: {0}",
"95065f7f9499f75f49e3714aa4e2031d": "{{FileSystemProvider}}: 올바르지 않은 이름: ",
"c9fb0aba850059a14f4ed5e045e4ec3e": "올바르지 않은 이름: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: 올바르지 않은 디렉토리: {0}",
"45c1c136e750c62179d75a1c99151281": "{{maxFileSize}}이(가) 초과되었습니다. {0}바이트의 필드 데이터를 받았습니다(최대값 {1}).",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: 올바르지 않은 이름: {0}",
"78f6f36e8300e15cff778496fb1dd178": "{{contentType}} \"{0}\"이(가) 허용되지 않습니다([{1}]에 있어야 함).",
"b8a9e184534171cf66caf58d29ad76f5": "{{maxFieldsSize}}이(가) 초과되었습니다. {0}바이트의 필드 데이터를 받았습니다."
"b8a9e184534171cf66caf58d29ad76f5": "{{maxFieldsSize}}이(가) 초과되었습니다. {0}바이트의 필드 데이터를 받았습니다.",
"c9fb0aba850059a14f4ed5e045e4ec3e": "올바르지 않은 이름: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: 올바르지 않은 디렉토리: {0}"
}

View File

@ -1,11 +1,10 @@
{
"2eb418c4dc7f7a3e989bb71a8f5388d7": "{{FileSystemProvider}}: Pad bestaat niet: {0}",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: Ongeldige naam: {0}",
"95065f7f9499f75f49e3714aa4e2031d": "{{FileSystemProvider}}Ongeldige naam: ",
"c9fb0aba850059a14f4ed5e045e4ec3e": "Ongeldige naam: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: Ongeldige directory: {0}",
"45c1c136e750c62179d75a1c99151281": "{{maxFileSize}} overschreden, {0} bytes aan veldgegevens ontvangen (max is {1})",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: Ongeldige naam: {0}",
"78f6f36e8300e15cff778496fb1dd178": "{{contentType}} \"{0}\" is niet toegestaan (moet vallen in [{1}])",
"b8a9e184534171cf66caf58d29ad76f5": "{{maxFieldsSize}} overschreden, {0} bytes aan veldgegevens ontvangen"
"b8a9e184534171cf66caf58d29ad76f5": "{{maxFieldsSize}} overschreden, {0} bytes aan veldgegevens ontvangen",
"c9fb0aba850059a14f4ed5e045e4ec3e": "Ongeldige naam: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: Ongeldige directory: {0}"
}

10
intl/pl/messages.json Normal file
View File

@ -0,0 +1,10 @@
{
"2eb418c4dc7f7a3e989bb71a8f5388d7": "{{FileSystemProvider}}: Ścieżka nie istnieje: {0}",
"45c1c136e750c62179d75a1c99151281": "Przekroczono {{maxFileSize}}, odebrano {0} B danych pola (maksymalnie {1})",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: niepoprawna nazwa: {0}",
"78f6f36e8300e15cff778496fb1dd178": "Element {{contentType}} \"{0}\" jest niedozwolony (musi być w [{1}])",
"b8a9e184534171cf66caf58d29ad76f5": "Przekroczono {{maxFieldsSize}}, odebrano {0} B danych pola",
"c9fb0aba850059a14f4ed5e045e4ec3e": "Niepoprawna nazwa: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: niepoprawny katalog: {0}"
}

View File

@ -1,11 +1,10 @@
{
"2eb418c4dc7f7a3e989bb71a8f5388d7": "{{FileSystemProvider}}: Caminho não existe: {0}",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: Nome inválido: {0}",
"95065f7f9499f75f49e3714aa4e2031d": "{{FileSystemProvider}}: Nome inválido: ",
"c9fb0aba850059a14f4ed5e045e4ec3e": "Nome inválido: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: Diretório inválido: {0}",
"45c1c136e750c62179d75a1c99151281": "{{maxFileSize}} excedido, recebidos {0} bytes de dados do campo (máximo é {1})",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: Nome inválido: {0}",
"78f6f36e8300e15cff778496fb1dd178": "{{contentType}} \"{0}\" não é permitido (deve estar em [{1}])",
"b8a9e184534171cf66caf58d29ad76f5": "{{maxFieldsSize}} excedido, recebidos {0} bytes de dados do campo"
"b8a9e184534171cf66caf58d29ad76f5": "{{maxFieldsSize}} excedido, recebidos {0} bytes de dados do campo",
"c9fb0aba850059a14f4ed5e045e4ec3e": "Nome inválido: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: Diretório inválido: {0}"
}

10
intl/ru/messages.json Normal file
View File

@ -0,0 +1,10 @@
{
"2eb418c4dc7f7a3e989bb71a8f5388d7": "{{FileSystemProvider}}: путь не существует: {0}",
"45c1c136e750c62179d75a1c99151281": "Превышен {{maxFileSize}}, получено {0} байт данных поля (максимальное значение - {1})",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: недопустимое имя: {0}",
"78f6f36e8300e15cff778496fb1dd178": "{{contentType}} \"{0}\" не разрешен (должен быть в [{1}])",
"b8a9e184534171cf66caf58d29ad76f5": "Превышено значение {{maxFieldsSize}}, получено {0} байт данных поля",
"c9fb0aba850059a14f4ed5e045e4ec3e": "Недопустимое имя: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: недопустимый каталог: {0}"
}

View File

@ -1,11 +1,10 @@
{
"2eb418c4dc7f7a3e989bb71a8f5388d7": "{{FileSystemProvider}}: Yol yok: {0}",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: Geçersiz ad: {0}",
"95065f7f9499f75f49e3714aa4e2031d": "{{FileSystemProvider}}: Geçersiz ad: ",
"c9fb0aba850059a14f4ed5e045e4ec3e": "Geçersiz ad: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: Geçersiz dizin: {0}",
"45c1c136e750c62179d75a1c99151281": "{{maxFileSize}} aşıldı, {0} baytlık alan verisi alındı (üst sınır {1})",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}: Geçersiz ad: {0}",
"78f6f36e8300e15cff778496fb1dd178": "{{contentType}} \"{0}\" kullanılamaz ([{1}] içinde olmalıdır)",
"b8a9e184534171cf66caf58d29ad76f5": "{{maxFieldsSize}} aşıldı, {0} baytlık alan verisi alındı"
"b8a9e184534171cf66caf58d29ad76f5": "{{maxFieldsSize}} aşıldı, {0} baytlık alan verisi alındı",
"c9fb0aba850059a14f4ed5e045e4ec3e": "Geçersiz ad: {0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}: Geçersiz dizin: {0}"
}

View File

@ -1,11 +1,10 @@
{
"2eb418c4dc7f7a3e989bb71a8f5388d7": "{{FileSystemProvider}}:路径不存在:{0}",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}:无效名称:{0}",
"95065f7f9499f75f49e3714aa4e2031d": "{{FileSystemProvider}}:无效名称",
"c9fb0aba850059a14f4ed5e045e4ec3e": "无效名称:{0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}:无效目录:{0}",
"45c1c136e750c62179d75a1c99151281": "已超过 {{maxFileSize}},收到 {0} 字节字段数据(最大为 {1}",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}:无效名称:{0}",
"78f6f36e8300e15cff778496fb1dd178": "不允许 {{contentType}}“{0}”(必须在 [{1}] 中)",
"b8a9e184534171cf66caf58d29ad76f5": "已超过 {{maxFieldsSize}},收到 {0} 字节字段数据"
"b8a9e184534171cf66caf58d29ad76f5": "已超过 {{maxFieldsSize}},收到 {0} 字节字段数据",
"c9fb0aba850059a14f4ed5e045e4ec3e": "无效名称:{0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}:无效目录:{0}"
}

View File

@ -1,11 +1,10 @@
{
"2eb418c4dc7f7a3e989bb71a8f5388d7": "{{FileSystemProvider}}:路徑不存在:{0}",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}:無效名稱:{0}",
"95065f7f9499f75f49e3714aa4e2031d": "{{FileSystemProvider}}:無效名稱:",
"c9fb0aba850059a14f4ed5e045e4ec3e": "無效名稱:{0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}:無效目錄:{0}",
"45c1c136e750c62179d75a1c99151281": "已超出 {{maxFileSize}},收到 {0} 位元組的欄位資料(最大值為 {1}",
"6af59b6408b92f4c6b13a2c9b06379f2": "{{FileSystemProvider}}:無效名稱:{0}",
"78f6f36e8300e15cff778496fb1dd178": "不接受 {{contentType}} \"{0}\"(必須在 [{1}] 中)",
"b8a9e184534171cf66caf58d29ad76f5": "已超出 {{maxFieldsSize}},收到 {0} 位元組的欄位資料"
"b8a9e184534171cf66caf58d29ad76f5": "已超出 {{maxFieldsSize}},收到 {0} 位元組的欄位資料",
"c9fb0aba850059a14f4ed5e045e4ec3e": "無效名稱:{0}",
"f589fe721f4e6fa112d1f66081ed29ac": "{{FileSystemProvider}}:無效目錄:{0}"
}

View File

@ -1,7 +1,8 @@
// Copyright IBM Corp. 2013,2014. All Rights Reserved.
// Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback-component-storage
// This file is licensed under the Artistic License 2.0.
// License text available at https://opensource.org/licenses/Artistic-2.0
'use strict';
var pkgcloud = require('pkgcloud');

View File

@ -1,7 +1,8 @@
// Copyright IBM Corp. 2013,2014. All Rights Reserved.
// Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback-component-storage
// This file is licensed under the Artistic License 2.0.
// License text available at https://opensource.org/licenses/Artistic-2.0
'use strict';
var base = require('pkgcloud').storage;

View File

@ -1,7 +1,8 @@
// Copyright IBM Corp. 2013,2014. All Rights Reserved.
// Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback-component-storage
// This file is licensed under the Artistic License 2.0.
// License text available at https://opensource.org/licenses/Artistic-2.0
'use strict';
var base = require('pkgcloud').storage;

View File

@ -1,7 +1,8 @@
// Copyright IBM Corp. 2013,2015. All Rights Reserved.
// Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback-component-storage
// This file is licensed under the Artistic License 2.0.
// License text available at https://opensource.org/licenses/Artistic-2.0
'use strict';
// Globalization
@ -18,6 +19,8 @@ var fs = require('fs'),
File = require('./file').File,
Container = require('./container').Container;
var utils = require('./../../utils');
module.exports.storage = module.exports; // To make it consistent with pkgcloud
module.exports.File = File;
@ -29,6 +32,12 @@ module.exports.createClient = function(options) {
function FileSystemProvider(options) {
options = options || {};
if (!path.isAbsolute(options.root)) {
var basePath = path.dirname(path.dirname(require.main.filename));
options.root = path.join(basePath, options.root);
}
this.root = options.root;
var exists = fs.existsSync(this.root);
if (!exists) {
@ -95,10 +104,17 @@ function populateMetadata(stat, props) {
}
FileSystemProvider.prototype.getContainers = function(cb) {
cb = cb || utils.createPromiseCallback();
var self = this;
fs.readdir(self.root, function(err, files) {
var containers = [];
var tasks = [];
if (!files) {
files = [];
}
files.forEach(function(f) {
tasks.push(fs.stat.bind(fs, path.join(self.root, f)));
});
@ -119,15 +135,20 @@ FileSystemProvider.prototype.getContainers = function(cb) {
}
});
});
return cb.promise;
};
FileSystemProvider.prototype.createContainer = function(options, cb) {
cb = cb || utils.createPromiseCallback();
var self = this;
var name = options.name;
var dir = path.join(this.root, name);
validateName(name, cb) && fs.mkdir(dir, options, function(err) {
if (err) {
return cb && cb(err);
cb && cb(err);
return;
}
fs.stat(dir, function(err, stat) {
var container = null;
@ -139,9 +160,13 @@ FileSystemProvider.prototype.createContainer = function(options, cb) {
cb && cb(err, container);
});
});
return cb.promise;
};
FileSystemProvider.prototype.destroyContainer = function(containerName, cb) {
cb = cb || utils.createPromiseCallback();
if (!validateName(containerName, cb)) return;
var dir = path.join(this.root, containerName);
@ -160,9 +185,13 @@ FileSystemProvider.prototype.destroyContainer = function(containerName, cb) {
}
});
});
return cb.promise;
};
FileSystemProvider.prototype.getContainer = function(containerName, cb) {
cb = cb || utils.createPromiseCallback();
var self = this;
if (!validateName(containerName, cb)) return;
var dir = path.join(this.root, containerName);
@ -175,6 +204,8 @@ FileSystemProvider.prototype.getContainer = function(containerName, cb) {
}
cb && cb(err, container);
});
return cb.promise;
};
// File related functions
@ -252,6 +283,9 @@ FileSystemProvider.prototype.getFiles = function(container, options, cb) {
cb = options;
options = false;
}
cb = cb || utils.createPromiseCallback();
var self = this;
if (!validateName(container, cb)) return;
var dir = path.join(this.root, container);
@ -278,9 +312,13 @@ FileSystemProvider.prototype.getFiles = function(container, options, cb) {
}
});
});
return cb.promise;
};
FileSystemProvider.prototype.getFile = function(container, file, cb) {
cb = cb || utils.createPromiseCallback();
var self = this;
if (!validateName(container, cb)) return;
if (!validateName(file, cb)) return;
@ -294,6 +332,8 @@ FileSystemProvider.prototype.getFile = function(container, file, cb) {
}
cb && cb(err, f);
});
return cb.promise;
};
FileSystemProvider.prototype.getUrl = function(options) {
@ -303,9 +343,13 @@ FileSystemProvider.prototype.getUrl = function(options) {
};
FileSystemProvider.prototype.removeFile = function(container, file, cb) {
cb = cb || utils.createPromiseCallback();
if (!validateName(container, cb)) return;
if (!validateName(file, cb)) return;
var filePath = path.join(this.root, container, file);
fs.unlink(filePath, cb);
return cb.promise;
};

View File

@ -1,7 +1,8 @@
// Copyright IBM Corp. 2013,2014. All Rights Reserved.
// Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback-component-storage
// This file is licensed under the Artistic License 2.0.
// License text available at https://opensource.org/licenses/Artistic-2.0
'use strict';
var StorageService = require('./storage-service');

View File

@ -1,7 +1,8 @@
// Copyright IBM Corp. 2013,2015. All Rights Reserved.
// Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback-component-storage
// This file is licensed under the Artistic License 2.0.
// License text available at https://opensource.org/licenses/Artistic-2.0
'use strict';
// Globalization
@ -11,6 +12,7 @@ var IncomingForm = require('formidable');
var StringDecoder = require('string_decoder').StringDecoder;
var path = require('path');
var uuid = require('uuid');
var debug = require('debug')('loopback:storage:handler');
var defaultOptions = {
maxFileSize: 10 * 1024 * 1024, // 10 MB
@ -37,6 +39,7 @@ exports.upload = function(provider, req, res, options, cb) {
var form = new IncomingForm(options);
var container = options.container || req.params.container;
debug('Uploading to container with options %o', options);
var fields = {};
var files = {};
form.handlePart = function(part) {
@ -50,9 +53,11 @@ exports.upload = function(provider, req, res, options, cb) {
self._fieldsSize += buffer.length;
if (self._fieldsSize > self.maxFieldsSize) {
self._error(new Error(
g.f('{{maxFieldsSize}} exceeded, received %s bytes of field data',
g.f(
'{{maxFieldsSize}} exceeded, received %s bytes of field data',
self._fieldsSize
)));
)
));
return;
}
value += decoder.write(buffer);
@ -102,10 +107,12 @@ exports.upload = function(provider, req, res, options, cb) {
if (Array.isArray(allowedContentTypes) && allowedContentTypes.length !== 0) {
if (allowedContentTypes.indexOf(file.type) === -1) {
self._error(new Error(
g.f('{{contentType}} "%s" is not allowed (Must be in [%s])',
g.f(
'{{contentType}} "%s" is not allowed (Must be in [%s])',
file.type,
allowedContentTypes.join(', ')
)));
)
));
return;
}
}
@ -141,6 +148,23 @@ exports.upload = function(provider, req, res, options, cb) {
uploadParams.acl = file.acl;
}
// AWS specific options
// See http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property
const awsOptionNames = [
'StorageClass',
'CacheControl',
'ServerSideEncryption',
'SSEKMSKeyId',
'SSECustomerAlgorithm',
'SSECustomerKey',
'SSECustomerKeyMD5',
];
for (const awsOption of awsOptionNames) {
if (typeof options[awsOption] !== 'undefined') {
uploadParams[awsOption] = options[awsOption];
}
}
var writer = provider.upload(uploadParams);
writer.on('error', function(err) {
@ -177,10 +201,12 @@ exports.upload = function(provider, req, res, options, cb) {
// - s3-upload-stream doesn't provide a way to do this in it's public interface
// - We could call provider.delete file but it would not delete multipart data
self._error(new Error(
g.f('{{maxFileSize}} exceeded, received %s bytes of field data (max is %s)',
g.f(
'{{maxFileSize}} exceeded, received %s bytes of field data (max is %s)',
fileSize,
maxFileSize
)));
)
));
return;
}
});

View File

@ -1,13 +1,16 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback-component-storage
// This file is licensed under the Artistic License 2.0.
// License text available at https://opensource.org/licenses/Artistic-2.0
'use strict';
var factory = require('./factory');
var handler = require('./storage-handler');
var utils = require('./utils');
var storage = require('pkgcloud').storage;
var debug = require('debug')('loopback:storage:service');
module.exports = StorageService;
@ -52,6 +55,22 @@ function StorageService(options) {
if (options.maxFieldsSize) {
this.maxFieldsSize = options.maxFieldsSize;
}
// AWS specific options
// See http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property
const awsOptionNames = [
'StorageClass',
'CacheControl',
'ServerSideEncryption',
'SSEKMSKeyId',
'SSECustomerAlgorithm',
'SSECustomerKey',
'SSECustomerKeyMD5',
];
for (const awsOption of awsOptionNames) {
if (typeof options[awsOption] !== 'undefined') {
this[awsOption] = options[awsOption];
}
}
}
function map(obj) {
@ -63,8 +82,11 @@ function map(obj) {
* @callback {Function} callback Callback function
* @param {Object|String} err Error string or object
* @param {Object[]} containers An array of container metadata objects
* @promise
*/
StorageService.prototype.getContainers = function(cb) {
cb = cb || utils.createPromiseCallback();
this.client.getContainers(function(err, containers) {
if (err) {
cb(err, containers);
@ -74,6 +96,8 @@ StorageService.prototype.getContainers = function(cb) {
}));
}
});
return cb.promise;
};
/**
@ -84,18 +108,24 @@ StorageService.prototype.getContainers = function(cb) {
* @callback {Function} cb Callback function
* @param {Object|String} err Error string or object
* @param {Object} container Container metadata object
* @promise
*/
StorageService.prototype.createContainer = function(options, cb) {
options = options || {};
cb = cb || utils.createPromiseCallback();
if ('object' === typeof options && !(options instanceof storage.Container)) {
options.Name = options.name; // Amazon expects Name
var Container = factory.getProvider(this.provider).storage.Container;
options = new Container(this.client, options);
}
return this.client.createContainer(options, function(err, container) {
debug('Creating container with options %o', options);
this.client.createContainer(options, function(err, container) {
return cb(err, map(container));
});
return cb.promise;
};
/**
@ -103,9 +133,13 @@ StorageService.prototype.createContainer = function(options, cb) {
* @param {String} container Container name.
* @callback {Function} callback Callback function.
* @param {Object|String} err Error string or object
* @promise
*/
StorageService.prototype.destroyContainer = function(container, cb) {
return this.client.destroyContainer(container, cb);
cb = cb || utils.createPromiseCallback();
this.client.destroyContainer(container, cb);
return cb.promise;
};
/**
@ -114,15 +148,20 @@ StorageService.prototype.destroyContainer = function(container, cb) {
* @callback {Function} callback Callback function.
* @param {Object|String} err Error string or object
* @param {Object} container Container metadata object
* @promise
*/
StorageService.prototype.getContainer = function(container, cb) {
return this.client.getContainer(container, function(err, container) {
cb = cb || utils.createPromiseCallback();
this.client.getContainer(container, function(err, container) {
if (err && err.code === 'ENOENT') {
err.statusCode = err.status = 404;
return cb(err);
}
return cb(err, map(container));
});
return cb.promise;
};
/**
@ -130,8 +169,6 @@ StorageService.prototype.getContainer = function(container, cb) {
* @param {String} container Container name
* @param {String} file File name
* @options {Object} [options] Options for uploading
* @callback callback Callback function
* @param {String|Object} err Error string or object
* @returns {Stream} Stream for uploading
*/
StorageService.prototype.uploadStream = function(container, file, options) {
@ -145,7 +182,7 @@ StorageService.prototype.uploadStream = function(container, file, options) {
if (file) {
options.remote = file;
}
debug('Obtaining upload stream for file %s and options %o', file, options);
return this.client.upload(options);
};
@ -154,8 +191,6 @@ StorageService.prototype.uploadStream = function(container, file, options) {
* @param {String} container Container name.
* @param {String} file File name.
* @options {Object} options Options for downloading
* @callback {Function} callback Callback function
* @param {String|Object} err Error string or object
* @returns {Stream} Stream for downloading
*/
StorageService.prototype.downloadStream = function(container, file, options) {
@ -169,7 +204,7 @@ StorageService.prototype.downloadStream = function(container, file, options) {
if (file) {
options.remote = file;
}
debug('Obtaining download stream for file %s and options %o', file, options);
return this.client.download(options);
};
@ -180,6 +215,7 @@ StorageService.prototype.downloadStream = function(container, file, options) {
* @callback {Function} cb Callback function
* @param {Object|String} err Error string or object
* @param {Object[]} files An array of file metadata objects
* @promise
*/
StorageService.prototype.getFiles = function(container, options, cb) {
if (typeof options === 'function' && !cb) {
@ -187,7 +223,10 @@ StorageService.prototype.getFiles = function(container, options, cb) {
cb = options;
options = {};
}
return this.client.getFiles(container, options, function(err, files) {
cb = cb || utils.createPromiseCallback();
this.client.getFiles(container, options, function(err, files) {
if (err) {
cb(err, files);
} else {
@ -196,6 +235,8 @@ StorageService.prototype.getFiles = function(container, options, cb) {
}));
}
});
return cb.promise;
};
/**
@ -205,11 +246,20 @@ StorageService.prototype.getFiles = function(container, options, cb) {
* @callback {Function} cb Callback function
* @param {Object|String} err Error string or object
* @param {Object} file File metadata object
* @promise
*/
StorageService.prototype.getFile = function(container, file, cb) {
return this.client.getFile(container, file, function(err, f) {
cb = cb || utils.createPromiseCallback();
this.client.getFile(container, file, function(err, f) {
if (err && err.code === 'ENOENT') {
err.statusCode = err.status = 404;
return cb(err);
}
return cb(err, map(f));
});
return cb.promise;
};
/**
@ -218,23 +268,41 @@ StorageService.prototype.getFile = function(container, file, cb) {
* @param {String} file File name
* @callback {Function} cb Callback function
* @param {Object|String} err Error string or object
* @promise
*/
StorageService.prototype.removeFile = function(container, file, cb) {
return this.client.removeFile(container, file, cb);
cb = cb || utils.createPromiseCallback();
this.client.removeFile(container, file, cb);
return cb.promise;
};
/**
* Upload middleware for the HTTP request/response <!-- Should this be documented? -->
* @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
* @promise
*/
StorageService.prototype.upload = function(req, res, options, cb) {
StorageService.prototype.upload = function(container, req, res, options, cb) {
debug('Configuring upload with options %o', options);
// 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 = {};
}
cb = cb || utils.createPromiseCallback();
if (this.getFilename && !options.getFilename) {
options.getFilename = this.getFilename;
}
@ -253,7 +321,32 @@ StorageService.prototype.upload = function(req, res, options, cb) {
if (this.maxFieldsSize && !options.maxFieldsSize) {
options.maxFieldsSize = this.maxFieldsSize;
}
return handler.upload(this.client, req, res, options, cb);
// AWS specific options
// See http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property
const awsOptionNames = [
'StorageClass',
'CacheControl',
'ServerSideEncryption',
'SSEKMSKeyId',
'SSECustomerAlgorithm',
'SSECustomerKey',
'SSECustomerKeyMD5',
];
for (const awsOption of awsOptionNames) {
if (this[awsOption] && !options[awsOption]) {
options[awsOption] = this[awsOption];
}
}
if (typeof container === 'string') {
options.container = container;
}
debug('Upload configured with options %o', options);
handler.upload(this.client, req, res, options, cb);
return cb.promise;
};
/**
@ -263,9 +356,13 @@ StorageService.prototype.upload = function(req, res, options, cb) {
* @param {Request} req HTTP request
* @param {Response} res HTTP response
* @param {Function} cb Callback function
* @promise
*/
StorageService.prototype.download = function(container, file, req, res, cb) {
return handler.download(this.client, req, res, container, file, cb);
cb = cb || utils.createPromiseCallback();
handler.download(this.client, req, res, container, file, cb);
return cb.promise;
};
StorageService.modelName = 'storage';
@ -282,7 +379,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 +401,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 +409,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 +417,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 +426,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 +435,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 +445,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'}},
];

20
lib/utils.js Normal file
View File

@ -0,0 +1,20 @@
// Copyright IBM Corp. 2018,2019. All Rights Reserved.
// Node module: loopback-component-storage
// This file is licensed under the Artistic License 2.0.
// License text available at https://opensource.org/licenses/Artistic-2.0
'use strict';
exports.createPromiseCallback = createPromiseCallback;
function createPromiseCallback() {
var cb;
var promise = new Promise(function(resolve, reject) {
cb = function(err, data) {
if (err) return reject(err);
return resolve(data);
};
});
cb.promise = promise;
return cb;
}

View File

@ -2,9 +2,9 @@
"name": "loopback-component-storage",
"description": "Loopback Storage Service",
"engines": {
"node": ">=4"
"node": ">=8"
},
"version": "3.2.0",
"version": "3.7.0",
"main": "index.js",
"scripts": {
"lint": "eslint .",
@ -12,25 +12,27 @@
"posttest": "npm run lint"
},
"dependencies": {
"async": "^2.1.5",
"formidable": "^1.0.16",
"pkgcloud": "^1.1.0",
"strong-globalize": "^2.6.2",
"uuid": "^3.0.1"
"async": "^2.6.1",
"debug": "^3.1.0",
"formidable": "^1.2.1",
"pkgcloud": "^2.1.1",
"strong-globalize": "^4.1.1",
"uuid": "^3.2.1"
},
"devDependencies": {
"eslint": "^3.17.1",
"eslint-config-loopback": "^8.0.0",
"express": "^4.11.0",
"loopback": "^3.0.0",
"mkdirp": "^0.5.0",
"mocha": "^3.2.0",
"supertest": "^3.0.0",
"semver": "^5.3.0"
"eslint": "^5.4.0",
"eslint-config-loopback": "^11.0.0",
"express": "^4.16.3",
"loopback": "^3.22.1",
"mkdirp": "^0.5.1",
"mocha": "^5.2.0",
"supertest": "^3.1.0",
"semver": "^5.5.0"
},
"repository": {
"type": "git",
"url": "https://github.com/strongloop/loopback-component-storage.git"
},
"license": "Artistic-2.0"
"license": "Artistic-2.0",
"author": "IBM Corp."
}

View File

@ -1,7 +1,8 @@
// Copyright IBM Corp. 2016. All Rights Reserved.
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-component-storage
// This file is licensed under the Artistic License 2.0.
// License text available at https://opensource.org/licenses/Artistic-2.0
'use strict';
var loopback = require('loopback');

View File

@ -1,7 +1,8 @@
// Copyright IBM Corp. 2013,2014. All Rights Reserved.
// Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback-component-storage
// This file is licensed under the Artistic License 2.0.
// License text available at https://opensource.org/licenses/Artistic-2.0
'use strict';
var FileSystemProvider = require('../lib/providers/filesystem/index.js').Client;
@ -24,13 +25,32 @@ describe('FileSystem based storage provider', function() {
describe('container apis', function() {
var client = null;
it('should require an existing directory as the root', function(done) {
client = new FileSystemProvider({root: path.join(__dirname, 'storage')});
client = new FileSystemProvider({
root: path.join(__dirname, 'storage'),
});
process.nextTick(done);
});
it('should work even it is ran from other path', function(done) {
process.chdir('../../../');
try {
console.log(`running from ${process.cwd()}`);
client = new FileSystemProvider({
root: path.join(__dirname, 'storage'),
});
process.nextTick(done);
} catch (error) {
process.nextTick(done(error));
}
});
it('should complain if the root directory doesn\'t exist', function(done) {
try {
client = new FileSystemProvider({root: path.join(__dirname, '_storage')});
client = new FileSystemProvider({
root: path.join(__dirname, '_storage'),
});
process.nextTick(done.bind(null, 'Error'));
} catch (err) {
// Should be here
@ -41,11 +61,20 @@ describe('FileSystem based storage provider', function() {
it('should return an empty list of containers', function(done) {
client.getContainers(function(err, containers) {
assert(!err);
assert.equal(0, containers.length);
assert.equal(containers.length, 0);
done(err, containers);
});
});
it('should return an empty list of containers - promise', function(done) {
client.getContainers()
.then(function(containers) {
assert.equal(containers.length, 0);
done();
})
.catch(done);
});
it('should create a new container', function(done) {
client.createContainer({name: 'c1'}, function(err, container) {
assert(!err);
@ -54,6 +83,15 @@ describe('FileSystem based storage provider', function() {
});
});
it('should create a new container - promise', function(done) {
client.createContainer({name: 'c3'})
.then(function(container) {
verifyMetadata(container, 'c3');
done();
})
.catch(done);
});
it('should get a container c1', function(done) {
client.getContainer('c1', function(err, container) {
assert(!err);
@ -62,6 +100,15 @@ describe('FileSystem based storage provider', function() {
});
});
it('should get a container c1 - promise', function(done) {
client.getContainer('c1')
.then(function(container) {
verifyMetadata(container, 'c1');
done();
})
.catch(done);
});
it('should not get a container c2', function(done) {
client.getContainer('c2', function(err, container) {
assert(err);
@ -69,10 +116,18 @@ describe('FileSystem based storage provider', function() {
});
});
it('should destroy a container c3 - promise', function(done) {
client.destroyContainer('c3')
.then(function(container) {
done(null, container);
})
.catch(done);
});
it('should return one container', function(done) {
client.getContainers(function(err, containers) {
assert(!err);
assert.equal(1, containers.length);
assert.equal(containers.length, 1);
done(err, containers);
});
});
@ -94,7 +149,9 @@ describe('FileSystem based storage provider', function() {
describe('file apis', function() {
var fs = require('fs');
var client = new FileSystemProvider({root: path.join(__dirname, 'storage')});
var client = new FileSystemProvider({
root: path.join(__dirname, 'storage'),
});
it('should create a new container', function(done) {
client.createContainer({name: 'c1'}, function(err, container) {
@ -110,6 +167,7 @@ describe('FileSystem based storage provider', function() {
writer.on('error', done);
});
/* eslint-disable mocha/handle-done-callback */
it('should fail to upload a file with invalid characters', function(done) {
var writer = client.upload({container: 'c1', remote: 'a/f1.txt'});
fs.createReadStream(path.join(__dirname, 'files/f1.txt')).pipe(writer);
@ -124,20 +182,26 @@ describe('FileSystem based storage provider', function() {
cb = clearCb;
});
});
/* eslint-enable mocha/handle-done-callback */
it('should download a file', function(done) {
var reader = client.download({
container: 'c1',
remote: 'f1.txt',
});
reader.pipe(fs.createWriteStream(path.join(__dirname, 'files/f1_downloaded.txt')));
reader.pipe(
fs.createWriteStream(path.join(__dirname, 'files/f1_downloaded.txt'))
);
reader.on('end', done);
reader.on('error', done);
});
/* eslint-disable mocha/handle-done-callback */
it('should fail to download a file with invalid characters', function(done) {
var reader = client.download({container: 'c1', remote: 'a/f1.txt'});
reader.pipe(fs.createWriteStream(path.join(__dirname, 'files/a-f1_downloaded.txt')));
reader.pipe(
fs.createWriteStream(path.join(__dirname, 'files/a-f1_downloaded.txt'))
);
var cb = done;
var clearCb = function() {};
reader.on('error', function() {
@ -149,15 +213,25 @@ describe('FileSystem based storage provider', function() {
cb = clearCb;
});
});
/* eslint-enable mocha/handle-done-callback */
it('should get files for a container', function(done) {
client.getFiles('c1', function(err, files) {
assert(!err);
assert.equal(1, files.length);
assert.equal(files.length, 1);
done(err, files);
});
});
it('should get files for a container - promise', function(done) {
client.getFiles('c1')
.then(function(files) {
assert.equal(files.length, 1);
done();
})
.catch(done);
});
it('should get a file', function(done) {
client.getFile('c1', 'f1.txt', function(err, f) {
assert(!err);
@ -167,6 +241,16 @@ describe('FileSystem based storage provider', function() {
});
});
it('should get a file - promise', function(done) {
client.getFile('c1', 'f1.txt')
.then(function(f) {
assert.ok(f);
verifyMetadata(f, 'f1.txt');
done();
})
.catch(done);
});
it('should remove a file', function(done) {
client.removeFile('c1', 'f1.txt', function(err) {
assert(!err);
@ -174,20 +258,67 @@ describe('FileSystem based storage provider', function() {
});
});
it('should remove a file - promise', function(done) {
createFile('c1', 'f1.txt').then(function() {
return client.removeFile('c1', 'f1.txt')
.then(function() {
done();
});
})
.catch(done);
});
it('should get no files from a container', function(done) {
client.getFiles('c1', function(err, files) {
assert(!err);
assert.equal(0, files.length);
assert.equal(files.length, 0);
done(err, files);
});
});
it('should get no files from a container - promise', function(done) {
client.getFiles('c1')
.then(function(files) {
assert.equal(files.length, 0);
done();
})
.catch(done);
});
it('should not get a file from a container', function(done) {
client.getFile('c1', 'f2.txt', function(err, f) {
assert(err);
assert.equal(err.code, 'ENOENT');
assert(!f);
done();
});
});
it('should not get a file from a container - promise', function(done) {
client.getFile('c1', 'f2.txt')
.then(function() {
throw new Error('should not be throw');
})
.catch(function(err) {
assert.equal(err.code, 'ENOENT');
done();
});
});
it('should destroy a container c1', function(done) {
client.destroyContainer('c1', function(err, container) {
// console.error(err);
assert(!err);
done(err, container);
});
});
function createFile(container, file) {
return new Promise(function(resolve, reject) {
var writer = client.upload({container: container, remote: file});
fs.createReadStream(path.join(__dirname, 'files/f1.txt')).pipe(writer);
writer.on('finish', resolve);
writer.on('error', reject);
});
}
});
});

View File

@ -1,3 +1,4 @@
test.jpg
image-*.jpg
customimagefield_test.jpg
customimagefield_test.jpg
customimagefield1_test.jpg

View File

@ -1,7 +1,8 @@
// Copyright IBM Corp. 2013,2015. All Rights Reserved.
// Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback-component-storage
// This file is licensed under the Artistic License 2.0.
// License text available at https://opensource.org/licenses/Artistic-2.0
'use strict';
var StorageService = require('../lib/storage-service.js');
@ -19,11 +20,20 @@ describe('Storage service', function() {
it('should return an empty list of containers', function(done) {
storageService.getContainers(function(err, containers) {
assert(!err);
assert.equal(0, containers.length);
assert.equal(containers.length, 0);
done(err, containers);
});
});
it('should return an empty list of containers - promise', function(done) {
storageService.getContainers()
.then(function(containers) {
assert.equal(containers.length, 0);
done();
})
.catch(done);
});
it('should create a new container', function(done) {
storageService.createContainer({name: 'c1'}, function(err, container) {
assert(!err);
@ -32,6 +42,15 @@ describe('Storage service', function() {
});
});
it('should create a new container - promise', function(done) {
storageService.createContainer({name: 'c3'})
.then(function(container) {
assert(container.getMetadata());
done();
})
.catch(done);
});
it('should get a container c1', function(done) {
storageService.getContainer('c1', function(err, container) {
assert(!err);
@ -40,6 +59,15 @@ describe('Storage service', function() {
});
});
it('should get a container c1 - promise', function(done) {
storageService.getContainer('c1')
.then(function(container) {
assert(container.getMetadata());
done();
})
.catch(done);
});
it('should not get a container c2', function(done) {
storageService.getContainer('c2', function(err, container) {
assert(err);
@ -47,10 +75,18 @@ describe('Storage service', function() {
});
});
it('should destroy a container c3 - promise', function(done) {
storageService.destroyContainer('c3')
.then(function(container) {
done(null, container);
})
.catch(done);
});
it('should return one container', function(done) {
storageService.getContainers(function(err, containers) {
assert(!err);
assert.equal(1, containers.length);
assert.equal(containers.length, 1);
done(err, containers);
});
});
@ -104,11 +140,20 @@ describe('Storage service', function() {
it('should get files for a container', function(done) {
storageService.getFiles('c1', function(err, files) {
assert(!err);
assert.equal(1, files.length);
assert.equal(files.length, 1);
done(err, files);
});
});
it('should get files for a container - promise', function(done) {
storageService.getFiles('c1')
.then(function(files) {
assert.equal(files.length, 1);
done();
})
.catch(done);
});
it('should get a file', function(done) {
storageService.getFile('c1', 'f1.txt', function(err, f) {
assert(!err);
@ -118,6 +163,16 @@ describe('Storage service', function() {
});
});
it('should get a file - promise', function(done) {
storageService.getFile('c1', 'f1.txt')
.then(function(f) {
assert.ok(f);
assert(f.getMetadata());
done();
})
.catch(done);
});
it('should remove a file', function(done) {
storageService.removeFile('c1', 'f1.txt', function(err) {
assert(!err);
@ -125,21 +180,61 @@ describe('Storage service', function() {
});
});
it('should remove a file - promise', function(done) {
createFile('c1', 'f1.txt')
.then(function() {
return storageService.removeFile('c1', 'f1.txt')
.then(function() {
done();
});
})
.catch(done);
});
it('should get no files from a container', function(done) {
storageService.getFiles('c1', function(err, files) {
assert(!err);
assert.equal(0, files.length);
assert.equal(files.length, 0);
done(err, files);
});
});
it('should not get a file from a container', function(done) {
storageService.getFile('c1', 'f1.txt', function(err, f) {
assert(err);
assert.equal(err.code, 'ENOENT');
assert.equal(err.status, 404);
assert(!f);
done();
});
});
it('should not get a file from a container - promise', function(done) {
storageService.getFile('c1', 'f1.txt')
.then(function() {
throw new Error('should not be throw');
})
.catch(function(err) {
assert.equal(err.code, 'ENOENT');
assert.equal(err.status, 404);
done();
});
});
it('should destroy a container c1', function(done) {
storageService.destroyContainer('c1', function(err, container) {
// console.error(err);
assert(!err);
done(err, container);
});
});
function createFile(container, file) {
return new Promise(function(resolve, reject) {
var writer = storageService.uploadStream(container, file);
fs.createReadStream(path.join(__dirname, 'files/f1.txt')).pipe(writer);
writer.on('finish', resolve);
writer.on('error', reject);
});
}
});
});

View File

@ -1,7 +1,8 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback-component-storage
// This file is licensed under the Artistic License 2.0.
// License text available at https://opensource.org/licenses/Artistic-2.0
'use strict';
var request = require('supertest');
@ -32,6 +33,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());
@ -182,14 +200,27 @@ describe('storage service', function() {
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200, function(err, res) {
assert.deepEqual(res.body, {'result': {'files': {'image': [
{'container': 'album1', 'name': 'test.jpg', 'type': 'image/jpeg', 'field': 'image', 'size': 60475},
]}, 'fields': {}}});
assert.deepEqual(res.body, {
result: {
files: {
image: [
{
container: 'album1',
name: 'test.jpg',
type: 'image/jpeg',
field: 'image',
size: 60475,
},
],
},
fields: {},
},
});
done();
});
});
it('fails to upload using dotdot file path', function(done) {
it('fails to upload using dotdot file path (1)', function(done) {
request('http://localhost:' + app.get('port'))
.post('/containers/%2e%2e/upload')
.expect(200, function(err, res) {
@ -198,7 +229,7 @@ describe('storage service', function() {
});
});
it('fails to upload using dotdot file path', function(done) {
it('fails to upload using dotdot file path (2)', function(done) {
request('http://localhost:' + app.get('port'))
.post('%2e%2e/containers/upload')
.expect(200, function(err, res) {
@ -207,7 +238,7 @@ describe('storage service', function() {
});
});
it('fails to upload using dotdot file path', function(done) {
it('fails to upload using dotdot file path (3)', function(done) {
request('http://localhost:' + app.get('port'))
.post('%2e%2e')
.expect(200, function(err, res) {
@ -216,7 +247,7 @@ describe('storage service', function() {
});
});
it('fails to upload using dotdot file path', function(done) {
it('fails to upload using dotdot file path (4)', function(done) {
request('http://localhost:' + app.get('port'))
.post('/containers/upload/%2e%2e')
.expect(200, function(err, res) {
@ -232,9 +263,24 @@ describe('storage service', function() {
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200, function(err, res) {
assert.deepEqual(res.body, {'result': {'files': {'image': [
{'container': 'album1', 'name': 'image-test.jpg', 'originalFilename': 'test.jpg', 'type': 'image/jpeg', 'field': 'image', 'acl': 'public-read', 'size': 60475},
]}, 'fields': {}}});
assert.deepEqual(res.body, {
result: {
files: {
image: [
{
container: 'album1',
name: 'image-test.jpg',
originalFilename: 'test.jpg',
type: 'image/jpeg',
field: 'image',
acl: 'public-read',
size: 60475,
},
],
},
fields: {},
},
});
done();
});
});
@ -274,10 +320,14 @@ describe('storage service', function() {
.set('Connection', 'keep-alive')
.expect('Content-Type', /json/)
.expect(400, function(err, res) {
var indexOfMsg =
res.body.error.message.toLowerCase().indexOf('no file');
assert.notEqual(indexOfMsg, -1,
'Error message does not contain \"no file\"');
var indexOfMsg = res.body.error.message
.toLowerCase()
.indexOf('no file');
assert.notEqual(
indexOfMsg,
-1,
'Error message does not contain "no file"'
);
done(err);
});
} else {
@ -287,8 +337,10 @@ describe('storage service', function() {
.set('Connection', 'keep-alive')
.expect('Content-Type', /json/)
.expect(500, function(err, res) {
assert.equal(res.body.error.message,
'bad content-type header, no content-type');
assert.equal(
res.body.error.message,
'bad content-type header, no content-type'
);
done(err);
});
}
@ -404,19 +456,62 @@ describe('storage service', function() {
});
});
it('should upload a file with custom route accessing directly to the ' +
'storage connector with renamer', function(done) {
it(
'should upload a file with custom route accessing directly to the ' +
'storage connector with renamer',
function(done) {
request('http://localhost:' + app.get('port'))
.post('/custom/upload')
.attach('customimagefield', 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: {
customimagefield: [
{
container: 'album1',
name: 'customimagefield_test.jpg',
originalFilename: 'test.jpg',
type: 'image/jpeg',
field: 'customimagefield',
size: 60475,
},
],
},
fields: {},
},
});
done();
});
}
);
it('should upload a file with container param', function(done) {
request('http://localhost:' + app.get('port'))
.post('/custom/upload')
.attach('customimagefield', path.join(__dirname, './fixtures/test.jpg'))
.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': {'customimagefield': [
{'container': 'album1', 'name': 'customimagefield_test.jpg',
'originalFilename': 'test.jpg', 'type': 'image/jpeg',
'field': 'customimagefield', 'size': 60475},
]}, 'fields': {}}});
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();
});
});