-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.ts
122 lines (105 loc) · 3.51 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import { validate, validateSync, ValidatorOptions } from 'class-validator';
import cleanDeep from 'clean-deep';
import Err from 'err';
import HTTP_STATUS from 'http-status';
import { isObject, omit, isDate, isFunction, isNil, isNumber, isString } from 'lodash';
import { DateTime } from 'luxon';
export const stringNotDate = (input: any | string): input is string => {
return isString(input);
};
interface FirestoreTimestamp {
toDate(): Date;
}
export const firestoreTimestamp = (input: any | FirestoreTimestamp): input is FirestoreTimestamp => {
return isFunction((input as FirestoreTimestamp).toDate);
};
export const toDate = (input: Date | string | FirestoreTimestamp | DateTime | number): Date => {
if (isNil(input)) return new Date('');
if (isNumber(input))
return DateTime.fromMillis(input as number)
.toUTC()
.toJSDate();
if (isDate(input)) return DateTime.fromJSDate(input).toUTC().toJSDate();
if (firestoreTimestamp(input)) return DateTime.fromJSDate(input.toDate()).toJSDate();
if (DateTime.isDateTime(input)) return input.toJSDate();
return stringNotDate(input) ? DateTime.fromISO(input).toUTC().toJSDate() : input;
};
/**
* Strip unsupported characters
*
* @param {string} input
* @returns {string}
*/
export const cleanString = (input: string): string => {
return input.replace(/\s+/g, ' ').trim();
};
const getErrorStrings = (errors, base = ''): string[] => {
return errors.reduce((result, { constraints = {}, children = [], property }) => {
result = [...result, ...Object.values(constraints).map((constraint) => base + constraint)];
if (children.length > 0) {
result = [...result, ...getErrorStrings(children, (base += `${property}.`))];
}
return result;
}, []);
};
const DEFAULT_VALIDATION_OPTIONS = {
forbidUnknownValues: true,
whitelist: true,
forbidNonWhitelisted: true,
};
/**
* Enum error message
*
* @param {{}} entity
* @returns {string | undefined}
*/
export const enumError = (entity: any): string | undefined => {
if (!isObject(entity)) return;
// eslint-disable-next-line consistent-return
return `$property must be one of: ${Object.values(entity).join(', ')}`;
};
/**
* @class
*/
export abstract class ValidatedBase {
/**
* Perform async validation
*
* @param {ValidatorOptions} [options]
* @returns {Promise<void>}
*/
async validateAsync(options?: ValidatorOptions): Promise<void> {
options = { ...options, ...DEFAULT_VALIDATION_OPTIONS };
const errors = await validate(this, options);
if (errors.length > 0) {
const errorStrings = getErrorStrings(errors);
throw new Err(errorStrings.join(', '), HTTP_STATUS.BAD_REQUEST);
}
}
/**
* Perform sync validation
*
* @param {ValidatorOptions} [options]
* @returns {void}
*/
validate(options?: ValidatorOptions): void {
options = { ...options, ...DEFAULT_VALIDATION_OPTIONS };
const errors = validateSync(this, options);
if (errors.length > 0) {
const errorStrings = getErrorStrings(errors);
throw new Err(errorStrings.join(', '), HTTP_STATUS.BAD_REQUEST);
}
}
/**
* Convert model to json
*
* @param {string[]} [omitProperties=[]]
* @returns {{}}
*/
convertToJSON(omitProperties: string[] = []): any {
const self = cleanDeep(this, { emptyStrings: false, emptyArrays: false, nullValues: false });
const privateKeys = Object.keys(self).filter((key) => key.startsWith('_'));
const cleaned = omit(self, ...privateKeys);
return JSON.parse(JSON.stringify(omit(cleaned, omitProperties)));
}
}