@budibase/server 1.0.46-alpha.0 → 1.0.46-alpha.4

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 (214) hide show
  1. package/builder/assets/{index.0df4f7ab.css → index.243ca52c.css} +1 -1
  2. package/builder/assets/{index.f163603e.js → index.c21ee1fb.js} +144 -144
  3. package/builder/index.html +2 -2
  4. package/coverage/clover.xml +1932 -1756
  5. package/coverage/coverage-final.json +138 -132
  6. package/coverage/lcov-report/api/controllers/analytics.js.html +6 -6
  7. package/coverage/lcov-report/api/controllers/apikeys.js.html +6 -6
  8. package/coverage/lcov-report/api/controllers/application.js.html +90 -90
  9. package/coverage/lcov-report/api/controllers/auth.js.html +7 -7
  10. package/coverage/lcov-report/api/controllers/automation.js.html +21 -21
  11. package/coverage/lcov-report/api/controllers/backup.js.html +3 -3
  12. package/coverage/lcov-report/api/controllers/cloud.js.html +12 -12
  13. package/coverage/lcov-report/api/controllers/component.js.html +8 -8
  14. package/coverage/lcov-report/api/controllers/datasource.js.html +30 -30
  15. package/coverage/lcov-report/api/controllers/dev.js.html +19 -19
  16. package/coverage/lcov-report/api/controllers/hosting.js.html +8 -8
  17. package/coverage/lcov-report/api/controllers/index.html +1 -1
  18. package/coverage/lcov-report/api/controllers/integration.js.html +4 -4
  19. package/coverage/lcov-report/api/controllers/layout.js.html +6 -6
  20. package/coverage/lcov-report/api/controllers/metadata.js.html +9 -9
  21. package/coverage/lcov-report/api/controllers/permission.js.html +15 -15
  22. package/coverage/lcov-report/api/controllers/query/index.html +1 -1
  23. package/coverage/lcov-report/api/controllers/query/index.js.html +19 -19
  24. package/coverage/lcov-report/api/controllers/query/validation.js.html +9 -9
  25. package/coverage/lcov-report/api/controllers/role.js.html +9 -9
  26. package/coverage/lcov-report/api/controllers/routing.js.html +9 -9
  27. package/coverage/lcov-report/api/controllers/row/external.js.html +16 -16
  28. package/coverage/lcov-report/api/controllers/row/index.html +1 -1
  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 +25 -25
  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 +5 -5
  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 +26 -26
  50. package/coverage/lcov-report/api/routes/analytics.js.html +6 -6
  51. package/coverage/lcov-report/api/routes/apikeys.js.html +8 -8
  52. package/coverage/lcov-report/api/routes/application.js.html +9 -9
  53. package/coverage/lcov-report/api/routes/auth.js.html +6 -6
  54. package/coverage/lcov-report/api/routes/automation.js.html +14 -14
  55. package/coverage/lcov-report/api/routes/backup.js.html +8 -8
  56. package/coverage/lcov-report/api/routes/cloud.js.html +8 -8
  57. package/coverage/lcov-report/api/routes/component.js.html +8 -8
  58. package/coverage/lcov-report/api/routes/datasource.js.html +13 -13
  59. package/coverage/lcov-report/api/routes/deploy.js.html +8 -8
  60. package/coverage/lcov-report/api/routes/dev.js.html +13 -13
  61. package/coverage/lcov-report/api/routes/hosting.js.html +8 -8
  62. package/coverage/lcov-report/api/routes/index.html +1 -1
  63. package/coverage/lcov-report/api/routes/index.js.html +30 -30
  64. package/coverage/lcov-report/api/routes/integration.js.html +8 -8
  65. package/coverage/lcov-report/api/routes/layout.js.html +8 -8
  66. package/coverage/lcov-report/api/routes/metadata.js.html +9 -9
  67. package/coverage/lcov-report/api/routes/permission.js.html +12 -12
  68. package/coverage/lcov-report/api/routes/query.js.html +10 -10
  69. package/coverage/lcov-report/api/routes/role.js.html +12 -12
  70. package/coverage/lcov-report/api/routes/routing.js.html +8 -8
  71. package/coverage/lcov-report/api/routes/row.js.html +10 -10
  72. package/coverage/lcov-report/api/routes/screen.js.html +11 -11
  73. package/coverage/lcov-report/api/routes/script.js.html +1 -1
  74. package/coverage/lcov-report/api/routes/static.js.html +28 -13
  75. package/coverage/lcov-report/api/routes/table.js.html +12 -12
  76. package/coverage/lcov-report/api/routes/templates.js.html +8 -8
  77. package/coverage/lcov-report/api/routes/user.js.html +8 -8
  78. package/coverage/lcov-report/api/routes/view.js.html +10 -10
  79. package/coverage/lcov-report/api/routes/webhook.js.html +11 -11
  80. package/coverage/lcov-report/automations/actions.js.html +24 -24
  81. package/coverage/lcov-report/automations/automationUtils.js.html +5 -5
  82. package/coverage/lcov-report/automations/bullboard.js.html +16 -16
  83. package/coverage/lcov-report/automations/index.html +1 -1
  84. package/coverage/lcov-report/automations/index.js.html +7 -7
  85. package/coverage/lcov-report/automations/steps/bash.js.html +6 -6
  86. package/coverage/lcov-report/automations/steps/createRow.js.html +13 -19
  87. package/coverage/lcov-report/automations/steps/delay.js.html +4 -4
  88. package/coverage/lcov-report/automations/steps/deleteRow.js.html +11 -20
  89. package/coverage/lcov-report/automations/steps/discord.js.html +7 -7
  90. package/coverage/lcov-report/automations/steps/executeQuery.js.html +6 -6
  91. package/coverage/lcov-report/automations/steps/executeScript.js.html +6 -6
  92. package/coverage/lcov-report/automations/steps/filter.js.html +7 -7
  93. package/coverage/lcov-report/automations/steps/index.html +13 -13
  94. package/coverage/lcov-report/automations/steps/integromat.js.html +5 -5
  95. package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +8 -8
  96. package/coverage/lcov-report/automations/steps/queryRows.js.html +10 -10
  97. package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +5 -5
  98. package/coverage/lcov-report/automations/steps/serverLog.js.html +3 -3
  99. package/coverage/lcov-report/automations/steps/slack.js.html +5 -5
  100. package/coverage/lcov-report/automations/steps/updateRow.js.html +6 -6
  101. package/coverage/lcov-report/automations/steps/utils.js.html +3 -3
  102. package/coverage/lcov-report/automations/steps/zapier.js.html +5 -5
  103. package/coverage/lcov-report/automations/triggerInfo/app.js.html +2 -2
  104. package/coverage/lcov-report/automations/triggerInfo/cron.js.html +2 -2
  105. package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
  106. package/coverage/lcov-report/automations/triggerInfo/index.js.html +8 -8
  107. package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +2 -2
  108. package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +2 -2
  109. package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +2 -2
  110. package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +2 -2
  111. package/coverage/lcov-report/automations/triggers.js.html +18 -18
  112. package/coverage/lcov-report/automations/utils.js.html +32 -32
  113. package/coverage/lcov-report/constants/index.html +1 -1
  114. package/coverage/lcov-report/constants/index.js.html +22 -22
  115. package/coverage/lcov-report/constants/layouts.js.html +5 -5
  116. package/coverage/lcov-report/constants/screens.js.html +4 -4
  117. package/coverage/lcov-report/db/client.js.html +18 -18
  118. package/coverage/lcov-report/db/inMemoryView.js.html +7 -7
  119. package/coverage/lcov-report/db/index.html +15 -15
  120. package/coverage/lcov-report/db/index.js.html +3 -3
  121. package/coverage/lcov-report/db/linkedRows/LinkController.js.html +18 -18
  122. package/coverage/lcov-report/db/linkedRows/index.html +1 -1
  123. package/coverage/lcov-report/db/linkedRows/index.js.html +31 -31
  124. package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +15 -15
  125. package/coverage/lcov-report/db/newid.js.html +4 -4
  126. package/coverage/lcov-report/db/utils.js.html +85 -85
  127. package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
  128. package/coverage/lcov-report/events/index.html +1 -1
  129. package/coverage/lcov-report/events/index.js.html +6 -6
  130. package/coverage/lcov-report/events/utils.js.html +3 -3
  131. package/coverage/lcov-report/index.html +68 -38
  132. package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
  133. package/coverage/lcov-report/integrations/tests/index.html +1 -1
  134. package/coverage/lcov-report/middleware/appInfo.js.html +5 -5
  135. package/coverage/lcov-report/middleware/authorized.js.html +15 -15
  136. package/coverage/lcov-report/middleware/builder.js.html +16 -16
  137. package/coverage/lcov-report/middleware/currentapp.js.html +43 -43
  138. package/coverage/lcov-report/middleware/index.html +20 -20
  139. package/coverage/lcov-report/middleware/joi-validator.js.html +6 -6
  140. package/coverage/lcov-report/middleware/resourceId.js.html +22 -22
  141. package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
  142. package/coverage/lcov-report/middleware/usageQuota.js.html +291 -57
  143. package/coverage/lcov-report/middleware/utils.js.html +4 -4
  144. package/coverage/lcov-report/migrations/usageQuotas/index.html +141 -0
  145. package/coverage/lcov-report/migrations/usageQuotas/index.js.html +152 -0
  146. package/coverage/lcov-report/migrations/usageQuotas/syncApps.js.html +134 -0
  147. package/coverage/lcov-report/migrations/usageQuotas/syncRows.js.html +143 -0
  148. package/coverage/lcov-report/threads/automation.js.html +1 -1
  149. package/coverage/lcov-report/threads/index.html +1 -1
  150. package/coverage/lcov-report/threads/index.js.html +10 -10
  151. package/coverage/lcov-report/threads/query.js.html +1 -1
  152. package/coverage/lcov-report/threads/utils.js.html +14 -14
  153. package/coverage/lcov-report/utilities/bcrypt.js.html +1 -1
  154. package/coverage/lcov-report/utilities/budibaseDir.js.html +7 -7
  155. package/coverage/lcov-report/utilities/centralPath.js.html +4 -4
  156. package/coverage/lcov-report/utilities/csvParser.js.html +6 -6
  157. package/coverage/lcov-report/utilities/global.js.html +26 -26
  158. package/coverage/lcov-report/utilities/index.html +1 -1
  159. package/coverage/lcov-report/utilities/index.js.html +25 -25
  160. package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +9 -9
  161. package/coverage/lcov-report/utilities/queue/index.html +1 -1
  162. package/coverage/lcov-report/utilities/routing/index.html +1 -1
  163. package/coverage/lcov-report/utilities/routing/index.js.html +6 -6
  164. package/coverage/lcov-report/utilities/rowProcessor/index.html +1 -1
  165. package/coverage/lcov-report/utilities/rowProcessor/index.js.html +53 -53
  166. package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +10 -10
  167. package/coverage/lcov-report/utilities/scriptRunner.js.html +5 -5
  168. package/coverage/lcov-report/utilities/security.js.html +8 -8
  169. package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
  170. package/coverage/lcov-report/utilities/usageQuota/index.html +141 -0
  171. package/coverage/lcov-report/utilities/usageQuota/index.js.html +356 -0
  172. package/coverage/lcov-report/utilities/usageQuota/rows.js.html +302 -0
  173. package/coverage/lcov-report/utilities/usageQuota/usageQuoteReset.js.html +134 -0
  174. package/coverage/lcov-report/utilities/users.js.html +5 -5
  175. package/coverage/lcov-report/utilities/workerRequests.js.html +31 -31
  176. package/coverage/lcov.info +2530 -2232
  177. package/dist/api/controllers/static/index.js +50 -0
  178. package/dist/api/controllers/table/internal.js +2 -0
  179. package/dist/api/controllers/table/utils.js +5 -0
  180. package/dist/api/routes/static.js +2 -1
  181. package/dist/automations/steps/createRow.js +2 -4
  182. package/dist/automations/steps/deleteRow.js +1 -4
  183. package/dist/environment.js +1 -0
  184. package/dist/middleware/usageQuota.js +82 -13
  185. package/dist/migrations/usageQuotas/index.js +24 -0
  186. package/dist/migrations/usageQuotas/syncApps.js +26 -0
  187. package/dist/migrations/usageQuotas/syncRows.js +29 -0
  188. package/dist/package.json +4 -4
  189. package/dist/tsconfig.tsbuildinfo +1 -1
  190. package/dist/utilities/{usageQuota.js → usageQuota/index.js} +44 -31
  191. package/dist/utilities/usageQuota/rows.js +77 -0
  192. package/dist/utilities/usageQuota/usageQuoteReset.js +16 -0
  193. package/package.json +5 -5
  194. package/src/api/controllers/static/index.js +50 -0
  195. package/src/api/controllers/table/internal.js +2 -0
  196. package/src/api/controllers/table/utils.js +5 -0
  197. package/src/api/routes/static.js +5 -0
  198. package/src/api/routes/tests/static.spec.js +98 -0
  199. package/src/automations/steps/createRow.js +2 -4
  200. package/src/automations/steps/deleteRow.js +1 -4
  201. package/src/environment.js +1 -0
  202. package/src/middleware/tests/usageQuota.spec.js +18 -24
  203. package/src/middleware/usageQuota.js +93 -15
  204. package/src/migrations/tests/usageQuotas/index.spec.js +27 -0
  205. package/src/migrations/tests/usageQuotas/syncApps.spec.js +37 -0
  206. package/src/migrations/tests/usageQuotas/syncRows.spec.js +43 -0
  207. package/src/migrations/usageQuotas/index.js +24 -0
  208. package/src/migrations/usageQuotas/syncApps.js +18 -0
  209. package/src/migrations/usageQuotas/syncRows.js +21 -0
  210. package/src/utilities/tests/usageQuota/usageQuota.spec.js +72 -0
  211. package/src/utilities/usageQuota/index.js +92 -0
  212. package/src/utilities/usageQuota/rows.js +74 -0
  213. package/src/utilities/usageQuota/usageQuoteReset.js +18 -0
  214. 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
