@becollective/utils 1.10.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/.semaphore/utils.yml +1 -1
  2. package/index.ts +7 -0
  3. package/lib/FeatureFlag.d.ts +12 -0
  4. package/lib/FeatureFlag.js +51 -0
  5. package/lib/constants/allowedHtmlTags.d.ts +7 -0
  6. package/lib/constants/allowedHtmlTags.js +8 -0
  7. package/lib/constants/avatarColours.d.ts +35 -0
  8. package/lib/constants/avatarColours.js +36 -0
  9. package/lib/constants/countries.d.ts +253 -0
  10. package/lib/constants/countries.js +254 -0
  11. package/lib/constants/currencies.d.ts +29 -0
  12. package/lib/constants/currencies.js +36 -0
  13. package/lib/constants/customFields.d.ts +11 -0
  14. package/lib/constants/customFields.js +12 -0
  15. package/lib/constants/distancesUnits.d.ts +11 -0
  16. package/lib/constants/distancesUnits.js +13 -0
  17. package/lib/constants/groups.d.ts +13 -0
  18. package/lib/constants/groups.js +85 -0
  19. package/lib/constants/insuranceStatuses.d.ts +15 -0
  20. package/lib/constants/insuranceStatuses.js +7 -0
  21. package/lib/constants/insuranceTypes.d.ts +35 -0
  22. package/lib/constants/insuranceTypes.js +36 -0
  23. package/lib/constants/languages.d.ts +2 -0
  24. package/lib/constants/languages.js +194 -0
  25. package/lib/constants/locales.d.ts +119 -0
  26. package/lib/constants/locales.js +191 -0
  27. package/lib/constants/notifications.d.ts +47 -0
  28. package/lib/constants/notifications.js +31 -0
  29. package/lib/constants/opportunities.d.ts +72 -0
  30. package/lib/constants/opportunities.js +82 -0
  31. package/lib/constants/qualificationTypes.d.ts +4 -0
  32. package/lib/constants/qualificationTypes.js +18 -0
  33. package/lib/constants/rules.d.ts +13 -0
  34. package/lib/constants/rules.js +21 -0
  35. package/lib/constants/subscriptions.d.ts +9 -0
  36. package/lib/constants/subscriptions.js +10 -0
  37. package/lib/constants/surveys.d.ts +16 -0
  38. package/lib/constants/surveys.js +17 -0
  39. package/lib/constants.d.ts +1 -0
  40. package/lib/constants.js +76 -0
  41. package/lib/date-time.d.ts +28 -0
  42. package/lib/date-time.js +79 -0
  43. package/lib/forms.d.ts +1 -0
  44. package/lib/forms.js +728 -0
  45. package/lib/locality.d.ts +5 -0
  46. package/lib/locality.js +15 -0
  47. package/lib/money.d.ts +7 -0
  48. package/lib/money.js +32 -0
  49. package/lib/opportunity.d.ts +1 -0
  50. package/lib/opportunity.js +11 -0
  51. package/lib/opportunityUser.d.ts +8 -0
  52. package/lib/opportunityUser.js +29 -0
  53. package/lib/password.d.ts +8 -0
  54. package/lib/password.js +43 -0
  55. package/lib/rrule/date.d.ts +4 -0
  56. package/lib/rrule/date.js +30 -0
  57. package/lib/rrule/index.d.ts +2 -0
  58. package/lib/rrule/index.js +18 -0
  59. package/lib/rrule/parsing.d.ts +6 -0
  60. package/lib/rrule/parsing.js +130 -0
  61. package/lib/rrule/parsing.test.d.ts +1 -0
  62. package/lib/rrule/parsing.test.js +11 -0
  63. package/lib/rrule.d.ts +49 -0
  64. package/lib/rrule.js +303 -0
  65. package/lib/src/FeatureFlag.d.ts +12 -0
  66. package/lib/src/FeatureFlag.js +51 -0
  67. package/lib/src/date-time.d.ts +28 -0
  68. package/lib/src/date-time.js +79 -0
  69. package/lib/src/forms.d.ts +1 -0
  70. package/lib/src/forms.js +728 -0
  71. package/lib/src/locality.d.ts +5 -0
  72. package/lib/src/locality.js +15 -0
  73. package/lib/src/money.d.ts +7 -0
  74. package/lib/src/money.js +32 -0
  75. package/lib/src/opportunity.d.ts +1 -0
  76. package/lib/src/opportunity.js +11 -0
  77. package/lib/src/opportunityUser.d.ts +8 -0
  78. package/lib/src/opportunityUser.js +29 -0
  79. package/lib/src/password.d.ts +8 -0
  80. package/lib/src/password.js +43 -0
  81. package/package.json +15 -29
  82. package/src/FeatureFlag.ts +26 -10
  83. package/src/constants/allowedHtmlTags.ts +6 -0
  84. package/src/constants/avatarColours.ts +34 -0
  85. package/src/constants/countries.ts +252 -0
  86. package/src/constants/currencies.ts +33 -0
  87. package/src/constants/customFields.ts +10 -0
  88. package/src/constants/distancesUnits.ts +11 -0
  89. package/src/constants/groups.ts +83 -0
  90. package/src/constants/insuranceStatuses.ts +5 -0
  91. package/src/constants/insuranceTypes.ts +34 -0
  92. package/src/constants/languages.ts +192 -0
  93. package/src/constants/locales.ts +189 -0
  94. package/src/constants/notifications.ts +30 -0
  95. package/src/constants/opportunities.ts +84 -0
  96. package/src/constants/qualificationTypes.ts +17 -0
  97. package/src/constants/rules.ts +22 -0
  98. package/src/constants/subscriptions.ts +8 -0
  99. package/src/constants/surveys.ts +15 -0
  100. package/src/constants.ts +40 -0
  101. package/src/{date-time.js → date-time.ts} +18 -11
  102. package/src/forms.ts +807 -0
  103. package/src/{opportunityUser.js → opportunityUser.ts} +5 -1
  104. package/src/{password.js → password.ts} +2 -0
  105. package/src/rrule/date.ts +25 -0
  106. package/src/rrule/index.ts +2 -0
  107. package/src/rrule/parsing.ts +136 -0
  108. package/src/rrule.ts +367 -0
  109. package/tests/{date-time.test.js → date-time.test.ts} +48 -46
  110. package/tests/featureFlag.test.ts +36 -39
  111. package/tests/forms.test-data.json +43 -0
  112. package/tests/forms.test-schema.json +144 -0
  113. package/tests/forms.test-uischema.json +117 -0
  114. package/tests/forms.test.ts +674 -0
  115. package/tests/{money.test.js → money.test.ts} +2 -3
  116. package/tests/{password.test.js → password.test.ts} +7 -6
  117. package/tests/rrule/parsing.test.ts +12 -0
  118. package/tests/rrule.test.ts +625 -0
  119. package/tsconfig.json +15 -12
  120. package/bundle.js +0 -270
  121. package/index.js +0 -25
  122. package/rollup.config.js +0 -25
  123. /package/src/{locality.js → locality.ts} +0 -0
  124. /package/src/{money.js → money.ts} +0 -0
  125. /package/src/{opportunity.js → opportunity.ts} +0 -0
