Files
directus/api/src/utils/generate-joi.ts
Rijk van Zanten f64a5bef7e Add new advanced filters experience (#8570)
* Remove advanced filter sidebar detail

So long, and thanks for all the fish.

* Remove filter conversion logic

* Start replacing/removing old skool filters

* Add inline mode for usages in search bar

* Make filter work in header bar

* Emit empty string as null in filter

* Move shared filter types to shared

* Upgrade use-items

* Fix manual sort on tabular

* Cleanup styling in search bar usage

* Tweak styling

* Fix filtering issues

* Update cards

* Remove activeFilterCount from tabular

* Update maps to work with new filters

* Update calendar to new filter/sort structure

* Fix activity module nav/search

* Fix no-results message

* Update file library filtering

* Finalize user search

* Allow filtering in drawer-collection

* Handle cancelled responses semi-gracefully

* Add loading start state timeout

* Replace sort type in api

* Last commit before redoing a bunch

* Finish new visual style

* Remove unused rounded prop from v-menu

* Tweak sizing

* Enough size tweaking for now

* Count all filter operators instead of top

* Fix archive casting

* Fix api build

* Add merge filters util

* Split filter in user vs system

* Fix export sidebar detail

* Show field label on permissions configuration

* Add migration for filter/sort

* Use filters in insights
2021-10-07 18:06:03 -04:00

171 lines
3.9 KiB
TypeScript

import BaseJoi, { AnySchema } from 'joi';
import { escapeRegExp } from 'lodash';
import { Filter } from '@directus/shared/types';
const Joi: typeof BaseJoi = BaseJoi.extend({
type: 'string',
base: BaseJoi.string(),
messages: {
'string.contains': '{{#label}} must contain [{{#substring}}]',
'string.ncontains': "{{#label}} can't contain [{{#substring}}]",
},
rules: {
contains: {
args: [
{
name: 'substring',
ref: true,
assert: (val) => typeof val === 'string',
message: 'must be a string',
},
],
method(substring) {
return this.$_addRule({ name: 'contains', args: { substring } });
},
validate(value, helpers, { substring }) {
if (value.includes(substring) === false) {
return helpers.error('string.contains', { substring });
}
return value;
},
},
ncontains: {
args: [
{
name: 'substring',
ref: true,
assert: (val) => typeof val === 'string',
message: 'must be a string',
},
],
method(substring) {
return this.$_addRule({ name: 'ncontains', args: { substring } });
},
validate(value, helpers, { substring }) {
if (value.includes(substring) === true) {
return helpers.error('string.ncontains', { substring });
}
return value;
},
},
},
});
export default function generateJoi(filter: Filter | null): AnySchema {
filter = filter || {};
if (Object.keys(filter).length === 0) return Joi.any();
let schema: any;
for (const [key, value] of Object.entries(filter)) {
if (key.startsWith('_') === false) {
if (!schema) schema = {};
const operator = Object.keys(value)[0];
const val = Object.values(value)[0];
schema[key] = getJoi(operator, val);
}
}
return Joi.object(schema).unknown();
}
function getJoi(operator: string, value: any) {
if (operator === '_eq') {
return Joi.any().equal(value);
}
if (operator === '_neq') {
return Joi.any().not(value);
}
if (operator === '_contains') {
// @ts-ignore
return Joi.string().contains(value);
}
if (operator === '_ncontains') {
// @ts-ignore
return Joi.string().ncontains(value);
}
if (operator === '_starts_with') {
return Joi.string().pattern(new RegExp(`^${escapeRegExp(value)}.*`), { name: 'starts_with' });
}
if (operator === '_nstarts_with') {
return Joi.string().pattern(new RegExp(`^${escapeRegExp(value)}.*`), { name: 'starts_with', invert: true });
}
if (operator === '_ends_with') {
return Joi.string().pattern(new RegExp(`.*${escapeRegExp(value)}$`), { name: 'ends_with' });
}
if (operator === '_nends_with') {
return Joi.string().pattern(new RegExp(`.*${escapeRegExp(value)}$`), { name: 'ends_with', invert: true });
}
if (operator === '_in') {
return Joi.any().equal(...(value as (string | number)[]));
}
if (operator === '_nin') {
return Joi.any().not(...(value as (string | number)[]));
}
if (operator === '_gt') {
return Joi.number().greater(Number(value));
}
if (operator === '_gte') {
return Joi.number().min(Number(value));
}
if (operator === '_lt') {
return Joi.number().less(Number(value));
}
if (operator === '_lte') {
return Joi.number().max(Number(value));
}
if (operator === '_null') {
return Joi.any().valid(null);
}
if (operator === '_nnull') {
return Joi.any().invalid(null);
}
if (operator === '_empty') {
return Joi.any().valid('');
}
if (operator === '_nempty') {
return Joi.any().invalid('');
}
if (operator === '_between') {
const values = value as number[];
return Joi.number().greater(values[0]).less(values[1]);
}
if (operator === '_nbetween') {
const values = value as number[];
return Joi.number().less(values[0]).greater(values[1]);
}
if (operator === '_submitted') {
return Joi.required();
}
if (operator === '_regex') {
const wrapped = value.startsWith('/') && value.endsWith('/');
return Joi.string().regex(new RegExp(wrapped ? value.slice(1, -1) : value));
}
}