+ }
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.0",
4
+ "version": "1.0.46-alpha.4",
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.0",
74
- "@budibase/client": "^1.0.46-alpha.0",
75
- "@budibase/string-templates": "^1.0.46-alpha.0",
73
+ "@budibase/backend-core": "^1.0.46-alpha.4",
74
+ "@budibase/client": "^1.0.46-alpha.4",
75
+ "@budibase/string-templates": "^1.0.46-alpha.4",
76
76
  "@bull-board/api": "^3.7.0",
77
77
  "@bull-board/koa": "^3.7.0",
78
78
  "@elastic/elasticsearch": "7.10.0",
@@ -165,5 +165,5 @@
165
165
  "optionalDependencies": {
166
166
  "oracledb": "^5.3.0"
167
167
  },
168
- "gitHead": "364d02d883291374e72a4a3d8ebb62c1f4ed185b"
168
+ "gitHead": "965525d1b1437e7c94eaff0b21f2143e3f2f0ad7"
169
169
  }
@@ -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({
@@ -104,3 +106,51 @@ exports.serveClientLibrary = async function (ctx) {
104
106
  root: join(NODE_MODULES_PATH, "@budibase", "client", "dist"),
105
107
  })
106
108
  }
109
+
110
+ exports.getSignedUploadURL = async function (ctx) {
111
+ const database = new CouchDB(ctx.appId)
112
+
113
+ // Ensure datasource is valid
114
+ let datasource
115
+ try {
116
+ const { datasourceId } = ctx.params
117
+ datasource = await database.get(datasourceId)
118
+ if (!datasource) {
119
+ ctx.throw(400, "The specified datasource could not be found")
120
+ }
121
+ } catch (error) {
122
+ ctx.throw(400, "The specified datasource could not be found")
123
+ }
124
+
125
+ // Ensure we aren't using a custom endpoint
126
+ if (datasource?.config?.endpoint) {
127
+ ctx.throw(400, "S3 datasources with custom endpoints are not supported")
128
+ }
129
+
130
+ // Determine type of datasource and generate signed URL
131
+ let signedUrl
132
+ let publicUrl
133
+ if (datasource.source === "S3") {
134
+ const { bucket, key } = ctx.request.body || {}
135
+ if (!bucket || !key) {
136
+ ctx.throw(400, "bucket and key values are required")
137
+ return
138
+ }
139
+ try {
140
+ const s3 = new AWS.S3({
141
+ region: AWS_REGION,
142
+ accessKeyId: datasource?.config?.accessKeyId,
143
+ secretAccessKey: datasource?.config?.secretAccessKey,
144
+ apiVersion: "2006-03-01",
145
+ signatureVersion: "v4",
146
+ })
147
+ const params = { Bucket: bucket, Key: key }
148
+ signedUrl = s3.getSignedUrl("putObject", params)
149
+ publicUrl = `https://${bucket}.s3.${AWS_REGION}.amazonaws.com/${key}`
150
+ } catch (error) {
151
+ ctx.throw(400, error)
152
+ }
153
+ }
154
+
155
+ ctx.body = { signedUrl, publicUrl }
156
+ }
@@ -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
@@ -46,5 +46,10 @@ router
46
46
  )
