From c3a329b1cd47e7021d8fd9253a440b7896a64c83 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Wed, 14 Jul 2021 12:58:32 +0800 Subject: [PATCH] feat: set up standalone `/shared` root folder for clean sharing of types and utils (#2322) * build: set up separate shared package with its own node_modules this allows for true independence and allows typescript build orchestration * build: set up tooling to handle new shared root folder * ref: collapse types into single file * feat: correct types used in document types * feat: use shared types in client * chore: update mongoose to v5.13.2 allows some new inference of types for instance/static methods * feat(shared): add more Dto and Base types for Agency * feat(agency): use new shared types as base for backend types * build: revert alias tooling This partially reverts commit 089eee52e6ac6653deb63da5beaee9b6e4573028. Not going to bother for now, backend build somehow keeps failing with aliases * fix: correct built server file path changed due to inclusion of a new shared folder in the compilation step * feat: remove unused types/api files agency and user.ts have been superseded by their new shared/types files * feat: update _id type for UserBase Co-authored-by: Antariksh Mahajan Co-authored-by: Antariksh Mahajan --- .eslintrc | 1 + package-lock.json | 59 +-- package.json | 6 +- shared/.eslintrc | 20 + shared/package-lock.json | 450 ++++++++++++++++++ shared/package.json | 16 + shared/tsconfig.json | 72 +++ shared/types/agency.ts | 32 ++ shared/types/generic.ts | 10 + shared/types/user.ts | 35 ++ .../__tests__/agency.server.model.spec.ts | 7 +- src/app/models/agency.server.model.ts | 22 +- src/app/models/user.server.model.ts | 4 +- src/app/modules/examples/examples.types.ts | 6 +- src/public/services/AuthService.ts | 6 +- src/public/services/UserService.ts | 12 +- src/types/agency.ts | 35 +- src/types/api/agency.ts | 14 - src/types/api/user.ts | 30 -- src/types/user.ts | 6 +- tests/end-to-end/helpers/selectors.js | 4 +- tests/end-to-end/helpers/util.js | 8 +- tsconfig.json | 2 +- 23 files changed, 724 insertions(+), 133 deletions(-) create mode 100644 shared/.eslintrc create mode 100644 shared/package-lock.json create mode 100644 shared/package.json create mode 100644 shared/tsconfig.json create mode 100644 shared/types/agency.ts create mode 100644 shared/types/generic.ts create mode 100644 shared/types/user.ts delete mode 100644 src/types/api/agency.ts delete mode 100644 src/types/api/user.ts diff --git a/.eslintrc b/.eslintrc index 06e385d6c3..60f818dc24 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,6 +4,7 @@ "es6": true, "node": true }, + "ignorePatterns": ["./shared"], "extends": ["eslint:recommended", "plugin:prettier/recommended"], "globals": { "Atomics": "readonly", diff --git a/package-lock.json b/package-lock.json index 5282de4beb..07e62fac0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1855,9 +1855,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001243", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001243.tgz", - "integrity": "sha512-vNxw9mkTBtkmLFnJRv/2rhs1yufpDfCkBZexG3Y0xdOH2Z/eE/85E4Dl5j1YUN34nZVsSp6vVRFQRrez9wJMRA==", + "version": "1.0.30001244", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001244.tgz", + "integrity": "sha512-Wb4UFZPkPoJoKKVfELPWytRzpemjP/s0pe22NriANru1NoI+5bGNxzKtk7edYL8rmCWTfQO8eRiF0pn1Dqzx7Q==", "dev": true }, "colorette": { @@ -1867,9 +1867,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.770", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.770.tgz", - "integrity": "sha512-Kyh8DGK1KfEZuYKIHvuOmrKotsKZQ+qBkDIWHciE3QoFkxXB1KzPP+tfLilSHAfxTON0yYMnFCWkQtUOR7g6KQ==", + "version": "1.3.775", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.775.tgz", + "integrity": "sha512-EGuiJW4yBPOTj2NtWGZcX93ZE8IGj33HJAx4d3ouE2zOfW2trbWU+t1e0yzLr1qQIw81++txbM3BH52QwSRE6Q==", "dev": true }, "node-releases": { @@ -5155,22 +5155,6 @@ "@sentry/utils": "6.9.0", "localforage": "^1.8.1", "tslib": "^1.9.3" - }, - "dependencies": { - "@sentry/types": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.9.0.tgz", - "integrity": "sha512-v52HJqLoLapEnqS2NdVtUXPvT+aezQgNXQkp8hiQ3RUdTm5cffwBVG7wlbpE6OsOOIZxd6p1zKylFkwCypiIIA==" - }, - "@sentry/utils": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.9.0.tgz", - "integrity": "sha512-PimDr6KAi4cCp5hQZ8Az2/pDcdfhTu7WAU30Dd9MZwknpHSTmD4G6QvkdrB5er6kMMnNQOC7rMo6w/Do3m6X3w==", - "requires": { - "@sentry/types": "6.9.0", - "tslib": "^1.9.3" - } - } } }, "@sentry/minimal": { @@ -5501,9 +5485,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.20.tgz", - "integrity": "sha512-8qqFN4W53IEWa9bdmuVrUcVkFemQWnt5DKPQ/oa8xKDYgtjCr2OO6NX5TIK49NLFr3mPYU2cLh92DQquC3oWWQ==", + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", + "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", "dev": true, "requires": { "@types/node": "*", @@ -5751,15 +5735,15 @@ "dev": true }, "@types/qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-7s9EQWupR1fTc2pSMtXRQ9w9gLOcrJn+h7HOXw4evxyvVqMi4f+q7d2tnFe3ng3SNHjtK+0EzGMGFUQX4/AQRA==", + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", "dev": true }, "@types/range-parser": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", - "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", "dev": true }, "@types/responselike": { @@ -7225,9 +7209,9 @@ "integrity": "sha512-24q5Rh3bno7ldoyCq99d6hpnLI+PAMocdeVaaGt/5BTQMprvDwQToHfNnruqN11odCHZZIQbRBw+nZo1lTCH9g==" }, "aws-sdk": { - "version": "2.944.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.944.0.tgz", - "integrity": "sha512-QHKDbs/og1kUhOZsE6A8KN/Rw3LHkfEWBqziRaWDNoijDZzY9XlbLHnLmpkluCe4HNKY1KOOyBajhf2CkJahcQ==", + "version": "2.945.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.945.0.tgz", + "integrity": "sha512-tkcoFAUol7c+9ZBnXsBTKfsj9bNckJ7uzj7FdD/a8AMt/6/18LlEISCiuHFl9qr8MItcON7UgnphJdFCTV7zBw==", "requires": { "buffer": "4.9.2", "events": "1.1.1", @@ -18362,11 +18346,12 @@ "integrity": "sha1-D3ca0W9IOuZfQoeWlCjp+8SqYYE=" }, "mongoose": { - "version": "5.12.13", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.12.13.tgz", - "integrity": "sha512-QGn1FCzZ8Z+mMGVg8oR2kQw4NmhLloCHsw1NqKWg3Yr7WfPzkE4pe7s9P6o5pkYGsku17n9mqMHowne7EFK/zQ==", + "version": "5.13.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.2.tgz", + "integrity": "sha512-sBUKJGpdwZCq9102Lj6ZOaLcW4z/T4TI9aGWrNX5ZlICwChKWG4Wo5qriLImdww3H7bETPW9vYtSiADNlA4wSQ==", "requires": { "@types/mongodb": "^3.5.27", + "@types/node": "14.x || 15.x", "bson": "^1.1.4", "kareem": "2.3.2", "mongodb": "3.6.8", diff --git a/package.json b/package.json index 28af1a4358..f72c89395d 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "build-frontend": "webpack --config webpack.prod.js", "build-frontend-dev": "webpack --config webpack.dev.js", "build-frontend-dev:watch": "webpack --config webpack.dev.js --watch", - "start": "node -r dotenv/config dist/backend/app/server.js", + "start": "node -r dotenv/config dist/backend/src/app/server.js", "dev": "docker-compose up --build", "docker-dev": "npm run build-frontend-dev:watch & ts-node-dev --respawn --transpile-only --inspect=0.0.0.0 --exit-child -r dotenv/config -- src/app/server.ts", "test": "npm run test-backend && npm run test-frontend", @@ -33,7 +33,7 @@ "test-e2e-build": "npm run build-backend && npm run build-frontend-dev", "test-e2e-ci": "env-cmd -f tests/.test-env --use-shell \"npm run download-binary && npm run testcafe-command\"", "testcafe-command": "testcafe --skip-js-errors -c 2 chrome:headless ./tests/end-to-end --app \"npm run test-e2e-server\" --app-init-delay 10000", - "test-e2e-server": "concurrently --success last --kill-others \"mockpass\" \"maildev\" \"node dist/backend/app/server.js\" \"node ./tests/mock-webhook-server.js\"", + "test-e2e-server": "concurrently --success last --kill-others \"mockpass\" \"maildev\" \"node dist/backend/src/app/server.js\" \"node ./tests/mock-webhook-server.js\"", "lint-code": "eslint src/ --quiet --fix", "lint-style": "stylelint '*/**/*.css' --quiet --fix", "lint-html": "htmlhint && prettier --write './src/public/**/*.html' --ignore-path './dist/**' --loglevel silent", @@ -124,7 +124,7 @@ "lodash": "^4.17.21", "moment-timezone": "0.5.33", "mongodb-uri": "^0.9.7", - "mongoose": "^5.12.13", + "mongoose": "^5.13.2", "multiparty": ">=4.2.2", "neverthrow": "^4.2.2", "ng-infinite-scroll": "^1.3.0", diff --git a/shared/.eslintrc b/shared/.eslintrc new file mode 100644 index 0000000000..f444594c12 --- /dev/null +++ b/shared/.eslintrc @@ -0,0 +1,20 @@ +{ + "root": true, + "plugins": ["import", "simple-import-sort", "prettier"], + "extends": ["eslint:recommended", "plugin:prettier/recommended"], + "overrides": [ + { + "files": ["*.ts", "*.tsx"], + "extends": ["plugin:@typescript-eslint/recommended"], + "parser": "@typescript-eslint/parser", + "rules": { + "@typescript-eslint/no-unused-vars": "error" + } + } + ], + "env": { "es6": true }, + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module" + } +} diff --git a/shared/package-lock.json b/shared/package-lock.json new file mode 100644 index 0000000000..f1faa4465a --- /dev/null +++ b/shared/package-lock.json @@ -0,0 +1,450 @@ +{ + "name": "shared", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz", + "integrity": "sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@types/json-schema": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.2.tgz", + "integrity": "sha512-PGqpLLzHSxq956rzNGasO3GsAPf2lY9lDUBXhS++SKonglUmJypaUtcKzRtUte8CV7nruwnDxtLUKpVxs0wQBw==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "4.28.2", + "@typescript-eslint/scope-manager": "4.28.2", + "debug": "^4.3.1", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.1.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.2.tgz", + "integrity": "sha512-MwHPsL6qo98RC55IoWWP8/opTykjTp4JzfPu1VfO2Z0MshNP0UZ1GEV5rYSSnZSUI8VD7iHvtIPVGW5Nfh7klQ==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.7", + "@typescript-eslint/scope-manager": "4.28.2", + "@typescript-eslint/types": "4.28.2", + "@typescript-eslint/typescript-estree": "4.28.2", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.2.tgz", + "integrity": "sha512-Q0gSCN51eikAgFGY+gnd5p9bhhCUAl0ERMiDKrTzpSoMYRubdB8MJrTTR/BBii8z+iFwz8oihxd0RAdP4l8w8w==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "4.28.2", + "@typescript-eslint/types": "4.28.2", + "@typescript-eslint/typescript-estree": "4.28.2", + "debug": "^4.3.1" + } + }, + "@typescript-eslint/scope-manager": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.2.tgz", + "integrity": "sha512-MqbypNjIkJFEFuOwPWNDjq0nqXAKZvDNNs9yNseoGBB1wYfz1G0WHC2AVOy4XD7di3KCcW3+nhZyN6zruqmp2A==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.28.2", + "@typescript-eslint/visitor-keys": "4.28.2" + } + }, + "@typescript-eslint/types": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.2.tgz", + "integrity": "sha512-Gr15fuQVd93uD9zzxbApz3wf7ua3yk4ZujABZlZhaxxKY8ojo448u7XTm/+ETpy0V0dlMtj6t4VdDvdc0JmUhA==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.2.tgz", + "integrity": "sha512-86lLstLvK6QjNZjMoYUBMMsULFw0hPHJlk1fzhAVoNjDBuPVxiwvGuPQq3fsBMCxuDJwmX87tM/AXoadhHRljg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.28.2", + "@typescript-eslint/visitor-keys": "4.28.2", + "debug": "^4.3.1", + "globby": "^11.0.3", + "is-glob": "^4.0.1", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.2.tgz", + "integrity": "sha512-aT2B4PLyyRDUVUafXzpZFoc0C9t0za4BJAKP5sgWIhG+jHECQZUEjuQSCIwZdiJJ4w4cgu5r3Kh20SOdtEBl0w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.28.2", + "eslint-visitor-keys": "^2.0.0" + } + }, + "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 + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "date-fns": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.22.1.tgz", + "integrity": "sha512-yUFPQjrxEmIsMqlHhAhmxkuH769baF21Kk+nZwZGyrMoyLA+LugaQtC0+Tqf9CBUUULWwUJt6Q5ySI3LJDDCGg==" + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "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, + "requires": { + "path-type": "^4.0.0" + } + }, + "eslint-config-prettier": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "dev": true + }, + "eslint-plugin-prettier": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.0.tgz", + "integrity": "sha512-UDK6rJT6INSfcOo545jiaOwB701uAIt2/dR7WnFQoGCVl1/EMqdANBmwUaqqQ45aXprsTGzSa39LI1PyuRBxxw==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + } + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-glob": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.6.tgz", + "integrity": "sha512-GnLuqj/pvQ7pX8/L4J84nijv6sAnlwvSDpMkJi9i7nPmPxGtRPkBSStfvDW5l6nMdX9VWe+pkKWFTgD+vF2QSQ==", + "dev": true, + "requires": { + "@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" + } + }, + "fastq": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz", + "integrity": "sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globby": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "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 + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-fest": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.2.1.tgz", + "integrity": "sha512-SbmIRuXhJs8KTneu77Ecylt9zuqL683tuiLYpTRil4H++eIhqCmx6ko6KAFem9dty8sOdnEiX7j4K1nRE628fQ==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "zod": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.3.4.tgz", + "integrity": "sha512-g5V20IUn3QBpbMKjZxKp58ocUOO9/L0OlJ5+rl84QSeN6tK5ea/yQboYA59t2D8Puc/qVyz4YflhCgqR2uMtaA==" + } + } +} diff --git a/shared/package.json b/shared/package.json new file mode 100644 index 0000000000..297f92bed8 --- /dev/null +++ b/shared/package.json @@ -0,0 +1,16 @@ +{ + "name": "shared", + "version": "1.0.0", + "description": "", + "dependencies": { + "date-fns": "^2.22.1", + "type-fest": "^1.2.1", + "zod": "^3.3.4" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^4.28.2", + "@typescript-eslint/parser": "^4.28.2", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-prettier": "^3.4.0" + } +} diff --git a/shared/tsconfig.json b/shared/tsconfig.json new file mode 100644 index 0000000000..3c167ef85c --- /dev/null +++ b/shared/tsconfig.json @@ -0,0 +1,72 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */, + "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ + "declaration": true /* Generates corresponding '.d.ts' file. */, + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + // "outDir": "./", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + "composite": true /* Enable project compilation */, + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true /* Skip type checking of declaration files. */, + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +} diff --git a/shared/types/agency.ts b/shared/types/agency.ts new file mode 100644 index 0000000000..912a729f10 --- /dev/null +++ b/shared/types/agency.ts @@ -0,0 +1,32 @@ +import { Opaque } from 'type-fest' +import { z } from 'zod' +import { DateString } from './generic' + +export type AgencyId = Opaque +export const AgencyId = z.string() as unknown as z.Schema + +// Base used for being referenced by schema/model in the backend. +// Note the lack of typing of _id. +export const AgencyBase = z.object({ + _id: z.unknown(), + emailDomain: z.array(z.string()), + fullName: z.string(), + shortName: z.string(), + logo: z.string(), +}) +export type AgencyBase = z.infer + +export const AgencyDto = AgencyBase.extend({ + _id: AgencyId, + created: DateString, + lastModified: DateString, +}) +export type AgencyDto = z.infer + +export const PublicAgencyDto = AgencyDto.pick({ + shortName: true, + fullName: true, + emailDomain: true, + logo: true, +}) +export type PublicAgencyDto = z.infer diff --git a/shared/types/generic.ts b/shared/types/generic.ts new file mode 100644 index 0000000000..2479645f44 --- /dev/null +++ b/shared/types/generic.ts @@ -0,0 +1,10 @@ +import { Opaque } from 'type-fest' +import { z } from 'zod' +import { isDate, parseISO } from 'date-fns' + +export type DateString = Opaque + +export const DateString = z.custom().refine( + (val) => isDate(parseISO(val)), + (val) => ({ message: `${val} is not a valid date` }), +) diff --git a/shared/types/user.ts b/shared/types/user.ts new file mode 100644 index 0000000000..bf7b97d2bb --- /dev/null +++ b/shared/types/user.ts @@ -0,0 +1,35 @@ +import { z } from 'zod' +import { Opaque } from 'type-fest' + +import { DateString } from './generic' +import { AgencyBase, AgencyDto } from './agency' + +type UserId = Opaque +type UserContact = Opaque + +// Base used for being referenced by schema/model in the backend. +// Note the lack of typing of _id. +export const UserBase = z.object({ + _id: z.unknown(), + email: z.string().email(), + agency: AgencyBase.shape._id, + betaFlags: z.record(z.boolean()).optional(), + created: z.date(), + lastAccessed: z.date().optional(), + updatedAt: z.date(), + contact: (z.string() as unknown as z.Schema).optional(), +}) +export type UserBase = z.infer + +// Convert to serialized versions. +export const UserDto = UserBase.extend({ + _id: z.string() as unknown as z.Schema, + agency: AgencyDto.extend({ + created: DateString, + lastModified: DateString, + }), + created: DateString, + lastAccessed: DateString.optional(), + updatedAt: DateString, +}) +export type UserDto = z.infer diff --git a/src/app/models/__tests__/agency.server.model.spec.ts b/src/app/models/__tests__/agency.server.model.spec.ts index 40a7a2bf1d..7d557e6214 100644 --- a/src/app/models/__tests__/agency.server.model.spec.ts +++ b/src/app/models/__tests__/agency.server.model.spec.ts @@ -1,9 +1,8 @@ import { pick } from 'lodash' import mongoose from 'mongoose' +import { PublicAgencyDto } from 'shared/types/agency' -import getAgencyModel, { - AGENCY_PUBLIC_FIELDS, -} from 'src/app/models/agency.server.model' +import getAgencyModel from 'src/app/models/agency.server.model' import dbHandler from 'tests/unit/backend/helpers/jest-db' @@ -32,7 +31,7 @@ describe('agency.server.model', () => { const actual = agency.getPublicView() // Assert - expect(actual).toEqual(pick(agency, AGENCY_PUBLIC_FIELDS)) + expect(actual).toEqual(pick(agency, Object.keys(PublicAgencyDto.shape))) }) }) }) diff --git a/src/app/models/agency.server.model.ts b/src/app/models/agency.server.model.ts index 0930252b24..47532c7002 100644 --- a/src/app/models/agency.server.model.ts +++ b/src/app/models/agency.server.model.ts @@ -1,7 +1,13 @@ import { pick } from 'lodash' import { Mongoose, Schema } from 'mongoose' -import { IAgencyModel, IAgencySchema, PublicAgency } from '../../types' +import { PublicAgencyDto } from '../../../shared/types/agency' +import { + AgencyInstanceMethods, + IAgencyDocument, + IAgencyModel, + PublicAgency, +} from '../../types' export const AGENCY_SCHEMA_ID = 'Agency' @@ -13,7 +19,12 @@ export const AGENCY_PUBLIC_FIELDS = [ 'logo', ] -const AgencySchema = new Schema( +const AgencySchema = new Schema< + IAgencyDocument, + IAgencyModel, + undefined, + AgencyInstanceMethods +>( { shortName: { type: String, @@ -51,11 +62,12 @@ const AgencySchema = new Schema( // Methods AgencySchema.methods.getPublicView = function (): PublicAgency { - return pick(this, AGENCY_PUBLIC_FIELDS) as PublicAgency + return pick(this, Object.keys(PublicAgencyDto.shape)) as PublicAgency } -const compileAgencyModel = (db: Mongoose): IAgencyModel => - db.model(AGENCY_SCHEMA_ID, AgencySchema) +const compileAgencyModel = (db: Mongoose): IAgencyModel => { + return db.model(AGENCY_SCHEMA_ID, AgencySchema) +} /** * Retrieves the Agency model on the given Mongoose instance. If the model is diff --git a/src/app/models/user.server.model.ts b/src/app/models/user.server.model.ts index 16ba6a6399..a6b063910f 100644 --- a/src/app/models/user.server.model.ts +++ b/src/app/models/user.server.model.ts @@ -4,7 +4,7 @@ import { CallbackError, Mongoose, Schema } from 'mongoose' import validator from 'validator' import { - IAgencySchema, + AgencyDocument, IUser, IUserModel, IUserSchema, @@ -110,7 +110,7 @@ const compileUserModel = (db: Mongoose) => { // Return public view of nested agency document if populated. return { agency: this.populated('agency') - ? (this.agency as IAgencySchema).getPublicView() + ? (this.agency as AgencyDocument).getPublicView() : this.agency, } } diff --git a/src/app/modules/examples/examples.types.ts b/src/app/modules/examples/examples.types.ts index 132d406eb9..2971e6927e 100644 --- a/src/app/modules/examples/examples.types.ts +++ b/src/app/modules/examples/examples.types.ts @@ -1,5 +1,5 @@ import { - IAgency, + IAgencyDocument, IForm, IFormFeedbackSchema, IFormStatisticsTotalModel, @@ -19,8 +19,8 @@ export type QueryExecResult = { lastSubmission: Date | null title: IForm['title'] form_fields: IForm['form_fields'] - logo: IAgency['logo'] - agency: IAgency['shortName'] + logo: IAgencyDocument['logo'] + agency: IAgencyDocument['shortName'] colorTheme: StartPage['colorTheme'] avgFeedback: number | null } diff --git a/src/public/services/AuthService.ts b/src/public/services/AuthService.ts index 0dbdd7477a..a295a524c0 100644 --- a/src/public/services/AuthService.ts +++ b/src/public/services/AuthService.ts @@ -1,7 +1,7 @@ import axios from 'axios' import { Opaque } from 'type-fest' -import { User } from '../../types/api/user' +import { UserDto } from '../../../shared/types/user' // Exported for testing. export const AUTH_ENDPOINT = '/api/v3/auth' @@ -44,9 +44,9 @@ export const sendLoginOtp = async (email: Email): Promise => { export const verifyLoginOtp = async (params: { otp: string email: string -}): Promise => { +}): Promise => { return axios - .post(`${AUTH_ENDPOINT}/otp/verify`, params) + .post(`${AUTH_ENDPOINT}/otp/verify`, params) .then(({ data }) => data) } diff --git a/src/public/services/UserService.ts b/src/public/services/UserService.ts index 2cda68d0d0..d1851d385a 100644 --- a/src/public/services/UserService.ts +++ b/src/public/services/UserService.ts @@ -1,6 +1,6 @@ import axios from 'axios' -import { User } from '../../types/api/user' +import { UserDto } from '../../../shared/types/user' /** Exported for testing */ export const STORAGE_USER_KEY = 'user' @@ -13,12 +13,12 @@ export const USER_ENDPOINT = '/api/v3/user' * * @returns user if available, null otherwise */ -export const getUserFromLocalStorage = (): User | null => { +export const getUserFromLocalStorage = (): UserDto | null => { const userStringified = localStorage.getItem(STORAGE_USER_KEY) if (userStringified) { try { - return User.parse(JSON.parse(userStringified)) + return UserDto.parse(JSON.parse(userStringified)) } catch (error) { // Invalid shape, clear from storage. clearUserFromLocalStorage() @@ -34,7 +34,7 @@ export const getUserFromLocalStorage = (): User | null => { * * @param user the user to save to local storage */ -export const saveUserToLocalStorage = (user: User): void => { +export const saveUserToLocalStorage = (user: UserDto): void => { localStorage.setItem(STORAGE_USER_KEY, JSON.stringify(user)) } @@ -53,6 +53,6 @@ export const clearUserFromLocalStorage = (): void => { * Side effect: On error, clear the user (if any) from localStorage. * @returns the logged in user if session is valid, `null` otherwise */ -export const fetchUser = async (): Promise => { - return axios.get(USER_ENDPOINT).then(({ data }) => data) +export const fetchUser = async (): Promise => { + return axios.get(USER_ENDPOINT).then(({ data }) => data) } diff --git a/src/types/agency.ts b/src/types/agency.ts index 191ed9088e..047203a217 100644 --- a/src/types/agency.ts +++ b/src/types/agency.ts @@ -1,25 +1,28 @@ -import { Document, Model } from 'mongoose' +import { EnforceDocument, Model } from 'mongoose' + +import { AgencyBase, PublicAgencyDto } from '../../shared/types/agency' import { PublicView } from './database' -export interface IAgency { - shortName: string - fullName: string - emailDomain: string[] - logo: string +export type PublicAgency = PublicAgencyDto + +export type AgencyInstanceMethods = PublicView + +export interface IAgencySchema extends AgencyBase { created?: Date lastModified?: Date } -// Make sure this is kept in sync with agency.server.model#AGENCY_PUBLIC_FIELDS. -export type PublicAgency = Pick< - IAgencySchema, - 'shortName' | 'fullName' | 'emailDomain' | 'logo' -> +export interface IAgencyDocument extends IAgencySchema { + created: Date + lastModified: Date +} -export interface IAgencySchema - extends IAgency, - Document, - PublicView {} +// Used to cast created documents whenever needed. +export type AgencyDocument = EnforceDocument< + IAgencyDocument, + AgencyInstanceMethods +> -export type IAgencyModel = Model +// eslint-disable-next-line @typescript-eslint/ban-types +export type IAgencyModel = Model diff --git a/src/types/api/agency.ts b/src/types/api/agency.ts deleted file mode 100644 index d65a809515..0000000000 --- a/src/types/api/agency.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Opaque } from 'type-fest' -import { z } from 'zod' - -type AgencyId = Opaque - -export const Agency = z.object({ - emailDomain: z.array(z.string()), - fullName: z.string(), - shortName: z.string(), - logo: z.string(), - _id: z.string() as unknown as z.Schema, -}) - -export type Agency = z.infer diff --git a/src/types/api/user.ts b/src/types/api/user.ts deleted file mode 100644 index 3fe0028beb..0000000000 --- a/src/types/api/user.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { isDate, parseISO } from 'date-fns' -import { Opaque } from 'type-fest' -import { z } from 'zod' - -import { Agency } from './agency' - -type UserId = Opaque -type UserContact = Opaque - -const DateString = z.string().refine( - (val) => isDate(parseISO(val)), - (val) => ({ message: `${val} is not a valid date` }), -) - -export const User = z.object({ - _id: z.string() as unknown as z.Schema, - email: z.string().email(), - agency: Agency, - betaFlags: z - .object({ - sgid: z.boolean().optional(), - }) - .optional(), - created: DateString, - lastAccessed: DateString, - updatedAt: DateString, - contact: (z.string() as unknown as z.Schema).optional(), -}) - -export type User = z.infer diff --git a/src/types/user.ts b/src/types/user.ts index 8cac268bb1..d987bbc3e4 100644 --- a/src/types/user.ts +++ b/src/types/user.ts @@ -1,6 +1,6 @@ import { Document, Model, ObjectId } from 'mongoose' -import { IAgencySchema, PublicAgency } from './agency' +import { AgencyDocument, PublicAgency } from './agency' import { PublicView } from './database' export type PublicUser = { @@ -13,7 +13,7 @@ export type AdminContactOtpData = { export interface IUser { email: string - agency: IAgencySchema['_id'] + agency: AgencyDocument['_id'] contact?: string betaFlags?: { sgid?: boolean @@ -49,5 +49,5 @@ export interface IUserModel extends Model { } export interface IPopulatedUser extends IUserSchema { - agency: IAgencySchema + agency: AgencyDocument } diff --git a/tests/end-to-end/helpers/selectors.js b/tests/end-to-end/helpers/selectors.js index 39373c3236..c6d155ee31 100644 --- a/tests/end-to-end/helpers/selectors.js +++ b/tests/end-to-end/helpers/selectors.js @@ -1,10 +1,10 @@ const { Selector } = require('testcafe') const { types: basicTypes, -} = require('../../../dist/backend/shared/resources/basic') +} = require('../../../dist/backend/src/shared/resources/basic') const { types: myInfoTypes, -} = require('../../../dist/backend/shared/resources/myinfo') +} = require('../../../dist/backend/src/shared/resources/myinfo') const landingPage = { tagline: Selector('#tagline'), diff --git a/tests/end-to-end/helpers/util.js b/tests/end-to-end/helpers/util.js index 38175b99d7..9217d68c97 100644 --- a/tests/end-to-end/helpers/util.js +++ b/tests/end-to-end/helpers/util.js @@ -26,11 +26,11 @@ const { mockpass, } = require('./selectors') -const { types } = require('../../../dist/backend/shared/resources/basic') +const { types } = require('../../../dist/backend/src/shared/resources/basic') const { SPCPFieldTitle, -} = require('../../../dist/backend/types/field/fieldTypes') +} = require('../../../dist/backend/src/types/field/fieldTypes') const NON_SUBMITTED_FIELDS = types .filter((field) => !field.submitted) @@ -248,9 +248,9 @@ function makeModel(db, modelFilename, modelName) { // Need this try catch block as some schemas may have been converted to // TypeScript and use default exports instead. try { - return spec(`dist/backend/app/models/${modelFilename}`)(db) + return spec(`dist/backend/src/app/models/${modelFilename}`)(db) } catch (e) { - return spec(`dist/backend/app/models/${modelFilename}`).default(db) + return spec(`dist/backend/src/app/models/${modelFilename}`).default(db) } } diff --git a/tsconfig.json b/tsconfig.json index faf3c9d613..20e87955f4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -68,5 +68,5 @@ "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ }, - "include": ["src", "tests"], + "include": ["src", "tests", "shared"], }