Adding mechanism for verifying the Budibase properties, such as required and min/max as part of the external API.
This commit is contained in:
parent
6be2f6f793
commit
6b8d0ca9dd
|
@ -12,7 +12,7 @@ import * as exporters from "../view/exporters"
|
||||||
import { apiFileReturn } from "../../../utilities/fileSystem"
|
import { apiFileReturn } from "../../../utilities/fileSystem"
|
||||||
import {
|
import {
|
||||||
Operation,
|
Operation,
|
||||||
BBContext,
|
UserCtx,
|
||||||
Row,
|
Row,
|
||||||
PaginationJson,
|
PaginationJson,
|
||||||
Table,
|
Table,
|
||||||
|
@ -21,6 +21,7 @@ import {
|
||||||
SortJson,
|
SortJson,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
|
import * as utils from "./utils"
|
||||||
|
|
||||||
const { cleanExportRows } = require("./utils")
|
const { cleanExportRows } = require("./utils")
|
||||||
|
|
||||||
|
@ -49,12 +50,19 @@ export async function handleRequest(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function patch(ctx: BBContext) {
|
export async function patch(ctx: UserCtx) {
|
||||||
const inputs = ctx.request.body
|
const inputs = ctx.request.body
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
const id = inputs._id
|
const id = inputs._id
|
||||||
// don't save the ID to db
|
// don't save the ID to db
|
||||||
delete inputs._id
|
delete inputs._id
|
||||||
|
const validateResult = await utils.validate({
|
||||||
|
row: inputs,
|
||||||
|
tableId,
|
||||||
|
})
|
||||||
|
if (!validateResult.valid) {
|
||||||
|
throw { validation: validateResult.errors }
|
||||||
|
}
|
||||||
return handleRequest(Operation.UPDATE, tableId, {
|
return handleRequest(Operation.UPDATE, tableId, {
|
||||||
id: breakRowIdField(id),
|
id: breakRowIdField(id),
|
||||||
row: inputs,
|
row: inputs,
|
||||||
|
@ -62,16 +70,23 @@ export async function patch(ctx: BBContext) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function save(ctx: BBContext) {
|
export async function save(ctx: UserCtx) {
|
||||||
const inputs = ctx.request.body
|
const inputs = ctx.request.body
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
|
const validateResult = await utils.validate({
|
||||||
|
row: inputs,
|
||||||
|
tableId,
|
||||||
|
})
|
||||||
|
if (!validateResult.valid) {
|
||||||
|
throw { validation: validateResult.errors }
|
||||||
|
}
|
||||||
return handleRequest(Operation.CREATE, tableId, {
|
return handleRequest(Operation.CREATE, tableId, {
|
||||||
row: inputs,
|
row: inputs,
|
||||||
includeSqlRelationships: IncludeRelationship.EXCLUDE,
|
includeSqlRelationships: IncludeRelationship.EXCLUDE,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchView(ctx: BBContext) {
|
export async function fetchView(ctx: UserCtx) {
|
||||||
// there are no views in external datasources, shouldn't ever be called
|
// there are no views in external datasources, shouldn't ever be called
|
||||||
// for now just fetch
|
// for now just fetch
|
||||||
const split = ctx.params.viewName.split("all_")
|
const split = ctx.params.viewName.split("all_")
|
||||||
|
@ -79,14 +94,14 @@ export async function fetchView(ctx: BBContext) {
|
||||||
return fetch(ctx)
|
return fetch(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetch(ctx: BBContext) {
|
export async function fetch(ctx: UserCtx) {
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
return handleRequest(Operation.READ, tableId, {
|
return handleRequest(Operation.READ, tableId, {
|
||||||
includeSqlRelationships: IncludeRelationship.INCLUDE,
|
includeSqlRelationships: IncludeRelationship.INCLUDE,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function find(ctx: BBContext) {
|
export async function find(ctx: UserCtx) {
|
||||||
const id = ctx.params.rowId
|
const id = ctx.params.rowId
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
const response = (await handleRequest(Operation.READ, tableId, {
|
const response = (await handleRequest(Operation.READ, tableId, {
|
||||||
|
@ -96,7 +111,7 @@ export async function find(ctx: BBContext) {
|
||||||
return response ? response[0] : response
|
return response ? response[0] : response
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function destroy(ctx: BBContext) {
|
export async function destroy(ctx: UserCtx) {
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
const id = ctx.request.body._id
|
const id = ctx.request.body._id
|
||||||
const { row } = (await handleRequest(Operation.DELETE, tableId, {
|
const { row } = (await handleRequest(Operation.DELETE, tableId, {
|
||||||
|
@ -106,7 +121,7 @@ export async function destroy(ctx: BBContext) {
|
||||||
return { response: { ok: true }, row }
|
return { response: { ok: true }, row }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function bulkDestroy(ctx: BBContext) {
|
export async function bulkDestroy(ctx: UserCtx) {
|
||||||
const { rows } = ctx.request.body
|
const { rows } = ctx.request.body
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
let promises: Promise<Row[] | { row: Row; table: Table }>[] = []
|
let promises: Promise<Row[] | { row: Row; table: Table }>[] = []
|
||||||
|
@ -122,7 +137,7 @@ export async function bulkDestroy(ctx: BBContext) {
|
||||||
return { response: { ok: true }, rows: responses.map(resp => resp.row) }
|
return { response: { ok: true }, rows: responses.map(resp => resp.row) }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function search(ctx: BBContext) {
|
export async function search(ctx: UserCtx) {
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
const { paginate, query, ...params } = ctx.request.body
|
const { paginate, query, ...params } = ctx.request.body
|
||||||
let { bookmark, limit } = params
|
let { bookmark, limit } = params
|
||||||
|
@ -185,12 +200,7 @@ export async function search(ctx: BBContext) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function validate(ctx: BBContext) {
|
export async function exportRows(ctx: UserCtx) {
|
||||||
// can't validate external right now - maybe in future
|
|
||||||
return { valid: true }
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function exportRows(ctx: BBContext) {
|
|
||||||
const { datasourceId, tableName } = breakExternalTableId(ctx.params.tableId)
|
const { datasourceId, tableName } = breakExternalTableId(ctx.params.tableId)
|
||||||
const format = ctx.query.format
|
const format = ctx.query.format
|
||||||
const { columns } = ctx.request.body
|
const { columns } = ctx.request.body
|
||||||
|
@ -244,7 +254,7 @@ export async function exportRows(ctx: BBContext) {
|
||||||
return apiFileReturn(exporter(headers, exportRows))
|
return apiFileReturn(exporter(headers, exportRows))
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchEnrichedRow(ctx: BBContext) {
|
export async function fetchEnrichedRow(ctx: UserCtx) {
|
||||||
const id = ctx.params.rowId
|
const id = ctx.params.rowId
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
const { datasourceId, tableName } = breakExternalTableId(tableId)
|
const { datasourceId, tableName } = breakExternalTableId(tableId)
|
||||||
|
|
|
@ -2,6 +2,8 @@ import { quotas } from "@budibase/pro"
|
||||||
import * as internal from "./internal"
|
import * as internal from "./internal"
|
||||||
import * as external from "./external"
|
import * as external from "./external"
|
||||||
import { isExternalTable } from "../../../integrations/utils"
|
import { isExternalTable } from "../../../integrations/utils"
|
||||||
|
import { Ctx } from "@budibase/types"
|
||||||
|
import * as utils from "./utils"
|
||||||
|
|
||||||
function pickApi(tableId: any) {
|
function pickApi(tableId: any) {
|
||||||
if (isExternalTable(tableId)) {
|
if (isExternalTable(tableId)) {
|
||||||
|
@ -129,9 +131,12 @@ export async function search(ctx: any) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function validate(ctx: any) {
|
export async function validate(ctx: Ctx) {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
ctx.body = await pickApi(tableId).validate(ctx)
|
ctx.body = await utils.validate({
|
||||||
|
row: ctx.request.body,
|
||||||
|
tableId,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchEnrichedRow(ctx: any) {
|
export async function fetchEnrichedRow(ctx: any) {
|
||||||
|
|
|
@ -387,13 +387,6 @@ export async function search(ctx: Ctx) {
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function validate(ctx: Ctx) {
|
|
||||||
return utils.validate({
|
|
||||||
tableId: ctx.params.tableId,
|
|
||||||
row: ctx.request.body,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function exportRows(ctx: Ctx) {
|
export async function exportRows(ctx: Ctx) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const table = await db.get(ctx.params.tableId)
|
const table = await db.get(ctx.params.tableId)
|
||||||
|
|
|
@ -4,11 +4,11 @@ import { FieldTypes } from "../../../constants"
|
||||||
import { context } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
import { makeExternalQuery } from "../../../integrations/base/query"
|
import { makeExternalQuery } from "../../../integrations/base/query"
|
||||||
import { Row, Table } from "@budibase/types"
|
import { Row, Table } from "@budibase/types"
|
||||||
const validateJs = require("validate.js")
|
|
||||||
const { cloneDeep } = require("lodash/fp")
|
|
||||||
import { Format } from "../view/exporters"
|
import { Format } from "../view/exporters"
|
||||||
import { Ctx } from "@budibase/types"
|
import { Ctx } from "@budibase/types"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
|
const validateJs = require("validate.js")
|
||||||
|
const { cloneDeep } = require("lodash/fp")
|
||||||
|
|
||||||
validateJs.extend(validateJs.validators.datetime, {
|
validateJs.extend(validateJs.validators.datetime, {
|
||||||
parse: function (value: string) {
|
parse: function (value: string) {
|
||||||
|
@ -56,8 +56,7 @@ export async function validate({
|
||||||
}) {
|
}) {
|
||||||
let fetchedTable: Table
|
let fetchedTable: Table
|
||||||
if (!table) {
|
if (!table) {
|
||||||
const db = context.getAppDB()
|
fetchedTable = await sdk.tables.getTable(tableId)
|
||||||
fetchedTable = await db.get(tableId)
|
|
||||||
} else {
|
} else {
|
||||||
fetchedTable = table
|
fetchedTable = table
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import {
|
||||||
Operation,
|
Operation,
|
||||||
RenameColumn,
|
RenameColumn,
|
||||||
FieldSchema,
|
FieldSchema,
|
||||||
BBContext,
|
UserCtx,
|
||||||
TableRequest,
|
TableRequest,
|
||||||
RelationshipTypes,
|
RelationshipTypes,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
@ -194,7 +194,7 @@ function isRelationshipSetup(column: FieldSchema) {
|
||||||
return column.foreignKey || column.through
|
return column.foreignKey || column.through
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function save(ctx: BBContext) {
|
export async function save(ctx: UserCtx) {
|
||||||
const table: TableRequest = ctx.request.body
|
const table: TableRequest = ctx.request.body
|
||||||
const renamed = table?._rename
|
const renamed = table?._rename
|
||||||
// can't do this right now
|
// can't do this right now
|
||||||
|
@ -313,7 +313,7 @@ export async function save(ctx: BBContext) {
|
||||||
return tableToSave
|
return tableToSave
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function destroy(ctx: BBContext) {
|
export async function destroy(ctx: UserCtx) {
|
||||||
const tableToDelete: TableRequest = await sdk.tables.getTable(
|
const tableToDelete: TableRequest = await sdk.tables.getTable(
|
||||||
ctx.params.tableId
|
ctx.params.tableId
|
||||||
)
|
)
|
||||||
|
@ -339,7 +339,7 @@ export async function destroy(ctx: BBContext) {
|
||||||
return tableToDelete
|
return tableToDelete
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function bulkImport(ctx: BBContext) {
|
export async function bulkImport(ctx: UserCtx) {
|
||||||
const table = await sdk.tables.getTable(ctx.params.tableId)
|
const table = await sdk.tables.getTable(ctx.params.tableId)
|
||||||
const { rows }: { rows: unknown } = ctx.request.body
|
const { rows }: { rows: unknown } = ctx.request.body
|
||||||
const schema: unknown = table.schema
|
const schema: unknown = table.schema
|
||||||
|
@ -348,7 +348,7 @@ export async function bulkImport(ctx: BBContext) {
|
||||||
ctx.throw(400, "Provided data import information is invalid.")
|
ctx.throw(400, "Provided data import information is invalid.")
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedRows = await parse(rows, schema)
|
const parsedRows = parse(rows, schema)
|
||||||
await handleRequest(Operation.BULK_CREATE, table._id!, {
|
await handleRequest(Operation.BULK_CREATE, table._id!, {
|
||||||
rows: parsedRows,
|
rows: parsedRows,
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,7 +8,6 @@ import {
|
||||||
SearchFilters,
|
SearchFilters,
|
||||||
SortJson,
|
SortJson,
|
||||||
Table,
|
Table,
|
||||||
TableSchema,
|
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { OAuth2Client } from "google-auth-library"
|
import { OAuth2Client } from "google-auth-library"
|
||||||
import { buildExternalTableId } from "./utils"
|
import { buildExternalTableId } from "./utils"
|
||||||
|
@ -210,6 +209,26 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTableSchema(title: string, headerValues: string[], id?: string) {
|
||||||
|
// base table
|
||||||
|
const table: Table = {
|
||||||
|
name: title,
|
||||||
|
primary: ["rowNumber"],
|
||||||
|
schema: {},
|
||||||
|
}
|
||||||
|
if (id) {
|
||||||
|
table._id = id
|
||||||
|
}
|
||||||
|
// build schema from headers
|
||||||
|
for (let header of headerValues) {
|
||||||
|
table.schema[header] = {
|
||||||
|
name: header,
|
||||||
|
type: FieldTypes.STRING,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return table
|
||||||
|
}
|
||||||
|
|
||||||
async buildSchema(datasourceId: string) {
|
async buildSchema(datasourceId: string) {
|
||||||
await this.connect()
|
await this.connect()
|
||||||
const sheets = this.client.sheetsByIndex
|
const sheets = this.client.sheetsByIndex
|
||||||
|
@ -217,26 +236,14 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
for (let sheet of sheets) {
|
for (let sheet of sheets) {
|
||||||
// must fetch rows to determine schema
|
// must fetch rows to determine schema
|
||||||
await sheet.getRows()
|
await sheet.getRows()
|
||||||
// build schema
|
|
||||||
const schema: TableSchema = {}
|
|
||||||
|
|
||||||
// build schema from headers
|
const id = buildExternalTableId(datasourceId, sheet.title)
|
||||||
for (let header of sheet.headerValues) {
|
tables[sheet.title] = this.getTableSchema(
|
||||||
schema[header] = {
|
sheet.title,
|
||||||
name: header,
|
sheet.headerValues,
|
||||||
type: FieldTypes.STRING,
|
id
|
||||||
}
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// create tables
|
|
||||||
tables[sheet.title] = {
|
|
||||||
_id: buildExternalTableId(datasourceId, sheet.title),
|
|
||||||
name: sheet.title,
|
|
||||||
primary: ["rowNumber"],
|
|
||||||
schema,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.tables = tables
|
this.tables = tables
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,12 +318,19 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
} else {
|
} else {
|
||||||
const updatedHeaderValues = [...sheet.headerValues]
|
const updatedHeaderValues = [...sheet.headerValues]
|
||||||
|
|
||||||
const newField = Object.keys(table.schema).find(
|
// add new column - doesn't currently exist
|
||||||
key => !sheet.headerValues.includes(key)
|
for (let key of Object.keys(table.schema)) {
|
||||||
)
|
if (!sheet.headerValues.includes(key)) {
|
||||||
|
updatedHeaderValues.push(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (newField) {
|
// clear out deleted columns
|
||||||
updatedHeaderValues.push(newField)
|
for (let key of sheet.headerValues) {
|
||||||
|
if (!Object.keys(table.schema).includes(key)) {
|
||||||
|
const idx = updatedHeaderValues.indexOf(key)
|
||||||
|
updatedHeaderValues.splice(idx, 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await sheet.setHeaderRow(updatedHeaderValues)
|
await sheet.setHeaderRow(updatedHeaderValues)
|
||||||
|
|
|
@ -200,9 +200,9 @@ export function isIsoDateString(str: string) {
|
||||||
* @param column The column to check, to see if it is a valid relationship.
|
* @param column The column to check, to see if it is a valid relationship.
|
||||||
* @param tableIds The IDs of the tables which currently exist.
|
* @param tableIds The IDs of the tables which currently exist.
|
||||||
*/
|
*/
|
||||||
function shouldCopyRelationship(
|
export function shouldCopyRelationship(
|
||||||
column: { type: string; tableId?: string },
|
column: { type: string; tableId?: string },
|
||||||
tableIds: [string]
|
tableIds: string[]
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
column.type === FieldTypes.LINK &&
|
column.type === FieldTypes.LINK &&
|
||||||
|
@ -219,7 +219,7 @@ function shouldCopyRelationship(
|
||||||
* @param column The column to check for options or boolean type.
|
* @param column The column to check for options or boolean type.
|
||||||
* @param fetchedColumn The fetched column to check for the type in the external database.
|
* @param fetchedColumn The fetched column to check for the type in the external database.
|
||||||
*/
|
*/
|
||||||
function shouldCopySpecialColumn(
|
export function shouldCopySpecialColumn(
|
||||||
column: { type: string },
|
column: { type: string },
|
||||||
fetchedColumn: { type: string } | undefined
|
fetchedColumn: { type: string } | undefined
|
||||||
) {
|
) {
|
||||||
|
|
Loading…
Reference in New Issue