@budibase/server 1.0.46-alpha.2 → 1.0.46-alpha.6

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 (248) hide show
  1. package/builder/assets/{index.89280093.js → index.00221ff2.js} +244 -244
  2. package/builder/assets/{index.0df4f7ab.css → index.dd9e386f.css} +1 -1
  3. package/builder/assets/{vendor.10e2ad1b.js → vendor.3bd2f661.js} +1 -1
  4. package/builder/index.html +3 -3
  5. package/coverage/clover.xml +1802 -1639
  6. package/coverage/coverage-final.json +98 -94
  7. package/coverage/lcov-report/api/controllers/analytics.js.html +1 -1
  8. package/coverage/lcov-report/api/controllers/apikeys.js.html +1 -1
  9. package/coverage/lcov-report/api/controllers/application.js.html +192 -129
  10. package/coverage/lcov-report/api/controllers/auth.js.html +1 -1
  11. package/coverage/lcov-report/api/controllers/automation.js.html +21 -21
  12. package/coverage/lcov-report/api/controllers/backup.js.html +1 -1
  13. package/coverage/lcov-report/api/controllers/cloud.js.html +1 -1
  14. package/coverage/lcov-report/api/controllers/component.js.html +4 -4
  15. package/coverage/lcov-report/api/controllers/datasource.js.html +72 -36
  16. package/coverage/lcov-report/api/controllers/dev.js.html +1 -1
  17. package/coverage/lcov-report/api/controllers/index.html +27 -42
  18. package/coverage/lcov-report/api/controllers/integration.js.html +1 -1
  19. package/coverage/lcov-report/api/controllers/layout.js.html +6 -6
  20. package/coverage/lcov-report/api/controllers/metadata.js.html +1 -1
  21. package/coverage/lcov-report/api/controllers/permission.js.html +15 -15
  22. package/coverage/lcov-report/api/controllers/query/index.html +15 -15
  23. package/coverage/lcov-report/api/controllers/query/index.js.html +61 -28
  24. package/coverage/lcov-report/api/controllers/query/validation.js.html +6 -6
  25. package/coverage/lcov-report/api/controllers/role.js.html +9 -9
  26. package/coverage/lcov-report/api/controllers/routing.js.html +1 -1
  27. package/coverage/lcov-report/api/controllers/row/external.js.html +16 -16
  28. package/coverage/lcov-report/api/controllers/row/index.html +5 -5
  29. package/coverage/lcov-report/api/controllers/row/index.js.html +26 -26
  30. package/coverage/lcov-report/api/controllers/row/internal.js.html +44 -44
  31. package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +8 -8
  32. package/coverage/lcov-report/api/controllers/row/utils.js.html +29 -38
  33. package/coverage/lcov-report/api/controllers/screen.js.html +7 -7
  34. package/coverage/lcov-report/api/controllers/script.js.html +4 -4
  35. package/coverage/lcov-report/api/controllers/table/external.js.html +12 -12
  36. package/coverage/lcov-report/api/controllers/table/index.html +16 -16
  37. package/coverage/lcov-report/api/controllers/table/index.js.html +25 -25
  38. package/coverage/lcov-report/api/controllers/table/internal.js.html +40 -34
  39. package/coverage/lcov-report/api/controllers/table/utils.js.html +76 -61
  40. package/coverage/lcov-report/api/controllers/templates.js.html +1 -1
  41. package/coverage/lcov-report/api/controllers/user.js.html +20 -20
  42. package/coverage/lcov-report/api/controllers/view/exporters.js.html +4 -4
  43. package/coverage/lcov-report/api/controllers/view/index.html +1 -1
  44. package/coverage/lcov-report/api/controllers/view/index.js.html +14 -14
  45. package/coverage/lcov-report/api/controllers/view/utils.js.html +11 -11
  46. package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +7 -7
  47. package/coverage/lcov-report/api/controllers/webhook.js.html +15 -15
  48. package/coverage/lcov-report/api/index.html +1 -1
  49. package/coverage/lcov-report/api/index.js.html +10 -10
  50. package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
  51. package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
  52. package/coverage/lcov-report/api/routes/application.js.html +1 -1
  53. package/coverage/lcov-report/api/routes/auth.js.html +1 -1
  54. package/coverage/lcov-report/api/routes/automation.js.html +1 -1
  55. package/coverage/lcov-report/api/routes/backup.js.html +1 -1
  56. package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
  57. package/coverage/lcov-report/api/routes/component.js.html +1 -1
  58. package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
  59. package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
  60. package/coverage/lcov-report/api/routes/dev.js.html +1 -1
  61. package/coverage/lcov-report/api/routes/index.html +7 -22
  62. package/coverage/lcov-report/api/routes/index.js.html +4 -10
  63. package/coverage/lcov-report/api/routes/integration.js.html +1 -1
  64. package/coverage/lcov-report/api/routes/layout.js.html +1 -1
  65. package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
  66. package/coverage/lcov-report/api/routes/permission.js.html +1 -1
  67. package/coverage/lcov-report/api/routes/query.js.html +1 -1
  68. package/coverage/lcov-report/api/routes/role.js.html +1 -1
  69. package/coverage/lcov-report/api/routes/routing.js.html +1 -1
  70. package/coverage/lcov-report/api/routes/row.js.html +1 -1
  71. package/coverage/lcov-report/api/routes/screen.js.html +1 -1
  72. package/coverage/lcov-report/api/routes/script.js.html +1 -1
  73. package/coverage/lcov-report/api/routes/static.js.html +17 -2
  74. package/coverage/lcov-report/api/routes/table.js.html +1 -1
  75. package/coverage/lcov-report/api/routes/templates.js.html +1 -1
  76. package/coverage/lcov-report/api/routes/user.js.html +1 -1
  77. package/coverage/lcov-report/api/routes/view.js.html +1 -1
  78. package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
  79. package/coverage/lcov-report/automations/actions.js.html +24 -24
  80. package/coverage/lcov-report/automations/automationUtils.js.html +5 -5
  81. package/coverage/lcov-report/automations/bullboard.js.html +16 -16
  82. package/coverage/lcov-report/automations/index.html +1 -1
  83. package/coverage/lcov-report/automations/index.js.html +1 -1
  84. package/coverage/lcov-report/automations/steps/bash.js.html +6 -6
  85. package/coverage/lcov-report/automations/steps/createRow.js.html +13 -19
  86. package/coverage/lcov-report/automations/steps/delay.js.html +4 -4
  87. package/coverage/lcov-report/automations/steps/deleteRow.js.html +11 -20
  88. package/coverage/lcov-report/automations/steps/discord.js.html +7 -7
  89. package/coverage/lcov-report/automations/steps/executeQuery.js.html +6 -6
  90. package/coverage/lcov-report/automations/steps/executeScript.js.html +6 -6
  91. package/coverage/lcov-report/automations/steps/filter.js.html +7 -7
  92. package/coverage/lcov-report/automations/steps/index.html +13 -13
  93. package/coverage/lcov-report/automations/steps/integromat.js.html +5 -5
  94. package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +8 -8
  95. package/coverage/lcov-report/automations/steps/queryRows.js.html +10 -10
  96. package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +5 -5
  97. package/coverage/lcov-report/automations/steps/serverLog.js.html +3 -3
  98. package/coverage/lcov-report/automations/steps/slack.js.html +5 -5
  99. package/coverage/lcov-report/automations/steps/updateRow.js.html +6 -6
  100. package/coverage/lcov-report/automations/steps/utils.js.html +3 -3
  101. package/coverage/lcov-report/automations/steps/zapier.js.html +5 -5
  102. package/coverage/lcov-report/automations/triggerInfo/app.js.html +2 -2
  103. package/coverage/lcov-report/automations/triggerInfo/cron.js.html +2 -2
  104. package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
  105. package/coverage/lcov-report/automations/triggerInfo/index.js.html +8 -8
  106. package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +2 -2
  107. package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +2 -2
  108. package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +2 -2
  109. package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +2 -2
  110. package/coverage/lcov-report/automations/triggers.js.html +18 -18
  111. package/coverage/lcov-report/automations/utils.js.html +32 -32
  112. package/coverage/lcov-report/constants/index.html +5 -5
  113. package/coverage/lcov-report/constants/index.js.html +36 -24
  114. package/coverage/lcov-report/constants/layouts.js.html +5 -5
  115. package/coverage/lcov-report/constants/screens.js.html +4 -4
  116. package/coverage/lcov-report/db/client.js.html +18 -18
  117. package/coverage/lcov-report/db/inMemoryView.js.html +7 -7
  118. package/coverage/lcov-report/db/index.html +15 -15
  119. package/coverage/lcov-report/db/index.js.html +3 -3
  120. package/coverage/lcov-report/db/linkedRows/LinkController.js.html +18 -18
  121. package/coverage/lcov-report/db/linkedRows/index.html +1 -1
  122. package/coverage/lcov-report/db/linkedRows/index.js.html +31 -31
  123. package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +15 -15
  124. package/coverage/lcov-report/db/newid.js.html +4 -4
  125. package/coverage/lcov-report/db/utils.js.html +85 -85
  126. package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
  127. package/coverage/lcov-report/events/index.html +1 -1
  128. package/coverage/lcov-report/events/index.js.html +5 -5
  129. package/coverage/lcov-report/events/utils.js.html +3 -3
  130. package/coverage/lcov-report/index.html +110 -80
  131. package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
  132. package/coverage/lcov-report/integrations/tests/index.html +1 -1
  133. package/coverage/lcov-report/middleware/appInfo.js.html +1 -1
  134. package/coverage/lcov-report/middleware/authorized.js.html +11 -11
  135. package/coverage/lcov-report/middleware/builder.js.html +8 -8
  136. package/coverage/lcov-report/middleware/currentapp.js.html +34 -61
  137. package/coverage/lcov-report/middleware/index.html +25 -25
  138. package/coverage/lcov-report/middleware/joi-validator.js.html +3 -3
  139. package/coverage/lcov-report/middleware/resourceId.js.html +1 -1
  140. package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
  141. package/coverage/lcov-report/middleware/usageQuota.js.html +275 -41
  142. package/coverage/lcov-report/middleware/utils.js.html +2 -2
  143. package/coverage/lcov-report/migrations/usageQuotas/index.html +141 -0
  144. package/coverage/lcov-report/migrations/usageQuotas/index.js.html +152 -0
  145. package/coverage/lcov-report/{api/routes/hosting.js.html → migrations/usageQuotas/syncApps.js.html} +42 -27
  146. package/coverage/lcov-report/{api/controllers/hosting.js.html → migrations/usageQuotas/syncRows.js.html} +41 -44
  147. package/coverage/lcov-report/threads/automation.js.html +1 -1
  148. package/coverage/lcov-report/threads/index.html +15 -15
  149. package/coverage/lcov-report/threads/index.js.html +10 -10
  150. package/coverage/lcov-report/threads/query.js.html +23 -8
  151. package/coverage/lcov-report/threads/utils.js.html +14 -14
  152. package/coverage/lcov-report/utilities/bcrypt.js.html +1 -1
  153. package/coverage/lcov-report/utilities/budibaseDir.js.html +7 -7
  154. package/coverage/lcov-report/utilities/centralPath.js.html +4 -4
  155. package/coverage/lcov-report/utilities/csvParser.js.html +6 -6
  156. package/coverage/lcov-report/utilities/global.js.html +26 -26
  157. package/coverage/lcov-report/utilities/index.html +19 -19
  158. package/coverage/lcov-report/utilities/index.js.html +25 -25
  159. package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +8 -8
  160. package/coverage/lcov-report/utilities/queue/index.html +1 -1
  161. package/coverage/lcov-report/utilities/routing/index.html +1 -1
  162. package/coverage/lcov-report/utilities/routing/index.js.html +1 -1
  163. package/coverage/lcov-report/utilities/rowProcessor/index.html +1 -1
  164. package/coverage/lcov-report/utilities/rowProcessor/index.js.html +53 -53
  165. package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +10 -10
  166. package/coverage/lcov-report/utilities/scriptRunner.js.html +5 -5
  167. package/coverage/lcov-report/utilities/security.js.html +8 -8
  168. package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
  169. package/coverage/lcov-report/utilities/usageQuota/index.html +141 -0
  170. package/coverage/lcov-report/utilities/usageQuota/index.js.html +356 -0
  171. package/coverage/lcov-report/utilities/usageQuota/rows.js.html +302 -0
  172. package/coverage/lcov-report/utilities/usageQuota/usageQuoteReset.js.html +134 -0
  173. package/coverage/lcov-report/utilities/users.js.html +5 -5
  174. package/coverage/lcov-report/utilities/workerRequests.js.html +39 -108
  175. package/coverage/lcov.info +2644 -2356
  176. package/dist/api/controllers/application.js +45 -24
  177. package/dist/api/controllers/datasource.js +11 -0
  178. package/dist/api/controllers/query/index.js +11 -3
  179. package/dist/api/controllers/row/utils.js +1 -2
  180. package/dist/api/controllers/static/index.js +59 -10
  181. package/dist/api/controllers/table/internal.js +2 -0
  182. package/dist/api/controllers/table/utils.js +5 -0
  183. package/dist/api/routes/index.js +0 -2
  184. package/dist/api/routes/static.js +2 -1
  185. package/dist/automations/steps/createRow.js +2 -4
  186. package/dist/automations/steps/deleteRow.js +1 -4
  187. package/dist/constants/index.js +3 -0
  188. package/dist/definitions/datasource.js +1 -0
  189. package/dist/environment.js +1 -0
  190. package/dist/integrations/googlesheets.js +340 -0
  191. package/dist/integrations/index.js +3 -0
  192. package/dist/integrations/utils.js +6 -3
  193. package/dist/middleware/currentapp.js +0 -9
  194. package/dist/middleware/usageQuota.js +82 -13
  195. package/dist/migrations/usageQuotas/index.js +24 -0
  196. package/dist/migrations/usageQuotas/syncApps.js +26 -0
  197. package/dist/migrations/usageQuotas/syncRows.js +29 -0
  198. package/dist/package.json +8 -5
  199. package/dist/tests/utilities/TestConfiguration.js +3 -1
  200. package/dist/threads/query.js +4 -0
  201. package/dist/tsconfig.tsbuildinfo +1 -1
  202. package/dist/utilities/{usageQuota.js → usageQuota/index.js} +44 -31
  203. package/dist/utilities/usageQuota/rows.js +77 -0
  204. package/dist/utilities/usageQuota/usageQuoteReset.js +16 -0
  205. package/dist/utilities/workerRequests.js +0 -20
  206. package/nodemon.json +1 -1
  207. package/package.json +9 -6
  208. package/src/api/controllers/application.js +48 -27
  209. package/src/api/controllers/datasource.js +12 -0
  210. package/src/api/controllers/query/index.js +14 -3
  211. package/src/api/controllers/row/utils.js +1 -4
  212. package/src/api/controllers/static/index.js +63 -10
  213. package/src/api/controllers/table/internal.js +2 -0
  214. package/src/api/controllers/table/utils.js +5 -0
  215. package/src/api/routes/index.js +0 -2
  216. package/src/api/routes/static.js +5 -0
  217. package/src/api/routes/tests/application.spec.js +2 -2
  218. package/src/api/routes/tests/static.spec.js +98 -0
  219. package/src/automations/steps/createRow.js +2 -4
  220. package/src/automations/steps/deleteRow.js +1 -4
  221. package/src/constants/index.js +4 -0
  222. package/src/definitions/datasource.ts +3 -0
  223. package/src/environment.js +1 -0
  224. package/src/integrations/googlesheets.ts +353 -0
  225. package/src/integrations/index.ts +3 -0
  226. package/src/integrations/utils.ts +18 -5
  227. package/src/middleware/currentapp.js +0 -9
  228. package/src/middleware/tests/usageQuota.spec.js +18 -24
  229. package/src/middleware/usageQuota.js +93 -15
  230. package/src/migrations/tests/usageQuotas/index.spec.js +27 -0
  231. package/src/migrations/tests/usageQuotas/syncApps.spec.js +37 -0
  232. package/src/migrations/tests/usageQuotas/syncRows.spec.js +43 -0
  233. package/src/migrations/usageQuotas/index.js +24 -0
  234. package/src/migrations/usageQuotas/syncApps.js +18 -0
  235. package/src/migrations/usageQuotas/syncRows.js +21 -0
  236. package/src/tests/utilities/TestConfiguration.js +3 -1
  237. package/src/threads/query.js +5 -0
  238. package/src/utilities/tests/usageQuota/usageQuota.spec.js +72 -0
  239. package/src/utilities/usageQuota/index.js +92 -0
  240. package/src/utilities/usageQuota/rows.js +74 -0
  241. package/src/utilities/usageQuota/usageQuoteReset.js +18 -0
  242. package/src/utilities/workerRequests.js +0 -23
  243. package/dist/api/controllers/hosting.js +0 -30
  244. package/dist/api/routes/hosting.js +0 -11
  245. package/src/api/controllers/hosting.js +0 -22
  246. package/src/api/routes/hosting.js +0 -13
  247. package/src/api/routes/tests/hosting.spec.js +0 -36
  248. package/src/utilities/usageQuota.js +0 -73
