diff --git a/adminforth/index.ts b/adminforth/index.ts index 05ede79c7..7acf79efa 100644 --- a/adminforth/index.ts +++ b/adminforth/index.ts @@ -230,6 +230,55 @@ class AdminForth implements IAdminForth { }); } + validateRecordValues(resource: AdminForthResource, record: any): any { + // check if record with validation is valid + for (const column of resource.columns.filter((col) => col.name in record && col.validation)) { + let error = null; + if (column.isArray?.enabled) { + error = record[column.name].reduce((err, item) => { + return err || AdminForth.Utils.applyRegexValidation(item, column.validation); + }, null); + } else { + error = AdminForth.Utils.applyRegexValidation(record[column.name], column.validation); + } + if (error) { + return error; + } + } + + // check if record with minValue or maxValue is within limits + for (const column of resource.columns.filter((col) => col.name in record + && ['integer', 'decimal', 'float'].includes(col.isArray?.enabled ? col.isArray.itemType : col.type) + && (col.minValue !== undefined || col.maxValue !== undefined))) { + if (column.isArray?.enabled) { + const error = record[column.name].reduce((err, item) => { + if (err) return err; + + if (column.minValue !== undefined && item < column.minValue) { + return `Value in "${column.name}" must be greater than ${column.minValue}`; + } + if (column.maxValue !== undefined && item > column.maxValue) { + return `Value in "${column.name}" must be less than ${column.maxValue}`; + } + + return null; + }, null); + if (error) { + return error; + } + } else { + if (column.minValue !== undefined && record[column.name] < column.minValue) { + return `Value in "${column.name}" must be greater than ${column.minValue}`; + } + if (column.maxValue !== undefined && record[column.name] > column.maxValue) { + return `Value in "${column.name}" must be less than ${column.maxValue}`; + } + } + } + + return null; + } + async discoverDatabases() { this.statuses.dbDiscover = 'running'; @@ -350,24 +399,9 @@ class AdminForth implements IAdminForth { { resource: AdminForthResource, record: any, adminUser: AdminUser, extra?: HttpExtra } ): Promise<{ error?: string, createdRecord?: any }> { - // check if record with validation is valid - for (const column of resource.columns.filter((col) => col.name in record && col.validation)) { - const error = AdminForth.Utils.applyRegexValidation(record[column.name], column.validation); - if (error) { - return { error }; - } - } - - // check if record with minValue or maxValue is within limits - for (const column of resource.columns.filter((col) => col.name in record - && ['integer', 'decimal', 'float'].includes(col.type) - && (col.minValue !== undefined || col.maxValue !== undefined))) { - if (column.minValue !== undefined && record[column.name] < column.minValue) { - return { error: `Value in "${column.name}" must be greater than ${column.minValue}` }; - } - if (column.maxValue !== undefined && record[column.name] > column.maxValue) { - return { error: `Value in "${column.name}" must be less than ${column.maxValue}` }; - } + const err = this.validateRecordValues(resource, record); + if (err) { + return { error: err }; } // execute hook if needed @@ -435,24 +469,9 @@ class AdminForth implements IAdminForth { { resource: AdminForthResource, recordId: any, record: any, oldRecord: any, adminUser: AdminUser, extra?: HttpExtra } ): Promise<{ error?: string }> { - // check if record with validation is valid - for (const column of resource.columns.filter((col) => col.name in record && col.validation)) { - const error = AdminForth.Utils.applyRegexValidation(record[column.name], column.validation); - if (error) { - return { error }; - } - } - - // check if record with minValue or maxValue is within limits - for (const column of resource.columns.filter((col) => col.name in record - && ['integer', 'decimal', 'float'].includes(col.type) - && (col.minValue !== undefined || col.maxValue !== undefined))) { - if (column.minValue !== undefined && record[column.name] < column.minValue) { - return { error: `Value in "${column.name}" must be greater than ${column.minValue}` }; - } - if (column.maxValue !== undefined && record[column.name] > column.maxValue) { - return { error: `Value in "${column.name}" must be less than ${column.maxValue}` }; - } + const err = this.validateRecordValues(resource, record); + if (err) { + return { error: err }; } // remove editReadonly columns from record diff --git a/adminforth/modules/configValidator.ts b/adminforth/modules/configValidator.ts index e13a2392c..e35ef6077 100644 --- a/adminforth/modules/configValidator.ts +++ b/adminforth/modules/configValidator.ts @@ -22,6 +22,7 @@ import { AllowedActionsEnum, AdminForthComponentDeclaration , AdminForthResourcePages, + AdminForthDataTypes, } from "../types/Common.js"; import AdminForth from "adminforth"; import { AdminForthConfigMenuItem } from "adminforth"; @@ -400,6 +401,35 @@ export default class ConfigValidator implements IConfigValidator { col.editingNote = typeof inCol.editingNote === 'string' ? { create: inCol.editingNote, edit: inCol.editingNote } : inCol.editingNote; + if (col.isArray !== undefined) { + if (typeof col.isArray !== 'object') { + errors.push(`Resource "${res.resourceId}" column "${col.name}" isArray must be an object`); + } else if (col.isArray.enabled) { + if (col.primaryKey) { + errors.push(`Resource "${res.resourceId}" column "${col.name}" isArray cannot be used for a primary key columns`); + } + if (col.masked) { + errors.push(`Resource "${res.resourceId}" column "${col.name}" isArray cannot be used for a masked column`); + } + if (col.foreignResource) { + errors.push(`Resource "${res.resourceId}" column "${col.name}" isArray cannot be used for a foreignResource column`); + } + + if (!col.type || col.type !== AdminForthDataTypes.JSON) { + errors.push(`Resource "${res.resourceId}" column "${col.name}" isArray can be used only with column type JSON`); + } + + if (col.isArray.itemType === undefined) { + errors.push(`Resource "${res.resourceId}" column "${col.name}" isArray must have itemType`); + } + if (col.isArray.itemType === AdminForthDataTypes.JSON) { + errors.push(`Resource "${res.resourceId}" column "${col.name}" isArray itemType cannot be JSON`); + } + if (col.isArray.itemType === AdminForthDataTypes.RICHTEXT) { + errors.push(`Resource "${res.resourceId}" column "${col.name}" isArray itemType cannot be RICHTEXT`); + } + } + } if (col.foreignResource) { if (!col.foreignResource.resourceId) { diff --git a/adminforth/spa/src/components/ColumnValueInput.vue b/adminforth/spa/src/components/ColumnValueInput.vue new file mode 100644 index 000000000..98f458b07 --- /dev/null +++ b/adminforth/spa/src/components/ColumnValueInput.vue @@ -0,0 +1,175 @@ + +
{{ columnError(column) }}
{{ column.editingNote[mode] }}
@@ -157,12 +96,10 @@