47
47
  // TODO: this likely needs to be secured in some way
48
48
  .get("/:appId/:path*", controller.serveApp)
49
+ .post(
50
+ "/api/attachments/:datasourceId/url",
51
+ authorized(PermissionTypes.TABLE, PermissionLevels.READ),
52
+ controller.getSignedUploadURL
53
+ )
49
54
 
50
55
  module.exports = router
@@ -0,0 +1,98 @@
1
+ jest.mock("node-fetch")
2
+ jest.mock("aws-sdk", () => ({
3
+ config: {
4
+ update: jest.fn(),
5
+ },
6
+ DynamoDB: {
7
+ DocumentClient: jest.fn(),
8
+ },
9
+ S3: jest.fn(() => ({
10
+ getSignedUrl: jest.fn(() => {
11
+ return "my-url"
12
+ }),
13
+ })),
14
+ }))
15
+
16
+ const setup = require("./utilities")
17
+
18
+ describe("/attachments", () => {
19
+ let request = setup.getRequest()
20
+ let config = setup.getConfig()
21
+ let app
22
+
23
+ afterAll(setup.afterAll)
24
+
25
+ beforeEach(async () => {
26
+ app = await config.init()
27
+ })
28
+
29
+ describe("generateSignedUrls", () => {
30
+ let datasource
31
+
32
+ beforeEach(async () => {
33
+ datasource = await config.createDatasource({
34
+ datasource: {
35
+ type: "datasource",
36
+ name: "Test",
37
+ source: "S3",
38
+ config: {},
39
+ },
40
+ })
41
+ })
42
+
43
+ it("should be able to generate a signed upload URL", async () => {
44
+ const bucket = "foo"
45
+ const key = "bar"
46
+ const res = await request
47
+ .post(`/api/attachments/${datasource._id}/url`)
48
+ .send({ bucket, key })
49
+ .set(config.defaultHeaders())
50
+ .expect("Content-Type", /json/)
51
+ .expect(200)
52
+ expect(res.body.signedUrl).toEqual("my-url")
53
+ expect(res.body.publicUrl).toEqual(
54
+ `https://${bucket}.s3.eu-west-1.amazonaws.com/${key}`
55
+ )
56
+ })
57
+
58
+ it("should handle an invalid datasource ID", async () => {
59
+ const res = await request
60
+ .post(`/api/attachments/foo/url`)
61
+ .send({
62
+ bucket: "foo",
63
+ key: "bar",
64
+ })
65
+ .set(config.defaultHeaders())
66
+ .expect("Content-Type", /json/)
67
+ .expect(400)
68
+ expect(res.body.message).toEqual(
69
+ "The specified datasource could not be found"
70
+ )
71
+ })
72
+
73
+ it("should require a bucket parameter", async () => {
74
+ const res = await request
75
+ .post(`/api/attachments/${datasource._id}/url`)
76
+ .send({
77
+ bucket: undefined,
78
+ key: "bar",
79
+ })
80
+ .set(config.defaultHeaders())
81
+ .expect("Content-Type", /json/)
82
+ .expect(400)
83
+ expect(res.body.message).toEqual("bucket and key values are required")
84
+ })
85
+
86
+ it("should require a key parameter", async () => {
87
+ const res = await request
88
+ .post(`/api/attachments/${datasource._id}/url`)
89
+ .send({
90
+ bucket: "foo",
91
+ })
92
+ .set(config.defaultHeaders())
93
+ .expect("Content-Type", /json/)
94
+ .expect(400)
95
+ expect(res.body.message).toEqual("bucket and key values are required")
96
+ })
97
+ })
98
+ })
@@ -1,6 +1,5 @@
1
1
  const rowController = require("../../api/controllers/row")