@@ -1,14 +1,11 @@
1
1
  const CouchDB = require("../db")
2
2
  const usageQuota = require("../utilities/usageQuota")
3
- const env = require("../environment")
4
- const { getTenantId } = require("@budibase/backend-core/tenancy")
3
+ const { getUniqueRows } = require("../utilities/usageQuota/rows")
5
4
  const {
6
5
  isExternalTable,
7
6
  isRowId: isExternalRowId,
8
7
  } = require("../integrations/utils")
9
-
10
- // tenants without limits
11
- const EXCLUDED_TENANTS = ["bb", "default", "bbtest", "bbstaging"]
8
+ const migration = require("../migrations/usageQuotas")
12
9
 
13
10
  // currently only counting new writes and deletes
14
11
  const METHOD_MAP = {
@@ -18,13 +15,13 @@ const METHOD_MAP = {
18
15
 
19
16
  const DOMAIN_MAP = {
20
17
  rows: usageQuota.Properties.ROW,
21
- upload: usageQuota.Properties.UPLOAD,
22
- views: usageQuota.Properties.VIEW,
23
- users: usageQuota.Properties.USER,
18
+ // upload: usageQuota.Properties.UPLOAD, // doesn't work yet
19
+ // views: usageQuota.Properties.VIEW, // doesn't work yet
20
+ // users: usageQuota.Properties.USER, // doesn't work yet
24
21
  applications: usageQuota.Properties.APPS,
25
22
  // this will not be updated by endpoint calls
26
23
  // instead it will be updated by triggerInfo
27
- automationRuns: usageQuota.Properties.AUTOMATION,
24
+ // automationRuns: usageQuota.Properties.AUTOMATION, // doesn't work yet
28
25
  }
29
26
 
30
27
  function getProperty(url) {
@@ -36,10 +33,7 @@ function getProperty(url) {
36
33
  }
37
34
 
38
35
  module.exports = async (ctx, next) => {
39
- const tenantId = getTenantId()
40
-
41
- // if in development or a self hosted cloud usage quotas should not be executed
42
- if (env.isDev() || env.SELF_HOSTED || EXCLUDED_TENANTS.includes(tenantId)) {
36
+ if (!usageQuota.useQuotas()) {
43
37
  return next()
44
38
  }
45
39
 
@@ -80,9 +74,93 @@ module.exports = async (ctx, next) => {
80
74
  usage = files.map(file => file.size).reduce((total, size) => total + size)
81
75
  }
82
76
  try {
83
- await usageQuota.update(property, usage)
84
- return next()
77
+ await migration.run()
78
+ await performRequest(ctx, next, property, usage)
85
79
  } catch (err) {
86
80
  ctx.throw(400, err)
87
81
  }
88
82
  }
83
+
84
+ const performRequest = async (ctx, next, property, usage) => {
85
+ const usageContext = {
86
+ skipNext: false,
87
+ skipUsage: false,
88
+ [usageQuota.Properties.APPS]: {},
89
+ }
90
+
91
+ if (usage === -1) {
92
+ if (PRE_DELETE[property]) {
93
+ await PRE_DELETE[property](ctx, usageContext)
94
+ }
95
+ } else {
96
+ if (PRE_CREATE[property]) {
97
+ await PRE_CREATE[property](ctx, usageContext)
98
+ }
99
+ }
100
+
101
+ // run the request
102
+ if (!usageContext.skipNext) {
103
+ await usageQuota.update(property, usage, { dryRun: true })
104
+ await next()
105
+ }
106
+
107
+ if (usage === -1) {
108
+ if (POST_DELETE[property]) {
109
+ await POST_DELETE[property](ctx, usageContext)
110
+ }
111
+ } else {
112
+ if (POST_CREATE[property]) {
113
+ await POST_CREATE[property](ctx, usageContext)
114
+ }
115
+ }
116
+
117
+ // update the usage
118
+ if (!usageContext.skipUsage) {
119
+ await usageQuota.update(property, usage)
120
+ }
121
+ }
122
+
123
+ const appPreDelete = async (ctx, usageContext) => {
124
+ if (ctx.query.unpublish) {
125
+ // don't run usage decrement for unpublish
126
+ usageContext.skipUsage = true
127
+ return
128
+ }
129
+
130
+ // store the row count to delete
131
+ const rows = await getUniqueRows([ctx.appId])
132
+ if (rows.length) {
133
+ usageContext[usageQuota.Properties.APPS] = { rowCount: rows.length }
134
+ }
135
+ }
136
+
137
+ const appPostDelete = async (ctx, usageContext) => {
138
+ // delete the app rows from usage
139
+ const rowCount = usageContext[usageQuota.Properties.APPS].rowCount
140
+ if (rowCount) {
141
+ await usageQuota.update(usageQuota.Properties.ROW, -rowCount)
142
+ }
143
+ }
144
+
145
+ const appPostCreate = async ctx => {
146
+ // app import & template creation
147
+ if (ctx.request.body.useTemplate === "true") {
148
+ const rows = await getUniqueRows([ctx.response.body.appId])
149
+ const rowCount = rows ? rows.length : 0
150
+ await usageQuota.update(usageQuota.Properties.ROW, rowCount)
151
+ }
152
+ }
153
+
154
+ const PRE_DELETE = {
155
+ [usageQuota.Properties.APPS]: appPreDelete,
156
+ }
157
+
158
+ const POST_DELETE = {
159
+ [usageQuota.Properties.APPS]: appPostDelete,
160
+ }
161
+
162
+ const PRE_CREATE = {}
163
+
164
+ const POST_CREATE = {
165
+ [usageQuota.Properties.APPS]: appPostCreate,
166
+ }
@@ -0,0 +1,27 @@
1
+ const env = require("../../../environment")
2
+ const TestConfig = require("../../../tests/utilities/TestConfiguration")
3
+
4
+ const syncApps = jest.fn()
5
+ const syncRows = jest.fn()
6
+
7
+ jest.mock("../../usageQuotas/syncApps", () => ({ run: syncApps }) )
8
+ jest.mock("../../usageQuotas/syncRows", () => ({ run: syncRows }) )
9
+
10
+ const migrations = require("../../usageQuotas")
11
+
12
+ describe("run", () => {
13
+ let config = new TestConfig(false)
14
+
15
+ beforeEach(async () => {
16
+ await config.init()
17
+ env._set("USE_QUOTAS", 1)
18
+ })
19
+
20
+ afterAll(config.end)
21
+
22
+ it("runs the required migrations", async () => {
23
+ await migrations.run()
24
+ expect(syncApps).toHaveBeenCalledTimes(1)
25
+ expect(syncRows).toHaveBeenCalledTimes(1)
26
+ })
27
+ })
@@ -0,0 +1,37 @@
1
+ const { getGlobalDB } = require("@budibase/backend-core/tenancy")
2
+ const TestConfig = require("../../../tests/utilities/TestConfiguration")
3
+ const { getUsageQuotaDoc, update, Properties } = require("../../../utilities/usageQuota")
4
+ const syncApps = require("../../usageQuotas/syncApps")
5
+ const env = require("../../../environment")
6
+
7
+ describe("syncApps", () => {
8
+ let config = new TestConfig(false)
9
+
10
+ beforeEach(async () => {
11
+ await config.init()
12
+ env._set("USE_QUOTAS", 1)
13
+ })
14
+
15
+ afterAll(config.end)
16
+
17
+ it("runs successfully", async () => {
18
+ // create the usage quota doc and mock usages
19
+ const db = getGlobalDB()
20
+ await getUsageQuotaDoc(db)
21
+ await update(Properties.APPS, 3)
22
+
23
+ let usageDoc = await getUsageQuotaDoc(db)
24
+ expect(usageDoc.usageQuota.apps).toEqual(3)
25
+
26
+ // create an extra app to test the migration
27
+ await config.createApp("quota-test")
28
+
29
+ // migrate
30
+ await syncApps.run()
31
+
32
+ // assert the migration worked
33
+ usageDoc = await getUsageQuotaDoc(db)
34
+ expect(usageDoc.usageQuota.apps).toEqual(2)
35
+ })
36
+ })
37
+
@@ -0,0 +1,43 @@
1
+ const { getGlobalDB } = require("@budibase/backend-core/tenancy")
2
+ const TestConfig = require("../../../tests/utilities/TestConfiguration")
3
+ const { getUsageQuotaDoc, update, Properties } = require("../../../utilities/usageQuota")
4
+ const syncRows = require("../../usageQuotas/syncRows")
5
+ const env = require("../../../environment")
6
+
7
+ describe("syncRows", () => {
8
+ let config = new TestConfig(false)
9
+
10
+ beforeEach(async () => {
11
+ await config.init()
12
+ env._set("USE_QUOTAS", 1)
13
+ })
14
+
15
+ afterAll(config.end)
16
+
17
+ it("runs successfully", async () => {
18
+ // create the usage quota doc and mock usages
19
+ const db = getGlobalDB()
20
+ await getUsageQuotaDoc(db)
21
+ await update(Properties.ROW, 300)
22
+
23
+ let usageDoc = await getUsageQuotaDoc(db)
24
+ expect(usageDoc.usageQuota.rows).toEqual(300)
25
+
26
+ // app 1
27
+ await config.createTable()
28
+ await config.createRow()
29
+ // app 2
30
+ await config.createApp("second-app")
31
+ await config.createTable()
32
+ await config.createRow()
33
+ await config.createRow()
34
+
35
+ // migrate
36
+ await syncRows.run()
37
+
38
+ // assert the migration worked
39
+ usageDoc = await getUsageQuotaDoc(db)
40
+ expect(usageDoc.usageQuota.rows).toEqual(3)
41
+ })
42
+ })
43
+
@@ -0,0 +1,24 @@
1
+ const {
2
+ MIGRATIONS,
3
+ MIGRATION_DBS,
4
+ migrateIfRequired,
5
+ } = require("@budibase/backend-core/migrations")
6
+ const { useQuotas } = require("../../utilities/usageQuota")
7
+ const syncApps = require("./syncApps")
8
+ const syncRows = require("./syncRows")
9
+
10
+ exports.run = async () => {
11
+ if (!useQuotas()) {
12
+ return
13
+ }
14
+
15
+ // Jan 2022
16
+ await migrateIfRequired(
17
+ MIGRATION_DBS.GLOBAL_DB,
18
+ MIGRATIONS.QUOTAS_1,
19
+ async () => {
20
+ await syncApps.run()
21
+ await syncRows.run()
22
+ }
23
+ )
24
+ }
@@ -0,0 +1,18 @@
1
+ const { getGlobalDB, getTenantId } = require("@budibase/backend-core/tenancy")
2
+ const { getAllApps } = require("@budibase/backend-core/db")
3
+ const CouchDB = require("../../db")
4
+ const { getUsageQuotaDoc } = require("../../utilities/usageQuota")
5
+
6
+ exports.run = async () => {
7
+ const db = getGlobalDB()
8
+ // get app count
9
+ const devApps = await getAllApps(CouchDB, { dev: true })
10
+ const appCount = devApps ? devApps.length : 0
11
+
12
+ // sync app count
13
+ const tenantId = getTenantId()
14
+ console.log(`[Tenant: ${tenantId}] Syncing app count: ${appCount}`)
15
+ const usageDoc = await getUsageQuotaDoc(db)
16
+ usageDoc.usageQuota.apps = appCount
17
+ await db.put(usageDoc)
18
+ }
@@ -0,0 +1,21 @@
1
+ const { getGlobalDB, getTenantId } = require("@budibase/backend-core/tenancy")
2
+ const { getAllApps } = require("@budibase/backend-core/db")
3
+ const CouchDB = require("../../db")
4
+ const { getUsageQuotaDoc } = require("../../utilities/usageQuota")
5
+ const { getUniqueRows } = require("../../utilities/usageQuota/rows")
6
+
7
+ exports.run = async () => {
8
+ const db = getGlobalDB()
9
+ // get all rows in all apps
10
+ const allApps = await getAllApps(CouchDB, { all: true })
11
+ const appIds = allApps ? allApps.map(app => app.appId) : []
12
+ const rows = await getUniqueRows(appIds)
13
+ const rowCount = rows ? rows.length : 0
14
+
15
+ // sync row count
16
+ const tenantId = getTenantId()
17
+ console.log(`[Tenant: ${tenantId}] Syncing row count: ${rowCount}`)
18
+ const usageDoc = await getUsageQuotaDoc(db)
19
+ usageDoc.usageQuota.rows = rowCount
20
+ await db.put(usageDoc)
21
+ }
@@ -22,6 +22,7 @@ const { getGlobalDB } = require("@budibase/backend-core/tenancy")
22
22
  const { createASession } = require("@budibase/backend-core/sessions")
23
23
  const { user: userCache } = require("@budibase/backend-core/cache")
24
24
  const CouchDB = require("../../db")
25
+ const newid = require("../../db/newid")
25
26
  core.init(CouchDB)
26
27
 
27
28
  const GLOBAL_USER_ID = "us_uuid1"
@@ -98,7 +99,8 @@ class TestConfiguration {
98
99
  }
99
100
  }
100
101
 
101
- async init(appName = "test_application") {
102
+ // use a new id as the name to avoid name collisions
103
+ async init(appName = newid()) {
102
104
  await this.globalUser()
103
105
  return this.createApp(appName)
104
106
  }
@@ -78,6 +78,11 @@ class QueryRunner {
78
78
  return this.execute()
79
79
  }
80
80
 
81
+ // check for undefined response
82
+ if (!rows) {
83
+ rows = []
84
+ }
85
+
81
86
  // needs to an array for next step
82
87
  if (!Array.isArray(rows)) {
83
88
  rows = [rows]
@@ -0,0 +1,72 @@
1
+ const getTenantId = jest.fn()
2
+ jest.mock("@budibase/backend-core/tenancy", () => ({
3
+ getTenantId
4
+ }))
5
+ const usageQuota = require("../../usageQuota")
6
+ const env = require("../../../environment")
7
+
8
+ class TestConfiguration {
9
+ constructor() {
10
+ this.enableQuotas()
11
+ }
12
+
13
+ enableQuotas = () => {
14
+ env.USE_QUOTAS = 1
15
+ }
16
+
17
+ disableQuotas = () => {
18
+ env.USE_QUOTAS = null
19
+ }
20
+
21
+ setTenantId = (tenantId) => {
22
+ getTenantId.mockReturnValue(tenantId)
23
+ }
24
+
25
+ setExcludedTenants = (tenants) => {
26
+ env.EXCLUDE_QUOTAS_TENANTS = tenants
27
+ }
28
+
29
+ reset = () => {
30
+ this.disableQuotas()
31
+ this.setExcludedTenants(null)
32
+ }
33
+ }
34
+
35
+ describe("usageQuota", () => {
36
+ let config
37
+
38
+ beforeEach(() => {
39
+ config = new TestConfiguration()
40
+ })
41
+
42
+ afterEach(() => {
43
+ config.reset()
44
+ })
45
+
46
+ describe("useQuotas", () => {
47
+ it("works when no settings have been provided", () => {
48
+ config.reset()
49
+ expect(usageQuota.useQuotas()).toBe(false)
50
+ })
51
+ it("honours USE_QUOTAS setting", () => {
52
+ config.disableQuotas()
53
+ expect(usageQuota.useQuotas()).toBe(false)
54
+
55
+ config.enableQuotas()
56
+ expect(usageQuota.useQuotas()).toBe(true)
57
+ })
58
+ it("honours EXCLUDE_QUOTAS_TENANTS setting", () => {
59
+ config.setTenantId("test")
60
+
61
+ // tenantId is in the list
62
+ config.setExcludedTenants("test, test2, test2")
63
+ expect(usageQuota.useQuotas()).toBe(false)
64
+ config.setExcludedTenants("test,test2,test2")
65
+ expect(usageQuota.useQuotas()).toBe(false)
66
+
67
+ // tenantId is not in the list
68
+ config.setTenantId("other")
69
+ expect(usageQuota.useQuotas()).toBe(true)
70
+ })
71
+ })
72
+ })
@@ -0,0 +1,92 @@
1
+ const env = require("../../environment")
2
+ const { getGlobalDB, getTenantId } = require("@budibase/backend-core/tenancy")
3
+ const {
4
+ StaticDatabases,
5
+ generateNewUsageQuotaDoc,
6
+ } = require("@budibase/backend-core/db")
7
+
8
+ exports.useQuotas = () => {
9
+ // check if quotas are enabled
10
+ if (env.USE_QUOTAS) {
11
+ // check if there are any tenants without limits
12
+ if (env.EXCLUDE_QUOTAS_TENANTS) {
13
+ const excludedTenants = env.EXCLUDE_QUOTAS_TENANTS.replace(
14
+ /\s/g,
15
+ ""
16
+ ).split(",")
17
+ const tenantId = getTenantId()
18
+ if (excludedTenants.includes(tenantId)) {
19
+ return false
20
+ }
21
+ }
22
+ return true
23
+ }
24
+ return false
25
+ }
26
+
27
+ exports.Properties = {
28
+ ROW: "rows",
29
+ UPLOAD: "storage", // doesn't work yet
30
+ VIEW: "views", // doesn't work yet
31
+ USER: "users", // doesn't work yet
32
+ AUTOMATION: "automationRuns", // doesn't work yet
33
+ APPS: "apps",
34
+ EMAILS: "emails", // doesn't work yet
35
+ }
36
+
37
+ exports.getUsageQuotaDoc = async db => {
38
+ let quota
39
+ try {
40
+ quota = await db.get(StaticDatabases.GLOBAL.docs.usageQuota)
41
+ } catch (err) {
42
+ // doc doesn't exist. Create it
43
+ quota = generateNewUsageQuotaDoc()
44
+ const response = await db.put(quota)
45
+ quota._rev = response.rev
46
+ }
47
+
48
+ return quota
49
+ }
50
+
51
+ /**
52
+ * Given a specified tenantId this will add to the usage object for the specified property.
53
+ * @param {string} property The property which is to be added to (within the nested usageQuota object).
54
+ * @param {number} usage The amount (this can be negative) to adjust the number by.
55
+ * @returns {Promise<void>} When this completes the API key will now be up to date - the quota period may have
56
+ * also been reset after this call.
57
+ */
58
+ exports.update = async (property, usage, opts = { dryRun: false }) => {
59
+ if (!exports.useQuotas()) {
60
+ return
61
+ }
62
+
63
+ try {
64
+ const db = getGlobalDB()
65
+ const quota = await exports.getUsageQuotaDoc(db)
66
+
67
+ // increment the quota
68
+ quota.usageQuota[property] += usage
69
+
70
+ if (
71
+ quota.usageQuota[property] > quota.usageLimits[property] &&
72
+ usage > 0 // allow for decrementing usage when the quota is already exceeded
73
+ ) {
74
+ throw new Error(
75
+ `You have exceeded your usage quota of ${quota.usageLimits[property]} ${property}.`
76
+ )
77
+ }
78
+
79
+ if (quota.usageQuota[property] < 0) {
80
+ // never go negative if the quota has previously been exceeded
81
+ quota.usageQuota[property] = 0
82
+ }
83
+
84
+ // update the usage quotas
85
+ if (!opts.dryRun) {
86
+ await db.put(quota)
87
+ }
88
+ } catch (err) {
89
+ console.error(`Error updating usage quotas for ${property}`, err)
90
+ throw err
91
+ }
92
+ }
@@ -0,0 +1,74 @@
1
+ const { getRowParams, USER_METDATA_PREFIX } = require("../../db/utils")
2
+ const CouchDB = require("../../db")
3
+ const { isDevAppID, getDevelopmentAppID } = require("@budibase/backend-core/db")
4
+
5
+ const ROW_EXCLUSIONS = [USER_METDATA_PREFIX]
6
+
7
+ const getAppPairs = appIds => {
8
+ // collect the app ids into dev / prod pairs
9
+ // keyed by the dev app id
10
+ const pairs = {}
11
+ for (let appId of appIds) {
12
+ const devId = getDevelopmentAppID(appId)
13
+ if (!pairs[devId]) {
14
+ pairs[devId] = {}
15
+ }
16
+ if (isDevAppID(appId)) {
17
+ pairs[devId].devId = appId
18
+ } else {
19
+ pairs[devId].prodId = appId
20
+ }
21
+ }
22
+ return pairs
23
+ }
24
+
25
+ const getAppRows = async appId => {
26
+ const appDb = new CouchDB(appId)
27
+ const response = await appDb.allDocs(
28
+ getRowParams(null, null, {
29
+ include_docs: false,
30
+ })
31
+ )
32
+ return response.rows
33
+ .map(r => r.id)
34
+ .filter(id => {
35
+ for (let exclusion of ROW_EXCLUSIONS) {
36
+ if (id.startsWith(exclusion)) {
37
+ return false
38
+ }
39
+ }
40
+ return true
41
+ })
42
+ }
43
+
44
+ /**
45
+ * Return a set of all rows in the given app ids.
46
+ * The returned rows will be unique on a per dev/prod app basis.
47
+ * Rows duplicates may exist across apps due to data import so they are not filtered out.
48
+ */
49
+ exports.getUniqueRows = async appIds => {
50
+ let uniqueRows = []
51
+ const pairs = getAppPairs(appIds)
52
+
53
+ for (let pair of Object.values(pairs)) {
54
+ let appRows = []
55
+ for (let appId of [pair.devId, pair.prodId]) {
56
+ if (!appId) {
57
+ continue
58
+ }
59
+ try {
60
+ appRows.push(await getAppRows(appId))
61
+ } catch (e) {
62
+ console.error(e)
63
+ // don't error out if we can't count the app rows, just continue
64
+ }
65
+ }
66
+
67
+ // ensure uniqueness on a per app pair basis
68
+ // this can't be done on all rows because app import results in
69
+ // duplicate row ids across apps
70
+ uniqueRows = uniqueRows.concat(...new Set(appRows))
71
+ }
72
+
73
+ return uniqueRows
74
+ }
@@ -0,0 +1,18 @@
1
+ // UNUSED CODE
2
+ // Preserved for future use
3
+
4
+ /* eslint-disable no-unused-vars */
5
+
6
+ function getNewQuotaReset() {
7
+ return Date.now() + 2592000000
8
+ }
9
+
10
+ function resetQuotasIfRequired(quota) {
11
+ // Check if the quota needs reset
12
+ if (Date.now() >= quota.quotaReset) {
13
+ quota.quotaReset = getNewQuotaReset()
14
+ for (let prop of Object.keys(quota.usageQuota)) {
15
+ quota.usageQuota[prop] = 0
16
+ }
17
+ }
18
+ }
@@ -58,29 +58,6 @@ exports.sendSmtpEmail = async (to, from, subject, contents, automation) => {
58
58
  return response.json()
59
59
  }
60
60
 
61
- exports.getDeployedApps = async () => {
62
- try {
63
- const response = await fetch(
64
- checkSlashesInUrl(env.WORKER_URL + `/api/apps`),
65
- request(null, {
66
- method: "GET",
67
- })
68
- )
69
- const json = await response.json()
70
- const apps = {}
71
- for (let [key, value] of Object.entries(json)) {
72
- if (value.url) {
73
- value.url = value.url.toLowerCase()
74
- apps[key.toLowerCase()] = value
75
- }
76
- }
77
- return apps
78
- } catch (err) {
79
- // error, cannot determine deployed apps, don't stop app creation - sort this later
80
- return {}
81
- }
82
- }
83
-
84
61
  exports.getGlobalSelf = async (ctx, appId = null) => {
85
62
  const endpoint = `/api/global/users/self`
86
63
  const response = await fetch(
@@ -1,30 +0,0 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- const CouchDB = require("../../db");
12
- const { getDeployedApps } = require("../../utilities/workerRequests");
13
- const { getScopedConfig } = require("@budibase/backend-core/db");
14
- const { Configs } = require("@budibase/backend-core/constants");
15
- const { checkSlashesInUrl } = require("../../utilities");
16
- exports.fetchUrls = (ctx) => __awaiter(void 0, void 0, void 0, function* () {
17
- const appId = ctx.appId;
18
- const db = new CouchDB(appId);
19
- const settings = yield getScopedConfig(db, { type: Configs.SETTINGS });
20
- let appUrl = "http://localhost:10000/app";
21
- if (settings && settings["platformUrl"]) {
22
- appUrl = checkSlashesInUrl(`${settings["platformUrl"]}/app`);
23
- }
24
- ctx.body = {
25
- app: appUrl,
26
- };
27
- });
28
- exports.getDeployedApps = (ctx) => __awaiter(void 0, void 0, void 0, function* () {
29
- ctx.body = yield getDeployedApps();
30
- });
@@ -1,11 +0,0 @@
1
- "use strict";
2
- const Router = require("@koa/router");
3
- const controller = require("../controllers/hosting");
4
- const authorized = require("../../middleware/authorized");
5
- const { BUILDER } = require("@budibase/backend-core/permissions");
6
- const router = Router();
7
- router
8
- .get("/api/hosting/urls", authorized(BUILDER), controller.fetchUrls)
9
- // this isn't risky, doesn't return anything about apps other than names and URLs
10
- .get("/api/hosting/apps", controller.getDeployedApps);
11
- module.exports = router;