diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index f6a4f29a06..d39a6a92a7 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -34,7 +34,12 @@ import { getBindings } from "components/backend/DataTable/formula" import JSONSchemaModal from "./JSONSchemaModal.svelte" import { ValidColumnNameRegex } from "@budibase/shared-core" - import { FieldType, FieldSubtype, SourceName } from "@budibase/types" + import { + FieldType, + FieldSubtype, + SourceName, + FieldTypeSubtypes, + } from "@budibase/types" import RelationshipSelector from "components/common/RelationshipSelector.svelte" import { RowUtils } from "@budibase/frontend-core" import ServerBindingPanel from "components/common/bindings/ServerBindingPanel.svelte" @@ -191,8 +196,10 @@ // don't make field IDs for auto types if (type === AUTO_TYPE || autocolumn) { return type.toUpperCase() - } else { + } else if (type === FieldType.BB_REFERENCE) { return `${type}${subtype || ""}`.toUpperCase() + } else { + return type.toUpperCase() } } @@ -705,17 +712,14 @@ /> {:else if editableColumn.type === FieldType.ATTACHMENT} { if (!e.detail) { - editableColumn.constraints ??= { length: {} } - editableColumn.constraints.length ??= {} - editableColumn.constraints.length.maximum = 1 - editableColumn.constraints.length.message = - "cannot contain multiple files" + editableColumn.subtype = FieldTypeSubtypes.ATTACHMENT.SINGLE } else { - delete editableColumn.constraints?.length?.maximum - delete editableColumn.constraints?.length?.message + delete editableColumn.subtype } }} thin diff --git a/packages/server/src/api/controllers/table/utils.ts b/packages/server/src/api/controllers/table/utils.ts index 9e1bb59e9d..0c9933a4cf 100644 --- a/packages/server/src/api/controllers/table/utils.ts +++ b/packages/server/src/api/controllers/table/utils.ts @@ -30,6 +30,8 @@ import { View, RelationshipFieldMetadata, FieldType, + FieldTypeSubtypes, + AttachmentFieldMetadata, } from "@budibase/types" export async function clearColumns(table: Table, columnNames: string[]) { @@ -88,6 +90,27 @@ export async function checkForColumnUpdates( // Update views await checkForViewUpdates(updatedTable, deletedColumns, columnRename) } + + const changedAttachmentSubtypeColumns = Object.values( + updatedTable.schema + ).filter( + (column): column is AttachmentFieldMetadata => + column.type === FieldType.ATTACHMENT && + column.subtype !== oldTable?.schema[column.name]?.subtype + ) + for (const attachmentColumn of changedAttachmentSubtypeColumns) { + if (attachmentColumn.subtype === FieldTypeSubtypes.ATTACHMENT.SINGLE) { + attachmentColumn.constraints ??= { length: {} } + attachmentColumn.constraints.length ??= {} + attachmentColumn.constraints.length.maximum = 1 + attachmentColumn.constraints.length.message = + "cannot contain multiple files" + } else { + delete attachmentColumn.constraints?.length?.maximum + delete attachmentColumn.constraints?.length?.message + } + } + return { rows: updatedRows, table: updatedTable } } diff --git a/packages/server/src/integrations/base/sqlTable.ts b/packages/server/src/integrations/base/sqlTable.ts index 06c2184549..0feecefb89 100644 --- a/packages/server/src/integrations/base/sqlTable.ts +++ b/packages/server/src/integrations/base/sqlTable.ts @@ -60,7 +60,7 @@ function generateSchema( schema.text(key) break case FieldType.BB_REFERENCE: { - const subtype = column.subtype as FieldSubtype + const subtype = column.subtype switch (subtype) { case FieldSubtype.USER: schema.text(key) diff --git a/packages/server/src/sdk/app/rows/search/utils.ts b/packages/server/src/sdk/app/rows/search/utils.ts index d300fdbef0..086599665b 100644 --- a/packages/server/src/sdk/app/rows/search/utils.ts +++ b/packages/server/src/sdk/app/rows/search/utils.ts @@ -67,7 +67,7 @@ export function searchInputMapping(table: Table, options: SearchParams) { for (let [key, column] of Object.entries(table.schema)) { switch (column.type) { case FieldType.BB_REFERENCE: { - const subtype = column.subtype as FieldSubtype + const subtype = column.subtype switch (subtype) { case FieldSubtype.USER: case FieldSubtype.USERS: diff --git a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts index 31f1f5e575..a5fbfa981d 100644 --- a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts +++ b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts @@ -7,7 +7,7 @@ const ROW_PREFIX = DocumentType.ROW + SEPARATOR export async function processInputBBReferences( value: string | string[] | { _id: string } | { _id: string }[], - subtype: FieldSubtype + subtype: FieldSubtype.USER | FieldSubtype.USERS ): Promise { let referenceIds: string[] = [] @@ -61,7 +61,7 @@ export async function processInputBBReferences( export async function processOutputBBReferences( value: string | string[], - subtype: FieldSubtype + subtype: FieldSubtype.USER | FieldSubtype.USERS ) { if (value === null || value === undefined) { // Already processed or nothing to process diff --git a/packages/server/src/utilities/rowProcessor/index.ts b/packages/server/src/utilities/rowProcessor/index.ts index d956a94d0b..0015680e77 100644 --- a/packages/server/src/utilities/rowProcessor/index.ts +++ b/packages/server/src/utilities/rowProcessor/index.ts @@ -6,7 +6,6 @@ import { TYPE_TRANSFORM_MAP } from "./map" import { FieldType, AutoFieldSubType, - FieldSubtype, Row, RowAttachment, Table, @@ -159,10 +158,7 @@ export async function inputProcessing( } if (field.type === FieldType.BB_REFERENCE && value) { - clonedRow[key] = await processInputBBReferences( - value, - field.subtype as FieldSubtype - ) + clonedRow[key] = await processInputBBReferences(value, field.subtype) } } @@ -238,7 +234,7 @@ export async function outputProcessing( for (let row of enriched) { row[property] = await processOutputBBReferences( row[property], - column.subtype as FieldSubtype + column.subtype ) } } diff --git a/packages/server/src/utilities/schema.ts b/packages/server/src/utilities/schema.ts index 5c466ec510..85dfdd3506 100644 --- a/packages/server/src/utilities/schema.ts +++ b/packages/server/src/utilities/schema.ts @@ -1,26 +1,14 @@ -import { FieldType, FieldSubtype } from "@budibase/types" +import { + FieldType, + FieldSubtype, + TableSchema, + FieldSchema, + Row, +} from "@budibase/types" import { ValidColumnNameRegex, utils } from "@budibase/shared-core" import { db } from "@budibase/backend-core" import { parseCsvExport } from "../api/controllers/view/exporters" -interface SchemaColumn { - readonly name: string - readonly type: FieldType - readonly subtype: FieldSubtype - readonly autocolumn?: boolean - readonly constraints?: { - presence: boolean - } -} - -interface Schema { - readonly [index: string]: SchemaColumn -} - -interface Row { - [index: string]: any -} - type Rows = Array interface SchemaValidation { @@ -34,12 +22,10 @@ interface ValidationResults { errors: Record } -export function isSchema(schema: any): schema is Schema { +export function isSchema(schema: any): schema is TableSchema { return ( typeof schema === "object" && - Object.values(schema).every(rawColumn => { - const column = rawColumn as SchemaColumn - + Object.values(schema).every(column => { return ( column !== null && typeof column === "object" && @@ -54,7 +40,7 @@ export function isRows(rows: any): rows is Rows { return Array.isArray(rows) && rows.every(row => typeof row === "object") } -export function validate(rows: Rows, schema: Schema): ValidationResults { +export function validate(rows: Rows, schema: TableSchema): ValidationResults { const results: ValidationResults = { schemaValidation: {}, allValid: false, @@ -64,9 +50,11 @@ export function validate(rows: Rows, schema: Schema): ValidationResults { rows.forEach(row => { Object.entries(row).forEach(([columnName, columnData]) => { - const columnType = schema[columnName]?.type - const columnSubtype = schema[columnName]?.subtype - const isAutoColumn = schema[columnName]?.autocolumn + const { + type: columnType, + subtype: columnSubtype, + autocolumn: isAutoColumn, + } = schema[columnName] // If the column had an invalid value we don't want to override it if (results.schemaValidation[columnName] === false) { @@ -123,7 +111,7 @@ export function validate(rows: Rows, schema: Schema): ValidationResults { return results } -export function parse(rows: Rows, schema: Schema): Rows { +export function parse(rows: Rows, schema: TableSchema): Rows { return rows.map(row => { const parsedRow: Row = {} @@ -133,9 +121,7 @@ export function parse(rows: Rows, schema: Schema): Rows { return } - const columnType = schema[columnName].type - const columnSubtype = schema[columnName].subtype - + const { type: columnType, subtype: columnSubtype } = schema[columnName] if (columnType === FieldType.NUMBER) { // If provided must be a valid number parsedRow[columnName] = columnData ? Number(columnData) : columnData @@ -172,7 +158,7 @@ export function parse(rows: Rows, schema: Schema): Rows { function isValidBBReference( columnData: any, - columnSubtype: FieldSubtype + columnSubtype: FieldSubtype.USER | FieldSubtype.USERS ): boolean { switch (columnSubtype) { case FieldSubtype.USER: diff --git a/packages/types/src/documents/app/row.ts b/packages/types/src/documents/app/row.ts index b37412398d..aa8f50d4a8 100644 --- a/packages/types/src/documents/app/row.ts +++ b/packages/types/src/documents/app/row.ts @@ -38,11 +38,16 @@ export interface Row extends Document { export enum FieldSubtype { USER = "user", USERS = "users", + SINGLE = "single", } +// The 'as' are required for typescript not to type the outputs as generic FieldSubtype export const FieldTypeSubtypes = { BB_REFERENCE: { - USER: FieldSubtype.USER, - USERS: FieldSubtype.USERS, + USER: FieldSubtype.USER as FieldSubtype.USER, + USERS: FieldSubtype.USERS as FieldSubtype.USERS, + }, + ATTACHMENT: { + SINGLE: FieldSubtype.SINGLE as FieldSubtype.SINGLE, }, } diff --git a/packages/types/src/documents/app/table/schema.ts b/packages/types/src/documents/app/table/schema.ts index 17abf747b2..45e39268ac 100644 --- a/packages/types/src/documents/app/table/schema.ts +++ b/packages/types/src/documents/app/table/schema.ts @@ -112,6 +112,12 @@ export interface BBReferenceFieldMetadata relationshipType?: RelationshipType } +export interface AttachmentFieldMetadata + extends Omit { + type: FieldType.ATTACHMENT + subtype?: FieldSubtype.SINGLE +} + export interface FieldConstraints { type?: string email?: boolean @@ -119,6 +125,7 @@ export interface FieldConstraints { length?: { minimum?: string | number | null maximum?: string | number | null + message?: string } numericality?: { greaterThanOrEqualTo: string | null @@ -156,6 +163,8 @@ interface OtherFieldMetadata extends BaseFieldSchema { | FieldType.FORMULA | FieldType.NUMBER | FieldType.LONGFORM + | FieldType.BB_REFERENCE + | FieldType.ATTACHMENT > } @@ -169,6 +178,7 @@ export type FieldSchema = | LongFormFieldMetadata | BBReferenceFieldMetadata | JsonFieldMetadata + | AttachmentFieldMetadata export interface TableSchema { [key: string]: FieldSchema @@ -203,3 +213,9 @@ export function isBBReferenceField( ): field is BBReferenceFieldMetadata { return field.type === FieldType.BB_REFERENCE } + +export function isAttachmentField( + field: FieldSchema +): field is AttachmentFieldMetadata { + return field.type === FieldType.ATTACHMENT +}