@@ -0,0 +1,728 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ /* eslint-disable no-prototype-builtins */
7
+ const ajv_1 = __importDefault(require("ajv"));
8
+ const ajv = new ajv_1.default({
9
+ allErrors: true,
10
+ verbose: true,
11
+ });
12
+ const phoneNumber = require('libphonenumber-js');
13
+ const BC_REPORTABLE_FIELDS = {
14
+ BC_ADDRESS: 'BC_ADDRESS',
15
+ BC_PHONE: 'BC_PHONE',
16
+ BC_OWN_VEHICLE: 'BC_OWN_VEHICLE',
17
+ BC_DRIVERS_LICENCE: 'BC_DRIVERS_LICENCE',
18
+ BC_WORKING_WITH_CHILDREN: 'BC_WORKING_WITH_CHILDREN',
19
+ BC_POLICE_CHECK: 'BC_POLICE_CHECK',
20
+ BC_MEDICAL_CONDITIONS: 'BC_MEDICAL_CONDITIONS',
21
+ BC_COUNTRY_OF_BIRTH: 'BC_COUNTRY_OF_BIRTH',
22
+ };
23
+ const widgetTypes = [
24
+ 'WidgetDate',
25
+ 'WidgetDateRange',
26
+ 'WidgetDropDown',
27
+ 'WidgetHeading',
28
+ 'WidgetSpacer',
29
+ 'WidgetLocation',
30
+ 'WidgetNumber',
31
+ 'WidgetPhone',
32
+ 'WidgetRadio',
33
+ 'WidgetTextArea',
34
+ 'WidgetTextBox',
35
+ 'WidgetTextParagraph',
36
+ 'WidgetTickbox',
37
+ 'WidgetToggle',
38
+ 'WidgetSkillsRequired',
39
+ 'WidgetFile',
40
+ 'WidgetQualifications',
41
+ 'WidgetCountry',
42
+ 'WidgetSocialPrescribing',
43
+ ];
44
+ const countDecimals = (value) => {
45
+ if (Math.floor(value) === value)
46
+ return 0;
47
+ const str = value.toString();
48
+ // This handles scientific notation on long decimals from toString ie "1e-9"
49
+ if (str.indexOf('.') !== -1 && str.indexOf('-') !== -1) {
50
+ return parseInt(str.split('-')[1] || 0);
51
+ }
52
+ else if (str.indexOf('.') !== -1) {
53
+ return parseInt(str.split('.')[1].length || 0);
54
+ }
55
+ return parseInt(str.split('-')[1] || 0);
56
+ };
57
+ const isValidYear = (value) => {
58
+ const year = new Date(value).getFullYear();
59
+ return year < 9999 && year > 999;
60
+ };
61
+ const validateType = async (params) => {
62
+ const { type, data, schema, error } = params;
63
+ if (validator.dataTypeIsValid[type](data, schema)) {
64
+ return;
65
+ }
66
+ throw {
67
+ name: error || `Property is not a ${type}`
68
+ };
69
+ };
70
+ const validator = {
71
+ validateSchema: async (schema, uischema) => {
72
+ if (!schema.hasOwnProperty('properties')) {
73
+ throw {
74
+ name: 'missing-properties'
75
+ };
76
+ }
77
+ else if (!schema.hasOwnProperty('required')) {
78
+ throw {
79
+ name: 'missing-required'
80
+ };
81
+ }
82
+ let err;
83
+ let result;
84
+ const errors = [];
85
+ const keys = Object.keys(schema.properties);
86
+ result = validator.validateSchemaPropertiesExist(schema.required, keys, 'required-item-not-found');
87
+ if (result) {
88
+ throw result;
89
+ }
90
+ result = validator.validateSchemaPropertiesExist(uischema['ui:order'], keys, 'order-item-not-found');
91
+ if (result) {
92
+ throw result;
93
+ }
94
+ result = validator.validateSchemaPropertiesExist(keys, Object.keys(uischema), 'ui-data-schema-mismatch');
95
+ if (result) {
96
+ throw result;
97
+ }
98
+ result = validator.validatePropertiesTitleUniqueness(schema.properties, 'Titles must be unique.');
99
+ if (result) {
100
+ throw result;
101
+ }
102
+ for await (const item of keys) {
103
+ const o = schema.properties[item];
104
+ /*
105
+ o is each property. eg.
106
+ {
107
+ type: 'array',
108
+ title: 'Where did you hear about us?',
109
+ items: {
110
+ type: 'string',
111
+ enum: [ 'Newspaper', 'Google', 'Word of mouth' ]
112
+ },
113
+ uniqueItems: true,
114
+ bcReportable: 'BC_HOW_DID_YOU_HEAR',
115
+ bcRequired: false
116
+ }
117
+ */
118
+ const f = validator.getValidatorForType(o.bcType || o.type);
119
+ if (o.hasOwnProperty('bcReportable')) {
120
+ if (!BC_REPORTABLE_FIELDS[o.bcReportable] && !o.bcReportable.startsWith('USER_')) {
121
+ errors.push({
122
+ name: 'invalid-reportable-field',
123
+ object: o,
124
+ });
125
+ }
126
+ }
127
+ if (f) {
128
+ const valid = ajv.validate(f, o);
129
+ if (valid !== true) {
130
+ const errorMsgs = ajv.errors.map(({ instancePath, message }) => {
131
+ let property = instancePath.replace(/\//g, '.');
132
+ if (property.charAt(0) === '.') {
133
+ property = property.slice(1, property.length);
134
+ }
135
+ return {
136
+ key: item,
137
+ property,
138
+ message,
139
+ };
140
+ });
141
+ errors.push(...errorMsgs);
142
+ }
143
+ continue;
144
+ }
145
+ else {
146
+ errors.push({ name: 'invalid-schema-type', object: o });
147
+ continue;
148
+ }
149
+ }
150
+ if (errors.length) {
151
+ throw errors;
152
+ }
153
+ Object.keys(uischema).map((o) => {
154
+ if (o === 'ui:order') {
155
+ return;
156
+ }
157
+ const data = uischema[o];
158
+ const valid = ajv.validate(validator.uiSchema, data);
159
+ if (valid !== true) {
160
+ errors.push({ name: 'invalid-uischema-property', object: o });
161
+ }
162
+ });
163
+ if (errors.length) {
164
+ throw errors;
165
+ }
166
+ return true;
167
+ },
168
+ validateSchemaPropertiesExist: (properties, keys, err) => {
169
+ const errors = [];
170
+ for (const i in properties) {
171
+ if (keys.indexOf(properties[i]) === -1) {
172
+ errors.push({
173
+ name: err,
174
+ key: i,
175
+ value: properties[i],
176
+ });
177
+ }
178
+ }
179
+ if (errors.length) {
180
+ return errors;
181
+ }
182
+ return null;
183
+ },
184
+ /**
185
+ * Check if every field in the form has a unique title
186
+ * @param {object} properties fields properties
187
+ * @param {string} err error name
188
+ */
189
+ validatePropertiesTitleUniqueness: (properties, err) => {
190
+ const errors = [];
191
+ const titleCounts = {};
192
+ for (const key in properties) {
193
+ // "spacer" and "textParagraph" are special fields whose title is fixed and
194
+ // doesn't show in UI, hence no unique check required for them
195
+ if (properties[key].bcType &&
196
+ (properties[key].bcType === 'spacer' || properties[key].bcType === 'textParagraph')) {
197
+ continue;
198
+ }
199
+ const title = properties[key].title.trim().toLowerCase();
200
+ titleCounts[title] = titleCounts[title] ? [...titleCounts[title], key] : [key];
201
+ }
202
+ for (const title in titleCounts) {
203
+ if (titleCounts[title].length > 1) {
204
+ titleCounts[title].forEach((key) => {
205
+ errors.push({
206
+ key,
207
+ property: key,
208
+ message: err,
209
+ });
210
+ });
211
+ }
212
+ }
213
+ if (errors.length) {
214
+ return errors;
215
+ }
216
+ return null;
217
+ },
218
+ /**
219
+ * Creates a new object from the input obj.
220
+ * All entries from the input whose values are empty arrays are missing in the new object
221
+ * @param {Object} data 1-depth obj
222
+ * @returns A new object without empty arrays
223
+ */
224
+ stripEmptyArraysFromData: (data) => {
225
+ const strippedObject = {};
226
+ Object.keys(data).forEach((k) => {
227
+ if (!(Array.isArray(data[k]) && data[k].length === 0))
228
+ strippedObject[k] = data[k];
229
+ });
230
+ return strippedObject;
231
+ },
232
+ validateData: async (schema, data) => {
233
+ // data is the user submitted form data.
234
+ const keys = Object.keys(data);
235
+ const properties = Object.keys(schema.properties);
236
+ const errors = [];
237
+ keys.map((o) => {
238
+ const found = false;
239
+ if (properties.indexOf(o) === -1) {
240
+ errors.push({
241
+ name: 'properties-not-found',
242
+ key: o,
243
+ value: data[o],
244
+ });
245
+ }
246
+ });
247
+ schema.required.map((o) => {
248
+ const isFileType = schema.properties[o].bcType === 'file';
249
+ if (keys.indexOf(o) === -1 && !isFileType) {
250
+ errors.push({
251
+ name: 'required-data-not-found',
252
+ key: o,
253
+ value: data[o],
254
+ });
255
+ }
256
+ });
257
+ if (errors.length) {
258
+ throw errors;
259
+ }
260
+ for await (const item of properties) {
261
+ if (!data.hasOwnProperty(item)) {
262
+ continue;
263
+ }
264
+ // property is the schema property.
265
+ // eg. { title: 'Phone Number', type: 'string', bcType: 'phone' };
266
+ const property = schema.properties[item];
267
+ const value = data[item];
268
+ // value is the data the user submits for the above property
269
+ // eg. +61434111222
270
+ try {
271
+ const v = validator.getValidatorForTypeData(property.bcType || property.type);
272
+ await v(property, value);
273
+ }
274
+ catch (e) {
275
+ errors.push(e);
276
+ }
277
+ }
278
+ if (errors.length) {
279
+ throw (errors);
280
+ }
281
+ return;
282
+ },
283
+ getValidatorForType: (type) => {
284
+ return validator.schemaTypes[type] || null;
285
+ },
286
+ getValidatorForTypeData: (type) => {
287
+ const v = validator.dataTypes[type];
288
+ if (v) {
289
+ return v;
290
+ }
291
+ throw {
292
+ name: 'Datatype not found',
293
+ };
294
+ },
295
+ uiSchema: {
296
+ type: 'object',
297
+ required: ['ui:title', 'ui:widget'],
298
+ properties: {
299
+ 'ui:title': {
300
+ type: 'string',
301
+ },
302
+ 'ui:helpertext': {
303
+ type: 'string',
304
+ },
305
+ 'ui:bc-helpertext': {
306
+ type: 'string',
307
+ },
308
+ 'ui:bc-required': {
309
+ type: 'boolean',
310
+ },
311
+ 'ui:bc-multiSelect': {
312
+ type: 'boolean',
313
+ },
314
+ 'ui:widget': {
315
+ type: 'string',
316
+ enum: widgetTypes,
317
+ },
318
+ },
319
+ },
320
+ schemaTypes: {
321
+ string: {
322
+ type: 'object',
323
+ required: ['title'],
324
+ properties: {
325
+ title: {
326
+ type: 'string',
327
+ minLength: 1,
328
+ },
329
+ },
330
+ },
331
+ number: {
332
+ type: 'object',
333
+ required: ['title'],
334
+ properties: {
335
+ title: {
336
+ type: 'string',
337
+ minLength: 1,
338
+ },
339
+ },
340
+ },
341
+ boolean: {
342
+ type: 'object',
343
+ required: ['title'],
344
+ properties: {
345
+ title: {
346
+ type: 'string',
347
+ minLength: 1,
348
+ },
349
+ },
350
+ },
351
+ date: {
352
+ type: 'object',
353
+ required: ['title'],
354
+ properties: {
355
+ title: {
356
+ type: 'string',
357
+ minLength: 1,
358
+ },
359
+ type: {
360
+ const: 'string',
361
+ },
362
+ },
363
+ },
364
+ dateRange: {
365
+ type: 'object',
366
+ required: ['title'],
367
+ properties: {
368
+ title: {
369
+ type: 'string',
370
+ minLength: 1,
371
+ },
372
+ type: {
373
+ const: 'object',
374
+ },
375
+ },
376
+ },
377
+ phone: {
378
+ type: 'object',
379
+ required: ['title'],
380
+ properties: {
381
+ title: {
382
+ type: 'string',
383
+ minLength: 1,
384
+ },
385
+ type: {
386
+ const: 'string',
387
+ },
388
+ },
389
+ },
390
+ skills: {
391
+ type: 'object',
392
+ required: ['title'],
393
+ properties: {
394
+ title: {
395
+ type: 'string',
396
+ minLength: 1,
397
+ },
398
+ uniqueItems: {
399
+ type: 'boolean',
400
+ },
401
+ items: {
402
+ type: 'object',
403
+ properties: {
404
+ anyOf: {
405
+ type: 'array',
406
+ minItems: 1,
407
+ items: {
408
+ type: 'object',
409
+ properties: {
410
+ type: { type: 'string' },
411
+ title: { type: 'string' },
412
+ enum: {
413
+ type: 'array',
414
+ minItems: 1,
415
+ items: {
416
+ type: 'string',
417
+ minLength: 1,
418
+ },
419
+ },
420
+ },
421
+ },
422
+ },
423
+ },
424
+ },
425
+ },
426
+ },
427
+ qualifications: {
428
+ type: 'object',
429
+ required: ['title'],
430
+ properties: {
431
+ title: {
432
+ type: 'string',
433
+ minLength: 1,
434
+ },
435
+ uniqueItems: {
436
+ type: 'boolean',
437
+ },
438
+ items: {
439
+ type: 'object',
440
+ properties: {
441
+ type: {
442
+ const: 'string',
443
+ },
444
+ enum: {
445
+ type: 'array',
446
+ minItems: 1,
447
+ items: [{ type: 'string' }],
448
+ },
449
+ },
450
+ },
451
+ },
452
+ },
453
+ array: {
454
+ type: 'object',
455
+ required: ['title'],
456
+ properties: {
457
+ title: {
458
+ type: 'string',
459
+ minLength: 1,
460
+ },
461
+ uniqueItems: {
462
+ type: 'boolean',
463
+ },
464
+ items: {
465
+ type: 'object',
466
+ properties: {
467
+ anyOf: {
468
+ type: 'array',
469
+ minItems: 1,
470
+ uniqueItems: true,
471
+ items: {
472
+ type: 'object',
473
+ properties: {
474
+ type: { type: 'string' },
475
+ title: { type: 'string' },
476
+ enum: {
477
+ type: 'array',
478
+ minItems: 1,
479
+ items: {
480
+ type: 'string',
481
+ minLength: 1,
482
+ },
483
+ },
484
+ },
485
+ },
486
+ },
487
+ },
488
+ },
489
+ },
490
+ },
491
+ heading: {
492
+ type: 'object',
493
+ required: ['title'],
494
+ properties: {
495
+ title: {
496
+ type: 'string',
497
+ minLength: 1,
498
+ },
499
+ type: {
500
+ const: 'string',
501
+ },
502
+ },
503
+ },
504
+ spacer: {
505
+ type: 'object',
506
+ properties: {
507
+ type: {
508
+ const: 'string',
509
+ },
510
+ },
511
+ },
512
+ location: {
513
+ type: 'object',
514
+ required: ['title'],
515
+ properties: {
516
+ title: {
517
+ type: 'string',
518
+ minLength: 1,
519
+ },
520
+ type: {
521
+ const: 'object',
522
+ },
523
+ },
524
+ },
525
+ file: {
526
+ type: 'object',
527
+ required: ['title'],
528
+ properties: {
529
+ title: {
530
+ type: 'string',
531
+ minLength: 1,
532
+ },
533
+ },
534
+ },
535
+ textParagraph: {
536
+ type: 'object',
537
+ required: ['text'],
538
+ properties: {
539
+ text: {
540
+ type: 'string',
541
+ minLength: 1,
542
+ },
543
+ },
544
+ },
545
+ },
546
+ // NOTE: schema below is not the whole schema but the individual property
547
+ // eg. { title: 'Phone Number', type: 'string', bcType: 'phone' };
548
+ dataTypeIsValid: {
549
+ string: (data, schema) => {
550
+ const isString = schema && schema.required ? !!(typeof data === 'string' && data.length) : !!(typeof data === 'string');
551
+ if (schema && schema.items && schema.items.enum) {
552
+ return isString && schema.items.enum.includes(data);
553
+ }
554
+ return isString;
555
+ },
556
+ boolean: (data) => {
557
+ return data === true || data === false;
558
+ },
559
+ number: (data) => {
560
+ return typeof data === 'number' && countDecimals(data) <= 10;
561
+ },
562
+ // date and dateRange must be ISOString format
563
+ date: (data) => {
564
+ return typeof data === 'string' && new Date(data).toString() !== 'Invalid Date' && isValidYear(data);
565
+ },
566
+ dateRange: (data) => {
567
+ const fromIsValid = validator.dataTypeIsValid.date(data.from);
568
+ const toIsValid = validator.dataTypeIsValid.date(data.to);
569
+ const toDateIsAFterFromDate = new Date(data.from) <= new Date(data.to);
570
+ return fromIsValid && toIsValid && toDateIsAFterFromDate;
571
+ },
572
+ phone: (data) => {
573
+ return phoneNumber.isValidNumber(data || '');
574
+ },
575
+ skills: (data, schema) => {
576
+ if (!schema || !schema.items)
577
+ return false;
578
+ let hasAnyOf = false;
579
+ try {
580
+ hasAnyOf =
581
+ ajv.validate({
582
+ type: 'array',
583
+ items: schema.items.anyOf,
584
+ minItems: 1,
585
+ }, data) === true;
586
+ }
587
+ catch (e) { }
588
+ return hasAnyOf;
589
+ },
590
+ qualifications: (data, schema) => {
591
+ if (!schema || !schema.items)
592
+ return false;
593
+ return (ajv.validate({
594
+ type: 'array',
595
+ items: schema.items,
596
+ minItems: 1,
597
+ }, data) === true);
598
+ },
599
+ array: (data, schema) => {
600
+ if (!schema || !schema.items)
601
+ return false;
602
+ let hasAnyOf = false;
603
+ let hasItems = false;
604
+ try {
605
+ hasAnyOf =
606
+ ajv.validate({
607
+ type: 'array',
608
+ items: schema.items.anyOf,
609
+ minItems: 1,
610
+ }, data) === true;
611
+ hasItems =
612
+ ajv.validate({
613
+ type: 'array',
614
+ items: schema.items,
615
+ minItems: 1,
616
+ }, data) === true;
617
+ }
618
+ catch (e) { }
619
+ return hasItems || hasAnyOf;
620
+ },
621
+ heading: () => true,
622
+ spacer: () => true,
623
+ location: (data) => {
624
+ const s = {
625
+ type: 'object',
626
+ required: ['geo'],
627
+ properties: {
628
+ geo: {
629
+ type: 'object',
630
+ required: ['lat', 'lng'],
631
+ properties: {
632
+ lat: {
633
+ type: 'number',
634
+ minimum: -90,
635
+ maximum: 90,
636
+ },
637
+ lng: {
638
+ type: 'number',
639
+ minimum: -180,
640
+ maximum: 180,
641
+ },
642
+ },
643
+ },
644
+ locality: {
645
+ type: 'object',
646
+ properties: {
647
+ long: {
648
+ type: 'string',
649
+ },
650
+ short: {
651
+ type: 'string',
652
+ },
653
+ },
654
+ },
655
+ region: {
656
+ type: 'object',
657
+ properties: {
658
+ long: {
659
+ type: 'string',
660
+ },
661
+ short: {
662
+ type: 'string',
663
+ },
664
+ },
665
+ },
666
+ country: {
667
+ type: 'object',
668
+ properties: {
669
+ long: {
670
+ type: 'string',
671
+ },
672
+ short: {
673
+ type: 'string',
674
+ },
675
+ },
676
+ },
677
+ },
678
+ };
679
+ if (ajv.validate(s, data) === true) {
680
+ return true;
681
+ }
682
+ return false;
683
+ },
684
+ file: (data) => {
685
+ if (data === null || typeof data.name === 'string') {
686
+ return true;
687
+ }
688
+ const s = {
689
+ type: 'object',
690
+ required: ['opportunityId', 'volunteerId', 'filename'],
691
+ properties: {
692
+ opportunityId: {
693
+ type: 'string',
694
+ pattern: '[a-z0-9]{24}',
695
+ },
696
+ volunteerId: {
697
+ type: 'string',
698
+ pattern: '[a-z0-9]{24}',
699
+ },
700
+ filename: {
701
+ type: 'string',
702
+ },
703
+ },
704
+ };
705
+ if (ajv.validate(s, data) === true) {
706
+ return true;
707
+ }
708
+ return false;
709
+ },
710
+ },
711
+ dataTypes: {
712
+ string: (schema, data) => validateType({ type: 'string', data, schema }),
713
+ boolean: (schema, data) => validateType({ type: 'boolean', data }),
714
+ date: (schema, data) => validateType({ type: 'date', data }),
715
+ dateRange: (schema, data) => validateType({ type: 'dateRange', error: 'Property is not a date range', data }),
716
+ phone: (schema, data) => validateType({ type: 'phone', error: 'Property is not a phone number', data }),
717
+ skills: (schema, data) => validateType({ type: 'skills', error: 'Property is not valid list of skills', data, schema }),
718
+ qualifications: (schema, data) => validateType({ type: 'qualifications', error: 'Property is not valid list of qualifications', data, schema }),
719
+ array: (schema, data) => validateType({ type: 'array', error: 'Datatype is not an array of strings', data, schema }),
720
+ heading: (schema, data) => validateType({ type: 'heading', data }),
721
+ spacer: (schema, data) => validateType({ type: 'spacer', data: null }),
722
+ location: (schema, data) => validateType({ type: 'location', error: 'Datatype is not valid location object', data }),
723
+ file: (schema, data) => validateType({ type: 'file', error: 'File must not be set', data }),
724
+ number: (schema, data) => validateType({ type: 'number', error: 'Property is not a number', data }),
725
+ textParagraph: (schema, data) => validateType({ type: 'string', data, schema }),
726
+ },
727
+ };
728
+ module.exports = validator;