Move row.validate to the sdk
This commit is contained in:
parent
af933bd158
commit
11f0569446
|
@ -15,7 +15,6 @@ import {
|
||||||
UserCtx,
|
UserCtx,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import * as utils from "./utils"
|
|
||||||
|
|
||||||
async function getRow(
|
async function getRow(
|
||||||
tableId: string,
|
tableId: string,
|
||||||
|
@ -61,7 +60,7 @@ export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
const { id, ...rowData } = ctx.request.body
|
const { id, ...rowData } = ctx.request.body
|
||||||
|
|
||||||
const validateResult = await utils.validate({
|
const validateResult = await sdk.rows.utils.validate({
|
||||||
row: rowData,
|
row: rowData,
|
||||||
tableId,
|
tableId,
|
||||||
})
|
})
|
||||||
|
@ -84,7 +83,7 @@ export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
|
||||||
export async function save(ctx: UserCtx) {
|
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({
|
const validateResult = await sdk.rows.utils.validate({
|
||||||
row: inputs,
|
row: inputs,
|
||||||
tableId,
|
tableId,
|
||||||
})
|
})
|
||||||
|
|
|
@ -30,8 +30,8 @@ export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
|
||||||
const tableId = inputs.tableId
|
const tableId = inputs.tableId
|
||||||
const isUserTable = tableId === InternalTables.USER_METADATA
|
const isUserTable = tableId === InternalTables.USER_METADATA
|
||||||
let oldRow
|
let oldRow
|
||||||
|
const dbTable = await sdk.tables.getTable(tableId)
|
||||||
try {
|
try {
|
||||||
let dbTable = await sdk.tables.getTable(tableId)
|
|
||||||
oldRow = await outputProcessing(
|
oldRow = await outputProcessing(
|
||||||
dbTable,
|
dbTable,
|
||||||
await utils.findRow(ctx, tableId, inputs._id!)
|
await utils.findRow(ctx, tableId, inputs._id!)
|
||||||
|
@ -47,7 +47,7 @@ export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
|
||||||
throw "Row does not exist"
|
throw "Row does not exist"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let dbTable = await sdk.tables.getTable(tableId)
|
|
||||||
// need to build up full patch fields before coerce
|
// need to build up full patch fields before coerce
|
||||||
let combinedRow: any = cloneDeep(oldRow)
|
let combinedRow: any = cloneDeep(oldRow)
|
||||||
for (let key of Object.keys(inputs)) {
|
for (let key of Object.keys(inputs)) {
|
||||||
|
@ -60,7 +60,7 @@ export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
|
||||||
|
|
||||||
// this returns the table and row incase they have been updated
|
// this returns the table and row incase they have been updated
|
||||||
let { table, row } = inputProcessing(ctx.user, tableClone, combinedRow)
|
let { table, row } = inputProcessing(ctx.user, tableClone, combinedRow)
|
||||||
const validateResult = await utils.validate({
|
const validateResult = await sdk.rows.utils.validate({
|
||||||
row,
|
row,
|
||||||
table,
|
table,
|
||||||
})
|
})
|
||||||
|
@ -109,7 +109,7 @@ export async function save(ctx: UserCtx) {
|
||||||
|
|
||||||
let { table, row } = inputProcessing(ctx.user, tableClone, inputs)
|
let { table, row } = inputProcessing(ctx.user, tableClone, inputs)
|
||||||
|
|
||||||
const validateResult = await utils.validate({
|
const validateResult = await sdk.rows.utils.validate({
|
||||||
row,
|
row,
|
||||||
table,
|
table,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import { TableSchema } from "@budibase/types"
|
import cloneDeep from "lodash/cloneDeep"
|
||||||
|
import validateJs from "validate.js"
|
||||||
|
import { FieldType, Row, Table, TableSchema } from "@budibase/types"
|
||||||
import { FieldTypes } from "../../../constants"
|
import { FieldTypes } from "../../../constants"
|
||||||
import { makeExternalQuery } from "../../../integrations/base/query"
|
import { makeExternalQuery } from "../../../integrations/base/query"
|
||||||
import { Format } from "../../../api/controllers/view/exporters"
|
import { Format } from "../../../api/controllers/view/exporters"
|
||||||
|
@ -46,3 +48,90 @@ export function cleanExportRows(
|
||||||
|
|
||||||
return cleanRows
|
return cleanRows
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isForeignKey(key: string, table: Table) {
|
||||||
|
const relationships = Object.values(table.schema).filter(
|
||||||
|
column => column.type === FieldType.LINK
|
||||||
|
)
|
||||||
|
return relationships.some(relationship => relationship.foreignKey === key)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validate({
|
||||||
|
tableId,
|
||||||
|
row,
|
||||||
|
table,
|
||||||
|
}: {
|
||||||
|
tableId?: string
|
||||||
|
row: Row
|
||||||
|
table?: Table
|
||||||
|
}): Promise<{
|
||||||
|
valid: boolean
|
||||||
|
errors: Record<string, any>
|
||||||
|
}> {
|
||||||
|
let fetchedTable: Table
|
||||||
|
if (!table) {
|
||||||
|
fetchedTable = await sdk.tables.getTable(tableId)
|
||||||
|
} else {
|
||||||
|
fetchedTable = table
|
||||||
|
}
|
||||||
|
const errors: Record<string, any> = {}
|
||||||
|
for (let fieldName of Object.keys(fetchedTable.schema)) {
|
||||||
|
const column = fetchedTable.schema[fieldName]
|
||||||
|
const constraints = cloneDeep(column.constraints)
|
||||||
|
const type = column.type
|
||||||
|
// foreign keys are likely to be enriched
|
||||||
|
if (isForeignKey(fieldName, fetchedTable)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// formulas shouldn't validated, data will be deleted anyway
|
||||||
|
if (type === FieldTypes.FORMULA || column.autocolumn) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// special case for options, need to always allow unselected (empty)
|
||||||
|
if (type === FieldTypes.OPTIONS && constraints?.inclusion) {
|
||||||
|
constraints.inclusion.push(null as any, "")
|
||||||
|
}
|
||||||
|
let res
|
||||||
|
|
||||||
|
// Validate.js doesn't seem to handle array
|
||||||
|
if (type === FieldTypes.ARRAY && row[fieldName]) {
|
||||||
|
if (row[fieldName].length) {
|
||||||
|
if (!Array.isArray(row[fieldName])) {
|
||||||
|
row[fieldName] = row[fieldName].split(",")
|
||||||
|
}
|
||||||
|
row[fieldName].map((val: any) => {
|
||||||
|
if (
|
||||||
|
!constraints?.inclusion?.includes(val) &&
|
||||||
|
constraints?.inclusion?.length !== 0
|
||||||
|
) {
|
||||||
|
errors[fieldName] = "Field not in list"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (constraints?.presence && row[fieldName].length === 0) {
|
||||||
|
// non required MultiSelect creates an empty array, which should not throw errors
|
||||||
|
errors[fieldName] = [`${fieldName} is required`]
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
(type === FieldTypes.ATTACHMENT || type === FieldTypes.JSON) &&
|
||||||
|
typeof row[fieldName] === "string"
|
||||||
|
) {
|
||||||
|
// this should only happen if there is an error
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(row[fieldName])
|
||||||
|
if (type === FieldTypes.ATTACHMENT) {
|
||||||
|
if (Array.isArray(json)) {
|
||||||
|
row[fieldName] = json
|
||||||
|
} else {
|
||||||
|
errors[fieldName] = [`Must be an array`]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
errors[fieldName] = [`Contains invalid JSON`]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res = validateJs.single(row[fieldName], constraints)
|
||||||
|
}
|
||||||
|
if (res) errors[fieldName] = res
|
||||||
|
}
|
||||||
|
return { valid: Object.keys(errors).length === 0, errors }
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue