From 033c3533c3d2ed30cd899fc8ab45cba53e793dfe Mon Sep 17 00:00:00 2001 From: Kenn Sippell Date: Mon, 12 Feb 2024 10:23:05 +0700 Subject: [PATCH] Support input-type: Date of Birth (#41) * Update Uganda config to match VHT template * Passing lint * Support for Date of Birth inputs * Test case for date in the future * 1.0.7 * Genders should be lowercase * (village) should be autogenerated from village information * Add staging server * Update training and dev instances * 1.0.9 --- .vscode/launch.json | 24 ++++++++++++++ package-lock.json | 19 +++++++++-- package.json | 4 ++- src/config/chis-ug/config.json | 2 +- src/lib/validation.ts | 15 +++++---- src/lib/validator-dob.ts | 32 +++++++++++++++++++ src/public/app/view.html | 19 ++++++++--- .../components/contact_type_property.html | 2 +- test/lib/validation.spec.ts | 7 ++++ 9 files changed, 107 insertions(+), 17 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 src/lib/validator-dob.ts diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..23cc07b0 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,24 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/dist/index.js", + "envFile": "${workspaceFolder}/.env" + }, + { + "command": "npm test", + "name": "Run npm test", + "request": "launch", + "type": "node-terminal" + } + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6dbc11ea..da17db2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cht-user-management", - "version": "1.0.9", + "version": "1.0.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cht-user-management", - "version": "1.0.9", + "version": "1.0.10", "license": "ISC", "dependencies": { "@fastify/autoload": "^5.8.0", @@ -17,6 +17,7 @@ "@fastify/view": "^8.2.0", "@types/jsonwebtoken": "^9.0.5", "@types/lodash": "^4.14.201", + "@types/luxon": "^3.4.2", "@types/node": "^20.8.8", "@types/uuid": "^9.0.6", "axios": "^1.5.1", @@ -28,6 +29,7 @@ "libphonenumber-js": "^1.10.48", "liquidjs": "^10.9.2", "lodash": "^4.17.21", + "luxon": "^3.4.4", "pino-pretty": "^10.2.3", "typescript": "^5.2.2", "uuid": "^9.0.1" @@ -426,6 +428,11 @@ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==" }, + "node_modules/@types/luxon": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", + "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==" + }, "node_modules/@types/mocha": { "version": "10.0.6", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", @@ -2628,6 +2635,14 @@ "node": ">=10" } }, + "node_modules/luxon": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "engines": { + "node": ">=12" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", diff --git a/package.json b/package.json index 6fb07286..ee0416b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cht-user-management", - "version": "1.0.9", + "version": "1.0.10", "main": "dist/index.js", "dependencies": { "@fastify/autoload": "^5.8.0", @@ -11,6 +11,7 @@ "@fastify/view": "^8.2.0", "@types/jsonwebtoken": "^9.0.5", "@types/lodash": "^4.14.201", + "@types/luxon": "^3.4.2", "@types/node": "^20.8.8", "@types/uuid": "^9.0.6", "axios": "^1.5.1", @@ -22,6 +23,7 @@ "libphonenumber-js": "^1.10.48", "liquidjs": "^10.9.2", "lodash": "^4.17.21", + "luxon": "^3.4.4", "pino-pretty": "^10.2.3", "typescript": "^5.2.2", "uuid": "^9.0.1" diff --git a/src/config/chis-ug/config.json b/src/config/chis-ug/config.json index 30bad92a..0b361ae4 100644 --- a/src/config/chis-ug/config.json +++ b/src/config/chis-ug/config.json @@ -101,7 +101,7 @@ { "friendly_name": "Date of Birth", "property_name": "date_of_birth", - "type": "name", + "type": "dob", "required": true }, { diff --git a/src/lib/validation.ts b/src/lib/validation.ts index 4c423e47..83715374 100644 --- a/src/lib/validation.ts +++ b/src/lib/validation.ts @@ -1,15 +1,15 @@ import { Config, ContactProperty } from '../config'; - -import ValidatorString from './validator-string'; -import ValidatorPhone from './validator-phone'; -import ValidatorRegex from './validator-regex'; -import ValidatorName from './validator-name'; -import ValidatorGender from './validator-gender'; import Place from '../services/place'; -import ValidatorSkip from './validator-skip'; import RemotePlaceResolver from './remote-place-resolver'; import { RemotePlace } from './cht-api'; +import ValidatorDateOfBirth from './validator-dob'; +import ValidatorGender from './validator-gender'; +import ValidatorName from './validator-name'; +import ValidatorPhone from './validator-phone'; +import ValidatorRegex from './validator-regex'; +import ValidatorSkip from './validator-skip'; +import ValidatorString from './validator-string'; export type ValidationError = { property_name: string; @@ -33,6 +33,7 @@ const TypeValidatorMap: ValidatorMap = { phone: new ValidatorPhone(), none: new ValidatorSkip(), gender: new ValidatorGender(), + dob: new ValidatorDateOfBirth(), }; export class Validation { diff --git a/src/lib/validator-dob.ts b/src/lib/validator-dob.ts new file mode 100644 index 00000000..e8c71b0c --- /dev/null +++ b/src/lib/validator-dob.ts @@ -0,0 +1,32 @@ +import { DateTime } from 'luxon'; +import { IValidator } from './validation'; + +export default class ValidatorDateOfBirth implements IValidator { + isValid(input: string) : boolean | string { + try { + const parsed = parse(input); + return parsed.isValid && parsed.diffNow('hours').hours <= 0; + } catch (e) { + return false; + } + } + + format(input : string) : string { + const parsed = parse(input); + const asISODate = parsed.toISODate(); + if (!asISODate) { + return input; + } + + return asISODate; + } + + get defaultError(): string { + return 'Not a valid Date of Birth (eg. 1990-02-26)'; + } +} + +const parse = (input: string) => { + const strippedInput = input.replace(/ /ig, ''); + return DateTime.fromISO(strippedInput); +}; diff --git a/src/public/app/view.html b/src/public/app/view.html index 08930e61..a5a7f9b1 100644 --- a/src/public/app/view.html +++ b/src/public/app/view.html @@ -6,11 +6,12 @@ integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni" crossorigin="anonymous"> - - - + + + + + +