expressa 2.0.18 → 2.0.19

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.
Files changed (281) hide show
  1. package/.circleci/config.yml +21 -21
  2. package/.eslintrc.json +29 -29
  3. package/LICENSE +21 -21
  4. package/README.md +137 -137
  5. package/auth/index.js +76 -76
  6. package/auth/jwt.js +31 -31
  7. package/controllers/collections.js +260 -260
  8. package/controllers/install.js +105 -105
  9. package/controllers/status.js +43 -43
  10. package/controllers/users.js +52 -52
  11. package/cypress/fixtures/example.json +4 -4
  12. package/cypress/integration/0_install_spec.js +8 -8
  13. package/cypress/integration/custom_collections.js +28 -28
  14. package/cypress/integration/home.js +9 -9
  15. package/cypress/integration/settings.js +34 -34
  16. package/cypress/integration/users.js +61 -61
  17. package/cypress/plugins/index.js +17 -17
  18. package/cypress/support/commands.js +54 -54
  19. package/cypress/support/index.js +20 -20
  20. package/cypress/support/util.js +10 -10
  21. package/cypress.json +4 -4
  22. package/db/cached.js +40 -40
  23. package/db/file.js +112 -112
  24. package/db/memory.js +79 -79
  25. package/db/mongo.js +86 -86
  26. package/db/postgres.js +93 -93
  27. package/doc/authentication.md +38 -38
  28. package/doc/automatic-fields.md +12 -12
  29. package/doc/blogexample.md +16 -16
  30. package/doc/custom-endpoints.md +20 -20
  31. package/doc/database.md +23 -23
  32. package/doc/development.md +33 -33
  33. package/doc/listeners.md +103 -103
  34. package/doc/modules.md +8 -8
  35. package/doc/permissions.md +14 -14
  36. package/doc/relationships.md +65 -65
  37. package/doc/testing.md +47 -47
  38. package/doc/uploading-files.md +53 -53
  39. package/index.js +297 -297
  40. package/listeners.js +65 -65
  41. package/listeners_collection_permissions.js +29 -29
  42. package/listeners_users.js +96 -96
  43. package/listeners_validation.js +43 -43
  44. package/middleware/logging.js +30 -30
  45. package/middleware/permissions.js +41 -41
  46. package/modules/access_keys/access_keys.js +32 -32
  47. package/modules/admin/.babelrc +19 -19
  48. package/modules/admin/.editorconfig +14 -14
  49. package/modules/admin/.eslintignore +5 -5
  50. package/modules/admin/.eslintrc.js +197 -197
  51. package/modules/admin/.postcssrc.js +10 -10
  52. package/modules/admin/.travis.yml +5 -5
  53. package/modules/admin/LICENSE +21 -21
  54. package/modules/admin/README.md +88 -88
  55. package/modules/admin/build/build.js +45 -45
  56. package/modules/admin/build/check-versions.js +64 -64
  57. package/modules/admin/build/utils.js +108 -108
  58. package/modules/admin/build/vue-loader.conf.js +5 -5
  59. package/modules/admin/build/webpack.base.conf.js +112 -112
  60. package/modules/admin/build/webpack.dev.conf.js +95 -95
  61. package/modules/admin/build/webpack.prod.conf.js +112 -112
  62. package/modules/admin/config/dev.env.js +8 -8
  63. package/modules/admin/config/index.js +86 -86
  64. package/modules/admin/config/prod.env.js +5 -5
  65. package/modules/admin/dist/164c6167c1f3c497c07c.woff +1 -0
  66. package/modules/admin/dist/42ac07abc8473600c41f.ttf +1 -0
  67. package/modules/admin/dist/86dccb18a7eb4be8ef1a.woff +1 -0
  68. package/modules/admin/dist/c45297c558e0318fc9c5.ttf +1 -0
  69. package/modules/admin/dist/c717c9930715e24fa12c.woff +1 -0
  70. package/modules/admin/dist/ca6f07ef23f24cc2738a.ttf +1 -0
  71. package/modules/admin/dist/css/269.f7d5c7d5.css +37 -0
  72. package/modules/admin/dist/css/269.f7d5c7d5.css.gz +0 -0
  73. package/modules/admin/dist/css/451.c6646d20.css +1 -0
  74. package/modules/admin/dist/css/451.c6646d20.css.gz +0 -0
  75. package/modules/admin/dist/css/572.74d8e965.css +34 -0
  76. package/modules/admin/dist/css/572.74d8e965.css.gz +0 -0
  77. package/modules/admin/dist/css/651.e946bda7.css +6 -0
  78. package/modules/admin/dist/css/651.e946bda7.css.gz +0 -0
  79. package/modules/admin/dist/css/699.d1f90954.css +8 -0
  80. package/modules/admin/dist/css/699.d1f90954.css.gz +0 -0
  81. package/modules/admin/dist/css/750.4086915d.css +2 -0
  82. package/modules/admin/dist/css/750.4086915d.css.gz +0 -0
  83. package/modules/admin/dist/css/864.506917a4.css +7 -0
  84. package/modules/admin/dist/css/864.506917a4.css.gz +0 -0
  85. package/modules/admin/dist/css/899.506917a4.css +7 -0
  86. package/modules/admin/dist/css/899.506917a4.css.gz +0 -0
  87. package/modules/admin/dist/css/950.1fd9d57c.css +7 -0
  88. package/modules/admin/dist/css/950.1fd9d57c.css.gz +0 -0
  89. package/modules/admin/dist/css/app.11cc6245.css +39 -0
  90. package/modules/admin/dist/css/app.11cc6245.css.gz +0 -0
  91. package/modules/admin/dist/css/app.2e1b0d79.css +591 -0
  92. package/modules/admin/dist/css/app.2e1b0d79.css.gz +0 -0
  93. package/modules/admin/dist/css/app.4d43d0fd.css +592 -0
  94. package/modules/admin/dist/css/app.4d43d0fd.css.gz +0 -0
  95. package/modules/admin/dist/css/app.abace109.css +1 -0
  96. package/modules/admin/dist/css/app.abace109.css.gz +0 -0
  97. package/modules/admin/dist/css/chunk-elementUI.67ad86d7.css +1 -0
  98. package/modules/admin/dist/css/chunk-elementUI.67ad86d7.css.gz +0 -0
  99. package/modules/admin/dist/css/chunk-elementUI.9e660bfd.css +1 -0
  100. package/modules/admin/dist/css/chunk-elementUI.9e660bfd.css.gz +0 -0
  101. package/modules/admin/dist/css/chunk-elementUI.c77cf142.css +1 -0
  102. package/modules/admin/dist/css/chunk-elementUI.c77cf142.css.gz +0 -0
  103. package/modules/admin/dist/css/chunk-elementUI.f1625b35.css +1 -0
  104. package/modules/admin/dist/css/chunk-elementUI.f1625b35.css.gz +0 -0
  105. package/modules/admin/dist/css/chunk-elementUI.f25dfb9b.css +1 -0
  106. package/modules/admin/dist/css/chunk-elementUI.f25dfb9b.css.gz +0 -0
  107. package/modules/admin/dist/css/chunk-libs.de0d8484.css +551 -0
  108. package/modules/admin/dist/css/chunk-libs.de0d8484.css.gz +0 -0
  109. package/modules/admin/dist/index.html +1 -1
  110. package/modules/admin/dist/static/fonts/element-icons.313f7da.woff +0 -0
  111. package/modules/admin/dist/static/fonts/element-icons.4520188.ttf +0 -0
  112. package/modules/admin/dist/static/js/269.7245ceaf.js +1 -0
  113. package/modules/admin/dist/static/js/269.7245ceaf.js.gz +0 -0
  114. package/modules/admin/dist/static/js/451.645974a8.js +1 -0
  115. package/modules/admin/dist/static/js/451.645974a8.js.gz +0 -0
  116. package/modules/admin/dist/static/js/460.962d5bc0.js +1 -0
  117. package/modules/admin/dist/static/js/460.962d5bc0.js.gz +0 -0
  118. package/modules/admin/dist/static/js/556.1e08866a.js +1 -0
  119. package/modules/admin/dist/static/js/556.1e08866a.js.gz +0 -0
  120. package/modules/admin/dist/static/js/572.13392e1d.js +1 -0
  121. package/modules/admin/dist/static/js/572.13392e1d.js.gz +0 -0
  122. package/modules/admin/dist/static/js/603.7b34a650.js +1 -0
  123. package/modules/admin/dist/static/js/603.7b34a650.js.gz +0 -0
  124. package/modules/admin/dist/static/js/637.26413baa.js +1 -0
  125. package/modules/admin/dist/static/js/637.26413baa.js.gz +0 -0
  126. package/modules/admin/dist/static/js/651.564b24b6.js +1 -0
  127. package/modules/admin/dist/static/js/651.564b24b6.js.gz +0 -0
  128. package/modules/admin/dist/static/js/653.02ec1098.js +1 -0
  129. package/modules/admin/dist/static/js/653.02ec1098.js.gz +0 -0
  130. package/modules/admin/dist/static/js/699.c7564884.js +1 -0
  131. package/modules/admin/dist/static/js/699.c7564884.js.gz +0 -0
  132. package/modules/admin/dist/static/js/750.50c950da.js +1 -0
  133. package/modules/admin/dist/static/js/750.50c950da.js.gz +0 -0
  134. package/modules/admin/dist/static/js/864.4f43847d.js +1 -0
  135. package/modules/admin/dist/static/js/864.4f43847d.js.gz +0 -0
  136. package/modules/admin/dist/static/js/950.d4ff0a18.js +1 -0
  137. package/modules/admin/dist/static/js/950.d4ff0a18.js.gz +0 -0
  138. package/modules/admin/dist/static/js/app.981e71b3.js +2 -0
  139. package/modules/admin/dist/static/js/{chunk-libs.06c38828.js.LICENSE.txt → app.981e71b3.js.LICENSE.txt} +15 -18
  140. package/modules/admin/dist/static/js/app.981e71b3.js.LICENSE.txt.gz +0 -0
  141. package/modules/admin/dist/static/js/app.981e71b3.js.gz +0 -0
  142. package/modules/admin/index.html +16 -16
  143. package/modules/admin/package.json +101 -101
  144. package/modules/admin/src/App.vue +11 -11
  145. package/modules/admin/src/api/login.js +26 -26
  146. package/modules/admin/src/api/table.js +9 -9
  147. package/modules/admin/src/components/Breadcrumb/index.vue +79 -79
  148. package/modules/admin/src/components/Hamburger/index.vue +62 -62
  149. package/modules/admin/src/components/JSONEditor.vue +180 -180
  150. package/modules/admin/src/components/SvgIcon/index.vue +43 -43
  151. package/modules/admin/src/icons/index.js +9 -9
  152. package/modules/admin/src/icons/svgo.yml +22 -22
  153. package/modules/admin/src/main.js +42 -42
  154. package/modules/admin/src/permission.js +49 -49
  155. package/modules/admin/src/router/index.js +200 -200
  156. package/modules/admin/src/store/getters.js +11 -11
  157. package/modules/admin/src/store/index.js +17 -17
  158. package/modules/admin/src/store/modules/app.js +43 -43
  159. package/modules/admin/src/store/modules/user.js +104 -104
  160. package/modules/admin/src/styles/element-ui.scss +29 -29
  161. package/modules/admin/src/styles/index.scss +78 -78
  162. package/modules/admin/src/styles/mixin.scss +27 -27
  163. package/modules/admin/src/styles/sidebar.scss +133 -133
  164. package/modules/admin/src/styles/transition.scss +46 -46
  165. package/modules/admin/src/styles/variables.scss +4 -4
  166. package/modules/admin/src/utils/auth.js +15 -15
  167. package/modules/admin/src/utils/index.js +74 -74
  168. package/modules/admin/src/utils/request.js +48 -48
  169. package/modules/admin/src/utils/validate.js +31 -31
  170. package/modules/admin/src/views/404.vue +235 -235
  171. package/modules/admin/src/views/Dev.vue +33 -33
  172. package/modules/admin/src/views/EditDocument.vue +116 -116
  173. package/modules/admin/src/views/Endpoints.vue +279 -279
  174. package/modules/admin/src/views/Home.vue +84 -84
  175. package/modules/admin/src/views/Install.vue +79 -79
  176. package/modules/admin/src/views/ListDocuments.vue +72 -72
  177. package/modules/admin/src/views/ListDocuments2.vue +462 -462
  178. package/modules/admin/src/views/ManageListeners.vue +67 -67
  179. package/modules/admin/src/views/ManageMiddleware.vue +43 -43
  180. package/modules/admin/src/views/ManagePermissions.vue +75 -75
  181. package/modules/admin/src/views/ViewRequest.vue +77 -77
  182. package/modules/admin/src/views/form/index.vue +91 -91
  183. package/modules/admin/src/views/layout/Layout.vue +69 -69
  184. package/modules/admin/src/views/layout/components/AppMain.vue +41 -41
  185. package/modules/admin/src/views/layout/components/Navbar.vue +97 -97
  186. package/modules/admin/src/views/layout/components/Sidebar/Item.vue +29 -29
  187. package/modules/admin/src/views/layout/components/Sidebar/Link.vue +39 -39
  188. package/modules/admin/src/views/layout/components/Sidebar/SidebarItem.vue +103 -103
  189. package/modules/admin/src/views/layout/components/Sidebar/index.vue +62 -62
  190. package/modules/admin/src/views/layout/components/index.js +3 -3
  191. package/modules/admin/src/views/layout/mixin/ResizeHandler.js +41 -41
  192. package/modules/admin/src/views/login/index.vue +195 -195
  193. package/modules/admin/src/views/tree/index.vue +77 -77
  194. package/modules/collections/collections.js +39 -39
  195. package/modules/core/core.js +197 -197
  196. package/modules/logging/logging.js +88 -88
  197. package/modules/permissions/permissions.js +106 -106
  198. package/package.json +59 -59
  199. package/scripts/expressa +12 -12
  200. package/scripts/run_cypress_tests.sh +5 -5
  201. package/scripts/run_db_tests.sh +8 -8
  202. package/test/0-install.js +96 -96
  203. package/test/collections.js +431 -431
  204. package/test/collections.querying.js +288 -288
  205. package/test/db.js +258 -258
  206. package/test/db.strings.js +73 -73
  207. package/test/db.updating.js +144 -144
  208. package/test/logging.js +29 -29
  209. package/test/test.js +93 -93
  210. package/test/testserver.js +17 -17
  211. package/test/testutils.js +136 -136
  212. package/test/users.js +425 -425
  213. package/util.js +522 -522
  214. package/modules/admin/dist/index.html.gz +0 -0
  215. package/modules/admin/dist/static/css/app.9dd57eea.css +0 -1
  216. package/modules/admin/dist/static/css/app.9dd57eea.css.gz +0 -0
  217. package/modules/admin/dist/static/css/chunk-5061.ce0e4bdb.css +0 -1
  218. package/modules/admin/dist/static/css/chunk-5061.ce0e4bdb.css.gz +0 -0
  219. package/modules/admin/dist/static/css/chunk-5b97.7f9cb53e.css +0 -1
  220. package/modules/admin/dist/static/css/chunk-5b97.7f9cb53e.css.gz +0 -0
  221. package/modules/admin/dist/static/css/chunk-621e.b826be8d.css +0 -1
  222. package/modules/admin/dist/static/css/chunk-621e.b826be8d.css.gz +0 -0
  223. package/modules/admin/dist/static/css/chunk-656b.9c828b00.css +0 -1
  224. package/modules/admin/dist/static/css/chunk-656b.9c828b00.css.gz +0 -0
  225. package/modules/admin/dist/static/css/chunk-7291.13514e45.css +0 -0
  226. package/modules/admin/dist/static/css/chunk-7291.13514e45.css.gz +0 -0
  227. package/modules/admin/dist/static/css/chunk-7f29.9c828b00.css +0 -1
  228. package/modules/admin/dist/static/css/chunk-7f29.9c828b00.css.gz +0 -0
  229. package/modules/admin/dist/static/css/chunk-9a95.c647a2ef.css +0 -1
  230. package/modules/admin/dist/static/css/chunk-9a95.c647a2ef.css.gz +0 -0
  231. package/modules/admin/dist/static/css/chunk-b423.5f239333.css +0 -0
  232. package/modules/admin/dist/static/css/chunk-b423.5f239333.css.gz +0 -0
  233. package/modules/admin/dist/static/css/chunk-bd5f.aefa49d7.css +0 -1
  234. package/modules/admin/dist/static/css/chunk-bd5f.aefa49d7.css.gz +0 -0
  235. package/modules/admin/dist/static/css/chunk-e374.025d58bb.css +0 -1
  236. package/modules/admin/dist/static/css/chunk-e374.025d58bb.css.gz +0 -0
  237. package/modules/admin/dist/static/css/chunk-elementUI.3d004d9f.css +0 -1
  238. package/modules/admin/dist/static/css/chunk-elementUI.3d004d9f.css.gz +0 -0
  239. package/modules/admin/dist/static/css/chunk-f075.3a895a1e.css +0 -1
  240. package/modules/admin/dist/static/css/chunk-f075.3a895a1e.css.gz +0 -0
  241. package/modules/admin/dist/static/css/chunk-libs.327ad89e.css +0 -14
  242. package/modules/admin/dist/static/css/chunk-libs.327ad89e.css.gz +0 -0
  243. package/modules/admin/dist/static/fonts/element-icons.6f0a763.ttf +0 -0
  244. package/modules/admin/dist/static/js/app.e9461593.js +0 -1
  245. package/modules/admin/dist/static/js/app.e9461593.js.gz +0 -0
  246. package/modules/admin/dist/static/js/chunk-09d8.561880e0.js +0 -2
  247. package/modules/admin/dist/static/js/chunk-09d8.561880e0.js.LICENSE.txt +0 -13
  248. package/modules/admin/dist/static/js/chunk-09d8.561880e0.js.LICENSE.txt.gz +0 -0
  249. package/modules/admin/dist/static/js/chunk-09d8.561880e0.js.gz +0 -0
  250. package/modules/admin/dist/static/js/chunk-5061.83e6ccfa.js +0 -1
  251. package/modules/admin/dist/static/js/chunk-5061.83e6ccfa.js.gz +0 -0
  252. package/modules/admin/dist/static/js/chunk-5b97.ac360086.js +0 -1
  253. package/modules/admin/dist/static/js/chunk-5b97.ac360086.js.gz +0 -0
  254. package/modules/admin/dist/static/js/chunk-5e17.a2b69611.js +0 -1
  255. package/modules/admin/dist/static/js/chunk-5e17.a2b69611.js.gz +0 -0
  256. package/modules/admin/dist/static/js/chunk-621e.ccf42393.js +0 -1
  257. package/modules/admin/dist/static/js/chunk-621e.ccf42393.js.gz +0 -0
  258. package/modules/admin/dist/static/js/chunk-656b.9d5b03c0.js +0 -1
  259. package/modules/admin/dist/static/js/chunk-656b.9d5b03c0.js.gz +0 -0
  260. package/modules/admin/dist/static/js/chunk-7291.590524e9.js +0 -1
  261. package/modules/admin/dist/static/js/chunk-7291.590524e9.js.gz +0 -0
  262. package/modules/admin/dist/static/js/chunk-7f29.6ffc142e.js +0 -1
  263. package/modules/admin/dist/static/js/chunk-7f29.6ffc142e.js.gz +0 -0
  264. package/modules/admin/dist/static/js/chunk-9a95.bdd76583.js +0 -1
  265. package/modules/admin/dist/static/js/chunk-9a95.bdd76583.js.gz +0 -0
  266. package/modules/admin/dist/static/js/chunk-b423.387c504c.js +0 -1
  267. package/modules/admin/dist/static/js/chunk-b423.387c504c.js.gz +0 -0
  268. package/modules/admin/dist/static/js/chunk-bd5f.e9509f10.js +0 -1
  269. package/modules/admin/dist/static/js/chunk-bd5f.e9509f10.js.gz +0 -0
  270. package/modules/admin/dist/static/js/chunk-e374.7c87a0dd.js +0 -1
  271. package/modules/admin/dist/static/js/chunk-e374.7c87a0dd.js.gz +0 -0
  272. package/modules/admin/dist/static/js/chunk-e7c3.e5df8ceb.js +0 -1
  273. package/modules/admin/dist/static/js/chunk-e7c3.e5df8ceb.js.gz +0 -0
  274. package/modules/admin/dist/static/js/chunk-elementUI.b0c99c08.js +0 -1
  275. package/modules/admin/dist/static/js/chunk-elementUI.b0c99c08.js.gz +0 -0
  276. package/modules/admin/dist/static/js/chunk-f075.315ceec1.js +0 -1
  277. package/modules/admin/dist/static/js/chunk-f075.315ceec1.js.gz +0 -0
  278. package/modules/admin/dist/static/js/chunk-libs.06c38828.js +0 -2
  279. package/modules/admin/dist/static/js/chunk-libs.06c38828.js.LICENSE.txt.gz +0 -0
  280. package/modules/admin/dist/static/js/chunk-libs.06c38828.js.gz +0 -0
  281. /package/modules/admin/dist/static/img/{404.a57b6f3.png → 404.4cf6930.png} +0 -0
