budibase/packages/server/src/sdk/app/views/index.ts

172 lines
4.3 KiB
TypeScript

import {
RenameColumn,
TableSchema,
View,
ViewUIFieldMetadata,
ViewV2,
ViewV2Enriched,
} from "@budibase/types"
import { HTTPError, db as dbCore } from "@budibase/backend-core"
import { features } from "@budibase/pro"
import { cloneDeep } from "lodash"
import * as utils from "../../../db/utils"
import { isExternalTableID } from "../../../integrations/utils"
import * as internal from "./internal"
import * as external from "./external"
import sdk from "../../../sdk"
import { isRequired } from "../../../utilities/schema"
function pickApi(tableId: any) {
if (isExternalTableID(tableId)) {
return external
}
return internal
}
export async function get(viewId: string): Promise<ViewV2> {
const { tableId } = utils.extractViewInfoFromID(viewId)
return pickApi(tableId).get(viewId)
}
export async function getEnriched(viewId: string): Promise<ViewV2Enriched> {
const { tableId } = utils.extractViewInfoFromID(viewId)
return pickApi(tableId).getEnriched(viewId)
}
async function guardViewSchema(
tableId: string,
viewSchema?: Record<string, ViewUIFieldMetadata>
) {
if (!viewSchema || !Object.keys(viewSchema).length) {
return
}
const table = await sdk.tables.getTable(tableId)
for (const field of Object.keys(viewSchema)) {
const tableSchemaField = table.schema[field]
if (!tableSchemaField) {
throw new HTTPError(
`Field "${field}" is not valid for the requested table`,
400
)
}
if (viewSchema[field].readonly) {
if (
!(await features.isViewReadonlyColumnsEnabled()) &&
!(tableSchemaField as ViewUIFieldMetadata).readonly
) {
throw new HTTPError(`Readonly fields are not enabled`, 400)
}
if (isRequired(tableSchemaField.constraints)) {
throw new HTTPError(
`You can't make the required field "${field}" read only`,
400
)
}
if (!viewSchema[field].visible) {
throw new HTTPError(
`Field "${field}" must be visible if you want to make it readonly`,
400
)
}
}
}
}
export async function create(
tableId: string,
viewRequest: Omit<ViewV2, "id" | "version">
): Promise<ViewV2> {
await guardViewSchema(tableId, viewRequest.schema)
return pickApi(tableId).create(tableId, viewRequest)
}
export async function update(tableId: string, view: ViewV2): Promise<ViewV2> {
await guardViewSchema(tableId, view.schema)
return pickApi(tableId).update(tableId, view)
}
export function isV2(view: View | ViewV2): view is ViewV2 {
return (view as ViewV2).version === 2
}
export async function remove(viewId: string): Promise<ViewV2> {
const { tableId } = utils.extractViewInfoFromID(viewId)
return pickApi(tableId).remove(viewId)
}
export function allowedFields(view: View | ViewV2) {
return [
...Object.keys(view?.schema || {}).filter(key => {
if (!isV2(view)) {
return true
}
const fieldSchema = view.schema![key]
return fieldSchema.visible && !fieldSchema.readonly
}),
...dbCore.CONSTANT_EXTERNAL_ROW_COLS,
...dbCore.CONSTANT_INTERNAL_ROW_COLS,
]
}
export function enrichSchema(
view: ViewV2,
tableSchema: TableSchema
): ViewV2Enriched {
let schema = cloneDeep(tableSchema)
const anyViewOrder = Object.values(view.schema || {}).some(
ui => ui.order != null
)
for (const key of Object.keys(schema)) {
// if nothing specified in view, then it is not visible
const ui = view.schema?.[key] || { visible: false }
if (ui.visible === false) {
schema[key].visible = false
} else {
schema[key] = {
...schema[key],
...ui,
order: anyViewOrder ? ui?.order ?? undefined : schema[key].order,
}
}
}
return {
...view,
schema: schema,
}
}
export function syncSchema(
view: ViewV2,
schema: TableSchema,
renameColumn: RenameColumn | undefined
): ViewV2 {
if (renameColumn && view.schema) {
view.schema[renameColumn.updated] = view.schema[renameColumn.old]
delete view.schema[renameColumn.old]
}
if (view.schema) {
for (const fieldName of Object.keys(view.schema)) {
if (!schema[fieldName]) {
delete view.schema[fieldName]
}
}
for (const fieldName of Object.keys(schema)) {
if (!view.schema[fieldName]) {
view.schema[fieldName] = { visible: false }
}
}
}
return view
}