@budibase/server 2.3.18-alpha.1 → 2.3.18-alpha.11

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.
@@ -27,7 +27,9 @@ describe("row api - postgres", () => {
27
27
  let makeRequest: MakeRequestResponse,
28
28
  postgresDatasource: Datasource,
29
29
  primaryPostgresTable: Table,
30
- auxPostgresTable: Table
30
+ oneToManyRelationshipInfo: ForeignTableInfo,
31
+ manyToOneRelationshipInfo: ForeignTableInfo,
32
+ manyToManyRelationshipInfo: ForeignTableInfo
31
33
 
32
34
  let host: string
33
35
  let port: number
@@ -67,37 +69,58 @@ describe("row api - postgres", () => {
67
69
  },
68
70
  })
69
71
 
70
- auxPostgresTable = await config.createTable({
71
- name: generator.word({ length: 10 }),
72
- type: "external",
73
- primary: ["id"],
74
- schema: {
75
- id: {
76
- name: "id",
77
- type: FieldType.AUTO,
78
- constraints: {
79
- presence: true,
72
+ async function createAuxTable(prefix: string) {
73
+ return await config.createTable({
74
+ name: `${prefix}_${generator.word({ length: 6 })}`,
75
+ type: "external",
76
+ primary: ["id"],
77
+ primaryDisplay: "title",
78
+ schema: {
79
+ id: {
80
+ name: "id",
81
+ type: FieldType.AUTO,
82
+ autocolumn: true,
83
+ constraints: {
84
+ presence: true,
85
+ },
80
86
  },
81
- },
82
- title: {
83
- name: "title",
84
- type: FieldType.STRING,
85
- constraints: {
86
- presence: true,
87
+ title: {
88
+ name: "title",
89
+ type: FieldType.STRING,
90
+ constraints: {
91
+ presence: true,
92
+ },
87
93
  },
88
94
  },
89
- },
90
- sourceId: postgresDatasource._id,
91
- })
95
+ sourceId: postgresDatasource._id,
96
+ })
97
+ }
98
+
99
+ oneToManyRelationshipInfo = {
100
+ table: await createAuxTable("o2m"),
101
+ fieldName: "oneToManyRelation",
102
+ relationshipType: RelationshipTypes.ONE_TO_MANY,
103
+ }
104
+ manyToOneRelationshipInfo = {
105
+ table: await createAuxTable("m2o"),
106
+ fieldName: "manyToOneRelation",
107
+ relationshipType: RelationshipTypes.MANY_TO_ONE,
108
+ }
109
+ manyToManyRelationshipInfo = {
110
+ table: await createAuxTable("m2m"),
111
+ fieldName: "manyToManyRelation",
112
+ relationshipType: RelationshipTypes.MANY_TO_MANY,
113
+ }
92
114
 
93
115
  primaryPostgresTable = await config.createTable({
94
- name: generator.word({ length: 10 }),
116
+ name: `p_${generator.word({ length: 6 })}`,
95
117
  type: "external",
96
118
  primary: ["id"],
97
119
  schema: {
98
120
  id: {
99
121
  name: "id",
100
122
  type: FieldType.AUTO,
123
+ autocolumn: true,
101
124
  constraints: {
102
125
  presence: true,
103
126
  },
@@ -117,25 +140,48 @@ describe("row api - postgres", () => {
117
140
  name: "value",
118
141
  type: FieldType.NUMBER,
119
142
  },
120
- linkedField: {
143
+ oneToManyRelation: {
121
144
  type: FieldType.LINK,
122
145
  constraints: {
123
146
  type: "array",
124
147
  presence: false,
125
148
  },
126
- fieldName: "foreignField",
127
- name: "linkedField",
149
+ fieldName: oneToManyRelationshipInfo.fieldName,
150
+ name: "oneToManyRelation",
128
151
  relationshipType: RelationshipTypes.ONE_TO_MANY,
129
- tableId: auxPostgresTable._id,
152
+ tableId: oneToManyRelationshipInfo.table._id,
153
+ main: true,
154
+ },
155
+ manyToOneRelation: {
156
+ type: FieldType.LINK,
157
+ constraints: {
158
+ type: "array",
159
+ presence: false,
160
+ },
161
+ fieldName: manyToOneRelationshipInfo.fieldName,
162
+ name: "manyToOneRelation",
163
+ relationshipType: RelationshipTypes.MANY_TO_ONE,
164
+ tableId: manyToOneRelationshipInfo.table._id,
165
+ main: true,
166
+ },
167
+ manyToManyRelation: {
168
+ type: FieldType.LINK,
169
+ constraints: {
170
+ type: "array",
171
+ presence: false,
172
+ },
173
+ fieldName: manyToManyRelationshipInfo.fieldName,
174
+ name: "manyToManyRelation",
175
+ relationshipType: RelationshipTypes.MANY_TO_MANY,
176
+ tableId: manyToManyRelationshipInfo.table._id,
177
+ main: true,
130
178
  },
131
179
  },
132
180
  sourceId: postgresDatasource._id,
133
181
  })
134
182
  })
135
183
 
136
- afterAll(async () => {
137
- await config.end()
138
- })
184
+ afterAll(config.end)
139
185
 
140
186
  function generateRandomPrimaryRowData() {
141
187
  return {
@@ -151,22 +197,99 @@ describe("row api - postgres", () => {
151
197
  value: number
152
198
  }
153
199
 
200
+ type ForeignTableInfo = {
201
+ table: Table
202
+ fieldName: string
203
+ relationshipType: RelationshipTypes
204
+ }
205
+
206
+ type ForeignRowsInfo = {
207
+ row: Row
208
+ relationshipType: RelationshipTypes
209
+ }
210
+
154
211
  async function createPrimaryRow(opts: {
155
212
  rowData: PrimaryRowData
156
- createForeignRow?: boolean
213
+ createForeignRows?: {
214
+ createOneToMany?: boolean
215
+ createManyToOne?: number
216
+ createManyToMany?: number
217
+ }
157
218
  }) {
158
- let { rowData } = opts
159
- let foreignRow: Row | undefined
160
- if (opts?.createForeignRow) {
161
- foreignRow = await config.createRow({
162
- tableId: auxPostgresTable._id,
219
+ let { rowData } = opts as any
220
+ let foreignRows: ForeignRowsInfo[] = []
221
+
222
+ async function createForeignRow(tableInfo: ForeignTableInfo) {
223
+ const foreignKey = `fk_${tableInfo.table.name}_${tableInfo.fieldName}`
224
+
225
+ const foreignRow = await config.createRow({
226
+ tableId: tableInfo.table._id,
163
227
  title: generator.name(),
164
228
  })
165
229
 
166
230
  rowData = {
167
231
  ...rowData,
168
- [`fk_${auxPostgresTable.name}_foreignField`]: foreignRow.id,
232
+ [foreignKey]: foreignRow.id,
169
233
  }
234
+ foreignRows.push({
235
+ row: foreignRow,
236
+
237
+ relationshipType: tableInfo.relationshipType,
238
+ })
239
+ }
240
+
241
+ if (opts?.createForeignRows?.createOneToMany) {
242
+ const foreignKey = `fk_${oneToManyRelationshipInfo.table.name}_${oneToManyRelationshipInfo.fieldName}`
243
+
244
+ const foreignRow = await config.createRow({
245
+ tableId: oneToManyRelationshipInfo.table._id,
246
+ title: generator.name(),
247
+ })
248
+
249
+ rowData = {
250
+ ...rowData,
251
+ [foreignKey]: foreignRow.id,
252
+ }
253
+ foreignRows.push({
254
+ row: foreignRow,
255
+ relationshipType: oneToManyRelationshipInfo.relationshipType,
256
+ })
257
+ }
258
+
259
+ for (let i = 0; i < (opts?.createForeignRows?.createManyToOne || 0); i++) {
260
+ const foreignRow = await config.createRow({
261
+ tableId: manyToOneRelationshipInfo.table._id,
262
+ title: generator.name(),
263
+ })
264
+
265
+ rowData = {
266
+ ...rowData,
267
+ [manyToOneRelationshipInfo.fieldName]:
268
+ rowData[manyToOneRelationshipInfo.fieldName] || [],
269
+ }
270
+ rowData[manyToOneRelationshipInfo.fieldName].push(foreignRow._id)
271
+ foreignRows.push({
272
+ row: foreignRow,
273
+ relationshipType: RelationshipTypes.MANY_TO_ONE,
274
+ })
275
+ }
276
+
277
+ for (let i = 0; i < (opts?.createForeignRows?.createManyToMany || 0); i++) {
278
+ const foreignRow = await config.createRow({
279
+ tableId: manyToManyRelationshipInfo.table._id,
280
+ title: generator.name(),
281
+ })
282
+
283
+ rowData = {
284
+ ...rowData,
285
+ [manyToManyRelationshipInfo.fieldName]:
286
+ rowData[manyToManyRelationshipInfo.fieldName] || [],
287
+ }
288
+ rowData[manyToManyRelationshipInfo.fieldName].push(foreignRow._id)
289
+ foreignRows.push({
290
+ row: foreignRow,
291
+ relationshipType: RelationshipTypes.MANY_TO_MANY,
292
+ })
170
293
  }
171
294
 
172
295
  const row = await config.createRow({
@@ -174,7 +297,7 @@ describe("row api - postgres", () => {
174
297
  ...rowData,
175
298
  })
176
299
 
177
- return { row, foreignRow }
300
+ return { row, foreignRows }
178
301
  }
179
302
 
180
303
  async function createDefaultPgTable() {
@@ -198,7 +321,9 @@ describe("row api - postgres", () => {
198
321
  async function populatePrimaryRows(
199
322
  count: number,
200
323
  opts?: {
201
- createForeignRow?: boolean
324
+ createOneToMany?: boolean
325
+ createManyToOne?: number
326
+ createManyToMany?: number
202
327
  }
203
328
  ) {
204
329
  return await Promise.all(
@@ -210,7 +335,7 @@ describe("row api - postgres", () => {
210
335
  rowData,
211
336
  ...(await createPrimaryRow({
212
337
  rowData,
213
- createForeignRow: opts?.createForeignRow,
338
+ createForeignRows: opts,
214
339
  })),
215
340
  }
216
341
  })
@@ -295,7 +420,7 @@ describe("row api - postgres", () => {
295
420
  describe("given than a row exists", () => {
296
421
  let row: Row
297
422
  beforeEach(async () => {
298
- let rowResponse = _.sample(await populatePrimaryRows(10))!
423
+ let rowResponse = _.sample(await populatePrimaryRows(1))!
299
424
  row = rowResponse.row
300
425
  })
301
426
 
@@ -403,7 +528,7 @@ describe("row api - postgres", () => {
403
528
  let rows: { row: Row; rowData: PrimaryRowData }[]
404
529
 
405
530
  beforeEach(async () => {
406
- rows = await populatePrimaryRows(10)
531
+ rows = await populatePrimaryRows(5)
407
532
  })
408
533
 
409
534
  it("a single row can be retrieved successfully", async () => {
@@ -419,34 +544,136 @@ describe("row api - postgres", () => {
419
544
 
420
545
  describe("given a row with relation data", () => {
421
546
  let row: Row
422
- let foreignRow: Row
423
- beforeEach(async () => {
424
- let [createdRow] = await populatePrimaryRows(1, {
425
- createForeignRow: true,
547
+ let rowData: {
548
+ name: string
549
+ description: string
550
+ value: number
551
+ }
552
+ let foreignRows: ForeignRowsInfo[]
553
+
554
+ describe("with all relationship types", () => {
555
+ beforeEach(async () => {
556
+ let [createdRow] = await populatePrimaryRows(1, {
557
+ createOneToMany: true,
558
+ createManyToOne: 3,
559
+ createManyToMany: 2,
560
+ })
561
+ row = createdRow.row
562
+ rowData = createdRow.rowData
563
+ foreignRows = createdRow.foreignRows
564
+ })
565
+
566
+ it("only one to many foreign keys are retrieved", async () => {
567
+ const res = await getRow(primaryPostgresTable._id, row.id)
568
+
569
+ expect(res.status).toBe(200)
570
+
571
+ const one2ManyForeignRows = foreignRows.filter(
572
+ x => x.relationshipType === RelationshipTypes.ONE_TO_MANY
573
+ )
574
+ expect(one2ManyForeignRows).toHaveLength(1)
575
+
576
+ expect(res.body).toEqual({
577
+ ...rowData,
578
+ id: row.id,
579
+ tableId: row.tableId,
580
+ _id: expect.any(String),
581
+ _rev: expect.any(String),
582
+ [`fk_${oneToManyRelationshipInfo.table.name}_${oneToManyRelationshipInfo.fieldName}`]:
583
+ one2ManyForeignRows[0].row.id,
584
+ })
585
+
586
+ expect(res.body[oneToManyRelationshipInfo.fieldName]).toBeUndefined()
426
587
  })
427
- row = createdRow.row
428
- foreignRow = createdRow.foreignRow!
429
588
  })
430
589
 
431
- it("only foreign keys are retrieved", async () => {
432
- const res = await getRow(primaryPostgresTable._id, row.id)
590
+ describe("with only one to many", () => {
591
+ beforeEach(async () => {
592
+ let [createdRow] = await populatePrimaryRows(1, {
593
+ createOneToMany: true,
594
+ })
595
+ row = createdRow.row
596
+ rowData = createdRow.rowData
597
+ foreignRows = createdRow.foreignRows
598
+ })
433
599
 
434
- expect(res.status).toBe(200)
600
+ it("only one to many foreign keys are retrieved", async () => {
601
+ const res = await getRow(primaryPostgresTable._id, row.id)
435
602
 
436
- expect(res.body).toEqual({
437
- ...row,
438
- _id: expect.any(String),
439
- _rev: expect.any(String),
603
+ expect(res.status).toBe(200)
604
+
605
+ expect(foreignRows).toHaveLength(1)
606
+
607
+ expect(res.body).toEqual({
608
+ ...rowData,
609
+ id: row.id,
610
+ tableId: row.tableId,
611
+ _id: expect.any(String),
612
+ _rev: expect.any(String),
613
+ [`fk_${oneToManyRelationshipInfo.table.name}_${oneToManyRelationshipInfo.fieldName}`]:
614
+ foreignRows[0].row.id,
615
+ })
616
+
617
+ expect(res.body[oneToManyRelationshipInfo.fieldName]).toBeUndefined()
440
618
  })
619
+ })
441
620
 
442
- expect(res.body.foreignField).toBeUndefined()
621
+ describe("with only many to one", () => {
622
+ beforeEach(async () => {
623
+ let [createdRow] = await populatePrimaryRows(1, {
624
+ createManyToOne: 3,
625
+ })
626
+ row = createdRow.row
627
+ rowData = createdRow.rowData
628
+ foreignRows = createdRow.foreignRows
629
+ })
443
630
 
444
- expect(
445
- res.body[`fk_${auxPostgresTable.name}_foreignField`]
446
- ).toBeDefined()
447
- expect(res.body[`fk_${auxPostgresTable.name}_foreignField`]).toBe(
448
- foreignRow.id
449
- )
631
+ it("only one to many foreign keys are retrieved", async () => {
632
+ const res = await getRow(primaryPostgresTable._id, row.id)
633
+
634
+ expect(res.status).toBe(200)
635
+
636
+ expect(foreignRows).toHaveLength(3)
637
+
638
+ expect(res.body).toEqual({
639
+ ...rowData,
640
+ id: row.id,
641
+ tableId: row.tableId,
642
+ _id: expect.any(String),
643
+ _rev: expect.any(String),
644
+ })
645
+
646
+ expect(res.body[oneToManyRelationshipInfo.fieldName]).toBeUndefined()
647
+ })
648
+ })
649
+
650
+ describe("with only many to many", () => {
651
+ beforeEach(async () => {
652
+ let [createdRow] = await populatePrimaryRows(1, {
653
+ createManyToMany: 2,
654
+ })
655
+ row = createdRow.row
656
+ rowData = createdRow.rowData
657
+ foreignRows = createdRow.foreignRows
658
+ })
659
+
660
+ it("only one to many foreign keys are retrieved", async () => {
661
+ const res = await getRow(primaryPostgresTable._id, row.id)
662
+
663
+ expect(res.status).toBe(200)
664
+
665
+ expect(foreignRows).toHaveLength(2)
666
+
667
+ expect(res.body).toEqual({
668
+ ...rowData,
669
+ id: row.id,
670
+ tableId: row.tableId,
671
+ _id: expect.any(String),
672
+ _rev: expect.any(String),
673
+ })
674
+
675
+ expect(res.body[oneToManyRelationshipInfo.fieldName]).toBeUndefined()
676
+ })
450
677
  })
451
678
  })
452
679
  })
@@ -667,31 +894,74 @@ describe("row api - postgres", () => {
667
894
  const getAll = (tableId: string | undefined, rowId: string | undefined) =>
668
895
  makeRequest("get", `/api/${tableId}/${rowId}/enrich`)
669
896
  describe("given a row with relation data", () => {
670
- let row: Row, foreignRow: Row | undefined
897
+ let row: Row, rowData: PrimaryRowData, foreignRows: ForeignRowsInfo[]
671
898
 
672
- beforeEach(async () => {
673
- const rowsInfo = await createPrimaryRow({
674
- rowData: generateRandomPrimaryRowData(),
675
- createForeignRow: true,
676
- })
899
+ describe("with all relationship types", () => {
900
+ beforeEach(async () => {
901
+ rowData = generateRandomPrimaryRowData()
902
+ const rowsInfo = await createPrimaryRow({
903
+ rowData,
904
+ createForeignRows: {
905
+ createOneToMany: true,
906
+ createManyToOne: 3,
907
+ createManyToMany: 2,
908
+ },
909
+ })
677
910
 
678
- row = rowsInfo.row
679
- foreignRow = rowsInfo.foreignRow
680
- })
911
+ row = rowsInfo.row
912
+ foreignRows = rowsInfo.foreignRows
913
+ })
681
914
 
682
- it("enrich populates the foreign field", async () => {
683
- const res = await getAll(primaryPostgresTable._id, row.id)
915
+ it("enrich populates the foreign fields", async () => {
916
+ const res = await getAll(primaryPostgresTable._id, row.id)
684
917
 
685
- expect(res.status).toBe(200)
918
+ expect(res.status).toBe(200)
686
919
 
687
- expect(foreignRow).toBeDefined()
688
- expect(res.body).toEqual({
689
- ...row,
690
- linkedField: [
691
- {
692
- ...foreignRow,
693
- },
694
- ],
920
+ const foreignRowsByType = _.groupBy(
921
+ foreignRows,
922
+ x => x.relationshipType
923
+ )
924
+ expect(res.body).toEqual({
925
+ ...rowData,
926
+ [`fk_${oneToManyRelationshipInfo.table.name}_${oneToManyRelationshipInfo.fieldName}`]:
927
+ foreignRowsByType[RelationshipTypes.ONE_TO_MANY][0].row.id,
928
+ [oneToManyRelationshipInfo.fieldName]: [
929
+ {
930
+ ...foreignRowsByType[RelationshipTypes.ONE_TO_MANY][0].row,
931
+ _id: expect.any(String),
932
+ _rev: expect.any(String),
933
+ },
934
+ ],
935
+ [manyToOneRelationshipInfo.fieldName]: [
936
+ {
937
+ ...foreignRowsByType[RelationshipTypes.MANY_TO_ONE][0].row,
938
+ [`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]:
939
+ row.id,
940
+ },
941
+ {
942
+ ...foreignRowsByType[RelationshipTypes.MANY_TO_ONE][1].row,
943
+ [`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]:
944
+ row.id,
945
+ },
946
+ {
947
+ ...foreignRowsByType[RelationshipTypes.MANY_TO_ONE][2].row,
948
+ [`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]:
949
+ row.id,
950
+ },
951
+ ],
952
+ [manyToManyRelationshipInfo.fieldName]: [
953
+ {
954
+ ...foreignRowsByType[RelationshipTypes.MANY_TO_MANY][0].row,
955
+ },
956
+ {
957
+ ...foreignRowsByType[RelationshipTypes.MANY_TO_MANY][1].row,
958
+ },
959
+ ],
960
+ id: row.id,
961
+ tableId: row.tableId,
962
+ _id: expect.any(String),
963
+ _rev: expect.any(String),
964
+ })
695
965
  })
696
966
  })
697
967
  })
@@ -714,7 +984,7 @@ describe("row api - postgres", () => {
714
984
  const rowsCount = 6
715
985
  let rows: {
716
986
  row: Row
717
- foreignRow: Row | undefined
987
+ foreignRows: ForeignRowsInfo[]
718
988
  rowData: PrimaryRowData
719
989
  }[]
720
990
  beforeEach(async () => {
@@ -415,9 +415,7 @@ class InternalBuilder {
415
415
  if (opts.disableReturning) {
416
416
  return query.insert(parsedBody)
417
417
  } else {
418
- return query
419
- .insert(parsedBody)
420
- .returning(generateSelectStatement(json, knex))
418
+ return query.insert(parsedBody).returning("*")
421
419
  }
422
420
  }
423
421
 
@@ -502,9 +500,7 @@ class InternalBuilder {
502
500
  if (opts.disableReturning) {
503
501
  return query.update(parsedBody)
504
502
  } else {
505
- return query
506
- .update(parsedBody)
507
- .returning(generateSelectStatement(json, knex))
503
+ return query.update(parsedBody).returning("*")
508
504
  }
509
505
  }
510
506
 
@@ -79,10 +79,6 @@ export default (
79
79
  return ctx.throw(403, "No user info found")
80
80
  }
81
81
 
82
- // check general builder stuff, this middleware is a good way
83
- // to find API endpoints which are builder focused
84
- await builderMiddleware(ctx, permType)
85
-
86
82
  // get the resource roles
87
83
  let resourceRoles: any = []
88
84
  let otherLevelRoles: any = []
@@ -112,6 +108,12 @@ export default (
112
108
  return ctx.throw(403, "Session not authenticated")
113
109
  }
114
110
 
111
+ // check general builder stuff, this middleware is a good way
112
+ // to find API endpoints which are builder focused
113
+ if (permType === permissions.PermissionType.BUILDER) {
114
+ await builderMiddleware(ctx)
115
+ }
116
+
115
117
  try {
116
118
  // check authorized
117
119
  await checkAuthorized(ctx, resourceRoles, permType, permLevel)
@@ -64,13 +64,18 @@ async function updateAppUpdatedAt(ctx: BBContext) {
64
64
  })
65
65
  }
66
66
 
67
- export default async function builder(ctx: BBContext, permType: string) {
67
+ export default async function builder(ctx: BBContext) {
68
68
  const appId = ctx.appId
69
69
  // this only functions within an app context
70
70
  if (!appId) {
71
71
  return
72
72
  }
73
- const isBuilderApi = permType === permissions.PermissionType.BUILDER
73
+
74
+ // check authenticated
75
+ if (!ctx.isAuthenticated) {
76
+ return ctx.throw(403, "Session not authenticated")
77
+ }
78
+
74
79
  const referer = ctx.headers["referer"]
75
80
 
76
81
  const overviewPath = "/builder/portal/overview/"
@@ -82,7 +87,7 @@ export default async function builder(ctx: BBContext, permType: string) {
82
87
  const hasAppId = !referer ? false : referer.includes(appId)
83
88
  const editingApp = referer ? hasAppId : false
84
89
  // check this is a builder call and editing
85
- if (!isBuilderApi || !editingApp) {
90
+ if (!editingApp) {
86
91
  return
87
92
  }
88
93
  // check locks