mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Add field-level validation (#12363)
* Add field validation column * Add frontend config for validation * Make it work * Add regex to filter configuration * Fix const/let * Add custom validation message support * Add custom validation message tooltip inline * Fix custom names in validation errors up top * Fix type error * Nog eentje om het af te leren * resolve unused import warnings
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.schema.alterTable('directus_fields', (table) => {
|
||||
table.json('validation');
|
||||
table.text('validation_message');
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.alterTable('directus_fields', (table) => {
|
||||
table.dropColumn('validation');
|
||||
table.dropColumn('validation_message');
|
||||
});
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
import { Knex } from 'knex';
|
||||
import { cloneDeep, merge, uniq, uniqWith, flatten, isNil, isArray } from 'lodash';
|
||||
import getDatabase from '../database';
|
||||
import { ForbiddenException } from '../exceptions';
|
||||
import { FailedValidationException } from '@directus/shared/exceptions';
|
||||
import { validatePayload } from '@directus/shared/utils';
|
||||
import { AbstractServiceOptions, AST, FieldNode, Item, NestedCollectionNode, PrimaryKey } from '../types';
|
||||
import {
|
||||
Query,
|
||||
Accountability,
|
||||
Aggregate,
|
||||
Filter,
|
||||
Permission,
|
||||
PermissionsAction,
|
||||
Accountability,
|
||||
Query,
|
||||
SchemaOverview,
|
||||
Filter,
|
||||
} from '@directus/shared/types';
|
||||
import { validatePayload } from '@directus/shared/utils';
|
||||
import { Knex } from 'knex';
|
||||
import { cloneDeep, flatten, isArray, isNil, merge, uniq, uniqWith } from 'lodash';
|
||||
import getDatabase from '../database';
|
||||
import { ForbiddenException } from '../exceptions';
|
||||
import { AbstractServiceOptions, AST, FieldNode, Item, NestedCollectionNode, PrimaryKey } from '../types';
|
||||
import { stripFunction } from '../utils/strip-function';
|
||||
import { ItemsService } from './items';
|
||||
import { PayloadService } from './payload';
|
||||
import { stripFunction } from '../utils/strip-function';
|
||||
|
||||
export class AuthorizationService {
|
||||
knex: Knex;
|
||||
@@ -302,9 +302,15 @@ export class AuthorizationService {
|
||||
|
||||
const payloadWithPresets = merge({}, preset, payload);
|
||||
|
||||
const fieldValidationRules = Object.values(this.schema.collections[collection].fields)
|
||||
.map((field) => field.validation)
|
||||
.filter((v) => v) as Filter[];
|
||||
|
||||
const hasValidationRules =
|
||||
isNil(permission.validation) === false && Object.keys(permission.validation ?? {}).length > 0;
|
||||
|
||||
const hasFieldValidationRules = fieldValidationRules && fieldValidationRules.length > 0;
|
||||
|
||||
const requiredColumns: SchemaOverview['collections'][string]['fields'][string][] = [];
|
||||
|
||||
for (const field of Object.values(this.schema.collections[collection].fields)) {
|
||||
@@ -321,7 +327,7 @@ export class AuthorizationService {
|
||||
}
|
||||
}
|
||||
|
||||
if (hasValidationRules === false && requiredColumns.length === 0) {
|
||||
if (hasValidationRules === false && hasFieldValidationRules === false && requiredColumns.length === 0) {
|
||||
return payloadWithPresets;
|
||||
}
|
||||
|
||||
@@ -345,6 +351,14 @@ export class AuthorizationService {
|
||||
}
|
||||
}
|
||||
|
||||
if (hasFieldValidationRules) {
|
||||
if (permission.validation) {
|
||||
permission.validation = { _and: [permission.validation, ...fieldValidationRules] };
|
||||
} else {
|
||||
permission.validation = { _and: fieldValidationRules };
|
||||
}
|
||||
}
|
||||
|
||||
const validationErrors: FailedValidationException[] = [];
|
||||
|
||||
validationErrors.push(
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import SchemaInspector from '@directus/schema';
|
||||
import { Accountability, SchemaOverview, Filter } from '@directus/shared/types';
|
||||
import { toArray } from '@directus/shared/utils';
|
||||
import { Knex } from 'knex';
|
||||
import { mapValues } from 'lodash';
|
||||
import { getCache, setSystemCache } from '../cache';
|
||||
import getDatabase from '../database';
|
||||
import { systemCollectionRows } from '../database/system-data/collections';
|
||||
import { systemFieldRows } from '../database/system-data/fields';
|
||||
import env from '../env';
|
||||
import logger from '../logger';
|
||||
import { RelationsService } from '../services';
|
||||
import { Accountability, SchemaOverview } from '@directus/shared/types';
|
||||
import { toArray } from '@directus/shared/utils';
|
||||
import getDefaultValue from './get-default-value';
|
||||
import getLocalType from './get-local-type';
|
||||
import getDatabase from '../database';
|
||||
import { getCache, setSystemCache } from '../cache';
|
||||
import env from '../env';
|
||||
|
||||
export async function getSchema(options?: {
|
||||
accountability?: Accountability;
|
||||
@@ -106,6 +106,7 @@ async function getDatabaseSchema(
|
||||
scale: column.numeric_scale || null,
|
||||
special: [],
|
||||
note: null,
|
||||
validation: null,
|
||||
alias: false,
|
||||
};
|
||||
}),
|
||||
@@ -114,13 +115,16 @@ async function getDatabaseSchema(
|
||||
|
||||
const fields = [
|
||||
...(await database
|
||||
.select<{ id: number; collection: string; field: string; special: string; note: string | null }[]>(
|
||||
'id',
|
||||
'collection',
|
||||
'field',
|
||||
'special',
|
||||
'note'
|
||||
)
|
||||
.select<
|
||||
{
|
||||
id: number;
|
||||
collection: string;
|
||||
field: string;
|
||||
special: string;
|
||||
note: string | null;
|
||||
validation: string | Record<string, any> | null;
|
||||
}[]
|
||||
>('id', 'collection', 'field', 'special', 'note', 'validation')
|
||||
.from('directus_fields')),
|
||||
...systemFieldRows,
|
||||
].filter((field) => (field.special ? toArray(field.special) : []).includes('no-data') === false);
|
||||
@@ -132,6 +136,9 @@ async function getDatabaseSchema(
|
||||
const column = schemaOverview[field.collection].columns[field.field];
|
||||
const special = field.special ? toArray(field.special) : [];
|
||||
const type = (existing && getLocalType(column, { special })) || 'alias';
|
||||
let validation = field.validation ?? null;
|
||||
|
||||
if (validation && typeof validation === 'string') validation = JSON.parse(validation);
|
||||
|
||||
result.collections[field.collection].fields[field.field] = {
|
||||
field: field.field,
|
||||
@@ -145,6 +152,7 @@ async function getDatabaseSchema(
|
||||
special: special,
|
||||
note: field.note,
|
||||
alias: existing?.alias ?? true,
|
||||
validation: (field.validation as Filter) ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user