expressa 2.0.15 → 2.0.17

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