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
package/db/memory.js CHANGED
@@ -1,79 +1,79 @@
1
- /* eslint no-unused-vars: ["error", { "args": "none" }] */
2
- const util = require('../util')
3
-
4
- module.exports = function (settings, collection) {
5
- const store = {}
6
-
7
- return {
8
- type: 'memory',
9
- init: async function () {},
10
- all: async function () {
11
- return this.find({})
12
- },
13
- find: async function (query, offset, limit, orderby, fields) {
14
- const arr = Object.values(store)
15
- let matches = util.mongoSearch(arr, query)
16
- if (orderby) {
17
- matches = util.orderBy(matches, orderby)
18
- }
19
- if (typeof offset !== 'undefined' && typeof limit !== 'undefined') {
20
- matches = matches.slice(offset, offset + limit)
21
- } else if (typeof offset !== 'undefined') {
22
- matches = matches.slice(offset)
23
- } else if (typeof limit !== 'undefined') {
24
- matches = matches.slice(0, limit)
25
- }
26
- if (fields) {
27
- matches = matches.map((doc) => util.mongoProject(doc, fields))
28
- }
29
- return matches
30
- },
31
- count: async function(...params) {
32
- return (await this.find(...params)).length
33
- },
34
- get: async function (id, fields) {
35
- if (!store[id]) {
36
- throw new util.ApiError(404, 'document not found')
37
- }
38
- let doc = util.clone(store[id])
39
- if (fields) {
40
- doc = util.mongoProject(doc, fields)
41
- }
42
- return doc
43
- },
44
- create: async function (data) {
45
- util.addIdIfMissing(data)
46
- const id = data._id
47
- if (store[id]) {
48
- throw new util.ApiError(409, 'document already exists')
49
- }
50
- store[id] = data
51
- return id
52
- },
53
- update: async function (id, data) {
54
- data._id = data._id || id
55
- store[id] = data
56
- return data
57
- },
58
- // Designed to match Mongo
59
- // https://docs.mongodb.com/manual/reference/method/db.collection.updateMany/
60
- updateWithQuery: async function (query, update, options) {
61
- const arr = Object.keys(store).map((id) => ({ _id: id, ...store[id]}) )
62
- const matches = util.mongoSearch(arr, query)
63
- matches.forEach((doc) => {
64
- util.mongoUpdate(doc, update)
65
- store[doc._id] = doc
66
- })
67
- return {
68
- matchedCount: matches.length
69
- }
70
- },
71
- delete: async function (id) {
72
- if (store[id]) {
73
- delete store[id]
74
- } else {
75
- throw new util.ApiError(404, 'document not found')
76
- }
77
- }
78
- }
79
- }
1
+ /* eslint no-unused-vars: ["error", { "args": "none" }] */
2
+ const util = require('../util')
3
+
4
+ module.exports = function (settings, collection) {
5
+ const store = {}
6
+
7
+ return {
8
+ type: 'memory',
9
+ init: async function () {},
10
+ all: async function () {
11
+ return this.find({})
12
+ },
13
+ find: async function (query, offset, limit, orderby, fields) {
14
+ const arr = Object.values(store)
15
+ let matches = util.mongoSearch(arr, query)
16
+ if (orderby) {
17
+ matches = util.orderBy(matches, orderby)
18
+ }
19
+ if (typeof offset !== 'undefined' && typeof limit !== 'undefined') {
20
+ matches = matches.slice(offset, offset + limit)
21
+ } else if (typeof offset !== 'undefined') {
22
+ matches = matches.slice(offset)
23
+ } else if (typeof limit !== 'undefined') {
24
+ matches = matches.slice(0, limit)
25
+ }
26
+ if (fields) {
27
+ matches = matches.map((doc) => util.mongoProject(doc, fields))
28
+ }
29
+ return matches
30
+ },
31
+ count: async function(...params) {
32
+ return (await this.find(...params)).length
33
+ },
34
+ get: async function (id, fields) {
35
+ if (!store[id]) {
36
+ throw new util.ApiError(404, 'document not found')
37
+ }
38
+ let doc = util.clone(store[id])
39
+ if (fields) {
40
+ doc = util.mongoProject(doc, fields)
41
+ }
42
+ return doc
43
+ },
44
+ create: async function (data) {
45
+ util.addIdIfMissing(data)
46
+ const id = data._id
47
+ if (store[id]) {
48
+ throw new util.ApiError(409, 'document already exists')
49
+ }
50
+ store[id] = data
51
+ return id
52
+ },
53
+ update: async function (id, data) {
54
+ data._id = data._id || id
55
+ store[id] = data
56
+ return data
57
+ },
58
+ // Designed to match Mongo
59
+ // https://docs.mongodb.com/manual/reference/method/db.collection.updateMany/
60
+ updateWithQuery: async function (query, update, options) {
61
+ const arr = Object.keys(store).map((id) => ({ _id: id, ...store[id]}) )
62
+ const matches = util.mongoSearch(arr, query)
63
+ matches.forEach((doc) => {
64
+ util.mongoUpdate(doc, update)
65
+ store[doc._id] = doc
66
+ })
67
+ return {
68
+ matchedCount: matches.length
69
+ }
70
+ },
71
+ delete: async function (id) {
72
+ if (store[id]) {
73
+ delete store[id]
74
+ } else {
75
+ throw new util.ApiError(404, 'document not found')
76
+ }
77
+ }
78
+ }
79
+ }
package/db/mongo.js CHANGED
@@ -1,86 +1,86 @@
1
- const mongo = require('mongodb')
2
- const MongoClient = mongo.MongoClient
3
- const util = require('../util')
4
-
5
- module.exports = function (settings, collection) {
6
- return {
7
- getClient: async function () {
8
- const client = await MongoClient.connect(settings.mongodb_uri, { useNewUrlParser: true })
9
- const db = client.db(settings.mongodb_uri.split('/')[3])
10
- return db
11
- },
12
- init: function () {
13
- if (!settings.mongodb_uri) {
14
- console.error('{collection} uses mongo, but mongodb_url is undefined.')
15
- }
16
- },
17
- all: async function () {
18
- return this.find({})
19
- },
20
- find: async function (query, skip, limit, orderby, fields) {
21
- const db = await this.getClient()
22
- const cursor = db.collection(collection).find(query, { skip, limit, projection: fields, sort: orderby })
23
- const docs = await cursor.toArray()
24
- docs.forEach((doc) => {
25
- if (doc._id) {
26
- doc._id = doc._id.toString()
27
- }
28
- })
29
- return docs
30
- },
31
- count: async function(query, skip, limit) {
32
- const db = await this.getClient()
33
- const count = await db.collection(collection).count(query, { skip, limit })
34
- return count
35
- },
36
- get: async function (id, fields) {
37
- const db = await this.getClient()
38
- const doc = await db.collection(collection).findOne({ _id: id }, { projection: fields })
39
- if (doc) {
40
- doc._id = doc._id.toString()
41
- }
42
- if (!doc) {
43
- throw new util.ApiError(404, 'document not found')
44
- }
45
- return doc
46
- },
47
- create: async function (data) {
48
- util.addIdIfMissing(data)
49
-
50
- const db = await this.getClient()
51
- try {
52
- const doc = await db.collection(collection).insertOne(data)
53
- return doc.insertedId.toString()
54
- } catch (err) {
55
- if (err && err.message && err.message.includes('duplicate key error')) {
56
- throw new util.ApiError(409, 'document already exists')
57
- }
58
- }
59
- },
60
- update: async function (id, data) {
61
- data._id = id
62
- const db = await this.getClient()
63
- await db.collection(collection).replaceOne({
64
- _id: id
65
- }, data)
66
- // doc._id = doc._id.toString()
67
- return data
68
- },
69
- updateWithQuery: async function (query, update, options) {
70
- const db = await this.getClient()
71
- const res = await db.collection(collection).updateMany(query, update, options)
72
- return {
73
- matchedCount: res.matchedCount
74
- }
75
- },
76
- delete: async function (id) {
77
- const db = await this.getClient()
78
- const res = await db.collection(collection).deleteOne({
79
- _id: id
80
- })
81
- if (res.result.n !== 1) {
82
- throw new util.ApiError(404, 'document not found')
83
- }
84
- }
85
- }
86
- }
1
+ const mongo = require('mongodb')
2
+ const MongoClient = mongo.MongoClient
3
+ const util = require('../util')
4
+
5
+ module.exports = function (settings, collection) {
6
+ return {
7
+ getClient: async function () {
8
+ const client = await MongoClient.connect(settings.mongodb_uri, { useNewUrlParser: true })
9
+ const db = client.db(settings.mongodb_uri.split('/')[3])
10
+ return db
11
+ },
12
+ init: function () {
13
+ if (!settings.mongodb_uri) {
14
+ console.error('{collection} uses mongo, but mongodb_url is undefined.')
15
+ }
16
+ },
17
+ all: async function () {
18
+ return this.find({})
19
+ },
20
+ find: async function (query, skip, limit, orderby, fields) {
21
+ const db = await this.getClient()
22
+ const cursor = db.collection(collection).find(query, { skip, limit, projection: fields, sort: orderby })
23
+ const docs = await cursor.toArray()
24
+ docs.forEach((doc) => {
25
+ if (doc._id) {
26
+ doc._id = doc._id.toString()
27
+ }
28
+ })
29
+ return docs
30
+ },
31
+ count: async function(query, skip, limit) {
32
+ const db = await this.getClient()
33
+ const count = await db.collection(collection).count(query, { skip, limit })
34
+ return count
35
+ },
36
+ get: async function (id, fields) {
37
+ const db = await this.getClient()
38
+ const doc = await db.collection(collection).findOne({ _id: id }, { projection: fields })
39
+ if (doc) {
40
+ doc._id = doc._id.toString()
41
+ }
42
+ if (!doc) {
43
+ throw new util.ApiError(404, 'document not found')
44
+ }
45
+ return doc
46
+ },
47
+ create: async function (data) {
48
+ util.addIdIfMissing(data)
49
+
50
+ const db = await this.getClient()
51
+ try {
52
+ const doc = await db.collection(collection).insertOne(data)
53
+ return doc.insertedId.toString()
54
+ } catch (err) {
55
+ if (err && err.message && err.message.includes('duplicate key error')) {
56
+ throw new util.ApiError(409, 'document already exists')
57
+ }
58
+ }
59
+ },
60
+ update: async function (id, data) {
61
+ data._id = id
62
+ const db = await this.getClient()
63
+ await db.collection(collection).replaceOne({
64
+ _id: id
65
+ }, data)
66
+ // doc._id = doc._id.toString()
67
+ return data
68
+ },
69
+ updateWithQuery: async function (query, update, options) {
70
+ const db = await this.getClient()
71
+ const res = await db.collection(collection).updateMany(query, update, options)
72
+ return {
73
+ matchedCount: res.matchedCount
74
+ }
75
+ },
76
+ delete: async function (id) {
77
+ const db = await this.getClient()
78
+ const res = await db.collection(collection).deleteOne({
79
+ _id: id
80
+ })
81
+ if (res.result.n !== 1) {
82
+ throw new util.ApiError(404, 'document not found')
83
+ }
84
+ }
85
+ }
86
+ }
package/db/postgres.js CHANGED
@@ -1,93 +1,93 @@
1
- const util = require('../util')
2
-
3
- module.exports = function (settings, collectionId, collection) {
4
- const pool = util.getPgPool(settings.postgresql_uri)
5
-
6
- return {
7
- init: async function () {
8
- if (collection.plainStringIds) {
9
- await pool.query('CREATE TABLE IF NOT EXISTS ' + collectionId + ' (id text primary key, data jsonb)')
10
- } else {
11
- await pool.query('CREATE TABLE IF NOT EXISTS ' + collectionId + ' (id uuid primary key, data jsonb)')
12
- }
13
- },
14
- all: async function () {
15
- return this.find({})
16
- },
17
- find: async function (rawQuery, offset, limit, orderby, fields) {
18
- const pgSelect = util.mongoToPostgresSelect(collectionId, fields)
19
- const pgWhere = util.mongoToPostgresWhere(collectionId, rawQuery)
20
- let query = 'SELECT ' + pgSelect + ' FROM ' + collectionId + (pgWhere ? ' WHERE ' + pgWhere : '')
21
- if (typeof orderby !== 'undefined') {
22
- query += ' ORDER BY '
23
- query += util.mongoToPostgresOrderBy(collectionId, orderby)
24
- }
25
- if (typeof offset !== 'undefined') {
26
- query += ' OFFSET ' + offset
27
- }
28
- if (typeof limit !== 'undefined') {
29
- query += ' LIMIT ' + limit
30
- }
31
- const result = await pool.query(query)
32
- return result.rows.map((row) => row.data)
33
- },
34
- count: async function(rawQuery, offset, limit) {
35
- const pgWhere = util.mongoToPostgresWhere(collectionId, rawQuery)
36
- let query = 'SELECT COUNT(*) FROM ' + collectionId + (pgWhere ? ' WHERE ' + pgWhere : '')
37
- if (typeof offset !== 'undefined') {
38
- query += ' OFFSET ' + offset
39
- }
40
- if (typeof limit !== 'undefined') {
41
- query += ' LIMIT ' + limit
42
- }
43
- const result = await pool.query(query)
44
- return parseInt(result.rows[0].count)
45
- },
46
- get: async function (id, fields) {
47
- const pgSelect = util.mongoToPostgresSelect(collectionId, fields)
48
- const result = await pool.query(`SELECT ${pgSelect} FROM ${collectionId} WHERE id = $1`, [id])
49
- if (result.rowCount === 0) {
50
- throw new util.ApiError(404, 'document not found')
51
- }
52
- return result.rows[0].data
53
- },
54
- create: async function (data) {
55
- util.addIdIfMissing(data)
56
- try {
57
- await pool.query('INSERT INTO ' + collectionId + ' (id, data) VALUES ($1, $2)', [data._id, data])
58
- } catch (e) {
59
- if (e.message.includes('duplicate key')) {
60
- throw new util.ApiError(409, 'document already exists')
61
- }
62
- throw e
63
- }
64
- return data._id
65
- },
66
- update: async function (id, data) {
67
- if (typeof data._id === 'undefined') {
68
- data._id = id
69
- }
70
- const result = await pool.query('UPDATE ' + collectionId + ' SET data=$2,id=$3 WHERE id=$1', [id, data, data._id])
71
- if (result.rowCount === 0) {
72
- throw new util.ApiError(404, 'document not found')
73
- }
74
- return data
75
- },
76
- // eslint-disable-next-line no-unused-vars
77
- updateWithQuery: async function (query, update, options) {
78
- const pgWhere = util.mongoToPostgresWhere(collectionId, query)
79
- const updateSql = util.mongoToPostgresUpdate(collectionId, update)
80
- const result = await pool.query('UPDATE ' + collectionId + ' SET data=' + updateSql + ' WHERE ' + pgWhere)
81
- return {
82
- matchedCount: result.rowCount
83
- }
84
- },
85
- delete: async function (id) {
86
- const result = await pool.query('DELETE FROM ' + collectionId + ' WHERE id=$1', [id])
87
- if (result.rowCount === 0) {
88
- throw new util.ApiError(404, 'document not found')
89
- }
90
- return 'OK'
91
- }
92
- }
93
- }
1
+ const util = require('../util')
2
+
3
+ module.exports = function (settings, collectionId, collection) {
4
+ const pool = util.getPgPool(settings.postgresql_uri)
5
+
6
+ return {
7
+ init: async function () {
8
+ if (collection.plainStringIds) {
9
+ await pool.query('CREATE TABLE IF NOT EXISTS ' + collectionId + ' (id text primary key, data jsonb)')
10
+ } else {
11
+ await pool.query('CREATE TABLE IF NOT EXISTS ' + collectionId + ' (id uuid primary key, data jsonb)')
12
+ }
13
+ },
14
+ all: async function () {
15
+ return this.find({})
16
+ },
17
+ find: async function (rawQuery, offset, limit, orderby, fields) {
18
+ const pgSelect = util.mongoToPostgresSelect(collectionId, fields)
19
+ const pgWhere = util.mongoToPostgresWhere(collectionId, rawQuery)
20
+ let query = 'SELECT ' + pgSelect + ' FROM ' + collectionId + (pgWhere ? ' WHERE ' + pgWhere : '')
21
+ if (typeof orderby !== 'undefined') {
22
+ query += ' ORDER BY '
23
+ query += util.mongoToPostgresOrderBy(collectionId, orderby)
24
+ }
25
+ if (typeof offset !== 'undefined') {
26
+ query += ' OFFSET ' + offset
27
+ }
28
+ if (typeof limit !== 'undefined') {
29
+ query += ' LIMIT ' + limit
30
+ }
31
+ const result = await pool.query(query)
32
+ return result.rows.map((row) => row.data)
33
+ },
34
+ count: async function(rawQuery, offset, limit) {
35
+ const pgWhere = util.mongoToPostgresWhere(collectionId, rawQuery)
36
+ let query = 'SELECT COUNT(*) FROM ' + collectionId + (pgWhere ? ' WHERE ' + pgWhere : '')
37
+ if (typeof offset !== 'undefined') {
38
+ query += ' OFFSET ' + offset
39
+ }
40
+ if (typeof limit !== 'undefined') {
41
+ query += ' LIMIT ' + limit
42
+ }
43
+ const result = await pool.query(query)
44
+ return parseInt(result.rows[0].count)
45
+ },
46
+ get: async function (id, fields) {
47
+ const pgSelect = util.mongoToPostgresSelect(collectionId, fields)
48
+ const result = await pool.query(`SELECT ${pgSelect} FROM ${collectionId} WHERE id = $1`, [id])
49
+ if (result.rowCount === 0) {
50
+ throw new util.ApiError(404, 'document not found')
51
+ }
52
+ return result.rows[0].data
53
+ },
54
+ create: async function (data) {
55
+ util.addIdIfMissing(data)
56
+ try {
57
+ await pool.query('INSERT INTO ' + collectionId + ' (id, data) VALUES ($1, $2)', [data._id, data])
58
+ } catch (e) {
59
+ if (e.message.includes('duplicate key')) {
60
+ throw new util.ApiError(409, 'document already exists')
61
+ }
62
+ throw e
63
+ }
64
+ return data._id
65
+ },
66
+ update: async function (id, data) {
67
+ if (typeof data._id === 'undefined') {
68
+ data._id = id
69
+ }
70
+ const result = await pool.query('UPDATE ' + collectionId + ' SET data=$2,id=$3 WHERE id=$1', [id, data, data._id])
71
+ if (result.rowCount === 0) {
72
+ throw new util.ApiError(404, 'document not found')
73
+ }
74
+ return data
75
+ },
76
+ // eslint-disable-next-line no-unused-vars
77
+ updateWithQuery: async function (query, update, options) {
78
+ const pgWhere = util.mongoToPostgresWhere(collectionId, query)
79
+ const updateSql = util.mongoToPostgresUpdate(collectionId, update)
80
+ const result = await pool.query('UPDATE ' + collectionId + ' SET data=' + updateSql + ' WHERE ' + pgWhere)
81
+ return {
82
+ matchedCount: result.rowCount
83
+ }
84
+ },
85
+ delete: async function (id) {
86
+ const result = await pool.query('DELETE FROM ' + collectionId + ' WHERE id=$1', [id])
87
+ if (result.rowCount === 0) {
88
+ throw new util.ApiError(404, 'document not found')
89
+ }
90
+ return 'OK'
91
+ }
92
+ }
93
+ }
@@ -1,38 +1,38 @@
1
- ## Authentication
2
- Uses [JSON Web Tokens](https://jwt.io/)
3
-
4
- | method | endpoint | description |
5
- |--------|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
6
- | POST | /users/login | expects JSON in the message body. e.g. `{"email": "email@example.com", password: "<the password>"} |
7
-
8
- Returns the following
9
- ```javascript
10
- {
11
- "uid": "a52ee085-ff47-4ee1-bac3-a8a319f674ed",
12
- "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NmNkNWM5NmY4MjA4N2I1MDQ0OTM3YjEiLCJ1bml2ZXJzaXR5IjoiQllVIiwiZnVsbE5hbWUiOiJUaG9tYXMgSGFuc2VuIiwicGFzc3dvcmQiOiIkMmEkMTAkb0prdlBnTTlkR2FJRTIzaWFabGEvT0tjZC9PL3phSGFJOHFRUDBuZ2pPUVV1Ums3Vng2QkciLCJlbWFpbCI6InRoNDAxOUBnbWFpbC5jb20iLCJfX3YiOjAsImxpc3RpbmdzIjpbXSwiaWF0IjoxNDU2NDMwMjE5LCJleHAiOjE0NTY1MTY2MTl9._ijngdgwLU9AJnAjbySUgEFsR8hJCSw8PhH1AnyBHuM"
13
- }
14
- ```
15
- Or it will respond with a status code of 401 and a message explaining why they can't login.
16
-
17
- ```javascript
18
- {
19
- "success": false,
20
- "message": "Authentication failed. Wrong password."
21
- }
22
- ```
23
-
24
- The returned token must then be passed in as a header on future requests using the header x-access-token
25
-
26
- * `GET /users/me` - returns the logged in user's object
27
-
28
- ## Making authenticated requests
29
-
30
- After obtaining a token as described above you need to pass it with your request in one of these ways
31
-
32
- 1. Using the `x-access-token`
33
-
34
- `curl --header "x-access-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NmNkNWM5NmY4MjA4N2I1MDQ0OTM3YjEiLCJ1bml2ZXJzaXR5IjoiQllVIiwiZnVsbE5hbWUiOiJUaG9tYXMgSGFuc2VuIiwicGFzc3dvcmQiOiIkMmEkMTAkb0prdlBnTTlkR2FJRTIzaWFabGEvT0tjZC9PL3phSGFJOHFRUDBuZ2pPUVV1Ums3Vng2QkciLCJlbWFpbCI6InRoNDAxOUBnbWFpbC5jb20iLCJfX3YiOjAsImxpc3RpbmdzIjpbXSwiaWF0IjoxNDU2NDMwMjE5LCJleHAiOjE0NTY1MTY2MTl9._ijngdgwLU9AJnAjbySUgEFsR8hJCSw8PhH1AnyBHuM" api.example.com/users/me`
35
-
36
- 2. Pass the token as a query parameter.
37
-
38
- `curl api.example.com/users/me?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NmNkNWM5NmY4MjA4N2I1MDQ0OTM3YjEiLCJ1bml2ZXJzaXR5IjoiQllVIiwiZnVsbE5hbWUiOiJUaG9tYXMgSGFuc2VuIiwicGFzc3dvcmQiOiIkMmEkMTAkb0prdlBnTTlkR2FJRTIzaWFabGEvT0tjZC9PL3phSGFJOHFRUDBuZ2pPUVV1Ums3Vng2QkciLCJlbWFpbCI6InRoNDAxOUBnbWFpbC5jb20iLCJfX3YiOjAsImxpc3RpbmdzIjpbXSwiaWF0IjoxNDU2NDMwMjE5LCJleHAiOjE0NTY1MTY2MTl9._ijngdgwLU9AJnAjbySUgEFsR8hJCSw8PhH1AnyBHuM`
1
+ ## Authentication
2
+ Uses [JSON Web Tokens](https://jwt.io/)
3
+
4
+ | method | endpoint | description |
5
+ |--------|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
6
+ | POST | /users/login | expects JSON in the message body. e.g. `{"email": "email@example.com", password: "<the password>"} |
7
+
8
+ Returns the following
9
+ ```javascript
10
+ {
11
+ "uid": "a52ee085-ff47-4ee1-bac3-a8a319f674ed",
12
+ "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NmNkNWM5NmY4MjA4N2I1MDQ0OTM3YjEiLCJ1bml2ZXJzaXR5IjoiQllVIiwiZnVsbE5hbWUiOiJUaG9tYXMgSGFuc2VuIiwicGFzc3dvcmQiOiIkMmEkMTAkb0prdlBnTTlkR2FJRTIzaWFabGEvT0tjZC9PL3phSGFJOHFRUDBuZ2pPUVV1Ums3Vng2QkciLCJlbWFpbCI6InRoNDAxOUBnbWFpbC5jb20iLCJfX3YiOjAsImxpc3RpbmdzIjpbXSwiaWF0IjoxNDU2NDMwMjE5LCJleHAiOjE0NTY1MTY2MTl9._ijngdgwLU9AJnAjbySUgEFsR8hJCSw8PhH1AnyBHuM"
13
+ }
14
+ ```
15
+ Or it will respond with a status code of 401 and a message explaining why they can't login.
16
+
17
+ ```javascript
18
+ {
19
+ "success": false,
20
+ "message": "Authentication failed. Wrong password."
21
+ }
22
+ ```
23
+
24
+ The returned token must then be passed in as a header on future requests using the header x-access-token
25
+
26
+ * `GET /users/me` - returns the logged in user's object
27
+
28
+ ## Making authenticated requests
29
+
30
+ After obtaining a token as described above you need to pass it with your request in one of these ways
31
+
32
+ 1. Using the `x-access-token`
33
+
34
+ `curl --header "x-access-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NmNkNWM5NmY4MjA4N2I1MDQ0OTM3YjEiLCJ1bml2ZXJzaXR5IjoiQllVIiwiZnVsbE5hbWUiOiJUaG9tYXMgSGFuc2VuIiwicGFzc3dvcmQiOiIkMmEkMTAkb0prdlBnTTlkR2FJRTIzaWFabGEvT0tjZC9PL3phSGFJOHFRUDBuZ2pPUVV1Ums3Vng2QkciLCJlbWFpbCI6InRoNDAxOUBnbWFpbC5jb20iLCJfX3YiOjAsImxpc3RpbmdzIjpbXSwiaWF0IjoxNDU2NDMwMjE5LCJleHAiOjE0NTY1MTY2MTl9._ijngdgwLU9AJnAjbySUgEFsR8hJCSw8PhH1AnyBHuM" api.example.com/users/me`
35
+
36
+ 2. Pass the token as a query parameter.
37
+
38
+ `curl api.example.com/users/me?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NmNkNWM5NmY4MjA4N2I1MDQ0OTM3YjEiLCJ1bml2ZXJzaXR5IjoiQllVIiwiZnVsbE5hbWUiOiJUaG9tYXMgSGFuc2VuIiwicGFzc3dvcmQiOiIkMmEkMTAkb0prdlBnTTlkR2FJRTIzaWFabGEvT0tjZC9PL3phSGFJOHFRUDBuZ2pPUVV1Ums3Vng2QkciLCJlbWFpbCI6InRoNDAxOUBnbWFpbC5jb20iLCJfX3YiOjAsImxpc3RpbmdzIjpbXSwiaWF0IjoxNDU2NDMwMjE5LCJleHAiOjE0NTY1MTY2MTl9._ijngdgwLU9AJnAjbySUgEFsR8hJCSw8PhH1AnyBHuM`
@@ -1,12 +1,12 @@
1
- ## Automatic Fields
2
-
3
- Each object will contain this meta property which has autogenerated fields shared across all collection types:
4
-
5
- ```javascript
6
- "meta": {
7
- "created": "2016-05-16T23:56:11.615Z",
8
- "updated": "2016-05-16T23:56:28.262Z",
9
- "owner": "56cb5df7f56ef0b92f7b984b"
10
- },
11
- ```
12
-
1
+ ## Automatic Fields
2
+
3
+ Each object will contain this meta property which has autogenerated fields shared across all collection types:
4
+
5
+ ```javascript
6
+ "meta": {
7
+ "created": "2016-05-16T23:56:11.615Z",
8
+ "updated": "2016-05-16T23:56:28.262Z",
9
+ "owner": "56cb5df7f56ef0b92f7b984b"
10
+ },
11
+ ```
12
+
@@ -1,16 +1,16 @@
1
- ## Blog Example
2
-
3
- ### Creating the blog "post" collection and its schema.
4
-
5
- Here's what the UI looks like for adding this new custom collection type.
6
-
7
- ![post schema 3](https://cloud.githubusercontent.com/assets/406149/15308189/c3612ade-1b97-11e6-8f87-faeee4aba8bd.png)
8
-
9
- --------------------------------
10
-
11
- ### Creating a post
12
-
13
- After creating the blog post collection, here's what adding a document of that type looks like using the built in editor.
14
-
15
- ![create post 3](https://cloud.githubusercontent.com/assets/406149/15308188/c361368c-1b97-11e6-869a-ac40ba838dde.png)
16
-
1
+ ## Blog Example
2
+
3
+ ### Creating the blog "post" collection and its schema.
4
+
5
+ Here's what the UI looks like for adding this new custom collection type.
6
+
7
+ ![post schema 3](https://cloud.githubusercontent.com/assets/406149/15308189/c3612ade-1b97-11e6-8f87-faeee4aba8bd.png)
8
+
9
+ --------------------------------
10
+
11
+ ### Creating a post
12
+
13
+ After creating the blog post collection, here's what adding a document of that type looks like using the built in editor.
14
+
15
+ ![create post 3](https://cloud.githubusercontent.com/assets/406149/15308188/c361368c-1b97-11e6-869a-ac40ba838dde.png)
16
+