Compare commits
279 Commits
Author | SHA1 | Date |
---|---|---|
|
6638992b99 | |
|
e0fc5139c7 | |
|
21e69f0c14 | |
|
b064b6d4bf | |
|
228bc7519b | |
|
43a1f537db | |
|
24d0338941 | |
|
087dae6a13 | |
|
b27971e074 | |
|
c36f9e88a3 | |
|
8be91b8129 | |
|
490eced414 | |
|
1575becb92 | |
|
b2cf877d14 | |
|
c650b0db87 | |
|
9e30e277fe | |
|
7ddc0b14cf | |
|
6425e8307e | |
|
2e0f3d15f9 | |
|
e22b960d4e | |
|
787f393c7c | |
|
6e0e60c2a2 | |
|
01e2e61cfe | |
|
538bc9a7d5 | |
|
566caa9bab | |
|
4f928bf965 | |
|
cd8f1775bc | |
|
22bd0fc81f | |
|
0fdca8ee0d | |
|
41c31118d8 | |
|
2135abc1db | |
|
661d62e817 | |
|
f1f9aab606 | |
|
645d5c615b | |
|
62d6ecb9d2 | |
|
1ec7a265a7 | |
|
cf38c62c00 | |
|
50e0e4808a | |
|
ac2462e11d | |
|
a5ac1506e6 | |
|
78161ccd9b | |
|
cfb0148e53 | |
|
85c81f760c | |
|
4713e5e7ea | |
|
45284c3bf9 | |
|
989c3bba1f | |
|
91502db9f1 | |
|
10fddb64f7 | |
|
e334884fb1 | |
|
68d55b523e | |
|
a4154caf59 | |
|
42780567a8 | |
|
09b1fce34b | |
|
4e8252afad | |
|
8c76d7fc01 | |
|
322f6c12bb | |
|
0cc2b5b8db | |
|
05db4337cf | |
|
50743e94be | |
|
6a4198896f | |
|
03391f7b00 | |
|
1dac9ada0b | |
|
2ade55ec03 | |
|
98110f1b84 | |
|
97f96f4ab8 | |
|
56ad85ae2a | |
|
b2a00286a3 | |
|
5e7e7ca7e9 | |
|
6fcb7dba6a | |
|
b541c5bff8 | |
|
f1e31ca50c | |
|
f355f66114 | |
|
b8f9b85609 | |
|
d35e1a1b6f | |
|
c7d07b6600 | |
|
afd6dd7073 | |
|
f8b013dab8 | |
|
dc2b6530b7 | |
|
f64721a447 | |
|
5233dcb557 | |
|
0caee53f6b | |
|
659e9ce09b | |
|
6e3fc24121 | |
|
74bb1daf8a | |
|
693d52fc59 | |
|
ee106e4e15 | |
|
65a3a0b110 | |
|
d53d069763 | |
|
9c3d596106 | |
|
18a89e556a | |
|
b3a5bc739b | |
|
fe1c0b605b | |
|
5200b28deb | |
|
4ee086dcd0 | |
|
66e4e5be4a | |
|
01b2faf14a | |
|
9bea50c5e2 | |
|
4d41c67c54 | |
|
956f035482 | |
|
a759286330 | |
|
1fa785f66f | |
|
e7831f6c4d | |
|
63df861753 | |
|
a4a96eb39f | |
|
c0e96ffa12 | |
|
007b20df0c | |
|
d99d608876 | |
|
586fa1cebb | |
|
5c1558f969 | |
|
bc923bd781 | |
|
d06190dae6 | |
|
67e5c6ec1e | |
|
d61e173d6f | |
|
6e880137e4 | |
|
1915d09424 | |
|
f80b27880e | |
|
4cb9f0d74d | |
|
3e0fd94f60 | |
|
df13b094bb | |
|
809ba35fdb | |
|
f97906d397 | |
|
f9cd880eaa | |
|
060630aad6 | |
|
446d2a5078 | |
|
81c951f212 | |
|
f72fb69cfe | |
|
bf5c206bd6 | |
|
765e53098b | |
|
b3497c6778 | |
|
b445d80191 | |
|
6d08df4f0a | |
|
87a89db15f | |
|
b8b92fbeda | |
|
715bc1ece6 | |
|
bdeaf654fa | |
|
46f0c061c0 | |
|
26147b6bc2 | |
|
7037994a6b | |
|
e244153eb7 | |
|
4a0835a032 | |
|
f7dbc97763 | |
|
5ee43fec83 | |
|
6ac1f694b9 | |
|
f3317ec39b | |
|
74063ab559 | |
|
bd3f875ced | |
|
59eeb99803 | |
|
e10dcf7c2c | |
|
f7c74eb24d | |
|
f7f448d569 | |
|
fa310d5882 | |
|
14b8426687 | |
|
ec8250cf58 | |
|
68558d7afa | |
|
3df5b2814c | |
|
c4214024be | |
|
7d1f31cfb4 | |
|
8f642b593c | |
|
73962fb3cc | |
|
4d6f2da578 | |
|
f741c1c3b9 | |
|
4c013deaae | |
|
69103d53f1 | |
|
fcfdb73bdb | |
|
74deec8142 | |
|
381222bf7a | |
|
eb43412439 | |
|
f362084770 | |
|
bc10d68c54 | |
|
07a04b71da | |
|
c485d0f276 | |
|
069d3e8f2f | |
|
5f74e74ea0 | |
|
6e71a52e90 | |
|
bc3008d469 | |
|
55eb8d72e6 | |
|
f99e1a0242 | |
|
ecd881a0f3 | |
|
c538aa764d | |
|
0627f62e45 | |
|
b221af7cf6 | |
|
56fa9829f7 | |
|
91b9a00388 | |
|
ba2fe0ee05 | |
|
378aba60ae | |
|
7f02191838 | |
|
86d0befa11 | |
|
7932d75c44 | |
|
6c9df360b9 | |
|
e562137807 | |
|
25a86906b3 | |
|
d8aa6bdf00 | |
|
c83f84cd05 | |
|
3b88753c8e | |
|
1dab10da3c | |
|
3719ac4bb9 | |
|
5978cb4919 | |
|
4c8ad2908b | |
|
99dc1f9541 | |
|
bb1af5a691 | |
|
b08a1cfba3 | |
|
ca28e7ff9e | |
|
81318e603d | |
|
16c78a83a1 | |
|
3767940472 | |
|
52f1645713 | |
|
0eff26199c | |
|
2eec008e0e | |
|
593fd6e042 | |
|
fc5f16d833 | |
|
ed953a4c6f | |
|
fea3b781a0 | |
|
7f5f8d6df5 | |
|
4a93935825 | |
|
6fd87c45e7 | |
|
fa8ac8d324 | |
|
2ab599fdd1 | |
|
b3c66f34c8 | |
|
895629632f | |
|
13cf1aa160 | |
|
a8f30af49d | |
|
5024b50063 | |
|
619372e51e | |
|
e7bf538a2c | |
|
8bed218a74 | |
|
8fe77b2a06 | |
|
4480cd92ab | |
|
7e051a7549 | |
|
a1c98a8589 | |
|
f54a83d530 | |
|
b013e66883 | |
|
05f8774ed6 | |
|
75da4c7784 | |
|
25fe4970e6 | |
|
6097fbc005 | |
|
14aed2251c | |
|
8fef4845f8 | |
|
7868803711 | |
|
3f54b07ab8 | |
|
25ade96d27 | |
|
9311e918cb | |
|
e89fbd7ce8 | |
|
4798b2f8c9 | |
|
692c67384a | |
|
4d6f2e7ab7 | |
|
553889b378 | |
|
e2b1f78f1e | |
|
da2fb0ae15 | |
|
bd7f2b6db1 | |
|
c2ad201bf6 | |
|
6c59390754 | |
|
53cd449c9c | |
|
cae9786f0e | |
|
845c59eced | |
|
6d738690c8 | |
|
091c71df23 | |
|
2498c02f31 | |
|
1ea1cd612a | |
|
4678cb4b7c | |
|
e4b275243f | |
|
50e3578992 | |
|
a4c643e890 | |
|
97f376c239 | |
|
70aec01e84 | |
|
e98ed99fe7 | |
|
a0806eab89 | |
|
7a54da5870 | |
|
7d3d4f1cee | |
|
76ec49c96b | |
|
d123e95014 | |
|
4753373f4f | |
|
015e9cb80e | |
|
a0a1083564 | |
|
870e1010a8 | |
|
db0678baa6 | |
|
aff49ff63f | |
|
17bd101691 | |
|
8deec2e89a | |
|
5974c6afdf |
|
@ -0,0 +1,39 @@
|
||||||
|
<!--
|
||||||
|
- DO NOT ask questions using GitHub issues (only bug or feature requests please,
|
||||||
|
see http://loopback.io/doc/en/contrib/Reporting-issues.html#asking-questions)
|
||||||
|
|
||||||
|
- Please ask questions at https://groups.google.com/forum/#!forum/loopbackjs or
|
||||||
|
https://gitter.im/strongloop/loopback
|
||||||
|
|
||||||
|
- Immediate support is available through our subscription plans, see
|
||||||
|
https://strongloop.com/node-js/subscription-plans
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Bug or feature request
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Mark your choice with an "x" (eg. [x], NOT [*]).
|
||||||
|
-->
|
||||||
|
|
||||||
|
- [ ] Bug
|
||||||
|
- [ ] Feature request
|
||||||
|
|
||||||
|
### Description of feature (or steps to reproduce if bug)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Link to sample repo to reproduce issue (if bug)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Expected result
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Actual result (if bug)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Additional information (Node.js version, LoopBack version, etc)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
### Description
|
||||||
|
|
||||||
|
|
||||||
|
#### Related issues
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please use the following link syntaxes:
|
||||||
|
|
||||||
|
- #49 (to reference issues in the current repository)
|
||||||
|
- strongloop/loopback#49 (to reference issues in another repository)
|
||||||
|
-->
|
||||||
|
|
||||||
|
- None
|
||||||
|
|
||||||
|
### Checklist
|
||||||
|
|
||||||
|
- [ ] New tests are added to cover all changes
|
||||||
|
- [ ] Code conforms with the [style
|
||||||
|
guide](http://loopback.io/doc/en/contrib/style-guide.html)
|
|
@ -13,3 +13,4 @@
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
*xunit.xml
|
*xunit.xml
|
||||||
|
.nyc_output/
|
||||||
|
|
7
.jscsrc
7
.jscsrc
|
@ -10,14 +10,11 @@
|
||||||
],
|
],
|
||||||
"disallowMultipleVarDecl": "exceptUndefined",
|
"disallowMultipleVarDecl": "exceptUndefined",
|
||||||
"disallowSpacesInsideObjectBrackets": null,
|
"disallowSpacesInsideObjectBrackets": null,
|
||||||
|
"jsDoc": false,
|
||||||
|
"requireDotNotation": false,
|
||||||
"maximumLineLength": {
|
"maximumLineLength": {
|
||||||
"value": 150,
|
"value": 150,
|
||||||
"allowComments": true,
|
"allowComments": true,
|
||||||
"allowRegex": true
|
"allowRegex": true
|
||||||
},
|
|
||||||
"validateJSDoc": {
|
|
||||||
"checkParamNames": false,
|
|
||||||
"checkRedundantParams": false,
|
|
||||||
"requireParamTypes": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"exclude": [
|
||||||
|
"Gruntfile.js",
|
||||||
|
"test/**/*.js"
|
||||||
|
],
|
||||||
|
"cache": true
|
||||||
|
}
|
20
.travis.yml
20
.travis.yml
|
@ -1,7 +1,21 @@
|
||||||
sudo: false
|
sudo: false
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- "0.10"
|
- "4"
|
||||||
- "0.12"
|
- "6"
|
||||||
- "iojs"
|
- "8"
|
||||||
|
|
||||||
|
after_success: npm run coverage
|
||||||
|
|
||||||
|
# see https://www.npmjs.com/package/phantomjs-prebuilt#continuous-integration
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- travis_phantomjs
|
||||||
|
before_install:
|
||||||
|
# Upgrade PhantomJS to v2.1.1.
|
||||||
|
- "export PHANTOMJS_VERSION=2.1.1"
|
||||||
|
- "export PATH=$PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64/bin:$PATH"
|
||||||
|
- "if [ $(phantomjs --version) != $PHANTOMJS_VERSION ]; then rm -rf $PWD/travis_phantomjs; mkdir -p $PWD/travis_phantomjs; fi"
|
||||||
|
- "if [ $(phantomjs --version) != $PHANTOMJS_VERSION ]; then wget https://github.com/Medium/phantomjs/releases/download/v$PHANTOMJS_VERSION/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 -O $PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2; fi"
|
||||||
|
- "if [ $(phantomjs --version) != $PHANTOMJS_VERSION ]; then tar -xvf $PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 -C $PWD/travis_phantomjs; fi"
|
||||||
|
- "phantomjs --version"
|
||||||
|
|
419
CHANGES.md
419
CHANGES.md
|
@ -1,3 +1,414 @@
|
||||||
|
2018-11-26, Version 2.41.1
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Fix: treat empty access token string as undefined (andrey-abramow)
|
||||||
|
|
||||||
|
* Fix context propagation broken by async@2.x (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2018-10-09, Version 2.41.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Update LB2 LTS version (Diana Lau)
|
||||||
|
|
||||||
|
|
||||||
|
2018-08-08, Version 2.40.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* fix: accessToken create default acl (virkt25)
|
||||||
|
|
||||||
|
|
||||||
|
2018-02-12, Version 2.39.2
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Babelify juggler for Karma tests (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Fix Karma config to babelify node_modules too (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2018-01-31, Version 2.39.1
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* update juggler dep (Taranveer Virk)
|
||||||
|
|
||||||
|
* fix(id): replace with != null (Samuel Reed)
|
||||||
|
|
||||||
|
* fix(AccessContext): Tighten userid/appid checks (Samuel Reed)
|
||||||
|
|
||||||
|
|
||||||
|
2017-10-23, Version 2.39.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Drop support for Node.js versions 0.10 and 0.12 (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* test: fix too strict test assertion (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Add unit test for empty password (loay)
|
||||||
|
|
||||||
|
* Update translated strings Q2 2017 (Allen Boone)
|
||||||
|
|
||||||
|
|
||||||
|
2017-04-17, Version 2.38.3
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* use lower version of karma-browserify (Diana Lau)
|
||||||
|
|
||||||
|
* update karma-browserify to 5.x (Diana Lau)
|
||||||
|
|
||||||
|
* update translation msg (Diana Lau)
|
||||||
|
|
||||||
|
* Fix user-literal rewrite for anonymous requests (Aaron Buchanan)
|
||||||
|
|
||||||
|
* Forward options in prepareForTokenInvalidation (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2017-03-17, Version 2.38.2
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Fix file patch (Raymond Feng)
|
||||||
|
|
||||||
|
* Add nyc coverage, report data to coveralls.io (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2017-03-13, Version 2.38.1
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Fix User.verify to convert uid to string (phairow)
|
||||||
|
|
||||||
|
* Configure Travis CI to cache phantomjs binaries (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Improve "filter" arg description (Raymond Camden)
|
||||||
|
|
||||||
|
* Fix creation of verification links (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Include link to docs in logoutSessions warning (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Fix detection of logoutSessionsOnSensitiveChanges (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Preserve sessions on User.save() making no changes (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Remove unused dependencies (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Fix logout to handle no or missing accessToken (Ritchie Martori)
|
||||||
|
|
||||||
|
* Use English when running Mocha tests (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Role model: resolves related models by name (Benjamin Kroeger)
|
||||||
|
|
||||||
|
* Fix User methods to use correct Primary Key (Aris Kemper)
|
||||||
|
|
||||||
|
|
||||||
|
2017-01-20, Version 2.38.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Add app setting logoutSessionsOnSensitiveChanges (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Fix User.resetPassword to call createAccessToken() (João Ribeiro)
|
||||||
|
|
||||||
|
|
||||||
|
2017-01-16, Version 2.37.1
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Preserve current session when invalidating tokens (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Clean up access-token-invalidation tests (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2017-01-09, Version 2.37.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Emit resetPasswordRequest event with options (Sergey Reus)
|
||||||
|
|
||||||
|
* Fix false emailVerified on user model update (박대선)
|
||||||
|
|
||||||
|
* Add new flag injectOptionsFromRemoteContext (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Contextify DAO and relation methods (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Implement new http arg mapping optionsFromRequest (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Fix package.json CI downstreamIgnoreList nesting (David Cheung)
|
||||||
|
|
||||||
|
|
||||||
|
2016-12-21, Version 2.36.2
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Add option disabling periodic change rectification (kobaska)
|
||||||
|
|
||||||
|
* Release LTS LB2 (Simon Ho)
|
||||||
|
|
||||||
|
* Invalidate AccessTokens on password change (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Fix registration of operation hooks in User model (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Remove "options.template" from Email payload (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Opt-out downstream builds that are unstable (David Cheung)
|
||||||
|
|
||||||
|
* Allow password reset request for users in realms (Bram Borggreve)
|
||||||
|
|
||||||
|
* Add "returnOnlyRoleNames" option to Role.getRoles (Eric)
|
||||||
|
|
||||||
|
* Fix context within listByPrincipalType role method (codyolsen)
|
||||||
|
|
||||||
|
* Add templateFn option to User#verify() (Adrien Kiren)
|
||||||
|
|
||||||
|
* Add options to bulkUpdate (Kogulan Baskaran)
|
||||||
|
|
||||||
|
* Require verification after email change (Loay)
|
||||||
|
|
||||||
|
* adding check of string for case insensitive emails (Dhaval Trivedi)
|
||||||
|
|
||||||
|
* Fix PR template to not link all PRs to #49 (#2887) (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2016-10-24, Version 2.36.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Need index on principalId for performance. (#2883) (#2884) (Simon Ho)
|
||||||
|
|
||||||
|
* Remove redundant items in PR template (#2877) (#2878) (Simon Ho)
|
||||||
|
|
||||||
|
* Refactor PR template based on feedback (#2865) (#2874) (Simon Ho)
|
||||||
|
|
||||||
|
* Add pull request template (#2843) (#2862) (Simon Ho)
|
||||||
|
|
||||||
|
* Fix description of updateAll response (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2016-10-13, Version 2.35.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Reword ticking checkbox note in issue template (#2855) (Simon Ho)
|
||||||
|
|
||||||
|
* Add how to tick checkbox in issue template (#2851) (#2853) (Simon Ho)
|
||||||
|
|
||||||
|
* Use GitHub issue templates (#2810) (#2852) (Simon Ho)
|
||||||
|
|
||||||
|
* Allow tokens with eternal TTL (value -1) (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Update ja and nl translation files (Candy)
|
||||||
|
|
||||||
|
* Fix support for remote hooks returning a Promise (Tim van der Staaij)
|
||||||
|
|
||||||
|
* Validate non-email property partial update (Loay)
|
||||||
|
|
||||||
|
* Update translation files - round#2 (Candy)
|
||||||
|
|
||||||
|
* Update tests to use registry for model creation (gunjpan)
|
||||||
|
|
||||||
|
* Call new disable remote method from model class. (Richard Pringle)
|
||||||
|
|
||||||
|
* Temporarily disable Karma tests on Windows CI (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Add translation files for 2.x (Candy)
|
||||||
|
|
||||||
|
* Allow resetPassword if email is verified (Loay)
|
||||||
|
|
||||||
|
* Add docs for KeyValue model (Simon Ho)
|
||||||
|
|
||||||
|
* Invalidate sessions after email change (Loay)
|
||||||
|
|
||||||
|
* Upgrade loopback-testing to the latest ^1.4 (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2016-09-13, Version 2.34.1
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Fix double-slash in confirmation URL (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2016-09-12, Version 2.34.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
2016-09-09, Version 2.33.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Fix data argument for upsertWithWhere (Amir Jafarian)
|
||||||
|
|
||||||
|
* Expose upsertWithWhere (Sonali Samantaray)
|
||||||
|
|
||||||
|
* Fix remoting metadata for "data" arguments (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Rework email validation to use isemail (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2016-09-05, Version 2.32.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* test/user: don't attach User model twice (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* app.enableAuth: correctly detect attached models (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Apply g.f to literal strings (Candy)
|
||||||
|
|
||||||
|
* Make the app instance available to connectors (Subramanian Krishnan)
|
||||||
|
|
||||||
|
* Reorder PATCH Vs PUT endpoints (Amir Jafarian)
|
||||||
|
|
||||||
|
* streamline use if `self` (Benjamin Kroeger)
|
||||||
|
|
||||||
|
* resolve related models from correct registry (Benjamin Kroeger)
|
||||||
|
|
||||||
|
* KeyValueModel: add API for listing keys (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2016-08-17, Version 2.31.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Fix token middleware crash (Carl Fürstenberg)
|
||||||
|
|
||||||
|
* Support 'alias' in mail transport config. (Samuel Reed)
|
||||||
|
|
||||||
|
|
||||||
|
2016-08-16, Version 2.30.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Revert globalization of Swagger descriptions (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Expose `Replace*` methods (Amir Jafarian)
|
||||||
|
|
||||||
|
* Add bcrypt validation (Loay)
|
||||||
|
|
||||||
|
* Cache remoting descriptions to speed up tests (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Revert globalization of assert() messages (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Fix token middleware to not trigger CLS init (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* common: add KeyValueModel (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Globalize current-context deprecation messages (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Deprecate current-context API (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* test: increase timeout to prevent CI failures (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Backport of #2407 (Candy)
|
||||||
|
|
||||||
|
* test: fix timeout in rest.middleware.test (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* test: fix "socket hang up" error in app.test (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* test: increate timeout in Role test (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* test: make status test more robust (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* test: fix broken Role tests (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Update dependencies to their latest versions (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Increase timeout (jannyHou)
|
||||||
|
|
||||||
|
* Backport of #2565 (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Avoid calling deprecated methds (Amir Jafarian)
|
||||||
|
|
||||||
|
* test: use local registry in test fixtures (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Fix test case error (Loay)
|
||||||
|
|
||||||
|
* Backport/Fix security issue 580 (Loay)
|
||||||
|
|
||||||
|
|
||||||
|
2016-07-12, Version 2.29.1
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Fix description for User.prototype.hasPassword (Jue Hou)
|
||||||
|
|
||||||
|
* Fix verificationToken bug #2440 (Loay)
|
||||||
|
|
||||||
|
* add missing unit tests for #2108 (Benjamin Kroeger)
|
||||||
|
|
||||||
|
|
||||||
|
2016-06-07, Version 2.29.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* test: increase timeouts on CI (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* jscsrc: remove jsDoc rule (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Deprecate getters for express 3.x middleware (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Remove env.json and strong-pm dir (Ritchie Martori)
|
||||||
|
|
||||||
|
* Fix JSCS unsupported rule error (Jason)
|
||||||
|
|
||||||
|
* Resolver support return promise (juehou)
|
||||||
|
|
||||||
|
* Update user.js (Rik)
|
||||||
|
|
||||||
|
* Backport separate error checking and done logic (Simon Ho)
|
||||||
|
|
||||||
|
* Clean up by removing unnecessary comments (Supasate Choochaisri)
|
||||||
|
|
||||||
|
* Add feature to not allow duplicate role name (Supasate Choochaisri)
|
||||||
|
|
||||||
|
* update/insert copyright notices (Ryan Graham)
|
||||||
|
|
||||||
|
* relicense as MIT only (Ryan Graham)
|
||||||
|
|
||||||
|
* Upgrade phantomjs to 2.x (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* app: send port:0 instead of port:undefined (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* travis: drop node@5, add node@6 (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Disable DEBUG output for eslint on Jenkins CI (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* test/rest.middleware: use local registry (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Fix role.isOwner to support app-local registry (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* test/user: use local registry (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2016-05-02, Version 2.28.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Add new feature to emit a `remoteMethodDisabled` event when disabling a remote method. (Supasate Choochaisri)
|
||||||
|
|
||||||
|
* Fix typo in Model.nestRemoting (Tim Needham)
|
||||||
|
|
||||||
|
* Allow built-in token middleware to run repeatedly (Benjamin Kröger)
|
||||||
|
|
||||||
|
* Improve error message on connector init error (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* application: correct spelling of "cannont" (Sam Roberts)
|
||||||
|
|
||||||
|
|
||||||
|
2016-02-19, Version 2.27.0
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Remove sl-blip from dependency (Candy)
|
||||||
|
|
||||||
|
* Fix race condition in replication tests (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* test: remove errant console.log from test (Ryan Graham)
|
||||||
|
|
||||||
|
* Promisify Model Change (Jue Hou)
|
||||||
|
|
||||||
|
* Fix race condition in error handler test (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Travis: drop iojs, add v4.x and v5.x (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Correct JSDoc findOrCreate() callback in PersistedModel (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Hide verificationToken (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* test: use ephemeral port for e2e server (Ryan Graham)
|
||||||
|
|
||||||
|
* test: fail on error instead of crash (Ryan Graham)
|
||||||
|
|
||||||
|
* ensure app is booted before integration tests (Ryan Graham)
|
||||||
|
|
||||||
|
* Checkpoint speedup (Amir Jafarian)
|
||||||
|
|
||||||
|
* Pull in API doc fix from PR into master #1910 (crandmck)
|
||||||
|
|
||||||
|
|
||||||
2015-12-22, Version 2.26.2
|
2015-12-22, Version 2.26.2
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
|
@ -989,8 +1400,6 @@
|
||||||
2014-07-15, Version 2.0.0-beta6
|
2014-07-15, Version 2.0.0-beta6
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
* 2.0.0-beta6 (Miroslav Bajtoš)
|
|
||||||
|
|
||||||
* lib/application: publish Change models to REST API (Miroslav Bajtoš)
|
* lib/application: publish Change models to REST API (Miroslav Bajtoš)
|
||||||
|
|
||||||
* models/change: fix typo (Miroslav Bajtoš)
|
* models/change: fix typo (Miroslav Bajtoš)
|
||||||
|
@ -1001,8 +1410,6 @@
|
||||||
2014-07-03, Version 2.0.0-beta5
|
2014-07-03, Version 2.0.0-beta5
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
* 2.0.0-beta5 (Miroslav Bajtoš)
|
|
||||||
|
|
||||||
* app: update `url` on `listening` event (Miroslav Bajtoš)
|
* app: update `url` on `listening` event (Miroslav Bajtoš)
|
||||||
|
|
||||||
* Fix "ReferenceError: loopback is not defined" in registry.memory(). (Guilherme Cirne)
|
* Fix "ReferenceError: loopback is not defined" in registry.memory(). (Guilherme Cirne)
|
||||||
|
@ -1021,8 +1428,6 @@
|
||||||
2014-06-26, Version 2.0.0-beta4
|
2014-06-26, Version 2.0.0-beta4
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
* 2.0.0-beta4 (Miroslav Bajtoš)
|
|
||||||
|
|
||||||
* package: upgrade juggler to 2.0.0-beta2 (Miroslav Bajtoš)
|
* package: upgrade juggler to 2.0.0-beta2 (Miroslav Bajtoš)
|
||||||
|
|
||||||
* Fix loopback in PhantomJS, fix karma tests (Miroslav Bajtoš)
|
* Fix loopback in PhantomJS, fix karma tests (Miroslav Bajtoš)
|
||||||
|
@ -1149,8 +1554,6 @@
|
||||||
2014-05-28, Version 2.0.0-beta3
|
2014-05-28, Version 2.0.0-beta3
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
* 2.0.0-beta3 (Miroslav Bajtoš)
|
|
||||||
|
|
||||||
* package.json: fix malformed json (Miroslav Bajtoš)
|
* package.json: fix malformed json (Miroslav Bajtoš)
|
||||||
|
|
||||||
* 2.0.0-beta2 (Ritchie Martori)
|
* 2.0.0-beta2 (Ritchie Martori)
|
||||||
|
|
25
Gruntfile.js
25
Gruntfile.js
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/*global module:false*/
|
/*global module:false*/
|
||||||
module.exports = function(grunt) {
|
module.exports = function(grunt) {
|
||||||
|
|
||||||
|
@ -39,9 +44,6 @@ module.exports = function(grunt) {
|
||||||
common: {
|
common: {
|
||||||
src: ['common/**/*.js']
|
src: ['common/**/*.js']
|
||||||
},
|
},
|
||||||
browser: {
|
|
||||||
src: ['browser/**/*.js']
|
|
||||||
},
|
|
||||||
server: {
|
server: {
|
||||||
src: ['server/**/*.js']
|
src: ['server/**/*.js']
|
||||||
},
|
},
|
||||||
|
@ -54,7 +56,6 @@ module.exports = function(grunt) {
|
||||||
lib: ['lib/**/*.js'],
|
lib: ['lib/**/*.js'],
|
||||||
common: ['common/**/*.js'],
|
common: ['common/**/*.js'],
|
||||||
server: ['server/**/*.js'],
|
server: ['server/**/*.js'],
|
||||||
browser: ['browser/**/*.js'],
|
|
||||||
test: ['test/**/*.js']
|
test: ['test/**/*.js']
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -87,7 +88,8 @@ module.exports = function(grunt) {
|
||||||
src: 'test/*.js',
|
src: 'test/*.js',
|
||||||
options: {
|
options: {
|
||||||
reporter: 'dot',
|
reporter: 'dot',
|
||||||
}
|
require: require.resolve('./test/helpers/use-english.js'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'unit-xml': {
|
'unit-xml': {
|
||||||
src: 'test/*.js',
|
src: 'test/*.js',
|
||||||
|
@ -217,7 +219,14 @@ module.exports = function(grunt) {
|
||||||
grunt.registerTask('e2e-server', function() {
|
grunt.registerTask('e2e-server', function() {
|
||||||
var done = this.async();
|
var done = this.async();
|
||||||
var app = require('./test/fixtures/e2e/app');
|
var app = require('./test/fixtures/e2e/app');
|
||||||
app.listen(3000, done);
|
app.listen(0, function() {
|
||||||
|
process.env.PORT = this.address().port;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
grunt.registerTask('skip-karma-on-windows', function() {
|
||||||
|
console.log('*** SKIPPING PHANTOM-JS BASED TESTS ON WINDOWS ***');
|
||||||
});
|
});
|
||||||
|
|
||||||
grunt.registerTask('e2e', ['e2e-server', 'karma:e2e']);
|
grunt.registerTask('e2e', ['e2e-server', 'karma:e2e']);
|
||||||
|
@ -229,7 +238,9 @@ module.exports = function(grunt) {
|
||||||
'jscs',
|
'jscs',
|
||||||
'jshint',
|
'jshint',
|
||||||
process.env.JENKINS_HOME ? 'mochaTest:unit-xml' : 'mochaTest:unit',
|
process.env.JENKINS_HOME ? 'mochaTest:unit-xml' : 'mochaTest:unit',
|
||||||
'karma:unit-once']);
|
process.env.JENKINS_HOME && /^win/.test(process.platform) ?
|
||||||
|
'skip-karma-on-windows' : 'karma:unit-once',
|
||||||
|
]);
|
||||||
|
|
||||||
// alias for sl-ci-run and `npm test`
|
// alias for sl-ci-run and `npm test`
|
||||||
grunt.registerTask('mocha-and-karma', ['test']);
|
grunt.registerTask('mocha-and-karma', ['test']);
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
Copyright (c) IBM Corp. 2013,2016. All Rights Reserved.
|
||||||
|
Node module: loopback
|
||||||
|
This project is licensed under the MIT License, full text below.
|
||||||
|
|
||||||
|
--------
|
||||||
|
|
||||||
|
MIT license
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -1,9 +0,0 @@
|
||||||
Copyright (c) 2013-2015 StrongLoop, Inc and other contributors.
|
|
||||||
|
|
||||||
loopback uses a dual license model.
|
|
||||||
|
|
||||||
You may use this library under the terms of the [MIT License][],
|
|
||||||
or under the terms of the [StrongLoop Subscription Agreement][].
|
|
||||||
|
|
||||||
[MIT License]: http://opensource.org/licenses/MIT
|
|
||||||
[StrongLoop Subscription Agreement]: http://strongloop.com/license
|
|
14
README.md
14
README.md
|
@ -73,6 +73,20 @@ StrongLoop provides a number of example applications that illustrate various key
|
||||||
|
|
||||||
See [loopback-example](https://github.com/strongloop/loopback-example) for details.
|
See [loopback-example](https://github.com/strongloop/loopback-example) for details.
|
||||||
|
|
||||||
|
## Module Long Term Support Policy
|
||||||
|
|
||||||
|
LoopBack 2.x is now in maintenance LTS.
|
||||||
|
|
||||||
|
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 |
|
||||||
|
| ---------- | --------------- | --------- | -------------------- |
|
||||||
|
| LoopBack 4 | Current | Oct 2018 | Apr 2021 _(minimum)_ |
|
||||||
|
| Loopback 3 | Active LTS | Dec 2016 | Dec 2019 |
|
||||||
|
| Loopback 2 | Maintenance LTS | Jul 2014 | Apr 2019 |
|
||||||
|
|
||||||
|
Learn more about our LTS plan in [docs](https://loopback.io/doc/en/contrib/Long-term-support.html).
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
* [Documentation](http://docs.strongloop.com/display/LB/LoopBack).
|
* [Documentation](http://docs.strongloop.com/display/LB/LoopBack).
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
module.exports = function(loopback) {
|
|
||||||
loopback.getCurrentContext = function() {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
loopback.runInContext =
|
|
||||||
loopback.createContext = function() {
|
|
||||||
throw new Error('Current context is not supported in the browser.');
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,7 +1,14 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Module Dependencies.
|
* Module Dependencies.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var g = require('strong-globalize')();
|
||||||
|
|
||||||
var loopback = require('../../lib/loopback');
|
var loopback = require('../../lib/loopback');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var uid = require('uid2');
|
var uid = require('uid2');
|
||||||
|
@ -96,7 +103,7 @@ module.exports = function(AccessToken) {
|
||||||
|
|
||||||
var id = tokenIdForRequest(req, options);
|
var id = tokenIdForRequest(req, options);
|
||||||
|
|
||||||
if (id) {
|
if (id != null) {
|
||||||
this.findById(id, function(err, token) {
|
this.findById(id, function(err, token) {
|
||||||
if (err) {
|
if (err) {
|
||||||
cb(err);
|
cb(err);
|
||||||
|
@ -107,7 +114,7 @@ module.exports = function(AccessToken) {
|
||||||
} else if (isValid) {
|
} else if (isValid) {
|
||||||
cb(null, token);
|
cb(null, token);
|
||||||
} else {
|
} else {
|
||||||
var e = new Error('Invalid Access Token');
|
var e = new Error(g.f('Invalid Access Token'));
|
||||||
e.status = e.statusCode = 401;
|
e.status = e.statusCode = 401;
|
||||||
e.code = 'INVALID_TOKEN';
|
e.code = 'INVALID_TOKEN';
|
||||||
cb(e);
|
cb(e);
|
||||||
|
@ -142,11 +149,19 @@ module.exports = function(AccessToken) {
|
||||||
assert(this.ttl, 'token.ttl must exist');
|
assert(this.ttl, 'token.ttl must exist');
|
||||||
assert(this.ttl >= -1, 'token.ttl must be >= -1');
|
assert(this.ttl >= -1, 'token.ttl must be >= -1');
|
||||||
|
|
||||||
|
var AccessToken = this.constructor;
|
||||||
|
var userRelation = AccessToken.relations.user; // may not be set up
|
||||||
|
var User = userRelation && userRelation.modelTo;
|
||||||
|
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
var created = this.created.getTime();
|
var created = this.created.getTime();
|
||||||
var elapsedSeconds = (now - created) / 1000;
|
var elapsedSeconds = (now - created) / 1000;
|
||||||
var secondsToLive = this.ttl;
|
var secondsToLive = this.ttl;
|
||||||
var isValid = elapsedSeconds < secondsToLive;
|
var eternalTokensAllowed = !!(User && User.settings.allowEternalTokens);
|
||||||
|
var isEternalToken = secondsToLive === -1;
|
||||||
|
var isValid = isEternalToken ?
|
||||||
|
eternalTokensAllowed :
|
||||||
|
elapsedSeconds < secondsToLive;
|
||||||
|
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
cb(null, isValid);
|
cb(null, isValid);
|
||||||
|
@ -194,6 +209,11 @@ module.exports = function(AccessToken) {
|
||||||
if (typeof id === 'string') {
|
if (typeof id === 'string') {
|
||||||
// Add support for oAuth 2.0 bearer token
|
// Add support for oAuth 2.0 bearer token
|
||||||
// http://tools.ietf.org/html/rfc6750
|
// http://tools.ietf.org/html/rfc6750
|
||||||
|
|
||||||
|
// To prevent Error: Model::findById requires the id argument
|
||||||
|
// with loopback-datasource-juggler 2.56.0+
|
||||||
|
if (id === '') continue;
|
||||||
|
|
||||||
if (id.indexOf('Bearer ') === 0) {
|
if (id.indexOf('Bearer ') === 0) {
|
||||||
id = id.substring(7);
|
id = id.substring(7);
|
||||||
// Decode from base64
|
// Decode from base64
|
||||||
|
|
|
@ -27,12 +27,6 @@
|
||||||
"principalType": "ROLE",
|
"principalType": "ROLE",
|
||||||
"principalId": "$everyone",
|
"principalId": "$everyone",
|
||||||
"permission": "DENY"
|
"permission": "DENY"
|
||||||
},
|
|
||||||
{
|
|
||||||
"principalType": "ROLE",
|
|
||||||
"principalId": "$everyone",
|
|
||||||
"property": "create",
|
|
||||||
"permission": "ALLOW"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Schema ACL options
|
Schema ACL options
|
||||||
|
|
||||||
|
@ -31,6 +36,8 @@
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var g = require('strong-globalize')();
|
||||||
|
|
||||||
var loopback = require('../../lib/loopback');
|
var loopback = require('../../lib/loopback');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
@ -263,7 +270,7 @@ module.exports = function(ACL) {
|
||||||
* @return {Object[]} An array of ACLs
|
* @return {Object[]} An array of ACLs
|
||||||
*/
|
*/
|
||||||
ACL.getStaticACLs = function getStaticACLs(model, property) {
|
ACL.getStaticACLs = function getStaticACLs(model, property) {
|
||||||
var modelClass = loopback.findModel(model);
|
var modelClass = this.registry.findModel(model);
|
||||||
var staticACLs = [];
|
var staticACLs = [];
|
||||||
if (modelClass && modelClass.settings.acls) {
|
if (modelClass && modelClass.settings.acls) {
|
||||||
modelClass.settings.acls.forEach(function(acl) {
|
modelClass.settings.acls.forEach(function(acl) {
|
||||||
|
@ -355,7 +362,7 @@ module.exports = function(ACL) {
|
||||||
acls = acls.concat(dynACLs);
|
acls = acls.concat(dynACLs);
|
||||||
resolved = self.resolvePermission(acls, req);
|
resolved = self.resolvePermission(acls, req);
|
||||||
if (resolved && resolved.permission === ACL.DEFAULT) {
|
if (resolved && resolved.permission === ACL.DEFAULT) {
|
||||||
var modelClass = loopback.findModel(model);
|
var modelClass = self.registry.findModel(model);
|
||||||
resolved.permission = (modelClass && modelClass.settings.defaultPermission) || ACL.ALLOW;
|
resolved.permission = (modelClass && modelClass.settings.defaultPermission) || ACL.ALLOW;
|
||||||
}
|
}
|
||||||
if (callback) callback(null, resolved);
|
if (callback) callback(null, resolved);
|
||||||
|
@ -387,7 +394,9 @@ module.exports = function(ACL) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ACL.checkAccessForContext = function(context, callback) {
|
ACL.checkAccessForContext = function(context, callback) {
|
||||||
var registry = this.registry;
|
var self = this;
|
||||||
|
self.resolveRelatedModels();
|
||||||
|
var roleModel = self.roleModel;
|
||||||
|
|
||||||
if (!(context instanceof AccessContext)) {
|
if (!(context instanceof AccessContext)) {
|
||||||
context = new AccessContext(context);
|
context = new AccessContext(context);
|
||||||
|
@ -410,11 +419,9 @@ module.exports = function(ACL) {
|
||||||
var req = new AccessRequest(modelName, property, accessType, ACL.DEFAULT, methodNames);
|
var req = new AccessRequest(modelName, property, accessType, ACL.DEFAULT, methodNames);
|
||||||
|
|
||||||
var effectiveACLs = [];
|
var effectiveACLs = [];
|
||||||
var staticACLs = this.getStaticACLs(model.modelName, property);
|
var staticACLs = self.getStaticACLs(model.modelName, property);
|
||||||
|
|
||||||
var self = this;
|
self.find({where: {model: model.modelName, property: propertyQuery,
|
||||||
var roleModel = registry.getModelByType(Role);
|
|
||||||
this.find({where: {model: model.modelName, property: propertyQuery,
|
|
||||||
accessType: accessTypeQuery}}, function(err, acls) {
|
accessType: accessTypeQuery}}, function(err, acls) {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (callback) callback(err);
|
if (callback) callback(err);
|
||||||
|
@ -500,10 +507,10 @@ module.exports = function(ACL) {
|
||||||
ACL.resolveRelatedModels = function() {
|
ACL.resolveRelatedModels = function() {
|
||||||
if (!this.roleModel) {
|
if (!this.roleModel) {
|
||||||
var reg = this.registry;
|
var reg = this.registry;
|
||||||
this.roleModel = reg.getModelByType(loopback.Role);
|
this.roleModel = reg.getModelByType('Role');
|
||||||
this.roleMappingModel = reg.getModelByType(loopback.RoleMapping);
|
this.roleMappingModel = reg.getModelByType('RoleMapping');
|
||||||
this.userModel = reg.getModelByType(loopback.User);
|
this.userModel = reg.getModelByType('User');
|
||||||
this.applicationModel = reg.getModelByType(loopback.Application);
|
this.applicationModel = reg.getModelByType('Application');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -530,7 +537,7 @@ module.exports = function(ACL) {
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
process.nextTick(function() {
|
process.nextTick(function() {
|
||||||
var err = new Error('Invalid principal type: ' + type);
|
var err = new Error(g.f('Invalid principal type: %s', type));
|
||||||
err.statusCode = 400;
|
err.statusCode = 400;
|
||||||
cb(err);
|
cb(err);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var utils = require('../../lib/utils');
|
var utils = require('../../lib/utils');
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Module Dependencies.
|
* Module Dependencies.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var g = require('strong-globalize')();
|
||||||
|
|
||||||
var PersistedModel = require('../../lib/loopback').PersistedModel;
|
var PersistedModel = require('../../lib/loopback').PersistedModel;
|
||||||
var loopback = require('../../lib/loopback');
|
var loopback = require('../../lib/loopback');
|
||||||
|
var utils = require('../../lib/utils');
|
||||||
var crypto = require('crypto');
|
var crypto = require('crypto');
|
||||||
var CJSON = {stringify: require('canonical-json')};
|
var CJSON = {stringify: require('canonical-json')};
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
@ -77,6 +85,8 @@ module.exports = function(Change) {
|
||||||
var Change = this;
|
var Change = this;
|
||||||
var errors = [];
|
var errors = [];
|
||||||
|
|
||||||
|
callback = callback || utils.createPromiseCallback();
|
||||||
|
|
||||||
var tasks = modelIds.map(function(id) {
|
var tasks = modelIds.map(function(id) {
|
||||||
return function(cb) {
|
return function(cb) {
|
||||||
Change.findOrCreateChange(modelName, id, function(err, change) {
|
Change.findOrCreateChange(modelName, id, function(err, change) {
|
||||||
|
@ -104,13 +114,14 @@ module.exports = function(Change) {
|
||||||
})
|
})
|
||||||
.join('\n');
|
.join('\n');
|
||||||
|
|
||||||
var msg = 'Cannot rectify ' + modelName + ' changes:\n' + desc;
|
var msg = g.f('Cannot rectify %s changes:\n%s', modelName, desc);
|
||||||
err = new Error(msg);
|
err = new Error(msg);
|
||||||
err.details = { errors: errors };
|
err.details = { errors: errors };
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
|
return callback.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -137,7 +148,8 @@ module.exports = function(Change) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.findOrCreateChange = function(modelName, modelId, callback) {
|
Change.findOrCreateChange = function(modelName, modelId, callback) {
|
||||||
assert(loopback.findModel(modelName), modelName + ' does not exist');
|
assert(this.registry.findModel(modelName), modelName + ' does not exist');
|
||||||
|
callback = callback || utils.createPromiseCallback();
|
||||||
var id = this.idForModel(modelName, modelId);
|
var id = this.idForModel(modelName, modelId);
|
||||||
var Change = this;
|
var Change = this;
|
||||||
|
|
||||||
|
@ -155,6 +167,7 @@ module.exports = function(Change) {
|
||||||
Change.updateOrCreate(ch, callback);
|
Change.updateOrCreate(ch, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return callback.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -171,9 +184,7 @@ module.exports = function(Change) {
|
||||||
|
|
||||||
change.debug('rectify change');
|
change.debug('rectify change');
|
||||||
|
|
||||||
cb = cb || function(err) {
|
cb = cb || utils.createPromiseCallback();
|
||||||
if (err) throw new Error(err);
|
|
||||||
};
|
|
||||||
|
|
||||||
change.currentRevision(function(err, rev) {
|
change.currentRevision(function(err, rev) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
@ -194,6 +205,7 @@ module.exports = function(Change) {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
return cb.promise;
|
||||||
|
|
||||||
function doRectify(checkpoint, rev) {
|
function doRectify(checkpoint, rev) {
|
||||||
if (rev) {
|
if (rev) {
|
||||||
|
@ -248,6 +260,7 @@ module.exports = function(Change) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.prototype.currentRevision = function(cb) {
|
Change.prototype.currentRevision = function(cb) {
|
||||||
|
cb = cb || utils.createPromiseCallback();
|
||||||
var model = this.getModelCtor();
|
var model = this.getModelCtor();
|
||||||
var id = this.getModelId();
|
var id = this.getModelId();
|
||||||
model.findById(id, function(err, inst) {
|
model.findById(id, function(err, inst) {
|
||||||
|
@ -258,6 +271,7 @@ module.exports = function(Change) {
|
||||||
cb(null, null);
|
cb(null, null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return cb.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -390,8 +404,11 @@ module.exports = function(Change) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.diff = function(modelName, since, remoteChanges, callback) {
|
Change.diff = function(modelName, since, remoteChanges, callback) {
|
||||||
|
callback = callback || utils.createPromiseCallback();
|
||||||
|
|
||||||
if (!Array.isArray(remoteChanges) || remoteChanges.length === 0) {
|
if (!Array.isArray(remoteChanges) || remoteChanges.length === 0) {
|
||||||
return callback(null, {deltas: [], conflicts: []});
|
callback(null, {deltas: [], conflicts: []});
|
||||||
|
return callback.promise;
|
||||||
}
|
}
|
||||||
var remoteChangeIndex = {};
|
var remoteChangeIndex = {};
|
||||||
var modelIds = [];
|
var modelIds = [];
|
||||||
|
@ -455,6 +472,7 @@ module.exports = function(Change) {
|
||||||
conflicts: conflicts
|
conflicts: conflicts
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
return callback.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module Dependencies.
|
* Module Dependencies.
|
||||||
*/
|
*/
|
||||||
|
@ -27,43 +32,45 @@ module.exports = function(Checkpoint) {
|
||||||
* Get the current checkpoint id
|
* Get the current checkpoint id
|
||||||
* @callback {Function} callback
|
* @callback {Function} callback
|
||||||
* @param {Error} err
|
* @param {Error} err
|
||||||
* @param {Number} checkpointId The current checkpoint id
|
* @param {Number} checkpoint The current checkpoint seq
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Checkpoint.current = function(cb) {
|
Checkpoint.current = function(cb) {
|
||||||
var Checkpoint = this;
|
var Checkpoint = this;
|
||||||
this.find({
|
Checkpoint._getSingleton(function(err, cp) {
|
||||||
limit: 1,
|
cb(err, cp.seq);
|
||||||
order: 'seq DESC'
|
|
||||||
}, function(err, checkpoints) {
|
|
||||||
if (err) return cb(err);
|
|
||||||
var checkpoint = checkpoints[0];
|
|
||||||
if (checkpoint) {
|
|
||||||
cb(null, checkpoint.seq);
|
|
||||||
} else {
|
|
||||||
Checkpoint.create({ seq: 1 }, function(err, checkpoint) {
|
|
||||||
if (err) return cb(err);
|
|
||||||
cb(null, checkpoint.seq);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Checkpoint.observe('before save', function(ctx, next) {
|
Checkpoint._getSingleton = function(cb) {
|
||||||
if (!ctx.instance) {
|
var query = {limit: 1}; // match all instances, return only one
|
||||||
// Example: Checkpoint.updateAll() and Checkpoint.updateOrCreate()
|
var initialData = {seq: 1};
|
||||||
return next(new Error('Checkpoint does not support partial updates.'));
|
this.findOrCreate(query, initialData, cb);
|
||||||
}
|
};
|
||||||
|
|
||||||
var model = ctx.instance;
|
/**
|
||||||
if (!model.getId() && model.seq === undefined) {
|
* Increase the current checkpoint if it already exists otherwise initialize it
|
||||||
model.constructor.current(function(err, seq) {
|
* @callback {Function} callback
|
||||||
if (err) return next(err);
|
* @param {Error} err
|
||||||
model.seq = seq + 1;
|
* @param {Object} checkpoint The current checkpoint
|
||||||
next();
|
*/
|
||||||
|
Checkpoint.bumpLastSeq = function(cb) {
|
||||||
|
var Checkpoint = this;
|
||||||
|
Checkpoint._getSingleton(function(err, cp) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
var originalSeq = cp.seq;
|
||||||
|
cp.seq++;
|
||||||
|
// Update the checkpoint but only if it was not changed under our hands
|
||||||
|
Checkpoint.updateAll({id: cp.id, seq: originalSeq}, {seq: cp.seq}, function(err, info) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
// possible outcomes
|
||||||
|
// 1) seq was updated to seq+1 - exactly what we wanted!
|
||||||
|
// 2) somebody else already updated seq to seq+1 and our call was a no-op.
|
||||||
|
// That should be ok, checkpoints are time based, so we reuse the one created just now
|
||||||
|
// 3) seq was bumped more than once, so we will be using a value that is behind the latest seq.
|
||||||
|
// @bajtos is not entirely sure if this is ok, but since it wasn't handled by the current implementation either,
|
||||||
|
// he thinks we can keep it this way.
|
||||||
|
cb(null, cp);
|
||||||
});
|
});
|
||||||
} else {
|
});
|
||||||
next();
|
};
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Email model. Extends LoopBack base [Model](#model-new-model).
|
* Email model. Extends LoopBack base [Model](#model-new-model).
|
||||||
* @property {String} to Email addressee. Required.
|
* @property {String} to Email addressee. Required.
|
||||||
|
@ -10,6 +15,8 @@
|
||||||
* @inherits {Model}
|
* @inherits {Model}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var g = require('strong-globalize')();
|
||||||
|
|
||||||
module.exports = function(Email) {
|
module.exports = function(Email) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,13 +46,13 @@ module.exports = function(Email) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Email.send = function() {
|
Email.send = function() {
|
||||||
throw new Error('You must connect the Email Model to a Mail connector');
|
throw new Error(g.f('You must connect the {{Email}} Model to a {{Mail}} connector'));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A shortcut for Email.send(this).
|
* A shortcut for Email.send(this).
|
||||||
*/
|
*/
|
||||||
Email.prototype.send = function() {
|
Email.prototype.send = function() {
|
||||||
throw new Error('You must connect the Email Model to a Mail connector');
|
throw new Error(g.f('You must connect the {{Email}} Model to a {{Mail}} connector'));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,227 @@
|
||||||
|
var g = require('strong-globalize')();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data model for key-value databases.
|
||||||
|
*
|
||||||
|
* @class KeyValueModel
|
||||||
|
* @inherits {Model}
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = function(KeyValueModel) {
|
||||||
|
/**
|
||||||
|
* Return the value associated with a given key.
|
||||||
|
*
|
||||||
|
* @param {String} key Key to use when searching the database.
|
||||||
|
* @options {Object} options
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err Error object.
|
||||||
|
* @param {Any} result Value associated with the given key.
|
||||||
|
* @promise
|
||||||
|
*
|
||||||
|
* @header KeyValueModel.get(key, cb)
|
||||||
|
*/
|
||||||
|
KeyValueModel.get = function(key, options, callback) {
|
||||||
|
throwNotAttached(this.modelName, 'get');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist a value and associate it with the given key.
|
||||||
|
*
|
||||||
|
* @param {String} key Key to associate with the given value.
|
||||||
|
* @param {Any} value Value to persist.
|
||||||
|
* @options {Number|Object} options Optional settings for the key-value
|
||||||
|
* pair. If a Number is provided, it is set as the TTL (time to live) in ms
|
||||||
|
* (milliseconds) for the key-value pair.
|
||||||
|
* @property {Number} ttl TTL for the key-value pair in ms.
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err Error object.
|
||||||
|
* @promise
|
||||||
|
*
|
||||||
|
* @header KeyValueModel.set(key, value, cb)
|
||||||
|
*/
|
||||||
|
KeyValueModel.set = function(key, value, options, callback) {
|
||||||
|
throwNotAttached(this.modelName, 'set');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the TTL (time to live) in ms (milliseconds) for a given key. TTL is the
|
||||||
|
* remaining time before a key-value pair is discarded from the database.
|
||||||
|
*
|
||||||
|
* @param {String} key Key to use when searching the database.
|
||||||
|
* @param {Number} ttl TTL in ms to set for the key.
|
||||||
|
* @options {Object} options
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err Error object.
|
||||||
|
* @promise
|
||||||
|
*
|
||||||
|
* @header KeyValueModel.expire(key, ttl, cb)
|
||||||
|
*/
|
||||||
|
KeyValueModel.expire = function(key, ttl, options, callback) {
|
||||||
|
throwNotAttached(this.modelName, 'expire');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the TTL (time to live) for a given key. TTL is the remaining time
|
||||||
|
* before a key-value pair is discarded from the database.
|
||||||
|
*
|
||||||
|
* @param {String} key Key to use when searching the database.
|
||||||
|
* @options {Object} options
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @param {Error} error
|
||||||
|
* @param {Number} ttl Expiration time for the key-value pair. `undefined` if
|
||||||
|
* TTL was not initially set.
|
||||||
|
* @promise
|
||||||
|
*
|
||||||
|
* @header KeyValueModel.ttl(key, cb)
|
||||||
|
*/
|
||||||
|
KeyValueModel.ttl = function(key, options, callback) {
|
||||||
|
throwNotAttached(this.modelName, 'ttl');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all keys in the database.
|
||||||
|
*
|
||||||
|
* **WARNING**: This method is not suitable for large data sets as all
|
||||||
|
* key-values pairs are loaded into memory at once. For large data sets,
|
||||||
|
* use `iterateKeys()` instead.
|
||||||
|
*
|
||||||
|
* @param {Object} filter An optional filter object with the following
|
||||||
|
* @param {String} filter.match Glob string used to filter returned
|
||||||
|
* keys (i.e. `userid.*`). All connectors are required to support `*` and
|
||||||
|
* `?`, but may also support additional special characters specific to the
|
||||||
|
* database.
|
||||||
|
* @param {Object} options
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @promise
|
||||||
|
*
|
||||||
|
* @header KeyValueModel.keys(filter, cb)
|
||||||
|
*/
|
||||||
|
KeyValueModel.keys = function(filter, options, callback) {
|
||||||
|
throwNotAttached(this.modelName, 'keys');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronously iterate all keys in the database. Similar to `.keys()` but
|
||||||
|
* instead allows for iteration over large data sets without having to load
|
||||||
|
* everything into memory at once.
|
||||||
|
*
|
||||||
|
* Callback example:
|
||||||
|
* ```js
|
||||||
|
* // Given a model named `Color` with two keys `red` and `blue`
|
||||||
|
* var iterator = Color.iterateKeys();
|
||||||
|
* it.next(function(err, key) {
|
||||||
|
* // key contains `red`
|
||||||
|
* it.next(function(err, key) {
|
||||||
|
* // key contains `blue`
|
||||||
|
* });
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Promise example:
|
||||||
|
* ```js
|
||||||
|
* // Given a model named `Color` with two keys `red` and `blue`
|
||||||
|
* var iterator = Color.iterateKeys();
|
||||||
|
* Promise.resolve().then(function() {
|
||||||
|
* return it.next();
|
||||||
|
* })
|
||||||
|
* .then(function(key) {
|
||||||
|
* // key contains `red`
|
||||||
|
* return it.next();
|
||||||
|
* });
|
||||||
|
* .then(function(key) {
|
||||||
|
* // key contains `blue`
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param {Object} filter An optional filter object with the following
|
||||||
|
* @param {String} filter.match Glob string to use to filter returned
|
||||||
|
* keys (i.e. `userid.*`). All connectors are required to support `*` and
|
||||||
|
* `?`. They may also support additional special characters that are
|
||||||
|
* specific to the backing database.
|
||||||
|
* @param {Object} options
|
||||||
|
* @returns {AsyncIterator} An Object implementing `next(cb) -> Promise`
|
||||||
|
* function that can be used to iterate all keys.
|
||||||
|
*
|
||||||
|
* @header KeyValueModel.iterateKeys(filter)
|
||||||
|
*/
|
||||||
|
KeyValueModel.iterateKeys = function(filter, options) {
|
||||||
|
throwNotAttached(this.modelName, 'iterateKeys');
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Set up remoting metadata for this model.
|
||||||
|
*
|
||||||
|
* **Notes**:
|
||||||
|
* - The method is called automatically by `Model.extend` and/or
|
||||||
|
* `app.registry.createModel`
|
||||||
|
* - In general, base models use call this to ensure remote methods are
|
||||||
|
* inherited correctly, see bug at
|
||||||
|
* https://github.com/strongloop/loopback/issues/2350
|
||||||
|
*/
|
||||||
|
KeyValueModel.setup = function() {
|
||||||
|
KeyValueModel.base.setup.apply(this, arguments);
|
||||||
|
|
||||||
|
this.remoteMethod('get', {
|
||||||
|
accepts: {
|
||||||
|
arg: 'key', type: 'string', required: true,
|
||||||
|
http: { source: 'path' },
|
||||||
|
},
|
||||||
|
returns: { arg: 'value', type: 'any', root: true },
|
||||||
|
http: { path: '/:key', verb: 'get' },
|
||||||
|
rest: { after: convertNullToNotFoundError },
|
||||||
|
});
|
||||||
|
|
||||||
|
this.remoteMethod('set', {
|
||||||
|
accepts: [
|
||||||
|
{ arg: 'key', type: 'string', required: true,
|
||||||
|
http: { source: 'path' }},
|
||||||
|
{ arg: 'value', type: 'any', required: true,
|
||||||
|
http: { source: 'body' }},
|
||||||
|
{ arg: 'ttl', type: 'number',
|
||||||
|
http: { source: 'query' },
|
||||||
|
description: 'time to live in milliseconds' },
|
||||||
|
],
|
||||||
|
http: { path: '/:key', verb: 'put' },
|
||||||
|
});
|
||||||
|
|
||||||
|
this.remoteMethod('expire', {
|
||||||
|
accepts: [
|
||||||
|
{ arg: 'key', type: 'string', required: true,
|
||||||
|
http: { source: 'path' }},
|
||||||
|
{ arg: 'ttl', type: 'number', required: true,
|
||||||
|
http: { source: 'form' }},
|
||||||
|
],
|
||||||
|
http: { path: '/:key/expire', verb: 'put' },
|
||||||
|
});
|
||||||
|
|
||||||
|
this.remoteMethod('keys', {
|
||||||
|
accepts: {
|
||||||
|
arg: 'filter', type: 'object', required: false,
|
||||||
|
http: { source: 'query' },
|
||||||
|
},
|
||||||
|
returns: { arg: 'keys', type: ['string'], root: true },
|
||||||
|
http: { path: '/keys', verb: 'get' },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function throwNotAttached(modelName, methodName) {
|
||||||
|
throw new Error(g.f(
|
||||||
|
'Cannot call %s.%s(). ' +
|
||||||
|
'The %s method has not been setup. ' +
|
||||||
|
'The {{KeyValueModel}} has not been correctly attached ' +
|
||||||
|
'to a {{DataSource}}!',
|
||||||
|
modelName, methodName, methodName));
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertNullToNotFoundError(ctx, cb) {
|
||||||
|
if (ctx.result !== null) return cb();
|
||||||
|
|
||||||
|
var modelName = ctx.method.sharedClass.name;
|
||||||
|
var id = ctx.getArgByName('id');
|
||||||
|
var msg = g.f('Unknown "%s" {{key}} "%s".', modelName, id);
|
||||||
|
var error = new Error(msg);
|
||||||
|
error.statusCode = error.status = 404;
|
||||||
|
error.code = 'KEY_NOT_FOUND';
|
||||||
|
cb(error);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name": "KeyValueModel",
|
||||||
|
"base": "Model"
|
||||||
|
}
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var loopback = require('../../lib/loopback');
|
var loopback = require('../../lib/loopback');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -11,7 +11,10 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The principal type, such as user, application, or role"
|
"description": "The principal type, such as user, application, or role"
|
||||||
},
|
},
|
||||||
"principalId": "string"
|
"principalId": {
|
||||||
|
"type": "string",
|
||||||
|
"index": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"relations": {
|
"relations": {
|
||||||
"role": {
|
"role": {
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var loopback = require('../../lib/loopback');
|
var loopback = require('../../lib/loopback');
|
||||||
var debug = require('debug')('loopback:security:role');
|
var debug = require('debug')('loopback:security:role');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
@ -31,9 +36,9 @@ module.exports = function(Role) {
|
||||||
Role.resolveRelatedModels = function() {
|
Role.resolveRelatedModels = function() {
|
||||||
if (!this.userModel) {
|
if (!this.userModel) {
|
||||||
var reg = this.registry;
|
var reg = this.registry;
|
||||||
this.roleMappingModel = reg.getModelByType(loopback.RoleMapping);
|
this.roleMappingModel = reg.getModelByType('RoleMapping');
|
||||||
this.userModel = reg.getModelByType(loopback.User);
|
this.userModel = reg.getModelByType('User');
|
||||||
this.applicationModel = reg.getModelByType(loopback.Application);
|
this.applicationModel = reg.getModelByType('Application');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -75,26 +80,27 @@ module.exports = function(Role) {
|
||||||
};
|
};
|
||||||
|
|
||||||
var model = relsToModels[rel];
|
var model = relsToModels[rel];
|
||||||
listByPrincipalType(model, relsToTypes[rel], query, callback);
|
listByPrincipalType(this, model, relsToTypes[rel], query, callback);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch all models assigned to this role
|
* Fetch all models assigned to this role
|
||||||
* @private
|
* @private
|
||||||
|
* @param {object} Context role context
|
||||||
* @param {*} model model type to fetch
|
* @param {*} model model type to fetch
|
||||||
* @param {String} [principalType] principalType used in the rolemapping for model
|
* @param {String} [principalType] principalType used in the rolemapping for model
|
||||||
* @param {object} [query] query object passed to model find call
|
* @param {object} [query] query object passed to model find call
|
||||||
* @param {Function} [callback] callback function called with `(err, models)` arguments.
|
* @param {Function} [callback] callback function called with `(err, models)` arguments.
|
||||||
*/
|
*/
|
||||||
function listByPrincipalType(model, principalType, query, callback) {
|
function listByPrincipalType(context, model, principalType, query, callback) {
|
||||||
if (callback === undefined) {
|
if (callback === undefined) {
|
||||||
callback = query;
|
callback = query;
|
||||||
query = {};
|
query = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
roleModel.roleMappingModel.find({
|
roleModel.roleMappingModel.find({
|
||||||
where: {roleId: this.id, principalType: principalType}
|
where: {roleId: context.id, principalType: principalType},
|
||||||
}, function(err, mappings) {
|
}, function(err, mappings) {
|
||||||
var ids;
|
var ids;
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -123,8 +129,9 @@ module.exports = function(Role) {
|
||||||
/**
|
/**
|
||||||
* Add custom handler for roles.
|
* Add custom handler for roles.
|
||||||
* @param {String} role Name of role.
|
* @param {String} role Name of role.
|
||||||
* @param {Function} resolver Function that determines if a principal is in the specified role.
|
* @param {Function} resolver Function that determines
|
||||||
* Signature must be `function(role, context, callback)`
|
* if a principal is in the specified role.
|
||||||
|
* Should provide a callback or return a promise.
|
||||||
*/
|
*/
|
||||||
Role.registerResolver = function(role, resolver) {
|
Role.registerResolver = function(role, resolver) {
|
||||||
if (!Role.resolvers) {
|
if (!Role.resolvers) {
|
||||||
|
@ -147,12 +154,10 @@ module.exports = function(Role) {
|
||||||
});
|
});
|
||||||
|
|
||||||
function isUserClass(modelClass) {
|
function isUserClass(modelClass) {
|
||||||
if (modelClass) {
|
if (!modelClass) return false;
|
||||||
return modelClass === loopback.User ||
|
var User = modelClass.modelBuilder.models.User;
|
||||||
modelClass.prototype instanceof loopback.User;
|
if (!User) return false;
|
||||||
} else {
|
return modelClass == User || modelClass.prototype instanceof User;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -292,7 +297,14 @@ module.exports = function(Role) {
|
||||||
var resolver = Role.resolvers[role];
|
var resolver = Role.resolvers[role];
|
||||||
if (resolver) {
|
if (resolver) {
|
||||||
debug('Custom resolver found for role %s', role);
|
debug('Custom resolver found for role %s', role);
|
||||||
resolver(role, context, callback);
|
|
||||||
|
var promise = resolver(role, context, callback);
|
||||||
|
if (promise && typeof promise.then === 'function') {
|
||||||
|
promise.then(
|
||||||
|
function(result) { callback(null, result); },
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,7 +383,12 @@ module.exports = function(Role) {
|
||||||
* @param {Error} err Error object.
|
* @param {Error} err Error object.
|
||||||
* @param {String[]} roles An array of role IDs
|
* @param {String[]} roles An array of role IDs
|
||||||
*/
|
*/
|
||||||
Role.getRoles = function(context, callback) {
|
Role.getRoles = function(context, options, callback) {
|
||||||
|
if (!callback && typeof options === 'function') {
|
||||||
|
callback = options;
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
if (!(context instanceof AccessContext)) {
|
if (!(context instanceof AccessContext)) {
|
||||||
context = new AccessContext(context);
|
context = new AccessContext(context);
|
||||||
}
|
}
|
||||||
|
@ -421,15 +438,24 @@ module.exports = function(Role) {
|
||||||
if (principalType && principalId) {
|
if (principalType && principalId) {
|
||||||
// Please find() treat undefined matches all values
|
// Please find() treat undefined matches all values
|
||||||
inRoleTasks.push(function(done) {
|
inRoleTasks.push(function(done) {
|
||||||
roleMappingModel.find({where: {principalType: principalType,
|
var filter = {where: {principalType: principalType, principalId: principalId}};
|
||||||
principalId: principalId}}, function(err, mappings) {
|
if (options.returnOnlyRoleNames === true) {
|
||||||
|
filter.include = ['role'];
|
||||||
|
}
|
||||||
|
roleMappingModel.find(filter, function(err, mappings) {
|
||||||
debug('Role mappings found: %s %j', err, mappings);
|
debug('Role mappings found: %s %j', err, mappings);
|
||||||
if (err) {
|
if (err) {
|
||||||
if (done) done(err);
|
if (done) done(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mappings.forEach(function(m) {
|
mappings.forEach(function(m) {
|
||||||
addRole(m.roleId);
|
var role;
|
||||||
|
if (options.returnOnlyRoleNames === true) {
|
||||||
|
role = m.toJSON().role.name;
|
||||||
|
} else {
|
||||||
|
role = m.roleId;
|
||||||
|
}
|
||||||
|
addRole(role);
|
||||||
});
|
});
|
||||||
if (done) done();
|
if (done) done();
|
||||||
});
|
});
|
||||||
|
@ -442,4 +468,6 @@ module.exports = function(Role) {
|
||||||
if (callback) callback(err, roles);
|
if (callback) callback(err, roles);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Role.validatesUniquenessOf('name', { message: 'already exists' });
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var loopback = require('../../lib/loopback');
|
var loopback = require('../../lib/loopback');
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,22 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Module Dependencies.
|
* Module Dependencies.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var g = require('strong-globalize')();
|
||||||
|
|
||||||
|
var isEmail = require('isemail');
|
||||||
var loopback = require('../../lib/loopback');
|
var loopback = require('../../lib/loopback');
|
||||||
var utils = require('../../lib/utils');
|
var utils = require('../../lib/utils');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
|
var qs = require('querystring');
|
||||||
var SALT_WORK_FACTOR = 10;
|
var SALT_WORK_FACTOR = 10;
|
||||||
var crypto = require('crypto');
|
var crypto = require('crypto');
|
||||||
|
var MAX_PASSWORD_LENGTH = 72;
|
||||||
var bcrypt;
|
var bcrypt;
|
||||||
try {
|
try {
|
||||||
// Try the native module first
|
// Try the native module first
|
||||||
|
@ -200,14 +209,14 @@ module.exports = function(User) {
|
||||||
realmDelimiter);
|
realmDelimiter);
|
||||||
|
|
||||||
if (realmRequired && !query.realm) {
|
if (realmRequired && !query.realm) {
|
||||||
var err1 = new Error('realm is required');
|
var err1 = new Error(g.f('{{realm}} is required'));
|
||||||
err1.statusCode = 400;
|
err1.statusCode = 400;
|
||||||
err1.code = 'REALM_REQUIRED';
|
err1.code = 'REALM_REQUIRED';
|
||||||
fn(err1);
|
fn(err1);
|
||||||
return fn.promise;
|
return fn.promise;
|
||||||
}
|
}
|
||||||
if (!query.email && !query.username) {
|
if (!query.email && !query.username) {
|
||||||
var err2 = new Error('username or email is required');
|
var err2 = new Error(g.f('{{username}} or {{email}} is required'));
|
||||||
err2.statusCode = 400;
|
err2.statusCode = 400;
|
||||||
err2.code = 'USERNAME_EMAIL_REQUIRED';
|
err2.code = 'USERNAME_EMAIL_REQUIRED';
|
||||||
fn(err2);
|
fn(err2);
|
||||||
|
@ -215,7 +224,7 @@ module.exports = function(User) {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.findOne({where: query}, function(err, user) {
|
self.findOne({where: query}, function(err, user) {
|
||||||
var defaultError = new Error('login failed');
|
var defaultError = new Error(g.f('login failed'));
|
||||||
defaultError.statusCode = 401;
|
defaultError.statusCode = 401;
|
||||||
defaultError.code = 'LOGIN_FAILED';
|
defaultError.code = 'LOGIN_FAILED';
|
||||||
|
|
||||||
|
@ -245,7 +254,7 @@ module.exports = function(User) {
|
||||||
if (self.settings.emailVerificationRequired && !user.emailVerified) {
|
if (self.settings.emailVerificationRequired && !user.emailVerified) {
|
||||||
// Fail to log in if email verification is not done yet
|
// Fail to log in if email verification is not done yet
|
||||||
debug('User email has not been verified');
|
debug('User email has not been verified');
|
||||||
err = new Error('login failed as the email has not been verified');
|
err = new Error(g.f('login failed as the email has not been verified'));
|
||||||
err.statusCode = 401;
|
err.statusCode = 401;
|
||||||
err.code = 'LOGIN_FAILED_EMAIL_NOT_VERIFIED';
|
err.code = 'LOGIN_FAILED_EMAIL_NOT_VERIFIED';
|
||||||
fn(err);
|
fn(err);
|
||||||
|
@ -285,23 +294,49 @@ module.exports = function(User) {
|
||||||
|
|
||||||
User.logout = function(tokenId, fn) {
|
User.logout = function(tokenId, fn) {
|
||||||
fn = fn || utils.createPromiseCallback();
|
fn = fn || utils.createPromiseCallback();
|
||||||
this.relations.accessTokens.modelTo.findById(tokenId, function(err, accessToken) {
|
|
||||||
|
if (!tokenId) {
|
||||||
|
var err = new Error(g.f('{{accessToken}} is required to logout'));
|
||||||
|
err.status = 401;
|
||||||
|
process.nextTick(function() { fn(err); });
|
||||||
|
return fn.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.relations.accessTokens.modelTo.destroyById(tokenId, function(err, info) {
|
||||||
if (err) {
|
if (err) {
|
||||||
fn(err);
|
fn(err);
|
||||||
} else if (accessToken) {
|
} else if ('count' in info && info.count === 0) {
|
||||||
accessToken.destroy(fn);
|
err = new Error(g.f('Could not find {{accessToken}}'));
|
||||||
|
err.status = 401;
|
||||||
|
fn(err);
|
||||||
} else {
|
} else {
|
||||||
fn(new Error('could not find accessToken'));
|
fn();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return fn.promise;
|
return fn.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
User.observe('before delete', function(ctx, next) {
|
||||||
|
var AccessToken = ctx.Model.relations.accessTokens.modelTo;
|
||||||
|
var pkName = ctx.Model.definition.idName() || 'id';
|
||||||
|
ctx.Model.find({ where: ctx.where, fields: [pkName] }, function(err, list) {
|
||||||
|
if (err) return next(err);
|
||||||
|
|
||||||
|
var ids = list.map(function(u) { return u[pkName]; });
|
||||||
|
ctx.where = {};
|
||||||
|
ctx.where[pkName] = { inq: ids };
|
||||||
|
|
||||||
|
AccessToken.destroyAll({ userId: { inq: ids }}, next);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare the given `password` with the users hashed password.
|
* Compare the given `password` with the users hashed password.
|
||||||
*
|
*
|
||||||
* @param {String} password The plain text password
|
* @param {String} password The plain text password
|
||||||
* @returns {Boolean}
|
* @callback {Function} callback Callback function
|
||||||
|
* @param {Error} err Error object
|
||||||
|
* @param {Boolean} isMatch Returns true if the given `password` matches record
|
||||||
*/
|
*/
|
||||||
|
|
||||||
User.prototype.hasPassword = function(plain, fn) {
|
User.prototype.hasPassword = function(plain, fn) {
|
||||||
|
@ -341,6 +376,10 @@ module.exports = function(User) {
|
||||||
* @property {String} text Text of email.
|
* @property {String} text Text of email.
|
||||||
* @property {String} template Name of template that displays verification
|
* @property {String} template Name of template that displays verification
|
||||||
* page, for example, `'verify.ejs'.
|
* page, for example, `'verify.ejs'.
|
||||||
|
* @property {Function} templateFn A function generating the email HTML body
|
||||||
|
* from `verify()` options object and generated attributes like `options.verifyHref`.
|
||||||
|
* It must accept the option object and a callback function with `(err, html)`
|
||||||
|
* as parameters
|
||||||
* @property {String} redirect Page to which user will be redirected after
|
* @property {String} redirect Page to which user will be redirected after
|
||||||
* they verify their email, for example `'/'` for root URI.
|
* they verify their email, for example `'/'` for root URI.
|
||||||
* @property {Function} generateVerificationToken A function to be used to
|
* @property {Function} generateVerificationToken A function to be used to
|
||||||
|
@ -356,6 +395,7 @@ module.exports = function(User) {
|
||||||
var user = this;
|
var user = this;
|
||||||
var userModel = this.constructor;
|
var userModel = this.constructor;
|
||||||
var registry = userModel.registry;
|
var registry = userModel.registry;
|
||||||
|
var pkName = userModel.definition.idName() || 'id';
|
||||||
assert(typeof options === 'object', 'options required when calling user.verify()');
|
assert(typeof options === 'object', 'options required when calling user.verify()');
|
||||||
assert(options.type, 'You must supply a verification type (options.type)');
|
assert(options.type, 'You must supply a verification type (options.type)');
|
||||||
assert(options.type === 'email', 'Unsupported verification type');
|
assert(options.type === 'email', 'Unsupported verification type');
|
||||||
|
@ -377,18 +417,24 @@ module.exports = function(User) {
|
||||||
(options.protocol === 'https' && options.port == '443')
|
(options.protocol === 'https' && options.port == '443')
|
||||||
) ? '' : ':' + options.port;
|
) ? '' : ':' + options.port;
|
||||||
|
|
||||||
|
var urlPath = joinUrlPath(
|
||||||
|
options.restApiRoot,
|
||||||
|
userModel.http.path,
|
||||||
|
userModel.sharedClass.find('confirm', true).http.path
|
||||||
|
);
|
||||||
|
|
||||||
options.verifyHref = options.verifyHref ||
|
options.verifyHref = options.verifyHref ||
|
||||||
options.protocol +
|
options.protocol +
|
||||||
'://' +
|
'://' +
|
||||||
options.host +
|
options.host +
|
||||||
displayPort +
|
displayPort +
|
||||||
options.restApiRoot +
|
urlPath +
|
||||||
userModel.http.path +
|
'?' + qs.stringify({
|
||||||
userModel.sharedClass.find('confirm', true).http.path +
|
uid: '' + options.user[pkName],
|
||||||
'?uid=' +
|
redirect: options.redirect,
|
||||||
options.user.id +
|
});
|
||||||
'&redirect=' +
|
|
||||||
options.redirect;
|
options.templateFn = options.templateFn || createVerificationEmailBody;
|
||||||
|
|
||||||
// Email model
|
// Email model
|
||||||
var Email = options.mailer || this.constructor.email || registry.getModelByType(loopback.Email);
|
var Email = options.mailer || this.constructor.email || registry.getModelByType(loopback.Email);
|
||||||
|
@ -413,30 +459,50 @@ module.exports = function(User) {
|
||||||
function sendEmail(user) {
|
function sendEmail(user) {
|
||||||
options.verifyHref += '&token=' + user.verificationToken;
|
options.verifyHref += '&token=' + user.verificationToken;
|
||||||
|
|
||||||
options.text = options.text || 'Please verify your email by opening this link in a web browser:\n\t{href}';
|
options.text = options.text || g.f('Please verify your email by opening ' +
|
||||||
|
'this link in a web browser:\n\t%s', options.verifyHref);
|
||||||
|
|
||||||
options.text = options.text.replace('{href}', options.verifyHref);
|
options.text = options.text.replace(/\{href\}/g, options.verifyHref);
|
||||||
|
|
||||||
options.to = options.to || user.email;
|
options.to = options.to || user.email;
|
||||||
|
|
||||||
options.subject = options.subject || 'Thanks for Registering';
|
options.subject = options.subject || g.f('Thanks for Registering');
|
||||||
|
|
||||||
options.headers = options.headers || {};
|
options.headers = options.headers || {};
|
||||||
|
|
||||||
var template = loopback.template(options.template);
|
options.templateFn(options, function(err, html) {
|
||||||
options.html = template(options);
|
|
||||||
|
|
||||||
Email.send(options, function(err, email) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
fn(err);
|
fn(err);
|
||||||
} else {
|
} else {
|
||||||
fn(null, {email: email, token: user.verificationToken, uid: user.id});
|
setHtmlContentAndSend(html);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function setHtmlContentAndSend(html) {
|
||||||
|
options.html = html;
|
||||||
|
|
||||||
|
// Remove options.template to prevent rejection by certain
|
||||||
|
// nodemailer transport plugins.
|
||||||
|
delete options.template;
|
||||||
|
|
||||||
|
Email.send(options, function(err, email) {
|
||||||
|
if (err) {
|
||||||
|
fn(err);
|
||||||
|
} else {
|
||||||
|
fn(null, {email: email, token: user.verificationToken, uid: user[pkName]});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return fn.promise;
|
return fn.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function createVerificationEmailBody(options, cb) {
|
||||||
|
var template = loopback.template(options.template);
|
||||||
|
var body = template(options);
|
||||||
|
cb(null, body);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A default verification token generator which accepts the user the token is
|
* A default verification token generator which accepts the user the token is
|
||||||
* being generated for and a callback function to indicate completion.
|
* being generated for and a callback function to indicate completion.
|
||||||
|
@ -469,7 +535,7 @@ module.exports = function(User) {
|
||||||
fn(err);
|
fn(err);
|
||||||
} else {
|
} else {
|
||||||
if (user && user.verificationToken === token) {
|
if (user && user.verificationToken === token) {
|
||||||
user.verificationToken = undefined;
|
user.verificationToken = null;
|
||||||
user.emailVerified = true;
|
user.emailVerified = true;
|
||||||
user.save(function(err) {
|
user.save(function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -480,11 +546,11 @@ module.exports = function(User) {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (user) {
|
if (user) {
|
||||||
err = new Error('Invalid token: ' + token);
|
err = new Error(g.f('Invalid token: %s', token));
|
||||||
err.statusCode = 400;
|
err.statusCode = 400;
|
||||||
err.code = 'INVALID_TOKEN';
|
err.code = 'INVALID_TOKEN';
|
||||||
} else {
|
} else {
|
||||||
err = new Error('User not found: ' + uid);
|
err = new Error(g.f('User not found: %s', uid));
|
||||||
err.statusCode = 404;
|
err.statusCode = 404;
|
||||||
err.code = 'USER_NOT_FOUND';
|
err.code = 'USER_NOT_FOUND';
|
||||||
}
|
}
|
||||||
|
@ -496,11 +562,12 @@ module.exports = function(User) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a short lived acess token for temporary login. Allows users
|
* Create a short lived access token for temporary login. Allows users
|
||||||
* to change passwords if forgotten.
|
* to change passwords if forgotten.
|
||||||
*
|
*
|
||||||
* @options {Object} options
|
* @options {Object} options
|
||||||
* @prop {String} email The user's email address
|
* @property {String} email The user's email address
|
||||||
|
* @property {String} realm The user's realm (optional)
|
||||||
* @callback {Function} callback
|
* @callback {Function} callback
|
||||||
* @param {Error} err
|
* @param {Error} err
|
||||||
*/
|
*/
|
||||||
|
@ -509,29 +576,48 @@ module.exports = function(User) {
|
||||||
cb = cb || utils.createPromiseCallback();
|
cb = cb || utils.createPromiseCallback();
|
||||||
var UserModel = this;
|
var UserModel = this;
|
||||||
var ttl = UserModel.settings.resetPasswordTokenTTL || DEFAULT_RESET_PW_TTL;
|
var ttl = UserModel.settings.resetPasswordTokenTTL || DEFAULT_RESET_PW_TTL;
|
||||||
|
|
||||||
options = options || {};
|
options = options || {};
|
||||||
if (typeof options.email !== 'string') {
|
if (typeof options.email !== 'string') {
|
||||||
var err = new Error('Email is required');
|
var err = new Error(g.f('Email is required'));
|
||||||
err.statusCode = 400;
|
err.statusCode = 400;
|
||||||
err.code = 'EMAIL_REQUIRED';
|
err.code = 'EMAIL_REQUIRED';
|
||||||
cb(err);
|
cb(err);
|
||||||
return cb.promise;
|
return cb.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
UserModel.findOne({ where: {email: options.email} }, function(err, user) {
|
try {
|
||||||
|
if (options.password) {
|
||||||
|
UserModel.validatePassword(options.password);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
var where = {
|
||||||
|
email: options.email
|
||||||
|
};
|
||||||
|
if (options.realm) {
|
||||||
|
where.realm = options.realm;
|
||||||
|
}
|
||||||
|
UserModel.findOne({ where: where }, function(err, user) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
if (!user) {
|
if (!user) {
|
||||||
err = new Error('Email not found');
|
err = new Error(g.f('Email not found'));
|
||||||
err.statusCode = 404;
|
err.statusCode = 404;
|
||||||
err.code = 'EMAIL_NOT_FOUND';
|
err.code = 'EMAIL_NOT_FOUND';
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
// create a short lived access token for temp login to change password
|
// create a short lived access token for temp login to change password
|
||||||
// TODO(ritch) - eventually this should only allow password change
|
// TODO(ritch) - eventually this should only allow password change
|
||||||
user.accessTokens.create({ttl: ttl}, function(err, accessToken) {
|
if (UserModel.settings.emailVerificationRequired && !user.emailVerified) {
|
||||||
|
err = new Error(g.f('Email has not been verified'));
|
||||||
|
err.statusCode = 401;
|
||||||
|
err.code = 'RESET_FAILED_EMAIL_NOT_VERIFIED';
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
user.createAccessToken(ttl, function(err, accessToken) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
@ -539,7 +625,8 @@ module.exports = function(User) {
|
||||||
UserModel.emit('resetPasswordRequest', {
|
UserModel.emit('resetPasswordRequest', {
|
||||||
email: options.email,
|
email: options.email,
|
||||||
accessToken: accessToken,
|
accessToken: accessToken,
|
||||||
user: user
|
user: user,
|
||||||
|
options: options,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -557,14 +644,45 @@ module.exports = function(User) {
|
||||||
};
|
};
|
||||||
|
|
||||||
User.validatePassword = function(plain) {
|
User.validatePassword = function(plain) {
|
||||||
if (typeof plain === 'string' && plain) {
|
var err;
|
||||||
|
if (plain && typeof plain === 'string' && plain.length <= MAX_PASSWORD_LENGTH) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
var err = new Error('Invalid password: ' + plain);
|
if (plain.length > MAX_PASSWORD_LENGTH) {
|
||||||
|
err = new Error(g.f('Password too long: %s', plain));
|
||||||
|
err.code = 'PASSWORD_TOO_LONG';
|
||||||
|
} else {
|
||||||
|
err = new Error(g.f('Invalid password: %s', plain));
|
||||||
|
err.code = 'INVALID_PASSWORD';
|
||||||
|
}
|
||||||
err.statusCode = 422;
|
err.statusCode = 422;
|
||||||
throw err;
|
throw err;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
User._invalidateAccessTokensOfUsers = function(userIds, options, cb) {
|
||||||
|
if (typeof options === 'function' && cb === undefined) {
|
||||||
|
cb = options;
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(userIds) || !userIds.length)
|
||||||
|
return process.nextTick(cb);
|
||||||
|
|
||||||
|
var accessTokenRelation = this.relations.accessTokens;
|
||||||
|
if (!accessTokenRelation)
|
||||||
|
return process.nextTick(cb);
|
||||||
|
|
||||||
|
var AccessToken = accessTokenRelation.modelTo;
|
||||||
|
|
||||||
|
var query = {userId: {inq: userIds}};
|
||||||
|
var tokenPK = AccessToken.definition.idName() || 'id';
|
||||||
|
if (options.accessToken && tokenPK in options.accessToken) {
|
||||||
|
query[tokenPK] = {neq: options.accessToken[tokenPK]};
|
||||||
|
}
|
||||||
|
|
||||||
|
AccessToken.deleteAll(query, options, cb);
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Setup an extended user model.
|
* Setup an extended user model.
|
||||||
*/
|
*/
|
||||||
|
@ -599,14 +717,6 @@ module.exports = function(User) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Access token to normalize email credentials
|
|
||||||
UserModel.observe('access', function normalizeEmailCase(ctx, next) {
|
|
||||||
if (!ctx.Model.settings.caseSensitiveEmail && ctx.query.where && ctx.query.where.email) {
|
|
||||||
ctx.query.where.email = ctx.query.where.email.toLowerCase();
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Make sure emailVerified is not set by creation
|
// Make sure emailVerified is not set by creation
|
||||||
UserModel.beforeRemote('create', function(ctx, user, next) {
|
UserModel.beforeRemote('create', function(ctx, user, next) {
|
||||||
var body = ctx.req.body;
|
var body = ctx.req.body;
|
||||||
|
@ -622,17 +732,18 @@ module.exports = function(User) {
|
||||||
description: 'Login a user with username/email and password.',
|
description: 'Login a user with username/email and password.',
|
||||||
accepts: [
|
accepts: [
|
||||||
{arg: 'credentials', type: 'object', required: true, http: {source: 'body'}},
|
{arg: 'credentials', type: 'object', required: true, http: {source: 'body'}},
|
||||||
{arg: 'include', type: ['string'], http: {source: 'query' },
|
{arg: 'include', type: ['string'], http: {source: 'query'},
|
||||||
description: 'Related objects to include in the response. ' +
|
description: 'Related objects to include in the response. ' +
|
||||||
'See the description of return value for more details.'}
|
'See the description of return value for more details.' },
|
||||||
],
|
],
|
||||||
returns: {
|
returns: {
|
||||||
arg: 'accessToken', type: 'object', root: true,
|
arg: 'accessToken', type: 'object', root: true,
|
||||||
description:
|
description:
|
||||||
'The response body contains properties of the AccessToken created on login.\n' +
|
g.f('The response body contains properties of the {{AccessToken}} created on login.\n' +
|
||||||
'Depending on the value of `include` parameter, the body may contain ' +
|
'Depending on the value of `include` parameter, the body may contain ' +
|
||||||
'additional properties:\n\n' +
|
'additional properties:\n\n' +
|
||||||
' - `user` - `{User}` - Data of the currently logged in user. (`include=user`)\n\n'
|
' - `user` - `U+007BUserU+007D` - Data of the currently logged in user. ' +
|
||||||
|
'{{(`include=user`)}}\n\n'),
|
||||||
},
|
},
|
||||||
http: {verb: 'post'}
|
http: {verb: 'post'}
|
||||||
}
|
}
|
||||||
|
@ -643,15 +754,14 @@ module.exports = function(User) {
|
||||||
{
|
{
|
||||||
description: 'Logout a user with access token.',
|
description: 'Logout a user with access token.',
|
||||||
accepts: [
|
accepts: [
|
||||||
{arg: 'access_token', type: 'string', required: true, http: function(ctx) {
|
{arg: 'access_token', type: 'string', http: function(ctx) {
|
||||||
var req = ctx && ctx.req;
|
var req = ctx && ctx.req;
|
||||||
var accessToken = req && req.accessToken;
|
var accessToken = req && req.accessToken;
|
||||||
var tokenID = accessToken && accessToken.id;
|
var tokenID = accessToken ? accessToken.id : undefined;
|
||||||
|
return tokenID;
|
||||||
return tokenID;
|
}, description: 'Do not supply this argument, it is automatically extracted ' +
|
||||||
}, description: 'Do not supply this argument, it is automatically extracted ' +
|
'from request headers.',
|
||||||
'from request headers.'
|
},
|
||||||
}
|
|
||||||
],
|
],
|
||||||
http: {verb: 'all'}
|
http: {verb: 'all'}
|
||||||
}
|
}
|
||||||
|
@ -684,7 +794,7 @@ module.exports = function(User) {
|
||||||
UserModel.afterRemote('confirm', function(ctx, inst, next) {
|
UserModel.afterRemote('confirm', function(ctx, inst, next) {
|
||||||
if (ctx.args.redirect !== undefined) {
|
if (ctx.args.redirect !== undefined) {
|
||||||
if (!ctx.res) {
|
if (!ctx.res) {
|
||||||
return next(new Error('The transport does not support HTTP redirects.'));
|
return next(new Error(g.f('The transport does not support HTTP redirects.')));
|
||||||
}
|
}
|
||||||
ctx.res.location(ctx.args.redirect);
|
ctx.res.location(ctx.args.redirect);
|
||||||
ctx.res.status(302);
|
ctx.res.status(302);
|
||||||
|
@ -699,10 +809,9 @@ module.exports = function(User) {
|
||||||
assert(loopback.AccessToken, 'AccessToken model must be defined before User model');
|
assert(loopback.AccessToken, 'AccessToken model must be defined before User model');
|
||||||
UserModel.accessToken = loopback.AccessToken;
|
UserModel.accessToken = loopback.AccessToken;
|
||||||
|
|
||||||
// email validation regex
|
UserModel.validate('email', emailValidator, {
|
||||||
var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
message: g.f('Must provide a valid email')
|
||||||
|
});
|
||||||
UserModel.validatesFormatOf('email', {with: re, message: 'Must provide a valid email'});
|
|
||||||
|
|
||||||
// FIXME: We need to add support for uniqueness of composite keys in juggler
|
// FIXME: We need to add support for uniqueness of composite keys in juggler
|
||||||
if (!(UserModel.settings.realmRequired || UserModel.settings.realmDelimiter)) {
|
if (!(UserModel.settings.realmRequired || UserModel.settings.realmDelimiter)) {
|
||||||
|
@ -710,6 +819,34 @@ module.exports = function(User) {
|
||||||
UserModel.validatesUniquenessOf('username', {message: 'User already exists'});
|
UserModel.validatesUniquenessOf('username', {message: 'User already exists'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UserModel.once('attached', function() {
|
||||||
|
if (UserModel.app.get('logoutSessionsOnSensitiveChanges') !== undefined)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g.warn([
|
||||||
|
'',
|
||||||
|
'The user model %j is attached to an application that does not specify',
|
||||||
|
'whether other sessions should be invalidated when a password or',
|
||||||
|
'an email has changed. Session invalidation is important for security',
|
||||||
|
'reasons as it allows users to recover from various account breach',
|
||||||
|
'situations.',
|
||||||
|
'',
|
||||||
|
'We recommend turning this feature on by setting',
|
||||||
|
'"{{logoutSessionsOnSensitiveChanges}}" to {{true}} in',
|
||||||
|
'{{server/config.json}} (unless you have implemented your own solution',
|
||||||
|
'for token invalidation).',
|
||||||
|
'',
|
||||||
|
'We also recommend enabling "{{injectOptionsFromRemoteContext}}" in',
|
||||||
|
'%s\'s settings (typically via common/models/*.json file).',
|
||||||
|
'This setting is required for the invalidation algorithm to keep ',
|
||||||
|
'the current session valid.',
|
||||||
|
'',
|
||||||
|
'Learn more in our documentation at',
|
||||||
|
'https://loopback.io/doc/en/lb2/AccessToken-invalidation.html',
|
||||||
|
'',
|
||||||
|
].join('\n'), UserModel.modelName, UserModel.modelName);
|
||||||
|
});
|
||||||
|
|
||||||
return UserModel;
|
return UserModel;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -719,4 +856,105 @@ module.exports = function(User) {
|
||||||
|
|
||||||
User.setup();
|
User.setup();
|
||||||
|
|
||||||
|
// --- OPERATION HOOKS ---
|
||||||
|
//
|
||||||
|
// Important: Operation hooks are inherited by subclassed models,
|
||||||
|
// therefore they must be registered outside of setup() function
|
||||||
|
|
||||||
|
// Access token to normalize email credentials
|
||||||
|
User.observe('access', function normalizeEmailCase(ctx, next) {
|
||||||
|
if (!ctx.Model.settings.caseSensitiveEmail && ctx.query.where &&
|
||||||
|
ctx.query.where.email && typeof(ctx.query.where.email) === 'string') {
|
||||||
|
ctx.query.where.email = ctx.query.where.email.toLowerCase();
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
User.observe('before save', function prepareForTokenInvalidation(ctx, next) {
|
||||||
|
var invalidationEnabled = ctx.Model.app &&
|
||||||
|
ctx.Model.app.get('logoutSessionsOnSensitiveChanges');
|
||||||
|
if (!invalidationEnabled) return next();
|
||||||
|
|
||||||
|
if (ctx.isNewInstance) return next();
|
||||||
|
if (!ctx.where && !ctx.instance) return next();
|
||||||
|
|
||||||
|
var pkName = ctx.Model.definition.idName() || 'id';
|
||||||
|
|
||||||
|
var where = ctx.where;
|
||||||
|
if (!where) {
|
||||||
|
where = {};
|
||||||
|
where[pkName] = ctx.instance[pkName];
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Model.find({where: where}, ctx.options, function(err, userInstances) {
|
||||||
|
if (err) return next(err);
|
||||||
|
ctx.hookState.originalUserData = userInstances.map(function(u) {
|
||||||
|
var user = {};
|
||||||
|
user[pkName] = u[pkName];
|
||||||
|
user.email = u.email;
|
||||||
|
user.password = u.password;
|
||||||
|
return user;
|
||||||
|
});
|
||||||
|
var emailChanged;
|
||||||
|
if (ctx.instance) {
|
||||||
|
emailChanged = ctx.instance.email !== ctx.hookState.originalUserData[0].email;
|
||||||
|
if (emailChanged && ctx.Model.settings.emailVerificationRequired) {
|
||||||
|
ctx.instance.emailVerified = false;
|
||||||
|
}
|
||||||
|
} else if (ctx.data.email) {
|
||||||
|
emailChanged = ctx.hookState.originalUserData.some(function(data) {
|
||||||
|
return data.email != ctx.data.email;
|
||||||
|
});
|
||||||
|
if (emailChanged && ctx.Model.settings.emailVerificationRequired) {
|
||||||
|
ctx.data.emailVerified = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
User.observe('after save', function invalidateOtherTokens(ctx, next) {
|
||||||
|
var invalidationEnabled = ctx.Model.app &&
|
||||||
|
ctx.Model.app.get('logoutSessionsOnSensitiveChanges');
|
||||||
|
if (!invalidationEnabled) return next();
|
||||||
|
|
||||||
|
if (!ctx.instance && !ctx.data) return next();
|
||||||
|
if (!ctx.hookState.originalUserData) return next();
|
||||||
|
|
||||||
|
var pkName = ctx.Model.definition.idName() || 'id';
|
||||||
|
var newEmail = (ctx.instance || ctx.data).email;
|
||||||
|
var newPassword = (ctx.instance || ctx.data).password;
|
||||||
|
|
||||||
|
if (!newEmail && !newPassword) return next();
|
||||||
|
|
||||||
|
var userIdsToExpire = ctx.hookState.originalUserData.filter(function(u) {
|
||||||
|
return (newEmail && u.email !== newEmail) ||
|
||||||
|
(newPassword && u.password !== newPassword);
|
||||||
|
}).map(function(u) {
|
||||||
|
return u[pkName];
|
||||||
|
});
|
||||||
|
ctx.Model._invalidateAccessTokensOfUsers(userIdsToExpire, ctx.options, next);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function emailValidator(err, done) {
|
||||||
|
var value = this.email;
|
||||||
|
if (value == null)
|
||||||
|
return;
|
||||||
|
if (typeof value !== 'string')
|
||||||
|
return err('string');
|
||||||
|
if (value === '') return;
|
||||||
|
if (!isEmail(value))
|
||||||
|
return err('email');
|
||||||
|
}
|
||||||
|
|
||||||
|
function joinUrlPath(args) {
|
||||||
|
var result = arguments[0];
|
||||||
|
for (var ix = 1; ix < arguments.length; ix++) {
|
||||||
|
var next = arguments[ix];
|
||||||
|
result += result[result.length - 1] === '/' && next[0] === '/' ?
|
||||||
|
next.slice(1) : next;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
"options": {
|
"options": {
|
||||||
"caseSensitiveEmail": true
|
"caseSensitiveEmail": true
|
||||||
},
|
},
|
||||||
"hidden": ["password"],
|
"hidden": ["password", "verificationToken"],
|
||||||
"acls": [
|
"acls": [
|
||||||
{
|
{
|
||||||
"principalType": "ROLE",
|
"principalType": "ROLE",
|
||||||
|
@ -75,6 +75,12 @@
|
||||||
"permission": "ALLOW",
|
"permission": "ALLOW",
|
||||||
"property": "updateAttributes"
|
"property": "updateAttributes"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$owner",
|
||||||
|
"permission": "ALLOW",
|
||||||
|
"property": "replaceById"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"principalType": "ROLE",
|
"principalType": "ROLE",
|
||||||
"principalId": "$everyone",
|
"principalId": "$everyone",
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"lib/server-app.js",
|
"lib/server-app.js",
|
||||||
"lib/loopback.js",
|
"lib/loopback.js",
|
||||||
"lib/registry.js",
|
"lib/registry.js",
|
||||||
"server/current-context.js",
|
"lib/current-context.js",
|
||||||
"lib/access-context.js",
|
"lib/access-context.js",
|
||||||
{ "title": "Base models", "depth": 2 },
|
{ "title": "Base models", "depth": 2 },
|
||||||
"lib/model.js",
|
"lib/model.js",
|
||||||
|
@ -24,6 +24,7 @@
|
||||||
"common/models/application.js",
|
"common/models/application.js",
|
||||||
"common/models/change.js",
|
"common/models/change.js",
|
||||||
"common/models/email.js",
|
"common/models/email.js",
|
||||||
|
"common/models/key-value-model.js",
|
||||||
"common/models/role.js",
|
"common/models/role.js",
|
||||||
"common/models/role-mapping.js",
|
"common/models/role-mapping.js",
|
||||||
"common/models/scope.js",
|
"common/models/scope.js",
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
var g = require('strong-globalize')();
|
||||||
|
|
||||||
var loopback = require('../../');
|
var loopback = require('../../');
|
||||||
var client = loopback();
|
var client = loopback();
|
||||||
var CartItem = require('./models').CartItem;
|
var CartItem = require('./models').CartItem;
|
||||||
|
@ -11,10 +18,10 @@ CartItem.attachTo(remote);
|
||||||
|
|
||||||
// call the remote method
|
// call the remote method
|
||||||
CartItem.sum(1, function(err, total) {
|
CartItem.sum(1, function(err, total) {
|
||||||
console.log('result:', err || total);
|
g.log('result:%s', err || total);
|
||||||
});
|
});
|
||||||
|
|
||||||
// call a built in remote method
|
// call a built in remote method
|
||||||
CartItem.find(function(err, items) {
|
CartItem.find(function(err, items) {
|
||||||
console.log(items);
|
g.log(items);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var loopback = require('../../');
|
var loopback = require('../../');
|
||||||
|
|
||||||
var CartItem = exports.CartItem = loopback.PersistedModel.extend('CartItem', {
|
var CartItem = exports.CartItem = loopback.PersistedModel.extend('CartItem', {
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var loopback = require('../../');
|
var loopback = require('../../');
|
||||||
var server = module.exports = loopback();
|
var server = module.exports = loopback();
|
||||||
var CartItem = require('./models').CartItem;
|
var CartItem = require('./models').CartItem;
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
var g = require('strong-globalize')();
|
||||||
|
|
||||||
var loopback = require('../../');
|
var loopback = require('../../');
|
||||||
var app = loopback();
|
var app = loopback();
|
||||||
|
|
||||||
|
@ -7,9 +14,9 @@ var schema = {
|
||||||
name: String
|
name: String
|
||||||
};
|
};
|
||||||
|
|
||||||
var Color = app.model('color', schema);
|
app.dataSource('db', { connector: 'memory' });
|
||||||
|
var Color = app.registry.createModel('color', schema);
|
||||||
app.dataSource('db', {adapter: 'memory'}).attach(Color);
|
app.model(Color, { dataSource: 'db' });
|
||||||
|
|
||||||
Color.create({name: 'red'});
|
Color.create({name: 'red'});
|
||||||
Color.create({name: 'green'});
|
Color.create({name: 'green'});
|
||||||
|
@ -17,4 +24,4 @@ Color.create({name: 'blue'});
|
||||||
|
|
||||||
app.listen(3000);
|
app.listen(3000);
|
||||||
|
|
||||||
console.log('a list of colors is available at http://localhost:3000/colors');
|
g.log('a list of colors is available at {{http://localhost:3000/colors}}');
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
var g = require('strong-globalize')();
|
||||||
|
|
||||||
var loopback = require('../../');
|
var loopback = require('../../');
|
||||||
var app = loopback();
|
var app = loopback();
|
||||||
|
|
||||||
|
@ -17,7 +24,7 @@ var Color = loopback.createModel('color', { 'name': String });
|
||||||
Color.beforeRemote('**', function (ctx, unused, next) {
|
Color.beforeRemote('**', function (ctx, unused, next) {
|
||||||
// Inside LoopBack code, you can read the property from the context
|
// Inside LoopBack code, you can read the property from the context
|
||||||
var ns = loopback.getCurrentContext();
|
var ns = loopback.getCurrentContext();
|
||||||
console.log('Request to host', ns && ns.get('host'));
|
g.log('Request to host %s', ns && ns.get('host'));
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -25,5 +32,5 @@ app.dataSource('db', { connector: 'memory' });
|
||||||
app.model(Color, { dataSource: 'db' });
|
app.model(Color, { dataSource: 'db' });
|
||||||
|
|
||||||
app.listen(3000, function() {
|
app.listen(3000, function() {
|
||||||
console.log('A list of colors is available at http://localhost:3000/colors');
|
g.log('A list of colors is available at {{http://localhost:3000/colors}}');
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
var g = require('strong-globalize')();
|
||||||
|
|
||||||
var models = require('../../lib/models');
|
var models = require('../../lib/models');
|
||||||
|
|
||||||
var loopback = require('../../');
|
var loopback = require('../../');
|
||||||
|
@ -32,14 +39,15 @@ var data = {pushSettings: [
|
||||||
]}
|
]}
|
||||||
|
|
||||||
Application.create(data, function(err, data) {
|
Application.create(data, function(err, data) {
|
||||||
console.log('Created: ', data.toObject());
|
g.log('Created: %s', data.toObject());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Application.register('rfeng', 'MyApp', {description: 'My first mobile application'}, function (err, result) {
|
Application.register('rfeng', 'MyApp', { description: g.f('My first mobile application') },
|
||||||
|
function(err, result) {
|
||||||
|
console.log(result.toObject());
|
||||||
|
|
||||||
|
result.resetKeys(function(err, result) {
|
||||||
console.log(result.toObject());
|
console.log(result.toObject());
|
||||||
|
});
|
||||||
result.resetKeys(function (err, result) {
|
|
||||||
console.log(result.toObject());
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var loopback = require('../../');
|
var loopback = require('../../');
|
||||||
var app = loopback();
|
var app = loopback();
|
||||||
var db = app.dataSource('db', {connector: loopback.Memory});
|
var db = app.dataSource('db', { connector: 'memory' });
|
||||||
var Color = app.model('color', {dataSource: 'db', options: {trackChanges: true}});
|
var Color = app.registry.createModel('color', {}, { trackChanges: true });
|
||||||
var Color2 = app.model('color2', {dataSource: 'db', options: {trackChanges: true}});
|
app.model(Color, { dataSource: 'db' });
|
||||||
|
var Color2 = app.registry.createModel('color2', {}, { trackChanges: true });
|
||||||
|
app.model(Color2, { dataSource: 'db' });
|
||||||
var target = Color2;
|
var target = Color2;
|
||||||
var source = Color;
|
var source = Color;
|
||||||
var SPEED = process.env.SPEED || 100;
|
var SPEED = process.env.SPEED || 100;
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
var g = require('strong-globalize')();
|
||||||
|
|
||||||
var loopback = require('../../');
|
var loopback = require('../../');
|
||||||
var app = loopback();
|
var app = loopback();
|
||||||
|
|
||||||
|
@ -20,4 +27,4 @@ Color.all(function () {
|
||||||
|
|
||||||
app.listen(3000);
|
app.listen(3000);
|
||||||
|
|
||||||
console.log('a list of colors is available at http://localhost:3000/colors');
|
g.log('a list of colors is available at {{http://localhost:3000/colors}}');
|
||||||
|
|
8
index.js
8
index.js
|
@ -1,3 +1,11 @@
|
||||||
|
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
var SG = require('strong-globalize');
|
||||||
|
SG.SetRootDir(__dirname, { autonomousMsgLoading: 'all' });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* loopback ~ public api
|
* loopback ~ public api
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"03f79fa268fe199de2ce4345515431c1": "Kein Änderungssatz gefunden für {0} mit ID {1}",
|
||||||
|
"04bd8af876f001ceaf443aad6a9002f9": "Für die Authentifizierung muss Modell {0} definiert sein.",
|
||||||
|
"0731d0109e46c21a4e34af3346ed4856": "Dieses Verhalten kann sich in der nächsten Hauptversion ändern.",
|
||||||
|
"095afbf2f1f0e5be678f5dac5c54e717": "Zugriff verweigert",
|
||||||
|
"0caffe1d763c8cca6a61814abe33b776": "E-Mail ist erforderlich",
|
||||||
|
"10e01c895dc0b2fecc385f9f462f1ca6": "eine Liste mit Farben ist verfügbar unter {{http://localhost:3000/colors}}",
|
||||||
|
"1b2a6076dccbe91a56f1672eb3b8598c": "Der Antworthauptteil enthält Eigenschaften des bei der Anmeldung erstellten {{AccessToken}}.\nAbhängig vom Wert des Parameters 'include' kann der Hauptteil zusätzliche Eigenschaften enthalten:\n\n - user - U+007BUserU+007D - Daten des derzeit angemeldeten Benutzers. {{(`include=user`)}}\n\n",
|
||||||
|
"1d7833c3ca2f05fdad8fad7537531c40": "\t BETREFF:{0}",
|
||||||
|
"1e85f822b547a75d7d385048030e4ecb": "Erstellt: {0}",
|
||||||
|
"275f22ab95671f095640ca99194b7635": "\t VON:{0}",
|
||||||
|
"2d3071e3b18681c80a090dc0efbdb349": "{0} mit ID {1} konnte nicht gefunden werden",
|
||||||
|
"2d78192c43fd2ec52ec18f3918894f9a": "Middleware {0} ist veraltet. Siehe {1} für weitere Details.",
|
||||||
|
"308e1d484516a33df788f873e65faaff": "Modell '{0}' bietet veraltetes 'DataModel' an. Verwenden Sie stattdessen 'PersistedModel'.",
|
||||||
|
"316e5b82c203cf3de31a449ee07d0650": "Erwartet wurde boolescher Wert, {0} empfangen",
|
||||||
|
"320c482401afa1207c04343ab162e803": "Ungültiger Prinzipaltyp: {0}",
|
||||||
|
"3438fab56cc7ab92dfd88f0497e523e0": "Die relations-Eigenschaft der Konfiguration '{0}' muss ein Objekt sein",
|
||||||
|
"35e5252c62d80f8c54a5290d30f4c7d0": "Bestätigen Sie Ihre E-Mail-Adresse, indem Sie diesen Link in einem Web-Browser öffnen:\n\t{0}",
|
||||||
|
"3aae63bb7e8e046641767571c1591441": "Anmeldung fehlgeschlagen, da die E-Mail-Adresse nicht bestätigt wurde",
|
||||||
|
"3aecb24fa8bdd3f79d168761ca8a6729": "Unbekannte {{middleware}}-Phase {0}",
|
||||||
|
"3caaa84fc103d6d5612173ae6d43b245": "Ungültiges Token: {0}",
|
||||||
|
"3d617953470be16d0c2b32f0bcfbb5ee": "Vielen Dank für die Registrierung",
|
||||||
|
"3d63008ccfb2af1db2142e8cc2716ace": "Warnung: Keine E-Mail-Transportmethode für das Senden von E-Mails angegeben. Richten Sie eine Transportmethode für das Senden von E-Mails ein.",
|
||||||
|
"4203ab415ec66a78d3164345439ba76e": "{0}.{1}() kann nicht aufgerufen werden. Die Methode {2} wurde nicht konfiguriert. {{PersistedModel}} wurde nicht ordnungsgemäß an eine {{DataSource}} angehängt!",
|
||||||
|
"44a6c8b1ded4ed653d19ddeaaf89a606": "E-Mail nicht gefunden",
|
||||||
|
"4a4f04a4e480fc5d4ee73b84d9a4b904": "E-Mail senden:",
|
||||||
|
"4b494de07f524703ac0879addbd64b13": "E-Mail-Adresse wurde nicht bestätigt",
|
||||||
|
"4cac5f051ae431321673e04045d37772": "Modell '{0}' bietet das unbekannte Modell '{1}' an. 'PersistedModel' wird als Basis verwendet.",
|
||||||
|
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Kann Datenquelle {0} nicht erstellen: {1}",
|
||||||
|
"5858e63efaa0e4ad86b61c0459ea32fa": "Sie müssen das {{Email}}-Modell mit einem {{Mail}}-Konnektor verbinden",
|
||||||
|
"5e81ad3847a290dc650b47618b9cbc7e": "Anmeldung fehlgeschlagen",
|
||||||
|
"5fa3afb425819ebde958043e598cb664": "Modell mit {{id}} {0} konnte nicht gefunden werden",
|
||||||
|
"61e5deebaf44d68f4e6a508f30cc31a3": "Beziehung '{0} ist für Modell {1} nicht vorhanden",
|
||||||
|
"62e8b0a733417978bab22c8dacf5d7e6": "Massenaktualisierungen können nicht angewendet werden, der Konnektor meldet die Anzahl aktualisierter Datensätze nicht richtig.",
|
||||||
|
"63a091ced88001ab6acb58f61ec041c5": "\t TEXT:{0}",
|
||||||
|
"6bc376432cd9972cf991aad3de371e78": "Fehlende Daten für Änderung: {0}",
|
||||||
|
"705c2d456a3e204c4af56e671ec3225c": "{{accessToken}} konnte nicht gefunden werden",
|
||||||
|
"734a7bebb65e10899935126ba63dd51f": "Die options-Eigenschaft der Konfiguration '{0}' muss ein Objekt sein",
|
||||||
|
"779467f467862836e19f494a37d6ab77": "Die acls-Eigenschaft der Konfiguration '{0}' muss eine Reihe von Objekten sein",
|
||||||
|
"7d5e7ed0efaedf3f55f380caae0df8b8": "Meine erste mobile Anwendung",
|
||||||
|
"7e0fca41d098607e1c9aa353c67e0fa1": "Ungültiges Zugriffstoken",
|
||||||
|
"7e287fc885d9fdcf42da3a12f38572c1": "Berechtigung erforderlich",
|
||||||
|
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} muss sich abmelden",
|
||||||
|
"80a32e80cbed65eba2103201a7c94710": "Modell nicht gefunden: {0}",
|
||||||
|
"83cbdc2560ba9f09155ccfc63e08f1a1": "Eigenschaft '{0}' kann für {1} nicht rekonfiguriert werden",
|
||||||
|
"855ecd4a64885ba272d782435f72a4d4": "\"{0}\" unbekannt, ID \"{1}\".",
|
||||||
|
"860d1a0b8bd340411fb32baa72867989": "Die Transportmethode unterstützt keine HTTP-Umleitungen.",
|
||||||
|
"895b1f941d026870b3cc8e6af087c197": "{{username}} oder {{email}} ist erforderlich",
|
||||||
|
"8a17c5ef611e2e7535792316e66b8fca": "Kennwort zu lang: {0}",
|
||||||
|
"8a27e0c9ce3ebf0e0c3978efb456e13e": "Anforderung an Host {0}",
|
||||||
|
"8ae418c605b6a45f2651be9b1677c180": "Ungültige Remote-Methode: '{0}'",
|
||||||
|
"8bab6720ecc58ec6412358c858a53484": "Massenaktualisierung fehlgeschlagen, der Konnektor hat eine unerwartete Anzahl an Datensätzen geändert: {0}",
|
||||||
|
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
|
||||||
|
"97795efe0c3eb7f35ce8cf8cfe70682b": "Der Konfiguration von {0} fehlt die {{`dataSource`}}-Eigenschaft.\nVerwenden Sie 'null' oder 'false', um Modelle zu kennzeichnen, die mit keiner Datenquelle verbunden sind.",
|
||||||
|
"a40684f5a9f546115258b76938d1de37": "Eine Liste mit Farben ist verfügbar unter {{http://localhost:3000/colors}}",
|
||||||
|
"a50d10fc6e0959b220e085454c40381e": "Benutzer nicht gefunden: {0}",
|
||||||
|
"a80038252430df2754884bf3c845c4cf": "Den Remote-Anbindungs-Metadaten für \"{0}.{1}\" fehlt das Flag \"isStatic\"; die Methode ist als Instanzdefinitionsmethode registriert.",
|
||||||
|
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "\"{0}\" unbekannt, {{key}} \"{1}\".",
|
||||||
|
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} ist erforderlich",
|
||||||
|
"c2b5d51f007178170ca3952d59640ca4": "{0} Änderungen können nicht behoben werden:\n{1}",
|
||||||
|
"c68a93f0a9524fed4ff64372fc90c55f": "Eine gültige E-Mail-Adresse muss angegeben werden",
|
||||||
|
"cd0412f2f33a4a2a316acc834f3f21a6": "muss {{id}} oder {{data}} angeben",
|
||||||
|
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} wurde entfernt, verwenden Sie stattdessen das neue Modul {{loopback-boot}}",
|
||||||
|
"d9ef6dc3770dd8f80a129e92a79851f3": "{0} ist veraltet. Siehe {1} für weitere Details.",
|
||||||
|
"dc568bee32deb0f6eaf63e73b20e8ceb": "Nicht-Objekt-Einstellung \"{0}\" von \"methods\" wird ignoriert.",
|
||||||
|
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" unbekannt, {{id}} \"{1}\".",
|
||||||
|
"e92aa25b6b864e3454b65a7c422bd114": "Massenaktualisierung fehlgeschlagen, der Konnektor hat eine unerwartete Anzahl an Datensätzen gelöscht : {0}",
|
||||||
|
"ea63d226b6968e328bdf6876010786b5": "Massenaktualisierungen können nicht angewendet werden, der Konnektor meldet die Anzahl gelöschter Datensätze nicht richtig.",
|
||||||
|
"ead044e2b4bce74b4357f8a03fb78ec4": "{0}.{1}() kann nicht aufgerufen werden. Die Methode {2} wurde nicht konfiguriert. {{KeyValueModel}} wurde nicht ordnungsgemäß an eine {{DataSource}} angehängt!",
|
||||||
|
"ecb06666ef95e5db27a5ac1d6a17923b": "\t AN:{0}",
|
||||||
|
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORTMETHODE:{0}",
|
||||||
|
"f0bd73df8714cefb925e3b8da2f4c5f6": "Ergebnis:{0}",
|
||||||
|
"f1d4ac54357cc0932f385d56814ba7e4": "Konflikt",
|
||||||
|
"f58cdc481540cd1f69a4aa4da2e37981": "Ungültiges Kennwort: {0}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
{
|
||||||
|
"03f79fa268fe199de2ce4345515431c1": "No change record found for {0} with id {1}",
|
||||||
|
"04bd8af876f001ceaf443aad6a9002f9": "Authentication requires model {0} to be defined.",
|
||||||
|
"0731d0109e46c21a4e34af3346ed4856": "This behaviour may change in the next major version.",
|
||||||
|
"095afbf2f1f0e5be678f5dac5c54e717": "Access Denied",
|
||||||
|
"0caffe1d763c8cca6a61814abe33b776": "Email is required",
|
||||||
|
"10e01c895dc0b2fecc385f9f462f1ca6": "a list of colors is available at {{http://localhost:3000/colors}}",
|
||||||
|
"1b2a6076dccbe91a56f1672eb3b8598c": "The response body contains properties of the {{AccessToken}} created on login.\nDepending on the value of `include` parameter, the body may contain additional properties:\n\n - `user` - `U+007BUserU+007D` - Data of the currently logged in user. {{(`include=user`)}}\n\n",
|
||||||
|
"1d7833c3ca2f05fdad8fad7537531c40": "\t SUBJECT:{0}",
|
||||||
|
"1e85f822b547a75d7d385048030e4ecb": "Created: {0}",
|
||||||
|
"275f22ab95671f095640ca99194b7635": "\t FROM:{0}",
|
||||||
|
"2d3071e3b18681c80a090dc0efbdb349": "could not find {0} with id {1}",
|
||||||
|
"2d78192c43fd2ec52ec18f3918894f9a": "{0} middleware is deprecated. See {1} for more details.",
|
||||||
|
"308e1d484516a33df788f873e65faaff": "Model `{0}` is extending deprecated `DataModel. Use `PersistedModel` instead.",
|
||||||
|
"316e5b82c203cf3de31a449ee07d0650": "Expected boolean, got {0}",
|
||||||
|
"320c482401afa1207c04343ab162e803": "Invalid principal type: {0}",
|
||||||
|
"3438fab56cc7ab92dfd88f0497e523e0": "The relations property of `{0}` configuration must be an object",
|
||||||
|
"35e5252c62d80f8c54a5290d30f4c7d0": "Please verify your email by opening this link in a web browser:\n\t{0}",
|
||||||
|
"3aae63bb7e8e046641767571c1591441": "login failed as the email has not been verified",
|
||||||
|
"3aecb24fa8bdd3f79d168761ca8a6729": "Unknown {{middleware}} phase {0}",
|
||||||
|
"3caaa84fc103d6d5612173ae6d43b245": "Invalid token: {0}",
|
||||||
|
"3d617953470be16d0c2b32f0bcfbb5ee": "Thanks for Registering",
|
||||||
|
"3d63008ccfb2af1db2142e8cc2716ace": "Warning: No email transport specified for sending email. Setup a transport to send mail messages.",
|
||||||
|
"4203ab415ec66a78d3164345439ba76e": "Cannot call {0}.{1}(). The {2} method has not been setup. The {{PersistedModel}} has not been correctly attached to a {{DataSource}}!",
|
||||||
|
"44a6c8b1ded4ed653d19ddeaaf89a606": "Email not found",
|
||||||
|
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Sending Mail:",
|
||||||
|
"4b494de07f524703ac0879addbd64b13": "Email has not been verified",
|
||||||
|
"4cac5f051ae431321673e04045d37772": "Model `{0}` is extending an unknown model `{1}`. Using `PersistedModel` as the base.",
|
||||||
|
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Cannot create data source {0}: {1}",
|
||||||
|
"5858e63efaa0e4ad86b61c0459ea32fa": "You must connect the {{Email}} Model to a {{Mail}} connector",
|
||||||
|
"5e81ad3847a290dc650b47618b9cbc7e": "login failed",
|
||||||
|
"5fa3afb425819ebde958043e598cb664": "could not find a model with {{id}} {0}",
|
||||||
|
"61e5deebaf44d68f4e6a508f30cc31a3": "Relation `{0}` does not exist for model `{1}`",
|
||||||
|
"62e8b0a733417978bab22c8dacf5d7e6": "Cannot apply bulk updates, the connector does not correctly report the number of updated records.",
|
||||||
|
"63a091ced88001ab6acb58f61ec041c5": "\t TEXT:{0}",
|
||||||
|
"6bc376432cd9972cf991aad3de371e78": "Missing data for change: {0}",
|
||||||
|
"705c2d456a3e204c4af56e671ec3225c": "Could not find {{accessToken}}",
|
||||||
|
"734a7bebb65e10899935126ba63dd51f": "The options property of `{0}` configuration must be an object",
|
||||||
|
"779467f467862836e19f494a37d6ab77": "The acls property of `{0}` configuration must be an array of objects",
|
||||||
|
"7d5e7ed0efaedf3f55f380caae0df8b8": "My first mobile application",
|
||||||
|
"7e0fca41d098607e1c9aa353c67e0fa1": "Invalid Access Token",
|
||||||
|
"7e287fc885d9fdcf42da3a12f38572c1": "Authorization Required",
|
||||||
|
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} is required to logout",
|
||||||
|
"80a32e80cbed65eba2103201a7c94710": "Model not found: {0}",
|
||||||
|
"83cbdc2560ba9f09155ccfc63e08f1a1": "Property `{0}` cannot be reconfigured for `{1}`",
|
||||||
|
"855ecd4a64885ba272d782435f72a4d4": "Unknown \"{0}\" id \"{1}\".",
|
||||||
|
"860d1a0b8bd340411fb32baa72867989": "The transport does not support HTTP redirects.",
|
||||||
|
"895b1f941d026870b3cc8e6af087c197": "{{username}} or {{email}} is required",
|
||||||
|
"8a17c5ef611e2e7535792316e66b8fca": "Password too long: {0}",
|
||||||
|
"8a27e0c9ce3ebf0e0c3978efb456e13e": "Request to host {0}",
|
||||||
|
"8ae418c605b6a45f2651be9b1677c180": "Invalid remote method: `{0}`",
|
||||||
|
"8bab6720ecc58ec6412358c858a53484": "Bulk update failed, the connector has modified unexpected number of records: {0}",
|
||||||
|
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
|
||||||
|
"97795efe0c3eb7f35ce8cf8cfe70682b": "The configuration of `{0}` is missing {{`dataSource`}} property.\nUse `null` or `false` to mark models not attached to any data source.",
|
||||||
|
"a40684f5a9f546115258b76938d1de37": "A list of colors is available at {{http://localhost:3000/colors}}",
|
||||||
|
"a50d10fc6e0959b220e085454c40381e": "User not found: {0}",
|
||||||
|
"a80038252430df2754884bf3c845c4cf": "Remoting metadata for \"{0}.{1}\" is missing \"isStatic\" flag, the method is registered as an instance method.",
|
||||||
|
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "Unknown \"{0}\" {{key}} \"{1}\".",
|
||||||
|
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} is required",
|
||||||
|
"c2b5d51f007178170ca3952d59640ca4": "Cannot rectify {0} changes:\n{1}",
|
||||||
|
"c68a93f0a9524fed4ff64372fc90c55f": "Must provide a valid email",
|
||||||
|
"cd0412f2f33a4a2a316acc834f3f21a6": "must specify an {{id}} or {{data}}",
|
||||||
|
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} was removed, use the new module {{loopback-boot}} instead",
|
||||||
|
"d9ef6dc3770dd8f80a129e92a79851f3": "{0} is deprecated. See {1} for more details.",
|
||||||
|
"dc568bee32deb0f6eaf63e73b20e8ceb": "Ignoring non-object \"methods\" setting of \"{0}\".",
|
||||||
|
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "Unknown \"{0}\" {{id}} \"{1}\".",
|
||||||
|
"e92aa25b6b864e3454b65a7c422bd114": "Bulk update failed, the connector has deleted unexpected number of records: {0}",
|
||||||
|
"ea63d226b6968e328bdf6876010786b5": "Cannot apply bulk updates, the connector does not correctly report the number of deleted records.",
|
||||||
|
"ead044e2b4bce74b4357f8a03fb78ec4": "Cannot call {0}.{1}(). The {2} method has not been setup. The {{KeyValueModel}} has not been correctly attached to a {{DataSource}}!",
|
||||||
|
"ecb06666ef95e5db27a5ac1d6a17923b": "\t TO:{0}",
|
||||||
|
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORT:{0}",
|
||||||
|
"f0bd73df8714cefb925e3b8da2f4c5f6": "result:{0}",
|
||||||
|
"f1d4ac54357cc0932f385d56814ba7e4": "Conflict",
|
||||||
|
"f58cdc481540cd1f69a4aa4da2e37981": "Invalid password: {0}"
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"03f79fa268fe199de2ce4345515431c1": "No se ha encontrado ningún registro de cambio para {0} con el id {1}",
|
||||||
|
"04bd8af876f001ceaf443aad6a9002f9": "La autenticación requiere la definición del modelo {0}.",
|
||||||
|
"0731d0109e46c21a4e34af3346ed4856": "Este comportamiento puede cambiar en la próxima versión principal.",
|
||||||
|
"095afbf2f1f0e5be678f5dac5c54e717": "Acceso denegado",
|
||||||
|
"0caffe1d763c8cca6a61814abe33b776": "Es necesario el correo electrónico",
|
||||||
|
"10e01c895dc0b2fecc385f9f462f1ca6": "una lista de colores está disponible en {{http://localhost:3000/colors}}",
|
||||||
|
"1b2a6076dccbe91a56f1672eb3b8598c": "El cuerpo de respuesta contiene propiedades de la {{AccessToken}} creada durante el inicio de la sesión.\nDependiendo del valor del parámetro `include`, el cuerpo puede contener propiedades adicionales:\n\n - `user` - `U+007BUserU+007D` - Datos del usuario conectado actualmente. {{(`include=user`)}}\n\n",
|
||||||
|
"1d7833c3ca2f05fdad8fad7537531c40": "\t ASUNTO:{0}",
|
||||||
|
"1e85f822b547a75d7d385048030e4ecb": "Creado: {0}",
|
||||||
|
"275f22ab95671f095640ca99194b7635": "\t DESDE:{0}",
|
||||||
|
"2d3071e3b18681c80a090dc0efbdb349": "no se ha encontrado {0} con el ID {1}",
|
||||||
|
"2d78192c43fd2ec52ec18f3918894f9a": "El middleware {0} está en desuso. Consulte {1} para obtener detalles.",
|
||||||
|
"308e1d484516a33df788f873e65faaff": "El modelo `{0}` está ampliando `DataModel` en desuso. Utilice `PersistedModel` en su lugar.",
|
||||||
|
"316e5b82c203cf3de31a449ee07d0650": "Se esperaba un booleano, se ha obtenido {0}",
|
||||||
|
"320c482401afa1207c04343ab162e803": "Tipo de principal no válido: {0}",
|
||||||
|
"3438fab56cc7ab92dfd88f0497e523e0": "La configuración de la propiedad relations de `{0}` debe ser un objeto",
|
||||||
|
"35e5252c62d80f8c54a5290d30f4c7d0": "Verifique su correo electrónico abriendo este enlace en un navegador:\n\t {0}",
|
||||||
|
"3aae63bb7e8e046641767571c1591441": "el inicio de sesión ha fallado porque el correo electrónico no ha sido verificado",
|
||||||
|
"3aecb24fa8bdd3f79d168761ca8a6729": "Fase de {{middleware}} desconocida {0}",
|
||||||
|
"3caaa84fc103d6d5612173ae6d43b245": "La señal no es válida: {0}",
|
||||||
|
"3d617953470be16d0c2b32f0bcfbb5ee": "Gracias por registrarse",
|
||||||
|
"3d63008ccfb2af1db2142e8cc2716ace": "Aviso: No se ha especificado ningún transporte de correo electrónico para enviar correo electrónico. Configure un transporte para enviar mensajes de correo.",
|
||||||
|
"4203ab415ec66a78d3164345439ba76e": "No se puede llamar a {0}.{1}(). El método {2} no se ha configurado. {{PersistedModel}} no se ha conectado correctamente a un {{DataSource}}.",
|
||||||
|
"44a6c8b1ded4ed653d19ddeaaf89a606": "Correo electrónico no encontrado",
|
||||||
|
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Enviando correo:",
|
||||||
|
"4b494de07f524703ac0879addbd64b13": "El correo electrónico no se ha verificado",
|
||||||
|
"4cac5f051ae431321673e04045d37772": "El modelo `{0}` está ampliando un modelo desconocido `{1}`. Se utiliza `PersistedModel` como base.",
|
||||||
|
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "No se puede crear el origen de datos {0}: {1}",
|
||||||
|
"5858e63efaa0e4ad86b61c0459ea32fa": "Debe conectar el modelo de {{Email}} a un conector de {{Mail}}",
|
||||||
|
"5e81ad3847a290dc650b47618b9cbc7e": "el inicio de sesión ha fallado",
|
||||||
|
"5fa3afb425819ebde958043e598cb664": "no se ha encontrado un modelo con {{id}} {0}",
|
||||||
|
"61e5deebaf44d68f4e6a508f30cc31a3": "La relación `{0}` no existe para el modelo `{1}`",
|
||||||
|
"62e8b0a733417978bab22c8dacf5d7e6": "No pueden aplicarse actualizaciones masivas, el conector no notifica correctamente el número de registros actualizados.",
|
||||||
|
"63a091ced88001ab6acb58f61ec041c5": "\t TEXTO:{0}",
|
||||||
|
"6bc376432cd9972cf991aad3de371e78": "Faltan datos para el cambio: {0}",
|
||||||
|
"705c2d456a3e204c4af56e671ec3225c": "No se ha encontrado {{accessToken}}",
|
||||||
|
"734a7bebb65e10899935126ba63dd51f": "La configuración de la propiedad de options de `{0}` debe ser un objeto",
|
||||||
|
"779467f467862836e19f494a37d6ab77": "La configuración de la propiedad acls de `{0}` debe ser una matriz de objetos",
|
||||||
|
"7d5e7ed0efaedf3f55f380caae0df8b8": "Mi primera aplicación móvil",
|
||||||
|
"7e0fca41d098607e1c9aa353c67e0fa1": "Señal de acceso no válida",
|
||||||
|
"7e287fc885d9fdcf42da3a12f38572c1": "Autorización necesaria",
|
||||||
|
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "Es necesario {{accessToken}} para cerrar la sesión",
|
||||||
|
"80a32e80cbed65eba2103201a7c94710": "No se ha encontrado el modelo: {0}",
|
||||||
|
"83cbdc2560ba9f09155ccfc63e08f1a1": "La propiedad `{0}` no puede reconfigurarse para `{1}`",
|
||||||
|
"855ecd4a64885ba272d782435f72a4d4": "Id de \"{0}\" desconocido \"{1}\".",
|
||||||
|
"860d1a0b8bd340411fb32baa72867989": "El transporte no admite redirecciones HTTP.",
|
||||||
|
"895b1f941d026870b3cc8e6af087c197": "{{username}} o {{email}} es obligatorio",
|
||||||
|
"8a17c5ef611e2e7535792316e66b8fca": "Contraseña demasiado larga: {0}",
|
||||||
|
"8a27e0c9ce3ebf0e0c3978efb456e13e": "Solicitud al host {0}",
|
||||||
|
"8ae418c605b6a45f2651be9b1677c180": "Método remoto no válido: `{0}`",
|
||||||
|
"8bab6720ecc58ec6412358c858a53484": "La actualización masiva ha fallado, el conector ha modificado un número de registros inesperado: {0}",
|
||||||
|
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
|
||||||
|
"97795efe0c3eb7f35ce8cf8cfe70682b": "En la configuración de `{0}` falta la propiedad {{`dataSource`}}.\nUtilice `null` o `false` para marcar los modelos no conectados a ningún origen de datos.",
|
||||||
|
"a40684f5a9f546115258b76938d1de37": "Una lista de colores está disponible en {{http://localhost:3000/colors}}",
|
||||||
|
"a50d10fc6e0959b220e085454c40381e": "No se ha encontrado el usuario: {0}",
|
||||||
|
"a80038252430df2754884bf3c845c4cf": "En los metadatos de interacción remota para \"{0}.{1}\" falta el indicador \"isStatic\", el método está registrado como método de instancia.",
|
||||||
|
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "{{key}} de \"{0}\" desconocido \"{1}\".",
|
||||||
|
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} es obligatorio",
|
||||||
|
"c2b5d51f007178170ca3952d59640ca4": "No se pueden rectificar los cambios de {0}:\n{1}",
|
||||||
|
"c68a93f0a9524fed4ff64372fc90c55f": "Debe proporcionar un correo electrónico válido",
|
||||||
|
"cd0412f2f33a4a2a316acc834f3f21a6": "debe especificar un {{id}} o {{data}}",
|
||||||
|
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} se ha eliminado, utilice el nuevo módulo {{loopback-boot}} en su lugar",
|
||||||
|
"d9ef6dc3770dd8f80a129e92a79851f3": "{0} está en desuso. Consulte {1} para obtener detalles.",
|
||||||
|
"dc568bee32deb0f6eaf63e73b20e8ceb": "Se ignora el valor \"methods\" no de objeto de \"{0}\".",
|
||||||
|
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "{{id}} de \"{0}\" desconocido \"{1}\".",
|
||||||
|
"e92aa25b6b864e3454b65a7c422bd114": "La actualización masiva ha fallado, el conector ha suprimido un número de registros inesperado: {0}",
|
||||||
|
"ea63d226b6968e328bdf6876010786b5": "No pueden aplicarse actualizaciones masivas, el conector no notifica correctamente el número de registros suprimidos.",
|
||||||
|
"ead044e2b4bce74b4357f8a03fb78ec4": "No se puede llamar a {0}.{1}(). El método {2} no se ha configurado. {{KeyValueModel}} no se ha conectado correctamente a un {{DataSource}}.",
|
||||||
|
"ecb06666ef95e5db27a5ac1d6a17923b": "\t A:{0}",
|
||||||
|
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORTE:{0}",
|
||||||
|
"f0bd73df8714cefb925e3b8da2f4c5f6": "resultado:{0}",
|
||||||
|
"f1d4ac54357cc0932f385d56814ba7e4": "Conflicto",
|
||||||
|
"f58cdc481540cd1f69a4aa4da2e37981": "Contraseña no válida: {0}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"03f79fa268fe199de2ce4345515431c1": "Aucun enregistrement de changement trouvé pour {0} avec l'id {1}",
|
||||||
|
"04bd8af876f001ceaf443aad6a9002f9": "L'authentification exige que le modèle {0} soit défini.",
|
||||||
|
"0731d0109e46c21a4e34af3346ed4856": "Ce comportement peut changer dans la version principale suivante.",
|
||||||
|
"095afbf2f1f0e5be678f5dac5c54e717": "Accès refusé",
|
||||||
|
"0caffe1d763c8cca6a61814abe33b776": "L'adresse électronique est obligatoire",
|
||||||
|
"10e01c895dc0b2fecc385f9f462f1ca6": "une liste de couleurs est disponible sur {{http://localhost:3000/colors}}",
|
||||||
|
"1b2a6076dccbe91a56f1672eb3b8598c": "Le corps de réponse contient les propriétés de {{AccessToken}} créées lors de la connexion.\nEn fonction de la valeur du paramètre `include`, le corps peut contenir des propriétés supplémentaires :\n\n - `user` - `U+007BUserU+007D` - Données de l'utilisateur connecté. {{(`include=user`)}}\n\n",
|
||||||
|
"1d7833c3ca2f05fdad8fad7537531c40": "\t SUJET :{0}",
|
||||||
|
"1e85f822b547a75d7d385048030e4ecb": "Création de : {0}",
|
||||||
|
"275f22ab95671f095640ca99194b7635": "\t DE :{0}",
|
||||||
|
"2d3071e3b18681c80a090dc0efbdb349": "impossible de trouver {0} avec l'id {1}",
|
||||||
|
"2d78192c43fd2ec52ec18f3918894f9a": "Le middleware {0} est obsolète. Pour plus de détails, voir {1}.",
|
||||||
|
"308e1d484516a33df788f873e65faaff": "Le modèle `{0}` étend le `DataModel obsolète. Utilisez à la place `PersistedModel`.",
|
||||||
|
"316e5b82c203cf3de31a449ee07d0650": "Valeur booléenne attendue, {0} obtenu",
|
||||||
|
"320c482401afa1207c04343ab162e803": "Type de principal non valide : {0}",
|
||||||
|
"3438fab56cc7ab92dfd88f0497e523e0": "La propriété relations de la configuration `{0}` doit être un objet",
|
||||||
|
"35e5252c62d80f8c54a5290d30f4c7d0": "Vérifiez votre courrier électronique en ouvrant ce lien dans un navigateur Web :\n\t{0}",
|
||||||
|
"3aae63bb7e8e046641767571c1591441": "la connexion a échoué car l'adresse électronique n'a pas été vérifiée",
|
||||||
|
"3aecb24fa8bdd3f79d168761ca8a6729": "Phase {{middleware}} inconnue {0}",
|
||||||
|
"3caaa84fc103d6d5612173ae6d43b245": "Jeton non valide : {0}",
|
||||||
|
"3d617953470be16d0c2b32f0bcfbb5ee": "Merci pour votre inscription",
|
||||||
|
"3d63008ccfb2af1db2142e8cc2716ace": "Avertissement : Aucun transport de courrier électronique n'est spécifié pour l'envoi d'un message électronique. Configurez un transport pour envoyer des messages électroniques.",
|
||||||
|
"4203ab415ec66a78d3164345439ba76e": "Impossible d'appeler {0}.{1}(). La méthode {2} n'a pas été configurée. {{PersistedModel}} n'a pas été associé correctement à {{DataSource}} !",
|
||||||
|
"44a6c8b1ded4ed653d19ddeaaf89a606": "Adresse électronique introuvable",
|
||||||
|
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Envoi d'un message électronique :",
|
||||||
|
"4b494de07f524703ac0879addbd64b13": "Le courrier électronique n'a pas été vérifié",
|
||||||
|
"4cac5f051ae431321673e04045d37772": "Le modèle `{0}` étend un modèle inconnu `{1}`. Utilisation de `PersistedModel` comme base.",
|
||||||
|
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Impossible de créer la source de données {0} : {1}",
|
||||||
|
"5858e63efaa0e4ad86b61c0459ea32fa": "Vous devez connecter le modèle {{Email}} à un connecteur {{Mail}}",
|
||||||
|
"5e81ad3847a290dc650b47618b9cbc7e": "échec de la connexion",
|
||||||
|
"5fa3afb425819ebde958043e598cb664": "impossible de trouver un modèle avec {{id}} {0}",
|
||||||
|
"61e5deebaf44d68f4e6a508f30cc31a3": "La relation `{0}` n'existe pas pour le modèle `{1}`",
|
||||||
|
"62e8b0a733417978bab22c8dacf5d7e6": "Impossible d'appliquer des mises à jour en bloc ; le connecteur ne signale pas correctement le nombre d'enregistrements mis à jour.",
|
||||||
|
"63a091ced88001ab6acb58f61ec041c5": "\t TEXTE :{0}",
|
||||||
|
"6bc376432cd9972cf991aad3de371e78": "Données manquantes pour le changement : {0}",
|
||||||
|
"705c2d456a3e204c4af56e671ec3225c": "{{accessToken}} introuvable",
|
||||||
|
"734a7bebb65e10899935126ba63dd51f": "La propriété options de la configuration `{0}` doit être un objet",
|
||||||
|
"779467f467862836e19f494a37d6ab77": "La propriété acls de la configuration `{0}` doit être un tableau d'objets",
|
||||||
|
"7d5e7ed0efaedf3f55f380caae0df8b8": "Ma première application mobile",
|
||||||
|
"7e0fca41d098607e1c9aa353c67e0fa1": "Jeton d'accès non valide",
|
||||||
|
"7e287fc885d9fdcf42da3a12f38572c1": "Autorisation requise",
|
||||||
|
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} est nécessaire pour la déconnexion",
|
||||||
|
"80a32e80cbed65eba2103201a7c94710": "Modèle introuvable : {0}",
|
||||||
|
"83cbdc2560ba9f09155ccfc63e08f1a1": "La propriété `{0}` ne peut pas être reconfigurée pour `{1}`",
|
||||||
|
"855ecd4a64885ba272d782435f72a4d4": "ID \"{0}\" inconnu \"{1}\".",
|
||||||
|
"860d1a0b8bd340411fb32baa72867989": "Le transport ne prend pas en charge les réacheminements HTTP.",
|
||||||
|
"895b1f941d026870b3cc8e6af087c197": "{{username}} ou {{email}} est obligatoire",
|
||||||
|
"8a17c5ef611e2e7535792316e66b8fca": "Mot de passe trop long : {0}",
|
||||||
|
"8a27e0c9ce3ebf0e0c3978efb456e13e": "Demande à l'hôte {0}",
|
||||||
|
"8ae418c605b6a45f2651be9b1677c180": "Méthode distante non valide : `{0}`",
|
||||||
|
"8bab6720ecc58ec6412358c858a53484": "La mise à jour en bloc a échoué ; le connecteur a modifié un nombre inattendu d'enregistrements : {0}",
|
||||||
|
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML :{0}",
|
||||||
|
"97795efe0c3eb7f35ce8cf8cfe70682b": "La propriété {{`dataSource`}} est manquante dans la configuration de `{0}`.\nUtilisez `null` ou `false` pour marquer les modèles non associés à une source de données.",
|
||||||
|
"a40684f5a9f546115258b76938d1de37": "Une liste de couleurs est disponible sur {{http://localhost:3000/colors}}",
|
||||||
|
"a50d10fc6e0959b220e085454c40381e": "Utilisateur introuvable : {0}",
|
||||||
|
"a80038252430df2754884bf3c845c4cf": "Métadonnées remoting pour \"{0}.{1}\" ne comporte pas l'indicateur \"isStatic\" ; la méthode est enregistrée en tant que méthode instance.",
|
||||||
|
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "\"{0}\" {{key}} \"{1}\" inconnu.",
|
||||||
|
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} est obligatoire",
|
||||||
|
"c2b5d51f007178170ca3952d59640ca4": "Impossible de rectifier les modifications {0} :\n{1}",
|
||||||
|
"c68a93f0a9524fed4ff64372fc90c55f": "Obligation de fournir une adresse électronique valide",
|
||||||
|
"cd0412f2f33a4a2a316acc834f3f21a6": "obligation de spécifier {{id}} ou {{data}}",
|
||||||
|
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} a été supprimé ; utilisez à la place le nouveau module {{loopback-boot}}",
|
||||||
|
"d9ef6dc3770dd8f80a129e92a79851f3": "{0} est obsolète. Pour plus de détails, voir {1}.",
|
||||||
|
"dc568bee32deb0f6eaf63e73b20e8ceb": "Le paramètre \"methods\" non objet de \"{0}\" est ignoré.",
|
||||||
|
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" {{id}} \"{1}\" inconnu.",
|
||||||
|
"e92aa25b6b864e3454b65a7c422bd114": "La mise à jour en bloc a échoué ; le connecteur a supprimé un nombre inattendu d'enregistrements : {0}",
|
||||||
|
"ea63d226b6968e328bdf6876010786b5": "Impossible d'appliquer des mises à jour en bloc ; le connecteur ne signale pas correctement le nombre d'enregistrements supprimés.",
|
||||||
|
"ead044e2b4bce74b4357f8a03fb78ec4": "Impossible d'appeler {0}.{1}(). La méthode {2} n'a pas été configurée. {{KeyValueModel}} n'a pas été associé correctement à {{DataSource}} !",
|
||||||
|
"ecb06666ef95e5db27a5ac1d6a17923b": "\t A :{0}",
|
||||||
|
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORT :{0}",
|
||||||
|
"f0bd73df8714cefb925e3b8da2f4c5f6": "résultat :{0}",
|
||||||
|
"f1d4ac54357cc0932f385d56814ba7e4": "Conflit",
|
||||||
|
"f58cdc481540cd1f69a4aa4da2e37981": "Mot de passe non valide : {0}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"03f79fa268fe199de2ce4345515431c1": "Nessun record di modifica trovato per {0} con id {1}",
|
||||||
|
"04bd8af876f001ceaf443aad6a9002f9": "L'autenticazione richiede che sia definito il modello {0}.",
|
||||||
|
"0731d0109e46c21a4e34af3346ed4856": "Questo funzionamento può essere modificato nella versione principale successiva.",
|
||||||
|
"095afbf2f1f0e5be678f5dac5c54e717": "Accesso negato",
|
||||||
|
"0caffe1d763c8cca6a61814abe33b776": "L'email è obbligatoria",
|
||||||
|
"10e01c895dc0b2fecc385f9f462f1ca6": "un elenco dei colori è disponibile all'indirizzo {{http://localhost:3000/colors}}",
|
||||||
|
"1b2a6076dccbe91a56f1672eb3b8598c": "Il corpo della risposta contiene proprietà del {{AccessToken}} creato all'accesso.\nIn base al valore del parametro `include`, il corpo può contenere ulteriori proprietà:\n\n - `user` - `U+007BUserU+007D` - Dati dell'utente attualmente collegato.. {{(`include=user`)}}\n\n",
|
||||||
|
"1d7833c3ca2f05fdad8fad7537531c40": "\t OGGETTO:{0}",
|
||||||
|
"1e85f822b547a75d7d385048030e4ecb": "Creato: {0}",
|
||||||
|
"275f22ab95671f095640ca99194b7635": "\t DA:{0}",
|
||||||
|
"2d3071e3b18681c80a090dc0efbdb349": "impossibile trovare {0} con id {1}",
|
||||||
|
"2d78192c43fd2ec52ec18f3918894f9a": "{0} middleware is deprecated. Consultare {1} per ulteriori dettagli.",
|
||||||
|
"308e1d484516a33df788f873e65faaff": "Il modello `{0}` estende il modello `DataModel obsoleto. Utilizzare `PersistedModel`.",
|
||||||
|
"316e5b82c203cf3de31a449ee07d0650": "Previsto valore booleano, ricevuto {0}",
|
||||||
|
"320c482401afa1207c04343ab162e803": "Tipo principal non valido: {0}",
|
||||||
|
"3438fab56cc7ab92dfd88f0497e523e0": "La proprietà relations della configurazione `{0}` deve essere un oggetto",
|
||||||
|
"35e5252c62d80f8c54a5290d30f4c7d0": "Verificare la e-mail aprendo questo link in un browser web:\n\t{0}",
|
||||||
|
"3aae63bb7e8e046641767571c1591441": "login non riuscito perché l'email non è stata verificata",
|
||||||
|
"3aecb24fa8bdd3f79d168761ca8a6729": "Fase {{middleware}} sconosciuta {0}",
|
||||||
|
"3caaa84fc103d6d5612173ae6d43b245": "Token non valido: {0}",
|
||||||
|
"3d617953470be16d0c2b32f0bcfbb5ee": "Grazie per essersi registrati",
|
||||||
|
"3d63008ccfb2af1db2142e8cc2716ace": "Avvertenza: nessun trasporto email specificato per l'invio della email. Configurare un trasporto per inviare messaggi email.",
|
||||||
|
"4203ab415ec66a78d3164345439ba76e": "Impossibile richiamare {0}.{1}(). Il metodo {2} non è stato configurato. {{PersistedModel}} non è stato correttamente collegato ad una {{DataSource}}!",
|
||||||
|
"44a6c8b1ded4ed653d19ddeaaf89a606": "Email non trovata",
|
||||||
|
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Invio email:",
|
||||||
|
"4b494de07f524703ac0879addbd64b13": "La e-mail non è stata verificata",
|
||||||
|
"4cac5f051ae431321673e04045d37772": "Il modello `{0}` estende un modello sconosciuto `{1}`. Viene utilizzato `PersistedModel` come base.",
|
||||||
|
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Impossibile creare l'origine dati {0}: {1}",
|
||||||
|
"5858e63efaa0e4ad86b61c0459ea32fa": "È necessario collegare il modello {{Email}} ad un connettore {{Mail}}",
|
||||||
|
"5e81ad3847a290dc650b47618b9cbc7e": "login non riuscito",
|
||||||
|
"5fa3afb425819ebde958043e598cb664": "impossibile trovare un modello con {{id}} {0}",
|
||||||
|
"61e5deebaf44d68f4e6a508f30cc31a3": "La relazione `{0}` non esiste per il modello `{1}`",
|
||||||
|
"62e8b0a733417978bab22c8dacf5d7e6": "Impossibile applicare gli aggiornamenti in massa, il connettore non indica correttamente il numero di record aggiornati.",
|
||||||
|
"63a091ced88001ab6acb58f61ec041c5": "\t TESTO:{0}",
|
||||||
|
"6bc376432cd9972cf991aad3de371e78": "Dati mancanti per la modifica: {0}",
|
||||||
|
"705c2d456a3e204c4af56e671ec3225c": "Could not find {{accessToken}}",
|
||||||
|
"734a7bebb65e10899935126ba63dd51f": "La proprietà options della configurazione `{0}` deve essere un oggetto",
|
||||||
|
"779467f467862836e19f494a37d6ab77": "La proprietà acls della configurazione `{0}` deve essere un array di oggetti",
|
||||||
|
"7d5e7ed0efaedf3f55f380caae0df8b8": "Prima applicazione mobile personale",
|
||||||
|
"7e0fca41d098607e1c9aa353c67e0fa1": "Token di accesso non valido",
|
||||||
|
"7e287fc885d9fdcf42da3a12f38572c1": "Autorizzazione richiesta",
|
||||||
|
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} is required to logout",
|
||||||
|
"80a32e80cbed65eba2103201a7c94710": "Modello non trovato: {0}",
|
||||||
|
"83cbdc2560ba9f09155ccfc63e08f1a1": "Impossibile riconfigurare la proprietà `{0}` per `{1}`",
|
||||||
|
"855ecd4a64885ba272d782435f72a4d4": "ID sconosciuto \"{0}\" \"{1}\".",
|
||||||
|
"860d1a0b8bd340411fb32baa72867989": "Il trasporto non supporta i reindirizzamenti HTTP.",
|
||||||
|
"895b1f941d026870b3cc8e6af087c197": "Sono richiesti {{username}} o {{email}}",
|
||||||
|
"8a17c5ef611e2e7535792316e66b8fca": "Password troppo lunga: {0}",
|
||||||
|
"8a27e0c9ce3ebf0e0c3978efb456e13e": "Richiesta all'host {0}",
|
||||||
|
"8ae418c605b6a45f2651be9b1677c180": "Metodo remoto non valido: `{0}`",
|
||||||
|
"8bab6720ecc58ec6412358c858a53484": "Aggiornamento in massa non riuscito, il connettore ha modificato un numero non previsto di record: {0}",
|
||||||
|
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
|
||||||
|
"97795efe0c3eb7f35ce8cf8cfe70682b": "La configurazione di `{0}` non contiene la proprietà {{`dataSource`}}.\nUtilizzare `null` o `false` per contrassegnare i modelli non collegati ad alcuna origine dati.",
|
||||||
|
"a40684f5a9f546115258b76938d1de37": "Un elenco dei colori è disponibile all'indirizzo {{http://localhost:3000/colors}}",
|
||||||
|
"a50d10fc6e0959b220e085454c40381e": "Utente non trovato: {0}",
|
||||||
|
"a80038252430df2754884bf3c845c4cf": "Metadati della comunicazione in remoto per \"{0}.{1}\" non presenta l'indicatore \"isStatic\", il metodo è registrato come metodo dell'istanza.",
|
||||||
|
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "{{key}} \"{0}\" sconosciuto \"{1}\".",
|
||||||
|
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} è obbligatorio",
|
||||||
|
"c2b5d51f007178170ca3952d59640ca4": "Impossibile correggere {0} modifiche:\n{1}",
|
||||||
|
"c68a93f0a9524fed4ff64372fc90c55f": "È necessario fornire una email valida",
|
||||||
|
"cd0412f2f33a4a2a316acc834f3f21a6": "è necessario specificare {{id}} o {{data}}",
|
||||||
|
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} è stato rimosso, utilizzare il nuovo modulo {{loopback-boot}}",
|
||||||
|
"d9ef6dc3770dd8f80a129e92a79851f3": "{0} is deprecated. Consultare {1} per ulteriori dettagli.",
|
||||||
|
"dc568bee32deb0f6eaf63e73b20e8ceb": "L'impostazione \"methods\" non oggetto di \"{0}\" viene ignorata.",
|
||||||
|
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "{{id}} \"{0}\" sconosciuto \"{1}\".",
|
||||||
|
"e92aa25b6b864e3454b65a7c422bd114": "Aggiornamento in massa non riuscito, il connettore ha eliminato un numero non previsto di record: {0}",
|
||||||
|
"ea63d226b6968e328bdf6876010786b5": "Impossibile applicare gli aggiornamenti in massa, il connettore non indica correttamente il numero di record eliminati.",
|
||||||
|
"ead044e2b4bce74b4357f8a03fb78ec4": "Impossibile richiamare {0}.{1}(). Il metodo {2} non è stato configurato. {{KeyValueModel}} non è stato correttamente collegato ad una {{DataSource}}!",
|
||||||
|
"ecb06666ef95e5db27a5ac1d6a17923b": "\t A:{0}",
|
||||||
|
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRASPORTO:{0}",
|
||||||
|
"f0bd73df8714cefb925e3b8da2f4c5f6": "risultato:{0}",
|
||||||
|
"f1d4ac54357cc0932f385d56814ba7e4": "Conflitto",
|
||||||
|
"f58cdc481540cd1f69a4aa4da2e37981": "Password non valida: {0}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"03f79fa268fe199de2ce4345515431c1": "ID {1} の {0} の変更レコードが見つかりませんでした",
|
||||||
|
"04bd8af876f001ceaf443aad6a9002f9": "認証では、モデル {0} を定義する必要があります。",
|
||||||
|
"0731d0109e46c21a4e34af3346ed4856": "この動作は次のメジャー・バージョンで変更される可能性があります。",
|
||||||
|
"095afbf2f1f0e5be678f5dac5c54e717": "アクセス拒否",
|
||||||
|
"0caffe1d763c8cca6a61814abe33b776": "E メールは必須です",
|
||||||
|
"10e01c895dc0b2fecc385f9f462f1ca6": "カラー・リストは {{http://localhost:3000/colors}} で利用できます",
|
||||||
|
"1b2a6076dccbe91a56f1672eb3b8598c": "応答本文には、ログイン時に作成された {{AccessToken}} のプロパティーが含まれます。\n`include` パラメーターの値によっては、本文に追加のプロパティーが含まれる場合があります:\n\n - `user` - `U+007BUserU+007D` - 現在ログインしているユーザーのデータ。 {{(`include=user`)}}\n\n",
|
||||||
|
"1d7833c3ca2f05fdad8fad7537531c40": "\t 件名:{0}",
|
||||||
|
"1e85f822b547a75d7d385048030e4ecb": "作成済み: {0}",
|
||||||
|
"275f22ab95671f095640ca99194b7635": "\t 送信元:{0}",
|
||||||
|
"2d3071e3b18681c80a090dc0efbdb349": "ID {1} の {0} が見つかりませんでした",
|
||||||
|
"2d78192c43fd2ec52ec18f3918894f9a": "{0} ミドルウェアは非推奨です。詳しくは、{1} を参照してください。",
|
||||||
|
"308e1d484516a33df788f873e65faaff": "モデル `{0}` は非推奨の `DataModel を拡張しています。 代わりに `PersistedModel` を使用してください。",
|
||||||
|
"316e5b82c203cf3de31a449ee07d0650": "ブール値が必要ですが、{0} が取得されました",
|
||||||
|
"320c482401afa1207c04343ab162e803": "無効なプリンシパル・タイプ: {0}",
|
||||||
|
"3438fab56cc7ab92dfd88f0497e523e0": "`{0}` 構成の関係プロパティーはオブジェクトでなければなりません",
|
||||||
|
"35e5252c62d80f8c54a5290d30f4c7d0": "Web ブラウザーで次のリンクを開いて、E メールを検証してください: \n\t{0}",
|
||||||
|
"3aae63bb7e8e046641767571c1591441": "E メールが検証されていないため、ログインに失敗しました",
|
||||||
|
"3aecb24fa8bdd3f79d168761ca8a6729": "不明な {{middleware}} フェーズ {0}",
|
||||||
|
"3caaa84fc103d6d5612173ae6d43b245": "無効なトークン: {0}",
|
||||||
|
"3d617953470be16d0c2b32f0bcfbb5ee": "ご登録いただき、ありがとうございます。",
|
||||||
|
"3d63008ccfb2af1db2142e8cc2716ace": "警告: E メール送信用の E メール・トランスポートが指定されていません。 メール・メッセージを送信するためのトランスポートをセットアップしてください。",
|
||||||
|
"4203ab415ec66a78d3164345439ba76e": "{0}.{1}() を呼び出せません。 {2} メソッドがセットアップされていません。 {{PersistedModel}} は {{DataSource}} に正しく付加されていません。",
|
||||||
|
"44a6c8b1ded4ed653d19ddeaaf89a606": "E メールが見つかりません",
|
||||||
|
"4a4f04a4e480fc5d4ee73b84d9a4b904": "メールの送信:",
|
||||||
|
"4b494de07f524703ac0879addbd64b13": "E メールが検証されていません",
|
||||||
|
"4cac5f051ae431321673e04045d37772": "モデル `{0}` は不明のモデル `{1}` を拡張しています。 ベースとして `PersistedModel` を使用します。",
|
||||||
|
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "データ・ソース {0}: {1} を作成できません",
|
||||||
|
"5858e63efaa0e4ad86b61c0459ea32fa": "{{Email}} モデルを {{Mail}} コネクターに接続する必要があります",
|
||||||
|
"5e81ad3847a290dc650b47618b9cbc7e": "ログインに失敗しました",
|
||||||
|
"5fa3afb425819ebde958043e598cb664": "{{id}} {0} のモデルが見つかりませんでした",
|
||||||
|
"61e5deebaf44d68f4e6a508f30cc31a3": "モデル `{1}` には関係 `{0}` が存在しません",
|
||||||
|
"62e8b0a733417978bab22c8dacf5d7e6": "一括更新を適用できません。コネクターは更新されたレコードの数を正しく報告していません。",
|
||||||
|
"63a091ced88001ab6acb58f61ec041c5": "\t テキスト:{0}",
|
||||||
|
"6bc376432cd9972cf991aad3de371e78": "変更用のデータがありません: {0}",
|
||||||
|
"705c2d456a3e204c4af56e671ec3225c": "{{accessToken}} が見つかりませんでした",
|
||||||
|
"734a7bebb65e10899935126ba63dd51f": "`{0}` 構成のオプション・プロパティーはオブジェクトでなければなりません",
|
||||||
|
"779467f467862836e19f494a37d6ab77": "`{0}` 構成の ACL プロパティーはオブジェクトの配列でなければなりません",
|
||||||
|
"7d5e7ed0efaedf3f55f380caae0df8b8": "最初のモバイル・アプリケーション",
|
||||||
|
"7e0fca41d098607e1c9aa353c67e0fa1": "無効なアクセス・トークン",
|
||||||
|
"7e287fc885d9fdcf42da3a12f38572c1": "許可が必要です",
|
||||||
|
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "ログアウトするには {{accessToken}} が必要です",
|
||||||
|
"80a32e80cbed65eba2103201a7c94710": "モデルが見つかりません: {0}",
|
||||||
|
"83cbdc2560ba9f09155ccfc63e08f1a1": "`{1}` のプロパティー `{0}` を再構成できません",
|
||||||
|
"855ecd4a64885ba272d782435f72a4d4": "\"{0}\" ID \"{1}\" が不明です。",
|
||||||
|
"860d1a0b8bd340411fb32baa72867989": "トランスポートでは HTTP リダイレクトはサポートされません。",
|
||||||
|
"895b1f941d026870b3cc8e6af087c197": "{{username}} または {{email}} が必要です",
|
||||||
|
"8a17c5ef611e2e7535792316e66b8fca": "パスワードが長すぎます: {0}",
|
||||||
|
"8a27e0c9ce3ebf0e0c3978efb456e13e": "ホスト {0} への要求",
|
||||||
|
"8ae418c605b6a45f2651be9b1677c180": "無効なリモート・メソッド: `{0}`",
|
||||||
|
"8bab6720ecc58ec6412358c858a53484": "一括更新が失敗しました。コネクターは予期しない数のレコードを変更しました: {0}",
|
||||||
|
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
|
||||||
|
"97795efe0c3eb7f35ce8cf8cfe70682b": "`{0}` の構成は {{`dataSource`}} プロパティーがありません。\nどのデータ・ソースにも付加されていないモデルにマークを付けるには `null` または `false` を使用します。",
|
||||||
|
"a40684f5a9f546115258b76938d1de37": "カラー・リストは {{http://localhost:3000/colors}} で利用できます",
|
||||||
|
"a50d10fc6e0959b220e085454c40381e": "ユーザーが見つかりません: {0}",
|
||||||
|
"a80038252430df2754884bf3c845c4cf": "\"{0}.{1}\" のリモート・メタデータに「isStatic」フラグがありません。このメソッドはインスタンス・メソッドとして登録されます。",
|
||||||
|
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "\"{0}\" {{key}} \"{1}\" が不明です。",
|
||||||
|
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} は必須です",
|
||||||
|
"c2b5d51f007178170ca3952d59640ca4": "{0} の変更を修正できません:\n{1}",
|
||||||
|
"c68a93f0a9524fed4ff64372fc90c55f": "有効な E メールを指定する必要があります",
|
||||||
|
"cd0412f2f33a4a2a316acc834f3f21a6": "{{id}} または {{data}} を指定する必要があります",
|
||||||
|
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} は削除されました。代わりに新規モジュール {{loopback-boot}} を使用してください",
|
||||||
|
"d9ef6dc3770dd8f80a129e92a79851f3": "{0} は非推奨です。詳しくは、{1} を参照してください。",
|
||||||
|
"dc568bee32deb0f6eaf63e73b20e8ceb": "\"{0}\" の非オブジェクト「メソッド」設定を無視します。",
|
||||||
|
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" {{id}} \"{1}\" が不明です。",
|
||||||
|
"e92aa25b6b864e3454b65a7c422bd114": "一括更新が失敗しました。コネクターは予期しない数のレコードを削除しました: {0}",
|
||||||
|
"ea63d226b6968e328bdf6876010786b5": "一括更新を適用できません。コネクターは削除されたレコードの数を正しく報告していません。",
|
||||||
|
"ead044e2b4bce74b4357f8a03fb78ec4": "{0}.{1}() を呼び出せません。 {2} メソッドがセットアップされていません。 {{KeyValueModel}} は {{DataSource}} に正しく付加されていません。",
|
||||||
|
"ecb06666ef95e5db27a5ac1d6a17923b": "\t 宛先:{0}",
|
||||||
|
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t トランスポート:{0}",
|
||||||
|
"f0bd73df8714cefb925e3b8da2f4c5f6": "結果:{0}",
|
||||||
|
"f1d4ac54357cc0932f385d56814ba7e4": "競合",
|
||||||
|
"f58cdc481540cd1f69a4aa4da2e37981": "無効なパスワード: {0}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"03f79fa268fe199de2ce4345515431c1": "ID가 {1}인 {0}에 대한 변경 레코드를 찾을 수 없음",
|
||||||
|
"04bd8af876f001ceaf443aad6a9002f9": "인증을 위해 {0} 모델이 정의되어야 함",
|
||||||
|
"0731d0109e46c21a4e34af3346ed4856": "이 동작은 다음 주요 버전에서 변경될 수 있습니다.",
|
||||||
|
"095afbf2f1f0e5be678f5dac5c54e717": "액세스 거부",
|
||||||
|
"0caffe1d763c8cca6a61814abe33b776": "이메일은 필수입니다.",
|
||||||
|
"10e01c895dc0b2fecc385f9f462f1ca6": "색상 목록은 {{http://localhost:3000/colors}}에 있음",
|
||||||
|
"1b2a6076dccbe91a56f1672eb3b8598c": "응답 본문에 로그인 시 작성한 {{AccessToken}} 특성이 포함됩니다. \n`include` 매개변수 값에 따라 본문에 추가 특성이 포함될 수 있습니다. \n\n - `user` - `U+007BUserU+007D` - 현재 로그인된 사용자의 데이터. {{(`include=user`)}}\n\n",
|
||||||
|
"1d7833c3ca2f05fdad8fad7537531c40": "\t 제목:{0}",
|
||||||
|
"1e85f822b547a75d7d385048030e4ecb": "작성 날짜: {0}",
|
||||||
|
"275f22ab95671f095640ca99194b7635": "\t 발신인:{0}",
|
||||||
|
"2d3071e3b18681c80a090dc0efbdb349": "ID {1}(으)로 {0}을(를) 찾을 수 없음 ",
|
||||||
|
"2d78192c43fd2ec52ec18f3918894f9a": "{0} 미들웨어는 더 이상 사용되지 않습니다. 자세한 정보는 {1}을(를) 참조하십시오. ",
|
||||||
|
"308e1d484516a33df788f873e65faaff": "모델 `{0}`은(는) 더 이상 사용되지 않는 `DataModel`의 확장입니다. 대신 `PersistedModel`을 사용하십시오.",
|
||||||
|
"316e5b82c203cf3de31a449ee07d0650": "예상 부울, 실제 {0}",
|
||||||
|
"320c482401afa1207c04343ab162e803": "올바르지 않은 프린시펄 유형: {0}",
|
||||||
|
"3438fab56cc7ab92dfd88f0497e523e0": "`{0}` 구성의 관계 특성은 오브젝트여야 함",
|
||||||
|
"35e5252c62d80f8c54a5290d30f4c7d0": "웹 브라우저에서 이 링크를 열어 이메일을 확인하십시오.\n\t{0}",
|
||||||
|
"3aae63bb7e8e046641767571c1591441": "이메일이 확인되지 않아서 로그인에 실패했습니다. ",
|
||||||
|
"3aecb24fa8bdd3f79d168761ca8a6729": "알 수 없는 {{middleware}} 단계 {0}",
|
||||||
|
"3caaa84fc103d6d5612173ae6d43b245": "올바르지 않은 토큰: {0}",
|
||||||
|
"3d617953470be16d0c2b32f0bcfbb5ee": "등록해 주셔서 감사합니다.",
|
||||||
|
"3d63008ccfb2af1db2142e8cc2716ace": "경고: 이메일 발송을 위해 이메일 전송이 지정되지 않았습니다. 메일 메시지를 보내려면 전송을 설정하십시오. ",
|
||||||
|
"4203ab415ec66a78d3164345439ba76e": "{0}.{1}()을(를) 호출할 수 없습니다. {2} 메소드가 설정되지 않았습니다. {{PersistedModel}}이(가) {{DataSource}}에 재대로 첨부되지 않았습니다!",
|
||||||
|
"44a6c8b1ded4ed653d19ddeaaf89a606": "이메일을 찾을 수 없음",
|
||||||
|
"4a4f04a4e480fc5d4ee73b84d9a4b904": "메일 발송 중:",
|
||||||
|
"4b494de07f524703ac0879addbd64b13": "이메일이 확인되지 않았습니다.",
|
||||||
|
"4cac5f051ae431321673e04045d37772": "모델 `{0}`은(는) 알 수 없는 모델 `{1}`의 확장입니다. `PersistedModel`을 기본으로 사용하십시오.",
|
||||||
|
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "데이터 소스 {0}을(를) 작성할 수 없음: {1}",
|
||||||
|
"5858e63efaa0e4ad86b61c0459ea32fa": "{{Email}} 모델을 {{Mail}} 커넥터에 연결해야 합니다. ",
|
||||||
|
"5e81ad3847a290dc650b47618b9cbc7e": "로그인 실패",
|
||||||
|
"5fa3afb425819ebde958043e598cb664": "{{id}} {0}인 모델을 찾을 수 없음",
|
||||||
|
"61e5deebaf44d68f4e6a508f30cc31a3": "모델 `{1}`에 대해 관계 `{0}`이(가) 없습니다. ",
|
||||||
|
"62e8b0a733417978bab22c8dacf5d7e6": "벌크 업데이트를 적용할 수 없습니다. 커넥터가 업데이트된 레코드 수를 제대로 보고하지 않습니다. ",
|
||||||
|
"63a091ced88001ab6acb58f61ec041c5": "\t 텍스트:{0}",
|
||||||
|
"6bc376432cd9972cf991aad3de371e78": "변경을 위한 데이터 누락: {0}",
|
||||||
|
"705c2d456a3e204c4af56e671ec3225c": "{{accessToken}}을(를) 찾을 수 없음",
|
||||||
|
"734a7bebb65e10899935126ba63dd51f": "`{0}` 구성의 옵션 특성은 오브젝트여야 함",
|
||||||
|
"779467f467862836e19f494a37d6ab77": "`{0}` 구성의 acls 특성은 오브젝트 배열이어야 함",
|
||||||
|
"7d5e7ed0efaedf3f55f380caae0df8b8": "내 첫 번째 모바일 애플리케이션",
|
||||||
|
"7e0fca41d098607e1c9aa353c67e0fa1": "올바르지 않은 액세스 토큰",
|
||||||
|
"7e287fc885d9fdcf42da3a12f38572c1": "권한 필수",
|
||||||
|
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}}이(가) 로그아웃해야 함",
|
||||||
|
"80a32e80cbed65eba2103201a7c94710": "모델을 찾을 수 없음: {0}",
|
||||||
|
"83cbdc2560ba9f09155ccfc63e08f1a1": "`{1}`에 대해 `{0}` 특성을 다시 구성할 수 없음",
|
||||||
|
"855ecd4a64885ba272d782435f72a4d4": "알 수 없는 \"{0}\" ID \"{1}\".",
|
||||||
|
"860d1a0b8bd340411fb32baa72867989": "전송에서 HTTP 경로 재지원을 지원하지 않습니다.",
|
||||||
|
"895b1f941d026870b3cc8e6af087c197": "{{username}} 또는 {{email}}은(는) 필수입니다.",
|
||||||
|
"8a17c5ef611e2e7535792316e66b8fca": "비밀번호가 너무 김: {0}",
|
||||||
|
"8a27e0c9ce3ebf0e0c3978efb456e13e": "호스트 {0}에 요청",
|
||||||
|
"8ae418c605b6a45f2651be9b1677c180": "올바르지 않은 원격 메소드: `{0}`",
|
||||||
|
"8bab6720ecc58ec6412358c858a53484": "벌크 업데이트에 실패했습니다. 커넥터가 예상치 못한 수의 레코드를 수정했습니다. {0}",
|
||||||
|
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
|
||||||
|
"97795efe0c3eb7f35ce8cf8cfe70682b": "`{0}`의 구성에 {{`dataSource`}} 특성이 누락되었습니다.\n데이터 소스에 첨부되지 않은 모델을 표시하려면 `null` 또는 `false`를 사용하십시오.",
|
||||||
|
"a40684f5a9f546115258b76938d1de37": "색상 목록은 {{http://localhost:3000/colors}}에 있음",
|
||||||
|
"a50d10fc6e0959b220e085454c40381e": "사용자를 찾을 수 없음: {0}",
|
||||||
|
"a80038252430df2754884bf3c845c4cf": "\"{0}.{1}\"에 대한 원격 메타데이터에 \"isStatic\" 플래그가 누락되었습니다. 이 메소드는 인스턴스 메소드로 등록되어 있습니다.",
|
||||||
|
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "알 수 없는 \"{0}\" {{key}} \"{1}\".",
|
||||||
|
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}}은(는) 필수입니다.",
|
||||||
|
"c2b5d51f007178170ca3952d59640ca4": "{0} 변경사항을 교정할 수 없음:\n{1}",
|
||||||
|
"c68a93f0a9524fed4ff64372fc90c55f": "올바른 이메일을 제공해야 함",
|
||||||
|
"cd0412f2f33a4a2a316acc834f3f21a6": "{{id}} 또는 {{data}}을(를) 지정해야 함",
|
||||||
|
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}}이(가) 제거되었습니다. 대신 새 모듈 {{loopback-boot}}을(를) 사용하십시오. ",
|
||||||
|
"d9ef6dc3770dd8f80a129e92a79851f3": "{0}은(는) 더 이상 사용되지 않습니다. 자세한 정보는 {1}을(를) 참조하십시오. ",
|
||||||
|
"dc568bee32deb0f6eaf63e73b20e8ceb": "\"{0}\"의 비오브젝트 \"methods\" 설정 무시",
|
||||||
|
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "알 수 없는 \"{0}\" {{id}} \"{1}\".",
|
||||||
|
"e92aa25b6b864e3454b65a7c422bd114": "벌크 업데이트에 실패했습니다. 커넥터가 예상치 못한 수의 레코드를 삭제했습니다. {0}",
|
||||||
|
"ea63d226b6968e328bdf6876010786b5": "벌크 업데이트를 적용할 수 없습니다. 커넥터가 삭제된 레코드 수를 제대로 보고하지 않습니다. ",
|
||||||
|
"ead044e2b4bce74b4357f8a03fb78ec4": "{0}.{1}()을(를) 호출할 수 없습니다. {2} 메소드가 설정되지 않았습니다. {{KeyValueModel}}이(가) {{DataSource}}에 재대로 첨부되지 않았습니다!",
|
||||||
|
"ecb06666ef95e5db27a5ac1d6a17923b": "\t 수신인:{0}",
|
||||||
|
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t 전송:{0}",
|
||||||
|
"f0bd73df8714cefb925e3b8da2f4c5f6": "결과: {0}",
|
||||||
|
"f1d4ac54357cc0932f385d56814ba7e4": "충돌",
|
||||||
|
"f58cdc481540cd1f69a4aa4da2e37981": "올바르지 않은 비밀번호: {0}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"03f79fa268fe199de2ce4345515431c1": "Geen wijzigingsrecord gevonden voor {0} met ID {1}",
|
||||||
|
"04bd8af876f001ceaf443aad6a9002f9": "Voor verificatie moet model {0} worden gedefinieerd.",
|
||||||
|
"0731d0109e46c21a4e34af3346ed4856": "Dit gedrag kan gewijzigd worden in de volgende hoofdversie.",
|
||||||
|
"095afbf2f1f0e5be678f5dac5c54e717": "Toegang geweigerd",
|
||||||
|
"0caffe1d763c8cca6a61814abe33b776": "E-mail is vereist",
|
||||||
|
"10e01c895dc0b2fecc385f9f462f1ca6": "een lijst van kleuren is beschikbaar op {{http://localhost:3000/colors}}",
|
||||||
|
"1b2a6076dccbe91a56f1672eb3b8598c": "De lopende tekst van het antwoord bevat eigenschappen van het {{AccessToken}} dat is gemaakt bij aanmelding.\nAfhankelijk van de waarde van de parameter 'include' kan de lopende tekst aanvullende eigenschappen bevatten:\n\n - 'user' - 'U+007BUserU+007D' - Gegevens van de aangemelde gebruiker. {{(`include=user`)}}\n\n",
|
||||||
|
"1d7833c3ca2f05fdad8fad7537531c40": "\t ONDERWERP: {0}",
|
||||||
|
"1e85f822b547a75d7d385048030e4ecb": "Gemaakt: {0}",
|
||||||
|
"275f22ab95671f095640ca99194b7635": "\t VAN: {0}",
|
||||||
|
"2d3071e3b18681c80a090dc0efbdb349": "kan {0} met ID {1} niet vinden",
|
||||||
|
"2d78192c43fd2ec52ec18f3918894f9a": "{0}-middleware is gedeprecieerd. Zie {1} voor meer informatie.",
|
||||||
|
"308e1d484516a33df788f873e65faaff": "Model '{0}' is een uitbreiding van het gedeprecieerde 'DataModel'. Gebruik in plaats daarvan 'PersistedModel'.",
|
||||||
|
"316e5b82c203cf3de31a449ee07d0650": "Booleaanse waarde verwacht, {0} ontvangen",
|
||||||
|
"320c482401afa1207c04343ab162e803": "Ongeldig type principal: {0}",
|
||||||
|
"3438fab56cc7ab92dfd88f0497e523e0": "De relaties-eigenschap van de '{0}'-configuratie moet een object zijn",
|
||||||
|
"35e5252c62d80f8c54a5290d30f4c7d0": "Controleer uw e-mail door deze link te openen in een webbrowser:\n\t{0}",
|
||||||
|
"3aae63bb7e8e046641767571c1591441": "Aanmelding mislukt omdat e-mail niet is gecontroleerd",
|
||||||
|
"3aecb24fa8bdd3f79d168761ca8a6729": "Onbekende {{middleware}}-fase {0}",
|
||||||
|
"3caaa84fc103d6d5612173ae6d43b245": "Ongeldig token: {0}",
|
||||||
|
"3d617953470be16d0c2b32f0bcfbb5ee": "Hartelijk dank voor uw registratie",
|
||||||
|
"3d63008ccfb2af1db2142e8cc2716ace": "Waarschuwing: Geen e-mailtransport opgegeven voor verzending van e-mail. Configureer een transport om e-mailberichten te verzenden.",
|
||||||
|
"4203ab415ec66a78d3164345439ba76e": "{0} kan niet worden aangeroepen. {1}(). De methode {2} is niet geconfigureerd. De {{PersistedModel}} is niet correct gekoppeld aan een {{DataSource}}!",
|
||||||
|
"44a6c8b1ded4ed653d19ddeaaf89a606": "E-mail is niet gevonden",
|
||||||
|
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Mail verzenden:",
|
||||||
|
"4b494de07f524703ac0879addbd64b13": "E-mail is niet geverifieerd",
|
||||||
|
"4cac5f051ae431321673e04045d37772": "Model '{0}' is een uitbreiding van onbekend model '{1}'. 'PersistedModel' wordt gebruikt als basis.",
|
||||||
|
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Gegevensbron {0}: {1} kan n iet worden gemaakt",
|
||||||
|
"5858e63efaa0e4ad86b61c0459ea32fa": "U moet verbinding maken tussen het model {{Email}} en een {{Mail}}-connector",
|
||||||
|
"5e81ad3847a290dc650b47618b9cbc7e": "Aanmelden is mislukt",
|
||||||
|
"5fa3afb425819ebde958043e598cb664": "geen model gevonden met {{id}} {0}",
|
||||||
|
"61e5deebaf44d68f4e6a508f30cc31a3": "Relatie '{0}' voor model '{1}' bestaat niet",
|
||||||
|
"62e8b0a733417978bab22c8dacf5d7e6": "Bulkupdates kunnen niet worden toegepast, de connector meldt niet het juiste aantal bijgewerkte records.",
|
||||||
|
"63a091ced88001ab6acb58f61ec041c5": "\t TEKST: {0}",
|
||||||
|
"6bc376432cd9972cf991aad3de371e78": "Ontbrekende gegevens voor wijziging: {0}",
|
||||||
|
"705c2d456a3e204c4af56e671ec3225c": "{{accessToken}} is niet gevonden",
|
||||||
|
"734a7bebb65e10899935126ba63dd51f": "De opties-eigenschap van de '{0}'-configuratie moet een object zijn",
|
||||||
|
"779467f467862836e19f494a37d6ab77": "De acls-eigenschap van de '{0}'-configuratie moet een array objecten zijn",
|
||||||
|
"7d5e7ed0efaedf3f55f380caae0df8b8": "Mijn eerste mobiele toepassing",
|
||||||
|
"7e0fca41d098607e1c9aa353c67e0fa1": "Ongeldig toegangstoken",
|
||||||
|
"7e287fc885d9fdcf42da3a12f38572c1": "Verplichte verificatie",
|
||||||
|
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} is vereist voor afmelding",
|
||||||
|
"80a32e80cbed65eba2103201a7c94710": "Model is niet gevonden: {0}",
|
||||||
|
"83cbdc2560ba9f09155ccfc63e08f1a1": "Eigenschap '{0}' mag niet opnieuw worden geconfigureerd voor '{1}'",
|
||||||
|
"855ecd4a64885ba272d782435f72a4d4": "Onbekend \"{0}\"-ID \"{1}\".",
|
||||||
|
"860d1a0b8bd340411fb32baa72867989": "Transport biedt geen ondersteuning voor HTTP-omleidingen.",
|
||||||
|
"895b1f941d026870b3cc8e6af087c197": "{{username}} of {{email}} is verplicht",
|
||||||
|
"8a17c5ef611e2e7535792316e66b8fca": "Wachtwoord is te lang: {0}",
|
||||||
|
"8a27e0c9ce3ebf0e0c3978efb456e13e": "Aanvraag voor host {0}",
|
||||||
|
"8ae418c605b6a45f2651be9b1677c180": "Ongeldige niet-lokale methode: '{0}'",
|
||||||
|
"8bab6720ecc58ec6412358c858a53484": "Bulkupdate is mislukt; connector heeft een onverwacht aantal records gewijzigd: {0}",
|
||||||
|
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML: {0}",
|
||||||
|
"97795efe0c3eb7f35ce8cf8cfe70682b": "De eigenschap {{`dataSource`}} ontbreekt in de configuratie van '{0}'.\nGebruik 'null' of 'false' om modellen te markeren die niet gekoppeld zijn aan een gegevensbron.",
|
||||||
|
"a40684f5a9f546115258b76938d1de37": "Een lijst van kleuren is beschikbaar op {{http://localhost:3000/colors}}",
|
||||||
|
"a50d10fc6e0959b220e085454c40381e": "Gebruiker is niet gevonden: {0}",
|
||||||
|
"a80038252430df2754884bf3c845c4cf": "Vlag \"isStatic\" ontbreekt in remoting (externe) metagegevens voor \"{0}.{1}\"; de methode wordt geregistreerd als instancemethode.",
|
||||||
|
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "Onbekend \"{0}\" {{key}} \"{1}\".",
|
||||||
|
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} is verplicht",
|
||||||
|
"c2b5d51f007178170ca3952d59640ca4": "Wijzigingen van {0} kunnen niet worden hersteld:\n{1}",
|
||||||
|
"c68a93f0a9524fed4ff64372fc90c55f": "U moet een geldig e-mailadres opgeven",
|
||||||
|
"cd0412f2f33a4a2a316acc834f3f21a6": "U moet een {{id}} of {{data}} opgeven",
|
||||||
|
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} is verwijderd; gebruik in plaats daarvan de nieuwe module {{loopback-boot}}",
|
||||||
|
"d9ef6dc3770dd8f80a129e92a79851f3": "{0} is gedeprecieerd. Zie {1} voor meer informatie.",
|
||||||
|
"dc568bee32deb0f6eaf63e73b20e8ceb": "Niet-object \"methods\"-instelling \"{0}\" wordt genegeerd.",
|
||||||
|
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "Onbekend \"{0}\" {{id}} \"{1}\".",
|
||||||
|
"e92aa25b6b864e3454b65a7c422bd114": "Bulkupdate is mislukt; connector heeft een onverwacht aantal records gewist: {0}",
|
||||||
|
"ea63d226b6968e328bdf6876010786b5": "Bulkupdates kunnen niet worden toegepast, de connector meldt niet het juiste aantal gewiste records.",
|
||||||
|
"ead044e2b4bce74b4357f8a03fb78ec4": "{0} kan niet worden aangeroepen. {1}(). De methode {2} is niet geconfigureerd. De {{KeyValueModel}} is niet correct gekoppeld aan een {{DataSource}}!",
|
||||||
|
"ecb06666ef95e5db27a5ac1d6a17923b": "\t AAN: {0}",
|
||||||
|
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORT: {0}",
|
||||||
|
"f0bd73df8714cefb925e3b8da2f4c5f6": "resultaat:{0}",
|
||||||
|
"f1d4ac54357cc0932f385d56814ba7e4": "Conflict",
|
||||||
|
"f58cdc481540cd1f69a4aa4da2e37981": "Ongeldige wachtwoord: {0}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"03f79fa268fe199de2ce4345515431c1": "Nenhum registro de mudança localizado para {0} com o ID {1}",
|
||||||
|
"04bd8af876f001ceaf443aad6a9002f9": "Autenticação requer que modelo {0} seja definido.",
|
||||||
|
"0731d0109e46c21a4e34af3346ed4856": "Este comportamento pode mudar na próxima versão principal.",
|
||||||
|
"095afbf2f1f0e5be678f5dac5c54e717": "Acesso Negado",
|
||||||
|
"0caffe1d763c8cca6a61814abe33b776": "E-mail é necessário",
|
||||||
|
"10e01c895dc0b2fecc385f9f462f1ca6": "uma lista de cores está disponível em {{http://localhost:3000/colors}}",
|
||||||
|
"1b2a6076dccbe91a56f1672eb3b8598c": "O corpo de resposta contém propriedades do {{AccessToken}} criado no login.\nDependendo do valor do parâmetro `include`, o corpo poderá conter propriedades adicionais:\n\n - `user` - `U+007BUserU+007D` - Dados do usuário com login efetuado atualmente. {{(`include=user`)}}\n\n",
|
||||||
|
"1d7833c3ca2f05fdad8fad7537531c40": "\t ASSUNTO:{0}",
|
||||||
|
"1e85f822b547a75d7d385048030e4ecb": "Criado: {0}",
|
||||||
|
"275f22ab95671f095640ca99194b7635": "\t DE:{0}",
|
||||||
|
"2d3071e3b18681c80a090dc0efbdb349": "não foi possível localizar {0} com ID {1}",
|
||||||
|
"2d78192c43fd2ec52ec18f3918894f9a": "O middleware {0} foi descontinuado. Consulte {1} para obter mais detalhes.",
|
||||||
|
"308e1d484516a33df788f873e65faaff": "O modelo `{0}` está estendendo `DataModel` descontinuado. Use `PersistedModel` no lugar.",
|
||||||
|
"316e5b82c203cf3de31a449ee07d0650": "Booleano esperado, obteve {0}",
|
||||||
|
"320c482401afa1207c04343ab162e803": "Tipo principal inválido: {0}",
|
||||||
|
"3438fab56cc7ab92dfd88f0497e523e0": "A propriedade de relações da configuração de `{0}` deve ser um objeto",
|
||||||
|
"35e5252c62d80f8c54a5290d30f4c7d0": "Verifique seu e-mail abrindo este link em um navegador da web:\n\t{0}",
|
||||||
|
"3aae63bb7e8e046641767571c1591441": "login com falha pois o e-mail não foi verificado",
|
||||||
|
"3aecb24fa8bdd3f79d168761ca8a6729": "Fase {0} do {{middleware}} desconhecida",
|
||||||
|
"3caaa84fc103d6d5612173ae6d43b245": "Token inválido: {0}",
|
||||||
|
"3d617953470be16d0c2b32f0bcfbb5ee": "Obrigado por se Registrar",
|
||||||
|
"3d63008ccfb2af1db2142e8cc2716ace": "Aviso: Nenhum transporte de e-mail especificado para enviar e-mail. Configure um transporte para enviar mensagens de e-mail.",
|
||||||
|
"4203ab415ec66a78d3164345439ba76e": "Não é possível chamar {0}.{1}(). O método {2} não foi configurado. O {{PersistedModel}} não foi conectado corretamente a uma {{DataSource}}!",
|
||||||
|
"44a6c8b1ded4ed653d19ddeaaf89a606": "E-mail não encontrado",
|
||||||
|
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Enviando E-mail:",
|
||||||
|
"4b494de07f524703ac0879addbd64b13": "E-mail não foi verificado",
|
||||||
|
"4cac5f051ae431321673e04045d37772": "O modelo `{0}` está estendendo um modelo `{1}` desconhecido. Usando `PersistedModel` como a base.",
|
||||||
|
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Não é possível criar origem de dados {0}: {1}",
|
||||||
|
"5858e63efaa0e4ad86b61c0459ea32fa": "Deve-se conectar o Modelo de {{Email}} em um conector de {{Mail}}",
|
||||||
|
"5e81ad3847a290dc650b47618b9cbc7e": "falha de login",
|
||||||
|
"5fa3afb425819ebde958043e598cb664": "não foi possível localizar um modelo com {{id}} {0}",
|
||||||
|
"61e5deebaf44d68f4e6a508f30cc31a3": "Relação `{0}` não existe para o modelo `{1}`",
|
||||||
|
"62e8b0a733417978bab22c8dacf5d7e6": "Não é possível aplicar atualizações em massa, o conector não relata o número de registros de atualização corretamente.",
|
||||||
|
"63a091ced88001ab6acb58f61ec041c5": "\t TEXTO:{0}",
|
||||||
|
"6bc376432cd9972cf991aad3de371e78": "Dados ausentes para a mudança: {0}",
|
||||||
|
"705c2d456a3e204c4af56e671ec3225c": "Não foi possível localizar o {{accessToken}}",
|
||||||
|
"734a7bebb65e10899935126ba63dd51f": "A propriedade de opções da configuração de `{0}` deve ser um objeto",
|
||||||
|
"779467f467862836e19f494a37d6ab77": "A propriedade acls da configuração de `{0}` deve ser uma matriz de objetos",
|
||||||
|
"7d5e7ed0efaedf3f55f380caae0df8b8": "Meu primeiro aplicativo móvel",
|
||||||
|
"7e0fca41d098607e1c9aa353c67e0fa1": "Token de Acesso Inválido",
|
||||||
|
"7e287fc885d9fdcf42da3a12f38572c1": "Autorização Necessária",
|
||||||
|
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} é necessário para efetuar logout",
|
||||||
|
"80a32e80cbed65eba2103201a7c94710": "Modelo não localizado: {0}",
|
||||||
|
"83cbdc2560ba9f09155ccfc63e08f1a1": "A propriedade `{0}` não pode ser reconfigurada para `{1}`",
|
||||||
|
"855ecd4a64885ba272d782435f72a4d4": "ID \"{1}\" de \"{0}\" desconhecido.",
|
||||||
|
"860d1a0b8bd340411fb32baa72867989": "O transporte não suporta redirecionamentos de HTTP.",
|
||||||
|
"895b1f941d026870b3cc8e6af087c197": "{{username}} ou {{email}} é necessário",
|
||||||
|
"8a17c5ef611e2e7535792316e66b8fca": "Senha muito longa: {0}",
|
||||||
|
"8a27e0c9ce3ebf0e0c3978efb456e13e": "Solicitação para o host {0}",
|
||||||
|
"8ae418c605b6a45f2651be9b1677c180": "Método remoto inválido: `{0}`",
|
||||||
|
"8bab6720ecc58ec6412358c858a53484": "Atualização em massa falhou, o conector modificou um número inesperado de registros: {0}",
|
||||||
|
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
|
||||||
|
"97795efe0c3eb7f35ce8cf8cfe70682b": "A configuração de `{0}` não possui a propriedade {{`dataSource`}}.\nUse `null` ou `false` para marcar modelos não conectados a nenhuma origem de dados.",
|
||||||
|
"a40684f5a9f546115258b76938d1de37": "Uma lista de cores está disponível em {{http://localhost:3000/colors}}",
|
||||||
|
"a50d10fc6e0959b220e085454c40381e": "Usuário não localizado: {0}",
|
||||||
|
"a80038252430df2754884bf3c845c4cf": "Metadados remotos para \"{0}.{1}\" não possui sinalização \" isStatic\", o método foi registrado como um método de instância.",
|
||||||
|
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "\"{0}\" {{key}} \"{1}\" desconhecido.",
|
||||||
|
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} é obrigatório",
|
||||||
|
"c2b5d51f007178170ca3952d59640ca4": "Não é possível retificar mudanças de {0}:\n{1}",
|
||||||
|
"c68a93f0a9524fed4ff64372fc90c55f": "Deve-se fornecer um e-mail válido",
|
||||||
|
"cd0412f2f33a4a2a316acc834f3f21a6": "deve-se especificar um {{id}} ou {{data}}",
|
||||||
|
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} foi removido, use o novo módulo {{loopback-boot}} no lugar",
|
||||||
|
"d9ef6dc3770dd8f80a129e92a79851f3": "{0} foi descontinuado. Consulte {1} para obter mais detalhes.",
|
||||||
|
"dc568bee32deb0f6eaf63e73b20e8ceb": "Ignorando configuração de \"methods\" de não objeto de \"{0}\".",
|
||||||
|
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" {{id}} \"{1}\" desconhecido.",
|
||||||
|
"e92aa25b6b864e3454b65a7c422bd114": "Atualização em massa falhou, o conector excluiu um número inesperado de registros: {0}",
|
||||||
|
"ea63d226b6968e328bdf6876010786b5": "Não é possível aplicar atualizações em massa, o conector não relata o número de registros excluídos corretamente.",
|
||||||
|
"ead044e2b4bce74b4357f8a03fb78ec4": "Não é possível chamar {0}.{1}(). O método {2} não foi configurado. O {{KeyValueModel}} não foi conectado corretamente a uma {{DataSource}}!",
|
||||||
|
"ecb06666ef95e5db27a5ac1d6a17923b": "\t PARA:{0}",
|
||||||
|
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORTE:{0}",
|
||||||
|
"f0bd73df8714cefb925e3b8da2f4c5f6": "resultado:{0}",
|
||||||
|
"f1d4ac54357cc0932f385d56814ba7e4": "Conflito",
|
||||||
|
"f58cdc481540cd1f69a4aa4da2e37981": "Senha inválida: {0}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"03f79fa268fe199de2ce4345515431c1": "{0} için {1} tanıtıcılı bir değişiklik kaydı bulunamadı",
|
||||||
|
"04bd8af876f001ceaf443aad6a9002f9": "Kimlik doğrulaması {0} modelinin tanımlanmasını gerektiriyor.",
|
||||||
|
"0731d0109e46c21a4e34af3346ed4856": "Bu davranış sonraki ana sürümde değişebilir.",
|
||||||
|
"095afbf2f1f0e5be678f5dac5c54e717": "Erişim Verilmedi",
|
||||||
|
"0caffe1d763c8cca6a61814abe33b776": "E-posta zorunludur",
|
||||||
|
"10e01c895dc0b2fecc385f9f462f1ca6": "renklerin listesine şu adresle erişebilirsiniz: {{http://localhost:3000/colors}}",
|
||||||
|
"1b2a6076dccbe91a56f1672eb3b8598c": "Yanıt gövdesi, oturum açma sırasında yaratılan {{AccessToken}} belirtecine ilişkin özellikleri içerir.\n`include` parametresinin değerine bağlı olarak, gövde ek özellikler içerebilir:\n\n - `user` - `U+007BUserU+007D` - Oturum açmış olan kullanıcıya ilişkin veriler. {{(`include=user`)}}\n\n",
|
||||||
|
"1d7833c3ca2f05fdad8fad7537531c40": "\t KONU:{0}",
|
||||||
|
"1e85f822b547a75d7d385048030e4ecb": "Yaratıldığı tarih: {0}",
|
||||||
|
"275f22ab95671f095640ca99194b7635": "\t KİMDEN:{0}",
|
||||||
|
"2d3071e3b18681c80a090dc0efbdb349": "{1} tanıtıcılı {0} bulunamadı",
|
||||||
|
"2d78192c43fd2ec52ec18f3918894f9a": "{0} ara katman yazılımı kullanım dışı bırakıldı. Daha fazla ayrıntı için bkz. {1}.",
|
||||||
|
"308e1d484516a33df788f873e65faaff": "`{0}` modeli kullanım dışı bırakılmış şu modeli genişletiyor: `DataModel. Onun yerine `PersistedModel` modelini kullanın.",
|
||||||
|
"316e5b82c203cf3de31a449ee07d0650": "Boole beklenirken {0} alındı",
|
||||||
|
"320c482401afa1207c04343ab162e803": "Geçersiz birincil kullanıcı tipi: {0}",
|
||||||
|
"3438fab56cc7ab92dfd88f0497e523e0": "`{0}` yapılandırmasının ilişkiler (relations) özelliği bir nesne olmalıdır",
|
||||||
|
"35e5252c62d80f8c54a5290d30f4c7d0": "Lütfen bu bağlantıyı bir web tarayıcısında açarak e-postanızı doğrulayın:\n\t{0}",
|
||||||
|
"3aae63bb7e8e046641767571c1591441": "e-posta doğrulanmadığından oturum açma başarısız oldu",
|
||||||
|
"3aecb24fa8bdd3f79d168761ca8a6729": "Bilinmeyen {{middleware}} aşaması {0}",
|
||||||
|
"3caaa84fc103d6d5612173ae6d43b245": "Geçersiz belirteç: {0}",
|
||||||
|
"3d617953470be16d0c2b32f0bcfbb5ee": "Kaydolduğunuz için teşekkürler",
|
||||||
|
"3d63008ccfb2af1db2142e8cc2716ace": "Uyarı: E-posta göndermek için e-posta aktarımı belirtilmedi. Posta iletileri göndermek için aktarım ayarlayın.",
|
||||||
|
"4203ab415ec66a78d3164345439ba76e": "Çağrılamıyor: {0}.{1}(). {2} yöntemi ayarlanmamış. {{PersistedModel}}, bir veri kaynağına ({{DataSource}}) doğru olarak eklenmedi!",
|
||||||
|
"44a6c8b1ded4ed653d19ddeaaf89a606": "E-posta bulunamadı",
|
||||||
|
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Posta Gönderiliyor:",
|
||||||
|
"4b494de07f524703ac0879addbd64b13": "E-posta doğrulanmadı",
|
||||||
|
"4cac5f051ae431321673e04045d37772": "`{0}` modeli, bilinmeyen `{1}` modelini genişletiyor. Temel olarak 'PersistedModel' kullanılıyor.",
|
||||||
|
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Veri kaynağı {0} yaratılamıyor: {1}",
|
||||||
|
"5858e63efaa0e4ad86b61c0459ea32fa": "{{Email}} modelini bir {{Mail}} bağlayıcısına bağlamalısınız",
|
||||||
|
"5e81ad3847a290dc650b47618b9cbc7e": "oturum açma başarısız oldu",
|
||||||
|
"5fa3afb425819ebde958043e598cb664": "{{id}} {0} tanıtıcılı bir model bulunamadı",
|
||||||
|
"61e5deebaf44d68f4e6a508f30cc31a3": "`{1}` modeli için `{0}` ilişkisi yok",
|
||||||
|
"62e8b0a733417978bab22c8dacf5d7e6": "Toplu güncelleme uygulanamaz; bağlayıcı, güncellenen kayıtların sayısını doğru olarak bildirmiyor.",
|
||||||
|
"63a091ced88001ab6acb58f61ec041c5": "\t METİN:{0}",
|
||||||
|
"6bc376432cd9972cf991aad3de371e78": "Değişiklik için veri eksik: {0}",
|
||||||
|
"705c2d456a3e204c4af56e671ec3225c": "{{accessToken}} bulunamadı",
|
||||||
|
"734a7bebb65e10899935126ba63dd51f": "`{0}` yapılandırmasının seçenekler (options) özelliği bir nesne olmalıdır.",
|
||||||
|
"779467f467862836e19f494a37d6ab77": "`{0}` yapılandırmasının erişim denetim listeleri (acls) özelliği bir nesne dizisi olmalıdır.",
|
||||||
|
"7d5e7ed0efaedf3f55f380caae0df8b8": "İlk mobil uygulamam",
|
||||||
|
"7e0fca41d098607e1c9aa353c67e0fa1": "Geçersiz Erişim Belirteci",
|
||||||
|
"7e287fc885d9fdcf42da3a12f38572c1": "Yetkilendirme Gerekli",
|
||||||
|
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "Oturumu kapatmak için {{accessToken}} zorunludur",
|
||||||
|
"80a32e80cbed65eba2103201a7c94710": "Model bulunamadı: {0}",
|
||||||
|
"83cbdc2560ba9f09155ccfc63e08f1a1": "`{0}` özelliği `{1}` için yeniden yapılandırılamıyor",
|
||||||
|
"855ecd4a64885ba272d782435f72a4d4": "Bilinmeyen \"{0}\" tanıtıcısı \"{1}\".",
|
||||||
|
"860d1a0b8bd340411fb32baa72867989": "Aktarım HTTP yeniden yönlendirmelerini desteklemiyor.",
|
||||||
|
"895b1f941d026870b3cc8e6af087c197": "{{username}} ya da {{email}} zorunludur",
|
||||||
|
"8a17c5ef611e2e7535792316e66b8fca": "Parola çok uzun: {0}",
|
||||||
|
"8a27e0c9ce3ebf0e0c3978efb456e13e": "{0} ana makinesine yönelik istek",
|
||||||
|
"8ae418c605b6a45f2651be9b1677c180": "Uzak yöntem geçersiz: `{0}`",
|
||||||
|
"8bab6720ecc58ec6412358c858a53484": "Toplu güncelleme başarısız oldu, bağlayıcı beklenmeyen sayıda kaydı değiştirdi: {0}",
|
||||||
|
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
|
||||||
|
"97795efe0c3eb7f35ce8cf8cfe70682b": "`{0}` yapılandırmasında {{`dataSource`}} özelliği eksik.\nHiçbir veri kaynağına eklenmemiş modelleri işaretlemek için `null` ya da `false` kullanın.",
|
||||||
|
"a40684f5a9f546115258b76938d1de37": "Renklerin listesine şu adresle erişebilirsiniz: {{http://localhost:3000/colors}}",
|
||||||
|
"a50d10fc6e0959b220e085454c40381e": "Kullanıcı bulunamadı: {0}",
|
||||||
|
"a80038252430df2754884bf3c845c4cf": "\"{0}.{1}\" ile ilgili uzaktan iletişim meta verisinde \"isStatic\" işareti eksik; yöntem bir eşgörünüm yöntemi olarak kaydedildi.",
|
||||||
|
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "Bilinmeyen \"{0}\" {{key}} \"{1}\".",
|
||||||
|
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} zorunludur",
|
||||||
|
"c2b5d51f007178170ca3952d59640ca4": "{0} değişiklik düzeltilemiyor:\n{1}",
|
||||||
|
"c68a93f0a9524fed4ff64372fc90c55f": "Geçerli bir e-posta belirtilmeli",
|
||||||
|
"cd0412f2f33a4a2a316acc834f3f21a6": "bir {{id}} ya da {{data}} belirtmelidir",
|
||||||
|
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} kaldırıldı, onun yerine yeni {{loopback-boot}} modülünü kullanın",
|
||||||
|
"d9ef6dc3770dd8f80a129e92a79851f3": "{0} kullanım dışı bırakıldı. Daha fazla ayrıntı için bkz. {1}.",
|
||||||
|
"dc568bee32deb0f6eaf63e73b20e8ceb": "\"{0}\" öğesinin nesne olmayan \"methods\" atarı yoksayılıyor.",
|
||||||
|
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "Bilinmeyen \"{0}\" {{id}} \"{1}\".",
|
||||||
|
"e92aa25b6b864e3454b65a7c422bd114": "Toplu güncelleme başarısız oldu, bağlayıcı beklenmeyen sayıda kaydı sildi: {0}",
|
||||||
|
"ea63d226b6968e328bdf6876010786b5": "Toplu güncelleme uygulanamaz; bağlayıcı, silinen kayıtların sayısını doğru olarak bildirmiyor.",
|
||||||
|
"ead044e2b4bce74b4357f8a03fb78ec4": "Çağrılamıyor: {0}.{1}(). {2} yöntemi ayarlanmamış. {{KeyValueModel}}, bir veri kaynağına ({{DataSource}}) doğru olarak eklenmedi!",
|
||||||
|
"ecb06666ef95e5db27a5ac1d6a17923b": "\t KİME:{0}",
|
||||||
|
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t AKTARIM:{0}",
|
||||||
|
"f0bd73df8714cefb925e3b8da2f4c5f6": "sonuç:{0}",
|
||||||
|
"f1d4ac54357cc0932f385d56814ba7e4": "Çakışma",
|
||||||
|
"f58cdc481540cd1f69a4aa4da2e37981": "Geçersiz parola: {0}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"03f79fa268fe199de2ce4345515431c1": "对于标识为 {1} 的 {0},找不到任何更改记录",
|
||||||
|
"04bd8af876f001ceaf443aad6a9002f9": "认证需要定义模型 {0}。",
|
||||||
|
"0731d0109e46c21a4e34af3346ed4856": "在下一个主版本中,此行为可能进行更改。",
|
||||||
|
"095afbf2f1f0e5be678f5dac5c54e717": "拒绝访问",
|
||||||
|
"0caffe1d763c8cca6a61814abe33b776": "电子邮件是必需的",
|
||||||
|
"10e01c895dc0b2fecc385f9f462f1ca6": "颜色列表位于:{{http://localhost:3000/colors}}",
|
||||||
|
"1b2a6076dccbe91a56f1672eb3b8598c": "响应主体包含在登录时创建的 {{AccessToken}} 的属性。\n根据“include”参数的值,主体可包含其他属性:\n\n - `user` - `U+007BUserU+007D` - 当前已登录用户的数据。 {{(`include=user`)}}\n\n",
|
||||||
|
"1d7833c3ca2f05fdad8fad7537531c40": "\t主题:{0}",
|
||||||
|
"1e85f822b547a75d7d385048030e4ecb": "创建时间:{0}",
|
||||||
|
"275f22ab95671f095640ca99194b7635": "\t发件人:{0}",
|
||||||
|
"2d3071e3b18681c80a090dc0efbdb349": "无法找到标识为 {1} 的 {0}",
|
||||||
|
"2d78192c43fd2ec52ec18f3918894f9a": "不推荐 {0} 中间件。请参阅 {1} 以获取更多详细信息。",
|
||||||
|
"308e1d484516a33df788f873e65faaff": "模型“{0}”正在扩展不推荐使用的“DataModel”。请改用“PersistedModel”。",
|
||||||
|
"316e5b82c203cf3de31a449ee07d0650": "期望布尔值,获取 {0}",
|
||||||
|
"320c482401afa1207c04343ab162e803": "无效的主体类型:{0}",
|
||||||
|
"3438fab56cc7ab92dfd88f0497e523e0": "“{0}”配置的关系属性必须是对象。",
|
||||||
|
"35e5252c62d80f8c54a5290d30f4c7d0": "请通过在 Web 浏览器中打开此链接来验证您的电子邮件:\n\t{0}",
|
||||||
|
"3aae63bb7e8e046641767571c1591441": "因为尚未验证电子邮件,登录失败",
|
||||||
|
"3aecb24fa8bdd3f79d168761ca8a6729": "未知的 {{middleware}} 阶段 {0}",
|
||||||
|
"3caaa84fc103d6d5612173ae6d43b245": "无效的令牌:{0}",
|
||||||
|
"3d617953470be16d0c2b32f0bcfbb5ee": "感谢您注册",
|
||||||
|
"3d63008ccfb2af1db2142e8cc2716ace": "警告:未指定用于发送电子邮件的电子邮件传输。设置传输以发送电子邮件消息。",
|
||||||
|
"4203ab415ec66a78d3164345439ba76e": "无法调用 {0}.{1}()。尚未设置 {2} 方法。{{PersistedModel}} 未正确附加到 {{DataSource}}!",
|
||||||
|
"44a6c8b1ded4ed653d19ddeaaf89a606": "找不到电子邮件",
|
||||||
|
"4a4f04a4e480fc5d4ee73b84d9a4b904": "正在发送电子邮件:",
|
||||||
|
"4b494de07f524703ac0879addbd64b13": "尚未验证电子邮件",
|
||||||
|
"4cac5f051ae431321673e04045d37772": "模型“{0}”正在扩展未知的模型“{1}”。使用“PersistedModel”作为基础。",
|
||||||
|
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "无法创建数据源 {0}:{1}",
|
||||||
|
"5858e63efaa0e4ad86b61c0459ea32fa": "您必须将 {{Email}} 模型连接到 {{Mail}} 连接器",
|
||||||
|
"5e81ad3847a290dc650b47618b9cbc7e": "登录失败",
|
||||||
|
"5fa3afb425819ebde958043e598cb664": "找不到具有 {{id}} {0} 的模型",
|
||||||
|
"61e5deebaf44d68f4e6a508f30cc31a3": "对于模型“{1}”,关系“{0}”不存在",
|
||||||
|
"62e8b0a733417978bab22c8dacf5d7e6": "无法应用批量更新,连接器未正确报告更新的记录数。",
|
||||||
|
"63a091ced88001ab6acb58f61ec041c5": "\t 文本:{0}",
|
||||||
|
"6bc376432cd9972cf991aad3de371e78": "缺少更改的数据:{0}",
|
||||||
|
"705c2d456a3e204c4af56e671ec3225c": "无法找到 {{accessToken}}",
|
||||||
|
"734a7bebb65e10899935126ba63dd51f": "“{0}”配置的选项属性必须是对象。",
|
||||||
|
"779467f467862836e19f494a37d6ab77": "“{0}”配置的 acls 属性必须是对象数组。",
|
||||||
|
"7d5e7ed0efaedf3f55f380caae0df8b8": "我的第一个移动应用程序",
|
||||||
|
"7e0fca41d098607e1c9aa353c67e0fa1": "无效的访问令牌",
|
||||||
|
"7e287fc885d9fdcf42da3a12f38572c1": "需要授权",
|
||||||
|
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} 需要注销",
|
||||||
|
"80a32e80cbed65eba2103201a7c94710": "找不到模型:{0}",
|
||||||
|
"83cbdc2560ba9f09155ccfc63e08f1a1": "无法针对“{1}”重新配置属性“{0}”。",
|
||||||
|
"855ecd4a64885ba272d782435f72a4d4": "未知的“{0}”标识“{1}”。",
|
||||||
|
"860d1a0b8bd340411fb32baa72867989": "传输不支持 HTTP 重定向。",
|
||||||
|
"895b1f941d026870b3cc8e6af087c197": "{{username}} 或 {{email}} 是必需的",
|
||||||
|
"8a17c5ef611e2e7535792316e66b8fca": "密码过长:{0}",
|
||||||
|
"8a27e0c9ce3ebf0e0c3978efb456e13e": "到主机 {0} 的请求",
|
||||||
|
"8ae418c605b6a45f2651be9b1677c180": "无效的远程方法:“{0}”",
|
||||||
|
"8bab6720ecc58ec6412358c858a53484": "批量更新失败,连接器已修改意外数量的记录:{0}",
|
||||||
|
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
|
||||||
|
"97795efe0c3eb7f35ce8cf8cfe70682b": "“{0}”的配置缺少 {{`dataSource`}} 属性。\n使用“null”或“false”来标记未附加到任何数据源的模型。",
|
||||||
|
"a40684f5a9f546115258b76938d1de37": "颜色列表位于:{{http://localhost:3000/colors}}",
|
||||||
|
"a50d10fc6e0959b220e085454c40381e": "找不到用户:{0}",
|
||||||
|
"a80038252430df2754884bf3c845c4cf": "“{0}.{1}”的远程处理元数据缺少“isStatic”标志,方法注册为实例方法。",
|
||||||
|
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "未知的“{0}”{{key}}“{1}”。",
|
||||||
|
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} 是必需的",
|
||||||
|
"c2b5d51f007178170ca3952d59640ca4": "无法纠正 {0} 更改:\n{1}",
|
||||||
|
"c68a93f0a9524fed4ff64372fc90c55f": "必须提供有效电子邮件",
|
||||||
|
"cd0412f2f33a4a2a316acc834f3f21a6": "必须指定 {{id}} 或 {{data}}",
|
||||||
|
"d5552322de5605c58b62f47ad26d2716": "已除去 {{`app.boot`}},请改用新模块 {{loopback-boot}}",
|
||||||
|
"d9ef6dc3770dd8f80a129e92a79851f3": "不推荐使用 {0}。请参阅 {1} 以获取更多详细信息。",
|
||||||
|
"dc568bee32deb0f6eaf63e73b20e8ceb": "忽略“{0}”的非对象“方法”设置。",
|
||||||
|
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "未知的“{0}”{{id}}“{1}”。",
|
||||||
|
"e92aa25b6b864e3454b65a7c422bd114": "批量更新失败,连接器已删除意外数量的记录:{0}",
|
||||||
|
"ea63d226b6968e328bdf6876010786b5": "无法应用批量更新,连接器未正确报告删除的记录数。",
|
||||||
|
"ead044e2b4bce74b4357f8a03fb78ec4": "无法调用 {0}.{1}()。尚未设置 {2} 方法。{{KeyValueModel}} 未正确附加到 {{DataSource}}!",
|
||||||
|
"ecb06666ef95e5db27a5ac1d6a17923b": "\t 收件人:{0}",
|
||||||
|
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t 传输:{0}",
|
||||||
|
"f0bd73df8714cefb925e3b8da2f4c5f6": "结果:{0}",
|
||||||
|
"f1d4ac54357cc0932f385d56814ba7e4": "冲突",
|
||||||
|
"f58cdc481540cd1f69a4aa4da2e37981": "无效的密码:{0}"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"03f79fa268fe199de2ce4345515431c1": "對於 id 為 {1} 的 {0},找不到變更記錄",
|
||||||
|
"04bd8af876f001ceaf443aad6a9002f9": "需要定義模型 {0} 才能鑑別。",
|
||||||
|
"0731d0109e46c21a4e34af3346ed4856": "下一個主要版本中可能會變更這個行為。",
|
||||||
|
"095afbf2f1f0e5be678f5dac5c54e717": "拒絕存取",
|
||||||
|
"0caffe1d763c8cca6a61814abe33b776": "需要電子郵件",
|
||||||
|
"10e01c895dc0b2fecc385f9f462f1ca6": "{{http://localhost:3000/colors}} 提供顏色清單",
|
||||||
|
"1b2a6076dccbe91a56f1672eb3b8598c": "回應內文包含登入時建立的 {{AccessToken}} 的內容。\n根據 `include` 參數的值而定,內文還可能包含其他內容:\n\n - `user` - `U+007BUserU+007D` - 目前登入的使用者的資料。 {{(`include=user`)}}\n\n",
|
||||||
|
"1d7833c3ca2f05fdad8fad7537531c40": "\t 主旨:{0}",
|
||||||
|
"1e85f822b547a75d7d385048030e4ecb": "已建立:{0}",
|
||||||
|
"275f22ab95671f095640ca99194b7635": "\t 寄件者:{0}",
|
||||||
|
"2d3071e3b18681c80a090dc0efbdb349": "找不到 id 為 {1} 的 {0}",
|
||||||
|
"2d78192c43fd2ec52ec18f3918894f9a": "{0} 中介軟體已淘汰。如需詳細資料,請參閱 {1}。",
|
||||||
|
"308e1d484516a33df788f873e65faaff": "模型 `{0}` 正在延伸已淘汰的 `DataModel。請改用 `PersistedModel`。",
|
||||||
|
"316e5b82c203cf3de31a449ee07d0650": "預期為布林,但卻取得 {0}",
|
||||||
|
"320c482401afa1207c04343ab162e803": "無效的主體類型:{0}",
|
||||||
|
"3438fab56cc7ab92dfd88f0497e523e0": "`{0}` 配置的 relations 內容必須是物件",
|
||||||
|
"35e5252c62d80f8c54a5290d30f4c7d0": "請在 Web 瀏覽器中開啟此鏈結來驗證電子郵件:\n\t{0}",
|
||||||
|
"3aae63bb7e8e046641767571c1591441": "因為尚未驗證電子郵件,所以登入失敗",
|
||||||
|
"3aecb24fa8bdd3f79d168761ca8a6729": "{{middleware}} 階段 {0} 不明",
|
||||||
|
"3caaa84fc103d6d5612173ae6d43b245": "無效記號:{0}",
|
||||||
|
"3d617953470be16d0c2b32f0bcfbb5ee": "感謝您登錄",
|
||||||
|
"3d63008ccfb2af1db2142e8cc2716ace": "警告:未指定用於傳送電子郵件的電子郵件傳輸。請設定傳輸來傳送郵件訊息。",
|
||||||
|
"4203ab415ec66a78d3164345439ba76e": "無法呼叫 {0}.{1}()。尚未設定 {2} 方法。{{PersistedModel}} 未正確連接至 {{DataSource}}!",
|
||||||
|
"44a6c8b1ded4ed653d19ddeaaf89a606": "找不到電子郵件",
|
||||||
|
"4a4f04a4e480fc5d4ee73b84d9a4b904": "正在傳送郵件:",
|
||||||
|
"4b494de07f524703ac0879addbd64b13": "尚未驗證電子郵件",
|
||||||
|
"4cac5f051ae431321673e04045d37772": "模型 `{0}` 正在延伸不明模型 `{1}`。請使用 `PersistedModel` 作為基礎。",
|
||||||
|
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "無法建立資料來源 {0}:{1}",
|
||||||
|
"5858e63efaa0e4ad86b61c0459ea32fa": "您必須將 {{Email}} 模型連接至 {{Mail}} 連接器",
|
||||||
|
"5e81ad3847a290dc650b47618b9cbc7e": "登入失敗",
|
||||||
|
"5fa3afb425819ebde958043e598cb664": "找不到 {{id}} 為 {0} 的模型",
|
||||||
|
"61e5deebaf44d68f4e6a508f30cc31a3": "模型 `{1}` 的關係 `{0}` 不存在",
|
||||||
|
"62e8b0a733417978bab22c8dacf5d7e6": "無法套用大量更新,連接器未正確報告已更新的記錄數。",
|
||||||
|
"63a091ced88001ab6acb58f61ec041c5": "\t 文字:{0}",
|
||||||
|
"6bc376432cd9972cf991aad3de371e78": "遺漏變更的資料:{0}",
|
||||||
|
"705c2d456a3e204c4af56e671ec3225c": "找不到 {{accessToken}}",
|
||||||
|
"734a7bebb65e10899935126ba63dd51f": "`{0}` 配置的 options 內容必須是物件",
|
||||||
|
"779467f467862836e19f494a37d6ab77": "`{0}` 配置的 acls 內容必須是物件陣列",
|
||||||
|
"7d5e7ed0efaedf3f55f380caae0df8b8": "我的第一個行動式應用程式",
|
||||||
|
"7e0fca41d098607e1c9aa353c67e0fa1": "存取記號無效",
|
||||||
|
"7e287fc885d9fdcf42da3a12f38572c1": "需要授權",
|
||||||
|
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "需要 {{accessToken}} 才能登出",
|
||||||
|
"80a32e80cbed65eba2103201a7c94710": "找不到模型:{0}",
|
||||||
|
"83cbdc2560ba9f09155ccfc63e08f1a1": "無法為 `{1}` 重新配置內容 `{0}`",
|
||||||
|
"855ecd4a64885ba272d782435f72a4d4": "\"{0}\" ID \"{1}\" 不明。",
|
||||||
|
"860d1a0b8bd340411fb32baa72867989": "傳輸不支援 HTTP 重新導向。",
|
||||||
|
"895b1f941d026870b3cc8e6af087c197": "需要 {{username}} 或 {{email}}",
|
||||||
|
"8a17c5ef611e2e7535792316e66b8fca": "密碼太長:{0}",
|
||||||
|
"8a27e0c9ce3ebf0e0c3978efb456e13e": "向主機 {0} 要求",
|
||||||
|
"8ae418c605b6a45f2651be9b1677c180": "無效的遠端方法:`{0}`",
|
||||||
|
"8bab6720ecc58ec6412358c858a53484": "大量更新失敗,連接器已修改超乎預期的記錄數:{0}",
|
||||||
|
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
|
||||||
|
"97795efe0c3eb7f35ce8cf8cfe70682b": "`{0}` 的配置遺漏 {{`dataSource`}} 內容。\n請使用 `null` 或 `false` 來標示未連接至任何資料來源的模型。",
|
||||||
|
"a40684f5a9f546115258b76938d1de37": "{{http://localhost:3000/colors}} 提供顏色清單",
|
||||||
|
"a50d10fc6e0959b220e085454c40381e": "找不到使用者:{0}",
|
||||||
|
"a80038252430df2754884bf3c845c4cf": "\"{0}.{1}\" 的遠端 meta 資料遺漏 \"isStatic\" 旗標,這個方法已登錄為實例方法。",
|
||||||
|
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "\"{0}\" {{key}} \"{1}\" 不明。",
|
||||||
|
"ba96498b10c179f9cd75f75c8def4f70": "需要 {{realm}}",
|
||||||
|
"c2b5d51f007178170ca3952d59640ca4": "無法更正 {0} 個變更:\n{1}",
|
||||||
|
"c68a93f0a9524fed4ff64372fc90c55f": "必須提供有效的電子郵件",
|
||||||
|
"cd0412f2f33a4a2a316acc834f3f21a6": "必須指定 {{id}} 或 {{data}}",
|
||||||
|
"d5552322de5605c58b62f47ad26d2716": "已移除 {{`app.boot`}},請改用新的模組 {{loopback-boot}}",
|
||||||
|
"d9ef6dc3770dd8f80a129e92a79851f3": "{0} 已淘汰。如需詳細資料,請參閱 {1}。",
|
||||||
|
"dc568bee32deb0f6eaf63e73b20e8ceb": "忽略 \"{0}\" 的非物件 \"methods\" 設定。",
|
||||||
|
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" {{id}} \"{1}\" 不明。",
|
||||||
|
"e92aa25b6b864e3454b65a7c422bd114": "大量更新失敗,連接器已刪除非預期的記錄數:{0}",
|
||||||
|
"ea63d226b6968e328bdf6876010786b5": "無法套用大量更新,連接器未正確報告已刪除的記錄數。",
|
||||||
|
"ead044e2b4bce74b4357f8a03fb78ec4": "無法呼叫 {0}.{1}()。尚未設定 {2} 方法。{{KeyValueModel}} 未正確連接至 {{DataSource}}!",
|
||||||
|
"ecb06666ef95e5db27a5ac1d6a17923b": "\t 收件者:{0}",
|
||||||
|
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t 傳輸:{0}",
|
||||||
|
"f0bd73df8714cefb925e3b8da2f4c5f6": "結果:{0}",
|
||||||
|
"f1d4ac54357cc0932f385d56814ba7e4": "衝突",
|
||||||
|
"f58cdc481540cd1f69a4aa4da2e37981": "無效密碼:{0}"
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var loopback = require('./loopback');
|
var loopback = require('./loopback');
|
||||||
var debug = require('debug')('loopback:security:access-context');
|
var debug = require('debug')('loopback:security:access-context');
|
||||||
|
@ -56,16 +61,16 @@ function AccessContext(context) {
|
||||||
var principalType = context.principalType || Principal.USER;
|
var principalType = context.principalType || Principal.USER;
|
||||||
var principalId = context.principalId || undefined;
|
var principalId = context.principalId || undefined;
|
||||||
var principalName = context.principalName || undefined;
|
var principalName = context.principalName || undefined;
|
||||||
if (principalId) {
|
if (principalId != null) {
|
||||||
this.addPrincipal(principalType, principalId, principalName);
|
this.addPrincipal(principalType, principalId, principalName);
|
||||||
}
|
}
|
||||||
|
|
||||||
var token = this.accessToken || {};
|
var token = this.accessToken || {};
|
||||||
|
|
||||||
if (token.userId) {
|
if (token.userId != null) {
|
||||||
this.addPrincipal(Principal.USER, token.userId);
|
this.addPrincipal(Principal.USER, token.userId);
|
||||||
}
|
}
|
||||||
if (token.appId) {
|
if (token.appId != null) {
|
||||||
this.addPrincipal(Principal.APPLICATION, token.appId);
|
this.addPrincipal(Principal.APPLICATION, token.appId);
|
||||||
}
|
}
|
||||||
this.remotingContext = context.remotingContext;
|
this.remotingContext = context.remotingContext;
|
||||||
|
@ -146,7 +151,7 @@ AccessContext.prototype.getAppId = function() {
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
AccessContext.prototype.isAuthenticated = function() {
|
AccessContext.prototype.isAuthenticated = function() {
|
||||||
return !!(this.getUserId() || this.getAppId());
|
return this.getUserId() != null || this.getAppId() != null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
|
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Module dependencies.
|
* Module dependencies.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var g = require('strong-globalize')();
|
||||||
|
|
||||||
var DataSource = require('loopback-datasource-juggler').DataSource;
|
var DataSource = require('loopback-datasource-juggler').DataSource;
|
||||||
var Registry = require('./registry');
|
var Registry = require('./registry');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
@ -153,6 +160,11 @@ app.model = function(Model, config) {
|
||||||
this.emit('modelRemoted', Model.sharedClass);
|
this.emit('modelRemoted', Model.sharedClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
Model.on('remoteMethodDisabled', function(model, methodName) {
|
||||||
|
self.emit('remoteMethodDisabled', model, methodName);
|
||||||
|
});
|
||||||
|
|
||||||
Model.shared = isPublic;
|
Model.shared = isPublic;
|
||||||
Model.app = this;
|
Model.app = this;
|
||||||
Model.emit('attached', this);
|
Model.emit('attached', this);
|
||||||
|
@ -219,11 +231,20 @@ app.models = function() {
|
||||||
* @param {Object} config The data source config
|
* @param {Object} config The data source config
|
||||||
*/
|
*/
|
||||||
app.dataSource = function(name, config) {
|
app.dataSource = function(name, config) {
|
||||||
var ds = dataSourcesFromConfig(name, config, this.connectors, this.registry);
|
try {
|
||||||
this.dataSources[name] =
|
var ds = dataSourcesFromConfig(name, config, this.connectors, this.registry);
|
||||||
this.dataSources[classify(name)] =
|
this.dataSources[name] =
|
||||||
this.dataSources[camelize(name)] = ds;
|
this.dataSources[classify(name)] =
|
||||||
return ds;
|
this.dataSources[camelize(name)] = ds;
|
||||||
|
ds.app = this;
|
||||||
|
return ds;
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message) {
|
||||||
|
err.message = g.f('Cannot create data source %s: %s',
|
||||||
|
JSON.stringify(name), err.message);
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -304,11 +325,13 @@ app.enableAuth = function(options) {
|
||||||
var Model = app.registry.findModel(m);
|
var Model = app.registry.findModel(m);
|
||||||
if (!Model) {
|
if (!Model) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Authentication requires model ' + m + ' to be defined.');
|
g.f('Authentication requires model %s to be defined.', m));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m.dataSource || m.app) return;
|
if (Model.dataSource || Model.app) return;
|
||||||
|
|
||||||
|
// Find descendants of Model that are attached,
|
||||||
|
// for example "Customer" extending "User" model
|
||||||
for (var name in appModels) {
|
for (var name in appModels) {
|
||||||
var candidate = appModels[name];
|
var candidate = appModels[name];
|
||||||
var isSubclass = candidate.prototype instanceof Model;
|
var isSubclass = candidate.prototype instanceof Model;
|
||||||
|
@ -360,17 +383,17 @@ app.enableAuth = function(options) {
|
||||||
|
|
||||||
var messages = {
|
var messages = {
|
||||||
403: {
|
403: {
|
||||||
message: 'Access Denied',
|
message: g.f('Access Denied'),
|
||||||
code: 'ACCESS_DENIED'
|
code: 'ACCESS_DENIED',
|
||||||
},
|
},
|
||||||
404: {
|
404: {
|
||||||
message: ('could not find ' + modelName + ' with id ' + modelId),
|
message: (g.f('could not find %s with id %s', modelName, modelId)),
|
||||||
code: 'MODEL_NOT_FOUND'
|
code: 'MODEL_NOT_FOUND',
|
||||||
},
|
},
|
||||||
401: {
|
401: {
|
||||||
message: 'Authorization Required',
|
message: g.f('Authorization Required'),
|
||||||
code: 'AUTHORIZATION_REQUIRED'
|
code: 'AUTHORIZATION_REQUIRED',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
var e = new Error(messages[errStatusCode].message || messages[403].message);
|
var e = new Error(messages[errStatusCode].message || messages[403].message);
|
||||||
|
@ -390,14 +413,14 @@ app.enableAuth = function(options) {
|
||||||
|
|
||||||
app.boot = function(options) {
|
app.boot = function(options) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'`app.boot` was removed, use the new module loopback-boot instead');
|
g.f('{{`app.boot`}} was removed, use the new module {{loopback-boot}} instead'));
|
||||||
};
|
};
|
||||||
|
|
||||||
function dataSourcesFromConfig(name, config, connectorRegistry, registry) {
|
function dataSourcesFromConfig(name, config, connectorRegistry, registry) {
|
||||||
var connectorPath;
|
var connectorPath;
|
||||||
|
|
||||||
assert(typeof config === 'object',
|
assert(typeof config === 'object',
|
||||||
'cannont create data source without config object');
|
'can not create data source without config object');
|
||||||
|
|
||||||
if (typeof config.connector === 'string') {
|
if (typeof config.connector === 'string') {
|
||||||
name = config.connector;
|
name = config.connector;
|
||||||
|
@ -410,6 +433,8 @@ function dataSourcesFromConfig(name, config, connectorRegistry, registry) {
|
||||||
config.connector = require(connectorPath);
|
config.connector = require(connectorPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!config.connector.name)
|
||||||
|
config.connector.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
return registry.createDataSource(config);
|
return registry.createDataSource(config);
|
||||||
|
@ -466,7 +491,7 @@ function setSharedMethodSharedProperties(model, app, modelConfigs) {
|
||||||
var settingValue = settings[setting];
|
var settingValue = settings[setting];
|
||||||
var settingValueType = typeof settingValue;
|
var settingValueType = typeof settingValue;
|
||||||
if (settingValueType !== 'boolean')
|
if (settingValueType !== 'boolean')
|
||||||
throw new TypeError('Expected boolean, got ' + settingValueType);
|
throw new TypeError(g.f('Expected boolean, got %s', settingValueType));
|
||||||
});
|
});
|
||||||
|
|
||||||
// set sharedMethod.shared using the merged settings
|
// set sharedMethod.shared using the merged settings
|
||||||
|
@ -551,7 +576,11 @@ app.listen = function(cb) {
|
||||||
(arguments.length == 1 && typeof arguments[0] == 'function');
|
(arguments.length == 1 && typeof arguments[0] == 'function');
|
||||||
|
|
||||||
if (useAppConfig) {
|
if (useAppConfig) {
|
||||||
server.listen(this.get('port'), this.get('host'), cb);
|
var port = this.get('port');
|
||||||
|
// NOTE(bajtos) port:undefined no longer works on node@6,
|
||||||
|
// we must pass port:0 explicitly
|
||||||
|
if (port === undefined) port = 0;
|
||||||
|
server.listen(port, this.get('host'), cb);
|
||||||
} else {
|
} else {
|
||||||
server.listen.apply(server, arguments);
|
server.listen.apply(server, arguments);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require('events').EventEmitter;
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
module.exports = function(registry) {
|
module.exports = function(registry) {
|
||||||
// NOTE(bajtos) we must use static require() due to browserify limitations
|
// NOTE(bajtos) we must use static require() due to browserify limitations
|
||||||
|
|
||||||
|
registry.KeyValueModel = createModel(
|
||||||
|
require('../common/models/key-value-model.json'),
|
||||||
|
require('../common/models/key-value-model.js'));
|
||||||
|
|
||||||
registry.Email = createModel(
|
registry.Email = createModel(
|
||||||
require('../common/models/email.json'),
|
require('../common/models/email.json'),
|
||||||
require('../common/models/email.js'));
|
require('../common/models/email.js'));
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expose `Connector`.
|
* Expose `Connector`.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
|
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dependencies.
|
* Dependencies.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var g = require('strong-globalize')();
|
||||||
|
|
||||||
var mailer = require('nodemailer');
|
var mailer = require('nodemailer');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var debug = require('debug')('loopback:connector:mail');
|
var debug = require('debug')('loopback:connector:mail');
|
||||||
|
@ -54,10 +61,11 @@ MailConnector.prototype.DataAccessObject = Mailer;
|
||||||
* Example:
|
* Example:
|
||||||
*
|
*
|
||||||
* Email.setupTransport({
|
* Email.setupTransport({
|
||||||
* type: 'SMTP',
|
* type: "SMTP",
|
||||||
* host: "smtp.gmail.com", // hostname
|
* host: "smtp.gmail.com", // hostname
|
||||||
* secureConnection: true, // use SSL
|
* secureConnection: true, // use SSL
|
||||||
* port: 465, // port for secure SMTP
|
* port: 465, // port for secure SMTP
|
||||||
|
* alias: "gmail", // optional alias for use with 'transport' option when sending
|
||||||
* auth: {
|
* auth: {
|
||||||
* user: "gmail.user@gmail.com",
|
* user: "gmail.user@gmail.com",
|
||||||
* pass: "userpass"
|
* pass: "userpass"
|
||||||
|
@ -83,7 +91,7 @@ MailConnector.prototype.setupTransport = function(setting) {
|
||||||
transport = mailer.createTransport(transportModule(setting));
|
transport = mailer.createTransport(transportModule(setting));
|
||||||
}
|
}
|
||||||
|
|
||||||
connector.transportsIndex[setting.type] = transport;
|
connector.transportsIndex[setting.alias || setting.type] = transport;
|
||||||
connector.transports.push(transport);
|
connector.transports.push(transport);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -122,7 +130,8 @@ MailConnector.prototype.defaultTransport = function() {
|
||||||
* to: "bar@blurdybloop.com, baz@blurdybloop.com", // list of receivers
|
* to: "bar@blurdybloop.com, baz@blurdybloop.com", // list of receivers
|
||||||
* subject: "Hello ✔", // Subject line
|
* subject: "Hello ✔", // Subject line
|
||||||
* text: "Hello world ✔", // plaintext body
|
* text: "Hello world ✔", // plaintext body
|
||||||
* html: "<b>Hello world ✔</b>" // html body
|
* html: "<b>Hello world ✔</b>", // html body
|
||||||
|
* transport: "gmail", // See 'alias' option above in setupTransport
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* See https://github.com/andris9/Nodemailer for other supported options.
|
* See https://github.com/andris9/Nodemailer for other supported options.
|
||||||
|
@ -144,22 +153,22 @@ Mailer.send = function(options, fn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (debug.enabled || settings && settings.debug) {
|
if (debug.enabled || settings && settings.debug) {
|
||||||
console.log('Sending Mail:');
|
g.log('Sending Mail:');
|
||||||
if (options.transport) {
|
if (options.transport) {
|
||||||
console.log('\t TRANSPORT:', options.transport);
|
console.log(g.f('\t TRANSPORT:%s', options.transport));
|
||||||
}
|
}
|
||||||
console.log('\t TO:', options.to);
|
g.log('\t TO:%s', options.to);
|
||||||
console.log('\t FROM:', options.from);
|
g.log('\t FROM:%s', options.from);
|
||||||
console.log('\t SUBJECT:', options.subject);
|
g.log('\t SUBJECT:%s', options.subject);
|
||||||
console.log('\t TEXT:', options.text);
|
g.log('\t TEXT:%s', options.text);
|
||||||
console.log('\t HTML:', options.html);
|
g.log('\t HTML:%s', options.html);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transport) {
|
if (transport) {
|
||||||
assert(transport.sendMail, 'You must supply an Email.settings.transports containing a valid transport');
|
assert(transport.sendMail, 'You must supply an Email.settings.transports containing a valid transport');
|
||||||
transport.sendMail(options, fn);
|
transport.sendMail(options, fn);
|
||||||
} else {
|
} else {
|
||||||
console.warn('Warning: No email transport specified for sending email.' +
|
g.warn('Warning: No email transport specified for sending email.' +
|
||||||
' Setup a transport to send mail messages.');
|
' Setup a transport to send mail messages.');
|
||||||
process.nextTick(function() {
|
process.nextTick(function() {
|
||||||
fn(null, options);
|
fn(null, options);
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expose `Memory`.
|
* Expose `Memory`.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
var juggler = require('loopback-datasource-juggler');
|
||||||
|
var remoting = require('strong-remoting');
|
||||||
|
var LoopBackContext = require('loopback-context');
|
||||||
|
var deprecated = require('depd')('loopback');
|
||||||
|
var g = require('strong-globalize')();
|
||||||
|
|
||||||
|
module.exports = function(loopback) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current context object. The context is preserved
|
||||||
|
* across async calls, it behaves like a thread-local storage.
|
||||||
|
*
|
||||||
|
* @returns {ChainedContext} The context object or null.
|
||||||
|
*/
|
||||||
|
loopback.getCurrentContext = function() {
|
||||||
|
// NOTE(bajtos) LoopBackContext.getCurrentContext is overriden whenever
|
||||||
|
// the context changes, therefore we cannot simply assign
|
||||||
|
// LoopBackContext.getCurrentContext() to loopback.getCurrentContext()
|
||||||
|
deprecated(g.f('%s is deprecated. See %s for more details.',
|
||||||
|
'loopback.getCurrentContext()',
|
||||||
|
'https://docs.strongloop.com/display/APIC/Using%20current%20context'));
|
||||||
|
return LoopBackContext.getCurrentContext();
|
||||||
|
};
|
||||||
|
|
||||||
|
juggler.getCurrentContext =
|
||||||
|
remoting.getCurrentContext = loopback.getCurrentContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the given function in such way that
|
||||||
|
* `loopback.getCurrentContext` returns the
|
||||||
|
* provided context object.
|
||||||
|
*
|
||||||
|
* **NOTE**
|
||||||
|
*
|
||||||
|
* The method is supported on the server only, it does not work
|
||||||
|
* in the browser at the moment.
|
||||||
|
*
|
||||||
|
* @param {Function} fn The function to run, it will receive arguments
|
||||||
|
* (currentContext, currentDomain).
|
||||||
|
* @param {ChainedContext} context An optional context object.
|
||||||
|
* When no value is provided, then the default global context is used.
|
||||||
|
*/
|
||||||
|
loopback.runInContext = function(fn, ctx) {
|
||||||
|
deprecated(g.f('%s is deprecated. See %s for more details.',
|
||||||
|
'loopback.runInContext()',
|
||||||
|
'https://docs.strongloop.com/display/APIC/Using%20current%20context'));
|
||||||
|
return LoopBackContext.runInContext(fn, ctx);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new LoopBackContext instance that can be used
|
||||||
|
* for `loopback.runInContext`.
|
||||||
|
*
|
||||||
|
* **NOTES**
|
||||||
|
*
|
||||||
|
* At the moment, `loopback.getCurrentContext` supports
|
||||||
|
* a single global context instance only. If you call `createContext()`
|
||||||
|
* multiple times, `getCurrentContext` will return the last context
|
||||||
|
* created.
|
||||||
|
*
|
||||||
|
* The method is supported on the server only, it does not work
|
||||||
|
* in the browser at the moment.
|
||||||
|
*
|
||||||
|
* @param {String} scopeName An optional scope name.
|
||||||
|
* @return {ChainedContext} The new context object.
|
||||||
|
*/
|
||||||
|
loopback.createContext = function(scopeName) {
|
||||||
|
deprecated(g.f('%s is deprecated. See %s for more details.',
|
||||||
|
'loopback.createContext()',
|
||||||
|
'https://docs.strongloop.com/display/APIC/Using%20current%20context'));
|
||||||
|
return LoopBackContext.createContext(scopeName);
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,4 +1,10 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
|
var deprecated = require('depd')('loopback');
|
||||||
|
|
||||||
var middlewares = exports;
|
var middlewares = exports;
|
||||||
|
|
||||||
|
@ -24,12 +30,10 @@ var middlewareModules = {
|
||||||
'cookieParser': 'cookie-parser',
|
'cookieParser': 'cookie-parser',
|
||||||
'cookieSession': 'cookie-session',
|
'cookieSession': 'cookie-session',
|
||||||
'csrf': 'csurf',
|
'csrf': 'csurf',
|
||||||
'errorHandler': 'errorhandler',
|
|
||||||
'session': 'express-session',
|
'session': 'express-session',
|
||||||
'methodOverride': 'method-override',
|
'methodOverride': 'method-override',
|
||||||
'logger': 'morgan',
|
'logger': 'morgan',
|
||||||
'responseTime': 'response-time',
|
'responseTime': 'response-time',
|
||||||
'favicon': 'serve-favicon',
|
|
||||||
'directory': 'serve-index',
|
'directory': 'serve-index',
|
||||||
// 'static': 'serve-static',
|
// 'static': 'serve-static',
|
||||||
'vhost': 'vhost'
|
'vhost': 'vhost'
|
||||||
|
@ -39,14 +43,20 @@ middlewares.bodyParser = safeRequire('body-parser');
|
||||||
middlewares.json = middlewares.bodyParser && middlewares.bodyParser.json;
|
middlewares.json = middlewares.bodyParser && middlewares.bodyParser.json;
|
||||||
middlewares.urlencoded = middlewares.bodyParser && middlewares.bodyParser.urlencoded;
|
middlewares.urlencoded = middlewares.bodyParser && middlewares.bodyParser.urlencoded;
|
||||||
|
|
||||||
|
['bodyParser', 'json', 'urlencoded'].forEach(function(name) {
|
||||||
|
if (!middlewares[name]) return;
|
||||||
|
middlewares[name] = deprecated.function(
|
||||||
|
middlewares[name],
|
||||||
|
deprecationMessage(name, 'body-parser'));
|
||||||
|
});
|
||||||
|
|
||||||
for (var m in middlewareModules) {
|
for (var m in middlewareModules) {
|
||||||
var moduleName = middlewareModules[m];
|
var moduleName = middlewareModules[m];
|
||||||
middlewares[m] = safeRequire(moduleName) || createMiddlewareNotInstalled(m, moduleName);
|
middlewares[m] = safeRequire(moduleName) || createMiddlewareNotInstalled(m, moduleName);
|
||||||
|
deprecated.property(middlewares, m, deprecationMessage(m, moduleName));
|
||||||
}
|
}
|
||||||
|
|
||||||
// serve-favicon requires a path
|
function deprecationMessage(accessor, moduleName) {
|
||||||
var favicon = middlewares.favicon;
|
return 'loopback.' + accessor + ' is deprecated. ' +
|
||||||
middlewares.favicon = function(icon, options) {
|
'Use `require(\'' + moduleName + '\');` instead.';
|
||||||
icon = icon || path.join(__dirname, '../favicon.ico');
|
}
|
||||||
return favicon(icon, options);
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Module dependencies.
|
* Module dependencies.
|
||||||
*/
|
*/
|
||||||
|
@ -217,7 +222,7 @@ loopback.template = function(file) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
require('../server/current-context')(loopback);
|
require('../lib/current-context')(loopback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a named vanilla JavaScript class constructor with an attached
|
* Create a named vanilla JavaScript class constructor with an attached
|
||||||
|
|
441
lib/model.js
441
lib/model.js
|
@ -1,10 +1,20 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Module Dependencies.
|
* Module Dependencies.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var g = require('strong-globalize')();
|
||||||
|
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
var debug = require('debug')('loopback:model');
|
||||||
var RemoteObjects = require('strong-remoting');
|
var RemoteObjects = require('strong-remoting');
|
||||||
var SharedClass = require('strong-remoting').SharedClass;
|
var SharedClass = require('strong-remoting').SharedClass;
|
||||||
var extend = require('util')._extend;
|
var extend = require('util')._extend;
|
||||||
|
var format = require('util').format;
|
||||||
|
|
||||||
module.exports = function(registry) {
|
module.exports = function(registry) {
|
||||||
|
|
||||||
|
@ -116,6 +126,80 @@ module.exports = function(registry) {
|
||||||
var options = this.settings;
|
var options = this.settings;
|
||||||
var typeName = this.modelName;
|
var typeName = this.modelName;
|
||||||
|
|
||||||
|
// support remoting prototype methods
|
||||||
|
// it's important to setup this function *before* calling `new SharedClass`
|
||||||
|
// otherwise remoting metadata from our base model is picked up
|
||||||
|
ModelCtor.sharedCtor = function(data, id, options, fn) {
|
||||||
|
var ModelCtor = this;
|
||||||
|
|
||||||
|
var isRemoteInvocationWithOptions = typeof data !== 'object' &&
|
||||||
|
typeof id === 'object' &&
|
||||||
|
typeof options === 'function';
|
||||||
|
if (isRemoteInvocationWithOptions) {
|
||||||
|
// sharedCtor(id, options, fn)
|
||||||
|
fn = options;
|
||||||
|
options = id;
|
||||||
|
id = data;
|
||||||
|
data = null;
|
||||||
|
} else if (typeof data === 'function') {
|
||||||
|
// sharedCtor(fn)
|
||||||
|
fn = data;
|
||||||
|
data = null;
|
||||||
|
id = null;
|
||||||
|
options = null;
|
||||||
|
} else if (typeof id === 'function') {
|
||||||
|
// sharedCtor(data, fn)
|
||||||
|
// sharedCtor(id, fn)
|
||||||
|
fn = id;
|
||||||
|
options = null;
|
||||||
|
|
||||||
|
if (typeof data !== 'object') {
|
||||||
|
id = data;
|
||||||
|
data = null;
|
||||||
|
} else {
|
||||||
|
id = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id != null && data) {
|
||||||
|
var model = new ModelCtor(data);
|
||||||
|
model.id = id;
|
||||||
|
fn(null, model);
|
||||||
|
} else if (data) {
|
||||||
|
fn(null, new ModelCtor(data));
|
||||||
|
} else if (id != null) {
|
||||||
|
var filter = {};
|
||||||
|
ModelCtor.findById(id, filter, options, function(err, model) {
|
||||||
|
if (err) {
|
||||||
|
fn(err);
|
||||||
|
} else if (model) {
|
||||||
|
fn(null, model);
|
||||||
|
} else {
|
||||||
|
err = new Error(g.f('could not find a model with {{id}} %s', id));
|
||||||
|
err.statusCode = 404;
|
||||||
|
err.code = 'MODEL_NOT_FOUND';
|
||||||
|
fn(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
fn(new Error(g.f('must specify an {{id}} or {{data}}')));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var idDesc = ModelCtor.modelName + ' id';
|
||||||
|
ModelCtor.sharedCtor.accepts = this._removeOptionsArgIfDisabled([
|
||||||
|
{arg: 'id', type: 'any', required: true, http: {source: 'path'},
|
||||||
|
description: idDesc},
|
||||||
|
// {arg: 'instance', type: 'object', http: {source: 'body'}}
|
||||||
|
{arg: 'options', type: 'object', http: createOptionsViaModelMethod},
|
||||||
|
]);
|
||||||
|
|
||||||
|
ModelCtor.sharedCtor.http = [
|
||||||
|
{path: '/:id'}
|
||||||
|
];
|
||||||
|
|
||||||
|
ModelCtor.sharedCtor.returns = {root: true};
|
||||||
|
|
||||||
var remotingOptions = {};
|
var remotingOptions = {};
|
||||||
extend(remotingOptions, options.remoting || {});
|
extend(remotingOptions, options.remoting || {});
|
||||||
|
|
||||||
|
@ -131,69 +215,13 @@ module.exports = function(registry) {
|
||||||
return val ? new ModelCtor(val) : val;
|
return val ? new ModelCtor(val) : val;
|
||||||
});
|
});
|
||||||
|
|
||||||
// support remoting prototype methods
|
|
||||||
ModelCtor.sharedCtor = function(data, id, fn) {
|
|
||||||
var ModelCtor = this;
|
|
||||||
|
|
||||||
if (typeof data === 'function') {
|
|
||||||
fn = data;
|
|
||||||
data = null;
|
|
||||||
id = null;
|
|
||||||
} else if (typeof id === 'function') {
|
|
||||||
fn = id;
|
|
||||||
|
|
||||||
if (typeof data !== 'object') {
|
|
||||||
id = data;
|
|
||||||
data = null;
|
|
||||||
} else {
|
|
||||||
id = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id && data) {
|
|
||||||
var model = new ModelCtor(data);
|
|
||||||
model.id = id;
|
|
||||||
fn(null, model);
|
|
||||||
} else if (data) {
|
|
||||||
fn(null, new ModelCtor(data));
|
|
||||||
} else if (id) {
|
|
||||||
ModelCtor.findById(id, function(err, model) {
|
|
||||||
if (err) {
|
|
||||||
fn(err);
|
|
||||||
} else if (model) {
|
|
||||||
fn(null, model);
|
|
||||||
} else {
|
|
||||||
err = new Error('could not find a model with id ' + id);
|
|
||||||
err.statusCode = 404;
|
|
||||||
err.code = 'MODEL_NOT_FOUND';
|
|
||||||
fn(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
fn(new Error('must specify an id or data'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var idDesc = ModelCtor.modelName + ' id';
|
|
||||||
ModelCtor.sharedCtor.accepts = [
|
|
||||||
{arg: 'id', type: 'any', required: true, http: {source: 'path'},
|
|
||||||
description: idDesc}
|
|
||||||
// {arg: 'instance', type: 'object', http: {source: 'body'}}
|
|
||||||
];
|
|
||||||
|
|
||||||
ModelCtor.sharedCtor.http = [
|
|
||||||
{path: '/:id'}
|
|
||||||
];
|
|
||||||
|
|
||||||
ModelCtor.sharedCtor.returns = {root: true};
|
|
||||||
|
|
||||||
// before remote hook
|
// before remote hook
|
||||||
ModelCtor.beforeRemote = function(name, fn) {
|
ModelCtor.beforeRemote = function(name, fn) {
|
||||||
var className = this.modelName;
|
var className = this.modelName;
|
||||||
this._runWhenAttachedToApp(function(app) {
|
this._runWhenAttachedToApp(function(app) {
|
||||||
var remotes = app.remotes();
|
var remotes = app.remotes();
|
||||||
remotes.before(className + '.' + name, function(ctx, next) {
|
remotes.before(className + '.' + name, function(ctx, next) {
|
||||||
fn(ctx, ctx.result, next);
|
return fn(ctx, ctx.result, next);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -204,7 +232,7 @@ module.exports = function(registry) {
|
||||||
this._runWhenAttachedToApp(function(app) {
|
this._runWhenAttachedToApp(function(app) {
|
||||||
var remotes = app.remotes();
|
var remotes = app.remotes();
|
||||||
remotes.after(className + '.' + name, function(ctx, next) {
|
remotes.after(className + '.' + name, function(ctx, next) {
|
||||||
fn(ctx, ctx.result, next);
|
return fn(ctx, ctx.result, next);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -229,6 +257,14 @@ module.exports = function(registry) {
|
||||||
sharedClass.resolve(function resolver(define) {
|
sharedClass.resolve(function resolver(define) {
|
||||||
|
|
||||||
var relations = ModelCtor.relations || {};
|
var relations = ModelCtor.relations || {};
|
||||||
|
var defineRaw = define;
|
||||||
|
define = function(name, options, fn) {
|
||||||
|
if (options.accepts) {
|
||||||
|
options = extend({}, options);
|
||||||
|
options.accepts = setupOptionsArgs(options.accepts);
|
||||||
|
}
|
||||||
|
defineRaw(name, options, fn);
|
||||||
|
};
|
||||||
|
|
||||||
// get the relations
|
// get the relations
|
||||||
for (var relationName in relations) {
|
for (var relationName in relations) {
|
||||||
|
@ -357,6 +393,8 @@ module.exports = function(registry) {
|
||||||
return ACL.WRITE;
|
return ACL.WRITE;
|
||||||
case 'updateOrCreate':
|
case 'updateOrCreate':
|
||||||
return ACL.WRITE;
|
return ACL.WRITE;
|
||||||
|
case 'upsertWithWhere':
|
||||||
|
return ACL.WRITE;
|
||||||
case 'upsert':
|
case 'upsert':
|
||||||
return ACL.WRITE;
|
return ACL.WRITE;
|
||||||
case 'exists':
|
case 'exists':
|
||||||
|
@ -417,9 +455,40 @@ module.exports = function(registry) {
|
||||||
if (options.isStatic === undefined) {
|
if (options.isStatic === undefined) {
|
||||||
options.isStatic = true;
|
options.isStatic = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.accepts) {
|
||||||
|
options = extend({}, options);
|
||||||
|
options.accepts = setupOptionsArgs(options.accepts);
|
||||||
|
}
|
||||||
|
|
||||||
this.sharedClass.defineMethod(name, options);
|
this.sharedClass.defineMethod(name, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function setupOptionsArgs(accepts) {
|
||||||
|
if (!Array.isArray(accepts))
|
||||||
|
accepts = [accepts];
|
||||||
|
|
||||||
|
return accepts.map(function(arg) {
|
||||||
|
if (arg.http && arg.http === 'optionsFromRequest') {
|
||||||
|
// clone to preserve the input value
|
||||||
|
arg = extend({}, arg);
|
||||||
|
arg.http = createOptionsViaModelMethod;
|
||||||
|
}
|
||||||
|
return arg;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createOptionsViaModelMethod(ctx) {
|
||||||
|
var EMPTY_OPTIONS = {};
|
||||||
|
var ModelCtor = ctx.method && ctx.method.ctor;
|
||||||
|
if (!ModelCtor)
|
||||||
|
return EMPTY_OPTIONS;
|
||||||
|
if (typeof ModelCtor.createOptionsFromRemotingContext !== 'function')
|
||||||
|
return EMPTY_OPTIONS;
|
||||||
|
debug('createOptionsFromRemotingContext for %s', ctx.method.stringName);
|
||||||
|
return ModelCtor.createOptionsFromRemotingContext(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable remote invocation for the method with the given name.
|
* Disable remote invocation for the method with the given name.
|
||||||
*
|
*
|
||||||
|
@ -430,7 +499,21 @@ module.exports = function(registry) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Model.disableRemoteMethod = function(name, isStatic) {
|
Model.disableRemoteMethod = function(name, isStatic) {
|
||||||
this.sharedClass.disableMethod(name, isStatic || false);
|
var key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic);
|
||||||
|
this.sharedClass.disableMethodByName(key);
|
||||||
|
this.emit('remoteMethodDisabled', this.sharedClass, key);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable remote invocation for the method with the given name.
|
||||||
|
*
|
||||||
|
* @param {String} name The name of the method (include "prototype." if the method is defined on the prototype).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
Model.disableRemoteMethodByName = function(name) {
|
||||||
|
this.sharedClass.disableMethodByName(name);
|
||||||
|
this.emit('remoteMethodDisabled', this.sharedClass, name);
|
||||||
};
|
};
|
||||||
|
|
||||||
Model.belongsToRemoting = function(relationName, relation, define) {
|
Model.belongsToRemoting = function(relationName, relation, define) {
|
||||||
|
@ -441,10 +524,13 @@ module.exports = function(registry) {
|
||||||
define('__get__' + relationName, {
|
define('__get__' + relationName, {
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
http: {verb: 'get', path: '/' + pathName},
|
http: {verb: 'get', path: '/' + pathName},
|
||||||
accepts: {arg: 'refresh', type: 'boolean', http: {source: 'query'}},
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
|
{arg: 'refresh', type: 'boolean', http: {source: 'query'}},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
accessType: 'READ',
|
accessType: 'READ',
|
||||||
description: 'Fetches belongsTo relation ' + relationName + '.',
|
description: format('Fetches belongsTo relation %s.', relationName),
|
||||||
returns: {arg: relationName, type: modelName, root: true}
|
returns: {arg: relationName, type: modelName, root: true},
|
||||||
}, fn);
|
}, fn);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -452,7 +538,7 @@ module.exports = function(registry) {
|
||||||
if (ctx.result !== null) return cb();
|
if (ctx.result !== null) return cb();
|
||||||
|
|
||||||
var fk = ctx.getArgByName('fk');
|
var fk = ctx.getArgByName('fk');
|
||||||
var msg = 'Unknown "' + toModelName + '" id "' + fk + '".';
|
var msg = g.f('Unknown "%s" id "%s".', toModelName, fk);
|
||||||
var error = new Error(msg);
|
var error = new Error(msg);
|
||||||
error.statusCode = error.status = 404;
|
error.statusCode = error.status = 404;
|
||||||
error.code = 'MODEL_NOT_FOUND';
|
error.code = 'MODEL_NOT_FOUND';
|
||||||
|
@ -466,8 +552,11 @@ module.exports = function(registry) {
|
||||||
define('__get__' + relationName, {
|
define('__get__' + relationName, {
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
http: {verb: 'get', path: '/' + pathName},
|
http: {verb: 'get', path: '/' + pathName},
|
||||||
accepts: {arg: 'refresh', type: 'boolean', http: {source: 'query'}},
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
description: 'Fetches hasOne relation ' + relationName + '.',
|
{arg: 'refresh', type: 'boolean', http: {source: 'query'}},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
|
description: format('Fetches hasOne relation %s.', relationName),
|
||||||
accessType: 'READ',
|
accessType: 'READ',
|
||||||
returns: {arg: relationName, type: relation.modelTo.modelName, root: true},
|
returns: {arg: relationName, type: relation.modelTo.modelName, root: true},
|
||||||
rest: {after: convertNullToNotFoundError.bind(null, toModelName)}
|
rest: {after: convertNullToNotFoundError.bind(null, toModelName)}
|
||||||
|
@ -476,8 +565,14 @@ module.exports = function(registry) {
|
||||||
define('__create__' + relationName, {
|
define('__create__' + relationName, {
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
http: {verb: 'post', path: '/' + pathName},
|
http: {verb: 'post', path: '/' + pathName},
|
||||||
accepts: {arg: 'data', type: toModelName, http: {source: 'body'}},
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
description: 'Creates a new instance in ' + relationName + ' of this model.',
|
{
|
||||||
|
arg: 'data', type: 'object', model: toModelName,
|
||||||
|
http: {source: 'body'},
|
||||||
|
},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
|
description: format('Creates a new instance in %s of this model.', relationName),
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
returns: {arg: 'data', type: toModelName, root: true}
|
returns: {arg: 'data', type: toModelName, root: true}
|
||||||
});
|
});
|
||||||
|
@ -485,8 +580,14 @@ module.exports = function(registry) {
|
||||||
define('__update__' + relationName, {
|
define('__update__' + relationName, {
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
http: {verb: 'put', path: '/' + pathName},
|
http: {verb: 'put', path: '/' + pathName},
|
||||||
accepts: {arg: 'data', type: toModelName, http: {source: 'body'}},
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
description: 'Update ' + relationName + ' of this model.',
|
{
|
||||||
|
arg: 'data', type: 'object', model: toModelName,
|
||||||
|
http: {source: 'body'},
|
||||||
|
},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
|
description: format('Update %s of this model.', relationName),
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
returns: {arg: 'data', type: toModelName, root: true}
|
returns: {arg: 'data', type: toModelName, root: true}
|
||||||
});
|
});
|
||||||
|
@ -494,8 +595,11 @@ module.exports = function(registry) {
|
||||||
define('__destroy__' + relationName, {
|
define('__destroy__' + relationName, {
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
http: {verb: 'delete', path: '/' + pathName},
|
http: {verb: 'delete', path: '/' + pathName},
|
||||||
description: 'Deletes ' + relationName + ' of this model.',
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
accessType: 'WRITE'
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
|
description: format('Deletes %s of this model.', relationName),
|
||||||
|
accessType: 'WRITE',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -507,10 +611,16 @@ module.exports = function(registry) {
|
||||||
define('__findById__' + relationName, {
|
define('__findById__' + relationName, {
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
http: {verb: 'get', path: '/' + pathName + '/:fk'},
|
http: {verb: 'get', path: '/' + pathName + '/:fk'},
|
||||||
accepts: {arg: 'fk', type: 'any',
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
description: 'Foreign key for ' + relationName, required: true,
|
{
|
||||||
http: {source: 'path'}},
|
arg: 'fk', type: 'any',
|
||||||
description: 'Find a related item by id for ' + relationName + '.',
|
description: format('Foreign key for %s', relationName),
|
||||||
|
required: true,
|
||||||
|
http: {source: 'path'},
|
||||||
|
},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
|
description: format('Find a related item by id for %s.', relationName),
|
||||||
accessType: 'READ',
|
accessType: 'READ',
|
||||||
returns: {arg: 'result', type: toModelName, root: true},
|
returns: {arg: 'result', type: toModelName, root: true},
|
||||||
rest: {after: convertNullToNotFoundError.bind(null, toModelName)}
|
rest: {after: convertNullToNotFoundError.bind(null, toModelName)}
|
||||||
|
@ -520,10 +630,16 @@ module.exports = function(registry) {
|
||||||
define('__destroyById__' + relationName, {
|
define('__destroyById__' + relationName, {
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
http: {verb: 'delete', path: '/' + pathName + '/:fk'},
|
http: {verb: 'delete', path: '/' + pathName + '/:fk'},
|
||||||
accepts: {arg: 'fk', type: 'any',
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
description: 'Foreign key for ' + relationName, required: true,
|
{
|
||||||
http: {source: 'path'}},
|
arg: 'fk', type: 'any',
|
||||||
description: 'Delete a related item by id for ' + relationName + '.',
|
description: format('Foreign key for %s', relationName),
|
||||||
|
required: true,
|
||||||
|
http: {source: 'path'},
|
||||||
|
},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
|
description: format('Delete a related item by id for %s.', relationName),
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
returns: []
|
returns: []
|
||||||
}, destroyByIdFunc);
|
}, destroyByIdFunc);
|
||||||
|
@ -532,13 +648,15 @@ module.exports = function(registry) {
|
||||||
define('__updateById__' + relationName, {
|
define('__updateById__' + relationName, {
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
http: {verb: 'put', path: '/' + pathName + '/:fk'},
|
http: {verb: 'put', path: '/' + pathName + '/:fk'},
|
||||||
accepts: [
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
{arg: 'fk', type: 'any',
|
{arg: 'fk', type: 'any',
|
||||||
description: 'Foreign key for ' + relationName, required: true,
|
description: format('Foreign key for %s', relationName),
|
||||||
http: {source: 'path'}},
|
required: true,
|
||||||
{arg: 'data', type: toModelName, http: {source: 'body'}}
|
http: { source: 'path' }},
|
||||||
],
|
{arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}},
|
||||||
description: 'Update a related item by id for ' + relationName + '.',
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
|
description: format('Update a related item by id for %s.', relationName),
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
returns: {arg: 'result', type: toModelName, root: true}
|
returns: {arg: 'result', type: toModelName, root: true}
|
||||||
}, updateByIdFunc);
|
}, updateByIdFunc);
|
||||||
|
@ -549,17 +667,21 @@ module.exports = function(registry) {
|
||||||
var accepts = [];
|
var accepts = [];
|
||||||
if (relation.type === 'hasMany' && relation.modelThrough) {
|
if (relation.type === 'hasMany' && relation.modelThrough) {
|
||||||
// Restrict: only hasManyThrough relation can have additional properties
|
// Restrict: only hasManyThrough relation can have additional properties
|
||||||
accepts.push({arg: 'data', type: modelThrough.modelName, http: {source: 'body'}});
|
accepts.push({arg: 'data', type: 'object', model: modelThrough.modelName, http: {source: 'body'}});
|
||||||
}
|
}
|
||||||
|
|
||||||
var addFunc = this.prototype['__link__' + relationName];
|
var addFunc = this.prototype['__link__' + relationName];
|
||||||
define('__link__' + relationName, {
|
define('__link__' + relationName, {
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
http: {verb: 'put', path: '/' + pathName + '/rel/:fk'},
|
http: {verb: 'put', path: '/' + pathName + '/rel/:fk'},
|
||||||
accepts: [{arg: 'fk', type: 'any',
|
accepts: [{ arg: 'fk', type: 'any',
|
||||||
description: 'Foreign key for ' + relationName, required: true,
|
description: format('Foreign key for %s', relationName),
|
||||||
http: {source: 'path'}}].concat(accepts),
|
required: true,
|
||||||
description: 'Add a related item by id for ' + relationName + '.',
|
http: {source: 'path'}},
|
||||||
|
].concat(accepts).concat(this._removeOptionsArgIfDisabled([
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
])),
|
||||||
|
description: format('Add a related item by id for %s.', relationName),
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
returns: {arg: relationName, type: modelThrough.modelName, root: true}
|
returns: {arg: relationName, type: modelThrough.modelName, root: true}
|
||||||
}, addFunc);
|
}, addFunc);
|
||||||
|
@ -568,10 +690,16 @@ module.exports = function(registry) {
|
||||||
define('__unlink__' + relationName, {
|
define('__unlink__' + relationName, {
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'},
|
http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'},
|
||||||
accepts: {arg: 'fk', type: 'any',
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
description: 'Foreign key for ' + relationName, required: true,
|
{
|
||||||
http: {source: 'path'}},
|
arg: 'fk', type: 'any',
|
||||||
description: 'Remove the ' + relationName + ' relation to an item by id.',
|
description: format('Foreign key for %s', relationName),
|
||||||
|
required: true,
|
||||||
|
http: {source: 'path'},
|
||||||
|
},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
|
description: format('Remove the %s relation to an item by id.', relationName),
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
returns: []
|
returns: []
|
||||||
}, removeFunc);
|
}, removeFunc);
|
||||||
|
@ -582,10 +710,16 @@ module.exports = function(registry) {
|
||||||
define('__exists__' + relationName, {
|
define('__exists__' + relationName, {
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
http: {verb: 'head', path: '/' + pathName + '/rel/:fk'},
|
http: {verb: 'head', path: '/' + pathName + '/rel/:fk'},
|
||||||
accepts: {arg: 'fk', type: 'any',
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
description: 'Foreign key for ' + relationName, required: true,
|
{
|
||||||
http: {source: 'path'}},
|
arg: 'fk', type: 'any',
|
||||||
description: 'Check the existence of ' + relationName + ' relation to an item by id.',
|
description: format('Foreign key for %s', relationName),
|
||||||
|
required: true,
|
||||||
|
http: {source: 'path'},
|
||||||
|
},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
|
description: format('Check the existence of %s relation to an item by id.', relationName),
|
||||||
accessType: 'READ',
|
accessType: 'READ',
|
||||||
returns: {arg: 'exists', type: 'boolean', root: true},
|
returns: {arg: 'exists', type: 'boolean', root: true},
|
||||||
rest: {
|
rest: {
|
||||||
|
@ -594,7 +728,7 @@ module.exports = function(registry) {
|
||||||
if (ctx.result === false) {
|
if (ctx.result === false) {
|
||||||
var modelName = ctx.method.sharedClass.name;
|
var modelName = ctx.method.sharedClass.name;
|
||||||
var id = ctx.getArgByName('id');
|
var id = ctx.getArgByName('id');
|
||||||
var msg = 'Unknown "' + modelName + '" id "' + id + '".';
|
var msg = g.f('Unknown "%s" {{id}} "%s".', modelName, id);
|
||||||
var error = new Error(msg);
|
var error = new Error(msg);
|
||||||
error.statusCode = error.status = 404;
|
error.statusCode = error.status = 404;
|
||||||
error.code = 'MODEL_NOT_FOUND';
|
error.code = 'MODEL_NOT_FOUND';
|
||||||
|
@ -627,8 +761,11 @@ module.exports = function(registry) {
|
||||||
define('__get__' + scopeName, {
|
define('__get__' + scopeName, {
|
||||||
isStatic: isStatic,
|
isStatic: isStatic,
|
||||||
http: {verb: 'get', path: '/' + pathName},
|
http: {verb: 'get', path: '/' + pathName},
|
||||||
accepts: {arg: 'filter', type: 'object'},
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
description: 'Queries ' + scopeName + ' of ' + this.modelName + '.',
|
{arg: 'filter', type: 'object'},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
|
description: format('Queries %s of %s.', scopeName, this.modelName),
|
||||||
accessType: 'READ',
|
accessType: 'READ',
|
||||||
returns: {arg: scopeName, type: [toModelName], root: true}
|
returns: {arg: scopeName, type: [toModelName], root: true}
|
||||||
});
|
});
|
||||||
|
@ -636,8 +773,16 @@ module.exports = function(registry) {
|
||||||
define('__create__' + scopeName, {
|
define('__create__' + scopeName, {
|
||||||
isStatic: isStatic,
|
isStatic: isStatic,
|
||||||
http: {verb: 'post', path: '/' + pathName},
|
http: {verb: 'post', path: '/' + pathName},
|
||||||
accepts: {arg: 'data', type: toModelName, http: {source: 'body'}},
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
description: 'Creates a new instance in ' + scopeName + ' of this model.',
|
{
|
||||||
|
arg: 'data',
|
||||||
|
type: 'object',
|
||||||
|
model: toModelName,
|
||||||
|
http: {source: 'body'},
|
||||||
|
},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
|
description: format('Creates a new instance in %s of this model.', scopeName),
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
returns: {arg: 'data', type: toModelName, root: true}
|
returns: {arg: 'data', type: toModelName, root: true}
|
||||||
});
|
});
|
||||||
|
@ -645,21 +790,46 @@ module.exports = function(registry) {
|
||||||
define('__delete__' + scopeName, {
|
define('__delete__' + scopeName, {
|
||||||
isStatic: isStatic,
|
isStatic: isStatic,
|
||||||
http: {verb: 'delete', path: '/' + pathName},
|
http: {verb: 'delete', path: '/' + pathName},
|
||||||
description: 'Deletes all ' + scopeName + ' of this model.',
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
accessType: 'WRITE'
|
{
|
||||||
|
arg: 'where', type: 'object',
|
||||||
|
// The "where" argument is not exposed in the REST API
|
||||||
|
// but we need to provide a value so that we can pass "options"
|
||||||
|
// as the third argument.
|
||||||
|
http: function(ctx) { return undefined; },
|
||||||
|
},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
|
description: format('Deletes all %s of this model.', scopeName),
|
||||||
|
accessType: 'WRITE',
|
||||||
});
|
});
|
||||||
|
|
||||||
define('__count__' + scopeName, {
|
define('__count__' + scopeName, {
|
||||||
isStatic: isStatic,
|
isStatic: isStatic,
|
||||||
http: {verb: 'get', path: '/' + pathName + '/count'},
|
http: {verb: 'get', path: '/' + pathName + '/count'},
|
||||||
accepts: {arg: 'where', type: 'object', description: 'Criteria to match model instances'},
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
description: 'Counts ' + scopeName + ' of ' + this.modelName + '.',
|
{
|
||||||
|
arg: 'where', type: 'object',
|
||||||
|
description: 'Criteria to match model instances',
|
||||||
|
},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
|
description: format('Counts %s of %s.', scopeName, this.modelName),
|
||||||
accessType: 'READ',
|
accessType: 'READ',
|
||||||
returns: {arg: 'count', type: 'number'}
|
returns: {arg: 'count', type: 'number'}
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Model._removeOptionsArgIfDisabled = function(accepts) {
|
||||||
|
if (this.settings.injectOptionsFromRemoteContext)
|
||||||
|
return accepts;
|
||||||
|
var lastArg = accepts[accepts.length - 1];
|
||||||
|
var hasOptions = lastArg.arg === 'options' && lastArg.type === 'object';
|
||||||
|
assert(hasOptions, 'last accepts argument is "options" arg');
|
||||||
|
return accepts.slice(0, -1);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enabled deeply-nested queries of related models via REST API.
|
* Enabled deeply-nested queries of related models via REST API.
|
||||||
*
|
*
|
||||||
|
@ -702,9 +872,9 @@ module.exports = function(registry) {
|
||||||
acceptArgs = [
|
acceptArgs = [
|
||||||
{
|
{
|
||||||
arg: paramName, type: 'any', http: { source: 'path' },
|
arg: paramName, type: 'any', http: { source: 'path' },
|
||||||
description: 'Foreign key for ' + relation.name + '.',
|
description: format('Foreign key for %s.', relation.name),
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
httpPath = pathName;
|
httpPath = pathName;
|
||||||
|
@ -732,12 +902,12 @@ module.exports = function(registry) {
|
||||||
|
|
||||||
var getterFn = relation.modelFrom.prototype[getterName];
|
var getterFn = relation.modelFrom.prototype[getterName];
|
||||||
if (typeof getterFn !== 'function') {
|
if (typeof getterFn !== 'function') {
|
||||||
throw new Error('Invalid remote method: `' + getterName + '`');
|
throw new Error(g.f('Invalid remote method: `%s`', getterName));
|
||||||
}
|
}
|
||||||
|
|
||||||
var nestedFn = relation.modelTo.prototype[method.name];
|
var nestedFn = relation.modelTo.prototype[method.name];
|
||||||
if (typeof nestedFn !== 'function') {
|
if (typeof nestedFn !== 'function') {
|
||||||
throw new Error('Invalid remote method: `' + method.name + '`');
|
throw new Error(g.f('Invalid remote method: `%s`', method.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
var opts = {};
|
var opts = {};
|
||||||
|
@ -806,8 +976,8 @@ module.exports = function(registry) {
|
||||||
listenerTree.before = listenerTree.before || {};
|
listenerTree.before = listenerTree.before || {};
|
||||||
listenerTree.after = listenerTree.after || {};
|
listenerTree.after = listenerTree.after || {};
|
||||||
|
|
||||||
var beforeListeners = remotes.listenerTree.before[toModelName] || {};
|
var beforeListeners = listenerTree.before[toModelName] || {};
|
||||||
var afterListeners = remotes.listenerTree.after[toModelName] || {};
|
var afterListeners = listenerTree.after[toModelName] || {};
|
||||||
|
|
||||||
sharedClass.methods().forEach(function(method) {
|
sharedClass.methods().forEach(function(method) {
|
||||||
var delegateTo = method.rest && method.rest.delegateTo;
|
var delegateTo = method.rest && method.rest.delegateTo;
|
||||||
|
@ -830,12 +1000,53 @@ module.exports = function(registry) {
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Relation `' + relationName + '` does not exist for model `' + this.modelName + '`');
|
var msg = g.f('Relation `%s` does not exist for model `%s`', relationName, this.modelName);
|
||||||
|
throw new Error(msg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Model.ValidationError = require('loopback-datasource-juggler').ValidationError;
|
Model.ValidationError = require('loopback-datasource-juggler').ValidationError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create "options" value to use when invoking model methods
|
||||||
|
* via strong-remoting (e.g. REST).
|
||||||
|
*
|
||||||
|
* Example
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* MyModel.myMethod = function(options, cb) {
|
||||||
|
* // by default, options contains only one property "accessToken"
|
||||||
|
* var accessToken = options && options.accessToken;
|
||||||
|
* var userId = accessToken && accessToken.userId;
|
||||||
|
* var message = 'Hello ' + (userId ? 'user #' + userId : 'anonymous');
|
||||||
|
* cb(null, message);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* MyModel.remoteMethod('myMethod', {
|
||||||
|
* accepts: {
|
||||||
|
* arg: 'options',
|
||||||
|
* type: 'object',
|
||||||
|
* // "optionsFromRequest" is a loopback-specific HTTP mapping that
|
||||||
|
* // calls Model's createOptionsFromRemotingContext
|
||||||
|
* // to build the argument value
|
||||||
|
* http: 'optionsFromRequest'
|
||||||
|
* },
|
||||||
|
* returns: {
|
||||||
|
* arg: 'message',
|
||||||
|
* type: 'string'
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param {Object} ctx A strong-remoting Context instance
|
||||||
|
* @returns {Object} The value to pass to "options" argument.
|
||||||
|
*/
|
||||||
|
Model.createOptionsFromRemotingContext = function(ctx) {
|
||||||
|
return {
|
||||||
|
accessToken: ctx.req.accessToken,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// setup the initial model
|
// setup the initial model
|
||||||
Model.setup();
|
Model.setup();
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Module Dependencies.
|
* Module Dependencies.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var g = require('strong-globalize')();
|
||||||
var runtime = require('./runtime');
|
var runtime = require('./runtime');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
@ -60,9 +66,10 @@ module.exports = function(registry) {
|
||||||
|
|
||||||
function throwNotAttached(modelName, methodName) {
|
function throwNotAttached(modelName, methodName) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Cannot call ' + modelName + '.' + methodName + '().' +
|
g.f('Cannot call %s.%s().' +
|
||||||
' The ' + methodName + ' method has not been setup.' +
|
' The %s method has not been setup.' +
|
||||||
' The PersistedModel has not been correctly attached to a DataSource!'
|
' The {{PersistedModel}} has not been correctly attached to a {{DataSource}}!',
|
||||||
|
modelName, methodName, methodName)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +84,7 @@ module.exports = function(registry) {
|
||||||
|
|
||||||
var modelName = ctx.method.sharedClass.name;
|
var modelName = ctx.method.sharedClass.name;
|
||||||
var id = ctx.getArgByName('id');
|
var id = ctx.getArgByName('id');
|
||||||
var msg = 'Unknown "' + modelName + '" id "' + id + '".';
|
var msg = g.f('Unknown "%s" {{id}} "%s".', modelName, id);
|
||||||
var error = new Error(msg);
|
var error = new Error(msg);
|
||||||
error.statusCode = error.status = 404;
|
error.statusCode = error.status = 404;
|
||||||
error.code = 'MODEL_NOT_FOUND';
|
error.code = 'MODEL_NOT_FOUND';
|
||||||
|
@ -106,22 +113,78 @@ module.exports = function(registry) {
|
||||||
* @param {Object} model Updated model instance.
|
* @param {Object} model Updated model instance.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
PersistedModel.upsert = PersistedModel.updateOrCreate = function upsert(data, callback) {
|
PersistedModel.upsert = PersistedModel.updateOrCreate = PersistedModel.patchOrCreate =
|
||||||
|
function upsert(data, callback) {
|
||||||
throwNotAttached(this.modelName, 'upsert');
|
throwNotAttached(this.modelName, 'upsert');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find one record matching the optional `where` filter. The same as `find`, but limited to one object.
|
* Update or insert a model instance based on the search criteria.
|
||||||
* Returns an object, not collection.
|
* If there is a single instance retrieved, update the retrieved model.
|
||||||
* If not found, create the object using data provided as second argument.
|
* Creates a new model if no model instances were found.
|
||||||
*
|
* Returns an error if multiple instances are found.
|
||||||
* @param {Object} where Where clause, such as `{test: 'me'}`
|
* * @param {Object} [where] `where` filter, like
|
||||||
|
* ```
|
||||||
|
* { key: val, key2: {gt: 'val2'}, ...}
|
||||||
|
* ```
|
||||||
* <br/>see
|
* <br/>see
|
||||||
* [Where filter](https://docs.strongloop.com/display/LB/Where+filter#Wherefilter-Whereclauseforothermethods).
|
* [Where filter](https://docs.strongloop.com/display/LB/Where+filter#Wherefilter-Whereclauseforothermethods).
|
||||||
|
* @param {Object} data The model instance data to insert.
|
||||||
|
* @callback {Function} callback Callback function called with `cb(err, obj)` signature.
|
||||||
|
* @param {Error} err Error object; see [Error object](http://docs.strongloop.com/display/LB/Error+object).
|
||||||
|
* @param {Object} model Updated model instance.
|
||||||
|
*/
|
||||||
|
|
||||||
|
PersistedModel.upsertWithWhere =
|
||||||
|
PersistedModel.patchOrCreateWithWhere = function upsertWithWhere(where, data, callback) {
|
||||||
|
throwNotAttached(this.modelName, 'upsertWithWhere');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace or insert a model instance; replace existing record if one is found,
|
||||||
|
* such that parameter `data.id` matches `id` of model instance; otherwise,
|
||||||
|
* insert a new record.
|
||||||
|
* @param {Object} data The model instance data.
|
||||||
|
* @options {Object} [options] Options for replaceOrCreate
|
||||||
|
* @property {Boolean} validate Perform validation before saving. Default is true.
|
||||||
|
* @callback {Function} callback Callback function called with `cb(err, obj)` signature.
|
||||||
|
* @param {Error} err Error object; see [Error object](http://docs.strongloop.com/display/LB/Error+object).
|
||||||
|
* @param {Object} model Replaced model instance.
|
||||||
|
*/
|
||||||
|
|
||||||
|
PersistedModel.replaceOrCreate = function replaceOrCreate(data, callback) {
|
||||||
|
throwNotAttached(this.modelName, 'replaceOrCreate');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds one record matching the optional filter object. If not found, creates
|
||||||
|
* the object using the data provided as second argument. In this sense it is
|
||||||
|
* the same as `find`, but limited to one object. Returns an object, not
|
||||||
|
* collection. If you don't provide the filter object argument, it tries to
|
||||||
|
* locate an existing object that matches the `data` argument.
|
||||||
|
*
|
||||||
|
* @options {Object} [filter] Optional Filter object; see below.
|
||||||
|
* @property {String|Object|Array} fields Identify fields to include in return result.
|
||||||
|
* <br/>See [Fields filter](http://docs.strongloop.com/display/LB/Fields+filter).
|
||||||
|
* @property {String|Object|Array} include See PersistedModel.include documentation.
|
||||||
|
* <br/>See [Include filter](http://docs.strongloop.com/display/LB/Include+filter).
|
||||||
|
* @property {Number} limit Maximum number of instances to return.
|
||||||
|
* <br/>See [Limit filter](http://docs.strongloop.com/display/LB/Limit+filter).
|
||||||
|
* @property {String} order Sort order: either "ASC" for ascending or "DESC" for descending.
|
||||||
|
* <br/>See [Order filter](http://docs.strongloop.com/display/LB/Order+filter).
|
||||||
|
* @property {Number} skip Number of results to skip.
|
||||||
|
* <br/>See [Skip filter](http://docs.strongloop.com/display/LB/Skip+filter).
|
||||||
|
* @property {Object} where Where clause, like
|
||||||
|
* ```
|
||||||
|
* {where: {key: val, key2: {gt: val2}, ...}}
|
||||||
|
* ```
|
||||||
|
* <br/>See
|
||||||
|
* [Where filter](https://docs.strongloop.com/display/LB/Where+filter#Wherefilter-Whereclauseforqueries).
|
||||||
* @param {Object} data Data to insert if object matching the `where` filter is not found.
|
* @param {Object} data Data to insert if object matching the `where` filter is not found.
|
||||||
* @callback {Function} callback Callback function called with `cb(err, instance)` arguments. Required.
|
* @callback {Function} callback Callback function called with `cb(err, instance, created)` arguments. Required.
|
||||||
* @param {Error} err Error object; see [Error object](http://docs.strongloop.com/display/LB/Error+object).
|
* @param {Error} err Error object; see [Error object](http://docs.strongloop.com/display/LB/Error+object).
|
||||||
* @param {Object} instance Model instance matching the `where` filter, if found.
|
* @param {Object} instance Model instance matching the `where` filter, if found.
|
||||||
|
* @param {Boolean} created True if the instance matching the `where` filter was created.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
PersistedModel.findOrCreate = function findOrCreate(query, data, callback) {
|
PersistedModel.findOrCreate = function findOrCreate(query, data, callback) {
|
||||||
|
@ -456,10 +519,45 @@ module.exports = function(registry) {
|
||||||
* @param {Object} instance Updated instance.
|
* @param {Object} instance Updated instance.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
PersistedModel.prototype.updateAttributes = function updateAttributes(data, cb) {
|
PersistedModel.prototype.updateAttributes = PersistedModel.prototype.patchAttributes =
|
||||||
|
function updateAttributes(data, cb) {
|
||||||
throwNotAttached(this.modelName, 'updateAttributes');
|
throwNotAttached(this.modelName, 'updateAttributes');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace attributes for a model instance and persist it into the datasource.
|
||||||
|
* Performs validation before replacing.
|
||||||
|
*
|
||||||
|
* @param {Object} data Data to replace.
|
||||||
|
* @options {Object} [options] Options for replace
|
||||||
|
* @property {Boolean} validate Perform validation before saving. Default is true.
|
||||||
|
* @callback {Function} callback Callback function called with `(err, instance)` arguments.
|
||||||
|
* @param {Error} err Error object; see [Error object](http://docs.strongloop.com/display/LB/Error+object).
|
||||||
|
* @param {Object} instance Replaced instance.
|
||||||
|
*/
|
||||||
|
|
||||||
|
PersistedModel.prototype.replaceAttributes = function replaceAttributes(data, cb) {
|
||||||
|
throwNotAttached(this.modelName, 'replaceAttributes');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace attributes for a model instance whose id is the first input
|
||||||
|
* argument and persist it into the datasource.
|
||||||
|
* Performs validation before replacing.
|
||||||
|
*
|
||||||
|
* @param {*} id The ID value of model instance to replace.
|
||||||
|
* @param {Object} data Data to replace.
|
||||||
|
* @options {Object} [options] Options for replace
|
||||||
|
* @property {Boolean} validate Perform validation before saving. Default is true.
|
||||||
|
* @callback {Function} callback Callback function called with `(err, instance)` arguments.
|
||||||
|
* @param {Error} err Error object; see [Error object](http://docs.strongloop.com/display/LB/Error+object).
|
||||||
|
* @param {Object} instance Replaced instance.
|
||||||
|
*/
|
||||||
|
|
||||||
|
PersistedModel.replaceById = function replaceById(id, data, cb) {
|
||||||
|
throwNotAttached(this.modelName, 'replaceById');
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reload object from persistence. Requires `id` member of `object` to be able to call `find`.
|
* Reload object from persistence. Requires `id` member of `object` to be able to call `find`.
|
||||||
* @callback {Function} callback Callback function called with `(err, instance)` arguments. Required.
|
* @callback {Function} callback Callback function called with `(err, instance)` arguments. Required.
|
||||||
|
@ -528,6 +626,9 @@ module.exports = function(registry) {
|
||||||
var typeName = PersistedModel.modelName;
|
var typeName = PersistedModel.modelName;
|
||||||
var options = PersistedModel.settings;
|
var options = PersistedModel.settings;
|
||||||
|
|
||||||
|
// This is just for LB 2.x
|
||||||
|
options.replaceOnPUT = options.replaceOnPUT === true;
|
||||||
|
|
||||||
function setRemoting(scope, name, options) {
|
function setRemoting(scope, name, options) {
|
||||||
var fn = scope[name];
|
var fn = scope[name];
|
||||||
fn._delegate = true;
|
fn._delegate = true;
|
||||||
|
@ -538,24 +639,82 @@ module.exports = function(registry) {
|
||||||
setRemoting(PersistedModel, 'create', {
|
setRemoting(PersistedModel, 'create', {
|
||||||
description: 'Create a new instance of the model and persist it into the data source.',
|
description: 'Create a new instance of the model and persist it into the data source.',
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}},
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
|
{
|
||||||
|
arg: 'data', type: 'object', model: typeName, allowArray: true,
|
||||||
|
description: 'Model instance data',
|
||||||
|
http: {source: 'body'},
|
||||||
|
},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
returns: {arg: 'data', type: typeName, root: true},
|
returns: {arg: 'data', type: typeName, root: true},
|
||||||
http: {verb: 'post', path: '/'}
|
http: {verb: 'post', path: '/'}
|
||||||
});
|
});
|
||||||
|
|
||||||
setRemoting(PersistedModel, 'upsert', {
|
var upsertOptions = {
|
||||||
aliases: ['updateOrCreate'],
|
aliases: ['patchOrCreate', 'updateOrCreate'],
|
||||||
description: 'Update an existing model instance or insert a new one into the data source.',
|
description: 'Patch an existing model instance or insert a new one into the data source.',
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}},
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
|
{
|
||||||
|
arg: 'data', type: 'object', model: typeName, http: {source: 'body'},
|
||||||
|
description: 'Model instance data',
|
||||||
|
},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
returns: {arg: 'data', type: typeName, root: true},
|
returns: {arg: 'data', type: typeName, root: true},
|
||||||
http: {verb: 'put', path: '/'}
|
http: [{verb: 'patch', path: '/'}],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!options.replaceOnPUT) {
|
||||||
|
upsertOptions.http.unshift({ verb: 'put', path: '/' });
|
||||||
|
}
|
||||||
|
setRemoting(PersistedModel, 'upsert', upsertOptions);
|
||||||
|
|
||||||
|
var replaceOrCreateOptions = {
|
||||||
|
description: 'Replace an existing model instance or insert a new one into the data source.',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
|
{
|
||||||
|
arg: 'data', type: 'object', model: typeName,
|
||||||
|
http: {source: 'body'},
|
||||||
|
description: 'Model instance data',
|
||||||
|
},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
|
returns: {arg: 'data', type: typeName, root: true},
|
||||||
|
http: [{verb: 'post', path: '/replaceOrCreate'}],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options.replaceOnPUT) {
|
||||||
|
replaceOrCreateOptions.http.push({ verb: 'put', path: '/' });
|
||||||
|
}
|
||||||
|
|
||||||
|
setRemoting(PersistedModel, 'replaceOrCreate', replaceOrCreateOptions);
|
||||||
|
|
||||||
|
setRemoting(PersistedModel, 'upsertWithWhere', {
|
||||||
|
aliases: ['patchOrCreateWithWhere'],
|
||||||
|
description: 'Update an existing model instance or insert a new one into ' +
|
||||||
|
'the data source based on the where criteria.',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
|
{arg: 'where', type: 'object', http: {source: 'query'},
|
||||||
|
description: 'Criteria to match model instances'},
|
||||||
|
{arg: 'data', type: 'object', model: typeName, http: {source: 'body'},
|
||||||
|
description: 'An object of model property name/value pairs'},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
|
returns: { arg: 'data', type: typeName, root: true },
|
||||||
|
http: { verb: 'post', path: '/upsertWithWhere' },
|
||||||
});
|
});
|
||||||
|
|
||||||
setRemoting(PersistedModel, 'exists', {
|
setRemoting(PersistedModel, 'exists', {
|
||||||
description: 'Check whether a model instance exists in the data source.',
|
description: 'Check whether a model instance exists in the data source.',
|
||||||
accessType: 'READ',
|
accessType: 'READ',
|
||||||
accepts: {arg: 'id', type: 'any', description: 'Model id', required: true},
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
|
{arg: 'id', type: 'any', description: 'Model id', required: true},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
returns: {arg: 'exists', type: 'boolean'},
|
returns: {arg: 'exists', type: 'boolean'},
|
||||||
http: [
|
http: [
|
||||||
{verb: 'get', path: '/:id/exists'},
|
{verb: 'get', path: '/:id/exists'},
|
||||||
|
@ -584,23 +743,51 @@ module.exports = function(registry) {
|
||||||
});
|
});
|
||||||
|
|
||||||
setRemoting(PersistedModel, 'findById', {
|
setRemoting(PersistedModel, 'findById', {
|
||||||
description: 'Find a model instance by id from the data source.',
|
description: 'Find a model instance by {{id}} from the data source.',
|
||||||
accessType: 'READ',
|
accessType: 'READ',
|
||||||
accepts: [
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
{ arg: 'id', type: 'any', description: 'Model id', required: true,
|
{ arg: 'id', type: 'any', description: 'Model id', required: true,
|
||||||
http: {source: 'path'}},
|
http: {source: 'path'}},
|
||||||
{ arg: 'filter', type: 'object',
|
{arg: 'filter', type: 'object',
|
||||||
description: 'Filter defining fields and include'}
|
description:
|
||||||
],
|
'Filter defining fields and include - must be a JSON-encoded string (' +
|
||||||
|
'{"something":"value"})'},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
returns: {arg: 'data', type: typeName, root: true},
|
returns: {arg: 'data', type: typeName, root: true},
|
||||||
http: {verb: 'get', path: '/:id'},
|
http: {verb: 'get', path: '/:id'},
|
||||||
rest: {after: convertNullToNotFoundError}
|
rest: {after: convertNullToNotFoundError}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var replaceByIdOptions = {
|
||||||
|
description: 'Replace attributes for a model instance and persist it into the data source.',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
|
{arg: 'id', type: 'any', description: 'Model id', required: true,
|
||||||
|
http: {source: 'path'}},
|
||||||
|
{arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description:
|
||||||
|
'Model instance data'},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
|
returns: { arg: 'data', type: typeName, root: true },
|
||||||
|
http: [{ verb: 'post', path: '/:id/replace' }],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options.replaceOnPUT) {
|
||||||
|
replaceByIdOptions.http.push({ verb: 'put', path: '/:id' });
|
||||||
|
}
|
||||||
|
|
||||||
|
setRemoting(PersistedModel, 'replaceById', replaceByIdOptions);
|
||||||
|
|
||||||
setRemoting(PersistedModel, 'find', {
|
setRemoting(PersistedModel, 'find', {
|
||||||
description: 'Find all instances of the model matched by filter from the data source.',
|
description: 'Find all instances of the model matched by filter from the data source.',
|
||||||
accessType: 'READ',
|
accessType: 'READ',
|
||||||
accepts: {arg: 'filter', type: 'object', description: 'Filter defining fields, where, include, order, offset, and limit'},
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
|
{arg: 'filter', type: 'object', description:
|
||||||
|
'Filter defining fields, where, include, order, offset, and limit - must be a ' +
|
||||||
|
'JSON-encoded string ({"something":"value"})'},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
returns: {arg: 'data', type: [typeName], root: true},
|
returns: {arg: 'data', type: [typeName], root: true},
|
||||||
http: {verb: 'get', path: '/'}
|
http: {verb: 'get', path: '/'}
|
||||||
});
|
});
|
||||||
|
@ -608,7 +795,12 @@ module.exports = function(registry) {
|
||||||
setRemoting(PersistedModel, 'findOne', {
|
setRemoting(PersistedModel, 'findOne', {
|
||||||
description: 'Find first instance of the model matched by filter from the data source.',
|
description: 'Find first instance of the model matched by filter from the data source.',
|
||||||
accessType: 'READ',
|
accessType: 'READ',
|
||||||
accepts: {arg: 'filter', type: 'object', description: 'Filter defining fields, where, include, order, offset, and limit'},
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
|
{arg: 'filter', type: 'object', description:
|
||||||
|
'Filter defining fields, where, include, order, offset, and limit - must be a ' +
|
||||||
|
'JSON-encoded string ({"something":"value"})'},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
returns: {arg: 'data', type: typeName, root: true},
|
returns: {arg: 'data', type: typeName, root: true},
|
||||||
http: {verb: 'get', path: '/findOne'},
|
http: {verb: 'get', path: '/findOne'},
|
||||||
rest: {after: convertNullToNotFoundError}
|
rest: {after: convertNullToNotFoundError}
|
||||||
|
@ -617,7 +809,10 @@ module.exports = function(registry) {
|
||||||
setRemoting(PersistedModel, 'destroyAll', {
|
setRemoting(PersistedModel, 'destroyAll', {
|
||||||
description: 'Delete all matching records.',
|
description: 'Delete all matching records.',
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
accepts: {arg: 'where', type: 'object', description: 'filter.where object'},
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
|
{arg: 'where', type: 'object', description: 'filter.where object'},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
returns: {
|
returns: {
|
||||||
arg: 'count',
|
arg: 'count',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
@ -630,29 +825,38 @@ module.exports = function(registry) {
|
||||||
|
|
||||||
setRemoting(PersistedModel, 'updateAll', {
|
setRemoting(PersistedModel, 'updateAll', {
|
||||||
aliases: ['update'],
|
aliases: ['update'],
|
||||||
description: 'Update instances of the model matched by where from the data source.',
|
description: 'Update instances of the model matched by {{where}} from the data source.',
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
accepts: [
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
{arg: 'where', type: 'object', http: {source: 'query'},
|
{arg: 'where', type: 'object', http: { source: 'query'},
|
||||||
description: 'Criteria to match model instances'},
|
description: 'Criteria to match model instances'},
|
||||||
{arg: 'data', type: 'object', http: {source: 'body'},
|
{arg: 'data', type: 'object', model: typeName, http: {source: 'body'},
|
||||||
description: 'An object of model property name/value pairs'},
|
description: 'An object of model property name/value pairs'},
|
||||||
],
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
returns: {
|
returns: {
|
||||||
arg: 'count',
|
arg: 'info',
|
||||||
description: 'The number of instances updated',
|
description: 'Information related to the outcome of the operation',
|
||||||
type: 'object',
|
type: {
|
||||||
root: true
|
count: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'The number of instances updated',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
root: true,
|
||||||
},
|
},
|
||||||
http: {verb: 'post', path: '/update'}
|
http: {verb: 'post', path: '/update'}
|
||||||
});
|
});
|
||||||
|
|
||||||
setRemoting(PersistedModel, 'deleteById', {
|
setRemoting(PersistedModel, 'deleteById', {
|
||||||
aliases: ['destroyById', 'removeById'],
|
aliases: ['destroyById', 'removeById'],
|
||||||
description: 'Delete a model instance by id from the data source.',
|
description: 'Delete a model instance by {{id}} from the data source.',
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
accepts: {arg: 'id', type: 'any', description: 'Model id', required: true,
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
http: {source: 'path'}},
|
{arg: 'id', type: 'any', description: 'Model id', required: true,
|
||||||
|
http: {source: 'path'}},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
http: {verb: 'del', path: '/:id'},
|
http: {verb: 'del', path: '/:id'},
|
||||||
returns: {arg: 'count', type: 'object', root: true}
|
returns: {arg: 'count', type: 'object', root: true}
|
||||||
});
|
});
|
||||||
|
@ -660,18 +864,35 @@ module.exports = function(registry) {
|
||||||
setRemoting(PersistedModel, 'count', {
|
setRemoting(PersistedModel, 'count', {
|
||||||
description: 'Count instances of the model matched by where from the data source.',
|
description: 'Count instances of the model matched by where from the data source.',
|
||||||
accessType: 'READ',
|
accessType: 'READ',
|
||||||
accepts: {arg: 'where', type: 'object', description: 'Criteria to match model instances'},
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
|
{arg: 'where', type: 'object', description: 'Criteria to match model instances'},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
returns: {arg: 'count', type: 'number'},
|
returns: {arg: 'count', type: 'number'},
|
||||||
http: {verb: 'get', path: '/count'}
|
http: {verb: 'get', path: '/count'}
|
||||||
});
|
});
|
||||||
|
|
||||||
setRemoting(PersistedModel.prototype, 'updateAttributes', {
|
var updateAttributesOptions = {
|
||||||
description: 'Update attributes for a model instance and persist it into the data source.',
|
aliases: ['patchAttributes'],
|
||||||
|
description: 'Patch attributes for a model instance and persist it into the data source.',
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
accepts: {arg: 'data', type: 'object', http: {source: 'body'}, description: 'An object of model property name/value pairs'},
|
accepts: this._removeOptionsArgIfDisabled([
|
||||||
|
{
|
||||||
|
arg: 'data', type: 'object', model: typeName,
|
||||||
|
http: {source: 'body'},
|
||||||
|
description: 'An object of model property name/value pairs',
|
||||||
|
},
|
||||||
|
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
||||||
|
]),
|
||||||
returns: {arg: 'data', type: typeName, root: true},
|
returns: {arg: 'data', type: typeName, root: true},
|
||||||
http: {verb: 'put', path: '/'}
|
http: [{verb: 'patch', path: '/'}],
|
||||||
});
|
};
|
||||||
|
|
||||||
|
setRemoting(PersistedModel.prototype, 'updateAttributes', updateAttributesOptions);
|
||||||
|
|
||||||
|
if (!options.replaceOnPUT) {
|
||||||
|
updateAttributesOptions.http.unshift({ verb: 'put', path: '/' });
|
||||||
|
}
|
||||||
|
|
||||||
if (options.trackChanges || options.enableRemoteReplication) {
|
if (options.trackChanges || options.enableRemoteReplication) {
|
||||||
setRemoting(PersistedModel, 'diff', {
|
setRemoting(PersistedModel, 'diff', {
|
||||||
|
@ -680,7 +901,7 @@ module.exports = function(registry) {
|
||||||
accepts: [
|
accepts: [
|
||||||
{arg: 'since', type: 'number', description: 'Find deltas since this checkpoint'},
|
{arg: 'since', type: 'number', description: 'Find deltas since this checkpoint'},
|
||||||
{arg: 'remoteChanges', type: 'array', description: 'an array of change objects',
|
{arg: 'remoteChanges', type: 'array', description: 'an array of change objects',
|
||||||
http: {source: 'body'}}
|
http: {source: 'body'}}
|
||||||
],
|
],
|
||||||
returns: {arg: 'result', type: 'object', root: true},
|
returns: {arg: 'result', type: 'object', root: true},
|
||||||
http: {verb: 'post', path: '/diff'}
|
http: {verb: 'post', path: '/diff'}
|
||||||
|
@ -746,10 +967,9 @@ module.exports = function(registry) {
|
||||||
});
|
});
|
||||||
|
|
||||||
setRemoting(PersistedModel, 'updateLastChange', {
|
setRemoting(PersistedModel, 'updateLastChange', {
|
||||||
description: [
|
description:
|
||||||
'Update the properties of the most recent change record',
|
'Update the properties of the most recent change record ' +
|
||||||
'kept for this instance.'
|
'kept for this instance.',
|
||||||
],
|
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
accepts: [
|
accepts: [
|
||||||
{
|
{
|
||||||
|
@ -757,8 +977,8 @@ module.exports = function(registry) {
|
||||||
description: 'Model id'
|
description: 'Model id'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
arg: 'data', type: 'object', http: {source: 'body'},
|
arg: 'data', type: 'object', model: typeName, http: {source: 'body'},
|
||||||
description: 'An object of Change property name/value pairs'
|
description: 'An object of Change property name/value pairs',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
returns: { arg: 'result', type: this.Change.modelName, root: true },
|
returns: { arg: 'result', type: this.Change.modelName, root: true },
|
||||||
|
@ -882,12 +1102,7 @@ module.exports = function(registry) {
|
||||||
|
|
||||||
PersistedModel.checkpoint = function(cb) {
|
PersistedModel.checkpoint = function(cb) {
|
||||||
var Checkpoint = this.getChangeModel().getCheckpointModel();
|
var Checkpoint = this.getChangeModel().getCheckpointModel();
|
||||||
this.getSourceId(function(err, sourceId) {
|
Checkpoint.bumpLastSeq(cb);
|
||||||
if (err) return cb(err);
|
|
||||||
Checkpoint.create({
|
|
||||||
sourceId: sourceId
|
|
||||||
}, cb);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -908,7 +1123,7 @@ module.exports = function(registry) {
|
||||||
*
|
*
|
||||||
* @param {Number} [since] Since this checkpoint
|
* @param {Number} [since] Since this checkpoint
|
||||||
* @param {Model} targetModel Target this model class
|
* @param {Model} targetModel Target this model class
|
||||||
* @param {Object} [options]
|
* @param {Object} [options] An optional options object to pass to underlying data-access calls.
|
||||||
* @param {Object} [options.filter] Replicate models that match this filter
|
* @param {Object} [options.filter] Replicate models that match this filter
|
||||||
* @callback {Function} [callback] Callback function called with `(err, conflicts)` arguments.
|
* @callback {Function} [callback] Callback function called with `(err, conflicts)` arguments.
|
||||||
* @param {Error} err Error object; see [Error object](http://docs.strongloop.com/display/LB/Error+object).
|
* @param {Error} err Error object; see [Error object](http://docs.strongloop.com/display/LB/Error+object).
|
||||||
|
@ -933,6 +1148,10 @@ module.exports = function(registry) {
|
||||||
since = { source: since, target: since };
|
since = { source: since, target: since };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof options === 'function') {
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
var sourceModel = this;
|
var sourceModel = this;
|
||||||
|
@ -1047,7 +1266,7 @@ module.exports = function(registry) {
|
||||||
function bulkUpdate(_updates, cb) {
|
function bulkUpdate(_updates, cb) {
|
||||||
debug('\tstarting bulk update');
|
debug('\tstarting bulk update');
|
||||||
updates = _updates;
|
updates = _updates;
|
||||||
targetModel.bulkUpdate(updates, function(err) {
|
targetModel.bulkUpdate(updates, options, function(err) {
|
||||||
var conflicts = err && err.details && err.details.conflicts;
|
var conflicts = err && err.details && err.details.conflicts;
|
||||||
if (conflicts && err.statusCode == 409) {
|
if (conflicts && err.statusCode == 409) {
|
||||||
diff.conflicts = conflicts;
|
diff.conflicts = conflicts;
|
||||||
|
@ -1131,7 +1350,7 @@ module.exports = function(registry) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
if (!inst) {
|
if (!inst) {
|
||||||
return cb &&
|
return cb &&
|
||||||
cb(new Error('Missing data for change: ' + change.modelId));
|
cb(new Error(g.f('Missing data for change: %s', change.modelId)));
|
||||||
}
|
}
|
||||||
if (inst.toObject) {
|
if (inst.toObject) {
|
||||||
update.data = inst.toObject();
|
update.data = inst.toObject();
|
||||||
|
@ -1161,15 +1380,28 @@ module.exports = function(registry) {
|
||||||
* **Note: this is not atomic**
|
* **Note: this is not atomic**
|
||||||
*
|
*
|
||||||
* @param {Array} updates An updates list, usually from [createUpdates()](#persistedmodel-createupdates).
|
* @param {Array} updates An updates list, usually from [createUpdates()](#persistedmodel-createupdates).
|
||||||
|
* @param {Object} [options] An optional options object to pass to underlying data-access calls.
|
||||||
* @param {Function} callback Callback function.
|
* @param {Function} callback Callback function.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
PersistedModel.bulkUpdate = function(updates, callback) {
|
PersistedModel.bulkUpdate = function(updates, options, callback) {
|
||||||
var tasks = [];
|
var tasks = [];
|
||||||
var Model = this;
|
var Model = this;
|
||||||
var Change = this.getChangeModel();
|
var Change = this.getChangeModel();
|
||||||
var conflicts = [];
|
var conflicts = [];
|
||||||
|
|
||||||
|
var lastArg = arguments[arguments.length - 1];
|
||||||
|
|
||||||
|
if (typeof lastArg === 'function' && arguments.length > 1) {
|
||||||
|
callback = lastArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof options === 'function') {
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
buildLookupOfAffectedModelData(Model, updates, function(err, currentMap) {
|
buildLookupOfAffectedModelData(Model, updates, function(err, currentMap) {
|
||||||
if (err) return callback(err);
|
if (err) return callback(err);
|
||||||
|
|
||||||
|
@ -1179,18 +1411,18 @@ module.exports = function(registry) {
|
||||||
switch (update.type) {
|
switch (update.type) {
|
||||||
case Change.UPDATE:
|
case Change.UPDATE:
|
||||||
tasks.push(function(cb) {
|
tasks.push(function(cb) {
|
||||||
applyUpdate(Model, id, current, update.data, update.change, conflicts, cb);
|
applyUpdate(Model, id, current, update.data, update.change, conflicts, options, cb);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Change.CREATE:
|
case Change.CREATE:
|
||||||
tasks.push(function(cb) {
|
tasks.push(function(cb) {
|
||||||
applyCreate(Model, id, current, update.data, update.change, conflicts, cb);
|
applyCreate(Model, id, current, update.data, update.change, conflicts, options, cb);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case Change.DELETE:
|
case Change.DELETE:
|
||||||
tasks.push(function(cb) {
|
tasks.push(function(cb) {
|
||||||
applyDelete(Model, id, current, update.change, conflicts, cb);
|
applyDelete(Model, id, current, update.change, conflicts, options, cb);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1199,7 +1431,7 @@ module.exports = function(registry) {
|
||||||
async.parallel(tasks, function(err) {
|
async.parallel(tasks, function(err) {
|
||||||
if (err) return callback(err);
|
if (err) return callback(err);
|
||||||
if (conflicts.length) {
|
if (conflicts.length) {
|
||||||
err = new Error('Conflict');
|
err = new Error(g.f('Conflict'));
|
||||||
err.statusCode = 409;
|
err.statusCode = 409;
|
||||||
err.details = { conflicts: conflicts };
|
err.details = { conflicts: conflicts };
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
@ -1224,7 +1456,7 @@ module.exports = function(registry) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyUpdate(Model, id, current, data, change, conflicts, cb) {
|
function applyUpdate(Model, id, current, data, change, conflicts, options, cb) {
|
||||||
var Change = Model.getChangeModel();
|
var Change = Model.getChangeModel();
|
||||||
var rev = current ? Change.revisionForInst(current) : null;
|
var rev = current ? Change.revisionForInst(current) : null;
|
||||||
|
|
||||||
|
@ -1242,7 +1474,7 @@ module.exports = function(registry) {
|
||||||
// but not included in `data`
|
// but not included in `data`
|
||||||
// See https://github.com/strongloop/loopback/issues/1215
|
// See https://github.com/strongloop/loopback/issues/1215
|
||||||
|
|
||||||
Model.updateAll(current.toObject(), data, function(err, result) {
|
Model.updateAll(current.toObject(), data, options, function(err, result) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
var count = result && result.count;
|
var count = result && result.count;
|
||||||
|
@ -1263,22 +1495,22 @@ module.exports = function(registry) {
|
||||||
case undefined:
|
case undefined:
|
||||||
case null:
|
case null:
|
||||||
return cb(new Error(
|
return cb(new Error(
|
||||||
'Cannot apply bulk updates, ' +
|
g.f('Cannot apply bulk updates, ' +
|
||||||
'the connector does not correctly report ' +
|
'the connector does not correctly report ' +
|
||||||
'the number of updated records.'));
|
'the number of updated records.')));
|
||||||
|
|
||||||
default:
|
default:
|
||||||
debug('%s.updateAll modified unexpected number of instances: %j',
|
debug('%s.updateAll modified unexpected number of instances: %j',
|
||||||
Model.modelName, count);
|
Model.modelName, count);
|
||||||
return cb(new Error(
|
return cb(new Error(
|
||||||
'Bulk update failed, the connector has modified unexpected ' +
|
g.f('Bulk update failed, the connector has modified unexpected ' +
|
||||||
'number of records: ' + JSON.stringify(count)));
|
'number of records: %s', JSON.stringify(count))));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyCreate(Model, id, current, data, change, conflicts, cb) {
|
function applyCreate(Model, id, current, data, change, conflicts, options, cb) {
|
||||||
Model.create(data, function(createErr) {
|
Model.create(data, options, function(createErr) {
|
||||||
if (!createErr) return cb();
|
if (!createErr) return cb();
|
||||||
|
|
||||||
// We don't have a reliable way how to detect the situation
|
// We don't have a reliable way how to detect the situation
|
||||||
|
@ -1306,7 +1538,7 @@ module.exports = function(registry) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyDelete(Model, id, current, change, conflicts, cb) {
|
function applyDelete(Model, id, current, change, conflicts, options, cb) {
|
||||||
if (!current) {
|
if (!current) {
|
||||||
// The instance was either already deleted or not created at all,
|
// The instance was either already deleted or not created at all,
|
||||||
// we are done.
|
// we are done.
|
||||||
|
@ -1324,7 +1556,7 @@ module.exports = function(registry) {
|
||||||
return Change.rectifyModelChanges(Model.modelName, [id], cb);
|
return Change.rectifyModelChanges(Model.modelName, [id], cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
Model.deleteAll(current.toObject(), function(err, result) {
|
Model.deleteAll(current.toObject(), options, function(err, result) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
var count = result && result.count;
|
var count = result && result.count;
|
||||||
|
@ -1345,16 +1577,16 @@ module.exports = function(registry) {
|
||||||
case undefined:
|
case undefined:
|
||||||
case null:
|
case null:
|
||||||
return cb(new Error(
|
return cb(new Error(
|
||||||
'Cannot apply bulk updates, ' +
|
g.f('Cannot apply bulk updates, ' +
|
||||||
'the connector does not correctly report ' +
|
'the connector does not correctly report ' +
|
||||||
'the number of deleted records.'));
|
'the number of deleted records.')));
|
||||||
|
|
||||||
default:
|
default:
|
||||||
debug('%s.deleteAll modified unexpected number of instances: %j',
|
debug('%s.deleteAll modified unexpected number of instances: %j',
|
||||||
Model.modelName, count);
|
Model.modelName, count);
|
||||||
return cb(new Error(
|
return cb(new Error(
|
||||||
'Bulk update failed, the connector has deleted unexpected ' +
|
g.f('Bulk update failed, the connector has deleted unexpected ' +
|
||||||
'number of records: ' + JSON.stringify(count)));
|
'number of records: %s', JSON.stringify(count))));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1413,14 +1645,16 @@ module.exports = function(registry) {
|
||||||
var idDefn = idProp && idProp.defaultFn;
|
var idDefn = idProp && idProp.defaultFn;
|
||||||
if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) {
|
if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) {
|
||||||
deprecated('The model ' + this.modelName + ' is tracking changes, ' +
|
deprecated('The model ' + this.modelName + ' is tracking changes, ' +
|
||||||
'which requries a string id with GUID/UUID default value.');
|
'which requires a string id with GUID/UUID default value.');
|
||||||
}
|
}
|
||||||
|
|
||||||
Model.observe('after save', rectifyOnSave);
|
Model.observe('after save', rectifyOnSave);
|
||||||
|
|
||||||
Model.observe('after delete', rectifyOnDelete);
|
Model.observe('after delete', rectifyOnDelete);
|
||||||
|
|
||||||
if (runtime.isServer) {
|
// Only run if the run time is server
|
||||||
|
// Can switch off cleanup by setting the interval to -1
|
||||||
|
if (runtime.isServer && cleanupInterval > 0) {
|
||||||
// initial cleanup
|
// initial cleanup
|
||||||
cleanup();
|
cleanup();
|
||||||
|
|
||||||
|
@ -1449,7 +1683,7 @@ module.exports = function(registry) {
|
||||||
ctx.instance, ctx.currentInstance, ctx.where, ctx.data);
|
ctx.instance, ctx.currentInstance, ctx.where, ctx.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id) {
|
if (id != null) {
|
||||||
ctx.Model.rectifyChange(id, reportErrorAndNext);
|
ctx.Model.rectifyChange(id, reportErrorAndNext);
|
||||||
} else {
|
} else {
|
||||||
ctx.Model.rectifyAllChanges(reportErrorAndNext);
|
ctx.Model.rectifyAllChanges(reportErrorAndNext);
|
||||||
|
@ -1473,7 +1707,7 @@ module.exports = function(registry) {
|
||||||
debug('context instance:%j where:%j', ctx.instance, ctx.where);
|
debug('context instance:%j where:%j', ctx.instance, ctx.where);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id) {
|
if (id != null) {
|
||||||
ctx.Model.rectifyChange(id, reportErrorAndNext);
|
ctx.Model.rectifyChange(id, reportErrorAndNext);
|
||||||
} else {
|
} else {
|
||||||
ctx.Model.rectifyAllChanges(reportErrorAndNext);
|
ctx.Model.rectifyAllChanges(reportErrorAndNext);
|
||||||
|
@ -1569,8 +1803,8 @@ module.exports = function(registry) {
|
||||||
this.findLastChange(id, function(err, inst) {
|
this.findLastChange(id, function(err, inst) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
if (!inst) {
|
if (!inst) {
|
||||||
err = new Error('No change record found for ' +
|
err = new Error(g.f('No change record found for %s with id %s',
|
||||||
self.modelName + ' with id ' + id);
|
self.modelName, id));
|
||||||
err.statusCode = 404;
|
err.statusCode = 404;
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
var g = require('strong-globalize')();
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var extend = require('util')._extend;
|
var extend = require('util')._extend;
|
||||||
var juggler = require('loopback-datasource-juggler');
|
var juggler = require('loopback-datasource-juggler');
|
||||||
|
@ -108,11 +114,11 @@ Registry.prototype.createModel = function(name, properties, options) {
|
||||||
|
|
||||||
if (BaseModel === undefined) {
|
if (BaseModel === undefined) {
|
||||||
if (baseName === 'DataModel') {
|
if (baseName === 'DataModel') {
|
||||||
console.warn('Model `%s` is extending deprecated `DataModel. ' +
|
g.warn('Model `%s` is extending deprecated `DataModel. ' +
|
||||||
'Use `PersistedModel` instead.', name);
|
'Use `PersistedModel` instead.', name);
|
||||||
BaseModel = this.getModel('PersistedModel');
|
BaseModel = this.getModel('PersistedModel');
|
||||||
} else {
|
} else {
|
||||||
console.warn('Model `%s` is extending an unknown model `%s`. ' +
|
g.warn('Model `%s` is extending an unknown model `%s`. ' +
|
||||||
'Using `PersistedModel` as the base.', name, baseName);
|
'Using `PersistedModel` as the base.', name, baseName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,7 +198,7 @@ Registry.prototype.configureModel = function(ModelCtor, config) {
|
||||||
relations[key] = extend(relations[key] || {}, config.relations[key]);
|
relations[key] = extend(relations[key] || {}, config.relations[key]);
|
||||||
});
|
});
|
||||||
} else if (config.relations != null) {
|
} else if (config.relations != null) {
|
||||||
console.warn('The relations property of `%s` configuration ' +
|
g.warn('The relations property of `%s` configuration ' +
|
||||||
'must be an object', modelName);
|
'must be an object', modelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +209,7 @@ Registry.prototype.configureModel = function(ModelCtor, config) {
|
||||||
addACL(acls, acl);
|
addACL(acls, acl);
|
||||||
});
|
});
|
||||||
} else if (config.acls != null) {
|
} else if (config.acls != null) {
|
||||||
console.warn('The acls property of `%s` configuration ' +
|
g.warn('The acls property of `%s` configuration ' +
|
||||||
'must be an array of objects', modelName);
|
'must be an array of objects', modelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,12 +226,12 @@ Registry.prototype.configureModel = function(ModelCtor, config) {
|
||||||
if (!(p in excludedProperties)) {
|
if (!(p in excludedProperties)) {
|
||||||
settings[p] = config.options[p];
|
settings[p] = config.options[p];
|
||||||
} else {
|
} else {
|
||||||
console.warn('Property `%s` cannot be reconfigured for `%s`',
|
g.warn('Property `%s` cannot be reconfigured for `%s`',
|
||||||
p, modelName);
|
p, modelName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (config.options != null) {
|
} else if (config.options != null) {
|
||||||
console.warn('The options property of `%s` configuration ' +
|
g.warn('The options property of `%s` configuration ' +
|
||||||
'must be an object', modelName);
|
'must be an object', modelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,8 +250,8 @@ Registry.prototype.configureModel = function(ModelCtor, config) {
|
||||||
} else {
|
} else {
|
||||||
debug('Model `%s` is not attached to any DataSource, possibly by a mistake.',
|
debug('Model `%s` is not attached to any DataSource, possibly by a mistake.',
|
||||||
modelName);
|
modelName);
|
||||||
console.warn(
|
g.warn(
|
||||||
'The configuration of `%s` is missing `dataSource` property.\n' +
|
'The configuration of `%s` is missing {{`dataSource`}} property.\n' +
|
||||||
'Use `null` or `false` to mark models not attached to any data source.',
|
'Use `null` or `false` to mark models not attached to any data source.',
|
||||||
modelName);
|
modelName);
|
||||||
}
|
}
|
||||||
|
@ -257,7 +263,7 @@ Registry.prototype.configureModel = function(ModelCtor, config) {
|
||||||
Registry.prototype._defineRemoteMethods = function(ModelCtor, methods) {
|
Registry.prototype._defineRemoteMethods = function(ModelCtor, methods) {
|
||||||
if (!methods) return;
|
if (!methods) return;
|
||||||
if (typeof methods !== 'object') {
|
if (typeof methods !== 'object') {
|
||||||
console.warn('Ignoring non-object "methods" setting of "%s".',
|
g.warn('Ignoring non-object "methods" setting of "%s".',
|
||||||
ModelCtor.modelName);
|
ModelCtor.modelName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -265,11 +271,11 @@ Registry.prototype._defineRemoteMethods = function(ModelCtor, methods) {
|
||||||
Object.keys(methods).forEach(function(key) {
|
Object.keys(methods).forEach(function(key) {
|
||||||
var meta = methods[key];
|
var meta = methods[key];
|
||||||
if (typeof meta.isStatic !== 'boolean') {
|
if (typeof meta.isStatic !== 'boolean') {
|
||||||
console.warn('Remoting metadata for "%s.%s" is missing "isStatic" ' +
|
g.warn('Remoting metadata for "%s.%s" is missing "isStatic" ' +
|
||||||
'flag, the method is registered as an instance method.',
|
'flag, the method is registered as an instance method.',
|
||||||
ModelCtor.modelName,
|
ModelCtor.modelName,
|
||||||
key);
|
key);
|
||||||
console.warn('This behaviour may change in the next major version.');
|
g.warn('This behaviour may change in the next major version.');
|
||||||
}
|
}
|
||||||
ModelCtor.remoteMethod(key, meta);
|
ModelCtor.remoteMethod(key, meta);
|
||||||
});
|
});
|
||||||
|
@ -301,7 +307,7 @@ Registry.prototype.getModel = function(modelName) {
|
||||||
var model = this.findModel(modelName);
|
var model = this.findModel(modelName);
|
||||||
if (model) return model;
|
if (model) return model;
|
||||||
|
|
||||||
throw new Error('Model not found: ' + modelName);
|
throw new Error(g.f('Model not found: %s', modelName));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This is an internal file that should not be used outside of loopback.
|
* This is an internal file that should not be used outside of loopback.
|
||||||
* All exported entities can be accessed via the `loopback` object.
|
* All exported entities can be accessed via the `loopback` object.
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
var g = require('strong-globalize')();
|
||||||
|
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var merge = require('util')._extend;
|
var merge = require('util')._extend;
|
||||||
|
@ -183,7 +190,7 @@ proto.middleware = function(name, paths, handler) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._requestHandlingPhases.indexOf(name) === -1)
|
if (this._requestHandlingPhases.indexOf(name) === -1)
|
||||||
throw new Error('Unknown middleware phase ' + name);
|
throw new Error(g.f('Unknown {{middleware}} phase %s', name));
|
||||||
|
|
||||||
debug('use %s %s %s', fullPhaseName, paths, handlerName);
|
debug('use %s %s %s', fullPhaseName, paths, handlerName);
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
exports.createPromiseCallback = createPromiseCallback;
|
exports.createPromiseCallback = createPromiseCallback;
|
||||||
|
|
||||||
function createPromiseCallback() {
|
function createPromiseCallback() {
|
||||||
|
|
98
package.json
98
package.json
|
@ -1,6 +1,9 @@
|
||||||
{
|
{
|
||||||
"name": "loopback",
|
"name": "loopback",
|
||||||
"version": "2.26.2",
|
"version": "2.41.1",
|
||||||
|
"publishConfig": {
|
||||||
|
"tag": "lts"
|
||||||
|
},
|
||||||
"description": "LoopBack: Open Source Framework for Node.js",
|
"description": "LoopBack: Open Source Framework for Node.js",
|
||||||
"homepage": "http://loopback.io",
|
"homepage": "http://loopback.io",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
@ -29,64 +32,78 @@
|
||||||
"mBaaS"
|
"mBaaS"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "grunt mocha-and-karma"
|
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||||
|
"test": "nyc grunt mocha-and-karma"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "^0.9.0",
|
"async": "^2.0.1",
|
||||||
"bcryptjs": "^2.1.0",
|
"bcryptjs": "^2.1.0",
|
||||||
"body-parser": "^1.12.0",
|
"body-parser": "^1.12.0",
|
||||||
"canonical-json": "0.0.4",
|
"canonical-json": "0.0.4",
|
||||||
"continuation-local-storage": "^3.1.3",
|
|
||||||
"cookie-parser": "^1.3.4",
|
"cookie-parser": "^1.3.4",
|
||||||
"debug": "^2.1.2",
|
"debug": "^2.1.2",
|
||||||
"depd": "^1.0.0",
|
"depd": "^1.0.0",
|
||||||
"ejs": "^2.3.1",
|
"ejs": "^2.3.1",
|
||||||
"errorhandler": "^1.3.4",
|
"errorhandler": "^1.3.4",
|
||||||
"express": "^4.12.2",
|
"express": "^4.16.2",
|
||||||
"inflection": "^1.6.0",
|
"inflection": "^1.6.0",
|
||||||
|
"isemail": "^1.2.0",
|
||||||
"loopback-connector-remote": "^1.0.3",
|
"loopback-connector-remote": "^1.0.3",
|
||||||
|
"loopback-context": "^1.0.0",
|
||||||
"loopback-phase": "^1.2.0",
|
"loopback-phase": "^1.2.0",
|
||||||
"nodemailer": "^1.3.1",
|
"nodemailer": "^2.5.0",
|
||||||
"nodemailer-stub-transport": "^0.1.5",
|
"nodemailer-stub-transport": "^1.0.0",
|
||||||
"serve-favicon": "^2.2.0",
|
"serve-favicon": "^2.2.0",
|
||||||
"stable": "^0.1.5",
|
"stable": "^0.1.5",
|
||||||
|
"strong-globalize": "^2.6.2",
|
||||||
"strong-remoting": "^2.21.0",
|
"strong-remoting": "^2.21.0",
|
||||||
"uid2": "0.0.3",
|
"uid2": "0.0.3",
|
||||||
"underscore.string": "^3.0.3"
|
"underscore.string": "^3.0.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"loopback-datasource-juggler": "^2.19.0"
|
"loopback-datasource-juggler": "^2.56.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bluebird": "^2.9.9",
|
"babel-preset-es2015": "^6.24.1",
|
||||||
"browserify": "^10.0.0",
|
"babelify": "^7.3.0",
|
||||||
"chai": "^2.1.1",
|
"bluebird": "^3.4.1",
|
||||||
|
"browserify": "^13.1.0",
|
||||||
|
"chai": "^3.5.0",
|
||||||
|
"coveralls": "^2.11.15",
|
||||||
"es5-shim": "^4.1.0",
|
"es5-shim": "^4.1.0",
|
||||||
"grunt": "^0.4.5",
|
"express-session": "^1.14.0",
|
||||||
"grunt-browserify": "^3.5.0",
|
"grunt": "^1.0.1",
|
||||||
"grunt-cli": "^0.1.13",
|
"grunt-browserify": "^5.0.0",
|
||||||
"grunt-contrib-jshint": "^0.11.0",
|
"grunt-cli": "^1.2.0",
|
||||||
"grunt-contrib-uglify": "^0.9.1",
|
"grunt-contrib-jshint": "^1.0.0",
|
||||||
"grunt-contrib-watch": "^0.6.1",
|
"grunt-contrib-uglify": "^2.0.0",
|
||||||
"grunt-jscs": "^1.5.0",
|
"grunt-contrib-watch": "^1.0.0",
|
||||||
"grunt-karma": "^0.10.1",
|
"grunt-jscs": "^3.0.1",
|
||||||
|
"grunt-karma": "^2.0.0",
|
||||||
"grunt-mocha-test": "^0.12.7",
|
"grunt-mocha-test": "^0.12.7",
|
||||||
"karma": "^0.12.31",
|
"karma": "^1.1.2",
|
||||||
"karma-browserify": "^4.0.0",
|
"karma-browserify": "^5.0.5",
|
||||||
"karma-chrome-launcher": "^0.1.7",
|
"karma-chrome-launcher": "^1.0.1",
|
||||||
"karma-firefox-launcher": "^0.1.4",
|
"karma-firefox-launcher": "^1.0.0",
|
||||||
"karma-html2js-preprocessor": "^0.1.0",
|
"karma-html2js-preprocessor": "^1.0.0",
|
||||||
"karma-junit-reporter": "^0.2.2",
|
"karma-junit-reporter": "^1.0.0",
|
||||||
"karma-mocha": "^0.1.10",
|
"karma-mocha": "^1.1.1",
|
||||||
"karma-phantomjs-launcher": "^0.1.4",
|
"karma-phantomjs-launcher": "^1.0.0",
|
||||||
"karma-script-launcher": "^0.1.0",
|
"karma-script-launcher": "^1.0.0",
|
||||||
"loopback-boot": "^2.7.0",
|
"loopback-boot": "^2.7.0",
|
||||||
"loopback-datasource-juggler": "^2.19.1",
|
"loopback-datasource-juggler": "^2.56.0",
|
||||||
"loopback-testing": "~1.1.0",
|
"loopback-testing": "^1.4.0",
|
||||||
"mocha": "^2.1.0",
|
"mocha": "^3.0.0",
|
||||||
|
"nyc": "^10.1.2",
|
||||||
|
"phantomjs-prebuilt": "^2.1.7",
|
||||||
"sinon": "^1.13.0",
|
"sinon": "^1.13.0",
|
||||||
|
"sinon-chai": "^2.8.0",
|
||||||
"strong-task-emitter": "^0.0.6",
|
"strong-task-emitter": "^0.0.6",
|
||||||
"supertest": "^0.15.0"
|
"supertest": "^2.0.0",
|
||||||
|
"supertest-as-promised": "^4.0.2"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -95,15 +112,22 @@
|
||||||
"browser": {
|
"browser": {
|
||||||
"express": "./lib/browser-express.js",
|
"express": "./lib/browser-express.js",
|
||||||
"./lib/server-app.js": "./lib/browser-express.js",
|
"./lib/server-app.js": "./lib/browser-express.js",
|
||||||
"./server/current-context.js": "./browser/current-context.js",
|
|
||||||
"connect": false,
|
"connect": false,
|
||||||
"nodemailer": false,
|
"nodemailer": false,
|
||||||
"supertest": false,
|
"supertest": false,
|
||||||
"depd": "loopback-datasource-juggler/lib/browser.depd.js",
|
"depd": "loopback-datasource-juggler/lib/browser.depd.js",
|
||||||
"bcrypt": false
|
"bcrypt": false
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"config": {
|
||||||
"optionalDependencies": {
|
"ci": {
|
||||||
"sl-blip": "http://blip.strongloop.com/loopback@2.26.2"
|
"debug": "*,-mocha:*,-eslint:*"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"ci": {
|
||||||
|
"downstreamIgnoreList": [
|
||||||
|
"dashboard-controller",
|
||||||
|
"gateway-director-management-interface"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,138 +0,0 @@
|
||||||
var juggler = require('loopback-datasource-juggler');
|
|
||||||
var remoting = require('strong-remoting');
|
|
||||||
var cls = require('continuation-local-storage');
|
|
||||||
var domain = require('domain');
|
|
||||||
|
|
||||||
module.exports = function(loopback) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current context object. The context is preserved
|
|
||||||
* across async calls, it behaves like a thread-local storage.
|
|
||||||
*
|
|
||||||
* @returns {ChainedContext} The context object or null.
|
|
||||||
*/
|
|
||||||
loopback.getCurrentContext = function() {
|
|
||||||
// A placeholder method, see loopback.createContext() for the real version
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run the given function in such way that
|
|
||||||
* `loopback.getCurrentContext` returns the
|
|
||||||
* provided context object.
|
|
||||||
*
|
|
||||||
* **NOTE**
|
|
||||||
*
|
|
||||||
* The method is supported on the server only, it does not work
|
|
||||||
* in the browser at the moment.
|
|
||||||
*
|
|
||||||
* @param {Function} fn The function to run, it will receive arguments
|
|
||||||
* (currentContext, currentDomain).
|
|
||||||
* @param {ChainedContext} context An optional context object.
|
|
||||||
* When no value is provided, then the default global context is used.
|
|
||||||
*/
|
|
||||||
loopback.runInContext = function(fn, context) {
|
|
||||||
var currentDomain = domain.create();
|
|
||||||
currentDomain.oldBind = currentDomain.bind;
|
|
||||||
currentDomain.bind = function(callback, context) {
|
|
||||||
return currentDomain.oldBind(ns.bind(callback, context), context);
|
|
||||||
};
|
|
||||||
|
|
||||||
var ns = context || loopback.createContext('loopback');
|
|
||||||
|
|
||||||
currentDomain.run(function() {
|
|
||||||
ns.run(function executeInContext(context) {
|
|
||||||
fn(ns, currentDomain);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new LoopBackContext instance that can be used
|
|
||||||
* for `loopback.runInContext`.
|
|
||||||
*
|
|
||||||
* **NOTES**
|
|
||||||
*
|
|
||||||
* At the moment, `loopback.getCurrentContext` supports
|
|
||||||
* a single global context instance only. If you call `createContext()`
|
|
||||||
* multiple times, `getCurrentContext` will return the last context
|
|
||||||
* created.
|
|
||||||
*
|
|
||||||
* The method is supported on the server only, it does not work
|
|
||||||
* in the browser at the moment.
|
|
||||||
*
|
|
||||||
* @param {String} scopeName An optional scope name.
|
|
||||||
* @return {ChainedContext} The new context object.
|
|
||||||
*/
|
|
||||||
loopback.createContext = function(scopeName) {
|
|
||||||
// Make the namespace globally visible via the process.context property
|
|
||||||
process.context = process.context || {};
|
|
||||||
var ns = process.context[scopeName];
|
|
||||||
if (!ns) {
|
|
||||||
ns = cls.createNamespace(scopeName);
|
|
||||||
process.context[scopeName] = ns;
|
|
||||||
// Set up loopback.getCurrentContext()
|
|
||||||
loopback.getCurrentContext = function() {
|
|
||||||
return ns && ns.active ? ns : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
chain(juggler);
|
|
||||||
chain(remoting);
|
|
||||||
}
|
|
||||||
return ns;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a chained context
|
|
||||||
* @param {Object} child The child context
|
|
||||||
* @param {Object} parent The parent context
|
|
||||||
* @private
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ChainedContext(child, parent) {
|
|
||||||
this.child = child;
|
|
||||||
this.parent = parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the value by name from the context. If it doesn't exist in the child
|
|
||||||
* context, try the parent one
|
|
||||||
* @param {String} name Name of the context property
|
|
||||||
* @returns {*} Value of the context property
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ChainedContext.prototype.get = function(name) {
|
|
||||||
var val = this.child && this.child.get(name);
|
|
||||||
if (val === undefined) {
|
|
||||||
return this.parent && this.parent.get(name);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ChainedContext.prototype.set = function(name, val) {
|
|
||||||
if (this.child) {
|
|
||||||
return this.child.set(name, val);
|
|
||||||
} else {
|
|
||||||
return this.parent && this.parent.set(name, val);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ChainedContext.prototype.reset = function(name, val) {
|
|
||||||
if (this.child) {
|
|
||||||
return this.child.reset(name, val);
|
|
||||||
} else {
|
|
||||||
return this.parent && this.parent.reset(name, val);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function chain(child) {
|
|
||||||
if (typeof child.getCurrentContext === 'function') {
|
|
||||||
var childContext = new ChainedContext(child.getCurrentContext(),
|
|
||||||
loopback.getCurrentContext());
|
|
||||||
child.getCurrentContext = function() {
|
|
||||||
return childContext;
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
child.getCurrentContext = loopback.getCurrentContext;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,52 +1,15 @@
|
||||||
var loopback = require('../../lib/loopback');
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
module.exports = context;
|
var deprecated = require('depd')('loopback');
|
||||||
|
var g = require('strong-globalize')();
|
||||||
|
var perRequestContext = require('loopback-context').perRequest;
|
||||||
|
|
||||||
var name = 'loopback';
|
module.exports = function() {
|
||||||
|
deprecated(g.f('%s middleware is deprecated. See %s for more details.',
|
||||||
/**
|
'loopback#context',
|
||||||
* Context middleware.
|
'https://docs.strongloop.com/display/APIC/Using%20current%20context'));
|
||||||
* ```js
|
return perRequestContext.apply(this, arguments);
|
||||||
* var app = loopback();
|
};
|
||||||
* app.use(loopback.context(options);
|
|
||||||
* app.use(loopback.rest());
|
|
||||||
* app.listen();
|
|
||||||
* ```
|
|
||||||
* @options {Object} [options] Options for context
|
|
||||||
* @property {String} name Context scope name.
|
|
||||||
* @property {Boolean} enableHttpContext Whether HTTP context is enabled. Default is false.
|
|
||||||
* @header loopback.context([options])
|
|
||||||
*/
|
|
||||||
|
|
||||||
function context(options) {
|
|
||||||
options = options || {};
|
|
||||||
var scope = options.name || name;
|
|
||||||
var enableHttpContext = options.enableHttpContext || false;
|
|
||||||
var ns = loopback.createContext(scope);
|
|
||||||
|
|
||||||
// Return the middleware
|
|
||||||
return function contextHandler(req, res, next) {
|
|
||||||
if (req.loopbackContext) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
loopback.runInContext(function processRequestInContext(ns, domain) {
|
|
||||||
req.loopbackContext = ns;
|
|
||||||
|
|
||||||
// Bind req/res event emitters to the given namespace
|
|
||||||
ns.bindEmitter(req);
|
|
||||||
ns.bindEmitter(res);
|
|
||||||
|
|
||||||
// Add req/res event emitters to the current domain
|
|
||||||
domain.add(req);
|
|
||||||
domain.add(res);
|
|
||||||
|
|
||||||
// Run the code in the context of the namespace
|
|
||||||
if (enableHttpContext) {
|
|
||||||
// Set up the transport context
|
|
||||||
ns.set('http', {req: req, res: res});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var expressErrorHandler = require('errorhandler');
|
var expressErrorHandler = require('errorhandler');
|
||||||
expressErrorHandler.title = 'Loopback';
|
expressErrorHandler.title = 'Loopback';
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,16 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
var favicon = require('serve-favicon');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serve the LoopBack favicon.
|
* Serve the LoopBack favicon.
|
||||||
* @header loopback.favicon()
|
* @header loopback.favicon()
|
||||||
*/
|
*/
|
||||||
module.exports = require('../../lib/express-middleware').favicon;
|
module.exports = function(icon, options) {
|
||||||
|
icon = icon || path.join(__dirname, '../../favicon.ico');
|
||||||
|
return favicon(icon, options);
|
||||||
|
};
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Module dependencies.
|
* Module dependencies.
|
||||||
*/
|
*/
|
||||||
|
@ -68,8 +73,21 @@ function rest() {
|
||||||
if (handlers.length === 1) {
|
if (handlers.length === 1) {
|
||||||
return handlers[0](req, res, next);
|
return handlers[0](req, res, next);
|
||||||
}
|
}
|
||||||
async.eachSeries(handlers, function(handler, done) {
|
|
||||||
handler(req, res, done);
|
executeHandlers(handlers, req, res, next);
|
||||||
}, next);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A trimmed-down version of async.series that preserves current CLS context
|
||||||
|
function executeHandlers(handlers, req, res, cb) {
|
||||||
|
var ix = -1;
|
||||||
|
next();
|
||||||
|
|
||||||
|
function next(err) {
|
||||||
|
if (err || ++ix >= handlers.length) {
|
||||||
|
cb(err);
|
||||||
|
} else {
|
||||||
|
handlers[ix](req, res, next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serve static assets of a LoopBack application.
|
* Serve static assets of a LoopBack application.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Export the middleware.
|
* Export the middleware.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Module dependencies.
|
* Module dependencies.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
var g = require('strong-globalize')();
|
||||||
var loopback = require('../../lib/loopback');
|
var loopback = require('../../lib/loopback');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var debug = require('debug')('loopback:middleware:token');
|
var debug = require('debug')('loopback:middleware:token');
|
||||||
|
@ -15,18 +22,33 @@ module.exports = token;
|
||||||
/*
|
/*
|
||||||
* Rewrite the url to replace current user literal with the logged in user id
|
* Rewrite the url to replace current user literal with the logged in user id
|
||||||
*/
|
*/
|
||||||
function rewriteUserLiteral(req, currentUserLiteral) {
|
function rewriteUserLiteral(req, currentUserLiteral, next) {
|
||||||
if (req.accessToken && req.accessToken.userId && currentUserLiteral) {
|
if (!currentUserLiteral) return next();
|
||||||
|
var literalRegExp = new RegExp('/' + currentUserLiteral + '(/|$|\\?)', 'g');
|
||||||
|
|
||||||
|
if (req.accessToken && req.accessToken.userId) {
|
||||||
// Replace /me/ with /current-user-id/
|
// Replace /me/ with /current-user-id/
|
||||||
var urlBeforeRewrite = req.url;
|
var urlBeforeRewrite = req.url;
|
||||||
req.url = req.url.replace(
|
req.url = req.url.replace(literalRegExp,
|
||||||
new RegExp('/' + currentUserLiteral + '(/|$|\\?)', 'g'),
|
|
||||||
'/' + req.accessToken.userId + '$1');
|
'/' + req.accessToken.userId + '$1');
|
||||||
|
|
||||||
if (req.url !== urlBeforeRewrite) {
|
if (req.url !== urlBeforeRewrite) {
|
||||||
debug('req.url has been rewritten from %s to %s', urlBeforeRewrite,
|
debug('req.url has been rewritten from %s to %s', urlBeforeRewrite,
|
||||||
req.url);
|
req.url);
|
||||||
}
|
}
|
||||||
|
} else if (!req.accessToken && literalRegExp.test(req.url)) {
|
||||||
|
debug(
|
||||||
|
'URL %s matches current-user literal %s,' +
|
||||||
|
' but no (valid) access token was provided.',
|
||||||
|
req.url, currentUserLiteral);
|
||||||
|
|
||||||
|
var e = new Error(g.f('Authorization Required'));
|
||||||
|
e.status = e.statusCode = 401;
|
||||||
|
e.code = 'AUTHORIZATION_REQUIRED';
|
||||||
|
return next(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeRegExp(str) {
|
function escapeRegExp(str) {
|
||||||
|
@ -62,6 +84,8 @@ function escapeRegExp(str) {
|
||||||
* @property {Array} [headers] Array of header names.
|
* @property {Array} [headers] Array of header names.
|
||||||
* @property {Array} [params] Array of param names.
|
* @property {Array} [params] Array of param names.
|
||||||
* @property {Boolean} [searchDefaultTokenKeys] Use the default search locations for Token in request
|
* @property {Boolean} [searchDefaultTokenKeys] Use the default search locations for Token in request
|
||||||
|
* @property {Boolean} [enableDoublecheck] Execute middleware although an instance mounted earlier in the chain didn't find a token
|
||||||
|
* @property {Boolean} [overwriteExistingToken] only has effect in combination with `enableDoublecheck`. If truthy, will allow to overwrite an existing accessToken.
|
||||||
* @property {Function|String} [model] AccessToken model name or class to use.
|
* @property {Function|String} [model] AccessToken model name or class to use.
|
||||||
* @property {String} [currentUserLiteral] String literal for the current user.
|
* @property {String} [currentUserLiteral] String literal for the current user.
|
||||||
* @header loopback.token([options])
|
* @header loopback.token([options])
|
||||||
|
@ -80,6 +104,9 @@ function token(options) {
|
||||||
currentUserLiteral = escapeRegExp(currentUserLiteral);
|
currentUserLiteral = escapeRegExp(currentUserLiteral);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var enableDoublecheck = !!options.enableDoublecheck;
|
||||||
|
var overwriteExistingToken = !!options.overwriteExistingToken;
|
||||||
|
|
||||||
return function(req, res, next) {
|
return function(req, res, next) {
|
||||||
var app = req.app;
|
var app = req.app;
|
||||||
var registry = app.registry;
|
var registry = app.registry;
|
||||||
|
@ -97,15 +124,27 @@ function token(options) {
|
||||||
'loopback.token() middleware requires a AccessToken model');
|
'loopback.token() middleware requires a AccessToken model');
|
||||||
|
|
||||||
if (req.accessToken !== undefined) {
|
if (req.accessToken !== undefined) {
|
||||||
rewriteUserLiteral(req, currentUserLiteral);
|
if (!enableDoublecheck) {
|
||||||
return next();
|
// req.accessToken is defined already (might also be "null" or "false") and enableDoublecheck
|
||||||
|
// has not been set --> skip searching for credentials
|
||||||
|
return rewriteUserLiteral(req, currentUserLiteral, next);
|
||||||
|
}
|
||||||
|
if (req.accessToken && req.accessToken.id && !overwriteExistingToken) {
|
||||||
|
// req.accessToken.id is defined, which means that some other middleware has identified a valid user.
|
||||||
|
// when overwriteExistingToken is not set to a truthy value, skip searching for credentials.
|
||||||
|
return rewriteUserLiteral(req, currentUserLiteral, next);
|
||||||
|
}
|
||||||
|
// continue normal operation (as if req.accessToken was undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
TokenModel.findForRequest(req, options, function(err, token) {
|
TokenModel.findForRequest(req, options, function(err, token) {
|
||||||
req.accessToken = token || null;
|
req.accessToken = token || null;
|
||||||
rewriteUserLiteral(req, currentUserLiteral);
|
|
||||||
var ctx = loopback.getCurrentContext();
|
var ctx = req.loopbackContext;
|
||||||
if (ctx) ctx.set('accessToken', token);
|
if (ctx && ctx.active) ctx.set('accessToken', token);
|
||||||
next(err);
|
|
||||||
|
if (err) return next(err);
|
||||||
|
rewriteUserLiteral(req, currentUserLiteral, next);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Export the middleware.
|
* Export the middleware.
|
||||||
* See discussion in Connect pull request #954 for more details
|
* See discussion in Connect pull request #954 for more details
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/*jshint -W030 */
|
/*jshint -W030 */
|
||||||
|
|
||||||
var loopback = require('../');
|
var loopback = require('../');
|
||||||
|
@ -11,8 +16,15 @@ var CURRENT_USER = {email: 'current@test.test', password: 'test'};
|
||||||
var debug = require('debug')('loopback:test:access-control.integration');
|
var debug = require('debug')('loopback:test:access-control.integration');
|
||||||
|
|
||||||
describe('access control - integration', function() {
|
describe('access control - integration', function() {
|
||||||
|
before(function(done) {
|
||||||
|
if (app.booting) {
|
||||||
|
return app.once('booted', done);
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
lt.beforeEach.withApp(app);
|
lt.beforeEach.withApp(app);
|
||||||
|
lt.beforeEach.withUserModel('user');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
describe('accessToken', function() {
|
describe('accessToken', function() {
|
||||||
|
@ -94,7 +106,7 @@ describe('access control - integration', function() {
|
||||||
|
|
||||||
lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {
|
lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
this.url = '/api/users/' + this.user.id + '?ok';
|
this.url = '/api/users/' + this.loggedInAccessToken.userId + '?ok';
|
||||||
});
|
});
|
||||||
lt.describe.whenCalledRemotely('DELETE', '/api/users/:id', function() {
|
lt.describe.whenCalledRemotely('DELETE', '/api/users/:id', function() {
|
||||||
lt.it.shouldBeAllowed();
|
lt.it.shouldBeAllowed();
|
||||||
|
@ -110,11 +122,21 @@ describe('access control - integration', function() {
|
||||||
assert.equal(user.password, undefined);
|
assert.equal(user.password, undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// user has replaceOnPUT = false; so then both PUT and PATCH should be allowed for update
|
||||||
lt.describe.whenCalledRemotely('PUT', '/api/users/:id', function() {
|
lt.describe.whenCalledRemotely('PUT', '/api/users/:id', function() {
|
||||||
lt.it.shouldBeAllowed();
|
lt.it.shouldBeAllowed();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
lt.describe.whenCalledRemotely('PATCH', '/api/users/:id', function() {
|
||||||
|
lt.it.shouldBeAllowed();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/users/upsertWithWhere');
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/users/upsertWithWhere');
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/users/upsertWithWhere');
|
||||||
|
|
||||||
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForUser);
|
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForUser);
|
||||||
lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForUser);
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForUser);
|
||||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForUser);
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForUser);
|
||||||
|
@ -126,6 +148,7 @@ describe('access control - integration', function() {
|
||||||
var userCounter;
|
var userCounter;
|
||||||
function newUserData() {
|
function newUserData() {
|
||||||
userCounter = userCounter ? ++userCounter : 1;
|
userCounter = userCounter ? ++userCounter : 1;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
email: 'new-' + userCounter + '@test.test',
|
email: 'new-' + userCounter + '@test.test',
|
||||||
password: 'test'
|
password: 'test'
|
||||||
|
@ -134,6 +157,34 @@ describe('access control - integration', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('/banks', function() {
|
describe('/banks', function() {
|
||||||
|
var SPECIAL_USER = { email: 'special@test.test', password: 'test' };
|
||||||
|
|
||||||
|
// define dynamic role that would only grant access when the authenticated user's email is equal to
|
||||||
|
// SPECIAL_USER's email
|
||||||
|
|
||||||
|
before(function() {
|
||||||
|
var roleModel = app.registry.getModel('Role');
|
||||||
|
var userModel = app.registry.getModel('user');
|
||||||
|
|
||||||
|
roleModel.registerResolver('$dynamic-role', function(role, context, callback) {
|
||||||
|
if (!(context && context.accessToken && context.accessToken.userId)) {
|
||||||
|
return process.nextTick(function() {
|
||||||
|
callback && callback(null, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
var accessToken = context.accessToken;
|
||||||
|
userModel.findById(accessToken.userId, function(err, user) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err, false);
|
||||||
|
}
|
||||||
|
if (user && user.email === SPECIAL_USER.email) {
|
||||||
|
return callback(null, true);
|
||||||
|
}
|
||||||
|
return callback(null, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
lt.beforeEach.givenModel('bank');
|
lt.beforeEach.givenModel('bank');
|
||||||
|
|
||||||
lt.it.shouldBeAllowedWhenCalledAnonymously('GET', '/api/banks');
|
lt.it.shouldBeAllowedWhenCalledAnonymously('GET', '/api/banks');
|
||||||
|
@ -155,13 +206,18 @@ describe('access control - integration', function() {
|
||||||
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForBank);
|
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForBank);
|
||||||
lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForBank);
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForBank);
|
||||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForBank);
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForBank);
|
||||||
|
lt.it.shouldBeAllowedWhenCalledByUser(SPECIAL_USER, 'DELETE', urlForBank);
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/banks/upsertWithWhere');
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/banks/upsertWithWhere');
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/banks/upsertWithWhere');
|
||||||
|
|
||||||
function urlForBank() {
|
function urlForBank() {
|
||||||
return '/api/banks/' + this.bank.id;
|
return '/api/banks/' + this.bank.id;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('/accounts', function() {
|
describe('/accounts with replaceOnPUT true', function() {
|
||||||
var count = 0;
|
var count = 0;
|
||||||
before(function() {
|
before(function() {
|
||||||
var roleModel = loopback.getModelByType(loopback.Role);
|
var roleModel = loopback.getModelByType(loopback.Role);
|
||||||
|
@ -175,47 +231,68 @@ describe('access control - integration', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
lt.beforeEach.givenModel('account');
|
lt.beforeEach.givenModel('accountWithReplaceOnPUTtrue');
|
||||||
|
|
||||||
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', '/api/accounts');
|
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', '/api/accounts-replacing');
|
||||||
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', '/api/accounts');
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', '/api/accounts-replacing');
|
||||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', '/api/accounts');
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', '/api/accounts-replacing');
|
||||||
|
|
||||||
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', urlForAccount);
|
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', urlForAccount);
|
||||||
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', urlForAccount);
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', urlForAccount);
|
||||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', urlForAccount);
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', urlForAccount);
|
||||||
|
|
||||||
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/accounts');
|
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/accounts-replacing');
|
||||||
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/accounts');
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/accounts-replacing');
|
||||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/accounts');
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/accounts-replacing');
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', urlForReplaceAccountPOST);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', urlForReplaceAccountPOST);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', urlForReplaceAccountPOST);
|
||||||
|
|
||||||
lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForAccount);
|
lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForAccount);
|
||||||
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForAccount);
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForAccount);
|
||||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForAccount);
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForAccount);
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('PATCH', urlForAccount);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PATCH', urlForAccount);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PATCH', urlForAccount);
|
||||||
|
|
||||||
lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {
|
lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {
|
||||||
|
var actId;
|
||||||
beforeEach(function(done) {
|
beforeEach(function(done) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// Create an account under the given user
|
// Create an account under the given user
|
||||||
app.models.account.create({
|
app.models.accountWithReplaceOnPUTtrue.create({
|
||||||
userId: self.user.id,
|
userId: self.loggedInAccessToken.userId,
|
||||||
balance: 100
|
balance: 100
|
||||||
}, function(err, act) {
|
}, function(err, act) {
|
||||||
self.url = '/api/accounts/' + act.id;
|
actId = act.id;
|
||||||
|
self.url = '/api/accounts-replacing/' + actId;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
lt.describe.whenCalledRemotely('PATCH', '/api/accounts-replacing/:id', function() {
|
||||||
lt.describe.whenCalledRemotely('PUT', '/api/accounts/:id', function() {
|
|
||||||
lt.it.shouldBeAllowed();
|
lt.it.shouldBeAllowed();
|
||||||
});
|
});
|
||||||
lt.describe.whenCalledRemotely('GET', '/api/accounts/:id', function() {
|
lt.describe.whenCalledRemotely('PUT', '/api/accounts-replacing/:id', function() {
|
||||||
lt.it.shouldBeAllowed();
|
lt.it.shouldBeAllowed();
|
||||||
});
|
});
|
||||||
lt.describe.whenCalledRemotely('DELETE', '/api/accounts/:id', function() {
|
lt.describe.whenCalledRemotely('GET', '/api/accounts-replacing/:id', function() {
|
||||||
|
lt.it.shouldBeAllowed();
|
||||||
|
});
|
||||||
|
lt.describe.whenCalledRemotely('DELETE', '/api/accounts-replacing/:id', function() {
|
||||||
lt.it.shouldBeDenied();
|
lt.it.shouldBeDenied();
|
||||||
});
|
});
|
||||||
|
describe('replace on POST verb', function() {
|
||||||
|
beforeEach(function(done) {
|
||||||
|
this.url = '/api/accounts-replacing/' + actId + '/replace';
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
lt.describe.whenCalledRemotely('POST', '/api/accounts-replacing/:id/replace', function() {
|
||||||
|
lt.it.shouldBeAllowed();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForAccount);
|
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForAccount);
|
||||||
|
@ -223,7 +300,77 @@ describe('access control - integration', function() {
|
||||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForAccount);
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForAccount);
|
||||||
|
|
||||||
function urlForAccount() {
|
function urlForAccount() {
|
||||||
return '/api/accounts/' + this.account.id;
|
return '/api/accounts-replacing/' + this.accountWithReplaceOnPUTtrue.id;
|
||||||
|
}
|
||||||
|
function urlForReplaceAccountPOST() {
|
||||||
|
return '/api/accounts-replacing/' + this.accountWithReplaceOnPUTtrue.id + '/replace';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/accounts with replaceOnPUT false', function() {
|
||||||
|
lt.beforeEach.givenModel('accountWithReplaceOnPUTfalse');
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', urlForReplaceAccountPOST);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', urlForReplaceAccountPOST);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', urlForReplaceAccountPOST);
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForAccount);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForAccount);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForAccount);
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('PATCH', urlForAccount);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PATCH', urlForAccount);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PATCH', urlForAccount);
|
||||||
|
|
||||||
|
lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {
|
||||||
|
var actId;
|
||||||
|
beforeEach(function(done) {
|
||||||
|
var self = this;
|
||||||
|
// Create an account under the given user
|
||||||
|
app.models.accountWithReplaceOnPUTfalse.create({
|
||||||
|
userId: self.loggedInAccessToken.userId,
|
||||||
|
balance: 100,
|
||||||
|
}, function(err, act) {
|
||||||
|
actId = act.id;
|
||||||
|
self.url = '/api/accounts-updating/' + actId;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
lt.describe.whenCalledRemotely('PATCH', '/api/accounts-updating/:id', function() {
|
||||||
|
lt.it.shouldBeAllowed();
|
||||||
|
});
|
||||||
|
|
||||||
|
lt.describe.whenCalledRemotely('PUT', '/api/accounts-updating/:id', function() {
|
||||||
|
|
||||||
|
lt.it.shouldBeAllowed();
|
||||||
|
});
|
||||||
|
lt.describe.whenCalledRemotely('GET', '/api/accounts-updating/:id', function() {
|
||||||
|
lt.it.shouldBeAllowed();
|
||||||
|
});
|
||||||
|
lt.describe.whenCalledRemotely('DELETE', '/api/accounts-updating/:id', function() {
|
||||||
|
lt.it.shouldBeDenied();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('replace on POST verb', function() {
|
||||||
|
beforeEach(function(done) {
|
||||||
|
this.url = '/api/accounts-updating/' + actId + '/replace';
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
lt.describe.whenCalledRemotely('POST', '/api/accounts-updating/:id/replace', function() {
|
||||||
|
lt.it.shouldBeAllowed();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForAccount);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForAccount);
|
||||||
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForAccount);
|
||||||
|
|
||||||
|
function urlForAccount() {
|
||||||
|
return '/api/accounts-updating/' + this.accountWithReplaceOnPUTfalse.id;
|
||||||
|
}
|
||||||
|
function urlForReplaceAccountPOST() {
|
||||||
|
return '/api/accounts-updating/' + this.accountWithReplaceOnPUTfalse.id + '/replace';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
|
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
var cookieParser = require('cookie-parser');
|
||||||
var loopback = require('../');
|
var loopback = require('../');
|
||||||
var extend = require('util')._extend;
|
var extend = require('util')._extend;
|
||||||
|
var session = require('express-session');
|
||||||
|
|
||||||
var Token = loopback.AccessToken.extend('MyToken');
|
var Token = loopback.AccessToken.extend('MyToken');
|
||||||
var ds = loopback.createDataSource({connector: loopback.Memory});
|
var ds = loopback.createDataSource({connector: loopback.Memory});
|
||||||
Token.attachTo(ds);
|
Token.attachTo(ds);
|
||||||
|
@ -65,7 +73,7 @@ describe('loopback.token(options)', function() {
|
||||||
.end(done);
|
.end(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('populating req.toen from HTTP Basic Auth formatted authorization header', function() {
|
describe('populating req.token from HTTP Basic Auth formatted authorization header', function() {
|
||||||
it('parses "standalone-token"', function(done) {
|
it('parses "standalone-token"', function(done) {
|
||||||
var token = this.token.id;
|
var token = this.token.id;
|
||||||
token = 'Basic ' + new Buffer(token).toString('base64');
|
token = 'Basic ' + new Buffer(token).toString('base64');
|
||||||
|
@ -144,7 +152,8 @@ describe('loopback.token(options)', function() {
|
||||||
.set('authorization', id)
|
.set('authorization', id)
|
||||||
.end(function(err, res) {
|
.end(function(err, res) {
|
||||||
assert(!err);
|
assert(!err);
|
||||||
assert.deepEqual(res.body, {userId: userId});
|
assert.deepEqual(res.body, { userId: userId });
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -159,7 +168,8 @@ describe('loopback.token(options)', function() {
|
||||||
.set('authorization', id)
|
.set('authorization', id)
|
||||||
.end(function(err, res) {
|
.end(function(err, res) {
|
||||||
assert(!err);
|
assert(!err);
|
||||||
assert.deepEqual(res.body, {userId: userId, state: 1});
|
assert.deepEqual(res.body, { userId: userId, state: 1 });
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -174,15 +184,47 @@ describe('loopback.token(options)', function() {
|
||||||
.set('authorization', id)
|
.set('authorization', id)
|
||||||
.end(function(err, res) {
|
.end(function(err, res) {
|
||||||
assert(!err);
|
assert(!err);
|
||||||
assert.deepEqual(res.body, {userId: userId, state: 1});
|
assert.deepEqual(res.body, { userId: userId, state: 1 });
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should generate a 401 on a current user literal route without an authToken',
|
||||||
|
function(done) {
|
||||||
|
var app = createTestApp(null, done);
|
||||||
|
request(app)
|
||||||
|
.get('/users/me')
|
||||||
|
.set('authorization', null)
|
||||||
|
.expect(401)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a 401 on a current user literal route with empty authToken',
|
||||||
|
function(done) {
|
||||||
|
var app = createTestApp(null, done);
|
||||||
|
request(app)
|
||||||
|
.get('/users/me')
|
||||||
|
.set('authorization', '')
|
||||||
|
.expect(401)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a 401 on a current user literal route with invalid authToken',
|
||||||
|
function(done) {
|
||||||
|
var app = createTestApp(this.token, done);
|
||||||
|
request(app)
|
||||||
|
.get('/users/me')
|
||||||
|
.set('Authorization', 'invald-token-id')
|
||||||
|
.expect(401)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
it('should skip when req.token is already present', function(done) {
|
it('should skip when req.token is already present', function(done) {
|
||||||
var tokenStub = { id: 'stub id' };
|
var tokenStub = { id: 'stub id' };
|
||||||
app.use(function(req, res, next) {
|
app.use(function(req, res, next) {
|
||||||
req.accessToken = tokenStub;
|
req.accessToken = tokenStub;
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
app.use(loopback.token({ model: Token }));
|
app.use(loopback.token({ model: Token }));
|
||||||
|
@ -195,10 +237,139 @@ describe('loopback.token(options)', function() {
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.end(function(err, res) {
|
.end(function(err, res) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(res.body).to.eql(tokenStub);
|
expect(res.body).to.eql(tokenStub);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('loading multiple instances of token middleware', function() {
|
||||||
|
it('should skip when req.token is already present and no further options are set',
|
||||||
|
function(done) {
|
||||||
|
var tokenStub = { id: 'stub id' };
|
||||||
|
app.use(function(req, res, next) {
|
||||||
|
req.accessToken = tokenStub;
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
app.use(loopback.token({ model: Token }));
|
||||||
|
app.get('/', function(req, res, next) {
|
||||||
|
res.send(req.accessToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
request(app).get('/')
|
||||||
|
.set('Authorization', this.token.id)
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err, res) {
|
||||||
|
if (err) return done(err);
|
||||||
|
|
||||||
|
expect(res.body).to.eql(tokenStub);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not overwrite valid existing token (has "id" property) ' +
|
||||||
|
' when overwriteExistingToken is falsy',
|
||||||
|
function(done) {
|
||||||
|
var tokenStub = { id: 'stub id' };
|
||||||
|
app.use(function(req, res, next) {
|
||||||
|
req.accessToken = tokenStub;
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
app.use(loopback.token({
|
||||||
|
model: Token,
|
||||||
|
enableDoublecheck: true,
|
||||||
|
}));
|
||||||
|
app.get('/', function(req, res, next) {
|
||||||
|
res.send(req.accessToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
request(app).get('/')
|
||||||
|
.set('Authorization', this.token.id)
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err, res) {
|
||||||
|
if (err) return done(err);
|
||||||
|
|
||||||
|
expect(res.body).to.eql(tokenStub);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should overwrite invalid existing token (is !== undefined and has no "id" property) ' +
|
||||||
|
' when enableDoubkecheck is true',
|
||||||
|
function(done) {
|
||||||
|
var token = this.token;
|
||||||
|
|
||||||
|
app.use(function(req, res, next) {
|
||||||
|
req.accessToken = null;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use(loopback.token({
|
||||||
|
model: Token,
|
||||||
|
enableDoublecheck: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
app.get('/', function(req, res, next) {
|
||||||
|
res.send(req.accessToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
request(app).get('/')
|
||||||
|
.set('Authorization', token.id)
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err, res) {
|
||||||
|
if (err) return done(err);
|
||||||
|
expect(res.body).to.eql({
|
||||||
|
id: token.id,
|
||||||
|
ttl: token.ttl,
|
||||||
|
userId: token.userId,
|
||||||
|
created: token.created.toJSON(),
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should overwrite existing token when enableDoublecheck ' +
|
||||||
|
'and overwriteExistingToken options are truthy',
|
||||||
|
function(done) {
|
||||||
|
var token = this.token;
|
||||||
|
var tokenStub = { id: 'stub id' };
|
||||||
|
|
||||||
|
app.use(function(req, res, next) {
|
||||||
|
req.accessToken = tokenStub;
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
app.use(loopback.token({
|
||||||
|
model: Token,
|
||||||
|
enableDoublecheck: true,
|
||||||
|
overwriteExistingToken: true,
|
||||||
|
}));
|
||||||
|
app.get('/', function(req, res, next) {
|
||||||
|
res.send(req.accessToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
request(app).get('/')
|
||||||
|
.set('Authorization', token.id)
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err, res) {
|
||||||
|
if (err) return done(err);
|
||||||
|
|
||||||
|
expect(res.body).to.eql({
|
||||||
|
id: token.id,
|
||||||
|
ttl: token.ttl,
|
||||||
|
userId: token.userId,
|
||||||
|
created: token.created.toJSON(),
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('AccessToken', function() {
|
describe('AccessToken', function() {
|
||||||
|
@ -214,10 +385,38 @@ describe('AccessToken', function() {
|
||||||
assert(Object.prototype.toString.call(this.token.created), '[object Date]');
|
assert(Object.prototype.toString.call(this.token.created), '[object Date]');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be validateable', function(done) {
|
describe('.validate()', function() {
|
||||||
this.token.validate(function(err, isValid) {
|
it('accepts valid tokens', function(done) {
|
||||||
assert(isValid);
|
this.token.validate(function(err, isValid) {
|
||||||
done();
|
assert(isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects eternal TTL by default', function(done) {
|
||||||
|
this.token.ttl = -1;
|
||||||
|
this.token.validate(function(err, isValid) {
|
||||||
|
if (err) return done(err);
|
||||||
|
expect(isValid, 'isValid').to.equal(false);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows eternal tokens when enabled by User.allowEternalTokens',
|
||||||
|
function(done) {
|
||||||
|
var Token = givenLocalTokenModel();
|
||||||
|
|
||||||
|
// Overwrite User settings - enable eternal tokens
|
||||||
|
Token.app.models.User.settings.allowEternalTokens = true;
|
||||||
|
|
||||||
|
Token.create({ userId: '123', ttl: -1 }, function(err, token) {
|
||||||
|
if (err) return done(err);
|
||||||
|
token.validate(function(err, isValid) {
|
||||||
|
if (err) return done(err);
|
||||||
|
expect(isValid, 'isValid').to.equal(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -232,7 +431,9 @@ describe('AccessToken', function() {
|
||||||
|
|
||||||
Token.findForRequest(req, function(err, token) {
|
Token.findForRequest(req, function(err, token) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(token.id).to.eql(expectedTokenId);
|
expect(token.id).to.eql(expectedTokenId);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -255,6 +456,9 @@ describe('AccessToken', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('app.enableAuth()', function() {
|
describe('app.enableAuth()', function() {
|
||||||
|
beforeEach(function setupAuthWithModels() {
|
||||||
|
app.enableAuth({ dataSource: ds });
|
||||||
|
});
|
||||||
beforeEach(createTestingToken);
|
beforeEach(createTestingToken);
|
||||||
|
|
||||||
it('prevents remote call with 401 status on denied ACL', function(done) {
|
it('prevents remote call with 401 status on denied ACL', function(done) {
|
||||||
|
@ -266,15 +470,17 @@ describe('app.enableAuth()', function() {
|
||||||
if (err) {
|
if (err) {
|
||||||
return done(err);
|
return done(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
var errorResponse = res.body.error;
|
var errorResponse = res.body.error;
|
||||||
assert(errorResponse);
|
assert(errorResponse);
|
||||||
assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED');
|
assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED');
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('prevent remote call with app setting status on denied ACL', function(done) {
|
it('prevent remote call with app setting status on denied ACL', function(done) {
|
||||||
createTestAppAndRequest(this.token, {app:{aclErrorStatus:403}}, done)
|
createTestAppAndRequest(this.token, {app: {aclErrorStatus: 403}}, done)
|
||||||
.del('/tests/123')
|
.del('/tests/123')
|
||||||
.expect(403)
|
.expect(403)
|
||||||
.set('authorization', this.token.id)
|
.set('authorization', this.token.id)
|
||||||
|
@ -282,15 +488,17 @@ describe('app.enableAuth()', function() {
|
||||||
if (err) {
|
if (err) {
|
||||||
return done(err);
|
return done(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
var errorResponse = res.body.error;
|
var errorResponse = res.body.error;
|
||||||
assert(errorResponse);
|
assert(errorResponse);
|
||||||
assert.equal(errorResponse.code, 'ACCESS_DENIED');
|
assert.equal(errorResponse.code, 'ACCESS_DENIED');
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('prevent remote call with app setting status on denied ACL', function(done) {
|
it('prevent remote call with app setting status on denied ACL', function(done) {
|
||||||
createTestAppAndRequest(this.token, {model:{aclErrorStatus:404}}, done)
|
createTestAppAndRequest(this.token, {model: {aclErrorStatus: 404}}, done)
|
||||||
.del('/tests/123')
|
.del('/tests/123')
|
||||||
.expect(404)
|
.expect(404)
|
||||||
.set('authorization', this.token.id)
|
.set('authorization', this.token.id)
|
||||||
|
@ -298,9 +506,11 @@ describe('app.enableAuth()', function() {
|
||||||
if (err) {
|
if (err) {
|
||||||
return done(err);
|
return done(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
var errorResponse = res.body.error;
|
var errorResponse = res.body.error;
|
||||||
assert(errorResponse);
|
assert(errorResponse);
|
||||||
assert.equal(errorResponse.code, 'MODEL_NOT_FOUND');
|
assert.equal(errorResponse.code, 'MODEL_NOT_FOUND');
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -314,9 +524,11 @@ describe('app.enableAuth()', function() {
|
||||||
if (err) {
|
if (err) {
|
||||||
return done(err);
|
return done(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
var errorResponse = res.body.error;
|
var errorResponse = res.body.error;
|
||||||
assert(errorResponse);
|
assert(errorResponse);
|
||||||
assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED');
|
assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED');
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -324,7 +536,8 @@ describe('app.enableAuth()', function() {
|
||||||
it('stores token in the context', function(done) {
|
it('stores token in the context', function(done) {
|
||||||
var TestModel = loopback.createModel('TestModel', { base: 'Model' });
|
var TestModel = loopback.createModel('TestModel', { base: 'Model' });
|
||||||
TestModel.getToken = function(cb) {
|
TestModel.getToken = function(cb) {
|
||||||
cb(null, loopback.getCurrentContext().get('accessToken') || null);
|
var ctx = loopback.getCurrentContext();
|
||||||
|
cb(null, ctx && ctx.get('accessToken') || null);
|
||||||
};
|
};
|
||||||
TestModel.remoteMethod('getToken', {
|
TestModel.remoteMethod('getToken', {
|
||||||
returns: { arg: 'token', type: 'object' },
|
returns: { arg: 'token', type: 'object' },
|
||||||
|
@ -347,17 +560,44 @@ describe('app.enableAuth()', function() {
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.end(function(err, res) {
|
.end(function(err, res) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(res.body.token.id).to.eql(token.id);
|
expect(res.body.token.id).to.eql(token.id);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// See https://github.com/strongloop/loopback-context/issues/6
|
||||||
|
it('checks whether context is active', function(done) {
|
||||||
|
var app = loopback();
|
||||||
|
|
||||||
|
app.enableAuth();
|
||||||
|
app.use(loopback.context());
|
||||||
|
app.use(session({
|
||||||
|
secret: 'kitty',
|
||||||
|
saveUninitialized: true,
|
||||||
|
resave: true
|
||||||
|
}));
|
||||||
|
app.use(loopback.token({ model: Token }));
|
||||||
|
app.get('/', function(req, res) { res.send('OK'); });
|
||||||
|
app.use(loopback.rest());
|
||||||
|
|
||||||
|
request(app)
|
||||||
|
.get('/')
|
||||||
|
.set('authorization', this.token.id)
|
||||||
|
.set('cookie', 'connect.sid=s%3AFTyno9_MbGTJuOwdh9bxsYCVxlhlulTZ.PZvp85jzLXZBCBkhCsSfuUjhij%2Fb0B1K2RYZdxSQU0c')
|
||||||
|
.expect(200, 'OK')
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function createTestingToken(done) {
|
function createTestingToken(done) {
|
||||||
var test = this;
|
var test = this;
|
||||||
Token.create({userId: '123'}, function(err, token) {
|
Token.create({userId: '123'}, function(err, token) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
test.token = token;
|
test.token = token;
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -380,8 +620,9 @@ function createTestApp(testToken, settings, done) {
|
||||||
}, settings.token);
|
}, settings.token);
|
||||||
|
|
||||||
var app = loopback();
|
var app = loopback();
|
||||||
|
app.set('logoutSessionsOnSensitiveChanges', true);
|
||||||
|
|
||||||
app.use(loopback.cookieParser('secret'));
|
app.use(cookieParser('secret'));
|
||||||
app.use(loopback.token(tokenSettings));
|
app.use(loopback.token(tokenSettings));
|
||||||
app.get('/token', function(req, res) {
|
app.get('/token', function(req, res) {
|
||||||
res.cookie('authorization', testToken.id, {signed: true});
|
res.cookie('authorization', testToken.id, {signed: true});
|
||||||
|
@ -439,3 +680,17 @@ function createTestApp(testToken, settings, done) {
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function givenLocalTokenModel() {
|
||||||
|
var app = loopback({ localRegistry: true, loadBuiltinModels: true });
|
||||||
|
app.set('logoutSessionsOnSensitiveChanges', true);
|
||||||
|
app.dataSource('db', { connector: 'memory' });
|
||||||
|
|
||||||
|
var User = app.registry.getModel('User');
|
||||||
|
app.model(User, { dataSource: 'db' });
|
||||||
|
|
||||||
|
var Token = app.registry.getModel('AccessToken');
|
||||||
|
app.model(Token, { dataSource: 'db' });
|
||||||
|
|
||||||
|
return Token;
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var loopback = require('../index');
|
var loopback = require('../index');
|
||||||
var Scope = loopback.Scope;
|
var Scope = loopback.Scope;
|
||||||
|
@ -358,19 +363,17 @@ describe('security ACLs', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('access check', function() {
|
describe('access check', function() {
|
||||||
var app;
|
|
||||||
before(function() {
|
|
||||||
app = loopback();
|
|
||||||
app.use(loopback.rest());
|
|
||||||
app.enableAuth();
|
|
||||||
app.dataSource('test', {connector: 'memory'});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should occur before other remote hooks', function(done) {
|
it('should occur before other remote hooks', function(done) {
|
||||||
var MyTestModel = app.model('MyTestModel', {base: 'PersistedModel', dataSource: 'test'});
|
var app = loopback();
|
||||||
|
var MyTestModel = app.registry.createModel('MyTestModel');
|
||||||
var checkAccessCalled = false;
|
var checkAccessCalled = false;
|
||||||
var beforeHookCalled = false;
|
var beforeHookCalled = false;
|
||||||
|
|
||||||
|
app.use(loopback.rest());
|
||||||
|
app.enableAuth();
|
||||||
|
app.dataSource('test', { connector: 'memory' });
|
||||||
|
app.model(MyTestModel, { dataSource: 'test' });
|
||||||
|
|
||||||
// fake / spy on the checkAccess method
|
// fake / spy on the checkAccess method
|
||||||
MyTestModel.checkAccess = function() {
|
MyTestModel.checkAccess = function() {
|
||||||
var cb = arguments[arguments.length - 1];
|
var cb = arguments[arguments.length - 1];
|
||||||
|
@ -382,7 +385,9 @@ describe('access check', function() {
|
||||||
MyTestModel.beforeRemote('find', function(ctx, next) {
|
MyTestModel.beforeRemote('find', function(ctx, next) {
|
||||||
// ensure this is called after checkAccess
|
// ensure this is called after checkAccess
|
||||||
if (!checkAccessCalled) return done(new Error('incorrect order'));
|
if (!checkAccessCalled) return done(new Error('incorrect order'));
|
||||||
|
|
||||||
beforeHookCalled = true;
|
beforeHookCalled = true;
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -391,6 +396,7 @@ describe('access check', function() {
|
||||||
.end(function(err, result) {
|
.end(function(err, result) {
|
||||||
assert(beforeHookCalled, 'the before hook should be called');
|
assert(beforeHookCalled, 'the before hook should be called');
|
||||||
assert(checkAccessCalled, 'checkAccess should have been called');
|
assert(checkAccessCalled, 'checkAccess should have been called');
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
165
test/app.test.js
165
test/app.test.js
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/*jshint -W030 */
|
/*jshint -W030 */
|
||||||
|
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
@ -9,6 +14,7 @@ var loopback = require('../');
|
||||||
var PersistedModel = loopback.PersistedModel;
|
var PersistedModel = loopback.PersistedModel;
|
||||||
|
|
||||||
var describe = require('./util/describe');
|
var describe = require('./util/describe');
|
||||||
|
var expect = require('chai').expect;
|
||||||
var it = require('./util/it');
|
var it = require('./util/it');
|
||||||
|
|
||||||
describe('app', function() {
|
describe('app', function() {
|
||||||
|
@ -34,10 +40,12 @@ describe('app', function() {
|
||||||
|
|
||||||
executeMiddlewareHandlers(app, function(err) {
|
executeMiddlewareHandlers(app, function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(steps).to.eql([
|
expect(steps).to.eql([
|
||||||
'initial', 'session', 'auth', 'parse',
|
'initial', 'session', 'auth', 'parse',
|
||||||
'main', 'routes', 'files', 'final'
|
'main', 'routes', 'files', 'final'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -48,7 +56,9 @@ describe('app', function() {
|
||||||
|
|
||||||
executeMiddlewareHandlers(app, function(err) {
|
executeMiddlewareHandlers(app, function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(steps).to.eql(['first', 'second']);
|
expect(steps).to.eql(['first', 'second']);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -60,7 +70,9 @@ describe('app', function() {
|
||||||
|
|
||||||
executeMiddlewareHandlers(app, function(err) {
|
executeMiddlewareHandlers(app, function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(steps).to.eql(['routes:before', 'main', 'routes:after']);
|
expect(steps).to.eql(['routes:before', 'main', 'routes:after']);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -80,7 +92,9 @@ describe('app', function() {
|
||||||
expect(found).have.property('phase', 'routes:before');
|
expect(found).have.property('phase', 'routes:before');
|
||||||
executeMiddlewareHandlers(app, function(err) {
|
executeMiddlewareHandlers(app, function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(steps).to.eql(['my-handler', 'extra-handler']);
|
expect(steps).to.eql(['my-handler', 'extra-handler']);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -98,7 +112,9 @@ describe('app', function() {
|
||||||
expect(found).have.property('phase', 'routes:before');
|
expect(found).have.property('phase', 'routes:before');
|
||||||
executeMiddlewareHandlers(app, function(err) {
|
executeMiddlewareHandlers(app, function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(steps).to.eql(['my-handler']);
|
expect(steps).to.eql(['my-handler']);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -116,7 +132,9 @@ describe('app', function() {
|
||||||
expect(found).have.property('phase', 'routes:before');
|
expect(found).have.property('phase', 'routes:before');
|
||||||
executeMiddlewareHandlers(app, function(err) {
|
executeMiddlewareHandlers(app, function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(steps).to.eql(['my-handler']);
|
expect(steps).to.eql(['my-handler']);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -126,6 +144,7 @@ describe('app', function() {
|
||||||
|
|
||||||
app.middleware('initial', function(req, res, next) {
|
app.middleware('initial', function(req, res, next) {
|
||||||
steps.push('initial');
|
steps.push('initial');
|
||||||
|
|
||||||
next(expectedError);
|
next(expectedError);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -133,12 +152,15 @@ describe('app', function() {
|
||||||
app.use(function errorHandler(err, req, res, next) {
|
app.use(function errorHandler(err, req, res, next) {
|
||||||
expect(err).to.equal(expectedError);
|
expect(err).to.equal(expectedError);
|
||||||
steps.push('error');
|
steps.push('error');
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
executeMiddlewareHandlers(app, function(err) {
|
executeMiddlewareHandlers(app, function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(steps).to.eql(['initial', 'error']);
|
expect(steps).to.eql(['initial', 'error']);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -152,6 +174,7 @@ describe('app', function() {
|
||||||
|
|
||||||
executeMiddlewareHandlers(app, function(err) {
|
executeMiddlewareHandlers(app, function(err) {
|
||||||
expect(err).to.equal(expectedError);
|
expect(err).to.equal(expectedError);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -170,12 +193,15 @@ describe('app', function() {
|
||||||
|
|
||||||
app.middleware('initial', function(err, req, res, next) {
|
app.middleware('initial', function(err, req, res, next) {
|
||||||
handledError = err;
|
handledError = err;
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
executeMiddlewareHandlers(app, function(err) {
|
executeMiddlewareHandlers(app, function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(handledError).to.equal(expectedError);
|
expect(handledError).to.equal(expectedError);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -188,7 +214,9 @@ describe('app', function() {
|
||||||
function(url, next) { executeMiddlewareHandlers(app, url, next); },
|
function(url, next) { executeMiddlewareHandlers(app, url, next); },
|
||||||
function(err) {
|
function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(steps).to.eql(['/scope', '/scope/item']);
|
expect(steps).to.eql(['/scope', '/scope/item']);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -201,7 +229,9 @@ describe('app', function() {
|
||||||
function(url, next) { executeMiddlewareHandlers(app, url, next); },
|
function(url, next) { executeMiddlewareHandlers(app, url, next); },
|
||||||
function(err) {
|
function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(steps).to.eql(['/a', '/b']);
|
expect(steps).to.eql(['/a', '/b']);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -214,7 +244,9 @@ describe('app', function() {
|
||||||
function(url, next) { executeMiddlewareHandlers(app, url, next); },
|
function(url, next) { executeMiddlewareHandlers(app, url, next); },
|
||||||
function(err) {
|
function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(steps).to.eql(['/a', '/b', '/scope']);
|
expect(steps).to.eql(['/a', '/b', '/scope']);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -222,12 +254,15 @@ describe('app', function() {
|
||||||
it('sets req.url to a sub-path', function(done) {
|
it('sets req.url to a sub-path', function(done) {
|
||||||
app.middleware('initial', ['/scope'], function(req, res, next) {
|
app.middleware('initial', ['/scope'], function(req, res, next) {
|
||||||
steps.push(req.url);
|
steps.push(req.url);
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
executeMiddlewareHandlers(app, '/scope/id', function(err) {
|
executeMiddlewareHandlers(app, '/scope/id', function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(steps).to.eql(['/id']);
|
expect(steps).to.eql(['/id']);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -239,11 +274,13 @@ describe('app', function() {
|
||||||
app.middleware('initial', function(rq, rs, next) {
|
app.middleware('initial', function(rq, rs, next) {
|
||||||
req = rq;
|
req = rq;
|
||||||
res = rs;
|
res = rs;
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
executeMiddlewareHandlers(app, function(err) {
|
executeMiddlewareHandlers(app, function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(getObjectAndPrototypeKeys(req), 'request').to.include.members([
|
expect(getObjectAndPrototypeKeys(req), 'request').to.include.members([
|
||||||
'accepts',
|
'accepts',
|
||||||
'get',
|
'get',
|
||||||
|
@ -273,12 +310,15 @@ describe('app', function() {
|
||||||
var reqProps;
|
var reqProps;
|
||||||
app.middleware('initial', function(req, res, next) {
|
app.middleware('initial', function(req, res, next) {
|
||||||
reqProps = { baseUrl: req.baseUrl, originalUrl: req.originalUrl };
|
reqProps = { baseUrl: req.baseUrl, originalUrl: req.originalUrl };
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
executeMiddlewareHandlers(app, '/test/url', function(err) {
|
executeMiddlewareHandlers(app, '/test/url', function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(reqProps).to.eql({ baseUrl: '', originalUrl: '/test/url' });
|
expect(reqProps).to.eql({ baseUrl: '', originalUrl: '/test/url' });
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -290,7 +330,9 @@ describe('app', function() {
|
||||||
|
|
||||||
executeMiddlewareHandlers(app, '/test', function(err) {
|
executeMiddlewareHandlers(app, '/test', function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(steps).to.eql(['route', 'files']);
|
expect(steps).to.eql(['route', 'files']);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -310,7 +352,9 @@ describe('app', function() {
|
||||||
|
|
||||||
executeMiddlewareHandlers(app, function(err) {
|
executeMiddlewareHandlers(app, function(err) {
|
||||||
if (err) return done;
|
if (err) return done;
|
||||||
|
|
||||||
expect(steps).to.eql(numbers);
|
expect(steps).to.eql(numbers);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -324,6 +368,7 @@ describe('app', function() {
|
||||||
mountpath: req.app.mountpath,
|
mountpath: req.app.mountpath,
|
||||||
parent: req.app.parent
|
parent: req.app.parent
|
||||||
};
|
};
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
subapp.on('mount', function() { mountWasEmitted = true; });
|
subapp.on('mount', function() { mountWasEmitted = true; });
|
||||||
|
@ -332,11 +377,13 @@ describe('app', function() {
|
||||||
|
|
||||||
executeMiddlewareHandlers(app, '/mountpath/test', function(err) {
|
executeMiddlewareHandlers(app, '/mountpath/test', function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(mountWasEmitted, 'mountWasEmitted').to.be.true;
|
expect(mountWasEmitted, 'mountWasEmitted').to.be.true;
|
||||||
expect(data).to.eql({
|
expect(data).to.eql({
|
||||||
mountpath: '/mountpath',
|
mountpath: '/mountpath',
|
||||||
parent: app
|
parent: app
|
||||||
});
|
});
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -350,25 +397,30 @@ describe('app', function() {
|
||||||
subapp.use(function verifyTestAssumptions(req, res, next) {
|
subapp.use(function verifyTestAssumptions(req, res, next) {
|
||||||
expect(req.__proto__).to.not.equal(expected.req);
|
expect(req.__proto__).to.not.equal(expected.req);
|
||||||
expect(res.__proto__).to.not.equal(expected.res);
|
expect(res.__proto__).to.not.equal(expected.res);
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.middleware('initial', function saveOriginalValues(req, res, next) {
|
app.middleware('initial', function saveOriginalValues(req, res, next) {
|
||||||
expected.req = req.__proto__;
|
expected.req = req.__proto__;
|
||||||
expected.res = res.__proto__;
|
expected.res = res.__proto__;
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
app.middleware('routes', subapp);
|
app.middleware('routes', subapp);
|
||||||
app.middleware('final', function saveActualValues(req, res, next) {
|
app.middleware('final', function saveActualValues(req, res, next) {
|
||||||
actual.req = req.__proto__;
|
actual.req = req.__proto__;
|
||||||
actual.res = res.__proto__;
|
actual.res = res.__proto__;
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
executeMiddlewareHandlers(app, function(err) {
|
executeMiddlewareHandlers(app, function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(actual.req, 'req').to.equal(expected.req);
|
expect(actual.req, 'req').to.equal(expected.req);
|
||||||
expect(actual.res, 'res').to.equal(expected.res);
|
expect(actual.res, 'res').to.equal(expected.res);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -383,6 +435,7 @@ describe('app', function() {
|
||||||
function pathSavingHandler() {
|
function pathSavingHandler() {
|
||||||
return function(req, res, next) {
|
return function(req, res, next) {
|
||||||
steps.push(req.originalUrl);
|
steps.push(req.originalUrl);
|
||||||
|
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -406,6 +459,7 @@ describe('app', function() {
|
||||||
var args = Array.prototype.slice.apply(arguments);
|
var args = Array.prototype.slice.apply(arguments);
|
||||||
return function(req, res, next) {
|
return function(req, res, next) {
|
||||||
steps.push(args);
|
steps.push(args);
|
||||||
|
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -456,12 +510,14 @@ describe('app', function() {
|
||||||
|
|
||||||
executeMiddlewareHandlers(app, function(err) {
|
executeMiddlewareHandlers(app, function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(steps).to.eql([
|
expect(steps).to.eql([
|
||||||
['before'],
|
['before'],
|
||||||
[expectedConfig],
|
[expectedConfig],
|
||||||
['after', 2],
|
['after', 2],
|
||||||
[{x: 1}]
|
[{x: 1}]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -472,6 +528,7 @@ describe('app', function() {
|
||||||
function factory() {
|
function factory() {
|
||||||
return function(req, res, next) {
|
return function(req, res, next) {
|
||||||
steps.push(req.originalUrl);
|
steps.push(req.originalUrl);
|
||||||
|
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -485,7 +542,9 @@ describe('app', function() {
|
||||||
function(url, next) { executeMiddlewareHandlers(app, url, next); },
|
function(url, next) { executeMiddlewareHandlers(app, url, next); },
|
||||||
function(err) {
|
function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(steps).to.eql(['/a', '/b', '/scope']);
|
expect(steps).to.eql(['/a', '/b', '/scope']);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -542,24 +601,27 @@ describe('app', function() {
|
||||||
names.forEach(function(it) {
|
names.forEach(function(it) {
|
||||||
app.middleware(it, function(req, res, next) {
|
app.middleware(it, function(req, res, next) {
|
||||||
steps.push(it);
|
steps.push(it);
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
executeMiddlewareHandlers(app, function(err) {
|
executeMiddlewareHandlers(app, function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(steps).to.eql(names);
|
expect(steps).to.eql(names);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('app.model(Model)', function() {
|
describe('app.model(Model)', function() {
|
||||||
var app;
|
var app, db, MyTestModel;
|
||||||
var db;
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
app = loopback();
|
app = loopback();
|
||||||
db = loopback.createDataSource({connector: loopback.Memory});
|
db = loopback.createDataSource({ connector: loopback.Memory });
|
||||||
|
MyTestModel = app.registry.createModel('MyTestModel', {}, {base: 'Model'});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Expose a `Model` to remote clients', function() {
|
it('Expose a `Model` to remote clients', function() {
|
||||||
|
@ -570,8 +632,8 @@ describe('app', function() {
|
||||||
expect(app.models()).to.eql([Color]);
|
expect(app.models()).to.eql([Color]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses singlar name as app.remoteObjects() key', function() {
|
it('uses singular name as app.remoteObjects() key', function() {
|
||||||
var Color = PersistedModel.extend('color', {name: String});
|
var Color = PersistedModel.extend('color', { name: String });
|
||||||
app.model(Color);
|
app.model(Color);
|
||||||
Color.attachTo(db);
|
Color.attachTo(db);
|
||||||
expect(app.remoteObjects()).to.eql({ color: Color });
|
expect(app.remoteObjects()).to.eql({ color: Color });
|
||||||
|
@ -606,6 +668,22 @@ describe('app', function() {
|
||||||
expect(remotedClass).to.eql(Color.sharedClass);
|
expect(remotedClass).to.eql(Color.sharedClass);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('emits a `remoteMethodDisabled` event', function() {
|
||||||
|
var Color = PersistedModel.extend('color', { name: String });
|
||||||
|
Color.shared = true;
|
||||||
|
var remoteMethodDisabledClass, disabledRemoteMethod;
|
||||||
|
app.on('remoteMethodDisabled', function(sharedClass, methodName) {
|
||||||
|
remoteMethodDisabledClass = sharedClass;
|
||||||
|
disabledRemoteMethod = methodName;
|
||||||
|
});
|
||||||
|
app.model(Color);
|
||||||
|
app.models.Color.disableRemoteMethodByName('findOne');
|
||||||
|
expect(remoteMethodDisabledClass).to.exist;
|
||||||
|
expect(remoteMethodDisabledClass).to.eql(Color.sharedClass);
|
||||||
|
expect(disabledRemoteMethod).to.exist;
|
||||||
|
expect(disabledRemoteMethod).to.eql('findOne');
|
||||||
|
});
|
||||||
|
|
||||||
it.onServer('updates REST API when a new model is added', function(done) {
|
it.onServer('updates REST API when a new model is added', function(done) {
|
||||||
app.use(loopback.rest());
|
app.use(loopback.rest());
|
||||||
request(app).get('/colors').expect(404, function(err, res) {
|
request(app).get('/colors').expect(404, function(err, res) {
|
||||||
|
@ -617,18 +695,22 @@ describe('app', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('accepts null dataSource', function() {
|
it('accepts null dataSource', function(done) {
|
||||||
app.model('MyTestModel', { dataSource: null });
|
app.model(MyTestModel, { dataSource: null });
|
||||||
|
expect(MyTestModel.dataSource).to.eql(null);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('accepts false dataSource', function() {
|
it('accepts false dataSource', function(done) {
|
||||||
app.model('MyTestModel', { dataSource: false });
|
app.model(MyTestModel, { dataSource: false });
|
||||||
|
expect(MyTestModel.getDataSource()).to.eql(null);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not require dataSource', function() {
|
it('does not require dataSource', function(done) {
|
||||||
app.model('MyTestModel', {});
|
app.model(MyTestModel);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('app.model(name, config)', function() {
|
describe('app.model(name, config)', function() {
|
||||||
|
@ -636,6 +718,7 @@ describe('app', function() {
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
app = loopback();
|
app = loopback();
|
||||||
|
app.set('logoutSessionsOnSensitiveChanges', true);
|
||||||
app.dataSource('db', {
|
app.dataSource('db', {
|
||||||
connector: 'memory'
|
connector: 'memory'
|
||||||
});
|
});
|
||||||
|
@ -689,7 +772,6 @@ describe('app', function() {
|
||||||
expect(app.models.foo.app).to.equal(app);
|
expect(app.models.foo.app).to.equal(app);
|
||||||
expect(app.models.foo.shared).to.equal(true);
|
expect(app.models.foo.shared).to.equal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('app.model(ModelCtor, config)', function() {
|
describe('app.model(ModelCtor, config)', function() {
|
||||||
|
@ -702,7 +784,8 @@ describe('app', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(!previousModel || !previousModel.dataSource);
|
assert(!previousModel || !previousModel.dataSource);
|
||||||
app.model('TestModel', { dataSource: 'db' });
|
var TestModel = app.registry.createModel('TestModel');
|
||||||
|
app.model(TestModel, { dataSource: 'db' });
|
||||||
expect(app.models.TestModel.dataSource).to.equal(app.dataSources.db);
|
expect(app.models.TestModel.dataSource).to.equal(app.dataSources.db);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -710,7 +793,8 @@ describe('app', function() {
|
||||||
describe('app.models', function() {
|
describe('app.models', function() {
|
||||||
it('is unique per app instance', function() {
|
it('is unique per app instance', function() {
|
||||||
app.dataSource('db', { connector: 'memory' });
|
app.dataSource('db', { connector: 'memory' });
|
||||||
var Color = app.model('Color', { dataSource: 'db' });
|
var Color = app.registry.createModel('Color');
|
||||||
|
app.model(Color, { dataSource: 'db' });
|
||||||
expect(app.models.Color).to.equal(Color);
|
expect(app.models.Color).to.equal(Color);
|
||||||
var anotherApp = loopback();
|
var anotherApp = loopback();
|
||||||
expect(anotherApp.models.Color).to.equal(undefined);
|
expect(anotherApp.models.Color).to.equal(undefined);
|
||||||
|
@ -732,6 +816,22 @@ describe('app', function() {
|
||||||
app.dataSource('custom', { connector: 'custom' });
|
app.dataSource('custom', { connector: 'custom' });
|
||||||
expect(app.dataSources.custom.name).to.equal(loopback.Memory.name);
|
expect(app.dataSources.custom.name).to.equal(loopback.Memory.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('adds data source name to error messages', function() {
|
||||||
|
app.connector('throwing', {
|
||||||
|
initialize: function() { throw new Error('expected test error'); },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
app.dataSource('bad-ds', { connector: 'throwing' });
|
||||||
|
}).to.throw(/bad-ds.*throwing/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds app reference to the data source object', function() {
|
||||||
|
app.dataSource('ds', { connector: 'memory' });
|
||||||
|
expect(app.datasources.ds.app).to.not.equal(undefined);
|
||||||
|
expect(app.datasources.ds.app).to.equal(app);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe.onServer('listen()', function() {
|
describe.onServer('listen()', function() {
|
||||||
|
@ -755,6 +855,7 @@ describe('app', function() {
|
||||||
|
|
||||||
app.listen(function() {
|
app.listen(function() {
|
||||||
expect(app.get('port'), 'port').to.not.equal(0);
|
expect(app.get('port'), 'port').to.not.equal(0);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -768,6 +869,7 @@ describe('app', function() {
|
||||||
var host = process.platform === 'win32' ? 'localhost' : app.get('host');
|
var host = process.platform === 'win32' ? 'localhost' : app.get('host');
|
||||||
var expectedUrl = 'http://' + host + ':' + app.get('port') + '/';
|
var expectedUrl = 'http://' + host + ':' + app.get('port') + '/';
|
||||||
expect(app.get('url'), 'url').to.equal(expectedUrl);
|
expect(app.get('url'), 'url').to.equal(expectedUrl);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -778,6 +880,7 @@ describe('app', function() {
|
||||||
app.listen(0, '127.0.0.1', function() {
|
app.listen(0, '127.0.0.1', function() {
|
||||||
expect(app.get('port'), 'port').to.not.equal(0).and.not.equal(1);
|
expect(app.get('port'), 'port').to.not.equal(0).and.not.equal(1);
|
||||||
expect(this.address().address).to.equal('127.0.0.1');
|
expect(this.address().address).to.equal('127.0.0.1');
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -788,6 +891,7 @@ describe('app', function() {
|
||||||
app.set('port', 1);
|
app.set('port', 1);
|
||||||
app.listen(0).on('listening', function() {
|
app.listen(0).on('listening', function() {
|
||||||
expect(app.get('port'), 'port') .to.not.equal(0).and.not.equal(1);
|
expect(app.get('port'), 'port') .to.not.equal(0).and.not.equal(1);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -802,6 +906,7 @@ describe('app', function() {
|
||||||
app.listen()
|
app.listen()
|
||||||
.on('listening', function() {
|
.on('listening', function() {
|
||||||
expect(this.address().address).to.equal('127.0.0.1');
|
expect(this.address().address).to.equal('127.0.0.1');
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -818,6 +923,7 @@ describe('app', function() {
|
||||||
var AUTH_MODELS = ['User', 'ACL', 'AccessToken', 'Role', 'RoleMapping'];
|
var AUTH_MODELS = ['User', 'ACL', 'AccessToken', 'Role', 'RoleMapping'];
|
||||||
var app = loopback({ localRegistry: true, loadBuiltinModels: true });
|
var app = loopback({ localRegistry: true, loadBuiltinModels: true });
|
||||||
require('../lib/builtin-models')(app.registry);
|
require('../lib/builtin-models')(app.registry);
|
||||||
|
app.set('logoutSessionsOnSensitiveChanges', true);
|
||||||
var db = app.dataSource('db', { connector: 'memory' });
|
var db = app.dataSource('db', { connector: 'memory' });
|
||||||
|
|
||||||
app.enableAuth({ dataSource: 'db' });
|
app.enableAuth({ dataSource: 'db' });
|
||||||
|
@ -833,6 +939,7 @@ describe('app', function() {
|
||||||
|
|
||||||
it('detects already configured subclass of a required model', function() {
|
it('detects already configured subclass of a required model', function() {
|
||||||
var app = loopback({ localRegistry: true, loadBuiltinModels: true });
|
var app = loopback({ localRegistry: true, loadBuiltinModels: true });
|
||||||
|
app.set('logoutSessionsOnSensitiveChanges', true);
|
||||||
var db = app.dataSource('db', { connector: 'memory' });
|
var db = app.dataSource('db', { connector: 'memory' });
|
||||||
var Customer = app.registry.createModel('Customer', {}, { base: 'User' });
|
var Customer = app.registry.createModel('Customer', {}, { base: 'User' });
|
||||||
app.model(Customer, { dataSource: 'db' });
|
app.model(Customer, { dataSource: 'db' });
|
||||||
|
@ -853,18 +960,14 @@ describe('app', function() {
|
||||||
.end(function(err, res) {
|
.end(function(err, res) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
assert.equal(typeof res.body, 'object');
|
expect(res.body).to.be.an('object');
|
||||||
assert(res.body.started);
|
expect(res.body).to.have.property('started');
|
||||||
// The number can be 0
|
expect(res.body.uptime, 'uptime').to.be.gte(0);
|
||||||
assert(res.body.uptime !== undefined);
|
|
||||||
|
|
||||||
var elapsed = Date.now() - Number(new Date(res.body.started));
|
var elapsed = Date.now() - Number(new Date(res.body.started));
|
||||||
|
|
||||||
// elapsed should be a positive number...
|
// elapsed should be a small positive number...
|
||||||
assert(elapsed >= 0);
|
expect(elapsed, 'elapsed').to.be.within(0, 300);
|
||||||
|
|
||||||
// less than 100 milliseconds
|
|
||||||
assert(elapsed < 100);
|
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -957,8 +1060,18 @@ describe('app', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
function executeMiddlewareHandlers(app, urlPath, callback) {
|
function executeMiddlewareHandlers(app, urlPath, callback) {
|
||||||
|
var handlerError;
|
||||||
var server = http.createServer(function(req, res) {
|
var server = http.createServer(function(req, res) {
|
||||||
app.handle(req, res, callback);
|
app.handle(req, res, function(err) {
|
||||||
|
if (err) {
|
||||||
|
handlerError = err;
|
||||||
|
res.statusCode = err.status || err.statusCode || 500;
|
||||||
|
res.end(err.stack || err);
|
||||||
|
} else {
|
||||||
|
res.statusCode = 204;
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (callback === undefined && typeof urlPath === 'function') {
|
if (callback === undefined && typeof urlPath === 'function') {
|
||||||
|
@ -969,6 +1082,6 @@ function executeMiddlewareHandlers(app, urlPath, callback) {
|
||||||
request(server)
|
request(server)
|
||||||
.get(urlPath)
|
.get(urlPath)
|
||||||
.end(function(err) {
|
.end(function(err) {
|
||||||
if (err) return callback(err);
|
callback(handlerError || err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
|
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
describe('PersistedModel.createChangeStream()', function() {
|
describe('PersistedModel.createChangeStream()', function() {
|
||||||
describe('configured to source changes locally', function() {
|
describe('configured to source changes locally', function() {
|
||||||
before(function() {
|
before(function() {
|
||||||
var test = this;
|
var test = this;
|
||||||
var app = loopback({localRegistry: true});
|
var app = loopback({ localRegistry: true });
|
||||||
var ds = app.dataSource('ds', {connector: 'memory'});
|
var ds = app.dataSource('ds', { connector: 'memory' });
|
||||||
this.Score = app.model('Score', {
|
var Score = app.registry.createModel('Score');
|
||||||
|
this.Score = app.model(Score, {
|
||||||
dataSource: 'ds',
|
dataSource: 'ds',
|
||||||
changeDataSource: false // use only local observers
|
changeDataSource: false // use only local observers
|
||||||
});
|
});
|
||||||
|
@ -17,6 +23,7 @@ describe('PersistedModel.createChangeStream()', function() {
|
||||||
changes.on('data', function(change) {
|
changes.on('data', function(change) {
|
||||||
expect(change.type).to.equal('create');
|
expect(change.type).to.equal('create');
|
||||||
changes.destroy();
|
changes.destroy();
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -31,6 +38,7 @@ describe('PersistedModel.createChangeStream()', function() {
|
||||||
changes.on('data', function(change) {
|
changes.on('data', function(change) {
|
||||||
expect(change.type).to.equal('update');
|
expect(change.type).to.equal('update');
|
||||||
changes.destroy();
|
changes.destroy();
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
newScore.updateAttributes({
|
newScore.updateAttributes({
|
||||||
|
@ -47,6 +55,7 @@ describe('PersistedModel.createChangeStream()', function() {
|
||||||
changes.on('data', function(change) {
|
changes.on('data', function(change) {
|
||||||
expect(change.type).to.equal('remove');
|
expect(change.type).to.equal('remove');
|
||||||
changes.destroy();
|
changes.destroy();
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var expect = require('chai').expect;
|
var expect = require('chai').expect;
|
||||||
|
|
||||||
|
@ -28,9 +33,11 @@ describe('Change', function() {
|
||||||
};
|
};
|
||||||
TestModel.create(test.data, function(err, model) {
|
TestModel.create(test.data, function(err, model) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
test.model = model;
|
test.model = model;
|
||||||
test.modelId = model.id;
|
test.modelId = model.id;
|
||||||
test.revisionForModel = Change.revisionForInst(model);
|
test.revisionForModel = Change.revisionForInst(model);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -61,6 +68,7 @@ describe('Change', function() {
|
||||||
var test = this;
|
var test = this;
|
||||||
Change.rectifyModelChanges(this.modelName, [this.modelId], function(err, trackedChanges) {
|
Change.rectifyModelChanges(this.modelName, [this.modelId], function(err, trackedChanges) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -69,6 +77,7 @@ describe('Change', function() {
|
||||||
var test = this;
|
var test = this;
|
||||||
Change.find(function(err, trackedChanges) {
|
Change.find(function(err, trackedChanges) {
|
||||||
assert.equal(trackedChanges[0].modelId, test.modelId.toString());
|
assert.equal(trackedChanges[0].modelId, test.modelId.toString());
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -76,12 +85,47 @@ describe('Change', function() {
|
||||||
it('should only create one change', function(done) {
|
it('should only create one change', function(done) {
|
||||||
Change.count(function(err, count) {
|
Change.count(function(err, count) {
|
||||||
assert.equal(count, 1);
|
assert.equal(count, 1);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Change.rectifyModelChanges - promise variant', function() {
|
||||||
|
describe('using an existing untracked model', function() {
|
||||||
|
beforeEach(function(done) {
|
||||||
|
var test = this;
|
||||||
|
Change.rectifyModelChanges(this.modelName, [this.modelId])
|
||||||
|
.then(function(trackedChanges) {
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create an entry', function(done) {
|
||||||
|
var test = this;
|
||||||
|
Change.find()
|
||||||
|
.then(function(trackedChanges) {
|
||||||
|
assert.equal(trackedChanges[0].modelId, test.modelId.toString());
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only create one change', function(done) {
|
||||||
|
Change.count()
|
||||||
|
.then(function(count) {
|
||||||
|
assert.equal(count, 1);
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Change.findOrCreateChange(modelName, modelId, callback)', function() {
|
describe('Change.findOrCreateChange(modelName, modelId, callback)', function() {
|
||||||
|
|
||||||
describe('when a change doesnt exist', function() {
|
describe('when a change doesnt exist', function() {
|
||||||
|
@ -89,7 +133,9 @@ describe('Change', function() {
|
||||||
var test = this;
|
var test = this;
|
||||||
Change.findOrCreateChange(this.modelName, this.modelId, function(err, result) {
|
Change.findOrCreateChange(this.modelName, this.modelId, function(err, result) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
test.result = result;
|
test.result = result;
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -98,7 +144,33 @@ describe('Change', function() {
|
||||||
var test = this;
|
var test = this;
|
||||||
Change.findById(this.result.id, function(err, change) {
|
Change.findById(this.result.id, function(err, change) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
assert.equal(change.id, test.result.id);
|
assert.equal(change.id, test.result.id);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when a change doesnt exist - promise variant', function() {
|
||||||
|
beforeEach(function(done) {
|
||||||
|
var test = this;
|
||||||
|
Change.findOrCreateChange(this.modelName, this.modelId)
|
||||||
|
.then(function(result) {
|
||||||
|
test.result = result;
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create an entry', function(done) {
|
||||||
|
var test = this;
|
||||||
|
Change.findById(this.result.id, function(err, change) {
|
||||||
|
if (err) return done(err);
|
||||||
|
|
||||||
|
assert.equal(change.id, test.result.id);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -112,6 +184,7 @@ describe('Change', function() {
|
||||||
modelId: test.modelId
|
modelId: test.modelId
|
||||||
}, function(err, change) {
|
}, function(err, change) {
|
||||||
test.existingChange = change;
|
test.existingChange = change;
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -120,7 +193,9 @@ describe('Change', function() {
|
||||||
var test = this;
|
var test = this;
|
||||||
Change.findOrCreateChange(this.modelName, this.modelId, function(err, result) {
|
Change.findOrCreateChange(this.modelName, this.modelId, function(err, result) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
test.result = result;
|
test.result = result;
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -128,6 +203,7 @@ describe('Change', function() {
|
||||||
it('should find the entry', function(done) {
|
it('should find the entry', function(done) {
|
||||||
var test = this;
|
var test = this;
|
||||||
assert.equal(test.existingChange.id, test.result.id);
|
assert.equal(test.existingChange.id, test.result.id);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -143,6 +219,7 @@ describe('Change', function() {
|
||||||
},
|
},
|
||||||
function(err, ch) {
|
function(err, ch) {
|
||||||
change = ch;
|
change = ch;
|
||||||
|
|
||||||
done(err);
|
done(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -151,6 +228,7 @@ describe('Change', function() {
|
||||||
var test = this;
|
var test = this;
|
||||||
change.rectify(function(err, ch) {
|
change.rectify(function(err, ch) {
|
||||||
assert.equal(ch.rev, test.revisionForModel);
|
assert.equal(ch.rev, test.revisionForModel);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -174,6 +252,7 @@ describe('Change', function() {
|
||||||
expect(change.type(), 'type').to.equal('update');
|
expect(change.type(), 'type').to.equal('update');
|
||||||
expect(change.prev, 'prev').to.equal(originalRev);
|
expect(change.prev, 'prev').to.equal(originalRev);
|
||||||
expect(change.rev, 'rev').to.equal(test.revisionForModel);
|
expect(change.rev, 'rev').to.equal(test.revisionForModel);
|
||||||
|
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
], done);
|
], done);
|
||||||
|
@ -185,7 +264,9 @@ describe('Change', function() {
|
||||||
function checkpoint(next) {
|
function checkpoint(next) {
|
||||||
TestModel.checkpoint(function(err, inst) {
|
TestModel.checkpoint(function(err, inst) {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
cp = inst.seq;
|
cp = inst.seq;
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -196,6 +277,7 @@ describe('Change', function() {
|
||||||
model.name += 'updated';
|
model.name += 'updated';
|
||||||
model.save(function(err) {
|
model.save(function(err) {
|
||||||
test.revisionForModel = Change.revisionForInst(model);
|
test.revisionForModel = Change.revisionForInst(model);
|
||||||
|
|
||||||
next(err);
|
next(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -211,14 +293,40 @@ describe('Change', function() {
|
||||||
|
|
||||||
change.rectify(function(err, c) {
|
change.rectify(function(err, c) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(c.rev, 'rev').to.equal(originalRev); // sanity check
|
expect(c.rev, 'rev').to.equal(originalRev); // sanity check
|
||||||
expect(c.checkpoint, 'checkpoint').to.equal(originalCheckpoint);
|
expect(c.checkpoint, 'checkpoint').to.equal(originalCheckpoint);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('change.rectify - promise variant', function() {
|
||||||
|
var change;
|
||||||
|
beforeEach(function(done) {
|
||||||
|
Change.findOrCreateChange(this.modelName, this.modelId)
|
||||||
|
.then(function(ch) {
|
||||||
|
change = ch;
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a new change with the correct revision', function(done) {
|
||||||
|
var test = this;
|
||||||
|
change.rectify()
|
||||||
|
.then(function(ch) {
|
||||||
|
assert.equal(ch.rev, test.revisionForModel);
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('change.currentRevision(callback)', function() {
|
describe('change.currentRevision(callback)', function() {
|
||||||
it('should get the correct revision', function(done) {
|
it('should get the correct revision', function(done) {
|
||||||
var test = this;
|
var test = this;
|
||||||
|
@ -229,11 +337,30 @@ describe('Change', function() {
|
||||||
|
|
||||||
change.currentRevision(function(err, rev) {
|
change.currentRevision(function(err, rev) {
|
||||||
assert.equal(rev, test.revisionForModel);
|
assert.equal(rev, test.revisionForModel);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('change.currentRevision - promise variant', function() {
|
||||||
|
it('should get the correct revision', function(done) {
|
||||||
|
var test = this;
|
||||||
|
var change = new Change({
|
||||||
|
modelName: this.modelName,
|
||||||
|
modelId: this.modelId
|
||||||
|
});
|
||||||
|
|
||||||
|
change.currentRevision()
|
||||||
|
.then(function(rev) {
|
||||||
|
assert.equal(rev, test.revisionForModel);
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Change.hash(str)', function() {
|
describe('Change.hash(str)', function() {
|
||||||
// todo(ritch) test other hashing algorithms
|
// todo(ritch) test other hashing algorithms
|
||||||
it('should hash the given string', function() {
|
it('should hash the given string', function() {
|
||||||
|
@ -368,12 +495,34 @@ describe('Change', function() {
|
||||||
|
|
||||||
Change.diff(this.modelName, 0, remoteChanges, function(err, diff) {
|
Change.diff(this.modelName, 0, remoteChanges, function(err, diff) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
assert.equal(diff.deltas.length, 1);
|
assert.equal(diff.deltas.length, 1);
|
||||||
assert.equal(diff.conflicts.length, 1);
|
assert.equal(diff.conflicts.length, 1);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return delta and conflict lists - promise variant', function(done) {
|
||||||
|
var remoteChanges = [
|
||||||
|
// an update => should result in a delta
|
||||||
|
{rev: 'foo2', prev: 'foo', modelName: this.modelName, modelId: 9, checkpoint: 1},
|
||||||
|
// no change => should not result in a delta / conflict
|
||||||
|
{rev: 'bar', prev: 'bar', modelName: this.modelName, modelId: 10, checkpoint: 1},
|
||||||
|
// a conflict => should result in a conflict
|
||||||
|
{rev: 'bat2', prev: 'bat0', modelName: this.modelName, modelId: 11, checkpoint: 1},
|
||||||
|
];
|
||||||
|
|
||||||
|
Change.diff(this.modelName, 0, remoteChanges)
|
||||||
|
.then(function(diff) {
|
||||||
|
assert.equal(diff.deltas.length, 1);
|
||||||
|
assert.equal(diff.conflicts.length, 1);
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
it('should set "prev" to local revision in non-conflicting delta', function(done) {
|
it('should set "prev" to local revision in non-conflicting delta', function(done) {
|
||||||
var updateRecord = {
|
var updateRecord = {
|
||||||
rev: 'foo-new',
|
rev: 'foo-new',
|
||||||
|
@ -384,6 +533,7 @@ describe('Change', function() {
|
||||||
};
|
};
|
||||||
Change.diff(this.modelName, 0, [updateRecord], function(err, diff) {
|
Change.diff(this.modelName, 0, [updateRecord], function(err, diff) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(diff.conflicts, 'conflicts').to.have.length(0);
|
expect(diff.conflicts, 'conflicts').to.have.length(0);
|
||||||
expect(diff.deltas, 'deltas').to.have.length(1);
|
expect(diff.deltas, 'deltas').to.have.length(1);
|
||||||
var actual = diff.deltas[0].toObject();
|
var actual = diff.deltas[0].toObject();
|
||||||
|
@ -395,6 +545,7 @@ describe('Change', function() {
|
||||||
prev: 'foo', // this is the current local revision
|
prev: 'foo', // this is the current local revision
|
||||||
rev: 'foo-new',
|
rev: 'foo-new',
|
||||||
});
|
});
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -411,6 +562,7 @@ describe('Change', function() {
|
||||||
// with rev=foo CP=1
|
// with rev=foo CP=1
|
||||||
Change.diff(this.modelName, 2, [updateRecord], function(err, diff) {
|
Change.diff(this.modelName, 2, [updateRecord], function(err, diff) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(diff.conflicts, 'conflicts').to.have.length(0);
|
expect(diff.conflicts, 'conflicts').to.have.length(0);
|
||||||
expect(diff.deltas, 'deltas').to.have.length(1);
|
expect(diff.deltas, 'deltas').to.have.length(1);
|
||||||
var actual = diff.deltas[0].toObject();
|
var actual = diff.deltas[0].toObject();
|
||||||
|
@ -422,6 +574,7 @@ describe('Change', function() {
|
||||||
prev: 'foo', // this is the current local revision
|
prev: 'foo', // this is the current local revision
|
||||||
rev: 'foo-new',
|
rev: 'foo-new',
|
||||||
});
|
});
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -437,6 +590,7 @@ describe('Change', function() {
|
||||||
|
|
||||||
Change.diff(this.modelName, 0, [updateRecord], function(err, diff) {
|
Change.diff(this.modelName, 0, [updateRecord], function(err, diff) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
expect(diff.conflicts).to.have.length(0);
|
expect(diff.conflicts).to.have.length(0);
|
||||||
expect(diff.deltas).to.have.length(1);
|
expect(diff.deltas).to.have.length(1);
|
||||||
var actual = diff.deltas[0].toObject();
|
var actual = diff.deltas[0].toObject();
|
||||||
|
@ -448,6 +602,7 @@ describe('Change', function() {
|
||||||
prev: null, // this is the current local revision
|
prev: null, // this is the current local revision
|
||||||
rev: 'new-rev',
|
rev: 'new-rev',
|
||||||
});
|
});
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,28 +1,98 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var loopback = require('../');
|
var loopback = require('../');
|
||||||
|
var expect = require('chai').expect;
|
||||||
|
|
||||||
// create a unique Checkpoint model
|
|
||||||
var Checkpoint = loopback.Checkpoint.extend('TestCheckpoint');
|
var Checkpoint = loopback.Checkpoint.extend('TestCheckpoint');
|
||||||
|
|
||||||
var memory = loopback.createDataSource({
|
|
||||||
connector: loopback.Memory
|
|
||||||
});
|
|
||||||
Checkpoint.attachTo(memory);
|
|
||||||
|
|
||||||
describe('Checkpoint', function() {
|
describe('Checkpoint', function() {
|
||||||
describe('current()', function() {
|
describe('bumpLastSeq() and current()', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
var memory = loopback.createDataSource({
|
||||||
|
connector: loopback.Memory
|
||||||
|
});
|
||||||
|
Checkpoint.attachTo(memory);
|
||||||
|
});
|
||||||
|
|
||||||
it('returns the highest `seq` value', function(done) {
|
it('returns the highest `seq` value', function(done) {
|
||||||
async.series([
|
async.series([
|
||||||
Checkpoint.create.bind(Checkpoint),
|
Checkpoint.bumpLastSeq.bind(Checkpoint),
|
||||||
Checkpoint.create.bind(Checkpoint),
|
Checkpoint.bumpLastSeq.bind(Checkpoint),
|
||||||
function(next) {
|
function(next) {
|
||||||
Checkpoint.current(function(err, seq) {
|
Checkpoint.current(function(err, seq) {
|
||||||
if (err) next(err);
|
if (err) next(err);
|
||||||
|
|
||||||
expect(seq).to.equal(3);
|
expect(seq).to.equal(3);
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
], done);
|
], done);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should be no race condition for current() when calling in parallel', function(done) {
|
||||||
|
async.parallel([
|
||||||
|
function(next) { Checkpoint.current(next); },
|
||||||
|
function(next) { Checkpoint.current(next); }
|
||||||
|
], function(err, list) {
|
||||||
|
if (err) return done(err);
|
||||||
|
|
||||||
|
Checkpoint.find(function(err, data) {
|
||||||
|
if (err) return done(err);
|
||||||
|
|
||||||
|
expect(data).to.have.length(1);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be no race condition for bumpLastSeq() when calling in parallel', function(done) {
|
||||||
|
async.parallel([
|
||||||
|
function(next) { Checkpoint.bumpLastSeq(next); },
|
||||||
|
function(next) { Checkpoint.bumpLastSeq(next); }
|
||||||
|
], function(err, list) {
|
||||||
|
if (err) return done(err);
|
||||||
|
|
||||||
|
Checkpoint.find(function(err, data) {
|
||||||
|
if (err) return done(err);
|
||||||
|
// The invariant "we have at most 1 checkpoint instance" is preserved
|
||||||
|
// even when multiple calls are made in parallel
|
||||||
|
expect(data).to.have.length(1);
|
||||||
|
// There is a race condition here, we could end up with both 2 or 3 as the "seq".
|
||||||
|
// The current implementation of the memory connector always yields 2 though.
|
||||||
|
expect(data[0].seq).to.equal(2);
|
||||||
|
// In this particular case, since the new last seq is always 2, both results
|
||||||
|
// should be 2.
|
||||||
|
expect(list.map(function(it) {return it.seq;}))
|
||||||
|
.to.eql([2, 2]);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Checkpoint.current() for non existing checkpoint should initialize checkpoint', function(done) {
|
||||||
|
Checkpoint.current(function(err, seq) {
|
||||||
|
expect(seq).to.equal(1);
|
||||||
|
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('bumpLastSeq() works when singleton instance does not exists yet', function(done) {
|
||||||
|
Checkpoint.bumpLastSeq(function(err, cp) {
|
||||||
|
// We expect `seq` to be 2 since `checkpoint` does not exist and
|
||||||
|
// `bumpLastSeq` for the first time not only initializes it to one,
|
||||||
|
// but also increments the initialized value by one.
|
||||||
|
expect(cp.seq).to.equal(2);
|
||||||
|
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,454 @@
|
||||||
|
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var expect = require('chai').expect;
|
||||||
|
var loopback = require('..');
|
||||||
|
var supertest = require('supertest-as-promised')(require('bluebird'));
|
||||||
|
|
||||||
|
describe('OptionsFromRemotingContext', function() {
|
||||||
|
var app, request, accessToken, userId, Product, actualOptions;
|
||||||
|
|
||||||
|
beforeEach(setupAppAndRequest);
|
||||||
|
beforeEach(resetActualOptions);
|
||||||
|
|
||||||
|
context('when making updates via REST', function() {
|
||||||
|
beforeEach(observeOptionsBeforeSave);
|
||||||
|
|
||||||
|
it('injects options to create()', function() {
|
||||||
|
return request.post('/products')
|
||||||
|
.send({name: 'Pen'})
|
||||||
|
.expect(200)
|
||||||
|
.then(expectInjectedOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to patchOrCreate()', function() {
|
||||||
|
return request.patch('/products')
|
||||||
|
.send({id: 1, name: 'Pen'})
|
||||||
|
.expect(200)
|
||||||
|
.then(expectInjectedOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to replaceOrCreate()', function() {
|
||||||
|
return request.put('/products')
|
||||||
|
.send({id: 1, name: 'Pen'})
|
||||||
|
.expect(200)
|
||||||
|
.then(expectInjectedOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to patchOrCreateWithWhere()', function() {
|
||||||
|
return request.post('/products/upsertWithWhere?where[name]=Pen')
|
||||||
|
.send({name: 'Pencil'})
|
||||||
|
.expect(200)
|
||||||
|
.then(expectInjectedOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to replaceById()', function() {
|
||||||
|
return Product.create({id: 1, name: 'Pen'})
|
||||||
|
.then(function(p) {
|
||||||
|
return request.put('/products/1')
|
||||||
|
.send({name: 'Pencil'})
|
||||||
|
.expect(200);
|
||||||
|
})
|
||||||
|
.then(expectInjectedOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to prototype.patchAttributes()', function() {
|
||||||
|
return Product.create({id: 1, name: 'Pen'})
|
||||||
|
.then(function(p) {
|
||||||
|
return request.patch('/products/1')
|
||||||
|
.send({name: 'Pencil'})
|
||||||
|
.expect(200);
|
||||||
|
})
|
||||||
|
.then(expectInjectedOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to updateAll()', function() {
|
||||||
|
return request.post('/products/update?where[name]=Pen')
|
||||||
|
.send({name: 'Pencil'})
|
||||||
|
.expect(200)
|
||||||
|
.then(expectInjectedOptions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('when deleting via REST', function() {
|
||||||
|
beforeEach(observeOptionsBeforeDelete);
|
||||||
|
|
||||||
|
it('injects options to deleteById()', function() {
|
||||||
|
return Product.create({id: 1, name: 'Pen'})
|
||||||
|
.then(function(p) {
|
||||||
|
return request.delete('/products/1').expect(200);
|
||||||
|
})
|
||||||
|
.then(expectInjectedOptions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('when querying via REST', function() {
|
||||||
|
beforeEach(observeOptionsOnAccess);
|
||||||
|
beforeEach(givenProductId1);
|
||||||
|
|
||||||
|
it('injects options to find()', function() {
|
||||||
|
return request.get('/products').expect(200)
|
||||||
|
.then(expectInjectedOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to findById()', function() {
|
||||||
|
return request.get('/products/1').expect(200)
|
||||||
|
.then(expectInjectedOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to findOne()', function() {
|
||||||
|
return request.get('/products/findOne?where[id]=1').expect(200)
|
||||||
|
.then(expectInjectedOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to exists()', function() {
|
||||||
|
return request.head('/products/1').expect(200)
|
||||||
|
.then(expectInjectedOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to count()', function() {
|
||||||
|
return request.get('/products/count').expect(200)
|
||||||
|
.then(expectInjectedOptions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('when invoking prototype methods', function() {
|
||||||
|
beforeEach(observeOptionsOnAccess);
|
||||||
|
beforeEach(givenProductId1);
|
||||||
|
|
||||||
|
it('injects options to sharedCtor', function() {
|
||||||
|
Product.prototype.dummy = function(cb) { cb(); };
|
||||||
|
Product.remoteMethod('dummy', {isStatic: false});
|
||||||
|
return request.post('/products/1/dummy').expect(204)
|
||||||
|
.then(expectInjectedOptions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('honours injectOptionsFromRemoteContext in sharedCtor', function() {
|
||||||
|
var settings = {
|
||||||
|
forceId: false,
|
||||||
|
injectOptionsFromRemoteContext: false,
|
||||||
|
};
|
||||||
|
var TestModel = app.registry.createModel('TestModel', {}, settings);
|
||||||
|
app.model(TestModel, {dataSource: 'db'});
|
||||||
|
|
||||||
|
TestModel.prototype.dummy = function(cb) { cb(); };
|
||||||
|
TestModel.remoteMethod('dummy', {isStatic: false});
|
||||||
|
|
||||||
|
observeOptionsOnAccess(TestModel);
|
||||||
|
|
||||||
|
return TestModel.create({id: 1})
|
||||||
|
.then(function() {
|
||||||
|
return request.post('/TestModels/1/dummy').expect(204);
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
expect(actualOptions).to.eql({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Catch: because relations methods are defined on "modelFrom",
|
||||||
|
// they will invoke createOptionsFromRemotingContext on "modelFrom" too,
|
||||||
|
// despite the fact that under the hood a method on "modelTo" is called.
|
||||||
|
|
||||||
|
context('hasManyThrough', function() {
|
||||||
|
var Category, ThroughModel;
|
||||||
|
|
||||||
|
beforeEach(givenCategoryHasManyProductsThroughAnotherModel);
|
||||||
|
beforeEach(givenCategoryAndProduct);
|
||||||
|
|
||||||
|
it('injects options to findById', function() {
|
||||||
|
observeOptionsOnAccess(Product);
|
||||||
|
return request.get('/categories/1/products/1').expect(200)
|
||||||
|
.then(expectOptionsInjectedFromCategory);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to destroyById', function() {
|
||||||
|
observeOptionsBeforeDelete(Product);
|
||||||
|
return request.del('/categories/1/products/1').expect(204)
|
||||||
|
.then(expectOptionsInjectedFromCategory);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to updateById', function() {
|
||||||
|
observeOptionsBeforeSave(Product);
|
||||||
|
return request.put('/categories/1/products/1')
|
||||||
|
.send({description: 'a description'})
|
||||||
|
.expect(200)
|
||||||
|
.then(expectInjectedOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
context('through-model operations', function() {
|
||||||
|
it('injects options to link', function() {
|
||||||
|
observeOptionsBeforeSave(ThroughModel);
|
||||||
|
return Product.create({id: 2, name: 'Car2'})
|
||||||
|
.then(function() {
|
||||||
|
return request.put('/categories/1/products/rel/2')
|
||||||
|
.send({description: 'a description'})
|
||||||
|
.expect(200);
|
||||||
|
})
|
||||||
|
.then(expectOptionsInjectedFromCategory);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to unlink', function() {
|
||||||
|
observeOptionsBeforeDelete(ThroughModel);
|
||||||
|
return request.del('/categories/1/products/rel/1').expect(204)
|
||||||
|
.then(expectOptionsInjectedFromCategory);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to exists', function() {
|
||||||
|
observeOptionsOnAccess(ThroughModel);
|
||||||
|
return request.head('/categories/1/products/rel/1').expect(200)
|
||||||
|
.then(expectOptionsInjectedFromCategory);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('scope operations', function() {
|
||||||
|
it('injects options to get', function() {
|
||||||
|
observeOptionsOnAccess(Product);
|
||||||
|
return request.get('/categories/1/products').expect(200)
|
||||||
|
.then(expectOptionsInjectedFromCategory);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to create', function() {
|
||||||
|
observeOptionsBeforeSave(Product);
|
||||||
|
return request.post('/categories/1/products')
|
||||||
|
.send({name: 'Pen'})
|
||||||
|
.expect(200)
|
||||||
|
.then(expectOptionsInjectedFromCategory);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to delete', function() {
|
||||||
|
observeOptionsBeforeDelete(ThroughModel);
|
||||||
|
return request.del('/categories/1/products').expect(204)
|
||||||
|
.then(expectOptionsInjectedFromCategory);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to count', function() {
|
||||||
|
observeOptionsOnAccess(ThroughModel);
|
||||||
|
return request.get('/categories/1/products/count').expect(200)
|
||||||
|
.then(expectOptionsInjectedFromCategory);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function givenCategoryHasManyProductsThroughAnotherModel() {
|
||||||
|
var settings = {
|
||||||
|
forceId: false,
|
||||||
|
replaceOnPUT: true,
|
||||||
|
injectOptionsFromRemoteContext: true,
|
||||||
|
};
|
||||||
|
Category = app.registry.createModel(
|
||||||
|
'Category',
|
||||||
|
{name: String},
|
||||||
|
settings);
|
||||||
|
|
||||||
|
app.model(Category, {dataSource: 'db'});
|
||||||
|
// This is a shortcut for creating CategoryProduct "through" model
|
||||||
|
Category.hasAndBelongsToMany(Product);
|
||||||
|
|
||||||
|
Category.createOptionsFromRemotingContext = function(ctx) {
|
||||||
|
return {injectedFrom: 'Category'};
|
||||||
|
};
|
||||||
|
|
||||||
|
ThroughModel = app.registry.getModel('CategoryProduct');
|
||||||
|
}
|
||||||
|
|
||||||
|
function givenCategoryAndProduct() {
|
||||||
|
return Category.create({id: 1, name: 'First Category'})
|
||||||
|
.then(function(cat) {
|
||||||
|
return cat.products.create({id: 1, name: 'Pen'});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectOptionsInjectedFromCategory() {
|
||||||
|
expect(actualOptions).to.have.property('injectedFrom', 'Category');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
context('hasOne', function() {
|
||||||
|
var Category;
|
||||||
|
|
||||||
|
beforeEach(givenCategoryHasOneProduct);
|
||||||
|
beforeEach(givenCategoryId1);
|
||||||
|
|
||||||
|
it('injects options to get', function() {
|
||||||
|
observeOptionsOnAccess(Product);
|
||||||
|
return givenProductInCategory1()
|
||||||
|
.then(function() {
|
||||||
|
return request.get('/categories/1/product').expect(200);
|
||||||
|
})
|
||||||
|
.then(expectOptionsInjectedFromCategory);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to create', function() {
|
||||||
|
observeOptionsBeforeSave(Product);
|
||||||
|
return request.post('/categories/1/product')
|
||||||
|
.send({name: 'Pen'})
|
||||||
|
.expect(200)
|
||||||
|
.then(expectOptionsInjectedFromCategory);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to update', function() {
|
||||||
|
return givenProductInCategory1()
|
||||||
|
.then(function() {
|
||||||
|
observeOptionsBeforeSave(Product);
|
||||||
|
return request.put('/categories/1/product')
|
||||||
|
.send({description: 'a description'})
|
||||||
|
.expect(200);
|
||||||
|
})
|
||||||
|
.then(expectInjectedOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects options to destroy', function() {
|
||||||
|
observeOptionsBeforeDelete(Product);
|
||||||
|
return givenProductInCategory1()
|
||||||
|
.then(function() {
|
||||||
|
return request.del('/categories/1/product').expect(204);
|
||||||
|
})
|
||||||
|
.then(expectOptionsInjectedFromCategory);
|
||||||
|
});
|
||||||
|
|
||||||
|
function givenCategoryHasOneProduct() {
|
||||||
|
var settings = {
|
||||||
|
forceId: false,
|
||||||
|
replaceOnPUT: true,
|
||||||
|
injectOptionsFromRemoteContext: true,
|
||||||
|
};
|
||||||
|
Category = app.registry.createModel(
|
||||||
|
'Category',
|
||||||
|
{name: String},
|
||||||
|
settings);
|
||||||
|
|
||||||
|
app.model(Category, {dataSource: 'db'});
|
||||||
|
Category.hasOne(Product);
|
||||||
|
|
||||||
|
Category.createOptionsFromRemotingContext = function(ctx) {
|
||||||
|
return {injectedFrom: 'Category'};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function givenCategoryId1() {
|
||||||
|
return Category.create({id: 1, name: 'First Category'});
|
||||||
|
}
|
||||||
|
|
||||||
|
function givenProductInCategory1() {
|
||||||
|
return Product.create({id: 1, name: 'Pen', categoryId: 1});
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectOptionsInjectedFromCategory() {
|
||||||
|
expect(actualOptions).to.have.property('injectedFrom', 'Category');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
context('belongsTo', function() {
|
||||||
|
var Category;
|
||||||
|
|
||||||
|
beforeEach(givenCategoryBelongsToProduct);
|
||||||
|
|
||||||
|
it('injects options to get', function() {
|
||||||
|
observeOptionsOnAccess(Product);
|
||||||
|
return Product.create({id: 1, name: 'Pen'})
|
||||||
|
.then(function() {
|
||||||
|
return Category.create({id: 1, name: 'a name', productId: 1});
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
return request.get('/categories/1/product').expect(200);
|
||||||
|
})
|
||||||
|
.then(expectOptionsInjectedFromCategory);
|
||||||
|
});
|
||||||
|
|
||||||
|
function givenCategoryBelongsToProduct() {
|
||||||
|
var settings = {
|
||||||
|
forceId: false,
|
||||||
|
replaceOnPUT: true,
|
||||||
|
injectOptionsFromRemoteContext: true,
|
||||||
|
};
|
||||||
|
Category = app.registry.createModel(
|
||||||
|
'Category',
|
||||||
|
{name: String},
|
||||||
|
settings);
|
||||||
|
|
||||||
|
app.model(Category, {dataSource: 'db'});
|
||||||
|
Category.belongsTo(Product);
|
||||||
|
|
||||||
|
Category.createOptionsFromRemotingContext = function(ctx) {
|
||||||
|
return {injectedFrom: 'Category'};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function givenCategoryId1() {
|
||||||
|
return Category.create({id: 1, name: 'First Category'});
|
||||||
|
}
|
||||||
|
|
||||||
|
function givenProductInCategory1() {
|
||||||
|
return Product.create({id: 1, name: 'Pen', categoryId: 1});
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectOptionsInjectedFromCategory() {
|
||||||
|
expect(actualOptions).to.have.property('injectedFrom', 'Category');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupAppAndRequest() {
|
||||||
|
app = loopback({localRegistry: true});
|
||||||
|
app.dataSource('db', {connector: 'memory'});
|
||||||
|
|
||||||
|
var settings = {
|
||||||
|
forceId: false,
|
||||||
|
replaceOnPUT: true,
|
||||||
|
injectOptionsFromRemoteContext: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
Product = app.registry.createModel(
|
||||||
|
'Product',
|
||||||
|
{name: String},
|
||||||
|
settings);
|
||||||
|
|
||||||
|
Product.createOptionsFromRemotingContext = function(ctx) {
|
||||||
|
return {injectedFrom: 'Product'};
|
||||||
|
};
|
||||||
|
|
||||||
|
app.model(Product, {dataSource: 'db'});
|
||||||
|
|
||||||
|
app.use(loopback.rest());
|
||||||
|
request = supertest(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetActualOptions() {
|
||||||
|
actualOptions = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function observeOptionsBeforeSave() {
|
||||||
|
var Model = arguments[0] || Product;
|
||||||
|
Model.observe('before save', function(ctx, next) {
|
||||||
|
actualOptions = ctx.options;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function observeOptionsBeforeDelete() {
|
||||||
|
var Model = arguments[0] || Product;
|
||||||
|
Model.observe('before delete', function(ctx, next) {
|
||||||
|
actualOptions = ctx.options;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function observeOptionsOnAccess() {
|
||||||
|
var Model = arguments[0] || Product;
|
||||||
|
Model.observe('access', function(ctx, next) {
|
||||||
|
actualOptions = ctx.options;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function givenProductId1() {
|
||||||
|
return Product.create({id: 1, name: 'Pen'});
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectInjectedOptions(name) {
|
||||||
|
expect(actualOptions).to.have.property('injectedFrom');
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
describe('DataSource', function() {
|
describe('DataSource', function() {
|
||||||
var memory;
|
var memory;
|
||||||
|
|
||||||
|
@ -17,6 +22,7 @@ describe('DataSource', function() {
|
||||||
assert.isFunc(Color, 'findOne');
|
assert.isFunc(Color, 'findOne');
|
||||||
assert.isFunc(Color, 'create');
|
assert.isFunc(Color, 'create');
|
||||||
assert.isFunc(Color, 'updateOrCreate');
|
assert.isFunc(Color, 'updateOrCreate');
|
||||||
|
assert.isFunc(Color, 'upsertWithWhere');
|
||||||
assert.isFunc(Color, 'upsert');
|
assert.isFunc(Color, 'upsert');
|
||||||
assert.isFunc(Color, 'findOrCreate');
|
assert.isFunc(Color, 'findOrCreate');
|
||||||
assert.isFunc(Color, 'exists');
|
assert.isFunc(Color, 'exists');
|
||||||
|
@ -78,6 +84,7 @@ describe('DataSource', function() {
|
||||||
existsAndShared('_forDB', false);
|
existsAndShared('_forDB', false);
|
||||||
existsAndShared('create', true);
|
existsAndShared('create', true);
|
||||||
existsAndShared('updateOrCreate', true);
|
existsAndShared('updateOrCreate', true);
|
||||||
|
existsAndShared('upsertWithWhere', true);
|
||||||
existsAndShared('upsert', true);
|
existsAndShared('upsert', true);
|
||||||
existsAndShared('findOrCreate', false);
|
existsAndShared('findOrCreate', false);
|
||||||
existsAndShared('exists', true);
|
existsAndShared('exists', true);
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var loopback = require('../../');
|
var loopback = require('../../');
|
||||||
var models = require('../fixtures/e2e/models');
|
var models = require('../fixtures/e2e/models');
|
||||||
|
@ -19,7 +24,9 @@ describe('RemoteConnector', function() {
|
||||||
foo: 'bar'
|
foo: 'bar'
|
||||||
}, function(err, inst) {
|
}, function(err, inst) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
assert(inst.id);
|
assert(inst.id);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -30,7 +37,9 @@ describe('RemoteConnector', function() {
|
||||||
});
|
});
|
||||||
m.save(function(err, data) {
|
m.save(function(err, data) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
assert(data.foo === 'bar');
|
assert(data.foo === 'bar');
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var loopback = require('../../');
|
var loopback = require('../../');
|
||||||
var models = require('../fixtures/e2e/models');
|
var models = require('../fixtures/e2e/models');
|
||||||
|
@ -27,8 +32,10 @@ describe('Replication', function() {
|
||||||
}, function(err, created) {
|
}, function(err, created) {
|
||||||
LocalTestModel.replicate(0, TestModel, function() {
|
LocalTestModel.replicate(0, TestModel, function() {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
TestModel.findOne({n: RANDOM}, function(err, found) {
|
|
||||||
|
TestModel.findOne({ n: RANDOM }, function(err, found) {
|
||||||
assert.equal(created.id, found.id);
|
assert.equal(created.id, found.id);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var loopback = require('../');
|
var loopback = require('../');
|
||||||
var MyEmail;
|
var MyEmail;
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
@ -33,6 +38,14 @@ describe('Email connector', function() {
|
||||||
assert(connector.transportForName('smtp'));
|
assert(connector.transportForName('smtp'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set up a aliased transport for SMTP' , function() {
|
||||||
|
var connector = new MailConnector({transport:
|
||||||
|
{type: 'smtp', service: 'ses-us-east-1', alias: 'ses-smtp'}
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(connector.transportForName('ses-smtp'));
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Email and SMTP', function() {
|
describe('Email and SMTP', function() {
|
||||||
|
@ -61,6 +74,7 @@ describe('Email and SMTP', function() {
|
||||||
assert(mail.response);
|
assert(mail.response);
|
||||||
assert(mail.envelope);
|
assert(mail.envelope);
|
||||||
assert(mail.messageId);
|
assert(mail.messageId);
|
||||||
|
|
||||||
done(err);
|
done(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -78,6 +92,7 @@ describe('Email and SMTP', function() {
|
||||||
assert(mail.response);
|
assert(mail.response);
|
||||||
assert(mail.envelope);
|
assert(mail.envelope);
|
||||||
assert(mail.messageId);
|
assert(mail.messageId);
|
||||||
|
|
||||||
done(err);
|
done(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
|
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var loopback = require('../');
|
var loopback = require('../');
|
||||||
var app;
|
var app;
|
||||||
var assert = require('assert');
|
var expect = require('chai').expect;
|
||||||
var request = require('supertest');
|
var request = require('supertest');
|
||||||
|
|
||||||
describe('loopback.errorHandler(options)', function() {
|
describe('loopback.errorHandler(options)', function() {
|
||||||
|
@ -16,7 +21,9 @@ describe('loopback.errorHandler(options)', function() {
|
||||||
request(app)
|
request(app)
|
||||||
.get('/url-does-not-exist')
|
.get('/url-does-not-exist')
|
||||||
.end(function(err, res) {
|
.end(function(err, res) {
|
||||||
assert.ok(res.error.text.match(/<ul id="stacktrace"><li> at raiseUrlNotFoundError/));
|
expect(res.error.text).to.match(
|
||||||
|
/<ul id="stacktrace"><li>( )+at raiseUrlNotFoundError/);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -32,7 +39,8 @@ describe('loopback.errorHandler(options)', function() {
|
||||||
request(app)
|
request(app)
|
||||||
.get('/url-does-not-exist')
|
.get('/url-does-not-exist')
|
||||||
.end(function(err, res) {
|
.end(function(err, res) {
|
||||||
assert.ok(res.error.text.match(/<ul id="stacktrace"><\/ul>/));
|
expect(res.error.text).to.match(/<ul id="stacktrace"><\/ul>/);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -41,15 +49,24 @@ describe('loopback.errorHandler(options)', function() {
|
||||||
//arrange
|
//arrange
|
||||||
var app = loopback();
|
var app = loopback();
|
||||||
app.use(loopback.urlNotFound());
|
app.use(loopback.urlNotFound());
|
||||||
app.use(loopback.errorHandler({ includeStack: false, log: customLogger }));
|
|
||||||
|
var errorLogged;
|
||||||
|
app.use(loopback.errorHandler({
|
||||||
|
includeStack: false,
|
||||||
|
log: function customLogger(err, str, req) {
|
||||||
|
errorLogged = err;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
//act
|
//act
|
||||||
request(app).get('/url-does-not-exist').end();
|
request(app).get('/url-does-not-exist').end(function(err) {
|
||||||
|
if (err) return done(err);
|
||||||
|
//assert
|
||||||
|
expect(errorLogged)
|
||||||
|
.to.have.property('message', 'Cannot GET /url-does-not-exist');
|
||||||
|
|
||||||
//assert
|
|
||||||
function customLogger(err, str, req) {
|
|
||||||
assert.ok(err.message === 'Cannot GET /url-does-not-exist');
|
|
||||||
done();
|
done();
|
||||||
}
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "account",
|
"name": "accountWithReplaceOnPUTtrue",
|
||||||
|
"plural": "accounts-replacing",
|
||||||
"relations": {
|
"relations": {
|
||||||
"transactions": {
|
"transactions": {
|
||||||
"model": "transaction",
|
"model": "transaction",
|
||||||
|
@ -38,5 +39,6 @@
|
||||||
"principalId": "$dummy"
|
"principalId": "$dummy"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"properties": {}
|
"properties": {},
|
||||||
|
"replaceOnPUT": true
|
||||||
}
|
}
|
44
test/fixtures/access-control/common/models/accountWithReplaceOnPUTfalse.json
vendored
Normal file
44
test/fixtures/access-control/common/models/accountWithReplaceOnPUTfalse.json
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"name": "accountWithReplaceOnPUTfalse",
|
||||||
|
"plural": "accounts-updating",
|
||||||
|
"relations": {
|
||||||
|
"transactions": {
|
||||||
|
"model": "transaction",
|
||||||
|
"type": "hasMany"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"model": "user",
|
||||||
|
"type": "belongsTo",
|
||||||
|
"foreignKey": "userId"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"acls": [
|
||||||
|
{
|
||||||
|
"accessType": "*",
|
||||||
|
"permission": "DENY",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accessType": "*",
|
||||||
|
"permission": "ALLOW",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$owner"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"permission": "DENY",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$owner",
|
||||||
|
"property": "deleteById"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accessType": "*",
|
||||||
|
"permission": "DENY",
|
||||||
|
"property": "find",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$dummy"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {},
|
||||||
|
"replaceOnPUT": false
|
||||||
|
}
|
|
@ -22,6 +22,12 @@
|
||||||
"permission": "ALLOW",
|
"permission": "ALLOW",
|
||||||
"principalType": "ROLE",
|
"principalType": "ROLE",
|
||||||
"principalId": "$everyone"
|
"principalId": "$everyone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accessType": "WRITE",
|
||||||
|
"permission": "ALLOW",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$dynamic-role"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"properties": {}
|
"properties": {}
|
||||||
|
|
|
@ -19,5 +19,6 @@
|
||||||
"principalType": "ROLE",
|
"principalType": "ROLE",
|
||||||
"principalId": "$everyone"
|
"principalId": "$everyone"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"replaceOnPUT": false
|
||||||
}
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"port": 3000,
|
"port": 3000,
|
||||||
"host": "0.0.0.0",
|
"host": "0.0.0.0",
|
||||||
|
"logoutSessionsOnSensitiveChanges": true,
|
||||||
"legacyExplorer": false
|
"legacyExplorer": false
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"sources": [
|
"sources": [
|
||||||
"../common/models",
|
"../common/models",
|
||||||
"./models"
|
"./models",
|
||||||
|
"../../../../common/models"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ACL": {
|
"ACL": {
|
||||||
|
@ -33,7 +34,11 @@
|
||||||
"public": true,
|
"public": true,
|
||||||
"dataSource": "db"
|
"dataSource": "db"
|
||||||
},
|
},
|
||||||
"account": {
|
"accountWithReplaceOnPUTtrue": {
|
||||||
|
"public": true,
|
||||||
|
"dataSource": "db"
|
||||||
|
},
|
||||||
|
"accountWithReplaceOnPUTfalse": {
|
||||||
"public": true,
|
"public": true,
|
||||||
"dataSource": "db"
|
"dataSource": "db"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
|
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var loopback = require('../../../..');
|
var loopback = require('../../../..');
|
||||||
var boot = require('loopback-boot');
|
var boot = require('loopback-boot');
|
||||||
var app = module.exports = loopback();
|
var app = module.exports = loopback({
|
||||||
|
localRegistry: true,
|
||||||
|
loadBuiltinModels: true
|
||||||
|
});
|
||||||
|
|
||||||
boot(app, __dirname);
|
boot(app, __dirname);
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var loopback = require('../../../../index');
|
var loopback = require('../../../../index');
|
||||||
var PersistedModel = loopback.PersistedModel;
|
var PersistedModel = loopback.PersistedModel;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
|
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var loopback = require('../../../../index');
|
var loopback = require('../../../../index');
|
||||||
var app = module.exports = loopback();
|
var app = module.exports = loopback({ localRegistry: true });
|
||||||
var models = require('./models');
|
var models = require('./models');
|
||||||
var TestModel = models.TestModel;
|
var TestModel = models.TestModel;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
module.exports = function(Todo) {
|
module.exports = function(Todo) {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var boot = require('loopback-boot');
|
var boot = require('loopback-boot');
|
||||||
var loopback = require('../../../../../index');
|
var loopback = require('../../../../../index');
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue