@budibase/server 2.3.18-alpha.3 → 2.3.18-alpha.30

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 (93) hide show
  1. package/__mocks__/node-fetch.ts +3 -0
  2. package/builder/assets/blankScreenPreview.72634dd1.png +0 -0
  3. package/builder/assets/{index.9f78787d.js → index.11e16372.js} +429 -414
  4. package/builder/assets/index.b24b9dea.css +6 -0
  5. package/builder/assets/listScreenPreview.599c0aae.png +0 -0
  6. package/builder/index.html +2 -2
  7. package/dist/api/controllers/automation.js +11 -2
  8. package/dist/api/controllers/cloud.js +2 -2
  9. package/dist/api/controllers/row/ExternalRequest.js +49 -24
  10. package/dist/api/controllers/row/external.js +1 -1
  11. package/dist/api/controllers/row/internalSearch.js +6 -450
  12. package/dist/api/controllers/row/utils.js +1 -3
  13. package/dist/api/routes/automation.js +1 -1
  14. package/dist/api/routes/public/applications.js +7 -7
  15. package/dist/api/routes/public/queries.js +2 -2
  16. package/dist/api/routes/public/rows.js +5 -5
  17. package/dist/api/routes/public/tables.js +5 -5
  18. package/dist/api/routes/public/users.js +5 -5
  19. package/dist/app.js +2 -0
  20. package/dist/db/index.js +25 -2
  21. package/dist/db/utils.js +2 -5
  22. package/dist/db/views/staticViews.js +2 -1
  23. package/dist/integrations/base/sql.js +4 -8
  24. package/dist/integrations/googlesheets.js +17 -20
  25. package/dist/middleware/authorized.js +5 -3
  26. package/dist/middleware/builder.js +6 -3
  27. package/dist/migrations/functions/backfill/global/configs.js +10 -4
  28. package/dist/migrations/tests/helpers.js +1 -1
  29. package/dist/migrations/tests/structures.js +1 -1
  30. package/dist/package.json +9 -8
  31. package/dist/sdk/app/backups/constants.js +2 -1
  32. package/dist/sdk/app/backups/exports.js +12 -5
  33. package/dist/sdk/app/rows/attachments.js +1 -1
  34. package/dist/startup.js +3 -0
  35. package/dist/tsconfig.build.tsbuildinfo +1 -1
  36. package/jest.config.ts +1 -0
  37. package/package.json +10 -9
  38. package/scripts/test.sh +12 -0
  39. package/specs/{generate.js → generate.ts} +7 -9
  40. package/specs/openapi.json +24 -24
  41. package/specs/openapi.yaml +24 -24
  42. package/specs/{parameters.js → parameters.ts} +6 -6
  43. package/specs/resources/{application.js → application.ts} +4 -4
  44. package/specs/resources/{index.js → index.ts} +8 -8
  45. package/specs/resources/{misc.js → misc.ts} +3 -3
  46. package/specs/resources/{query.js → query.ts} +4 -4
  47. package/specs/resources/{row.js → row.ts} +3 -4
  48. package/specs/resources/{table.js → table.ts} +5 -5
  49. package/specs/resources/{user.js → user.ts} +3 -3
  50. package/specs/resources/utils/Resource.ts +39 -0
  51. package/specs/resources/utils/{index.js → index.ts} +1 -1
  52. package/specs/{security.js → security.ts} +1 -1
  53. package/src/api/controllers/automation.ts +13 -2
  54. package/src/api/controllers/cloud.ts +2 -2
  55. package/src/api/controllers/row/ExternalRequest.ts +95 -28
  56. package/src/api/controllers/row/external.ts +1 -1
  57. package/src/api/controllers/row/internalSearch.ts +11 -524
  58. package/src/api/controllers/row/utils.ts +1 -2
  59. package/src/api/routes/automation.ts +1 -1
  60. package/src/api/routes/public/applications.ts +7 -7
  61. package/src/api/routes/public/queries.ts +2 -2
  62. package/src/api/routes/public/rows.ts +5 -5
  63. package/src/api/routes/public/tables.ts +5 -5
  64. package/src/api/routes/public/tests/{compare.spec.js → compare.spec.ts} +44 -25
  65. package/src/api/routes/public/users.ts +5 -5
  66. package/src/api/routes/tests/{cloud.seq.spec.ts → cloud.spec.ts} +13 -20
  67. package/src/api/routes/tests/utilities/TestFunctions.ts +1 -2
  68. package/src/app.ts +2 -0
  69. package/src/db/index.ts +2 -2
  70. package/src/db/utils.ts +0 -4
  71. package/src/db/views/staticViews.ts +3 -3
  72. package/src/definitions/openapi.ts +449 -63
  73. package/src/integration-test/postgres.spec.ts +351 -81
  74. package/src/integrations/base/sql.ts +4 -8
  75. package/src/integrations/googlesheets.ts +21 -22
  76. package/src/integrations/tests/googlesheets.spec.ts +122 -0
  77. package/src/middleware/authorized.ts +6 -4
  78. package/src/middleware/builder.ts +8 -3
  79. package/src/migrations/functions/backfill/global/configs.ts +15 -9
  80. package/src/migrations/functions/tests/userEmailViewCasing.spec.js +3 -4
  81. package/src/migrations/tests/helpers.ts +2 -2
  82. package/src/migrations/tests/structures.ts +1 -0
  83. package/src/sdk/app/backups/constants.ts +1 -0
  84. package/src/sdk/app/backups/exports.ts +24 -8
  85. package/src/sdk/app/rows/attachments.ts +1 -1
  86. package/src/startup.ts +4 -1
  87. package/src/tests/jestEnv.ts +1 -0
  88. package/src/tests/utilities/TestConfiguration.ts +42 -30
  89. package/src/tests/utilities/structures.ts +0 -2
  90. package/builder/assets/index.7e76c039.css +0 -6
  91. package/dist/integrations/base/utils.js +0 -16
  92. package/specs/resources/utils/Resource.js +0 -26
  93. package/src/integrations/base/utils.ts +0 -12
@@ -1,5 +1,5 @@
1
- const { object } = require("./utils")
2
- const Resource = require("./utils/Resource")
1
+ import { object } from "./utils"
2
+ import Resource from "./utils/Resource"
3
3
 
4
4
  const baseRow = {
5
5
  _id: "ro_ta_5b1649e42a5b41dea4ef7742a36a7a70_e6dc7e38cf1343b2b56760265201cda4",
@@ -56,7 +56,6 @@ const rowSchema = {
56
56
  const rowOutputSchema = {
57
57
  ...rowSchema,
58
58
  properties: {
59
- ...rowSchema.properties,
60
59
  _id: {
61
60
  description: "The ID of the row.",
62
61
  type: "string",
@@ -93,7 +92,7 @@ const searchOutputSchema = {
93
92
  },
94
93
  }
95
94
 
96
- module.exports = new Resource()
95
+ export default new Resource()
97
96
  .setExamples({
98
97
  inputRow: {
99
98
  value: inputRow,
@@ -1,10 +1,10 @@
1
- const {
1
+ import {
2
2
  FieldTypes,
3
3
  RelationshipTypes,
4
4
  FormulaTypes,
5
- } = require("../../src/constants")
6
- const { object } = require("./utils")
7
- const Resource = require("./utils/Resource")
5
+ } from "../../src/constants"
6
+ import { object } from "./utils"
7
+ import Resource from "./utils/Resource"
8
8
 
9
9
  const table = {
10
10
  _id: "ta_5b1649e42a5b41dea4ef7742a36a7a70",
@@ -170,7 +170,7 @@ const tableOutputSchema = {
170
170
  required: [...tableSchema.required, "_id"],
171
171
  }
172
172
 
173
- module.exports = new Resource()
173
+ export default new Resource()
174
174
  .setExamples({
175
175
  table: {
176
176
  value: {
@@ -1,5 +1,5 @@
1
- const { object } = require("./utils")
2
- const Resource = require("./utils/Resource")
1
+ import { object } from "./utils"
2
+ import Resource from "./utils/Resource"
3
3
 
4
4
  const user = {
5
5
  _id: "us_693a73206518477283a8d5ae31103252",
@@ -105,7 +105,7 @@ const userOutputSchema = {
105
105
  required: [...userSchema.required, "_id"],
106
106
  }
107
107
 
108
- module.exports = new Resource()
108
+ export default new Resource()
109
109
  .setExamples({
110
110
  user: {
111
111
  value: {
@@ -0,0 +1,39 @@
1
+ type Example = {
2
+ [key: string]: {
3
+ [key: string]: any
4
+ }
5
+ }
6
+
7
+ type Schema = {
8
+ [key: string]: {
9
+ [key: string]: any
10
+ }
11
+ }
12
+
13
+ export default class Resource {
14
+ examples: Example
15
+ schemas: Schema
16
+
17
+ constructor() {
18
+ this.examples = {}
19
+ this.schemas = {}
20
+ }
21
+
22
+ setExamples(examples: Example) {
23
+ this.examples = examples
24
+ return this
25
+ }
26
+
27
+ setSchemas(schemas: Schema) {
28
+ this.schemas = schemas
29
+ return this
30
+ }
31
+
32
+ getExamples() {
33
+ return this.examples
34
+ }
35
+
36
+ getSchemas() {
37
+ return this.schemas
38
+ }
39
+ }
@@ -1,4 +1,4 @@
1
- exports.object = (props, opts) => {
1
+ export const object = (props: any, opts?: any) => {
2
2
  const base = {
3
3
  type: "object",
4
4
  properties: props,
@@ -1,4 +1,4 @@
1
- exports.ApiKeyAuth = {
1
+ export const ApiKeyAuth = {
2
2
  type: "apiKey",
3
3
  in: "header",
4
4
  name: "x-budibase-api-key",
@@ -65,10 +65,14 @@ export async function create(ctx: BBContext) {
65
65
 
66
66
  // call through to update if already exists
67
67
  if (automation._id && automation._rev) {
68
- return update(ctx)
68
+ await update(ctx)
69
+ return
69
70
  }
70
71
 
71
- automation._id = generateAutomationID()
72
+ // Respect existing IDs if recreating a deleted automation
73
+ if (!automation._id) {
74
+ automation._id = generateAutomationID()
75
+ }
72
76
 
73
77
  automation.type = "automation"
74
78
  automation = cleanAutomationInputs(automation)
@@ -126,6 +130,13 @@ export async function update(ctx: BBContext) {
126
130
  const db = context.getAppDB()
127
131
  let automation = ctx.request.body
128
132
  automation.appId = ctx.appId
133
+
134
+ // Call through to create if it doesn't exist
135
+ if (!automation._id || !automation._rev) {
136
+ await create(ctx)
137
+ return
138
+ }
139
+
129
140
  const oldAutomation = await db.get(automation._id)
130
141
  automation = cleanAutomationInputs(automation)
131
142
  automation = await checkForWebhooks({
@@ -58,7 +58,7 @@ export async function exportApps(ctx: Ctx) {
58
58
  }
59
59
 
60
60
  async function checkHasBeenImported() {
61
- if (!env.SELF_HOSTED || env.MULTI_TENANCY) {
61
+ if (!env.SELF_HOSTED) {
62
62
  return true
63
63
  }
64
64
  const apps = await dbCore.getAllApps({ all: true })
@@ -72,7 +72,7 @@ export async function hasBeenImported(ctx: Ctx) {
72
72
  }
73
73
 
74
74
  export async function importApps(ctx: Ctx) {
75
- if (!env.SELF_HOSTED || env.MULTI_TENANCY) {
75
+ if (!env.SELF_HOSTED) {
76
76
  ctx.throw(400, "Importing only allowed in self hosted environments.")
77
77
  }
78
78
  const beenImported = await checkHasBeenImported()
@@ -24,8 +24,7 @@ import { breakExternalTableId, isSQL } from "../../../integrations/utils"
24
24
  import { processObjectSync } from "@budibase/string-templates"
25
25
  import { cloneDeep } from "lodash/fp"
26
26
  import { processFormulas, processDates } from "../../../utilities/rowProcessor"
27
- import { context } from "@budibase/backend-core"
28
- import { removeKeyNumbering } from "./utils"
27
+ import { db as dbCore } from "@budibase/backend-core"
29
28
  import sdk from "../../../sdk"
30
29
 
31
30
  export interface ManyRelationship {
@@ -61,7 +60,7 @@ function buildFilters(
61
60
  let prefix = 1
62
61
  for (let operator of Object.values(filters)) {
63
62
  for (let field of Object.keys(operator || {})) {
64
- if (removeKeyNumbering(field) === "_id") {
63
+ if (dbCore.removeKeyNumbering(field) === "_id") {
65
64
  if (primary) {
66
65
  const parts = breakRowIdField(operator[field])
67
66
  for (let field of primary) {
@@ -142,7 +141,11 @@ function cleanupConfig(config: RunConfig, table: Table): RunConfig {
142
141
  return config
143
142
  }
144
143
 
145
- function generateIdForRow(row: Row | undefined, table: Table): string {
144
+ function generateIdForRow(
145
+ row: Row | undefined,
146
+ table: Table,
147
+ isLinked: boolean = false
148
+ ): string {
146
149
  const primary = table.primary
147
150
  if (!row || !primary) {
148
151
  return ""
@@ -150,8 +153,12 @@ function generateIdForRow(row: Row | undefined, table: Table): string {
150
153
  // build id array
151
154
  let idParts = []
152
155
  for (let field of primary) {
153
- // need to handle table name + field or just field, depending on if relationships used
154
- const fieldValue = row[`${table.name}.${field}`] || row[field]
156
+ let fieldValue = extractFieldValue({
157
+ row,
158
+ tableName: table.name,
159
+ fieldName: field,
160
+ isLinked,
161
+ })
155
162
  if (fieldValue) {
156
163
  idParts.push(fieldValue)
157
164
  }
@@ -174,18 +181,52 @@ function getEndpoint(tableId: string | undefined, operation: string) {
174
181
  }
175
182
  }
176
183
 
177
- function basicProcessing(row: Row, table: Table): Row {
184
+ // need to handle table name + field or just field, depending on if relationships used
185
+ function extractFieldValue({
186
+ row,
187
+ tableName,
188
+ fieldName,
189
+ isLinked,
190
+ }: {
191
+ row: Row
192
+ tableName: string
193
+ fieldName: string
194
+ isLinked: boolean
195
+ }) {
196
+ let value = row[`${tableName}.${fieldName}`]
197
+ if (value == null && !isLinked) {
198
+ value = row[fieldName]
199
+ }
200
+ return value
201
+ }
202
+
203
+ function basicProcessing({
204
+ row,
205
+ table,
206
+ isLinked,
207
+ }: {
208
+ row: Row
209
+ table: Table
210
+ isLinked: boolean
211
+ }): Row {
178
212
  const thisRow: Row = {}
179
213
  // filter the row down to what is actually the row (not joined)
180
- for (let fieldName of Object.keys(table.schema)) {
181
- const pathValue = row[`${table.name}.${fieldName}`]
182
- const value = pathValue != null ? pathValue : row[fieldName]
214
+ for (let field of Object.values(table.schema)) {
215
+ const fieldName = field.name
216
+
217
+ const value = extractFieldValue({
218
+ row,
219
+ tableName: table.name,
220
+ fieldName,
221
+ isLinked,
222
+ })
223
+
183
224
  // all responses include "select col as table.col" so that overlaps are handled
184
225
  if (value != null) {
185
226
  thisRow[fieldName] = value
186
227
  }
187
228
  }
188
- thisRow._id = generateIdForRow(row, table)
229
+ thisRow._id = generateIdForRow(row, table, isLinked)
189
230
  thisRow.tableId = table._id
190
231
  thisRow._rev = "rev"
191
232
  return processFormulas(table, thisRow)
@@ -293,7 +334,7 @@ export class ExternalRequest {
293
334
  // we're not inserting a doc, will be a bunch of update calls
294
335
  const otherKey: string = field.throughFrom || linkTablePrimary
295
336
  const thisKey: string = field.throughTo || tablePrimary
296
- row[key].map((relationship: any) => {
337
+ row[key].forEach((relationship: any) => {
297
338
  manyRelationships.push({
298
339
  tableId: field.through || field.tableId,
299
340
  isUpdate: false,
@@ -309,7 +350,7 @@ export class ExternalRequest {
309
350
  const thisKey: string = "id"
310
351
  // @ts-ignore
311
352
  const otherKey: string = field.fieldName
312
- row[key].map((relationship: any) => {
353
+ row[key].forEach((relationship: any) => {
313
354
  manyRelationships.push({
314
355
  tableId: field.tableId,
315
356
  isUpdate: true,
@@ -379,7 +420,8 @@ export class ExternalRequest {
379
420
  ) {
380
421
  continue
381
422
  }
382
- let linked = basicProcessing(row, linkedTable)
423
+
424
+ let linked = basicProcessing({ row, table: linkedTable, isLinked: true })
383
425
  if (!linked._id) {
384
426
  continue
385
427
  }
@@ -427,7 +469,10 @@ export class ExternalRequest {
427
469
  )
428
470
  continue
429
471
  }
430
- const thisRow = fixArrayTypes(basicProcessing(row, table), table)
472
+ const thisRow = fixArrayTypes(
473
+ basicProcessing({ row, table, isLinked: false }),
474
+ table
475
+ )
431
476
  if (thisRow._id == null) {
432
477
  throw "Unable to generate row ID for SQL rows"
433
478
  }
@@ -567,19 +612,41 @@ export class ExternalRequest {
567
612
  const { key, tableId, isUpdate, id, ...rest } = relationship
568
613
  const body: { [key: string]: any } = processObjectSync(rest, row, {})
569
614
  const linkTable = this.getTable(tableId)
570
- // @ts-ignore
571
- const linkPrimary = linkTable?.primary[0]
615
+ const relationshipPrimary = linkTable?.primary || []
616
+ const linkPrimary = relationshipPrimary[0]
572
617
  if (!linkTable || !linkPrimary) {
573
618
  return
574
619
  }
620
+
621
+ const linkSecondary = relationshipPrimary[1]
622
+
575
623
  const rows = related[key]?.rows || []
576
- const found = rows.find(
577
- (row: { [key: string]: any }) =>
624
+
625
+ function relationshipMatchPredicate({
626
+ row,
627
+ linkPrimary,
628
+ linkSecondary,
629
+ }: {
630
+ row: { [key: string]: any }
631
+ linkPrimary: string
632
+ linkSecondary?: string
633
+ }) {
634
+ const matchesPrimaryLink =
578
635
  row[linkPrimary] === relationship.id ||
579
636
  row[linkPrimary] === body?.[linkPrimary]
637
+ if (!matchesPrimaryLink || !linkSecondary) {
638
+ return matchesPrimaryLink
639
+ }
640
+
641
+ const matchesSecondayLink = row[linkSecondary] === body?.[linkSecondary]
642
+ return matchesPrimaryLink && matchesSecondayLink
643
+ }
644
+
645
+ const existingRelationship = rows.find((row: { [key: string]: any }) =>
646
+ relationshipMatchPredicate({ row, linkPrimary, linkSecondary })
580
647
  )
581
648
  const operation = isUpdate ? Operation.UPDATE : Operation.CREATE
582
- if (!found) {
649
+ if (!existingRelationship) {
583
650
  promises.push(
584
651
  getDatasourceAndQuery({
585
652
  endpoint: getEndpoint(tableId, operation),
@@ -590,7 +657,7 @@ export class ExternalRequest {
590
657
  )
591
658
  } else {
592
659
  // remove the relationship from cache so it isn't adjusted again
593
- rows.splice(rows.indexOf(found), 1)
660
+ rows.splice(rows.indexOf(existingRelationship), 1)
594
661
  }
595
662
  }
596
663
  // finally cleanup anything that needs to be removed
@@ -629,10 +696,7 @@ export class ExternalRequest {
629
696
  * Creating the specific list of fields that we desire, and excluding the ones that are no use to us
630
697
  * is more performant and has the added benefit of protecting against this scenario.
631
698
  */
632
- buildFields(
633
- table: Table,
634
- includeRelations: IncludeRelationship = IncludeRelationship.INCLUDE
635
- ) {
699
+ buildFields(table: Table, includeRelations: boolean) {
636
700
  function extractRealFields(table: Table, existing: string[] = []) {
637
701
  return Object.entries(table.schema)
638
702
  .filter(
@@ -691,6 +755,10 @@ export class ExternalRequest {
691
755
  }
692
756
  filters = buildFilters(id, filters || {}, table)
693
757
  const relationships = this.buildRelationships(table)
758
+
759
+ const includeSqlRelationships =
760
+ config.includeSqlRelationships === IncludeRelationship.INCLUDE
761
+
694
762
  // clean up row on ingress using schema
695
763
  const processed = this.inputProcessing(row, table)
696
764
  row = processed.row
@@ -708,9 +776,7 @@ export class ExternalRequest {
708
776
  },
709
777
  resource: {
710
778
  // have to specify the fields to avoid column overlap (for SQL)
711
- fields: isSql
712
- ? this.buildFields(table, config.includeSqlRelationships)
713
- : [],
779
+ fields: isSql ? this.buildFields(table, includeSqlRelationships) : [],
714
780
  },
715
781
  filters,
716
782
  sort,
@@ -725,6 +791,7 @@ export class ExternalRequest {
725
791
  table,
726
792
  },
727
793
  }
794
+
728
795
  // can't really use response right now
729
796
  const response = await getDatasourceAndQuery(json)
730
797
  // handle many to many relationships now if we know the ID (could be auto increment)
@@ -58,7 +58,7 @@ export async function patch(ctx: BBContext) {
58
58
  return handleRequest(Operation.UPDATE, tableId, {
59
59
  id: breakRowIdField(id),
60
60
  row: inputs,
61
- includeSqlRelationships: IncludeRelationship.EXCLUDE,
61
+ includeSqlRelationships: IncludeRelationship.INCLUDE,
62
62
  })
63
63
  }
64
64