@@ -1,260 +1,260 @@
1
- const debug = require('debug')('expressa')
2
- const MongoQS = require('mongo-querystring')
3
-
4
- const util = require('../util')
5
- const queryStringParser = new MongoQS({})
6
-
7
- function assertValidCollection(req) {
8
- if (!req.db[req.params.collection]) {
9
- throw new util.ApiError(404, 'unknown collection')
10
- }
11
- }
12
-
13
- async function validateDocumentOwner(req, doc) {
14
- if (!doc.meta.owner_collection) {
15
- throw new util.ApiError(417, 'no owner_collection for owner found')
16
- }
17
- util.validateSchemaProperty(req.params.collection, 'meta.owner_collection', doc.meta.owner_collection)
18
- if (!(doc._id === doc.meta.owner && req.params.collection === doc.meta.owner_collection)) {
19
- const count = await req.db[doc.meta.owner_collection].count({ _id: doc.meta.owner }, undefined, 1)
20
- if (!count) {
21
- throw new util.ApiError(417, 'invalid owner')
22
- }
23
- }
24
- util.validateSchemaProperty(doc.meta.owner_collection, 'meta.owner', doc.meta.owner)
25
- }
26
-
27
- async function setDocumentOwner(req, doc) {
28
- const schema = await req.db.collection.get(req.params.collection)
29
- if (schema.documentsHaveOwners) {
30
- util.addIdIfMissing(doc)
31
- doc.meta ??= {}
32
- if (req.uid) {
33
- // request done by logged in user
34
- if (!doc.meta.owner) {
35
- // no owner - make logged in user as owner
36
- doc.meta.owner = req.user._id
37
- doc.meta.owner_collection = req.ucollection
38
- } else {
39
- if (!req.hasPermission(`${req.params.collection}: modify owner`)) {
40
- // has an owner - but if no permission to modify owner then override owner to logged in user
41
- doc.meta.owner = req.user._id
42
- doc.meta.owner_collection = req.ucollection
43
- }
44
- }
45
- } else {
46
- // request done by NOT logged in user
47
- if (schema.enableLogin) {
48
- // is a user/login collection so make self owner
49
- doc.meta.owner = doc._id
50
- doc.meta.owner_collection = req.params.collection
51
- } else {
52
- // is a normal collection
53
- debug('unable to find valid owner')
54
- }
55
- }
56
- if (doc.meta.owner) {
57
- await validateDocumentOwner(req, doc)
58
- }
59
- }
60
- }
61
-
62
- exports.getSchema = async function (req) {
63
- const collection = await req.db.collection.get(req.params.collection)
64
- await util.notify('get', req, 'schemas', collection)
65
- return collection.schema
66
- }
67
-
68
- exports.get = async function (req) {
69
- assertValidCollection(req)
70
- if (typeof req.db[req.params.collection] === 'undefined') {
71
- throw new util.ApiError(404, 'unknown collection')
72
- }
73
-
74
- const fields = req.query.fields ? JSON.parse(req.query.fields) : null
75
-
76
- let data, query
77
- if (req.query.query) {
78
- query = JSON.parse(req.query.query)
79
- }
80
- else {
81
- const { skip, offset, limit, page, pageitems, pagemetadisable, orderby, ...params } = req.query // eslint-disable-line no-unused-vars
82
- delete params.fields
83
- query = queryStringParser.parse(params)
84
- }
85
- if (req.query.orderby) {
86
- req.query.orderby = JSON.parse(req.query.orderby)
87
- req.query.orderby = util.normalizeOrderBy(req.query.orderby)
88
- }
89
-
90
- if (req.uid) {
91
- // scenario where logged in user can only retrieve own docs
92
- if (!req.hasPermission(`${req.params.collection}: view`) && req.hasPermission(`${req.params.collection}: view own`)) {
93
- query['meta.owner'] = req.uid
94
- // if fields is null then all fields are requested
95
- if (fields) fields['meta.owner'] = 1
96
- }
97
- }
98
-
99
- if (req.query.page != null) {
100
- req.query.page = parseInt(req.query.page)
101
- if (req.query.page <= 0) {
102
- throw new util.ApiError(400, 'invalid page number')
103
- }
104
- req.query.pageitems = parseInt(req.query.pageitems) || parseInt(req.query.limit) || 10
105
- req.query.offset = util.getDatabaseOffset(req.query.page, req.query.pageitems)
106
- req.query.orderby = util.orderByForPagedRequests(req.query.orderby)
107
- delete req.query.limit
108
- delete req.query.skip
109
- let pageData, totalItems
110
- if (req.query.pagemetadisable) {
111
- // a way to skip the extra count database call, but lose additional page detail
112
- pageData = await req.db[req.params.collection].find(query, req.query.offset, req.query.pageitems, req.query.orderby, fields)
113
- }
114
- else {
115
- // this is default - full detail
116
- [pageData, totalItems] = await Promise.all([
117
- req.db[req.params.collection].find(query, req.query.offset, req.query.pageitems, req.query.orderby, fields),
118
- req.db[req.params.collection].count(query)
119
- ])
120
- }
121
- await Promise.all(pageData.map((doc) => util.notify('get', req, req.params.collection, doc)))
122
- data = util.createPagePagination(pageData, req.query.page, req.query.pageitems, totalItems)
123
- }
124
- else {
125
- if (req.query.limit) {
126
- req.query.limit = parseInt(req.query.limit)
127
- }
128
- if (req.query.skip) {
129
- req.query.offset = parseInt(req.query.skip)
130
- delete req.query.skip
131
- }
132
- else if (req.query.offset) {
133
- req.query.offset = parseInt(req.query.offset)
134
- }
135
- data = await req.db[req.params.collection].find(query, req.query.offset, req.query.limit, req.query.orderby, fields)
136
- await Promise.all(data.map((doc) => util.notify('get', req, req.params.collection, doc)))
137
- }
138
-
139
- await util.notify('getpresend', req, req.params.collection, data)
140
- return data
141
- }
142
-
143
- exports.insert = async function (req) {
144
- assertValidCollection(req)
145
- const data = req.body
146
- req.customResponseData = req.customResponseData || {}
147
- await util.notify('post', req, req.params.collection, data)
148
- await setDocumentOwner(req, data)
149
- const id = await req.db[req.params.collection].create(data)
150
- await util.notify('changed', req, req.params.collection, data)
151
- return {
152
- status: 'OK',
153
- id: id,
154
- ...req.customResponseData,
155
- }
156
- }
157
-
158
- exports.getById = async function (req) {
159
- assertValidCollection(req)
160
- const fields = req.query.fields ? JSON.parse(req.query.fields) : null
161
- const data = await req.db[req.params.collection].get(req.params.id, fields)
162
- await util.notify('get', req, req.params.collection, data)
163
- await util.notify('getpresend', req, req.params.collection, data)
164
- return data
165
- }
166
-
167
- exports.replaceById = async function (req) {
168
- assertValidCollection(req)
169
- req.customResponseData = req.customResponseData || {}
170
- const data = req.body
171
- let oldDoc = {}
172
- try {
173
- oldDoc = await req.db[req.params.collection].get(req.params.id)
174
- } catch (error) {
175
- // happens when new documents are created via a PUT
176
- oldDoc = { meta: { owner: req.user._id, owner_collection: 'users' } }
177
- data._id = req.params.id.trim()
178
- await setDocumentOwner(req, data)
179
- }
180
- if (data._id) {
181
- data._id = data._id.trim()
182
- } else {
183
- data._id = req.params.id.trim()
184
- }
185
- data.meta = data.meta || {}
186
- data.meta.created = (oldDoc.meta || {}).created
187
- if (data.meta.owner !== oldDoc.meta.owner || data.meta.owner_collection !== oldDoc.meta.owner_ollection) {
188
- if (req.hasPermission(`${req.params.collection}: modify owner`)) {
189
- if (oldDoc.meta.owner) {
190
- await validateDocumentOwner(req, data)
191
- }
192
- } else {
193
- debug('attempting to change document owner.')
194
- data.meta.owner = oldDoc.meta.owner
195
- data.meta.owner_collection = oldDoc.meta.owner_collection
196
- }
197
- }
198
- await util.notify('put', req, req.params.collection, data)
199
- await req.db[req.params.collection].update(req.params.id, data)
200
- await util.notify('changed', req, req.params.collection, data)
201
- return {
202
- status: 'OK',
203
- id: data._id,
204
- ...req.customResponseData,
205
- }
206
- }
207
-
208
- exports.updateById = async function (req) {
209
- assertValidCollection(req)
210
- req.customResponseData = req.customResponseData || {}
211
- const modifier = req.body
212
-
213
- const doc = await req.db[req.params.collection].get(req.params.id)
214
- const owner = doc.meta && doc.meta.owner
215
- const ownerCollection = doc.meta && doc.meta.owner_collection
216
- util.mongoUpdate(doc, modifier)
217
- const newOwner = doc.meta && doc.meta.owner
218
- const newOwnerCollection = doc.meta && doc.meta.owner_collection
219
- if (owner !== newOwner || ownerCollection !== newOwnerCollection) {
220
- if (req.hasPermission(`${req.params.collection}: modify owner`)) {
221
- if (owner) {
222
- await validateDocumentOwner(req, doc)
223
- }
224
- } else {
225
- debug('attempting to change document owner.')
226
- doc.meta.owner = owner
227
- doc.meta.owner_collection = ownerCollection
228
- }
229
- }
230
- if (doc._id) {
231
- doc._id = doc._id.trim()
232
- } else {
233
- throw new util.ApiError(400, '_id property is required')
234
- }
235
- req.body = doc
236
- await util.notify('put', req, req.params.collection, doc)
237
- await req.db[req.params.collection].update(req.params.id, req.body)
238
- await util.notify('changed', req, req.params.collection, req.body)
239
- if (Object.keys(req.customResponseData)) {
240
- return {
241
- ...doc,
242
- ...req.customResponseData,
243
- }
244
- }
245
- return doc
246
- }
247
-
248
- exports.deleteById = async function (req) {
249
- assertValidCollection(req)
250
- req.customResponseData = req.customResponseData || {}
251
- const doc = await req.db[req.params.collection].get(req.params.id)
252
- await util.notify('delete', req, req.params.collection, doc)
253
-
254
- await req.db[req.params.collection].delete(req.params.id)
255
- await util.notify('deleted', req, req.params.collection, doc)
256
- return {
257
- status: 'OK',
258
- ...req.customResponseData,
259
- }
260
- }
1
+ const debug = require('debug')('expressa')
2
+ const MongoQS = require('mongo-querystring')
3
+
4
+ const util = require('../util')
5
+ const queryStringParser = new MongoQS({})
6
+
7
+ function assertValidCollection(req) {
8
+ if (!req.db[req.params.collection]) {
9
+ throw new util.ApiError(404, 'unknown collection')
10
+ }
11
+ }
12
+
13
+ async function validateDocumentOwner(req, doc) {
14
+ if (!doc.meta.owner_collection) {
15
+ throw new util.ApiError(417, 'no owner_collection for owner found')
16
+ }
17
+ util.validateSchemaProperty(req.params.collection, 'meta.owner_collection', doc.meta.owner_collection)
18
+ if (!(doc._id === doc.meta.owner && req.params.collection === doc.meta.owner_collection)) {
19
+ const count = await req.db[doc.meta.owner_collection].count({ _id: doc.meta.owner }, undefined, 1)
20
+ if (!count) {
21
+ throw new util.ApiError(417, 'invalid owner')
22
+ }
23
+ }
24
+ util.validateSchemaProperty(doc.meta.owner_collection, 'meta.owner', doc.meta.owner)
25
+ }
26
+
27
+ async function setDocumentOwner(req, doc) {
28
+ const schema = await req.db.collection.get(req.params.collection)
29
+ if (schema.documentsHaveOwners) {
30
+ util.addIdIfMissing(doc)
31
+ doc.meta ??= {}
32
+ if (req.uid) {
33
+ // request done by logged in user
34
+ if (!doc.meta.owner) {
35
+ // no owner - make logged in user as owner
36
+ doc.meta.owner = req.user._id
37
+ doc.meta.owner_collection = req.ucollection
38
+ } else {
39
+ if (!req.hasPermission(`${req.params.collection}: modify owner`)) {
40
+ // has an owner - but if no permission to modify owner then override owner to logged in user
41
+ doc.meta.owner = req.user._id
42
+ doc.meta.owner_collection = req.ucollection
43
+ }
44
+ }
45
+ } else {
46
+ // request done by NOT logged in user
47
+ if (schema.enableLogin) {
48
+ // is a user/login collection so make self owner
49
+ doc.meta.owner = doc._id
50
+ doc.meta.owner_collection = req.params.collection
51
+ } else {
52
+ // is a normal collection
53
+ debug('unable to find valid owner')
54
+ }
55
+ }
56
+ if (doc.meta.owner) {
57
+ await validateDocumentOwner(req, doc)
58
+ }
59
+ }
60
+ }
61
+
62
+ exports.getSchema = async function (req) {
63
+ const collection = await req.db.collection.get(req.params.collection)
64
+ await util.notify('get', req, 'schemas', collection)
65
+ return collection.schema
66
+ }
67
+
68
+ exports.get = async function (req) {
69
+ assertValidCollection(req)
70
+ if (typeof req.db[req.params.collection] === 'undefined') {
71
+ throw new util.ApiError(404, 'unknown collection')
72
+ }
73
+
74
+ const fields = req.query.fields ? JSON.parse(req.query.fields) : null
75
+
76
+ let data, query
77
+ if (req.query.query) {
78
+ query = JSON.parse(req.query.query)
79
+ }
80
+ else {
81
+ const { skip, offset, limit, page, pageitems, pagemetadisable, orderby, ...params } = req.query // eslint-disable-line no-unused-vars
82
+ delete params.fields
83
+ query = queryStringParser.parse(params)
84
+ }
85
+ if (req.query.orderby) {
86
+ req.query.orderby = JSON.parse(req.query.orderby)
87
+ req.query.orderby = util.normalizeOrderBy(req.query.orderby)
88
+ }
89
+
90
+ if (req.uid) {
91
+ // scenario where logged in user can only retrieve own docs
92
+ if (!req.hasPermission(`${req.params.collection}: view`) && req.hasPermission(`${req.params.collection}: view own`)) {
93
+ query['meta.owner'] = req.uid
94
+ // if fields is null then all fields are requested
95
+ if (fields) fields['meta.owner'] = 1
96
+ }
97
+ }
98
+
99
+ if (req.query.page != null) {
100
+ req.query.page = parseInt(req.query.page)
101
+ if (req.query.page <= 0) {
102
+ throw new util.ApiError(400, 'invalid page number')
103
+ }
104
+ req.query.pageitems = parseInt(req.query.pageitems) || parseInt(req.query.limit) || 10
105
+ req.query.offset = util.getDatabaseOffset(req.query.page, req.query.pageitems)
106
+ req.query.orderby = util.orderByForPagedRequests(req.query.orderby)
107
+ delete req.query.limit
108
+ delete req.query.skip
109
+ let pageData, totalItems
110
+ if (req.query.pagemetadisable) {
111
+ // a way to skip the extra count database call, but lose additional page detail
112
+ pageData = await req.db[req.params.collection].find(query, req.query.offset, req.query.pageitems, req.query.orderby, fields)
113
+ }
114
+ else {
115
+ // this is default - full detail
116
+ [pageData, totalItems] = await Promise.all([
117
+ req.db[req.params.collection].find(query, req.query.offset, req.query.pageitems, req.query.orderby, fields),
118
+ req.db[req.params.collection].count(query)
119
+ ])
120
+ }
121
+ await Promise.all(pageData.map((doc) => util.notify('get', req, req.params.collection, doc)))
122
+ data = util.createPagePagination(pageData, req.query.page, req.query.pageitems, totalItems)
123
+ }
124
+ else {
125
+ if (req.query.limit) {
126
+ req.query.limit = parseInt(req.query.limit)
127
+ }
128
+ if (req.query.skip) {
129
+ req.query.offset = parseInt(req.query.skip)
130
+ delete req.query.skip
131
+ }
132
+ else if (req.query.offset) {
133
+ req.query.offset = parseInt(req.query.offset)
134
+ }
135
+ data = await req.db[req.params.collection].find(query, req.query.offset, req.query.limit, req.query.orderby, fields)
136
+ await Promise.all(data.map((doc) => util.notify('get', req, req.params.collection, doc)))
137
+ }
138
+
139
+ await util.notify('getpresend', req, req.params.collection, data)
140
+ return data
141
+ }
142
+
143
+ exports.insert = async function (req) {
144
+ assertValidCollection(req)
145
+ const data = req.body
146
+ req.customResponseData = req.customResponseData || {}
147
+ await util.notify('post', req, req.params.collection, data)
148
+ await setDocumentOwner(req, data)
149
+ const id = await req.db[req.params.collection].create(data)
150
+ await util.notify('changed', req, req.params.collection, data)
151
+ return {
152
+ status: 'OK',
153
+ id: id,
154
+ ...req.customResponseData,
155
+ }
156
+ }
157
+
158
+ exports.getById = async function (req) {
159
+ assertValidCollection(req)
160
+ const fields = req.query.fields ? JSON.parse(req.query.fields) : null
161
+ const data = await req.db[req.params.collection].get(req.params.id, fields)
162
+ await util.notify('get', req, req.params.collection, data)
163
+ await util.notify('getpresend', req, req.params.collection, data)
164
+ return data
165
+ }
166
+
167
+ exports.replaceById = async function (req) {
168
+ assertValidCollection(req)
169
+ req.customResponseData = req.customResponseData || {}
170
+ const data = req.body
171
+ let oldDoc = {}
172
+ try {
173
+ oldDoc = await req.db[req.params.collection].get(req.params.id)
174
+ } catch (error) {
175
+ // happens when new documents are created via a PUT
176
+ oldDoc = { meta: { owner: req.user._id, owner_collection: 'users' } }
177
+ data._id = req.params.id.trim()
178
+ await setDocumentOwner(req, data)
179
+ }
180
+ if (data._id) {
181
+ data._id = data._id.trim()
182
+ } else {
183
+ data._id = req.params.id.trim()
184
+ }
185
+ data.meta = data.meta || {}
186
+ data.meta.created = (oldDoc.meta || {}).created
187
+ if (data.meta.owner !== oldDoc.meta.owner || data.meta.owner_collection !== oldDoc.meta.owner_ollection) {
188
+ if (req.hasPermission(`${req.params.collection}: modify owner`)) {
189
+ if (oldDoc.meta.owner) {
190
+ await validateDocumentOwner(req, data)
191
+ }
192
+ } else {
193
+ debug('attempting to change document owner.')
194
+ data.meta.owner = oldDoc.meta.owner
195
+ data.meta.owner_collection = oldDoc.meta.owner_collection
196
+ }
197
+ }
198
+ await util.notify('put', req, req.params.collection, data)
199
+ await req.db[req.params.collection].update(req.params.id, data)
200
+ await util.notify('changed', req, req.params.collection, data)
201
+ return {
202
+ status: 'OK',
203
+ id: data._id,
204
+ ...req.customResponseData,
205
+ }
206
+ }
207
+
208
+ exports.updateById = async function (req) {
209
+ assertValidCollection(req)
210
+ req.customResponseData = req.customResponseData || {}
211
+ const modifier = req.body
212
+
213
+ const doc = await req.db[req.params.collection].get(req.params.id)
214
+ const owner = doc.meta && doc.meta.owner
215
+ const ownerCollection = doc.meta && doc.meta.owner_collection
216
+ util.mongoUpdate(doc, modifier)
217
+ const newOwner = doc.meta && doc.meta.owner
218
+ const newOwnerCollection = doc.meta && doc.meta.owner_collection
219
+ if (owner !== newOwner || ownerCollection !== newOwnerCollection) {
220
+ if (req.hasPermission(`${req.params.collection}: modify owner`)) {
221
+ if (owner) {
222
+ await validateDocumentOwner(req, doc)
223
+ }
224
+ } else {
225
+ debug('attempting to change document owner.')
226
+ doc.meta.owner = owner
227
+ doc.meta.owner_collection = ownerCollection
228
+ }
229
+ }
230
+ if (doc._id) {
231
+ doc._id = doc._id.trim()
232
+ } else {
233
+ throw new util.ApiError(400, '_id property is required')
234
+ }
235
+ req.body = doc
236
+ await util.notify('put', req, req.params.collection, doc)
237
+ await req.db[req.params.collection].update(req.params.id, req.body)
238
+ await util.notify('changed', req, req.params.collection, req.body)
239
+ if (Object.keys(req.customResponseData)) {
240
+ return {
241
+ ...doc,
242
+ ...req.customResponseData,
243
+ }
244
+ }
245
+ return doc
246
+ }
247
+
248
+ exports.deleteById = async function (req) {
249
+ assertValidCollection(req)
250
+ req.customResponseData = req.customResponseData || {}
251
+ const doc = await req.db[req.params.collection].get(req.params.id)
252
+ await util.notify('delete', req, req.params.collection, doc)
253
+
254
+ await req.db[req.params.collection].delete(req.params.id)
255
+ await util.notify('deleted', req, req.params.collection, doc)
256
+ return {
257
+ status: 'OK',
258
+ ...req.customResponseData,
259
+ }
260
+ }