expressa 2.0.18 → 2.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.circleci/config.yml +21 -21
- package/.eslintrc.json +29 -29
- package/LICENSE +21 -21
- package/README.md +137 -137
- package/auth/index.js +76 -76
- package/auth/jwt.js +31 -31
- package/controllers/collections.js +260 -260
- package/controllers/install.js +105 -105
- package/controllers/status.js +43 -43
- package/controllers/users.js +52 -52
- package/cypress/fixtures/example.json +4 -4
- package/cypress/integration/0_install_spec.js +8 -8
- package/cypress/integration/custom_collections.js +30 -28
- package/cypress/integration/home.js +9 -9
- package/cypress/integration/settings.js +34 -34
- package/cypress/integration/users.js +61 -61
- package/cypress/plugins/index.js +17 -17
- package/cypress/support/commands.js +69 -54
- package/cypress/support/index.js +20 -20
- package/cypress/support/util.js +10 -10
- package/cypress.config.js +18 -0
- package/cypress.json +4 -4
- package/db/cached.js +40 -40
- package/db/file.js +112 -112
- package/db/memory.js +79 -79
- package/db/mongo.js +86 -86
- package/db/postgres.js +93 -93
- package/doc/authentication.md +38 -38
- package/doc/automatic-fields.md +12 -12
- package/doc/blogexample.md +16 -16
- package/doc/custom-endpoints.md +20 -20
- package/doc/database.md +23 -23
- package/doc/development.md +33 -33
- package/doc/listeners.md +103 -103
- package/doc/modules.md +8 -8
- package/doc/permissions.md +14 -14
- package/doc/relationships.md +65 -65
- package/doc/testing.md +47 -47
- package/doc/uploading-files.md +53 -53
- package/index.js +297 -297
- package/listeners.js +65 -65
- package/listeners_collection_permissions.js +29 -29
- package/listeners_users.js +96 -96
- package/listeners_validation.js +43 -43
- package/middleware/logging.js +30 -30
- package/middleware/permissions.js +41 -41
- package/modules/access_keys/access_keys.js +32 -32
- package/modules/admin/.babelrc +19 -19
- package/modules/admin/.editorconfig +14 -14
- package/modules/admin/.eslintignore +5 -5
- package/modules/admin/.eslintrc.js +197 -197
- package/modules/admin/.postcssrc.js +10 -10
- package/modules/admin/.travis.yml +5 -5
- package/modules/admin/LICENSE +21 -21
- package/modules/admin/README.md +88 -88
- package/modules/admin/build/build.js +46 -45
- package/modules/admin/build/check-versions.js +64 -64
- package/modules/admin/build/utils.js +109 -108
- package/modules/admin/build/vue-loader.conf.js +5 -5
- package/modules/admin/build/webpack.base.conf.js +112 -112
- package/modules/admin/build/webpack.dev.conf.js +95 -95
- package/modules/admin/build/webpack.prod.conf.js +112 -112
- package/modules/admin/config/dev.env.js +8 -8
- package/modules/admin/config/index.js +86 -86
- package/modules/admin/config/prod.env.js +5 -5
- package/modules/admin/dist/css/197.7a90435e.css +7 -0
- package/modules/admin/dist/css/197.7a90435e.css.gz +0 -0
- package/modules/admin/dist/css/430.8c9b0482.css +8 -0
- package/modules/admin/dist/css/430.8c9b0482.css.gz +0 -0
- package/modules/admin/dist/css/451.c6646d20.css +1 -0
- package/modules/admin/dist/css/451.c6646d20.css.gz +0 -0
- package/modules/admin/dist/css/566.7a90435e.css +7 -0
- package/modules/admin/dist/css/566.7a90435e.css.gz +0 -0
- package/modules/admin/dist/css/569.f7d5c7d5.css +37 -0
- package/modules/admin/dist/css/569.f7d5c7d5.css.gz +0 -0
- package/modules/admin/dist/css/572.74d8e965.css +34 -0
- package/modules/admin/dist/css/572.74d8e965.css.gz +0 -0
- package/modules/admin/dist/css/651.e946bda7.css +6 -0
- package/modules/admin/dist/css/651.e946bda7.css.gz +0 -0
- package/modules/admin/dist/css/750.4086915d.css +2 -0
- package/modules/admin/dist/css/750.4086915d.css.gz +0 -0
- package/modules/admin/dist/css/950.1fd9d57c.css +7 -0
- package/modules/admin/dist/css/950.1fd9d57c.css.gz +0 -0
- package/modules/admin/dist/css/app.eba5f5e2.css +592 -0
- package/modules/admin/dist/css/app.eba5f5e2.css.gz +0 -0
- package/modules/admin/dist/index.html +1 -1
- package/modules/admin/dist/static/fonts/element-icons.313f7da.woff +0 -0
- package/modules/admin/dist/static/fonts/element-icons.4520188.ttf +0 -0
- package/modules/admin/dist/static/js/197.e511d91e.js +1 -0
- package/modules/admin/dist/static/js/197.e511d91e.js.gz +0 -0
- package/modules/admin/dist/static/js/430.b3941f30.js +1 -0
- package/modules/admin/dist/static/js/430.b3941f30.js.gz +0 -0
- package/modules/admin/dist/static/js/451.645974a8.js +1 -0
- package/modules/admin/dist/static/js/451.645974a8.js.gz +0 -0
- package/modules/admin/dist/static/js/460.962d5bc0.js +1 -0
- package/modules/admin/dist/static/js/460.962d5bc0.js.gz +0 -0
- package/modules/admin/dist/static/js/550.f27feb23.js +1 -0
- package/modules/admin/dist/static/js/550.f27feb23.js.gz +0 -0
- package/modules/admin/dist/static/js/556.1e08866a.js +1 -0
- package/modules/admin/dist/static/js/556.1e08866a.js.gz +0 -0
- package/modules/admin/dist/static/js/569.0fecfbee.js +1 -0
- package/modules/admin/dist/static/js/569.0fecfbee.js.gz +0 -0
- package/modules/admin/dist/static/js/572.13392e1d.js +1 -0
- package/modules/admin/dist/static/js/572.13392e1d.js.gz +0 -0
- package/modules/admin/dist/static/js/603.7b34a650.js +1 -0
- package/modules/admin/dist/static/js/603.7b34a650.js.gz +0 -0
- package/modules/admin/dist/static/js/651.564b24b6.js +1 -0
- package/modules/admin/dist/static/js/651.564b24b6.js.gz +0 -0
- package/modules/admin/dist/static/js/653.02ec1098.js +1 -0
- package/modules/admin/dist/static/js/653.02ec1098.js.gz +0 -0
- package/modules/admin/dist/static/js/750.50c950da.js +1 -0
- package/modules/admin/dist/static/js/750.50c950da.js.gz +0 -0
- package/modules/admin/dist/static/js/950.d4ff0a18.js +1 -0
- package/modules/admin/dist/static/js/950.d4ff0a18.js.gz +0 -0
- package/modules/admin/dist/static/js/app.ea86ae11.js +2 -0
- package/modules/admin/dist/static/js/{chunk-libs.06c38828.js.LICENSE.txt → app.ea86ae11.js.LICENSE.txt} +15 -18
- package/modules/admin/dist/static/js/app.ea86ae11.js.LICENSE.txt.gz +0 -0
- package/modules/admin/dist/static/js/app.ea86ae11.js.gz +0 -0
- package/modules/admin/index.html +16 -16
- package/modules/admin/package.json +101 -101
- package/modules/admin/src/App.vue +11 -11
- package/modules/admin/src/api/login.js +26 -26
- package/modules/admin/src/api/table.js +9 -9
- package/modules/admin/src/components/Breadcrumb/index.vue +79 -79
- package/modules/admin/src/components/Hamburger/index.vue +62 -62
- package/modules/admin/src/components/JSONEditor.vue +180 -180
- package/modules/admin/src/components/SvgIcon/index.vue +43 -43
- package/modules/admin/src/icons/index.js +9 -9
- package/modules/admin/src/icons/svgo.yml +22 -22
- package/modules/admin/src/main.js +42 -42
- package/modules/admin/src/permission.js +49 -49
- package/modules/admin/src/router/index.js +200 -200
- package/modules/admin/src/store/getters.js +11 -11
- package/modules/admin/src/store/index.js +17 -17
- package/modules/admin/src/store/modules/app.js +43 -43
- package/modules/admin/src/store/modules/user.js +104 -104
- package/modules/admin/src/styles/element-ui.scss +29 -29
- package/modules/admin/src/styles/index.scss +78 -78
- package/modules/admin/src/styles/mixin.scss +27 -27
- package/modules/admin/src/styles/sidebar.scss +133 -133
- package/modules/admin/src/styles/transition.scss +46 -46
- package/modules/admin/src/styles/variables.scss +4 -4
- package/modules/admin/src/utils/auth.js +15 -15
- package/modules/admin/src/utils/index.js +74 -74
- package/modules/admin/src/utils/request.js +48 -48
- package/modules/admin/src/utils/validate.js +31 -31
- package/modules/admin/src/views/404.vue +235 -235
- package/modules/admin/src/views/Dev.vue +33 -33
- package/modules/admin/src/views/EditDocument.vue +121 -116
- package/modules/admin/src/views/Endpoints.vue +279 -279
- package/modules/admin/src/views/Home.vue +84 -84
- package/modules/admin/src/views/Install.vue +79 -79
- package/modules/admin/src/views/ListDocuments.vue +72 -72
- package/modules/admin/src/views/ListDocuments2.vue +467 -462
- package/modules/admin/src/views/ManageListeners.vue +67 -67
- package/modules/admin/src/views/ManageMiddleware.vue +43 -43
- package/modules/admin/src/views/ManagePermissions.vue +75 -75
- package/modules/admin/src/views/ViewRequest.vue +77 -77
- package/modules/admin/src/views/form/index.vue +91 -91
- package/modules/admin/src/views/layout/Layout.vue +69 -69
- package/modules/admin/src/views/layout/components/AppMain.vue +41 -41
- package/modules/admin/src/views/layout/components/Navbar.vue +97 -97
- package/modules/admin/src/views/layout/components/Sidebar/Item.vue +29 -29
- package/modules/admin/src/views/layout/components/Sidebar/Link.vue +39 -39
- package/modules/admin/src/views/layout/components/Sidebar/SidebarItem.vue +103 -103
- package/modules/admin/src/views/layout/components/Sidebar/index.vue +62 -62
- package/modules/admin/src/views/layout/components/index.js +3 -3
- package/modules/admin/src/views/layout/mixin/ResizeHandler.js +41 -41
- package/modules/admin/src/views/login/index.vue +195 -195
- package/modules/admin/src/views/tree/index.vue +77 -77
- package/modules/collections/collections.js +39 -39
- package/modules/core/core.js +197 -197
- package/modules/logging/logging.js +88 -88
- package/modules/permissions/permissions.js +106 -106
- package/package.json +59 -59
- package/scripts/expressa +12 -12
- package/scripts/run_cypress_tests.sh +36 -6
- package/scripts/run_db_tests.sh +8 -8
- package/test/0-install.js +96 -96
- package/test/collections.js +431 -431
- package/test/collections.querying.js +288 -288
- package/test/db.js +258 -258
- package/test/db.strings.js +73 -73
- package/test/db.updating.js +144 -144
- package/test/logging.js +29 -29
- package/test/test.js +93 -93
- package/test/testserver.js +17 -17
- package/test/testutils.js +136 -136
- package/test/users.js +425 -425
- package/util.js +522 -522
- package/modules/admin/dist/index.html.gz +0 -0
- package/modules/admin/dist/static/css/app.9dd57eea.css +0 -1
- package/modules/admin/dist/static/css/app.9dd57eea.css.gz +0 -0
- package/modules/admin/dist/static/css/chunk-5061.ce0e4bdb.css +0 -1
- package/modules/admin/dist/static/css/chunk-5061.ce0e4bdb.css.gz +0 -0
- package/modules/admin/dist/static/css/chunk-5b97.7f9cb53e.css +0 -1
- package/modules/admin/dist/static/css/chunk-5b97.7f9cb53e.css.gz +0 -0
- package/modules/admin/dist/static/css/chunk-621e.b826be8d.css +0 -1
- package/modules/admin/dist/static/css/chunk-621e.b826be8d.css.gz +0 -0
- package/modules/admin/dist/static/css/chunk-656b.9c828b00.css +0 -1
- package/modules/admin/dist/static/css/chunk-656b.9c828b00.css.gz +0 -0
- package/modules/admin/dist/static/css/chunk-7291.13514e45.css +0 -0
- package/modules/admin/dist/static/css/chunk-7291.13514e45.css.gz +0 -0
- package/modules/admin/dist/static/css/chunk-7f29.9c828b00.css +0 -1
- package/modules/admin/dist/static/css/chunk-7f29.9c828b00.css.gz +0 -0
- package/modules/admin/dist/static/css/chunk-9a95.c647a2ef.css +0 -1
- package/modules/admin/dist/static/css/chunk-9a95.c647a2ef.css.gz +0 -0
- package/modules/admin/dist/static/css/chunk-b423.5f239333.css +0 -0
- package/modules/admin/dist/static/css/chunk-b423.5f239333.css.gz +0 -0
- package/modules/admin/dist/static/css/chunk-bd5f.aefa49d7.css +0 -1
- package/modules/admin/dist/static/css/chunk-bd5f.aefa49d7.css.gz +0 -0
- package/modules/admin/dist/static/css/chunk-e374.025d58bb.css +0 -1
- package/modules/admin/dist/static/css/chunk-e374.025d58bb.css.gz +0 -0
- package/modules/admin/dist/static/css/chunk-elementUI.3d004d9f.css +0 -1
- package/modules/admin/dist/static/css/chunk-elementUI.3d004d9f.css.gz +0 -0
- package/modules/admin/dist/static/css/chunk-f075.3a895a1e.css +0 -1
- package/modules/admin/dist/static/css/chunk-f075.3a895a1e.css.gz +0 -0
- package/modules/admin/dist/static/css/chunk-libs.327ad89e.css +0 -14
- package/modules/admin/dist/static/css/chunk-libs.327ad89e.css.gz +0 -0
- package/modules/admin/dist/static/fonts/element-icons.6f0a763.ttf +0 -0
- package/modules/admin/dist/static/js/app.e9461593.js +0 -1
- package/modules/admin/dist/static/js/app.e9461593.js.gz +0 -0
- package/modules/admin/dist/static/js/chunk-09d8.561880e0.js +0 -2
- package/modules/admin/dist/static/js/chunk-09d8.561880e0.js.LICENSE.txt +0 -13
- package/modules/admin/dist/static/js/chunk-09d8.561880e0.js.LICENSE.txt.gz +0 -0
- package/modules/admin/dist/static/js/chunk-09d8.561880e0.js.gz +0 -0
- package/modules/admin/dist/static/js/chunk-5061.83e6ccfa.js +0 -1
- package/modules/admin/dist/static/js/chunk-5061.83e6ccfa.js.gz +0 -0
- package/modules/admin/dist/static/js/chunk-5b97.ac360086.js +0 -1
- package/modules/admin/dist/static/js/chunk-5b97.ac360086.js.gz +0 -0
- package/modules/admin/dist/static/js/chunk-5e17.a2b69611.js +0 -1
- package/modules/admin/dist/static/js/chunk-5e17.a2b69611.js.gz +0 -0
- package/modules/admin/dist/static/js/chunk-621e.ccf42393.js +0 -1
- package/modules/admin/dist/static/js/chunk-621e.ccf42393.js.gz +0 -0
- package/modules/admin/dist/static/js/chunk-656b.9d5b03c0.js +0 -1
- package/modules/admin/dist/static/js/chunk-656b.9d5b03c0.js.gz +0 -0
- package/modules/admin/dist/static/js/chunk-7291.590524e9.js +0 -1
- package/modules/admin/dist/static/js/chunk-7291.590524e9.js.gz +0 -0
- package/modules/admin/dist/static/js/chunk-7f29.6ffc142e.js +0 -1
- package/modules/admin/dist/static/js/chunk-7f29.6ffc142e.js.gz +0 -0
- package/modules/admin/dist/static/js/chunk-9a95.bdd76583.js +0 -1
- package/modules/admin/dist/static/js/chunk-9a95.bdd76583.js.gz +0 -0
- package/modules/admin/dist/static/js/chunk-b423.387c504c.js +0 -1
- package/modules/admin/dist/static/js/chunk-b423.387c504c.js.gz +0 -0
- package/modules/admin/dist/static/js/chunk-bd5f.e9509f10.js +0 -1
- package/modules/admin/dist/static/js/chunk-bd5f.e9509f10.js.gz +0 -0
- package/modules/admin/dist/static/js/chunk-e374.7c87a0dd.js +0 -1
- package/modules/admin/dist/static/js/chunk-e374.7c87a0dd.js.gz +0 -0
- package/modules/admin/dist/static/js/chunk-e7c3.e5df8ceb.js +0 -1
- package/modules/admin/dist/static/js/chunk-e7c3.e5df8ceb.js.gz +0 -0
- package/modules/admin/dist/static/js/chunk-elementUI.b0c99c08.js +0 -1
- package/modules/admin/dist/static/js/chunk-elementUI.b0c99c08.js.gz +0 -0
- package/modules/admin/dist/static/js/chunk-f075.315ceec1.js +0 -1
- package/modules/admin/dist/static/js/chunk-f075.315ceec1.js.gz +0 -0
- package/modules/admin/dist/static/js/chunk-libs.06c38828.js +0 -2
- package/modules/admin/dist/static/js/chunk-libs.06c38828.js.LICENSE.txt.gz +0 -0
- package/modules/admin/dist/static/js/chunk-libs.06c38828.js.gz +0 -0
- /package/modules/admin/dist/static/img/{404.a57b6f3.png → 404.4cf6930.png} +0 -0
package/doc/listeners.md
CHANGED
|
@@ -1,103 +1,103 @@
|
|
|
1
|
-
## When to use listeners
|
|
2
|
-
|
|
3
|
-
* decorate endpoint responses with relational data
|
|
4
|
-
* fine grained role-permissions: hide certain properties based on role
|
|
5
|
-
* save bandwidth: hide certain properties like file/image-data
|
|
6
|
-
* do additional actions like sending emails when content is created or changed
|
|
7
|
-
* add fields whose values are computed from others
|
|
8
|
-
* custom validation like ensuring a user doesn't create too many documents of a collection type.
|
|
9
|
-
|
|
10
|
-
> TIP: to prevent having listenercode all over the place, use [expressa-folder](https://npmjs.org/package/expressa-folder) to automatically map listeners to files.
|
|
11
|
-
|
|
12
|
-
## Modifying behavior using listeners
|
|
13
|
-
Use `expressa.addListener(eventTypes, priority, callback)`
|
|
14
|
-
|
|
15
|
-
`eventTypes` is a string or array of the event types listed below e.g. 'get' or ['put', 'post']
|
|
16
|
-
|
|
17
|
-
`priority` is a number which determines the order of callback execution. Listeners with lower priority are executed first. If you don't care about order just use 0.
|
|
18
|
-
|
|
19
|
-
`callback` is a function like the following:
|
|
20
|
-
|
|
21
|
-
`function(req, collection, doc)` where
|
|
22
|
-
|
|
23
|
-
> `req` is the request
|
|
24
|
-
> `collection` is a string of the name of the collection acted upon
|
|
25
|
-
> `doc` is the relevant document.
|
|
26
|
-
|
|
27
|
-
### Before Event Types
|
|
28
|
-
|
|
29
|
-
Using these listeners you can control whether an action is allowed. Return `true` to allow the action. Return `false` (or an object with a custom message, as shown in the example below) to deny the action. Don't return anything or `undefined` to let other listeners decide. If all listeners return undefined the action is allowed. Order is significant because it's the first defined return value that controls whether the action is allowed.
|
|
30
|
-
|
|
31
|
-
A promise can be returned so that asynchronous logic can be perfomed. In this case, it will wait for the promise to fulfill and use the resolved value.
|
|
32
|
-
|
|
33
|
-
* `get` - called once for each document being retrieved. Returning false in a request involving multiple documents (e.g. all or find) will simply remove that document from the list.
|
|
34
|
-
* `post` - called before creating a new document
|
|
35
|
-
* `put` - called before changing a document
|
|
36
|
-
* `delete` - called before deleting a document. Note: only the _id of the document is available in the callback. If the full document is needed you will need to load it yourself.
|
|
37
|
-
|
|
38
|
-
For example to prevent modifying old posts you could add the following listener:
|
|
39
|
-
|
|
40
|
-
expressa.addListener('put', 10, function(req, collection, doc) {
|
|
41
|
-
if (collection == 'listing') {
|
|
42
|
-
if (Date.now() - new Date(doc.meta.created) > (1000*60*60*24)) { //older than a day
|
|
43
|
-
return {
|
|
44
|
-
code: 403,
|
|
45
|
-
message: 'You cannot modify posts older than a day'
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
### After Event Types
|
|
52
|
-
|
|
53
|
-
With these, the value returned from the listener is ignored.
|
|
54
|
-
|
|
55
|
-
* `changed` - called after a put or post has succeeded
|
|
56
|
-
* `deleted` - called after a successful deletion
|
|
57
|
-
|
|
58
|
-
## Debugging
|
|
59
|
-
|
|
60
|
-
Run `DEBUG=expressa node --use-strict app.js` or `DEBUG=* --use-strict node app.js` to see what's going on in your app
|
|
61
|
-
|
|
62
|
-
## Async wrapper example
|
|
63
|
-
|
|
64
|
-
var request = require('request');
|
|
65
|
-
expressa.addListener('put', -10, function(req, collection, doc) {
|
|
66
|
-
if (collection == 'users') {
|
|
67
|
-
var key = your google maps api key;
|
|
68
|
-
var loc = doc.address;
|
|
69
|
-
return new Promise(function(resolve, reject) {
|
|
70
|
-
request('https://maps.googleapis.com/maps/api/geocode/json?address=' + loc + '&key=' + key, function(err, response, body) {
|
|
71
|
-
if (err) {
|
|
72
|
-
console.error('failed to geolocate address');
|
|
73
|
-
return reject();
|
|
74
|
-
}
|
|
75
|
-
var data = JSON.parse(body);
|
|
76
|
-
if (!data.results[0]) {
|
|
77
|
-
console.error('Geolocation of user address had empty response.');
|
|
78
|
-
return reject();
|
|
79
|
-
}
|
|
80
|
-
doc.coordinates = data.results[0].geometry.location;
|
|
81
|
-
resolve();
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
## Manual wrappers
|
|
87
|
-
|
|
88
|
-
Sometimes you may need to wrap an expressa endpoint so you have full control before and after (like recovering from expressa errors, or other middleware). In those cases we can wrap an expressa-point like so:
|
|
89
|
-
|
|
90
|
-
app.post('/api/myendpoint', require('./lib/listener/myendpoint/post.js')(expressa) )
|
|
91
|
-
app.use('/api', expressa )
|
|
92
|
-
|
|
93
|
-
> NOTE: put it above the expressa init
|
|
94
|
-
|
|
95
|
-
And `lib/listener/myendpoint/post.js` like so:
|
|
96
|
-
|
|
97
|
-
module.exports = function(expressa) {
|
|
98
|
-
return function(req, res, next) {
|
|
99
|
-
// do stuff before expressa handler
|
|
100
|
-
next() // run expressa handler
|
|
101
|
-
// do stuff after expressa handler
|
|
102
|
-
}
|
|
103
|
-
}
|
|
1
|
+
## When to use listeners
|
|
2
|
+
|
|
3
|
+
* decorate endpoint responses with relational data
|
|
4
|
+
* fine grained role-permissions: hide certain properties based on role
|
|
5
|
+
* save bandwidth: hide certain properties like file/image-data
|
|
6
|
+
* do additional actions like sending emails when content is created or changed
|
|
7
|
+
* add fields whose values are computed from others
|
|
8
|
+
* custom validation like ensuring a user doesn't create too many documents of a collection type.
|
|
9
|
+
|
|
10
|
+
> TIP: to prevent having listenercode all over the place, use [expressa-folder](https://npmjs.org/package/expressa-folder) to automatically map listeners to files.
|
|
11
|
+
|
|
12
|
+
## Modifying behavior using listeners
|
|
13
|
+
Use `expressa.addListener(eventTypes, priority, callback)`
|
|
14
|
+
|
|
15
|
+
`eventTypes` is a string or array of the event types listed below e.g. 'get' or ['put', 'post']
|
|
16
|
+
|
|
17
|
+
`priority` is a number which determines the order of callback execution. Listeners with lower priority are executed first. If you don't care about order just use 0.
|
|
18
|
+
|
|
19
|
+
`callback` is a function like the following:
|
|
20
|
+
|
|
21
|
+
`function(req, collection, doc)` where
|
|
22
|
+
|
|
23
|
+
> `req` is the request
|
|
24
|
+
> `collection` is a string of the name of the collection acted upon
|
|
25
|
+
> `doc` is the relevant document.
|
|
26
|
+
|
|
27
|
+
### Before Event Types
|
|
28
|
+
|
|
29
|
+
Using these listeners you can control whether an action is allowed. Return `true` to allow the action. Return `false` (or an object with a custom message, as shown in the example below) to deny the action. Don't return anything or `undefined` to let other listeners decide. If all listeners return undefined the action is allowed. Order is significant because it's the first defined return value that controls whether the action is allowed.
|
|
30
|
+
|
|
31
|
+
A promise can be returned so that asynchronous logic can be perfomed. In this case, it will wait for the promise to fulfill and use the resolved value.
|
|
32
|
+
|
|
33
|
+
* `get` - called once for each document being retrieved. Returning false in a request involving multiple documents (e.g. all or find) will simply remove that document from the list.
|
|
34
|
+
* `post` - called before creating a new document
|
|
35
|
+
* `put` - called before changing a document
|
|
36
|
+
* `delete` - called before deleting a document. Note: only the _id of the document is available in the callback. If the full document is needed you will need to load it yourself.
|
|
37
|
+
|
|
38
|
+
For example to prevent modifying old posts you could add the following listener:
|
|
39
|
+
|
|
40
|
+
expressa.addListener('put', 10, function(req, collection, doc) {
|
|
41
|
+
if (collection == 'listing') {
|
|
42
|
+
if (Date.now() - new Date(doc.meta.created) > (1000*60*60*24)) { //older than a day
|
|
43
|
+
return {
|
|
44
|
+
code: 403,
|
|
45
|
+
message: 'You cannot modify posts older than a day'
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
### After Event Types
|
|
52
|
+
|
|
53
|
+
With these, the value returned from the listener is ignored.
|
|
54
|
+
|
|
55
|
+
* `changed` - called after a put or post has succeeded
|
|
56
|
+
* `deleted` - called after a successful deletion
|
|
57
|
+
|
|
58
|
+
## Debugging
|
|
59
|
+
|
|
60
|
+
Run `DEBUG=expressa node --use-strict app.js` or `DEBUG=* --use-strict node app.js` to see what's going on in your app
|
|
61
|
+
|
|
62
|
+
## Async wrapper example
|
|
63
|
+
|
|
64
|
+
var request = require('request');
|
|
65
|
+
expressa.addListener('put', -10, function(req, collection, doc) {
|
|
66
|
+
if (collection == 'users') {
|
|
67
|
+
var key = your google maps api key;
|
|
68
|
+
var loc = doc.address;
|
|
69
|
+
return new Promise(function(resolve, reject) {
|
|
70
|
+
request('https://maps.googleapis.com/maps/api/geocode/json?address=' + loc + '&key=' + key, function(err, response, body) {
|
|
71
|
+
if (err) {
|
|
72
|
+
console.error('failed to geolocate address');
|
|
73
|
+
return reject();
|
|
74
|
+
}
|
|
75
|
+
var data = JSON.parse(body);
|
|
76
|
+
if (!data.results[0]) {
|
|
77
|
+
console.error('Geolocation of user address had empty response.');
|
|
78
|
+
return reject();
|
|
79
|
+
}
|
|
80
|
+
doc.coordinates = data.results[0].geometry.location;
|
|
81
|
+
resolve();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
## Manual wrappers
|
|
87
|
+
|
|
88
|
+
Sometimes you may need to wrap an expressa endpoint so you have full control before and after (like recovering from expressa errors, or other middleware). In those cases we can wrap an expressa-point like so:
|
|
89
|
+
|
|
90
|
+
app.post('/api/myendpoint', require('./lib/listener/myendpoint/post.js')(expressa) )
|
|
91
|
+
app.use('/api', expressa )
|
|
92
|
+
|
|
93
|
+
> NOTE: put it above the expressa init
|
|
94
|
+
|
|
95
|
+
And `lib/listener/myendpoint/post.js` like so:
|
|
96
|
+
|
|
97
|
+
module.exports = function(expressa) {
|
|
98
|
+
return function(req, res, next) {
|
|
99
|
+
// do stuff before expressa handler
|
|
100
|
+
next() // run expressa handler
|
|
101
|
+
// do stuff after expressa handler
|
|
102
|
+
}
|
|
103
|
+
}
|
package/doc/modules.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
Modules are defined in modules/<module name>/<module name>.js and can export the following fields:
|
|
2
|
-
|
|
3
|
-
| Field Name | Type | Purpose |
|
|
4
|
-
|----------------|----------|-----------------------------------------------------------|
|
|
5
|
-
| settingsSchema | object | Properties to be added to the settings JSON schema object |
|
|
6
|
-
| collections | object[] | List of collections to be added on install |
|
|
7
|
-
| install | function | Function to be called once, on install |
|
|
8
|
-
| permissions | string[] | List of permissions |
|
|
1
|
+
Modules are defined in modules/<module name>/<module name>.js and can export the following fields:
|
|
2
|
+
|
|
3
|
+
| Field Name | Type | Purpose |
|
|
4
|
+
|----------------|----------|-----------------------------------------------------------|
|
|
5
|
+
| settingsSchema | object | Properties to be added to the settings JSON schema object |
|
|
6
|
+
| collections | object[] | List of collections to be added on install |
|
|
7
|
+
| install | function | Function to be called once, on install |
|
|
8
|
+
| permissions | string[] | List of permissions |
|
|
9
9
|
| init | function | Function to be called on application startup |
|
package/doc/permissions.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
## Permissions
|
|
2
|
-
|
|
3
|
-
Expressa lets you easily manage CRUD permissions for each type of action on collections using admin interface. Users can have one or more roles and each role is given ability to create, read, update, delete, etc.
|
|
4
|
-
|
|
5
|
-
By default you start with the following roles (but you can add your own):
|
|
6
|
-
|
|
7
|
-
* **Admin**: this is the "super user" role that lets you manage all data.
|
|
8
|
-
* **Authenticated**: any signed-in user
|
|
9
|
-
* **Anonymous**: permissions given to all requests that come from non-signed in users
|
|
10
|
-
|
|
11
|
-
You can declare collections as having documents that are owned. This lets you manage permissions for editing, reading, and deleting a user's own documents.
|
|
12
|
-
|
|
13
|
-
Here's a screenshot example of the admin UI for managing permissions on a "post" collection.
|
|
14
|
-
|
|
1
|
+
## Permissions
|
|
2
|
+
|
|
3
|
+
Expressa lets you easily manage CRUD permissions for each type of action on collections using admin interface. Users can have one or more roles and each role is given ability to create, read, update, delete, etc.
|
|
4
|
+
|
|
5
|
+
By default you start with the following roles (but you can add your own):
|
|
6
|
+
|
|
7
|
+
* **Admin**: this is the "super user" role that lets you manage all data.
|
|
8
|
+
* **Authenticated**: any signed-in user
|
|
9
|
+
* **Anonymous**: permissions given to all requests that come from non-signed in users
|
|
10
|
+
|
|
11
|
+
You can declare collections as having documents that are owned. This lets you manage permissions for editing, reading, and deleting a user's own documents.
|
|
12
|
+
|
|
13
|
+
Here's a screenshot example of the admin UI for managing permissions on a "post" collection.
|
|
14
|
+
|
|
15
15
|

|
package/doc/relationships.md
CHANGED
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
## Relationships & References
|
|
2
|
-
|
|
3
|
-
Let's suppose we want to extend our `/data/collection/users.json`-collection, by specifying which user belongs to another user:
|
|
4
|
-
|
|
5
|
-
{
|
|
6
|
-
"properties":{
|
|
7
|
-
"other_user":{
|
|
8
|
-
"title": "Has relationship with",
|
|
9
|
-
"type": "string",·
|
|
10
|
-
"links": [
|
|
11
|
-
{
|
|
12
|
-
"rel": "» show profile",
|
|
13
|
-
"href": "/admin/#/edit/users/{{self}}",
|
|
14
|
-
"class": "comment-link open-in-modal primary-text"
|
|
15
|
-
}
|
|
16
|
-
]
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
...
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
Done, now expressa-admin will show a textfield in which we can write the userid:
|
|
25
|
-
|
|
26
|
-

|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
## Dynamically generated Relationships
|
|
30
|
-
|
|
31
|
-
To make things extra convenient, lets generate a dropdown of all users:
|
|
32
|
-
|
|
33
|
-
expressa.addListener('get', -101, function(req,collection,doc){
|
|
34
|
-
if( req.url.match(/\/users\/schema$/) != null ) {
|
|
35
|
-
// add user reference to schema
|
|
36
|
-
var schema = {
|
|
37
|
-
"enumSource": [{
|
|
38
|
-
// A watched field source
|
|
39
|
-
source: [],
|
|
40
|
-
title: "{{item.title}}",
|
|
41
|
-
value: "{{item.id}}"
|
|
42
|
-
}]
|
|
43
|
-
}
|
|
44
|
-
return new Promise( function(resolve, reject ){
|
|
45
|
-
expressa.db.users.find()
|
|
46
|
-
.then( function(users){
|
|
47
|
-
users.map( function(u){·
|
|
48
|
-
schema.enumSource[0].source.push({title: u.firstname+" "+u.lastname+", "+u.email,id:u._id})·
|
|
49
|
-
})
|
|
50
|
-
doc.properties.id_parent.enumSource = schema.enumSource
|
|
51
|
-
return resolve({"code":200, "message":doc})
|
|
52
|
-
})
|
|
53
|
-
.catch(reject)
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
Done, now we'll have a nice dropdown to select our relationship:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-

|
|
62
|
-
|
|
63
|
-
> For more info on enumSource see the [json-editor docs](https://github.com/jdorn/json-editor)
|
|
64
|
-
|
|
65
|
-
> TIP: use [expressa-folder](https://npmjs.org/package/expressa-folder) to automatically map listeners to files.
|
|
1
|
+
## Relationships & References
|
|
2
|
+
|
|
3
|
+
Let's suppose we want to extend our `/data/collection/users.json`-collection, by specifying which user belongs to another user:
|
|
4
|
+
|
|
5
|
+
{
|
|
6
|
+
"properties":{
|
|
7
|
+
"other_user":{
|
|
8
|
+
"title": "Has relationship with",
|
|
9
|
+
"type": "string",·
|
|
10
|
+
"links": [
|
|
11
|
+
{
|
|
12
|
+
"rel": "» show profile",
|
|
13
|
+
"href": "/admin/#/edit/users/{{self}}",
|
|
14
|
+
"class": "comment-link open-in-modal primary-text"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
Done, now expressa-admin will show a textfield in which we can write the userid:
|
|
25
|
+
|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Dynamically generated Relationships
|
|
30
|
+
|
|
31
|
+
To make things extra convenient, lets generate a dropdown of all users:
|
|
32
|
+
|
|
33
|
+
expressa.addListener('get', -101, function(req,collection,doc){
|
|
34
|
+
if( req.url.match(/\/users\/schema$/) != null ) {
|
|
35
|
+
// add user reference to schema
|
|
36
|
+
var schema = {
|
|
37
|
+
"enumSource": [{
|
|
38
|
+
// A watched field source
|
|
39
|
+
source: [],
|
|
40
|
+
title: "{{item.title}}",
|
|
41
|
+
value: "{{item.id}}"
|
|
42
|
+
}]
|
|
43
|
+
}
|
|
44
|
+
return new Promise( function(resolve, reject ){
|
|
45
|
+
expressa.db.users.find()
|
|
46
|
+
.then( function(users){
|
|
47
|
+
users.map( function(u){·
|
|
48
|
+
schema.enumSource[0].source.push({title: u.firstname+" "+u.lastname+", "+u.email,id:u._id})·
|
|
49
|
+
})
|
|
50
|
+
doc.properties.id_parent.enumSource = schema.enumSource
|
|
51
|
+
return resolve({"code":200, "message":doc})
|
|
52
|
+
})
|
|
53
|
+
.catch(reject)
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
Done, now we'll have a nice dropdown to select our relationship:
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+

|
|
62
|
+
|
|
63
|
+
> For more info on enumSource see the [json-editor docs](https://github.com/jdorn/json-editor)
|
|
64
|
+
|
|
65
|
+
> TIP: use [expressa-folder](https://npmjs.org/package/expressa-folder) to automatically map listeners to files.
|
package/doc/testing.md
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
## Testing your express(a) app
|
|
2
|
-
|
|
3
|
-
Testing can be done in many ways, with or without a test framework.
|
|
4
|
-
Here's just an easy testframework-agnostic, linux way to test your app.
|
|
5
|
-
|
|
6
|
-
## package.json
|
|
7
|
-
|
|
8
|
-
Add the 'test' command in the `script`-section of your package.json:
|
|
9
|
-
|
|
10
|
-
"scripts":{
|
|
11
|
-
"test": "for i in test/*; do [ -x $i ] && [ ! -d $i ] && { printf '\n<▶ '$i'\n\n' && ./$i || exit 1; }; done;"
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
## app.js
|
|
15
|
-
|
|
16
|
-
In your expressa main-file (`app.js` e.g.), search for `app.listen()`, and modify it like this:
|
|
17
|
-
|
|
18
|
-
module.exports = {
|
|
19
|
-
expressa:expressa,
|
|
20
|
-
express:express,
|
|
21
|
-
app:app,
|
|
22
|
-
server: app.listen(port, function(){
|
|
23
|
-
console.log("listening on "+host)
|
|
24
|
-
if( module.exports.onServerReady ) setTimeout(module.exports.onServerReady, 500 ) // fire tests if any
|
|
25
|
-
})
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
## test/tests/mytest.js
|
|
29
|
-
|
|
30
|
-
#!/usr/bin/env node
|
|
31
|
-
var app = require('./../../app.js')
|
|
32
|
-
var expressa = app.expressa
|
|
33
|
-
|
|
34
|
-
var run = function(done){
|
|
35
|
-
// do mocha stuff here etc and call done()
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
app.onServerReady = run.bind(this, function(){
|
|
39
|
-
app.server.close()
|
|
40
|
-
process.exit(0)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
Dont forget to `chmod 755 test/tests/mytest.js` in the console
|
|
44
|
-
|
|
45
|
-
## That's it!
|
|
46
|
-
|
|
47
|
-
Now just run `npm test` or `./test/tests/mytest.js` and your test(s) will run
|
|
1
|
+
## Testing your express(a) app
|
|
2
|
+
|
|
3
|
+
Testing can be done in many ways, with or without a test framework.
|
|
4
|
+
Here's just an easy testframework-agnostic, linux way to test your app.
|
|
5
|
+
|
|
6
|
+
## package.json
|
|
7
|
+
|
|
8
|
+
Add the 'test' command in the `script`-section of your package.json:
|
|
9
|
+
|
|
10
|
+
"scripts":{
|
|
11
|
+
"test": "for i in test/*; do [ -x $i ] && [ ! -d $i ] && { printf '\n<▶ '$i'\n\n' && ./$i || exit 1; }; done;"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
## app.js
|
|
15
|
+
|
|
16
|
+
In your expressa main-file (`app.js` e.g.), search for `app.listen()`, and modify it like this:
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
expressa:expressa,
|
|
20
|
+
express:express,
|
|
21
|
+
app:app,
|
|
22
|
+
server: app.listen(port, function(){
|
|
23
|
+
console.log("listening on "+host)
|
|
24
|
+
if( module.exports.onServerReady ) setTimeout(module.exports.onServerReady, 500 ) // fire tests if any
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
## test/tests/mytest.js
|
|
29
|
+
|
|
30
|
+
#!/usr/bin/env node
|
|
31
|
+
var app = require('./../../app.js')
|
|
32
|
+
var expressa = app.expressa
|
|
33
|
+
|
|
34
|
+
var run = function(done){
|
|
35
|
+
// do mocha stuff here etc and call done()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
app.onServerReady = run.bind(this, function(){
|
|
39
|
+
app.server.close()
|
|
40
|
+
process.exit(0)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
Dont forget to `chmod 755 test/tests/mytest.js` in the console
|
|
44
|
+
|
|
45
|
+
## That's it!
|
|
46
|
+
|
|
47
|
+
Now just run `npm test` or `./test/tests/mytest.js` and your test(s) will run
|
package/doc/uploading-files.md
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
## Uploading files
|
|
2
|
-
|
|
3
|
-
Lets say you want your blog posts to contain images.
|
|
4
|
-
Here's how you add an image file-upload in `data/collection/post.json`:
|
|
5
|
-
|
|
6
|
-
"properties":{
|
|
7
|
-
|
|
8
|
-
...
|
|
9
|
-
|
|
10
|
-
"picture": {
|
|
11
|
-
"title": "Picture",
|
|
12
|
-
"type": "string",
|
|
13
|
-
"media": {
|
|
14
|
-
"binaryEncoding": "base64",
|
|
15
|
-
"type": "image/png"
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
> NOTE: you probably want to create an expressa listener which hides the 'picture'-property to save bandwidth. On top of that, you probably want automatic thumbnails using something like [this expressa middleware](https://gist.github.com/coderofsalvation/d3c67fbdf4639dfae4d292a37434097c)
|
|
20
|
-
|
|
21
|
-

|
|
22
|
-
|
|
23
|
-
"properties":{
|
|
24
|
-
|
|
25
|
-
...
|
|
26
|
-
|
|
27
|
-
"file": {
|
|
28
|
-
"type": "string",
|
|
29
|
-
"format": "file",
|
|
30
|
-
"title": "File"
|
|
31
|
-
"links": [
|
|
32
|
-
{
|
|
33
|
-
"rel": "Download File",
|
|
34
|
-
"href": "/custom_endpoint/{{self}}",
|
|
35
|
-
// Can also set `download` to a string as per the HTML5 spec
|
|
36
|
-
"download": true
|
|
37
|
-
}
|
|
38
|
-
]
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
> NOTE: the links probably will need a custom endpoint which serves the file with the proper media type
|
|
42
|
-
|
|
43
|
-
expressa.get('/files/:file',function(req,res,next){
|
|
44
|
-
var file = .... // get file
|
|
45
|
-
res.writeHeader(200, {
|
|
46
|
-
"Content-Type":"image/png"
|
|
47
|
-
})
|
|
48
|
-
res.send(file)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
For more info on the "links"- of "media"-property see [json-editor](https://github.com/jdorn/json-editor)
|
|
52
|
-
|
|
53
|
-
> TODO: more examples (like listeners proxying the base64 string to S3 Bucket or a local folder)
|
|
1
|
+
## Uploading files
|
|
2
|
+
|
|
3
|
+
Lets say you want your blog posts to contain images.
|
|
4
|
+
Here's how you add an image file-upload in `data/collection/post.json`:
|
|
5
|
+
|
|
6
|
+
"properties":{
|
|
7
|
+
|
|
8
|
+
...
|
|
9
|
+
|
|
10
|
+
"picture": {
|
|
11
|
+
"title": "Picture",
|
|
12
|
+
"type": "string",
|
|
13
|
+
"media": {
|
|
14
|
+
"binaryEncoding": "base64",
|
|
15
|
+
"type": "image/png"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
> NOTE: you probably want to create an expressa listener which hides the 'picture'-property to save bandwidth. On top of that, you probably want automatic thumbnails using something like [this expressa middleware](https://gist.github.com/coderofsalvation/d3c67fbdf4639dfae4d292a37434097c)
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
"properties":{
|
|
24
|
+
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
"file": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"format": "file",
|
|
30
|
+
"title": "File"
|
|
31
|
+
"links": [
|
|
32
|
+
{
|
|
33
|
+
"rel": "Download File",
|
|
34
|
+
"href": "/custom_endpoint/{{self}}",
|
|
35
|
+
// Can also set `download` to a string as per the HTML5 spec
|
|
36
|
+
"download": true
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
> NOTE: the links probably will need a custom endpoint which serves the file with the proper media type
|
|
42
|
+
|
|
43
|
+
expressa.get('/files/:file',function(req,res,next){
|
|
44
|
+
var file = .... // get file
|
|
45
|
+
res.writeHeader(200, {
|
|
46
|
+
"Content-Type":"image/png"
|
|
47
|
+
})
|
|
48
|
+
res.send(file)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
For more info on the "links"- of "media"-property see [json-editor](https://github.com/jdorn/json-editor)
|
|
52
|
+
|
|
53
|
+
> TODO: more examples (like listeners proxying the base64 string to S3 Bucket or a local folder)
|