2
2
  const automationUtils = require("../automationUtils")
3
- const env = require("../../environment")
4
3
  const usage = require("../../utilities/usageQuota")
5
4
  const { buildCtx } = require("./utils")
6
5
 
@@ -83,10 +82,9 @@ exports.run = async function ({ inputs, appId, emitter }) {
83
82
  inputs.row.tableId,
84
83
  inputs.row
85
84
  )
86
- if (env.USE_QUOTAS) {
87
- await usage.update(usage.Properties.ROW, 1)
88
- }
85
+ await usage.update(usage.Properties.ROW, 1, { dryRun: true })
89
86
  await rowController.save(ctx)
87
+ await usage.update(usage.Properties.ROW, 1)
90
88
  return {
91
89
  row: inputs.row,
92
90
  response: ctx.body,
@@ -1,5 +1,4 @@
1
1
  const rowController = require("../../api/controllers/row")
2
- const env = require("../../environment")
3
2
  const usage = require("../../utilities/usageQuota")
4
3
  const { buildCtx } = require("./utils")
5
4
  const automationUtils = require("../automationUtils")
@@ -74,9 +73,7 @@ exports.run = async function ({ inputs, appId, emitter }) {
74
73
  })
75
74
 
76
75
  try {
77
- if (env.isProd()) {
78
- await usage.update(usage.Properties.ROW, -1)
79
- }
76
+ await usage.update(usage.Properties.ROW, -1)
80
77
  await rowController.destroy(ctx)
81
78
  return {
82
79
  response: ctx.body,
@@ -38,6 +38,7 @@ module.exports = {
38
38
  MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
39
39
  MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
40
40
  USE_QUOTAS: process.env.USE_QUOTAS,
41
+ EXCLUDE_QUOTAS_TENANTS: process.env.EXCLUDE_QUOTAS_TENANTS,
41
42
  REDIS_URL: process.env.REDIS_URL,
42
43
  REDIS_PASSWORD: process.env.REDIS_PASSWORD,
43
44
  INTERNAL_API_KEY: process.env.INTERNAL_API_KEY,
@@ -1,11 +1,5 @@
1
1
  jest.mock("../../db")
2
2
  jest.mock("../../utilities/usageQuota")
3
- jest.mock("../../environment", () => ({
4
- isTest: () => true,
5
- isProd: () => false,
6
- isDev: () => true,
7
- _set: () => {},
8
- }))
9
3
  jest.mock("@budibase/backend-core/tenancy", () => ({
10
4
  getTenantId: () => "testing123"
11
5
  }))
@@ -29,9 +23,10 @@ class TestConfiguration {
29
23
  },
30
24
  req: {
31
25
  method: "POST",
32
- url: "/rows"
26
+ url: "/applications"
33
27
  }
34
28
  }
29
+ usageQuota.useQuotas = () => true
35
30
  }
36
31
 
37
32
  executeMiddleware() {
@@ -113,7 +108,6 @@ describe("usageQuota middleware", () => {
113
108
 
114
109
  it("calculates and persists the correct usage quota for the relevant action", async () => {
115
110
  config.setUrl("/rows")
116
- config.setProd(true)
117
111
 
118
112
  await config.executeMiddleware()
119
113
 
@@ -121,20 +115,20 @@ describe("usageQuota middleware", () => {
121
115
  expect(config.next).toHaveBeenCalled()
122
116
  })
123
117
 
124
- it("calculates the correct file size from a file upload call and adds it to quota", async () => {
125
- config.setUrl("/upload")
126
- config.setProd(true)
127
- config.setFiles([
128
- {
129
- size: 100
130
- },
131
- {
132
- size: 10000
133
- },
134
- ])
135
- await config.executeMiddleware()
136
-
137
- expect(usageQuota.update).toHaveBeenCalledWith("storage", 10100)
138
- expect(config.next).toHaveBeenCalled()
139
- })
118
+ // it("calculates the correct file size from a file upload call and adds it to quota", async () => {
119
+ // config.setUrl("/upload")
120
+ // config.setProd(true)
121
+ // config.setFiles([
122
+ // {
123
+ // size: 100
124
+ // },
125
+ // {
126
+ // size: 10000
127
+ // },
128
+ // ])
129
+ // await config.executeMiddleware()
130
+
131
+ // expect(usageQuota.update).toHaveBeenCalledWith("storage", 10100)
132
+ // expect(config.next).toHaveBeenCalled()
133
+ // })
140
134
  })