@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
@@ -8,12 +8,24 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
- const env = require("../environment");
12
- const { getGlobalDB } = require("@budibase/backend-core/tenancy");
11
+ const env = require("../../environment");
12
+ const { getGlobalDB, getTenantId } = require("@budibase/backend-core/tenancy");
13
13
  const { StaticDatabases, generateNewUsageQuotaDoc, } = require("@budibase/backend-core/db");
14
- function getNewQuotaReset() {
15
- return Date.now() + 2592000000;
16
- }
14
+ exports.useQuotas = () => {
15
+ // check if quotas are enabled
16
+ if (env.USE_QUOTAS) {
17
+ // check if there are any tenants without limits
18
+ if (env.EXCLUDE_QUOTAS_TENANTS) {
19
+ const excludedTenants = env.EXCLUDE_QUOTAS_TENANTS.replace(/\s/g, "").split(",");
20
+ const tenantId = getTenantId();
21
+ if (excludedTenants.includes(tenantId)) {
22
+ return false;
23
+ }
24
+ }
25
+ return true;
26
+ }
27
+ return false;
28
+ };
17
29
  exports.Properties = {
18
30
  ROW: "rows",
19
31
  UPLOAD: "storage",
@@ -21,21 +33,21 @@ exports.Properties = {
21
33
  USER: "users",
22
34
  AUTOMATION: "automationRuns",
23
35
  APPS: "apps",
24
- EMAILS: "emails",
36
+ EMAILS: "emails", // doesn't work yet
25
37
  };
26
- function getUsageQuotaDoc(db) {
27
- return __awaiter(this, void 0, void 0, function* () {
28
- let quota;
29
- try {
30
- quota = yield db.get(StaticDatabases.PLATFORM_INFO.docs.usageQuota);
31
- }
32
- catch (err) {
33
- // doc doesn't exist. Create it
34
- quota = yield db.post(generateNewUsageQuotaDoc());
35
- }
36
- return quota;
37
- });
38
- }
38
+ exports.getUsageQuotaDoc = (db) => __awaiter(void 0, void 0, void 0, function* () {
39
+ let quota;
40
+ try {
41
+ quota = yield db.get(StaticDatabases.GLOBAL.docs.usageQuota);
42
+ }
43
+ catch (err) {
44
+ // doc doesn't exist. Create it
45
+ quota = generateNewUsageQuotaDoc();
46
+ const response = yield db.put(quota);
47
+ quota._rev = response.rev;
48
+ }
49
+ return quota;
50
+ });
39
51
  /**
40
52
  * Given a specified tenantId this will add to the usage object for the specified property.
41
53
  * @param {string} property The property which is to be added to (within the nested usageQuota object).
@@ -43,27 +55,28 @@ function getUsageQuotaDoc(db) {
43
55
  * @returns {Promise<void>} When this completes the API key will now be up to date - the quota period may have
44
56
  * also been reset after this call.
45
57
  */
46
- exports.update = (property, usage) => __awaiter(void 0, void 0, void 0, function* () {
47
- if (!env.USE_QUOTAS) {
58
+ exports.update = (property, usage, opts = { dryRun: false }) => __awaiter(void 0, void 0, void 0, function* () {
59
+ if (!exports.useQuotas()) {
48
60
  return;
49
61
  }
50
62
  try {
51
63
  const db = getGlobalDB();
52
- const quota = yield getUsageQuotaDoc(db);
53
- // Check if the quota needs reset
54
- if (Date.now() >= quota.quotaReset) {
55
- quota.quotaReset = getNewQuotaReset();
56
- for (let prop of Object.keys(quota.usageQuota)) {
57
- quota.usageQuota[prop] = 0;
58
- }
59
- }
64
+ const quota = yield exports.getUsageQuotaDoc(db);
60
65
  // increment the quota
61
66
  quota.usageQuota[property] += usage;
62
- if (quota.usageQuota[property] > quota.usageLimits[property]) {
67
+ if (quota.usageQuota[property] > quota.usageLimits[property] &&
68
+ usage > 0 // allow for decrementing usage when the quota is already exceeded
69
+ ) {
63
70
  throw new Error(`You have exceeded your usage quota of ${quota.usageLimits[property]} ${property}.`);
64
71
  }
72
+ if (quota.usageQuota[property] < 0) {
73
+ // never go negative if the quota has previously been exceeded
74
+ quota.usageQuota[property] = 0;
75
+ }
65
76
  // update the usage quotas
66
- yield db.put(quota);
77
+ if (!opts.dryRun) {
78
+ yield db.put(quota);
79
+ }
67
80
  }
68
81
  catch (err) {
69
82
  console.error(`Error updating usage quotas for ${property}`, err);
@@ -0,0 +1,77 @@
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 { getRowParams, USER_METDATA_PREFIX } = require("../../db/utils");
12
+ const CouchDB = require("../../db");
13
+ const { isDevAppID, getDevelopmentAppID } = require("@budibase/backend-core/db");
14
+ const ROW_EXCLUSIONS = [USER_METDATA_PREFIX];
15
+ const getAppPairs = appIds => {
16
+ // collect the app ids into dev / prod pairs
17
+ // keyed by the dev app id
18
+ const pairs = {};
19
+ for (let appId of appIds) {
20
+ const devId = getDevelopmentAppID(appId);
21
+ if (!pairs[devId]) {
22
+ pairs[devId] = {};
23
+ }
24
+ if (isDevAppID(appId)) {
25
+ pairs[devId].devId = appId;
26
+ }
27
+ else {
28
+ pairs[devId].prodId = appId;
29
+ }
30
+ }
31
+ return pairs;
32
+ };
33
+ const getAppRows = (appId) => __awaiter(void 0, void 0, void 0, function* () {
34
+ const appDb = new CouchDB(appId);
35
+ const response = yield appDb.allDocs(getRowParams(null, null, {
36
+ include_docs: false,
37
+ }));
38
+ return response.rows
39
+ .map(r => r.id)
40
+ .filter(id => {
41
+ for (let exclusion of ROW_EXCLUSIONS) {
42
+ if (id.startsWith(exclusion)) {
43
+ return false;
44
+ }
45
+ }
46
+ return true;
47
+ });
48
+ });
49
+ /**
50
+ * Return a set of all rows in the given app ids.
51
+ * The returned rows will be unique on a per dev/prod app basis.
52
+ * Rows duplicates may exist across apps due to data import so they are not filtered out.
53
+ */
54
+ exports.getUniqueRows = (appIds) => __awaiter(void 0, void 0, void 0, function* () {
55
+ let uniqueRows = [];
56
+ const pairs = getAppPairs(appIds);
57
+ for (let pair of Object.values(pairs)) {
58
+ let appRows = [];
59
+ for (let appId of [pair.devId, pair.prodId]) {
60
+ if (!appId) {
61
+ continue;
62
+ }
63
+ try {
64
+ appRows.push(yield getAppRows(appId));
65
+ }
66
+ catch (e) {
67
+ console.error(e);
68
+ // don't error out if we can't count the app rows, just continue
69
+ }
70
+ }
71
+ // ensure uniqueness on a per app pair basis
72
+ // this can't be done on all rows because app import results in
73
+ // duplicate row ids across apps
74
+ uniqueRows = uniqueRows.concat(...new Set(appRows));
75
+ }
76
+ return uniqueRows;
77
+ });
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ // UNUSED CODE
3
+ // Preserved for future use
4
+ /* eslint-disable no-unused-vars */
5
+ function getNewQuotaReset() {
6
+ return Date.now() + 2592000000;
7
+ }
8
+ function resetQuotasIfRequired(quota) {
9
+ // Check if the quota needs reset
10
+ if (Date.now() >= quota.quotaReset) {
11
+ quota.quotaReset = getNewQuotaReset();
12
+ for (let prop of Object.keys(quota.usageQuota)) {
13
+ quota.usageQuota[prop] = 0;
14
+ }
15
+ }
16
+ }
@@ -61,26 +61,6 @@ exports.sendSmtpEmail = (to, from, subject, contents, automation) => __awaiter(v
61
61
  }
62
62
  return response.json();
63
63
  });
64
- exports.getDeployedApps = () => __awaiter(void 0, void 0, void 0, function* () {
65
- try {
66
- const response = yield fetch(checkSlashesInUrl(env.WORKER_URL + `/api/apps`), request(null, {
67
- method: "GET",
68
- }));
69
- const json = yield 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
- }
79
- catch (err) {
80
- // error, cannot determine deployed apps, don't stop app creation - sort this later
81
- return {};
82
- }
83
- });
84
64
  exports.getGlobalSelf = (ctx, appId = null) => __awaiter(void 0, void 0, void 0, function* () {
85
65
  const endpoint = `/api/global/users/self`;
86
66
  const response = yield fetch(checkSlashesInUrl(env.WORKER_URL + endpoint),
package/nodemon.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "watch": ["src", "../auth"],
2
+ "watch": ["src", "../backend-core"],
3
3
  "ext": "js,ts,json",
4
4
  "ignore": ["src/**/*.spec.ts", "src/**/*.spec.js"],
5
5
  "exec": "ts-node src/index.ts"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/server",
3
3
  "email": "hi@budibase.com",
4
- "version": "1.0.46-alpha.2",
4
+ "version": "1.0.46-alpha.6",
5
5
  "description": "Budibase Web Server",
6
6
  "main": "src/index.ts",
7
7
  "repository": {
@@ -70,9 +70,9 @@
70
70
  "license": "GPL-3.0",
71
71
  "dependencies": {
72
72
  "@apidevtools/swagger-parser": "^10.0.3",
73
- "@budibase/backend-core": "^1.0.46-alpha.2",
74
- "@budibase/client": "^1.0.46-alpha.2",
75
- "@budibase/string-templates": "^1.0.46-alpha.2",
73
+ "@budibase/backend-core": "^1.0.46-alpha.6",
74
+ "@budibase/client": "^1.0.46-alpha.6",
75
+ "@budibase/string-templates": "^1.0.46-alpha.6",
76
76
  "@bull-board/api": "^3.7.0",
77
77
  "@bull-board/koa": "^3.7.0",
78
78
  "@elastic/elasticsearch": "7.10.0",
@@ -92,6 +92,8 @@
92
92
  "fix-path": "3.0.0",
93
93
  "form-data": "^4.0.0",
94
94
  "fs-extra": "8.1.0",
95
+ "google-auth-library": "^7.11.0",
96
+ "google-spreadsheet": "^3.2.0",
95
97
  "jimp": "0.16.1",
96
98
  "joi": "17.2.1",
97
99
  "js-yaml": "^4.1.0",
@@ -110,7 +112,7 @@
110
112
  "mongodb": "3.6.3",
111
113
  "mssql": "6.2.3",
112
114
  "mysql2": "^2.3.1",
113
- "node-fetch": "2.6.0",
115
+ "node-fetch": "2.6.7",
114
116
  "open": "^8.4.0",
115
117
  "pg": "8.5.1",
116
118
  "pino-pretty": "4.0.0",
@@ -139,6 +141,7 @@
139
141
  "@jest/test-sequencer": "^24.8.0",
140
142
  "@types/apidoc": "^0.50.0",
141
143
  "@types/bull": "^3.15.1",
144
+ "@types/google-spreadsheet": "^3.1.5",
142
145
  "@types/jest": "^26.0.23",
143
146
  "@types/koa": "^2.13.3",
144
147
  "@types/koa-router": "^7.4.2",
@@ -165,5 +168,5 @@
165
168
  "optionalDependencies": {
166
169
  "oracledb": "^5.3.0"
167
170
  },
168
- "gitHead": "74c2b625437c6cace5a8547347470b8343c8fe4f"
171
+ "gitHead": "a3e5c5af7f047b46ba1d7875ad489f357632b267"
169
172
  }
@@ -33,10 +33,7 @@ const {
33
33
  Replication,
34
34
  } = require("@budibase/backend-core/db")
35
35
  const { USERS_TABLE_SCHEMA } = require("../../constants")
36
- const {
37
- getDeployedApps,
38
- removeAppFromUserRoles,
39
- } = require("../../utilities/workerRequests")
36
+ const { removeAppFromUserRoles } = require("../../utilities/workerRequests")
40
37
  const { clientLibraryPath, stringToReadStream } = require("../../utilities")
41
38
  const { getAllLocks } = require("../../utilities/redis")
42
39
  const {
@@ -78,29 +75,41 @@ function getUserRoleId(ctx) {
78
75
  : ctx.user.role._id
79
76
  }
80
77
 
81
- async function getAppUrlIfNotInUse(ctx) {
78
+ async function getAppUrl(ctx) {
79
+ // construct the url
82
80
  let url
83
81
  if (ctx.request.body.url) {
82
+ // if the url is provided, use that
84
83
  url = encodeURI(ctx.request.body.url)
85
- } else if (ctx.request.body.name) {
84
+ } else {
85
+ // otherwise use the name
86
86
  url = encodeURI(`${ctx.request.body.name}`)
87
87
  }
88
- if (url) {
89
- url = `/${url.replace(URL_REGEX_SLASH, "")}`.toLowerCase()
88
+ url = `/${url.replace(URL_REGEX_SLASH, "")}`.toLowerCase()
89
+
90
+ return url
91
+ }
92
+
93
+ const checkAppUrl = (ctx, apps, url, currentAppId) => {
94
+ if (currentAppId) {
95
+ apps = apps.filter(app => app.appId !== currentAppId)
96
+ }
97
+ if (apps.some(app => app.url === url)) {
98
+ ctx.throw(400, "App URL is already in use.")
99
+ }
100
+ }
101
+
102
+ const checkAppName = (ctx, apps, name, currentAppId) => {
103
+ // TODO: Replace with Joi
104
+ if (!name) {
105
+ ctx.throw(400, "Name is required")
90
106
  }
91
- if (!env.SELF_HOSTED) {
92
- return url
107
+ if (currentAppId) {
108
+ apps = apps.filter(app => app.appId !== currentAppId)
93
109
  }
94
- const deployedApps = await getDeployedApps()
95
- if (
96
- url &&
97
- deployedApps[url] != null &&
98
- ctx.params != null &&
99
- deployedApps[url].appId !== ctx.params.appId
100
- ) {
101
- ctx.throw(400, "App name/URL is already in use.")
110
+ if (apps.some(app => app.name === name)) {
111
+ ctx.throw(400, "App name is already in use.")
102
112
  }
103
- return url
104
113
  }
105
114
 
106
115
  async function createInstance(template) {
@@ -206,6 +215,12 @@ exports.fetchAppPackage = async ctx => {
206
215
  }
207
216
 
208
217
  exports.create = async ctx => {
218
+ const apps = await getAllApps(CouchDB, { dev: true })
219
+ const name = ctx.request.body.name
220
+ checkAppName(ctx, apps, name)
221
+ const url = await getAppUrl(ctx)
222
+ checkAppUrl(ctx, apps, url)
223
+
209
224
  const { useTemplate, templateKey, templateString } = ctx.request.body
210
225
  const instanceConfig = {
211
226
  useTemplate,
@@ -218,7 +233,6 @@ exports.create = async ctx => {
218
233
  const instance = await createInstance(instanceConfig)
219
234
  const appId = instance._id
220
235
 
221
- const url = await getAppUrlIfNotInUse(ctx)
222
236
  const db = new CouchDB(appId)
223
237
  let _rev
224
238
  try {
@@ -235,7 +249,7 @@ exports.create = async ctx => {
235
249
  type: "app",
236
250
  version: packageJson.version,
237
251
  componentLibraries: ["@budibase/standard-components"],
238
- name: ctx.request.body.name,
252
+ name: name,
239
253
  url: url,
240
254
  template: ctx.request.body.template,
241
255
  instance: instance,
@@ -263,7 +277,15 @@ exports.create = async ctx => {
263
277
  }
264
278
 
265
279
  exports.update = async ctx => {
266
- const data = await updateAppPackage(ctx, ctx.request.body, ctx.params.appId)
280
+ const apps = await getAllApps(CouchDB, { dev: true })
281
+ // validation
282
+ const name = ctx.request.body.name
283
+ checkAppName(ctx, apps, name, ctx.params.appId)
284
+ const url = await getAppUrl(ctx)
285
+ checkAppUrl(ctx, apps, url, ctx.params.appId)
286
+
287
+ const appPackageUpdates = { name, url }
288
+ const data = await updateAppPackage(appPackageUpdates, ctx.params.appId)
267
289
  ctx.status = 200
268
290
  ctx.body = data
269
291
  }
@@ -285,7 +307,7 @@ exports.updateClient = async ctx => {
285
307
  version: packageJson.version,
286
308
  revertableVersion: currentVersion,
287
309
  }
288
- const data = await updateAppPackage(ctx, appPackageUpdates, ctx.params.appId)
310
+ const data = await updateAppPackage(appPackageUpdates, ctx.params.appId)
289
311
  ctx.status = 200
290
312
  ctx.body = data
291
313
  }
@@ -308,7 +330,7 @@ exports.revertClient = async ctx => {
308
330
  version: application.revertableVersion,
309
331
  revertableVersion: null,
310
332
  }
311
- const data = await updateAppPackage(ctx, appPackageUpdates, ctx.params.appId)
333
+ const data = await updateAppPackage(appPackageUpdates, ctx.params.appId)
312
334
  ctx.status = 200
313
335
  ctx.body = data
314
336
  }
@@ -381,12 +403,11 @@ exports.sync = async (ctx, next) => {
381
403
  }
382
404
  }
383
405
 
384
- const updateAppPackage = async (ctx, appPackage, appId) => {
385
- const url = await getAppUrlIfNotInUse(ctx)
406
+ const updateAppPackage = async (appPackage, appId) => {
386
407
  const db = new CouchDB(appId)
387
408
  const application = await db.get(DocumentTypes.APP_METADATA)
388
409
 
389
- const newAppPackage = { ...application, ...appPackage, url }
410
+ const newAppPackage = { ...application, ...appPackage }
390
411
  if (appPackage._rev !== application._rev) {
391
412
  newAppPackage._rev = application._rev
392
413
  }
@@ -38,6 +38,13 @@ exports.fetch = async function (ctx) {
38
38
  )
39
39
  ).rows.map(row => row.doc)
40
40
 
41
+ for (let datasource of datasources) {
42
+ if (datasource.config && datasource.config.auth) {
43
+ // strip secrets from response so they don't show in the network request
44
+ delete datasource.config.auth
45
+ }
46
+ }
47
+
41
48
  ctx.body = [bbInternalDb, ...datasources]
42
49
  }
43
50
 
@@ -94,8 +101,13 @@ exports.update = async function (ctx) {
94
101
  const db = new CouchDB(ctx.appId)
95
102
  const datasourceId = ctx.params.datasourceId
96
103
  let datasource = await db.get(datasourceId)
104
+ const auth = datasource.config.auth
97
105
  await invalidateVariables(datasource, ctx.request.body)
98
106
  datasource = { ...datasource, ...ctx.request.body }
107
+ if (auth && !ctx.request.body.auth) {
108
+ // don't strip auth config from DB
109
+ datasource.config.auth = auth
110
+ }
99
111
 
100
112
  const response = await db.put(datasource)
101
113
  datasource._rev = response.rev
@@ -141,6 +141,16 @@ async function execute(ctx, opts = { rowsOnly: false }) {
141
141
  const query = await db.get(ctx.params.queryId)
142
142
  const datasource = await db.get(query.datasourceId)
143
143
 
144
+ const enrichedParameters = ctx.request.body.parameters || {}
145
+ // make sure parameters are fully enriched with defaults
146
+ if (query && query.parameters) {
147
+ for (let parameter of query.parameters) {
148
+ if (!enrichedParameters[parameter.name]) {
149
+ enrichedParameters[parameter.name] = parameter.default
150
+ }
151
+ }
152
+ }
153
+
144
154
  // call the relevant CRUD method on the integration class
145
155
  try {
146
156
  const { rows, pagination, extra } = await Runner.run({
@@ -149,7 +159,7 @@ async function execute(ctx, opts = { rowsOnly: false }) {
149
159
  queryVerb: query.queryVerb,
150
160
  fields: query.fields,
151
161
  pagination: ctx.request.body.pagination,
152
- parameters: ctx.request.body.parameters,
162
+ parameters: enrichedParameters,
153
163
  transformer: query.transformer,
154
164
  queryId: ctx.params.queryId,
155
165
  })
@@ -178,8 +188,9 @@ const removeDynamicVariables = async (db, queryId) => {
178
188
 
179
189
  if (dynamicVariables) {
180
190
  // delete dynamic variables from the datasource
181
- const newVariables = dynamicVariables.filter(dv => dv.queryId !== queryId)
182
- datasource.config.dynamicVariables = newVariables
191
+ datasource.config.dynamicVariables = dynamicVariables.filter(
192
+ dv => dv.queryId !== queryId
193
+ )
183
194
  await db.put(datasource)
184
195
 
185
196
  // invalidate the deleted variables
@@ -52,10 +52,7 @@ exports.validate = async ({ appId, tableId, row, table }) => {
52
52
  const constraints = cloneDeep(table.schema[fieldName].constraints)
53
53
  const type = table.schema[fieldName].type
54
54
  // special case for options, need to always allow unselected (null)
55
- if (
56
- (type === FieldTypes.OPTIONS || type === FieldTypes.ARRAY) &&
57
- constraints.inclusion
58
- ) {
55
+ if (type === FieldTypes.OPTIONS && constraints.inclusion) {
59
56
  constraints.inclusion.push(null)
60
57
  }
61
58
  let res
@@ -5,7 +5,7 @@ const { resolve, join } = require("../../../utilities/centralPath")
5
5
  const uuid = require("uuid")
6
6
  const { ObjectStoreBuckets } = require("../../../constants")
7
7
  const { processString } = require("@budibase/string-templates")
8
- const { getDeployedApps } = require("../../../utilities/workerRequests")
8
+ const { getAllApps } = require("@budibase/backend-core/db")
9
9
  const CouchDB = require("../../../db")
10
10
  const {
11
11
  loadHandlebarsFile,
@@ -17,6 +17,8 @@ const { clientLibraryPath } = require("../../../utilities")
17
17
  const { upload } = require("../../../utilities/fileSystem")
18
18
  const { attachmentsRelativeURL } = require("../../../utilities")
19
19
  const { DocumentTypes } = require("../../../db/utils")
20
+ const AWS = require("aws-sdk")
21
+ const AWS_REGION = env.AWS_REGION ? env.AWS_REGION : "eu-west-1"
20
22
 
21
23
  async function prepareUpload({ s3Key, bucket, metadata, file }) {
22
24
  const response = await upload({
@@ -37,12 +39,18 @@ async function prepareUpload({ s3Key, bucket, metadata, file }) {
37
39
  }
38
40
  }
39
41
 
40
- async function checkForSelfHostedURL(ctx) {
41
- // the "appId" component of the URL may actually be a specific self hosted URL
42
+ async function getAppIdFromUrl(ctx) {
43
+ // the "appId" component of the URL can be the id or the custom url
42
44
  let possibleAppUrl = `/${encodeURI(ctx.params.appId).toLowerCase()}`
43
- const apps = await getDeployedApps()
44
- if (apps[possibleAppUrl] && apps[possibleAppUrl].appId) {
45
- return apps[possibleAppUrl].appId
45
+
46
+ // search prod apps for a url that matches, exclude dev where id is always used
47
+ const apps = await getAllApps(CouchDB, { dev: false })
48
+ const app = apps.filter(
49
+ a => a.url && a.url.toLowerCase() === possibleAppUrl
50
+ )[0]
51
+
52
+ if (app && app.appId) {
53
+ return app.appId
46
54
  } else {
47
55
  return ctx.params.appId
48
56
  }
@@ -75,10 +83,7 @@ exports.uploadFile = async function (ctx) {
75
83
  }
76
84
 
77
85
  exports.serveApp = async function (ctx) {
78
- let appId = ctx.params.appId
79
- if (env.SELF_HOSTED) {
80
- appId = await checkForSelfHostedURL(ctx)
81
- }
86
+ let appId = await getAppIdFromUrl(ctx)
82
87
  const App = require("./templates/BudibaseApp.svelte").default
83
88
  const db = new CouchDB(appId, { skip_setup: true })
84
89
  const appInfo = await db.get(DocumentTypes.APP_METADATA)
@@ -104,3 +109,51 @@ exports.serveClientLibrary = async function (ctx) {
104
109
  root: join(NODE_MODULES_PATH, "@budibase", "client", "dist"),
105
110
  })
106
111
  }
112
+
113
+ exports.getSignedUploadURL = async function (ctx) {
114
+ const database = new CouchDB(ctx.appId)
115
+
116
+ // Ensure datasource is valid
117
+ let datasource
118
+ try {
119
+ const { datasourceId } = ctx.params
120
+ datasource = await database.get(datasourceId)
121
+ if (!datasource) {
122
+ ctx.throw(400, "The specified datasource could not be found")
123
+ }
124
+ } catch (error) {
125
+ ctx.throw(400, "The specified datasource could not be found")
126
+ }
127
+
128
+ // Ensure we aren't using a custom endpoint
129
+ if (datasource?.config?.endpoint) {
130
+ ctx.throw(400, "S3 datasources with custom endpoints are not supported")
131
+ }
132
+
133
+ // Determine type of datasource and generate signed URL
134
+ let signedUrl
135
+ let publicUrl
136
+ if (datasource.source === "S3") {
137
+ const { bucket, key } = ctx.request.body || {}
138
+ if (!bucket || !key) {
139
+ ctx.throw(400, "bucket and key values are required")
140
+ return
141
+ }
142
+ try {
143
+ const s3 = new AWS.S3({
144
+ region: AWS_REGION,
145
+ accessKeyId: datasource?.config?.accessKeyId,
146
+ secretAccessKey: datasource?.config?.secretAccessKey,
147
+ apiVersion: "2006-03-01",
148
+ signatureVersion: "v4",
149
+ })
150
+ const params = { Bucket: bucket, Key: key }
151
+ signedUrl = s3.getSignedUrl("putObject", params)
152
+ publicUrl = `https://${bucket}.s3.${AWS_REGION}.amazonaws.com/${key}`
153
+ } catch (error) {
154
+ ctx.throw(400, error)
155
+ }
156
+ }
157
+
158
+ ctx.body = { signedUrl, publicUrl }
159
+ }
@@ -8,6 +8,7 @@ const {
8
8
  getTable,
9
9
  handleDataImport,
10
10
  } = require("./utils")
11
+ const usageQuota = require("../../../utilities/usageQuota")
11
12
 
12
13
  exports.save = async function (ctx) {
13
14
  const appId = ctx.appId
@@ -119,6 +120,7 @@ exports.destroy = async function (ctx) {
119
120
  })
120
121
  )
121
122
  await db.bulkDocs(rows.rows.map(row => ({ ...row.doc, _deleted: true })))
123
+ await usageQuota.update(usageQuota.Properties.ROW, -rows.rows.length)
122
124
 
123
125
  // update linked rows
124
126
  await linkRows.updateLinks({
@@ -16,6 +16,7 @@ const {
16
16
  } = require("../../../integrations/utils")
17
17
  const { getViews, saveView } = require("../view/utils")
18
18
  const viewTemplate = require("../view/viewBuilder")
19
+ const usageQuota = require("../../../utilities/usageQuota")
19
20
 
20
21
  exports.checkForColumnUpdates = async (db, oldTable, updatedTable) => {
21
22
  let updatedRows = []
@@ -112,7 +113,11 @@ exports.handleDataImport = async (appId, user, table, dataImport) => {
112
113
  finalData.push(row)
113
114
  }
114
115
 
116
+ await usageQuota.update(usageQuota.Properties.ROW, finalData.length, {
117
+ dryRun: true,
118
+ })
115
119
  await db.bulkDocs(finalData)
120
+ await usageQuota.update(usageQuota.Properties.ROW, finalData.length)
116
121
  let response = await db.put(table)
117
122
  table._rev = response._rev
118
123
  return table
@@ -20,7 +20,6 @@ const integrationRoutes = require("./integration")
20
20
  const permissionRoutes = require("./permission")
21
21
  const datasourceRoutes = require("./datasource")
22
22
  const queryRoutes = require("./query")
23
- const hostingRoutes = require("./hosting")
24
23
  const backupRoutes = require("./backup")
25
24
  const metadataRoutes = require("./metadata")
26
25
  const devRoutes = require("./dev")
@@ -46,7 +45,6 @@ exports.mainRoutes = [
46
45
  permissionRoutes,
47
46
  datasourceRoutes,
48
47
  queryRoutes,
49
- hostingRoutes,
50
48
  backupRoutes,
51
49
  metadataRoutes,
52
50
  devRoutes,