mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-22 23:49:15 +00:00
refactor: extract regex utilities
This commit is contained in:
committed by
Carlos Valente
parent
44508ce8b4
commit
5bc286145d
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { CustomField } from 'ontime-types';
|
import { CustomField } from 'ontime-types';
|
||||||
import { customFieldLabelToKey, isAlphanumericWithSpace } from 'ontime-utils';
|
import { checkRegex, customFieldLabelToKey } from 'ontime-utils';
|
||||||
|
|
||||||
import { maybeAxiosError } from '../../../../../common/api/utils';
|
import { maybeAxiosError } from '../../../../../common/api/utils';
|
||||||
import Button from '../../../../../common/components/buttons/Button';
|
import Button from '../../../../../common/components/buttons/Button';
|
||||||
@@ -117,7 +117,7 @@ export default function CustomFieldForm({
|
|||||||
onChange: () => setValue('key', customFieldLabelToKey(getValues('label')) ?? 'N/A'),
|
onChange: () => setValue('key', customFieldLabelToKey(getValues('label')) ?? 'N/A'),
|
||||||
validate: (value) => {
|
validate: (value) => {
|
||||||
if (value.trim().length === 0) return 'Required field';
|
if (value.trim().length === 0) return 'Required field';
|
||||||
if (!isAlphanumericWithSpace(value)) return 'Only alphanumeric characters and space are allowed';
|
if (!checkRegex.isAlphanumericWithSpace(value)) return 'Only alphanumeric characters and space are allowed';
|
||||||
if (!isEditMode) {
|
if (!isEditMode) {
|
||||||
if (isEditMode && Object.keys(data).includes(value)) return 'Custom fields must be unique';
|
if (isEditMode && Object.keys(data).includes(value)) return 'Custom fields must be unique';
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useFieldArray, useForm } from 'react-hook-form';
|
import { useFieldArray, useForm } from 'react-hook-form';
|
||||||
import { IoAdd, IoTrash } from 'react-icons/io5';
|
import { IoAdd, IoTrash } from 'react-icons/io5';
|
||||||
import { ImportMap, isAlphanumericWithSpace } from 'ontime-utils';
|
import { checkRegex, ImportMap } from 'ontime-utils';
|
||||||
|
|
||||||
import Button from '../../../../../../common/components/buttons/Button';
|
import Button from '../../../../../../common/components/buttons/Button';
|
||||||
import IconButton from '../../../../../../common/components/buttons/IconButton';
|
import IconButton from '../../../../../../common/components/buttons/IconButton';
|
||||||
@@ -200,7 +200,7 @@ export default function ImportMapForm({
|
|||||||
placeholder='Name of the field as shown in Ontime'
|
placeholder='Name of the field as shown in Ontime'
|
||||||
{...register(`custom.${index}.ontimeName`, {
|
{...register(`custom.${index}.ontimeName`, {
|
||||||
validate: (value) => {
|
validate: (value) => {
|
||||||
if (!isAlphanumericWithSpace(value))
|
if (!checkRegex.isAlphanumericWithSpace(value))
|
||||||
return 'Only alphanumeric characters and space are allowed';
|
return 'Only alphanumeric characters and space are allowed';
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DatabaseModel, CustomFields, CustomField } from 'ontime-types';
|
import { DatabaseModel, CustomFields, CustomField } from 'ontime-types';
|
||||||
import { isAlphanumericWithSpace, customFieldLabelToKey } from 'ontime-utils';
|
import { checkRegex, customFieldLabelToKey } from 'ontime-utils';
|
||||||
|
|
||||||
import type { ErrorEmitter } from '../../utils/parserUtils.js';
|
import type { ErrorEmitter } from '../../utils/parserUtils.js';
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ export function sanitiseCustomFields(data: object): CustomFields {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isAlphanumericWithSpace(field.label)) {
|
if (!checkRegex.isAlphanumericWithSpace(field.label)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { isAlphanumericWithSpace } from 'ontime-utils';
|
import { checkRegex } from 'ontime-utils';
|
||||||
|
|
||||||
import { body, param } from 'express-validator';
|
import { body, param } from 'express-validator';
|
||||||
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
|
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
|
||||||
@@ -9,7 +9,7 @@ export const validateCustomField = [
|
|||||||
.trim()
|
.trim()
|
||||||
.notEmpty()
|
.notEmpty()
|
||||||
.custom((value) => {
|
.custom((value) => {
|
||||||
return isAlphanumericWithSpace(value);
|
return checkRegex.isAlphanumericWithSpace(value);
|
||||||
}),
|
}),
|
||||||
body('type').isIn(['text', 'image']),
|
body('type').isIn(['text', 'image']),
|
||||||
body('colour').isString().trim(),
|
body('colour').isString().trim(),
|
||||||
@@ -24,7 +24,7 @@ export const validateEditCustomField = [
|
|||||||
.trim()
|
.trim()
|
||||||
.notEmpty()
|
.notEmpty()
|
||||||
.custom((value) => {
|
.custom((value) => {
|
||||||
return isAlphanumericWithSpace(value);
|
return checkRegex.isAlphanumericWithSpace(value);
|
||||||
}),
|
}),
|
||||||
body('type').isIn(['text', 'image']),
|
body('type').isIn(['text', 'image']),
|
||||||
body('colour').isString().trim(),
|
body('colour').isString().trim(),
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
validateTimerType,
|
validateTimerType,
|
||||||
validateEndAction,
|
validateEndAction,
|
||||||
customFieldLabelToKey,
|
customFieldLabelToKey,
|
||||||
isAlphanumericWithSpace,
|
checkRegex,
|
||||||
} from 'ontime-utils';
|
} from 'ontime-utils';
|
||||||
|
|
||||||
import { Merge } from 'ts-essentials';
|
import { Merge } from 'ts-essentials';
|
||||||
@@ -325,7 +325,7 @@ export function getCustomFieldData(
|
|||||||
|
|
||||||
for (const ontimeLabel in importMap.custom) {
|
for (const ontimeLabel in importMap.custom) {
|
||||||
// if the label is not valid, we skip the import
|
// if the label is not valid, we skip the import
|
||||||
if (!isAlphanumericWithSpace(ontimeLabel)) {
|
if (!checkRegex.isAlphanumericWithSpace(ontimeLabel)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export {
|
|||||||
removeTrailingZero,
|
removeTrailingZero,
|
||||||
} from './src/date-utils/timeFormatting.js';
|
} from './src/date-utils/timeFormatting.js';
|
||||||
export { parseUserTime } from './src/date-utils/parseUserTime.js';
|
export { parseUserTime } from './src/date-utils/parseUserTime.js';
|
||||||
export { isAlphanumeric, isAlphanumericWithSpace } from './src/regex-utils/isAlphanumeric.js';
|
export { checkRegex, regex } from './src/regex-utils/checkRegex.js';
|
||||||
export { isColourHex } from './src/regex-utils/isColourHex.js';
|
export { isColourHex } from './src/regex-utils/isColourHex.js';
|
||||||
export { splitWhitespace } from './src/regex-utils/splitWhitespace.js';
|
export { splitWhitespace } from './src/regex-utils/splitWhitespace.js';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { checkRegex } from './checkRegex';
|
||||||
|
|
||||||
|
describe('checkRegex()', () => {
|
||||||
|
it('isOnlyNumbers', () => {
|
||||||
|
expect(checkRegex.isOnlyNumbers('12345')).toBe(true);
|
||||||
|
expect(checkRegex.isOnlyNumbers('123a')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isIPAddress', () => {
|
||||||
|
expect(checkRegex.isIPAddress('192.168.0.1')).toBe(true);
|
||||||
|
expect(checkRegex.isIPAddress('999.999.999.999')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('startsWithHttp', () => {
|
||||||
|
expect(checkRegex.startsWithHttp('http://example.com')).toBe(true);
|
||||||
|
expect(checkRegex.startsWithHttp('https://example.com')).toBe(true);
|
||||||
|
expect(checkRegex.startsWithHttp('ftp://example.com')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('startsWithSlash', () => {
|
||||||
|
expect(checkRegex.startsWithSlash('/path')).toBe(true);
|
||||||
|
expect(checkRegex.startsWithSlash('path')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isAlphanumericWithSpace', () => {
|
||||||
|
expect(checkRegex.isAlphanumericWithSpace('abc 123')).toBe(true);
|
||||||
|
expect(checkRegex.isAlphanumericWithSpace('abc-123')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isASCII', () => {
|
||||||
|
expect(checkRegex.isASCII('Hello, World!')).toBe(true);
|
||||||
|
expect(checkRegex.isASCII('こんにちは')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isASCIIorEmpty', () => {
|
||||||
|
expect(checkRegex.isASCIIorEmpty('')).toBe(true);
|
||||||
|
expect(checkRegex.isASCIIorEmpty('Hello')).toBe(true);
|
||||||
|
expect(checkRegex.isASCIIorEmpty('こんにちは')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isNotEmpty', () => {
|
||||||
|
expect(checkRegex.isNotEmpty('Hello')).toBe(true);
|
||||||
|
expect(checkRegex.isNotEmpty('')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
export const regex = {
|
||||||
|
isOnlyNumbers: /^\d+$/,
|
||||||
|
isIPAddress: /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/,
|
||||||
|
startsWithHttp: /^https?:\/\//,
|
||||||
|
startsWithSlash: /^\//,
|
||||||
|
isAlphanumeric: /^[a-z0-9]+$/i,
|
||||||
|
isAlphanumericWithSpace: /^[a-z0-9_ ]+$/i,
|
||||||
|
isASCII: /^[ -~]+$/, // https://catonmat.net/my-favorite-regex
|
||||||
|
isASCIIorEmpty: /^$|^[ -~]+$/, // https://catonmat.net/my-favorite-regex
|
||||||
|
isNotEmpty: /\S/,
|
||||||
|
isUrlSafe: /^[a-zA-Z0-9_-]*$/, // https://stackoverflow.com/questions/24419067/validate-a-string-to-be-url-safe-using-regex
|
||||||
|
};
|
||||||
|
|
||||||
|
export const checkRegex = {
|
||||||
|
isOnlyNumbers: (text: string): boolean => regex.isOnlyNumbers.test(text),
|
||||||
|
isIPAddress: (text: string): boolean => regex.isIPAddress.test(text),
|
||||||
|
startsWithHttp: (text: string): boolean => regex.startsWithHttp.test(text),
|
||||||
|
startsWithSlash: (text: string): boolean => regex.startsWithSlash.test(text),
|
||||||
|
isAlphanumeric: (text: string): boolean => regex.isAlphanumeric.test(text),
|
||||||
|
isAlphanumericWithSpace: (text: string): boolean => regex.isAlphanumericWithSpace.test(text),
|
||||||
|
isASCII: (text: string): boolean => regex.isASCII.test(text),
|
||||||
|
isASCIIorEmpty: (text: string): boolean => regex.isASCIIorEmpty.test(text),
|
||||||
|
isNotEmpty: (text: string): boolean => regex.isNotEmpty.test(text),
|
||||||
|
isUrlSafe: (text: string): boolean => regex.isUrlSafe.test(text),
|
||||||
|
};
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { isAlphanumeric } from './isAlphanumeric';
|
|
||||||
|
|
||||||
describe('test isAlphanumeric() function', () => {
|
|
||||||
it('it OK strings', () => {
|
|
||||||
const ts = ['abcdefghijklmnopqrstuvwxyz', '0123456798', '123asd'];
|
|
||||||
for (const s of ts) {
|
|
||||||
expect(isAlphanumeric(s)).toBe(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('it bad strings', () => {
|
|
||||||
const ts = ['!abcd1234', 'åøæ', '*'];
|
|
||||||
for (const s of ts) {
|
|
||||||
expect(isAlphanumeric(s)).toBe(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
/**
|
|
||||||
* @description Validates a alphanumeric string
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
export const isAlphanumeric = (text: string): boolean => {
|
|
||||||
const regex = /^[a-z0-9]+$/i;
|
|
||||||
return regex.test(text);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Validates a alphanumeric string allow space
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
export const isAlphanumericWithSpace = (text: string): boolean => {
|
|
||||||
const regex = /^[a-z0-9_ ]+$/i;
|
|
||||||
return regex.test(text);
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user