From 76f1c611bc924f002e73f4f2cd902607d6dc1255 Mon Sep 17 00:00:00 2001 From: Kenn Sippell Date: Tue, 23 Jan 2024 17:24:09 -0600 Subject: [PATCH] Run Eslint and require it to pass through GitHub CI (#27) --- .eslintrc | 86 +++++ .github/workflows/run-tests.yml | 1 + .gitignore | 1 + package-lock.json | 528 ++++++++++++++++++++++++++- package.json | 9 +- src/config/chis-ke/gross.ts | 6 +- src/config/chis-ke/index.ts | 4 +- src/config/chis-ug/index.ts | 4 +- src/config/config-factory.ts | 8 +- src/config/index.ts | 8 +- src/index.ts | 8 +- src/lib/authentication.ts | 8 +- src/lib/cht-api.ts | 34 +- src/lib/move.ts | 14 +- src/lib/remote-place-cache.ts | 12 +- src/lib/remote-place-resolver.ts | 45 +-- src/lib/search.ts | 40 +- src/lib/validation.ts | 37 +- src/lib/validator-gender.ts | 4 +- src/lib/validator-name.ts | 8 +- src/lib/validator-phone.ts | 6 +- src/lib/validator-regex.ts | 4 +- src/lib/validator-skip.ts | 4 +- src/lib/validator-string.ts | 2 +- src/plugins/services.ts | 10 +- src/routes/add-place.ts | 67 ++-- src/routes/app.ts | 28 +- src/routes/authentication.ts | 11 +- src/routes/events.ts | 28 +- src/routes/files.ts | 16 +- src/routes/move.ts | 32 +- src/routes/search.ts | 31 +- src/server.ts | 26 +- src/services/contact.ts | 6 +- src/services/place-factory.ts | 35 +- src/services/place.ts | 36 +- src/services/session-cache.ts | 27 +- src/services/upload-manager.ts | 28 +- src/services/upload.new.ts | 12 +- src/services/upload.replacement.ts | 10 +- src/services/user-payload.ts | 17 +- src/types/fastify/index.d.ts | 8 +- test/lib/move.spec.ts | 4 +- test/lib/search.spec.ts | 6 +- test/lib/validation.spec.ts | 4 +- test/mocks.ts | 28 +- test/services/place-factory.spec.ts | 4 +- test/services/place.spec.ts | 8 +- test/services/upload-manager.spec.ts | 6 +- 49 files changed, 1005 insertions(+), 364 deletions(-) create mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..b9f74416 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,86 @@ +{ + "extends": "@medic", + "env": { + "node": true + }, + "parserOptions": { + "ecmaVersion": 6 + }, + "ignorePatterns": [ + "**/node_modules/**", + "**/dist/**" + ], + "plugins": ["promise", "node"], + "rules": { + "array-bracket-newline": ["error", "consistent"], + "array-callback-return": ["error", { "allowImplicit": true }], + "arrow-spacing": ["error", { "before": true, "after": true }], + "brace-style": ["error", "1tbs"], + "comma-spacing": ["error", { "before": false, "after": true }], + "comma-style": ["error", "last"], + "default-param-last": "error", + "dot-location": ["error", "property"], + "dot-notation": ["error", { "allowKeywords": true }], + "func-call-spacing": ["error", "never"], + "function-call-argument-newline": ["error", "consistent"], + "function-paren-newline": ["error", "consistent"], + "implicit-arrow-linebreak": ["error", "beside"], + "key-spacing": ["error", { "beforeColon": false, "afterColon": true }], + "keyword-spacing": ["error", { "before": true, "after": true }], + "linebreak-style": ["error", "unix"], + "lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }], + "new-parens": "error", + "no-alert": "error", + "no-else-return": "error", + "no-extra-bind": "error", + "no-lone-blocks": "error", + "no-nested-ternary": "error", + "no-undef-init": "error", + "no-useless-rename": "error", + "no-whitespace-before-property": "error", + "node/no-exports-assign": "error", + "max-len": ["error", { "code": 150 }], + "rest-spread-spacing": ["error", "never"], + "semi-spacing": ["error", { "before": false, "after": true }], + "semi-style": ["error", "last"], + "template-curly-spacing": "error", + "unicode-bom": ["error", "never"] + }, + "overrides": [ + { + "files": [ "**/test/**", "**/tests/**" ], + "rules": { + "promise/catch-or-return": "error" + } + }, + { + "files": ["*.ts", "*.tsx"], + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "parserOptions": { + "createDefaultProgram": true + }, + "rules": { + "no-console": "off", + "no-restricted-syntax": "off", + // Prevent TypeScript-specific constructs from being erroneously flagged as unused + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": "error", + // Require a specific member delimiter style for interfaces and type literals + // Default Semicolon style + "@typescript-eslint/member-delimiter-style": "error", + "@typescript-eslint/ban-ts-comment": "error", + "quote-props": ["error", "as-needed"] + } + }, + { + "files": ["*.spec.ts"], + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "rules": { + // allow ts-comments in unit tests + "@typescript-eslint/ban-ts-comment": "off" + } + } + ] +} \ No newline at end of file diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index f7b40b8b..2697cc25 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -15,4 +15,5 @@ jobs: node-version: 20.x - run: npm ci - run: npm run build + - run: npm run lint - run: npm test diff --git a/.gitignore b/.gitignore index 1c239e30..f8e3adf6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules dist .env* src/package.json +.eslintcache \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 21c52230..65ab9b1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cht-user-management", - "version": "1.0.2", + "version": "1.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cht-user-management", - "version": "1.0.2", + "version": "1.0.3", "license": "ISC", "dependencies": { "@fastify/autoload": "^5.8.0", @@ -33,12 +33,18 @@ "uuid": "^9.0.1" }, "devDependencies": { + "@medic/eslint-config": "^1.1.0", "@types/chai": "^4.3.11", "@types/mocha": "^10.0.6", "@types/rewire": "^2.5.30", "@types/sinon": "^17.0.2", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", "chai": "^4.3.10", "chai-as-promised": "^7.1.1", + "eslint": "^8.56.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^6.1.1", "mocha": "^10.2.0", "rewire": "^7.0.0", "sinon": "^17.0.1", @@ -303,6 +309,12 @@ "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, + "node_modules/@medic/eslint-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@medic/eslint-config/-/eslint-config-1.1.0.tgz", + "integrity": "sha512-KQXLM4BJ2GVmFL56iIqDUQWPPmIrJXJ03gB8zqnB6tTxwPIwAUrdXQXsb3rudMItVe9VNVRXdzBVnpd/ntfAzw==", + "dev": true + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -388,6 +400,12 @@ "integrity": "sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==", "dev": true }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -428,6 +446,12 @@ "integrity": "sha512-CSyzr7TF1EUm85as2noToMtLaBBN/rKKlo5ZDdXedQ64cUiHT25LCNo1J1cI4QghBlGmTymElW/2h3TiWYOsZw==", "dev": true }, + "node_modules/@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true + }, "node_modules/@types/sinon": { "version": "17.0.3", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", @@ -448,6 +472,211 @@ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==" }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.0.tgz", + "integrity": "sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.19.0", + "@typescript-eslint/type-utils": "6.19.0", + "@typescript-eslint/utils": "6.19.0", + "@typescript-eslint/visitor-keys": "6.19.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.19.0.tgz", + "integrity": "sha512-1DyBLG5SH7PYCd00QlroiW60YJ4rWMuUGa/JBV0iZuqi4l4IK3twKPq5ZkEebmGqRjXWVgsUzfd3+nZveewgow==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.19.0", + "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/typescript-estree": "6.19.0", + "@typescript-eslint/visitor-keys": "6.19.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.0.tgz", + "integrity": "sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/visitor-keys": "6.19.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.19.0.tgz", + "integrity": "sha512-mcvS6WSWbjiSxKCwBcXtOM5pRkPQ6kcDds/juxcy/727IQr3xMEcwr/YLHW2A2+Fp5ql6khjbKBzOyjuPqGi/w==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.19.0", + "@typescript-eslint/utils": "6.19.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.0.tgz", + "integrity": "sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.0.tgz", + "integrity": "sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/visitor-keys": "6.19.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.0.tgz", + "integrity": "sha512-QR41YXySiuN++/dC9UArYOg4X86OAYP83OWTewpVx5ct1IZhjjgTLocj7QNxGhWoTqknsgpl7L+hGygCO+sdYw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.19.0", + "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/typescript-estree": "6.19.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.0.tgz", + "integrity": "sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.19.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -579,6 +808,15 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -1032,6 +1270,18 @@ "node": ">=0.3.1" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -1177,6 +1427,88 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "dependencies": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-plugin-node/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-node/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-node/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-promise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", + "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -1193,6 +1525,30 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", @@ -1376,6 +1732,22 @@ "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -1625,6 +1997,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -1717,6 +2098,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -1737,6 +2138,18 @@ "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", "integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==" }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -1839,6 +2252,18 @@ "node": ">=8" } }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2215,6 +2640,28 @@ "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", "dev": true }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -2482,12 +2929,27 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, "node_modules/path-to-regexp": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", "dev": true }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -2745,6 +3207,18 @@ "node": ">= 12.13.0" } }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -2762,6 +3236,23 @@ "node": ">=0.10.0" } }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2968,6 +3459,15 @@ "node": ">=8" } }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/sonic-boom": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.0.tgz", @@ -3116,6 +3616,18 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/text-decoding": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", @@ -3161,6 +3673,18 @@ "node": ">=12" } }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-mocha": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-10.0.0.tgz", diff --git a/package.json b/package.json index 4c43d72f..e4d67548 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cht-user-management", - "version": "1.0.2", + "version": "1.0.3", "main": "dist/index.js", "dependencies": { "@fastify/autoload": "^5.8.0", @@ -27,12 +27,18 @@ "uuid": "^9.0.1" }, "devDependencies": { + "@medic/eslint-config": "^1.1.0", "@types/chai": "^4.3.11", "@types/mocha": "^10.0.6", "@types/rewire": "^2.5.30", "@types/sinon": "^17.0.2", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", "chai": "^4.3.10", "chai-as-promised": "^7.1.1", + "eslint": "^8.56.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^6.1.1", "mocha": "^10.2.0", "rewire": "^7.0.0", "sinon": "^17.0.1", @@ -43,6 +49,7 @@ "cp-package-json": "cp package.json ./src", "test": "npx ts-mocha test/{,**}/*.spec.ts", "build": "npm run cp-package-json && npx tsc", + "lint": "npx eslint --color --cache .", "start": "node dist/index.js", "dev": "tsc-watch --onSuccess \"node dist/index.js\"", "publish": "node dist/publish.js" diff --git a/src/config/chis-ke/gross.ts b/src/config/chis-ke/gross.ts index 81c1d9a0..3cd3c376 100644 --- a/src/config/chis-ke/gross.ts +++ b/src/config/chis-ke/gross.ts @@ -3,8 +3,8 @@ * stuff that eCHIS needs that others probably "shouldn't do" or are unlikely to do */ -import { ChtApi, PlacePayload } from "../../lib/cht-api"; -import { Config } from ".."; +import { ChtApi, PlacePayload } from '../../lib/cht-api'; +import { Config } from '..'; export default async function mutate(payload: PlacePayload, chtApi: ChtApi, isReplacement: boolean): Promise { if (payload.contact_type !== 'd_community_health_volunteer_area') { @@ -43,4 +43,4 @@ export default async function mutate(payload: PlacePayload, chtApi: ChtApi, isRe scapeToPayload('chu_name', 'name'); scapeToPayload('chu_code', 'code'); return payload; -}; +} diff --git a/src/config/chis-ke/index.ts b/src/config/chis-ke/index.ts index b068dce8..3a94dd90 100644 --- a/src/config/chis-ke/index.ts +++ b/src/config/chis-ke/index.ts @@ -1,5 +1,5 @@ -import config from "./config.json"; -import mutate from "./gross"; +import config from './config.json'; +import mutate from './gross'; export default { config, diff --git a/src/config/chis-ug/index.ts b/src/config/chis-ug/index.ts index e7b4c22c..4bec1725 100644 --- a/src/config/chis-ug/index.ts +++ b/src/config/chis-ug/index.ts @@ -1,5 +1,5 @@ -import { PartnerConfig } from ".."; -import config from "./config.json"; +import { PartnerConfig } from '..'; +import config from './config.json'; const partnerConfig: PartnerConfig = { config diff --git a/src/config/config-factory.ts b/src/config/config-factory.ts index 1b63ec27..24d04185 100644 --- a/src/config/config-factory.ts +++ b/src/config/config-factory.ts @@ -1,6 +1,6 @@ -import { PartnerConfig } from "."; -import ugandaConfig from "./chis-ug"; -import kenyaConfig from "./chis-ke"; +import { PartnerConfig } from '.'; +import ugandaConfig from './chis-ug'; +import kenyaConfig from './chis-ke'; const CONFIG_MAP: { [key: string]: PartnerConfig } = { 'CHIS-KE': kenyaConfig, @@ -17,4 +17,4 @@ export default function getConfigByKey(key: string = 'CHIS-KE'): PartnerConfig { } return result; -}; +} diff --git a/src/config/index.ts b/src/config/index.ts index 14d3d623..656dbfa8 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1,11 +1,11 @@ -import _ from "lodash"; -import { ChtApi, PlacePayload } from "../lib/cht-api"; -import getConfigByKey from "./config-factory"; +import _ from 'lodash'; +import { ChtApi, PlacePayload } from '../lib/cht-api'; +import getConfigByKey from './config-factory'; export type ConfigSystem = { domains: AuthenticationInfo[]; contact_types: ContactType[]; - logoBase64: string, + logoBase64: string; }; export type PartnerConfig = { diff --git a/src/index.ts b/src/index.ts index ea189354..c702cdfe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ require('dotenv').config(); -import build from "./server"; +import build from './server'; import { env } from 'process'; const port: number = env.PORT ? parseInt(env.PORT) : 3000; @@ -7,14 +7,16 @@ const port: number = env.PORT ? parseInt(env.PORT) : 3000; (async () => { const loggerConfig = { transport: { - target: "pino-pretty", + target: 'pino-pretty', }, }; const server = build({ logger: loggerConfig, }); server.listen({ host: '0.0.0.0', port }, (err, address) => { - if (err) throw err; + if (err) { + throw err; + } console.log(`server is listening on ${address}`); }); })(); diff --git a/src/lib/authentication.ts b/src/lib/authentication.ts index c5dee8af..a524dc85 100644 --- a/src/lib/authentication.ts +++ b/src/lib/authentication.ts @@ -1,6 +1,6 @@ -import { env } from "process"; -import jwt from "jsonwebtoken"; -import { ChtSession } from "./cht-api"; +import { env } from 'process'; +import jwt from 'jsonwebtoken'; +import { ChtSession } from './cht-api'; const LOGIN_EXPIRES_AFTER_MS = 2 * 24 * 60 * 60 * 1000; const { COOKIE_PRIVATE_KEY } = env; @@ -34,4 +34,4 @@ export default class Auth { return session; } -}; +} diff --git a/src/lib/cht-api.ts b/src/lib/cht-api.ts index d42a048e..8b0aab7c 100644 --- a/src/lib/cht-api.ts +++ b/src/lib/cht-api.ts @@ -1,7 +1,7 @@ -import _ from "lodash"; -import axios, { AxiosHeaders } from "axios"; -import { UserPayload } from "../services/user-payload"; -import { AuthenticationInfo, Config, ContactType } from "../config"; +import _ from 'lodash'; +import axios, { AxiosHeaders } from 'axios'; +import { UserPayload } from '../services/user-payload'; +import { AuthenticationInfo, Config, ContactType } from '../config'; export type ChtSession = { authInfo: AuthenticationInfo; @@ -69,7 +69,7 @@ export class ChtApi { username, sessionToken, }; - }; + } // workaround https://github.com/medic/cht-core/issues/8674 updateContactParent = async (parentId: string): Promise => { @@ -155,20 +155,28 @@ export class ChtApi { await axios.post(url, user, this.authorizationOptions()); }; - getParentAndSibling = async (parentId: string, contactType: ContactType): Promise<{ parent: any, sibling: any }> => { - const url = `${this.protocolAndHost}/medic/_design/medic/_view/contacts_by_depth?keys=[[%22${parentId}%22,0],[%22${parentId}%22,1]]&include_docs=true`; + getParentAndSibling = async (parentId: string, contactType: ContactType): Promise<{ parent: any; sibling: any }> => { + const url = `${this.protocolAndHost}/medic/_design/medic/_view/contacts_by_depth`; console.log('axios.get', url); - const resp = await axios.get(url, this.authorizationOptions()); + const resp = await axios.get(url, { + ...this.authorizationOptions(), + params: { + keys: JSON.stringify([ + [parentId, 0], + [parentId, 1] + ]), + include_docs: true, + }, + }); const docs = resp.data?.rows?.map((row: any) => row.doc) || []; const parentType = Config.getParentProperty(contactType).contact_type; const parent = docs.find((d: any) => d.contact_type === parentType); const sibling = docs.find((d: any) => d.contact_type === contactType.name); return { parent, sibling }; - } + }; getPlacesWithType = async (placeType: string) - : Promise => - { + : Promise => { const url = `${this.protocolAndHost}/medic/_design/medic-client/_view/contacts_by_type_freetext`; const params = { startkey: JSON.stringify([ placeType, 'name:']), @@ -216,7 +224,7 @@ function minify(doc: any): any { _id: doc._id, parent: minify(doc.parent), }; -}; +} function extractLineage(doc: any): string[] { if (doc?.parent?._id) { @@ -224,4 +232,4 @@ function extractLineage(doc: any): string[] { } return []; -}; \ No newline at end of file +} diff --git a/src/lib/move.ts b/src/lib/move.ts index f13953d2..706a0d51 100644 --- a/src/lib/move.ts +++ b/src/lib/move.ts @@ -1,10 +1,8 @@ -import _ from "lodash"; - -import { ContactType } from "../config"; -import SessionCache from "../services/session-cache"; -import { ChtApi } from "../lib/cht-api"; -import RemotePlaceResolver from "../lib/remote-place-resolver"; -import Place from "../services/place"; +import { ContactType } from '../config'; +import SessionCache from '../services/session-cache'; +import { ChtApi } from '../lib/cht-api'; +import RemotePlaceResolver from '../lib/remote-place-resolver'; +import Place from '../services/place'; export default class MoveLib { constructor() {} @@ -24,7 +22,7 @@ export default class MoveLib { } const { authInfo } = chtApi.chtSession; - const url = `http${authInfo.useHttp ? '' : 's'}://${chtApi.chtSession.username}:password@${authInfo.domain}` + const url = `http${authInfo.useHttp ? '' : 's'}://${chtApi.chtSession.username}:password@${authInfo.domain}`; return { command: `npx cht --url=${url} move-contacts upload-docs -- --contacts=${fromId} --parent=${toId}`, fromLineage, diff --git a/src/lib/remote-place-cache.ts b/src/lib/remote-place-cache.ts index 8a107ccc..deb11f35 100644 --- a/src/lib/remote-place-cache.ts +++ b/src/lib/remote-place-cache.ts @@ -1,5 +1,5 @@ -import Place from "../services/place"; -import { ChtApi, RemotePlace } from "./cht-api"; +import Place from '../services/place'; +import { ChtApi, RemotePlace } from './cht-api'; type RemotePlacesByType = { [key: string]: RemotePlace[]; @@ -13,8 +13,7 @@ export default class RemotePlaceCache { private static cache: RemotePlaceDatastore = {}; public static async getPlacesWithType(chtApi: ChtApi, placeType: string) - : Promise - { + : Promise { const domainStore = await RemotePlaceCache.getDomainStore(chtApi, placeType); return domainStore; } @@ -36,8 +35,7 @@ export default class RemotePlaceCache { } private static async getDomainStore(chtApi: ChtApi, placeType: string) - : Promise - { + : Promise { const { domain } = chtApi.chtSession.authInfo; const { cache: domainCache } = RemotePlaceCache; @@ -52,4 +50,4 @@ export default class RemotePlaceCache { return domainCache[domain][placeType]; } -} \ No newline at end of file +} diff --git a/src/lib/remote-place-resolver.ts b/src/lib/remote-place-resolver.ts index 40954be4..fc4effe2 100644 --- a/src/lib/remote-place-resolver.ts +++ b/src/lib/remote-place-resolver.ts @@ -1,10 +1,10 @@ -import _ from "lodash"; -import Place from "../services/place"; -import SessionCache from "../services/session-cache"; -import { RemotePlace, ChtApi } from "./cht-api"; -import { Config, ContactType, HierarchyConstraint } from "../config"; -import { Validation } from "./validation"; -import RemotePlaceCache from "./remote-place-cache"; +import _ from 'lodash'; +import Place from '../services/place'; +import SessionCache from '../services/session-cache'; +import { RemotePlace, ChtApi } from './cht-api'; +import { Config, ContactType, HierarchyConstraint } from '../config'; +import { Validation } from './validation'; +import RemotePlaceCache from './remote-place-cache'; type RemotePlaceMap = { [key: string]: RemotePlace }; @@ -13,7 +13,7 @@ export type PlaceResolverOptions = { }; export default class RemotePlaceResolver { - public static readonly NoResult: RemotePlace = { id: "na", name: "Place Not Found", type: 'invalid', lineage: [] }; + public static readonly NoResult: RemotePlace = { id: 'na', name: 'Place Not Found', type: 'invalid', lineage: [] }; public static readonly Multiple: RemotePlace = { id: 'multiple', name: 'multiple places', type: 'invalid', lineage: [] }; public static resolve = async ( @@ -25,15 +25,14 @@ export default class RemotePlaceResolver { for (const place of places) { await this.resolveOne(place, sessionCache, chtApi, options); } - } + }; public static resolveOne = async ( place: Place, sessionCache: SessionCache, chtApi: ChtApi, options?: PlaceResolverOptions - ) : Promise => - { + ) : Promise => { const topDownHierarchy = Config.getHierarchyWithReplacement(place.type, 'desc'); for (const hierarchyLevel of topDownHierarchy) { if (!place.hierarchyProperties[hierarchyLevel.property_name]) { @@ -69,11 +68,10 @@ export default class RemotePlaceResolver { } await RemotePlaceResolver.resolveAmbiguousParent(place); - } + }; private static resolveAmbiguousParent = async (place: Place) - : Promise => - { + : Promise => { const topDownHierarchy = Config.getHierarchyWithReplacement(place.type, 'desc'); const ambiguousHierarchies = place.resolvedHierarchy .map((remotePlace, index) => ({ @@ -105,7 +103,7 @@ export default class RemotePlaceResolver { } } } - } + }; } function getFuzzFunction(hierarchyLevel: HierarchyConstraint, contactType: ContactType) { @@ -121,8 +119,7 @@ async function findRemotePlacesInHierarchy( place: Place, hierarchyLevel: HierarchyConstraint, chtApi: ChtApi -) : Promise -{ +) : Promise { let searchPool = await RemotePlaceCache.getPlacesWithType(chtApi, hierarchyLevel.contact_type); const topDownHierarchy = Config.getHierarchyWithReplacement(place.type, 'desc'); for (const { level } of topDownHierarchy) { @@ -148,7 +145,7 @@ async function findRemotePlacesInHierarchy( } return searchPool; -}; +} function getSearchKeys(place: Place, searchPropertyName: string, fuzzFunction: (key: string) => string, fuzz: boolean) : string[] { @@ -165,7 +162,7 @@ function getSearchKeys(place: Place, searchPropertyName: string, fuzzFunction: ( return _.uniq(keys); } -function pickFromMapOptimistic(map: RemotePlaceMap, placeName: string, fuzzFunction: (key: string) => string,fuzz: boolean) +function pickFromMapOptimistic(map: RemotePlaceMap, placeName: string, fuzzFunction: (key: string) => string, fuzz: boolean) : RemotePlace | undefined { if (!placeName) { return; @@ -182,7 +179,13 @@ function pickFromMapOptimistic(map: RemotePlaceMap, placeName: string, fuzzFunct return optimisticResult || result || fuzzyResult; } -function findLocalPlaces(name: string, type: string, sessionCache: SessionCache, options: PlaceResolverOptions | undefined, fuzzFunction: (key: string) => string): RemotePlace | undefined { +function findLocalPlaces( + name: string, + type: string, + sessionCache: SessionCache, + options: PlaceResolverOptions | undefined, + fuzzFunction: (key: string) => string +): RemotePlace | undefined { let places = sessionCache.getPlaces({ type, nameExact: name }); if (options?.fuzz && !places.length) { @@ -222,4 +225,4 @@ function addKeyToMap(map: RemotePlaceMap, key: string, value: RemotePlace) { } map[lowercaseKey] = value; -}; \ No newline at end of file +} diff --git a/src/lib/search.ts b/src/lib/search.ts index 16b9ea89..899a6155 100644 --- a/src/lib/search.ts +++ b/src/lib/search.ts @@ -1,14 +1,20 @@ -import _ from "lodash"; -import SessionCache from "../services/session-cache"; -import { ChtApi, RemotePlace } from "./cht-api"; -import RemotePlaceCache from "./remote-place-cache"; -import RemotePlaceResolver from "./remote-place-resolver"; -import { Config, ContactType, HierarchyConstraint } from "../config"; -import Place from "../services/place"; +import _ from 'lodash'; +import SessionCache from '../services/session-cache'; +import { ChtApi, RemotePlace } from './cht-api'; +import RemotePlaceCache from './remote-place-cache'; +import RemotePlaceResolver from './remote-place-resolver'; +import { Config, ContactType, HierarchyConstraint } from '../config'; +import Place from '../services/place'; export default class SearchLib { - public static search = async (contactType: ContactType, formData: any, dataPrefix: string, hierarchyLevel: HierarchyConstraint, chtApi: ChtApi, sessionCache: SessionCache | undefined) - : Promise => { + public static search = async ( + contactType: ContactType, + formData: any, + dataPrefix: string, + hierarchyLevel: HierarchyConstraint, + chtApi: ChtApi, + sessionCache: SessionCache | undefined + ) : Promise => { const searchString: string = formData[`${dataPrefix}${hierarchyLevel?.property_name}`]?.toLowerCase(); const localResults: Place[] = sessionCache ? await getLocalResults(hierarchyLevel, sessionCache, searchString) : []; @@ -24,12 +30,12 @@ export default class SearchLib { } return searchResults; - } -}; + }; +} async function getLocalResults(hierarchyLevel: HierarchyConstraint, sessionCache: SessionCache, searchString: string) : Promise { - if (hierarchyLevel.level == 0) { + if (hierarchyLevel.level === 0) { return []; } @@ -42,8 +48,14 @@ async function getLocalResults(hierarchyLevel: HierarchyConstraint, sessionCache return _.sortBy(result, 'name'); } -async function getRemoteResults(searchString: string, hierarchyLevel: HierarchyConstraint, contactType: ContactType, formData: any, chtApi: ChtApi, dataPrefix: string) - : Promise { +async function getRemoteResults( + searchString: string, + hierarchyLevel: HierarchyConstraint, + contactType: ContactType, + formData: any, + chtApi: ChtApi, + dataPrefix: string +) : Promise { const topDownHierarchy = Config.getHierarchyWithReplacement(contactType, 'desc'); const allResults = await RemotePlaceCache.getPlacesWithType(chtApi, hierarchyLevel.contact_type); let remoteResults = allResults.filter(place => place.name.includes(searchString)); diff --git a/src/lib/validation.ts b/src/lib/validation.ts index 393ffc07..4c423e47 100644 --- a/src/lib/validation.ts +++ b/src/lib/validation.ts @@ -1,5 +1,3 @@ -import _ from 'lodash'; - import { Config, ContactProperty } from '../config'; import ValidatorString from './validator-string'; @@ -22,10 +20,10 @@ export interface IValidator { isValid(input: string, property? : ContactProperty) : boolean | string; format(input : string, property? : ContactProperty) : string; get defaultError(): string; -}; +} type ValidatorMap = { - [key: string]: IValidator, + [key: string]: IValidator; }; const TypeValidatorMap: ValidatorMap = { @@ -49,17 +47,16 @@ export class Validation { return result; } - public static format(place: Place): Place - { + public static format(place: Place): Place { const alterAllProperties = (propertiesToAlter: ContactProperty[], objectToAlter: any) => { for (const property of propertiesToAlter) { this.alterProperty(property, objectToAlter); } - } + }; alterAllProperties(place.type.contact_properties, place.contact.properties); alterAllProperties(place.type.place_properties, place.properties); - for (let hierarchy of Config.getHierarchyWithReplacement(place.type)) { + for (const hierarchy of Config.getHierarchyWithReplacement(place.type)) { this.alterProperty(hierarchy, place.hierarchyProperties); } @@ -91,7 +88,8 @@ export class Validation { resolution, hierarchyLevel.contact_type, data, - place.hierarchyProperties[levelUp]), + place.hierarchyProperties[levelUp] + ), }); } } @@ -100,7 +98,12 @@ export class Validation { return result; } - private static validateProperties(obj : any, properties : ContactProperty[], requiredProperties: ContactProperty[], prefix: string) : ValidationError[] { + private static validateProperties( + obj : any, + properties : ContactProperty[], + requiredProperties: ContactProperty[], + prefix: string + ) : ValidationError[] { const invalid: ValidationError[] = []; for (const property of properties) { @@ -108,7 +111,7 @@ export class Validation { const isRequired = requiredProperties.includes(property); if (value || isRequired) { - let isValid = Validation.isValid(property, value); + const isValid = Validation.isValid(property, value); if (isValid === false || typeof isValid === 'string') { invalid.push({ property_name: `${prefix}${property.property_name}`, @@ -126,8 +129,7 @@ export class Validation { try { const isValid = validator.isValid(value, property); return isValid === false ? property.errorDescription || validator.defaultError : isValid; - } - catch (e) { + } catch (e) { const error = `Error in isValid for '${property.type}': ${e}`; console.log(error); return error; @@ -151,7 +153,12 @@ export class Validation { return validator; } - private static describeInvalidRemotePlace(remotePlace: RemotePlace | undefined, friendlyType: string, searchStr?: string, requiredParent?: string): string { + private static describeInvalidRemotePlace( + remotePlace: RemotePlace | undefined, + friendlyType: string, + searchStr?: string, + requiredParent?: string + ): string { if (!searchStr) { return `Cannot find ${friendlyType} because the search string is empty`; } @@ -164,4 +171,4 @@ export class Validation { return `Cannot find '${friendlyType}' matching '${searchStr}'${requiredParentSuffix}`; } -}; +} diff --git a/src/lib/validator-gender.ts b/src/lib/validator-gender.ts index 1f3d6ee9..ddd3359b 100644 --- a/src/lib/validator-gender.ts +++ b/src/lib/validator-gender.ts @@ -19,11 +19,11 @@ export default class ValidatorGender implements IValidator { return `Must be either 'Male' or 'Female'`; } - private parseGenders(input: string): { gender: string, isValid: boolean } { + private parseGenders(input: string): { gender: string; isValid: boolean } { const isFemale = input?.match(/[fw]/i); const isMale = input?.match(/m(? word[0].toUpperCase() + word.slice(1)).join(' '); - return titleCased.replace(/ '/g, "'"); + return titleCased.replace(/ '/g, '\''); } -}; +} diff --git a/src/lib/validator-phone.ts b/src/lib/validator-phone.ts index 0601be62..4203039c 100644 --- a/src/lib/validator-phone.ts +++ b/src/lib/validator-phone.ts @@ -1,6 +1,6 @@ -import { CountryCode, parsePhoneNumber, isValidNumberForRegion } from "libphonenumber-js"; +import { CountryCode, parsePhoneNumber, isValidNumberForRegion } from 'libphonenumber-js'; -import { ContactProperty } from "../config"; +import { ContactProperty } from '../config'; import { IValidator } from './validation'; export default class ValidatorPhone implements IValidator { @@ -37,4 +37,4 @@ export default class ValidatorPhone implements IValidator { get defaultError(): string { return 'Not a valid regional phone number'; } -}; +} diff --git a/src/lib/validator-regex.ts b/src/lib/validator-regex.ts index a949fdb6..8ddb418c 100644 --- a/src/lib/validator-regex.ts +++ b/src/lib/validator-regex.ts @@ -1,4 +1,4 @@ -import { ContactProperty } from "../config"; +import { ContactProperty } from '../config'; import { IValidator } from './validation'; import ValidatorString from './validator-string'; @@ -28,4 +28,4 @@ export default class ValidatorRegex implements IValidator { get defaultError(): string { throw Error(`property of type regex - 'errorDescription' is required`); } -}; +} diff --git a/src/lib/validator-skip.ts b/src/lib/validator-skip.ts index f4612b52..eee1b311 100644 --- a/src/lib/validator-skip.ts +++ b/src/lib/validator-skip.ts @@ -1,7 +1,7 @@ import { IValidator } from './validation'; export default class ValidatorSkip implements IValidator { - isValid(input: string) : boolean | string { + isValid() : boolean | string { return true; } @@ -12,4 +12,4 @@ export default class ValidatorSkip implements IValidator { get defaultError(): string { throw 'never'; } -}; +} diff --git a/src/lib/validator-string.ts b/src/lib/validator-string.ts index 10a7de86..c700e418 100644 --- a/src/lib/validator-string.ts +++ b/src/lib/validator-string.ts @@ -16,4 +16,4 @@ export default class ValidatorString implements IValidator { get defaultError(): string { return 'Is Required'; } -}; +} diff --git a/src/plugins/services.ts b/src/plugins/services.ts index 12861ecf..14458036 100644 --- a/src/plugins/services.ts +++ b/src/plugins/services.ts @@ -1,12 +1,12 @@ -import { FastifyInstance } from "fastify"; -import fp from "fastify-plugin"; -import { UploadManager } from "../services/upload-manager"; +import { FastifyInstance } from 'fastify'; +import fp from 'fastify-plugin'; +import { UploadManager } from '../services/upload-manager'; async function services(fastify: FastifyInstance) { const uploadManager = new UploadManager(); - fastify.decorate("uploadManager", uploadManager); + fastify.decorate('uploadManager', uploadManager); } export default fp(services, { - name: "services", + name: 'services', }); diff --git a/src/routes/add-place.ts b/src/routes/add-place.ts index fb617671..b8c12de1 100644 --- a/src/routes/add-place.ts +++ b/src/routes/add-place.ts @@ -1,25 +1,24 @@ -import { FastifyInstance } from "fastify"; +import { FastifyInstance } from 'fastify'; -import { Config } from "../config"; -import { ChtApi } from "../lib/cht-api"; -import PlaceFactory from "../services/place-factory"; -import SessionCache from "../services/session-cache"; -import { PlaceUploadState } from "../services/place"; -import RemotePlaceResolver from "../lib/remote-place-resolver"; -import { UploadManager } from "../services/upload-manager"; -import RemotePlaceCache from "../lib/remote-place-cache"; +import { Config } from '../config'; +import { ChtApi } from '../lib/cht-api'; +import PlaceFactory from '../services/place-factory'; +import SessionCache from '../services/session-cache'; +import RemotePlaceResolver from '../lib/remote-place-resolver'; +import { UploadManager } from '../services/upload-manager'; +import RemotePlaceCache from '../lib/remote-place-cache'; export default async function addPlace(fastify: FastifyInstance) { - fastify.get("/add-place", async (req, resp) => { + fastify.get('/add-place', async (req, resp) => { const queryParams: any = req.query; const contactTypes = Config.contactTypes(); const contactType = queryParams.type ? Config.getContactType(queryParams.type) : contactTypes[contactTypes.length - 1]; - const op = queryParams.op || "new"; + const op = queryParams.op || 'new'; const tmplData = { - view: "add", + view: 'add', logo: Config.getLogoBase64(), session: req.chtSession, op, @@ -28,23 +27,23 @@ export default async function addPlace(fastify: FastifyInstance) { contactTypes, }; - return resp.view("src/public/app/view.html", tmplData); + return resp.view('src/public/app/view.html', tmplData); }); // you want to create a place? replace a contact? you'll have to go through me first - fastify.post("/place", async (req, resp) => { + fastify.post('/place', async (req, resp) => { const { op, type: placeType } = req.query as any; const contactType = Config.getContactType(placeType); const sessionCache: SessionCache = req.sessionCache; const chtApi = new ChtApi(req.chtSession); - if (op === "new" || op === "replace") { + if (op === 'new' || op === 'replace') { await PlaceFactory.createOne(req.body, contactType, sessionCache, chtApi); - resp.header("HX-Redirect", `/`); + resp.header('HX-Redirect', `/`); return; } - if (op === "bulk") { + if (op === 'bulk') { // read the date we uploaded const fileData = await req.file(); if (!fileData) { @@ -54,7 +53,7 @@ export default async function addPlace(fastify: FastifyInstance) { const csvBuf = await fileData.toBuffer(); await PlaceFactory.createBulk(csvBuf, contactType, sessionCache, chtApi); } catch (error) { - return fastify.view("src/public/place/bulk_create_form.html", { + return fastify.view('src/public/place/bulk_create_form.html', { contactType, errors: { message: error, @@ -63,27 +62,27 @@ export default async function addPlace(fastify: FastifyInstance) { } // back to places list - resp.header("HX-Redirect", `/`); + resp.header('HX-Redirect', `/`); return; } - throw new Error("unknown op"); + throw new Error('unknown op'); }); - fastify.get("/place/edit/:id", async (req, resp) => { + fastify.get('/place/edit/:id', async (req, resp) => { const params: any = req.params; const { id } = params; const sessionCache: SessionCache = req.sessionCache; const place = sessionCache.getPlace(id); if (!place || place.isCreated) { - throw new Error("unknown place or place is already created"); + throw new Error('unknown place or place is already created'); } const data = place.asFormData('hierarchy_'); const tmplData = { - view: "edit", - op: "edit", + view: 'edit', + op: 'edit', logo: Config.getLogoBase64(), hierarchy: Config.getHierarchyWithReplacement(place.type, 'desc'), place, @@ -93,11 +92,11 @@ export default async function addPlace(fastify: FastifyInstance) { data, }; - resp.header("HX-Push-Url", `/place/edit/${id}`); - return resp.view("src/public/app/view.html", tmplData); + resp.header('HX-Push-Url', `/place/edit/${id}`); + return resp.view('src/public/app/view.html', tmplData); }); - fastify.post("/place/edit/:id", async (req, resp) => { + fastify.post('/place/edit/:id', async (req, resp) => { const { id } = req.params as any; const data: any = req.body; const sessionCache: SessionCache = req.sessionCache; @@ -106,10 +105,10 @@ export default async function addPlace(fastify: FastifyInstance) { await PlaceFactory.editOne(id, data, sessionCache, chtApi); // back to places list - resp.header("HX-Redirect", `/`); + resp.header('HX-Redirect', `/`); }); - fastify.post("/place/refresh/:id", async (req, resp) => { + fastify.post('/place/refresh/:id', async (req) => { const { id } = req.params as any; const sessionCache: SessionCache = req.sessionCache; const place = sessionCache.getPlace(id); @@ -122,10 +121,10 @@ export default async function addPlace(fastify: FastifyInstance) { await RemotePlaceResolver.resolveOne(place, sessionCache, chtApi, { fuzz: true }); place.validate(); - fastify.uploadManager.refresh(req.sessionCache, PlaceUploadState.PENDING); + fastify.uploadManager.refresh(req.sessionCache); }); - fastify.post("/place/upload/:id", async (req, resp) => { + fastify.post('/place/upload/:id', async (req) => { const { id } = req.params as any; const sessionCache: SessionCache = req.sessionCache; const place = sessionCache.getPlace(id); @@ -136,13 +135,13 @@ export default async function addPlace(fastify: FastifyInstance) { const chtApi = new ChtApi(req.chtSession); const uploadManager: UploadManager = fastify.uploadManager; await uploadManager.doUpload([place], chtApi); - fastify.uploadManager.refresh(req.sessionCache, PlaceUploadState.PENDING); + fastify.uploadManager.refresh(req.sessionCache); }); - fastify.post("/place/remove/:id", async (req, resp) => { + fastify.post('/place/remove/:id', async (req) => { const { id } = req.params as any; const sessionCache: SessionCache = req.sessionCache; sessionCache.removePlace(id); - fastify.uploadManager.refresh(req.sessionCache, PlaceUploadState.PENDING); + fastify.uploadManager.refresh(req.sessionCache); }); } diff --git a/src/routes/app.ts b/src/routes/app.ts index a28019c0..8b15409a 100644 --- a/src/routes/app.ts +++ b/src/routes/app.ts @@ -1,14 +1,14 @@ -import { FastifyInstance } from "fastify"; -import { Config } from "../config"; -import SessionCache from "../services/session-cache"; +import { FastifyInstance } from 'fastify'; +import { Config } from '../config'; +import SessionCache from '../services/session-cache'; import { UploadManager } from '../services/upload-manager'; -import { PlaceUploadState } from "../services/place"; -import { ChtApi } from "../lib/cht-api"; -import RemotePlaceResolver from "../lib/remote-place-resolver"; -import RemotePlaceCache from "../lib/remote-place-cache"; +import { PlaceUploadState } from '../services/place'; +import { ChtApi } from '../lib/cht-api'; +import RemotePlaceResolver from '../lib/remote-place-resolver'; +import RemotePlaceCache from '../lib/remote-place-cache'; export default async function sessionCache(fastify: FastifyInstance) { - fastify.get("/", async (req, resp) => { + fastify.get('/', async (req, resp) => { const contactTypes = Config.contactTypes(); const { op = 'table', @@ -29,7 +29,7 @@ export default async function sessionCache(fastify: FastifyInstance) { }); const tmplData = { - view: "list", + view: 'list', logo: Config.getLogoBase64(), contactType, contactTypes: placeData, @@ -41,16 +41,16 @@ export default async function sessionCache(fastify: FastifyInstance) { op, }; - return resp.view("src/public/app/view.html", tmplData); + return resp.view('src/public/app/view.html', tmplData); }); - fastify.post("/app/remove-all", async (req, resp) => { + fastify.post('/app/remove-all', async (req) => { const sessionCache: SessionCache = req.sessionCache; sessionCache.removeAll(); - fastify.uploadManager.refresh(req.sessionCache, PlaceUploadState.PENDING); + fastify.uploadManager.refresh(req.sessionCache); }); - fastify.post("/app/refresh-all", async (req, resp) => { + fastify.post('/app/refresh-all', async (req) => { const sessionCache: SessionCache = req.sessionCache; const chtApi = new ChtApi(req.chtSession); @@ -64,7 +64,7 @@ export default async function sessionCache(fastify: FastifyInstance) { }); // initiates place creation via the job manager - fastify.post("/app/apply-changes", async (req) => { + fastify.post('/app/apply-changes', async (req) => { const uploadManager: UploadManager = fastify.uploadManager; const sessionCache: SessionCache = req.sessionCache; diff --git a/src/routes/authentication.ts b/src/routes/authentication.ts index a6ebf6fc..f30c8286 100644 --- a/src/routes/authentication.ts +++ b/src/routes/authentication.ts @@ -2,7 +2,7 @@ import { FastifyInstance, FastifyRequest } from 'fastify'; import Auth from '../lib/authentication'; import { ChtApi } from '../lib/cht-api'; -import { Config } from "../config"; +import { Config } from '../config'; export default async function authentication(fastify: FastifyInstance) { const unauthenticatedOptions = { @@ -33,11 +33,8 @@ export default async function authentication(fastify: FastifyInstance) { let session; try { session = await ChtApi.createSession(authInfo, username, password); - if (!session.sessionToken) { - throw 'login fail';`` - } } catch (e: any) { - return resp.view("src/public/auth/authentication_form.html", { + return resp.view('src/public/auth/authentication_form.html', { domains: Config.getDomains, errors: true, }); @@ -52,11 +49,11 @@ export default async function authentication(fastify: FastifyInstance) { secure: true }); - resp.header("HX-Redirect", `/`); + resp.header('HX-Redirect', `/`); }); fastify.get('/_healthz', unauthenticatedOptions, () => { return 'OK'; }); -}; +} diff --git a/src/routes/events.ts b/src/routes/events.ts index 359640e0..1d8215be 100644 --- a/src/routes/events.ts +++ b/src/routes/events.ts @@ -1,11 +1,11 @@ -import { FastifyInstance } from "fastify"; +import { FastifyInstance } from 'fastify'; -import { Config } from "../config"; -import { PlaceUploadState } from "../services/place"; -import SessionCache, { SessionCacheUploadState } from "../services/session-cache"; +import { Config } from '../config'; +import { PlaceUploadState } from '../services/place'; +import SessionCache, { SessionCacheUploadState } from '../services/session-cache'; export default async function events(fastify: FastifyInstance) { - fastify.get("/events/places_list", async (req, resp) => { + fastify.get('/events/places_list', async (req, resp) => { const sessionCache: SessionCache = req.sessionCache; const contactTypes = Config.contactTypes(); const placeData = contactTypes.map((item) => { @@ -15,29 +15,29 @@ export default async function events(fastify: FastifyInstance) { hierarchy: Config.getHierarchyWithReplacement(item, 'desc'), }; }); - return resp.view("src/public/place/list.html", { + return resp.view('src/public/place/list.html', { contactTypes: placeData, session: req.chtSession, }); }); - fastify.get("/events/connection", async (req, resp) => { + fastify.get('/events/connection', async (req, resp) => { const { uploadManager } = fastify; resp.hijack(); const placesChangeListener = (arg: PlaceUploadState) => { - resp.sse({ event: "places_state_change", data: arg }); + resp.sse({ event: 'places_state_change', data: arg }); }; - uploadManager.on("places_state_change", placesChangeListener); + uploadManager.on('places_state_change', placesChangeListener); const sessionStateListener = (arg: SessionCacheUploadState) => { - resp.sse({ event: "session_state_change", data: arg }); + resp.sse({ event: 'session_state_change', data: arg }); }; - uploadManager.on("session_state_change", sessionStateListener); + uploadManager.on('session_state_change', sessionStateListener); - req.socket.on("close", () => { - uploadManager.removeListener("places_state_change", placesChangeListener); - uploadManager.removeListener("session_state_change", sessionStateListener); + req.socket.on('close', () => { + uploadManager.removeListener('places_state_change', placesChangeListener); + uploadManager.removeListener('session_state_change', sessionStateListener); }); }); } diff --git a/src/routes/files.ts b/src/routes/files.ts index 70ca16a0..cadd2091 100644 --- a/src/routes/files.ts +++ b/src/routes/files.ts @@ -1,12 +1,12 @@ -import _ from "lodash"; -import { FastifyInstance } from "fastify"; -import { transform, stringify } from "csv/sync"; -import { Config } from "../config"; -import SessionCache from "../services/session-cache"; -import Place from "../services/place"; +import _ from 'lodash'; +import { FastifyInstance } from 'fastify'; +import { transform, stringify } from 'csv/sync'; +import { Config } from '../config'; +import SessionCache from '../services/session-cache'; +import Place from '../services/place'; export default async function files(fastify: FastifyInstance) { - fastify.get("/files/template/:placeType", async (req, resp) => { + fastify.get('/files/template/:placeType', async (req) => { const params: any = req.params; const placeType = params.placeType; const placeTypeConfig = Config.getContactType(placeType); @@ -20,7 +20,7 @@ export default async function files(fastify: FastifyInstance) { return stringify([columns]); }); - fastify.get("/files/credentials", async (req, resp) => { + fastify.get('/files/credentials', async (req) => { const sessionCache: SessionCache = req.sessionCache; const places = sessionCache.getPlaces(); const refinedRecords = transform(places, (place: Place) => { diff --git a/src/routes/move.ts b/src/routes/move.ts index c01268a2..5af21d11 100644 --- a/src/routes/move.ts +++ b/src/routes/move.ts @@ -1,21 +1,21 @@ -import _ from "lodash"; +import _ from 'lodash'; -import { Config, ContactType } from "../config"; -import { ChtApi } from "../lib/cht-api"; -import { FastifyInstance } from "fastify"; -import MoveLib from "../lib/move"; -import SessionCache from "../services/session-cache"; +import { Config, ContactType } from '../config'; +import { ChtApi } from '../lib/cht-api'; +import { FastifyInstance } from 'fastify'; +import MoveLib from '../lib/move'; +import SessionCache from '../services/session-cache'; export default async function sessionCache(fastify: FastifyInstance) { - fastify.get("/move/:placeType", async (req, resp) => { + fastify.get('/move/:placeType', async (req, resp) => { const params: any = req.params; const placeType = params.placeType; const contactTypes = Config.contactTypes(); const contactType = Config.getContactType(placeType); const tmplData = { - view: "move", - op: "move", + view: 'move', + op: 'move', logo: Config.getLogoBase64(), contactTypes, contactType, @@ -23,10 +23,10 @@ export default async function sessionCache(fastify: FastifyInstance) { ...moveViewModel(contactType), }; - return resp.view("src/public/app/view.html", tmplData); + return resp.view('src/public/app/view.html', tmplData); }); - fastify.post("/move", async (req, resp) => { + fastify.post('/move', async (req, resp) => { const formData:any = req.body; const sessionCache: SessionCache = req.sessionCache; @@ -35,11 +35,11 @@ export default async function sessionCache(fastify: FastifyInstance) { try { const tmplData = await MoveLib.move(formData, contactType, sessionCache, chtApi); - return resp.view("src/public/components/move_result.html", tmplData); + return resp.view('src/public/components/move_result.html', tmplData); } catch (e: any) { const tmplData = { - view: "move", - op: "move", + view: 'move', + op: 'move', contactTypes: Config.contactTypes(), session: req.chtSession, data: formData, @@ -48,7 +48,7 @@ export default async function sessionCache(fastify: FastifyInstance) { error: e.toString(), }; - return resp.view("src/public/place/move_form.html", tmplData); + return resp.view('src/public/place/move_form.html', tmplData); } }); } @@ -67,4 +67,4 @@ export function moveViewModel(contactType: ContactType) { fromHierarchy, toHierarchy, }; -}; +} diff --git a/src/routes/search.ts b/src/routes/search.ts index 7a1d687e..97c2d6f5 100644 --- a/src/routes/search.ts +++ b/src/routes/search.ts @@ -1,12 +1,11 @@ -import _ from "lodash"; -import { FastifyInstance } from "fastify"; +import { FastifyInstance } from 'fastify'; -import { Config } from "../config"; -import { ChtApi, RemotePlace } from "../lib/cht-api"; -import SessionCache from "../services/session-cache"; -import SearchLib from "../lib/search"; +import { Config } from '../config'; +import { ChtApi, RemotePlace } from '../lib/cht-api'; +import SessionCache from '../services/session-cache'; +import SearchLib from '../lib/search'; -import { moveViewModel } from "./move"; +import { moveViewModel } from './move'; export default async function place(fastify: FastifyInstance) { // returns search results dropdown @@ -16,9 +15,9 @@ export default async function place(fastify: FastifyInstance) { op, place_id: placeId, type, - level, prefix: dataPrefix } = queryParams; + const level = parseInt(queryParams.level); const data: any = req.body; @@ -30,13 +29,13 @@ export default async function place(fastify: FastifyInstance) { } const chtApi = new ChtApi(req.chtSession); - const hierarchyLevel = Config.getHierarchyWithReplacement(contactType).find(hierarchy => hierarchy.level == level); + const hierarchyLevel = Config.getHierarchyWithReplacement(contactType).find(hierarchy => hierarchy.level === level); if (!hierarchyLevel) { throw Error(`not hierarchy constraint at ${level}`); } const searchResults: RemotePlace[] = await SearchLib.search(contactType, data, dataPrefix, hierarchyLevel, chtApi, sessionCache); - return resp.view("src/public/components/search_results.html", { + return resp.view('src/public/components/search_results.html', { op, place, prefix: dataPrefix, @@ -46,16 +45,16 @@ export default async function place(fastify: FastifyInstance) { }); // when we select a place from search results - fastify.post("/search/select", async (req, resp) => { + fastify.post('/search/select', async (req, resp) => { const data: any = req.body; - const params: any = req.query; + const queryParams: any = req.query; const { op = 'new', place_id: placeId, result_name: resultName, - level, prefix: dataPrefix, - } = params; + } = queryParams; + const level = parseInt(queryParams.level); const sessionCache: SessionCache = req.sessionCache; const place = sessionCache.getPlace(placeId); @@ -65,7 +64,7 @@ export default async function place(fastify: FastifyInstance) { const contactType = Config.getContactType(data.place_type); const moveModel = moveViewModel(contactType); - const hierarchyLevel = Config.getHierarchyWithReplacement(contactType).find(hierarchy => hierarchy.level == level); + const hierarchyLevel = Config.getHierarchyWithReplacement(contactType).find(hierarchy => hierarchy.level === level); if (!hierarchyLevel) { throw Error(`not hierarchy constraint at ${level}`); } @@ -90,6 +89,6 @@ export default async function place(fastify: FastifyInstance) { tmplData.backend = `/move`; } - return resp.view("src/public/app/form_switch.html", tmplData); + return resp.view('src/public/app/form_switch.html', tmplData); }); } diff --git a/src/server.ts b/src/server.ts index 693c3196..bbfa6e06 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,15 +1,15 @@ -import Fastify, { FastifyInstance, FastifyReply, FastifyRequest, FastifyServerOptions } from "fastify"; -import autoload from "@fastify/autoload"; -import cookie from "@fastify/cookie"; -import formbody from "@fastify/formbody"; -import multipart from "@fastify/multipart"; -import view from "@fastify/view"; -import { Liquid } from "liquidjs"; -import { FastifySSEPlugin } from "fastify-sse-v2"; -import path from "path"; +import Fastify, { FastifyInstance, FastifyReply, FastifyRequest, FastifyServerOptions } from 'fastify'; +import autoload from '@fastify/autoload'; +import cookie from '@fastify/cookie'; +import formbody from '@fastify/formbody'; +import multipart from '@fastify/multipart'; +import view from '@fastify/view'; +import { Liquid } from 'liquidjs'; +import { FastifySSEPlugin } from 'fastify-sse-v2'; +import path from 'path'; import Auth from './lib/authentication'; -import SessionCache from "./services/session-cache"; +import SessionCache from './services/session-cache'; const build = (opts: FastifyServerOptions): FastifyInstance => { const fastify = Fastify(opts); @@ -19,14 +19,14 @@ const build = (opts: FastifyServerOptions): FastifyInstance => { fastify.register(cookie); fastify.register(view, { engine: { - liquid: new Liquid({ extname: ".html", root: "src/public", jekyllInclude: true, dynamicPartials: true }), + liquid: new Liquid({ extname: '.html', root: 'src/public', jekyllInclude: true, dynamicPartials: true }), }, }); fastify.register(autoload, { - dir: path.join(__dirname, "plugins"), + dir: path.join(__dirname, 'plugins'), }); fastify.register(autoload, { - dir: path.join(__dirname, "routes"), + dir: path.join(__dirname, 'routes'), }); Auth.assertEnvironmentSetup(); diff --git a/src/services/contact.ts b/src/services/contact.ts index c6e0d47e..437cd5ea 100644 --- a/src/services/contact.ts +++ b/src/services/contact.ts @@ -1,5 +1,5 @@ -import { v4 as uuidv4 } from "uuid"; -import { Config, ContactType } from "../config"; +import { v4 as uuidv4 } from 'uuid'; +import { Config, ContactType } from '../config'; export default class Contact { public id: string; @@ -18,4 +18,4 @@ export default class Contact { const nameProperty = Config.getPropertyWithName(this.type.contact_properties, 'name'); return this.properties[nameProperty.property_name]; } -}; +} diff --git a/src/services/place-factory.ts b/src/services/place-factory.ts index 739b8dfe..acd4baf7 100644 --- a/src/services/place-factory.ts +++ b/src/services/place-factory.ts @@ -1,17 +1,14 @@ -import _ from "lodash"; -import { once } from "events"; -import { parse } from "csv"; +import { parse } from 'csv'; -import { ChtApi } from "../lib/cht-api"; -import { Config, ContactType } from "../config"; -import Place from "./place"; -import SessionCache from "./session-cache"; -import RemotePlaceResolver from "../lib/remote-place-resolver"; +import { ChtApi } from '../lib/cht-api'; +import { Config, ContactType } from '../config'; +import Place from './place'; +import SessionCache from './session-cache'; +import RemotePlaceResolver from '../lib/remote-place-resolver'; export default class PlaceFactory { public static async createBulk(csvBuffer: Buffer, contactType: ContactType, sessionCache: SessionCache, chtApi: ChtApi) - : Promise - { + : Promise { const places = await PlaceFactory.loadPlacesFromCsv(csvBuffer, contactType); const validateAll = () => places.forEach(p => p.validate()); @@ -19,11 +16,10 @@ export default class PlaceFactory { validateAll(); sessionCache.savePlaces(...places); return places; - }; + } public static createOne = async (formData: any, contactType: ContactType, sessionCache: SessionCache, chtApi: ChtApi) - : Promise => - { + : Promise => { const place = new Place(contactType); place.setPropertiesFromFormData(formData, 'hierarchy_'); @@ -34,11 +30,10 @@ export default class PlaceFactory { }; public static editOne = async (placeId: string, formData: any, sessionCache: SessionCache, chtApi: ChtApi) - : Promise => - { + : Promise => { const place = sessionCache.getPlace(placeId); if (!place || place.isCreated) { - throw new Error("unknown place or place has already been created"); + throw new Error('unknown place or place has already been created'); } place.setPropertiesFromFormData(formData, 'hierarchy_'); @@ -51,14 +46,14 @@ export default class PlaceFactory { private static async loadPlacesFromCsv(csvBuffer: Buffer, contactType: ContactType) : Promise { const csvColumns: string[] = []; const places: Place[] = []; - const parser = parse(csvBuffer, { delimiter: ",", trim: true, skip_empty_lines: true }); + const parser = parse(csvBuffer, { delimiter: ',', trim: true, skip_empty_lines: true }); let count = 0; for await (const row of parser) { if (count === 0) { const missingColumns = Config.getRequiredColumns(contactType, true).map(p => p.friendly_name) .filter((csvName) => !row.includes(csvName)); if (missingColumns.length > 0) { - throw new Error(`Missing columns: ${missingColumns.join(", ")}`); + throw new Error(`Missing columns: ${missingColumns.join(', ')}`); } csvColumns.push(...row); } else { @@ -77,8 +72,8 @@ export default class PlaceFactory { } places.push(place); } - count++ + count++; } return places; } -} \ No newline at end of file +} diff --git a/src/services/place.ts b/src/services/place.ts index 81e212a6..998c281e 100644 --- a/src/services/place.ts +++ b/src/services/place.ts @@ -1,13 +1,13 @@ -import _ from "lodash"; -import Contact from "./contact"; -import { v4 as uuidv4 } from "uuid"; +import _ from 'lodash'; +import Contact from './contact'; +import { v4 as uuidv4 } from 'uuid'; -import { Config, ContactProperty, ContactType } from "../config"; -import { PlacePayload, RemotePlace } from "../lib/cht-api"; -import { Validation } from "../lib/validation"; +import { Config, ContactProperty, ContactType } from '../config'; +import { PlacePayload, RemotePlace } from '../lib/cht-api'; +import { Validation } from '../lib/validation'; // can't use package.json because of rootDir in ts import { version as appVersion } from '../package.json'; -import RemotePlaceResolver from "../lib/remote-place-resolver"; +import RemotePlaceResolver from '../lib/remote-place-resolver'; export type UserCreationDetails = { username?: string; @@ -18,12 +18,12 @@ export type UserCreationDetails = { }; export enum PlaceUploadState { - SUCCESS = "success", - FAILURE = "failure", - PENDING = "pending", - SCHEDULED = "scheduled", - IN_PROGESS = "in_progress", -}; + SUCCESS = 'success', + FAILURE = 'failure', + PENDING = 'pending', + SCHEDULED = 'scheduled', + IN_PROGESS = 'in_progress', +} const PLACE_PREFIX = 'place_'; const CONTACT_PREFIX = 'contact_'; @@ -141,19 +141,19 @@ export default class Place { return { ...filteredProperties(this.properties), _id: this.isReplacement ? this.resolvedHierarchy[0]?.id : this.id, - type: "contact", + type: 'contact', contact_type: this.type.name, parent: this.resolvedHierarchy[1]?.id, user_attribution, contact: { ...filteredProperties(this.contact.properties), name: this.contact.name, - type: "contact", + type: 'contact', contact_type: this.contact.type.contact_type, user_attribution, } }; - }; + } public asRemotePlace() : RemotePlace { const isHierarchyValid = !this.resolvedHierarchy.find(h => h?.type === 'invalid'); @@ -187,7 +187,7 @@ export default class Place { public validate(): void { const errors = Validation.getValidationErrors(this); this.validationErrors = {}; - for (let error of errors) { + for (const error of errors) { this.validationErrors[error.property_name] = error.description; } @@ -226,4 +226,4 @@ export default class Place { public get isCreated(): boolean { return !!this.creationDetails.password; } -}; +} diff --git a/src/services/session-cache.ts b/src/services/session-cache.ts index 500df21a..12a59cf2 100644 --- a/src/services/session-cache.ts +++ b/src/services/session-cache.ts @@ -1,9 +1,7 @@ -import _ from "lodash"; +import Place, { PlaceUploadState } from './place'; +import { ChtSession } from '../lib/cht-api'; -import Place, { PlaceUploadState } from "./place"; -import { ChtSession } from "../lib/cht-api"; - -export type SessionCacheUploadState = "in_progress" | "done" | "pending"; +export type SessionCacheUploadState = 'in_progress' | 'done' | 'pending'; export default class SessionCache { private static caches: Map = new Map(); @@ -33,12 +31,12 @@ export default class SessionCache { public getPlace = (id: string): Place | undefined => this.places[id]; public getPlaces = (options?: { - type?: string, - state?: PlaceUploadState, - created?: boolean, - id?: string, - nameExact?: string, - nameIncludes?: string, + type?: string; + state?: PlaceUploadState; + created?: boolean; + id?: string; + nameExact?: string; + nameIncludes?: string; }) : Place[] => { return Object.values(this.places) .filter(p => !options?.type || p.type.name === options.type) @@ -46,8 +44,7 @@ export default class SessionCache { .filter(p => !options?.id || p.id === options.id) .filter(p => !options?.nameExact || p.name === options.nameExact) .filter(p => !options?.nameIncludes || p.name.toLowerCase().includes(options.nameIncludes.toLowerCase())) - .filter(p => options?.created === undefined || !!p.isCreated === options.created) - ; + .filter(p => options?.created === undefined || !!p.isCreated === options.created); }; public removePlace = (placeId: string): void => { @@ -56,9 +53,9 @@ export default class SessionCache { } delete this.places[placeId]; - } + }; public removeAll = (): void => { this.places = {}; - } + }; } diff --git a/src/services/upload-manager.ts b/src/services/upload-manager.ts index ebe4ea9c..bf372b62 100644 --- a/src/services/upload-manager.ts +++ b/src/services/upload-manager.ts @@ -1,13 +1,13 @@ -import EventEmitter from "events"; -import { ChtApi, PlacePayload } from "../lib/cht-api"; +import EventEmitter from 'events'; +import { ChtApi, PlacePayload } from '../lib/cht-api'; -import Place, { PlaceUploadState } from "./place"; -import { UserPayload } from "./user-payload"; -import SessionCache, { SessionCacheUploadState } from "./session-cache"; -import { UploadReplacementPlace } from "./upload.replacement"; -import { UploadNewPlace } from "./upload.new"; -import { Config } from "../config"; -import RemotePlaceCache from "../lib/remote-place-cache"; +import Place, { PlaceUploadState } from './place'; +import { UserPayload } from './user-payload'; +import SessionCache, { SessionCacheUploadState } from './session-cache'; +import { UploadReplacementPlace } from './upload.replacement'; +import { UploadNewPlace } from './upload.new'; +import { Config } from '../config'; +import RemotePlaceCache from '../lib/remote-place-cache'; const UPLOAD_BATCH_SIZE = 10; @@ -78,7 +78,7 @@ export class UploadManager extends EventEmitter { place.uploadError = errorDetails; this.eventedPlaceStateChange(place, PlaceUploadState.FAILURE); } - }; + } public refresh(sessionCache: SessionCache) { this.emit('session_state_change', sessionCache.state); @@ -99,7 +99,7 @@ export class UploadManager extends EventEmitter { this.emit('places_state_change', state); }; -}; +} async function tryCreateUser (userPayload: UserPayload, chtApi: ChtApi): Promise<{ username: string; password: string }> { for (let retryCount = 0; retryCount < 5; ++retryCount) { @@ -112,7 +112,7 @@ async function tryCreateUser (userPayload: UserPayload, chtApi: ChtApi): Promise } const msg = err.response?.data?.error?.message || err.response?.data; - console.error("createUser retry because", msg); + console.error('createUser retry because', msg); if (msg?.includes('already taken')) { userPayload.makeUsernameMoreComplex(); continue; @@ -127,5 +127,5 @@ async function tryCreateUser (userPayload: UserPayload, chtApi: ChtApi): Promise } } - throw new Error("could not create user " + userPayload.contact); -}; + throw new Error('could not create user ' + userPayload.contact); +} diff --git a/src/services/upload.new.ts b/src/services/upload.new.ts index fda9a8d6..95b60f0a 100644 --- a/src/services/upload.new.ts +++ b/src/services/upload.new.ts @@ -1,6 +1,6 @@ -import { ChtApi, PlacePayload } from "../lib/cht-api"; -import Place from "./place"; -import { Uploader } from "./upload-manager"; +import { ChtApi, PlacePayload } from '../lib/cht-api'; +import Place from './place'; +import { Uploader } from './upload-manager'; export class UploadNewPlace implements Uploader { private readonly chtApi: ChtApi; @@ -9,9 +9,9 @@ export class UploadNewPlace implements Uploader { this.chtApi = chtApi; } - handleContact = async function (payload: PlacePayload): Promise { + handleContact = async function (): Promise { return; - } + }; handlePlacePayload = async (place: Place, payload: PlacePayload): Promise => { return await this.chtApi.createPlace(payload); @@ -27,4 +27,4 @@ export class UploadNewPlace implements Uploader { const contactId = await this.chtApi.updateContactParent(placeId); place.creationDetails.contactId = contactId; }; -}; +} diff --git a/src/services/upload.replacement.ts b/src/services/upload.replacement.ts index f9fcd3d4..fb4e7dcd 100644 --- a/src/services/upload.replacement.ts +++ b/src/services/upload.replacement.ts @@ -1,6 +1,6 @@ -import { ChtApi, PlacePayload } from "../lib/cht-api"; -import Place from "./place"; -import { Uploader } from "./upload-manager"; +import { ChtApi, PlacePayload } from '../lib/cht-api'; +import Place from './place'; +import { Uploader } from './upload-manager'; export class UploadReplacementPlace implements Uploader { private readonly chtApi: ChtApi; @@ -11,7 +11,7 @@ export class UploadReplacementPlace implements Uploader { handleContact = async (payload: PlacePayload): Promise => { return await this.chtApi.createContact(payload); - } + }; handlePlacePayload = async (place: Place, payload: PlacePayload): Promise => { const contactId = place.creationDetails?.contactId; @@ -33,4 +33,4 @@ export class UploadReplacementPlace implements Uploader { const contactId = await this.chtApi.updateContactParent(placeId); place.creationDetails.contactId = contactId; }; -}; +} diff --git a/src/services/user-payload.ts b/src/services/user-payload.ts index c3011159..2493b796 100644 --- a/src/services/user-payload.ts +++ b/src/services/user-payload.ts @@ -1,5 +1,5 @@ -import * as crypto from "crypto"; -import Place from "./place"; +import * as crypto from 'crypto'; +import Place from './place'; export class UserPayload { public password: string; @@ -32,13 +32,12 @@ export class UserPayload { private generatePassword(): string { const LENGTH = 9; // CHT requires 8 minimum + special characters const ELIGIBLE_CHARACTERS = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,"; + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,'; - const pickRandomCharacter = () => - ELIGIBLE_CHARACTERS.charAt( - Math.floor(Math.random() * ELIGIBLE_CHARACTERS.length) - ); - const characters = Array(LENGTH).fill("").map(pickRandomCharacter); - return characters.join(""); + const pickRandomCharacter = () => ELIGIBLE_CHARACTERS.charAt( + Math.floor(Math.random() * ELIGIBLE_CHARACTERS.length) + ); + const characters = Array(LENGTH).fill('').map(pickRandomCharacter); + return characters.join(''); } } diff --git a/src/types/fastify/index.d.ts b/src/types/fastify/index.d.ts index 738e9df9..d1eeb47a 100644 --- a/src/types/fastify/index.d.ts +++ b/src/types/fastify/index.d.ts @@ -1,8 +1,8 @@ -import { FastifyInstance } from "fastify"; -import { ChtSession } from "../../lib/cht-api"; -import SessionCache from "../../services/session-cache"; +import { ChtSession } from '../../lib/cht-api'; +import SessionCache from '../../services/session-cache'; +import { UploadManager } from '../../services/upload-manager'; -declare module "fastify" { +declare module 'fastify' { interface FastifyInstance { uploadManager: UploadManager; } diff --git a/test/lib/move.spec.ts b/test/lib/move.spec.ts index bc77b70a..6243a1a3 100644 --- a/test/lib/move.spec.ts +++ b/test/lib/move.spec.ts @@ -4,7 +4,7 @@ import { Config } from '../../src/config'; import SessionCache from '../../src/services/session-cache'; import { mockChtApi } from '../mocks'; -import chaiAsPromised from "chai-as-promised"; +import chaiAsPromised from 'chai-as-promised'; Chai.use(chaiAsPromised); const { expect } = Chai; @@ -71,7 +71,7 @@ describe('lib/move', () => { const sessionCache = new SessionCache(); const actual = MoveLib.move(formData, contactType, sessionCache, chtApi()); - await expect(actual).to.eventually.be.rejectedWith("Cannot find 'b_sub_county' matching 'invalid sub'"); + await expect(actual).to.eventually.be.rejectedWith('Cannot find \'b_sub_county\' matching \'invalid sub\''); }); }); diff --git a/test/lib/search.spec.ts b/test/lib/search.spec.ts index 3883e725..9e42af3f 100644 --- a/test/lib/search.spec.ts +++ b/test/lib/search.spec.ts @@ -1,10 +1,10 @@ import { expect } from 'chai'; import sinon from 'sinon'; -import { ChtApi, RemotePlace } from '../../src/lib/cht-api'; +import { RemotePlace } from '../../src/lib/cht-api'; import RemotePlaceCache from '../../src/lib/remote-place-cache'; -import SearchLib from "../../src/lib/search"; -import { mockPlace, mockSimpleContactType, mockValidContactType } from '../mocks'; +import SearchLib from '../../src/lib/search'; +import { mockValidContactType } from '../mocks'; import SessionCache from '../../src/services/session-cache'; import { Config } from '../../src/config'; diff --git a/test/lib/validation.spec.ts b/test/lib/validation.spec.ts index a29ecc4e..980f31be 100644 --- a/test/lib/validation.spec.ts +++ b/test/lib/validation.spec.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { Validation } from '../../src/lib/validation'; -import { mockSimpleContactType, mockPlace, expectInvalidProperties } from '../mocks'; +import { mockSimpleContactType, mockPlace } from '../mocks'; type Scenario = { type: string; @@ -32,7 +32,7 @@ const scenarios: Scenario[] = [ { type: 'name', prop: 'abc', isValid: true, altered: 'Abc' }, { type: 'name', prop: 'a b c', isValid: true, altered: 'A B C' }, { type: 'name', prop: 'WELDON KO(E)CH \n', isValid: true, altered: 'Weldon Ko(e)ch' }, - { type: 'name', prop: "S 'am 's", isValid: true, altered: "S'am's" }, + { type: 'name', prop: 'S \'am \'s', isValid: true, altered: 'S\'am\'s' }, { type: 'name', prop: 'KYAMBOO/KALILUNI', isValid: true, altered: 'Kyamboo / Kaliluni' }, { type: 'name', prop: 'NZATANI / ILALAMBYU', isValid: true, altered: 'Nzatani / Ilalambyu' }, { type: 'name', prop: 'Sam\'s CHU', propertyParameter: ['CHU', 'Comm Unit'], isValid: true, altered: 'Sam\'s' }, diff --git a/test/mocks.ts b/test/mocks.ts index d9ddaa51..e7c1207d 100644 --- a/test/mocks.ts +++ b/test/mocks.ts @@ -1,8 +1,8 @@ -import { expect } from "chai"; -import { ChtApi, ChtSession, RemotePlace } from "../src/lib/cht-api"; -import { ContactProperty, ContactType } from "../src/lib/config"; -import Place from "../src/services/place"; -import Sinon from "sinon"; +import { expect } from 'chai'; +import { ChtApi, ChtSession, RemotePlace } from '../src/lib/cht-api'; +import { ContactProperty, ContactType } from '../src/lib/config'; +import Place from '../src/services/place'; +import Sinon from 'sinon'; export const mockPlace = (type: ContactType, prop: any) : Place => { const result = new Place(type); @@ -34,7 +34,11 @@ export const mockChtApi: ChtApi = (first: RemotePlace[] = [], second: RemotePlac getPlacesWithType: Sinon.stub().resolves(first).onSecondCall().resolves(second), }); -export const mockSimpleContactType = (propertyType = 'string', propertyValidator: string | string[] | undefined, errorDescription?: string) : ContactType => { +export const mockSimpleContactType = ( + propertyType, + propertyValidator: string | string[] | undefined, + errorDescription?: string +) : ContactType => { const mockedProperty = mockProperty(propertyType, propertyValidator); mockedProperty.errorDescription = errorDescription; return { @@ -59,7 +63,7 @@ export const mockSimpleContactType = (propertyType = 'string', propertyValidator }; }; -export const mockValidContactType = (propertyType = 'string', propertyValidator: string | string[] | undefined) : ContactType => ({ +export const mockValidContactType = (propertyType, propertyValidator: string | string[] | undefined) : ContactType => ({ name: 'contacttype-name', friendly: 'friendly', contact_type: 'contact-type', @@ -94,7 +98,7 @@ export const mockParentPlace = (parentPlaceType: ContactType, parentName: string return place; }; -export const mockProperty = (type = 'string', parameter: string | string[] | undefined, property_name: string = 'prop'): ContactProperty => ({ +export const mockProperty = (type, parameter: string | string[] | undefined, property_name: string = 'prop'): ContactProperty => ({ friendly_name: 'csv', property_name, type, @@ -112,10 +116,14 @@ export const mockChtSession = () : ChtSession => ({ username: 'username', }); -export function expectInvalidProperties(validationErrors: { [key: string]: string } | undefined, expectedProperties: string[], expectedError?: string): void { +export function expectInvalidProperties( + validationErrors: { [key: string]: string } | undefined, + expectedProperties: string[], + expectedError?: string +): void { expect(Object.keys(validationErrors as any)).to.deep.eq(expectedProperties); if (expectedError) { expect(Object.values(validationErrors as any)?.[0]).to.include(expectedError); } -}; +} diff --git a/test/services/place-factory.spec.ts b/test/services/place-factory.spec.ts index 7e788fa0..712d22ea 100644 --- a/test/services/place-factory.spec.ts +++ b/test/services/place-factory.spec.ts @@ -190,7 +190,7 @@ describe('services/place-factory.ts', () => { level: 3, contact_type: 'greatgrandparent', required: false, - };; + }; fakeFormData.hierarchy_GREATGRANDPARENT = greatParent.name; remotePlace.lineage[1] = greatParent.id; @@ -250,7 +250,7 @@ describe('services/place-factory.ts', () => { level: 3, contact_type: 'greatgrandparent', required: false, - };; + }; fakeFormData.hierarchy_GREATGRANDPARENT = greatParent.name; const ambiguous = { diff --git a/test/services/place.spec.ts b/test/services/place.spec.ts index a4b3cd74..c951be39 100644 --- a/test/services/place.spec.ts +++ b/test/services/place.spec.ts @@ -1,8 +1,8 @@ -import { expect } from "chai"; +import { expect } from 'chai'; -import Place from "../../src/services/place"; -import { mockSimpleContactType } from "../mocks"; -import RemotePlaceResolver from "../../src/lib/remote-place-resolver"; +import Place from '../../src/services/place'; +import { mockSimpleContactType } from '../mocks'; +import RemotePlaceResolver from '../../src/lib/remote-place-resolver'; describe('service place.ts', () => { it('setPropertiesFromFormData', () => { diff --git a/test/services/upload-manager.spec.ts b/test/services/upload-manager.spec.ts index 9e53f018..026b3976 100644 --- a/test/services/upload-manager.spec.ts +++ b/test/services/upload-manager.spec.ts @@ -130,12 +130,12 @@ describe('upload-manager.ts', () => { await uploadManager.doUpload([place], chtApi); expect(chtApi.createUser.args[0][0]).to.deep.include({ username: 'replacement_based_username', - }) + }); expect(place.isCreated).to.be.true; }); it('place with validation error is not uploaded', async () => { - const { remotePlace, sessionCache, contactType, fakeFormData, chtApi } = await createMocks(); + const { sessionCache, contactType, fakeFormData, chtApi } = await createMocks(); delete fakeFormData.place_name; const place = await PlaceFactory.createOne(fakeFormData, contactType, sessionCache, chtApi); expect(place.validationErrors).to.not.be.empty; @@ -173,7 +173,7 @@ describe('upload-manager.ts', () => { await RemotePlaceResolver.resolveOne(chp, sessionCache, chtApi, { fuzz: true }); chp.validate(); expect(chp.validationErrors).to.be.empty; -`` + // upload succeeds chtApi.getParentAndSibling = sinon.stub().resolves({ parent: chu.asChtPayload('user'), sibling: undefined }); const uploadManager = new UploadManager();