From 840a2c435d76e17f83aa8f6420fc9d1924775e11 Mon Sep 17 00:00:00 2001 From: khavinshankar Date: Sat, 28 Oct 2023 20:35:16 +0530 Subject: [PATCH] added typesafety Co-authored-by: Aman Shenoy --- Dockerfile | 25 +- package-lock.json | 319 ++++++++++++------ package.json | 20 +- ...estException.js => BadRequestException.ts} | 4 +- ...eHTTPException.js => BaseHTTPException.ts} | 6 +- ...xception.js => InternalServerException.ts} | 4 +- ...FoundException.js => NotFoundException.ts} | 4 +- ...ameraValidators.js => cameraValidators.ts} | 6 +- ...Validators.js => observationValidators.ts} | 11 +- ...oDataExtractor.js => autoDataExtractor.ts} | 75 ++-- .../helper/downloadImageWithDigestRouter.js | 23 -- .../helper/downloadImageWithDigestRouter.ts | 26 ++ ...oordinates.js => getMonitorCoordinates.ts} | 8 +- ...Controller.js => AssetConfigController.ts} | 70 ++-- src/controller/AuthController.js | 17 - src/controller/AuthController.ts | 17 + .../{BedController.js => BedController.ts} | 37 +- ...ameraController.js => CameraController.ts} | 46 +-- src/controller/ConfigController.js | 8 - src/controller/ConfigController.ts | 10 + src/controller/NotFoundController.js | 3 - src/controller/NotFoundController.ts | 9 + ...Controller.js => ObservationController.ts} | 171 ++++++---- src/controller/OpenidConfig.js | 15 - src/controller/OpenidConfig.ts | 20 ++ ...ontroller.js => ServerStatusController.ts} | 31 +- src/controller/healthCheckController.js | 48 --- src/controller/healthCheckController.ts | 52 +++ .../{makeDataDump.js => makeDataDump.ts} | 38 ++- .../{errorHandler.js => errorHandler.ts} | 12 +- src/middleware/getWs.js | 4 - src/middleware/getWs.ts | 15 + .../{morganWithWs.js => morganWithWs.ts} | 7 +- src/middleware/{validate.js => validate.ts} | 8 +- ...etConfigRouter.js => assetConfigRouter.ts} | 2 +- src/router/{authRouter.js => authRouter.ts} | 2 +- src/router/{bedRouter.js => bedRouter.ts} | 2 +- .../{cameraRouter.js => cameraRouter.ts} | 13 +- .../{configRouter.js => configRouter.ts} | 2 +- .../{healthRouter.js => healthRouter.ts} | 3 +- ...ervationRouter.js => observationRouter.ts} | 7 +- ...rStatusRouter.js => serverStatusRouter.ts} | 2 +- src/{server.js => server.ts} | 52 +-- src/swagger/{common.js => common.ts} | 0 src/swagger/{swagger.js => swagger.ts} | 4 +- src/types/camera.ts | 22 ++ src/types/external/onvif.d.ts | 8 + src/types/observation.ts | 148 ++++++++ src/types/ocr.ts | 20 ++ src/types/ws.ts | 6 + ...bservationUtils.js => ObservationUtils.ts} | 18 +- ...{ObservationsMap.js => ObservationsMap.ts} | 7 +- src/utils/assetUtils.js | 11 - src/utils/assetUtils.ts | 11 + src/utils/catchAsync.js | 2 - src/utils/catchAsync.ts | 6 + src/utils/{configs.js => configs.ts} | 11 +- ...{dailyRoundUtils.js => dailyRoundUtils.ts} | 39 ++- ...ntTypeConstant.js => eventTypeConstant.ts} | 0 src/utils/generateJWT.js | 27 -- src/utils/generateJWT.ts | 38 +++ src/utils/wsUtils.js | 4 - src/utils/wsUtils.ts | 9 + tsconfig.json | 20 ++ 64 files changed, 1085 insertions(+), 580 deletions(-) rename src/Exception/{BadRequestException.js => BadRequestException.ts} (52%) rename src/Exception/{BaseHTTPException.js => BaseHTTPException.ts} (65%) rename src/Exception/{InternalServerException.js => InternalServerException.ts} (54%) rename src/Exception/{NotFoundException.js => NotFoundException.ts} (51%) rename src/Validators/{cameraValidators.js => cameraValidators.ts} (95%) rename src/Validators/{observationValidators.js => observationValidators.ts} (94%) rename src/automation/{autoDataExtractor.js => autoDataExtractor.ts} (64%) delete mode 100644 src/automation/helper/downloadImageWithDigestRouter.js create mode 100644 src/automation/helper/downloadImageWithDigestRouter.ts rename src/automation/helper/{getMonitorCoordinates.js => getMonitorCoordinates.ts} (75%) rename src/controller/{AssetConfigController.js => AssetConfigController.ts} (71%) delete mode 100644 src/controller/AuthController.js create mode 100644 src/controller/AuthController.ts rename src/controller/{BedController.js => BedController.ts} (79%) rename src/controller/{CameraController.js => CameraController.ts} (86%) delete mode 100644 src/controller/ConfigController.js create mode 100644 src/controller/ConfigController.ts delete mode 100644 src/controller/NotFoundController.js create mode 100644 src/controller/NotFoundController.ts rename src/controller/{ObservationController.js => ObservationController.ts} (75%) delete mode 100644 src/controller/OpenidConfig.js create mode 100644 src/controller/OpenidConfig.ts rename src/controller/{ServerStatusController.js => ServerStatusController.ts} (57%) delete mode 100644 src/controller/healthCheckController.js create mode 100644 src/controller/healthCheckController.ts rename src/controller/helper/{makeDataDump.js => makeDataDump.ts} (54%) rename src/middleware/{errorHandler.js => errorHandler.ts} (69%) delete mode 100644 src/middleware/getWs.js create mode 100644 src/middleware/getWs.ts rename src/middleware/{morganWithWs.js => morganWithWs.ts} (69%) rename src/middleware/{validate.js => validate.ts} (54%) rename src/router/{assetConfigRouter.js => assetConfigRouter.ts} (86%) rename src/router/{authRouter.js => authRouter.ts} (69%) rename src/router/{bedRouter.js => bedRouter.ts} (85%) rename src/router/{cameraRouter.js => cameraRouter.ts} (76%) rename src/router/{configRouter.js => configRouter.ts} (75%) rename src/router/{healthRouter.js => healthRouter.ts} (65%) rename src/router/{observationRouter.js => observationRouter.ts} (77%) rename src/router/{serverStatusRouter.js => serverStatusRouter.ts} (67%) rename src/{server.js => server.ts} (64%) rename src/swagger/{common.js => common.ts} (100%) rename src/swagger/{swagger.js => swagger.ts} (86%) create mode 100644 src/types/camera.ts create mode 100644 src/types/external/onvif.d.ts create mode 100644 src/types/observation.ts create mode 100644 src/types/ocr.ts create mode 100644 src/types/ws.ts rename src/utils/{ObservationUtils.js => ObservationUtils.ts} (95%) rename src/utils/{ObservationsMap.js => ObservationsMap.ts} (79%) delete mode 100644 src/utils/assetUtils.js create mode 100644 src/utils/assetUtils.ts delete mode 100644 src/utils/catchAsync.js create mode 100644 src/utils/catchAsync.ts rename src/utils/{configs.js => configs.ts} (74%) rename src/utils/{dailyRoundUtils.js => dailyRoundUtils.ts} (57%) rename src/utils/{eventTypeConstant.js => eventTypeConstant.ts} (100%) delete mode 100644 src/utils/generateJWT.js create mode 100644 src/utils/generateJWT.ts delete mode 100644 src/utils/wsUtils.js create mode 100644 src/utils/wsUtils.ts create mode 100644 tsconfig.json diff --git a/Dockerfile b/Dockerfile index b8317b3..0ff2df7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,23 @@ -FROM node:17-alpine +FROM node:17-alpine AS build -WORKDIR /usr/src/app - -RUN mkdir images - -COPY package*.json ./ +WORKDIR /app +COPY package.*json ./ RUN npm install -#RUN npm ci COPY . . +RUN npm run build -RUN chmod +x ./start.sh -EXPOSE 8090 +FROM node:17-alpine AS production + +WORKDIR /app -ENTRYPOINT [ "./start.sh" ] +COPY package.*json ./ +RUN npm install --only=production + +COPY --from=build /app/prisma ./prisma +COPY --from=build /app/dist ./dist + +EXPOSE 8090 +CMD ["npm", "run", "start:prod"] diff --git a/package-lock.json b/package-lock.json index 387af96..2e37f29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,6 @@ "express-ws": "^5.0.2", "form-data": "^4.0.0", "helmet": "^4.6.0", - "http-auth": "^4.2.0", "lodash.groupby": "^4.6.0", "morgan": "^1.10.0", "node-jose": "^2.1.0", @@ -35,7 +34,16 @@ "swagger-ui-express": "^4.3.0" }, "devDependencies": { + "@types/connect-flash": "^0.0.39", + "@types/cors": "^2.8.15", + "@types/express-session": "^1.17.9", "@types/express-ws": "^3.0.1", + "@types/lodash.groupby": "^4.6.8", + "@types/morgan": "^1.9.7", + "@types/node-jose": "^1.1.12", + "@types/pidusage": "^2.0.4", + "@types/swagger-jsdoc": "^6.0.2", + "@types/swagger-ui-express": "^4.1.5", "nodemon": "^2.0.19", "prisma": "^4.15.0" } @@ -230,27 +238,55 @@ "@types/node": "*" } }, + "node_modules/@types/connect-flash": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/connect-flash/-/connect-flash-0.0.39.tgz", + "integrity": "sha512-m8xTfpcHZGw3eknCttCwYP37x5lS2ZzKM+BVStSqB0vyNpMnl0YLpvVkFjkxk8E7G3MWIgj3bib4QnHquI+/hw==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.15", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.15.tgz", + "integrity": "sha512-n91JxbNLD8eQIuXDIChAN1tCKNWCEgpceU9b7ZMbFA+P+Q4yIeh80jizFLEvolRPc1ES0VdwFlGv+kJTSirogw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", + "integrity": "sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw==", "dev": true, "dependencies": { "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", + "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "*" } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.30", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz", - "integrity": "sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ==", + "version": "4.17.39", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.39.tgz", + "integrity": "sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==", "dev": true, "dependencies": { "@types/node": "*", "@types/qs": "*", - "@types/range-parser": "*" + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express-session": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.9.tgz", + "integrity": "sha512-yIqficLlTPdloeEPhOVenpOUWILkdaXHUWhTOqFGx9JoSuTgeatNjb97k8VvJehbTk0kUSUAHy5r27PXMga89Q==", + "dev": true, + "dependencies": { + "@types/express": "*" } }, "node_modules/@types/express-ws": { @@ -269,18 +305,57 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, + "node_modules/@types/lodash": { + "version": "4.14.200", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.200.tgz", + "integrity": "sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q==", + "dev": true + }, + "node_modules/@types/lodash.groupby": { + "version": "4.6.8", + "resolved": "https://registry.npmjs.org/@types/lodash.groupby/-/lodash.groupby-4.6.8.tgz", + "integrity": "sha512-+VbBhRhzo6g6q5RdVQXlU1vwbYVodEkS9ZCVuqHtZvhlSu1muQLNYYR1yhyYwAcSz7gMDOHlWPnPvAoQqV4rlg==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/mime": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", "dev": true }, + "node_modules/@types/morgan": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.7.tgz", + "integrity": "sha512-4sJFBUBrIZkP5EvMm1L6VCXp3SQe8dnXqlVpe1jsmTjS1JQVmSjnpMNs8DosQd6omBi/K7BSKJ6z/Mc3ki0K9g==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "18.7.14", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.14.tgz", "integrity": "sha512-6bbDaETVi8oyIARulOE9qF1/Qdi/23z6emrUh0fNJRUmjznqrixD4MpGDdgOFk5Xb0m2H6Xu42JGdvAxaJR/wA==", "dev": true }, + "node_modules/@types/node-jose": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@types/node-jose/-/node-jose-1.1.12.tgz", + "integrity": "sha512-HtSXbirRMuONr/KSNtBgh631xCt/t3lPz0geQ4pe/FA+yu06TUrJrXEU5y8nJFHNy8KhiZrq6OVlqXD1AtT/dQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/pidusage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pidusage/-/pidusage-2.0.4.tgz", + "integrity": "sha512-yku7awugpbqBz+MjVFzDkJIW8eaN9jySnotHxYfOY5dqnsjRgvG4HQFMQ7wtEaGjk+WjJ8R3Ll3iaaZtVKY3JA==", + "dev": true + }, "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -293,6 +368,22 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", "dev": true }, + "node_modules/@types/send": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz", + "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/send/node_modules/@types/mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", + "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==", + "dev": true + }, "node_modules/@types/serve-static": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", @@ -303,6 +394,22 @@ "@types/node": "*" } }, + "node_modules/@types/swagger-jsdoc": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.2.tgz", + "integrity": "sha512-Qor3ObrH9Q27VWEiyw2zfE/nNgFCrA3D6Rm8V8fwYeLikfVw4au9Je0F+88QBgeCgMKSoCx12akTEaBqacIHjw==", + "dev": true + }, + "node_modules/@types/swagger-ui-express": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.5.tgz", + "integrity": "sha512-MRvm1OCzIR321glc/4tP34wRVmsupgLzs6XIq50CFp0CJUzxbpDsrhJxEBMQfoO46ixrlCiw3QXxEs5HHxYI8Q==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, "node_modules/@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -389,25 +496,6 @@ "node": ">= 8" } }, - "node_modules/apache-crypt": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/apache-crypt/-/apache-crypt-1.2.6.tgz", - "integrity": "sha512-072WetlM4blL8PREJVeY+WHiUh1R5VNt2HfceGS8aKqttPHcmqE5pkKuXPz/ULmJOFkc8Hw3kfKl6vy7Qka6DA==", - "dependencies": { - "unix-crypt-td-js": "^1.1.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/apache-md5": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/apache-md5/-/apache-md5-1.1.8.tgz", - "integrity": "sha512-FCAJojipPn0bXjuEpjOOOMN8FZDkxfWWp4JGN9mifU2IhxvKyXZYqpzPHdnTSUpmPDy+tsslB6Z1g+Vg6nVbYA==", - "engines": { - "node": ">=8" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -555,11 +643,6 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, - "node_modules/bcryptjs": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", - "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" - }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -1261,20 +1344,6 @@ "node": ">=10.0.0" } }, - "node_modules/http-auth": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-auth/-/http-auth-4.2.0.tgz", - "integrity": "sha512-trIkGI7dgnFJ5k8YaQFSr1Q5uq9c19vK6Y9ZCjlY0zBEQgdJpXZU3Cyrmk4nwrAGy4pKJhs599o7q6eicbVnhw==", - "dependencies": { - "apache-crypt": "^1.1.2", - "apache-md5": "^1.0.6", - "bcryptjs": "^2.4.3", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -2247,11 +2316,6 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, - "node_modules/unix-crypt-td-js": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/unix-crypt-td-js/-/unix-crypt-td-js-1.1.4.tgz", - "integrity": "sha512-8rMeVYWSIyccIJscb9NdCfZKSRBKYTeVnwmiRYT2ulE3qd1RaDQ0xQDP+rI3ccIWbhu/zuo5cgN8z73belNZgw==" - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -2562,27 +2626,55 @@ "@types/node": "*" } }, + "@types/connect-flash": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/connect-flash/-/connect-flash-0.0.39.tgz", + "integrity": "sha512-m8xTfpcHZGw3eknCttCwYP37x5lS2ZzKM+BVStSqB0vyNpMnl0YLpvVkFjkxk8E7G3MWIgj3bib4QnHquI+/hw==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/cors": { + "version": "2.8.15", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.15.tgz", + "integrity": "sha512-n91JxbNLD8eQIuXDIChAN1tCKNWCEgpceU9b7ZMbFA+P+Q4yIeh80jizFLEvolRPc1ES0VdwFlGv+kJTSirogw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", + "integrity": "sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw==", "dev": true, "requires": { "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", + "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "*" } }, "@types/express-serve-static-core": { - "version": "4.17.30", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz", - "integrity": "sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ==", + "version": "4.17.39", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.39.tgz", + "integrity": "sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==", "dev": true, "requires": { "@types/node": "*", "@types/qs": "*", - "@types/range-parser": "*" + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "@types/express-session": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.9.tgz", + "integrity": "sha512-yIqficLlTPdloeEPhOVenpOUWILkdaXHUWhTOqFGx9JoSuTgeatNjb97k8VvJehbTk0kUSUAHy5r27PXMga89Q==", + "dev": true, + "requires": { + "@types/express": "*" } }, "@types/express-ws": { @@ -2601,18 +2693,57 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, + "@types/lodash": { + "version": "4.14.200", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.200.tgz", + "integrity": "sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q==", + "dev": true + }, + "@types/lodash.groupby": { + "version": "4.6.8", + "resolved": "https://registry.npmjs.org/@types/lodash.groupby/-/lodash.groupby-4.6.8.tgz", + "integrity": "sha512-+VbBhRhzo6g6q5RdVQXlU1vwbYVodEkS9ZCVuqHtZvhlSu1muQLNYYR1yhyYwAcSz7gMDOHlWPnPvAoQqV4rlg==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, "@types/mime": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", "dev": true }, + "@types/morgan": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.7.tgz", + "integrity": "sha512-4sJFBUBrIZkP5EvMm1L6VCXp3SQe8dnXqlVpe1jsmTjS1JQVmSjnpMNs8DosQd6omBi/K7BSKJ6z/Mc3ki0K9g==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "18.7.14", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.14.tgz", "integrity": "sha512-6bbDaETVi8oyIARulOE9qF1/Qdi/23z6emrUh0fNJRUmjznqrixD4MpGDdgOFk5Xb0m2H6Xu42JGdvAxaJR/wA==", "dev": true }, + "@types/node-jose": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@types/node-jose/-/node-jose-1.1.12.tgz", + "integrity": "sha512-HtSXbirRMuONr/KSNtBgh631xCt/t3lPz0geQ4pe/FA+yu06TUrJrXEU5y8nJFHNy8KhiZrq6OVlqXD1AtT/dQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/pidusage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pidusage/-/pidusage-2.0.4.tgz", + "integrity": "sha512-yku7awugpbqBz+MjVFzDkJIW8eaN9jySnotHxYfOY5dqnsjRgvG4HQFMQ7wtEaGjk+WjJ8R3Ll3iaaZtVKY3JA==", + "dev": true + }, "@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -2625,6 +2756,24 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", "dev": true }, + "@types/send": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz", + "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + }, + "dependencies": { + "@types/mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", + "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==", + "dev": true + } + } + }, "@types/serve-static": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", @@ -2635,6 +2784,22 @@ "@types/node": "*" } }, + "@types/swagger-jsdoc": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.2.tgz", + "integrity": "sha512-Qor3ObrH9Q27VWEiyw2zfE/nNgFCrA3D6Rm8V8fwYeLikfVw4au9Je0F+88QBgeCgMKSoCx12akTEaBqacIHjw==", + "dev": true + }, + "@types/swagger-ui-express": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.5.tgz", + "integrity": "sha512-MRvm1OCzIR321glc/4tP34wRVmsupgLzs6XIq50CFp0CJUzxbpDsrhJxEBMQfoO46ixrlCiw3QXxEs5HHxYI8Q==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, "@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -2700,19 +2865,6 @@ "picomatch": "^2.0.4" } }, - "apache-crypt": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/apache-crypt/-/apache-crypt-1.2.6.tgz", - "integrity": "sha512-072WetlM4blL8PREJVeY+WHiUh1R5VNt2HfceGS8aKqttPHcmqE5pkKuXPz/ULmJOFkc8Hw3kfKl6vy7Qka6DA==", - "requires": { - "unix-crypt-td-js": "^1.1.4" - } - }, - "apache-md5": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/apache-md5/-/apache-md5-1.1.8.tgz", - "integrity": "sha512-FCAJojipPn0bXjuEpjOOOMN8FZDkxfWWp4JGN9mifU2IhxvKyXZYqpzPHdnTSUpmPDy+tsslB6Z1g+Vg6nVbYA==" - }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2829,11 +2981,6 @@ } } }, - "bcryptjs": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", - "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" - }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -3335,17 +3482,6 @@ "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.6.0.tgz", "integrity": "sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==" }, - "http-auth": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-auth/-/http-auth-4.2.0.tgz", - "integrity": "sha512-trIkGI7dgnFJ5k8YaQFSr1Q5uq9c19vK6Y9ZCjlY0zBEQgdJpXZU3Cyrmk4nwrAGy4pKJhs599o7q6eicbVnhw==", - "requires": { - "apache-crypt": "^1.1.2", - "apache-md5": "^1.0.6", - "bcryptjs": "^2.4.3", - "uuid": "^8.3.2" - } - }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -4053,11 +4189,6 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, - "unix-crypt-td-js": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/unix-crypt-td-js/-/unix-crypt-td-js-1.1.4.tgz", - "integrity": "sha512-8rMeVYWSIyccIJscb9NdCfZKSRBKYTeVnwmiRYT2ulE3qd1RaDQ0xQDP+rI3ccIWbhu/zuo5cgN8z73belNZgw==" - }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/package.json b/package.json index 4c1f6a8..8756717 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,6 @@ "express-ws": "^5.0.2", "form-data": "^4.0.0", "helmet": "^4.6.0", - "http-auth": "^4.2.0", "lodash.groupby": "^4.6.0", "morgan": "^1.10.0", "node-jose": "^2.1.0", @@ -26,14 +25,16 @@ "swagger-ui-express": "^4.3.0" }, "scripts": { - "start": "node src/server.js", - "dev": "nodemon src/server.js" + "start": "node dist/server.ts", + "dev": "nodemon src/server.ts", + "build": "tsc", + "start:prod": "prisma migrate deploy && npm run build && node dist/server.js", + "start:dev": "prisma migrate dev && npm run dev" }, "name": "teleicu_middleware", "description": "Middleware to help tunnel CCTV Streams and ONVIF APIs for TeleICU", "version": "0.0.1", - "main": "server.js", - "type": "module", + "main": "src/server.ts", "repository": { "type": "git", "url": "git+https://github.com/coronasafe/teleicu_middleware.git" @@ -50,7 +51,16 @@ }, "homepage": "https://github.com/coronasafe/teleicu_middleware#readme", "devDependencies": { + "@types/connect-flash": "^0.0.39", + "@types/cors": "^2.8.15", + "@types/express-session": "^1.17.9", "@types/express-ws": "^3.0.1", + "@types/lodash.groupby": "^4.6.8", + "@types/morgan": "^1.9.7", + "@types/node-jose": "^1.1.12", + "@types/pidusage": "^2.0.4", + "@types/swagger-jsdoc": "^6.0.2", + "@types/swagger-ui-express": "^4.1.5", "nodemon": "^2.0.19", "prisma": "^4.15.0" } diff --git a/src/Exception/BadRequestException.js b/src/Exception/BadRequestException.ts similarity index 52% rename from src/Exception/BadRequestException.js rename to src/Exception/BadRequestException.ts index 630bace..979fd5d 100644 --- a/src/Exception/BadRequestException.js +++ b/src/Exception/BadRequestException.ts @@ -1,7 +1,7 @@ -import { BaseHTTPException } from "./BaseHTTPException.js"; +import { BaseHTTPException } from "@/Exception/BaseHTTPException"; export class BadRequestException extends BaseHTTPException { - constructor(message) { + constructor(message: string) { super(message || "Bad Request", 400); } } diff --git a/src/Exception/BaseHTTPException.js b/src/Exception/BaseHTTPException.ts similarity index 65% rename from src/Exception/BaseHTTPException.js rename to src/Exception/BaseHTTPException.ts index d030414..82109dd 100644 --- a/src/Exception/BaseHTTPException.js +++ b/src/Exception/BaseHTTPException.ts @@ -1,5 +1,9 @@ export class BaseHTTPException extends Error { - constructor(message, statusCode) { + public statusCode: number; + public status: string; + public isOperational: boolean; + + constructor(message: string, statusCode: number) { super(message); this.statusCode = statusCode; diff --git a/src/Exception/InternalServerException.js b/src/Exception/InternalServerException.ts similarity index 54% rename from src/Exception/InternalServerException.js rename to src/Exception/InternalServerException.ts index b097e9d..d595752 100644 --- a/src/Exception/InternalServerException.js +++ b/src/Exception/InternalServerException.ts @@ -1,7 +1,7 @@ -import { BaseHTTPException } from "./BaseHTTPException.js"; +import { BaseHTTPException } from "@/Exception/BaseHTTPException"; export class InternalServerError extends BaseHTTPException { - constructor(message) { + constructor(message: string) { super(message || "Internal Server Error", 500); } } diff --git a/src/Exception/NotFoundException.js b/src/Exception/NotFoundException.ts similarity index 51% rename from src/Exception/NotFoundException.js rename to src/Exception/NotFoundException.ts index 9008367..53b2427 100644 --- a/src/Exception/NotFoundException.js +++ b/src/Exception/NotFoundException.ts @@ -1,7 +1,7 @@ -import { BaseHTTPException } from "./BaseHTTPException.js"; +import { BaseHTTPException } from "@/Exception/BaseHTTPException"; export class NotFoundException extends BaseHTTPException { - constructor(message) { + constructor(message: string) { super(message || "Not Found", 404); } } diff --git a/src/Validators/cameraValidators.js b/src/Validators/cameraValidators.ts similarity index 95% rename from src/Validators/cameraValidators.js rename to src/Validators/cameraValidators.ts index 9934158..9c735f7 100644 --- a/src/Validators/cameraValidators.js +++ b/src/Validators/cameraValidators.ts @@ -71,16 +71,16 @@ export const camMoveValidator = [ body("x") .exists({ checkNull: true }) .withMessage("x is required.") - .isNumeric({ max: 1, min: -1 }) + .isFloat({ max: 1, min: -1 }) .withMessage("x must be integer."), body("y") .exists({ checkNull: true }) .withMessage("y is required.") - .isNumeric({ max: 1, min: -1 }) + .isFloat({ max: 1, min: -1 }) .withMessage("y must be number."), body("zoom") .exists({ checkNull: true }) .withMessage("zoom is required.") - .isNumeric({ max: 1, min: -1 }) + .isFloat({ max: 1, min: -1 }) .withMessage("zoom must be number."), ]; diff --git a/src/Validators/observationValidators.js b/src/Validators/observationValidators.ts similarity index 94% rename from src/Validators/observationValidators.js rename to src/Validators/observationValidators.ts index f61383e..05e31ba 100644 --- a/src/Validators/observationValidators.js +++ b/src/Validators/observationValidators.ts @@ -1,5 +1,5 @@ -import { query, check, body, checkSchema } from "express-validator"; -import {baseCameraParamsValidators} from '../Validators/cameraValidators.js' +import { query, body } from "express-validator"; +import { baseCameraParamsValidators } from "@/Validators/cameraValidators"; const Observations = { ST: "ST", @@ -36,7 +36,7 @@ const baseObservationValidators = [ .withMessage("date-time is required.") .isString() .withMessage("date-time must be string.") - .custom((val) => !isNaN(new Date(val))) + .custom((val) => !isNaN(new Date(val).getTime())) .withMessage("date-time must be valid date string."), // body("*.patient-id") // .exists({ checkFalsy: true }) @@ -102,7 +102,7 @@ export const vitalsValidator = [ .withMessage("device_id is required.") .isString() .withMessage("device_id must be string."), -] +]; export const autoObservationValidator = [ ...baseCameraParamsValidators, @@ -111,5 +111,4 @@ export const autoObservationValidator = [ .withMessage("assetExternalId is required.") .isString() .withMessage("assetExternalId must be string."), - -] \ No newline at end of file +]; diff --git a/src/automation/autoDataExtractor.js b/src/automation/autoDataExtractor.ts similarity index 64% rename from src/automation/autoDataExtractor.js rename to src/automation/autoDataExtractor.ts index 4ba59f9..bd6ed64 100644 --- a/src/automation/autoDataExtractor.js +++ b/src/automation/autoDataExtractor.ts @@ -1,12 +1,17 @@ -import { CameraUtils } from "../utils/CameraUtils.js"; -import { downloadImage } from "./helper/downloadImageWithDigestRouter.js"; +import { CameraUtils } from "@/utils/CameraUtils"; +import { downloadImage } from "@/automation/helper/downloadImageWithDigestRouter"; import axios from "axios"; import path from "path"; import fs from "fs"; import FormData from "form-data"; import * as dotenv from "dotenv"; -import { saveOCRImages, waitBeforeOCRCapture } from "../utils/configs.js"; +import { saveOCRImages, waitBeforeOCRCapture } from "@/utils/configs"; +import type { CameraAsset, CameraParams, CameraPreset } from "@/types/camera"; +import type { + OCRObservationV1Raw, + OCRObservationV1Sanitized, +} from "@/types/ocr"; dotenv.config({ path: "./.env" }); @@ -14,28 +19,40 @@ const MONITOR_PRESET_NAME_PREFIX = "5 Para_Bed_"; const OCR_URL = process.env.OCR_URL; -const celsiusToFahrenheit = (celsius) => { - celsius = parseFloat(celsius); - const fahrenheit = (celsius * 9) / 5 + 32; +const celsiusToFahrenheit = (celsius: string) => { + const celsiusNumber = parseFloat(celsius); + const fahrenheit = (celsiusNumber * 9) / 5 + 32; return fahrenheit; }; -const validator = (value, parser, minvalue, maxValue) => { - if (isNaN(value)) { +const validator = ( + value: number | string | null | undefined, + parser: (value: any) => number, + minvalue?: number, + maxValue?: number +) => { + if (value === null || value === undefined || isNaN(Number(value))) { return null; } value = parser(value); - return value >= minvalue && value <= maxValue ? value : null; + if (minvalue !== undefined && value < minvalue) { + return null; + } + + if (maxValue !== undefined && value > maxValue) { + return null; + } + + return value; }; -const getSanitizedData = (data) => { + +const getSanitizedData = (data: OCRObservationV1Raw) => { console.log("Data from OCR: ", data); - const sanitizedData = {}; - sanitizedData["spo2"] = !isNaN(data?.["SpO2"]) - ? parseFloat(data?.["SpO2"]) - : null; + const sanitizedData: OCRObservationV1Sanitized = {}; + sanitizedData["spo2"] = validator(data?.["SpO2"], parseInt); sanitizedData["ventilator_spo2"] = validator( sanitizedData.spo2, @@ -60,18 +77,16 @@ const getSanitizedData = (data) => { 106 ); - sanitizedData["bp"] = !isNaN(data?.["Blood Pressure"]) - ? { - systolic: parseFloat(data?.["Blood Pressure"]), - mean: parseFloat(data?.["Blood Pressure"]), - diastolic: parseFloat(data?.["Blood Pressure"]), - } - : null; + const bp = validator(data?.["Blood Pressure"], parseFloat); + sanitizedData["bp"] = bp ? { systolic: bp, mean: bp, diastolic: bp } : null; return sanitizedData; }; -const extractData = async (camParams, monitorPreset = { x: 0, y: 0, z: 0 }) => { +const extractData = async ( + camParams: CameraParams, + monitorPreset = { x: 0, y: 0, zoom: 0 } +) => { try { console.log("Moving to coordinates: ", monitorPreset); await CameraUtils.absoluteMove({ camParams, ...monitorPreset }); @@ -101,6 +116,10 @@ const extractData = async (camParams, monitorPreset = { x: 0, y: 0, z: 0 }) => { const bodyFormData = new FormData(); bodyFormData.append("image", fs.createReadStream(imagePath)); + if (!OCR_URL) { + throw new Error("OCR_URL is not defined"); + } + const response = await axios.post(OCR_URL, bodyFormData, { headers: { ...bodyFormData.getHeaders(), @@ -131,7 +150,12 @@ const extractData = async (camParams, monitorPreset = { x: 0, y: 0, z: 0 }) => { } }; -const _getCamParams = (params) => { +const _getCamParams = (params: { + hostname: string; + username: string; + password: string; + port: string | number; +}) => { const { hostname, username, password, port } = params; const camParams = { @@ -145,7 +169,10 @@ const _getCamParams = (params) => { return camParams; }; -export const updateObservationAuto = async (cameraParams, monitorPreset) => { +export const updateObservationAuto = async ( + cameraParams: CameraAsset, + monitorPreset: CameraPreset +) => { try { const cameraParamsSanitized = _getCamParams(cameraParams); diff --git a/src/automation/helper/downloadImageWithDigestRouter.js b/src/automation/helper/downloadImageWithDigestRouter.js deleted file mode 100644 index 793912c..0000000 --- a/src/automation/helper/downloadImageWithDigestRouter.js +++ /dev/null @@ -1,23 +0,0 @@ -import fs from 'fs'; -import Axios from 'axios'; -import auth from 'http-auth'; - -export const downloadImage = async (url, filepath, username, password)=>{ - - const digest = auth.digest({ - username, - password - }); - - const response = await Axios({ - url, - method: 'GET', - responseType: 'stream', - headers: {Authorization: digest} - }); - return new Promise((resolve, reject) => { - response.data.pipe(fs.createWriteStream(filepath)) - .on('error', reject) - .once('close', () => resolve(filepath)); - }); -} \ No newline at end of file diff --git a/src/automation/helper/downloadImageWithDigestRouter.ts b/src/automation/helper/downloadImageWithDigestRouter.ts new file mode 100644 index 0000000..58c1207 --- /dev/null +++ b/src/automation/helper/downloadImageWithDigestRouter.ts @@ -0,0 +1,26 @@ +import fs from "fs"; +import Axios from "axios"; + +export const downloadImage = async ( + url: string, + filepath: string, + username: string, + password: string +) => { + const response = await Axios({ + url, + method: "GET", + responseType: "stream", + auth: { + username, + password, + }, + }); + + return new Promise((resolve, reject) => { + response.data + .pipe(fs.createWriteStream(filepath)) + .on("error", reject) + .once("close", () => resolve(filepath)); + }); +}; diff --git a/src/automation/helper/getMonitorCoordinates.js b/src/automation/helper/getMonitorCoordinates.ts similarity index 75% rename from src/automation/helper/getMonitorCoordinates.js rename to src/automation/helper/getMonitorCoordinates.ts index 2757564..7d71828 100644 --- a/src/automation/helper/getMonitorCoordinates.js +++ b/src/automation/helper/getMonitorCoordinates.ts @@ -1,8 +1,8 @@ -import { PrismaClient } from "@prisma/client" +import { PrismaClient } from "@prisma/client"; -const prisma = new PrismaClient() +const prisma = new PrismaClient(); -export const getMonitorCoordinates = async (bedId) => { +export const getMonitorCoordinates = async (bedId: string) => { try { const preset = await prisma.preset.findFirst({ where: { @@ -34,4 +34,4 @@ export const getMonitorCoordinates = async (bedId) => { zoom: 0, }; } -}; \ No newline at end of file +}; diff --git a/src/controller/AssetConfigController.js b/src/controller/AssetConfigController.ts similarity index 71% rename from src/controller/AssetConfigController.js rename to src/controller/AssetConfigController.ts index 24abafd..9351987 100644 --- a/src/controller/AssetConfigController.js +++ b/src/controller/AssetConfigController.ts @@ -1,12 +1,11 @@ -import { PrismaClient } from "@prisma/client" -import dayjs from "dayjs" - -const prisma = new PrismaClient() +import { PrismaClient } from "@prisma/client"; +import dayjs from "dayjs"; +import { Request, Response } from "express"; +const prisma = new PrismaClient(); export class AssetConfigController { - - static listAssets = async (req, res) => { + static listAssets = async (req: Request, res: Response) => { try { const assets = await prisma.asset.findMany({ where: { @@ -28,7 +27,7 @@ export class AssetConfigController { beds, errors: req.flash("error"), }); - } catch (err) { + } catch (err: any) { res.render("pages/assetList", { dayjs, assets: [], @@ -36,9 +35,9 @@ export class AssetConfigController { errors: [err.message], }); } - } + }; - static createAsset = async (req, res) => { + static createAsset = async (req: Request, res: Response) => { const { name, type, @@ -69,9 +68,9 @@ export class AssetConfigController { req.flash("error", err.message); res.redirect("/assets"); }); - } + }; - static updateAssetForm = async (req, res) => { + static updateAssetForm = async (req: Request, res: Response) => { try { const asset = await prisma.asset.findUniqueOrThrow({ where: { @@ -92,13 +91,13 @@ export class AssetConfigController { beds, errors: req.flash("error"), }); - } catch (err) { + } catch (err: any) { req.flash("error", err.message); res.redirect("/assets"); } - } + }; - static updateAsset = async (req, res) => { + static updateAsset = async (req: Request, res: Response) => { const { name, description, @@ -131,30 +130,33 @@ export class AssetConfigController { req.flash("error", err.message); res.redirect(`/assets/${req.params.id}`); }); - } + }; - static confirmDeleteAsset = async (req, res) => { + static confirmDeleteAsset = async (req: Request, res: Response) => { const asset = await prisma.asset.findUnique({ where: { - id: Number(req.params.id) - } + id: Number(req.params.id), + }, }); res.render("pages/assetDelete", { dayjs, asset }); - } + }; - static deleteAsset = async (req, res) => { - prisma.asset.update({ - where: { - id: Number(req.params.id) - }, - data: { - deleted: true, - } - }).then(_ => { - res.redirect("/assets"); - }).catch(err => { - req.flash("error", err.message); - res.redirect("/assets"); - }) - } + static deleteAsset = async (req: Request, res: Response) => { + prisma.asset + .update({ + where: { + id: Number(req.params.id), + }, + data: { + deleted: true, + }, + }) + .then((_) => { + res.redirect("/assets"); + }) + .catch((err) => { + req.flash("error", err.message); + res.redirect("/assets"); + }); + }; } diff --git a/src/controller/AuthController.js b/src/controller/AuthController.js deleted file mode 100644 index 05f6bb6..0000000 --- a/src/controller/AuthController.js +++ /dev/null @@ -1,17 +0,0 @@ -export class AuthController { - static verifyToken = (req, res) => { - const token = "" - - if (this._validateJwt(token)) { - return res.send({ status: "1" }); - } - - res.send({ status: "0" }); - - } - - static _validateJwt = (token) => { - return true - } - -} \ No newline at end of file diff --git a/src/controller/AuthController.ts b/src/controller/AuthController.ts new file mode 100644 index 0000000..843cc83 --- /dev/null +++ b/src/controller/AuthController.ts @@ -0,0 +1,17 @@ +import type { Request, Response } from "express"; + +export class AuthController { + static verifyToken = (req: Request, res: Response) => { + const token = ""; + + if (this._validateJwt(token)) { + return res.send({ status: "1" }); + } + + res.send({ status: "0" }); + }; + + static _validateJwt = (token: string) => { + return true; + }; +} diff --git a/src/controller/BedController.js b/src/controller/BedController.ts similarity index 79% rename from src/controller/BedController.js rename to src/controller/BedController.ts index 51a87b6..bea73ac 100644 --- a/src/controller/BedController.js +++ b/src/controller/BedController.ts @@ -1,10 +1,11 @@ import { PrismaClient } from "@prisma/client"; import dayjs from "dayjs"; +import type { Request, Response } from "express"; const prisma = new PrismaClient(); export class BedController { - static list = async (req, res) => { + static list = async (req: Request, res: Response) => { try { const beds = await prisma.bed.findMany({ where: { @@ -31,7 +32,7 @@ export class BedController { cameras, errors: req.flash("error"), }); - } catch (err) { + } catch (err: any) { res.render("pages/beds/list", { dayjs, beds: [], @@ -41,34 +42,34 @@ export class BedController { } }; - static create = async (req, res) => { + static create = async (req: Request, res: Response) => { const { name, externalId, cameraId, preset_x, preset_y, preset_zoom } = req.body; try { await prisma.bed.create({ data: { - name, - externalId, - cameraId: Number(cameraId) || undefined, + name: name as string, + externalId: externalId as string, + cameraId: Number(cameraId), monitorPreset: { create: { - x: Number(preset_x) || undefined, - y: Number(preset_y) || undefined, - zoom: Number(preset_zoom) || undefined, + x: Number(preset_x), + y: Number(preset_y), + zoom: Number(preset_zoom), }, }, }, }); res.redirect("/beds"); - } catch (err) { + } catch (err: any) { req.flash("error", err.message); res.redirect("/beds"); } }; - static show = async (req, res) => { + static show = async (req: Request, res: Response) => { const { id } = req.params; try { @@ -96,13 +97,13 @@ export class BedController { cameras, errors: req.flash("error"), }); - } catch (err) { + } catch (err: any) { req.flash("error", err.message); res.redirect("/beds"); } }; - static edit = async (req, res) => { + static edit = async (req: Request, res: Response) => { const { id } = req.params; const { name, externalId, cameraId, preset_x, preset_y, preset_zoom } = req.body; @@ -127,13 +128,13 @@ export class BedController { }); res.redirect("/beds"); - } catch (err) { + } catch (err: any) { req.flash("error", err.message); res.redirect("/beds"); } }; - static confirmDelete = async (req, res) => { + static confirmDelete = async (req: Request, res: Response) => { const { id } = req.params; try { @@ -144,13 +145,13 @@ export class BedController { }); res.render("pages/beds/delete", { dayjs, bed }); - } catch (err) { + } catch (err: any) { req.flash("error", err.message); res.redirect("/beds"); } }; - static delete = async (req, res) => { + static delete = async (req: Request, res: Response) => { const { id } = req.params; try { @@ -164,7 +165,7 @@ export class BedController { }); res.redirect("/beds"); - } catch (err) { + } catch (err: any) { req.flash("error", err.message); res.redirect("/beds"); } diff --git a/src/controller/CameraController.js b/src/controller/CameraController.ts similarity index 86% rename from src/controller/CameraController.js rename to src/controller/CameraController.ts index 626e375..e3cf871 100644 --- a/src/controller/CameraController.js +++ b/src/controller/CameraController.ts @@ -1,21 +1,27 @@ -import { CameraUtils } from "../utils/CameraUtils.js"; -import { catchAsync } from "../utils/catchAsync.js"; +import type { CameraAsset, CameraStatus } from "@/types/camera"; +import { CameraUtils } from "@/utils/CameraUtils"; +import { catchAsync } from "@/utils/catchAsync"; +import type { Request, Response } from "express"; -var assets = []; -var statuses = []; -var fetchStatusesInterval = null; +var assets: CameraAsset[] = []; +var statuses: CameraStatus[] = []; +var fetchStatusesInterval: NodeJS.Timer | undefined; const filterStatus = () => { const MIN_IN_MS = 60000; statuses = statuses.filter( - (status) => new Date() - new Date(status.time) <= 30 * MIN_IN_MS + (status) => + new Date().getTime() - new Date(status.time).getTime() <= 30 * MIN_IN_MS ); }; const fetchCameraStatuses = async () => { filterStatus(); - const cameraStatuses = await Promise.all( + const cameraStatuses: { + deviceId: string; + status: "up" | "down"; + }[] = await Promise.all( assets.map(async (camera) => { try { const camParams = CameraController._getCamParams(camera); @@ -41,12 +47,12 @@ const fetchCameraStatuses = async () => { status: cameraStatuses.reduce((acc, curr) => { acc[curr.deviceId] = curr.status; return acc; - }, {}), + }, {} as CameraStatus["status"]), }); }; export class CameraController { - static _getCamParams = (body) => { + static _getCamParams = (body: CameraAsset) => { const { hostname, username, password, port } = body; const camParams = { @@ -93,7 +99,7 @@ export class CameraController { * "200": * description: Return success message */ - static gotoPreset = catchAsync(async (req, res) => { + static gotoPreset = catchAsync(async (req: Request, res: Response) => { const camParams = this._getCamParams(req.body); const { preset } = req.body; @@ -137,8 +143,8 @@ export class CameraController { * "200": * description: Return all available presets */ - static getPresets = catchAsync(async (req, res) => { - const camParams = this._getCamParams(req.query); + static getPresets = catchAsync(async (req: Request, res: Response) => { + const camParams = this._getCamParams(req.query as unknown as CameraAsset); const presets = await CameraUtils.getPreset({ camParams }); res.send(presets); }); @@ -177,8 +183,8 @@ export class CameraController { * description: Return camera status */ - static getStatus = catchAsync(async (req, res) => { - const camParams = this._getCamParams(req.query); + static getStatus = catchAsync(async (req: Request, res: Response) => { + const camParams = this._getCamParams(req.query as unknown as CameraAsset); const status = await CameraUtils.getStatus({ camParams }); res.send(status); @@ -227,7 +233,7 @@ export class CameraController { * type: string * enum: [up, down] */ - static getCameraStatuses = catchAsync(async (req, res) => { + static getCameraStatuses = catchAsync(async (req: Request, res: Response) => { assets = req.body; await fetchCameraStatuses(); @@ -278,7 +284,7 @@ export class CameraController { * description: Return all available presets */ - static absoluteMove = catchAsync(async (req, res) => { + static absoluteMove = catchAsync(async (req: Request, res: Response) => { const camParams = this._getCamParams(req.body); const { x, y, zoom } = req.body; @@ -331,7 +337,7 @@ export class CameraController { * description: Return all available presets */ - static relativeMove = catchAsync(async (req, res) => { + static relativeMove = catchAsync(async (req: Request, res: Response) => { const camParams = this._getCamParams(req.body); const { x, y, zoom } = req.body; @@ -355,7 +361,7 @@ export class CameraController { * "200": * description: Return current time */ - static getTime = catchAsync(async (req, res) => { + static getTime = catchAsync(async (req: Request, res: Response) => { res.send({ time: new Date().toISOString(), }); @@ -395,8 +401,8 @@ export class CameraController { * "200": * description: Return 200 if new camera preset is created */ - static setPreset = catchAsync(async (req, res) => { - const camParams = this._getCamParams(req.query); + static setPreset = catchAsync(async (req: Request, res: Response) => { + const camParams = this._getCamParams(req.query as unknown as CameraAsset); const { presetName } = req.body; await CameraUtils.setPreset({ camParams, presetName }); diff --git a/src/controller/ConfigController.js b/src/controller/ConfigController.js deleted file mode 100644 index 75eebd0..0000000 --- a/src/controller/ConfigController.js +++ /dev/null @@ -1,8 +0,0 @@ -export class ConfigController { - static updateConfig = (req, res) => { - res.redirect("/config"); - }; - static renderUpdateConfig = (req, res) => { - res.render("pages/config", { hostname: "dev_middleware.coronasafe.live" }); - }; -} diff --git a/src/controller/ConfigController.ts b/src/controller/ConfigController.ts new file mode 100644 index 0000000..e7506d7 --- /dev/null +++ b/src/controller/ConfigController.ts @@ -0,0 +1,10 @@ +import type { Request, Response } from "express"; + +export class ConfigController { + static updateConfig = (req: Request, res: Response) => { + res.redirect("/config"); + }; + static renderUpdateConfig = (req: Request, res: Response) => { + res.render("pages/config", { hostname: "dev_middleware.coronasafe.live" }); + }; +} diff --git a/src/controller/NotFoundController.js b/src/controller/NotFoundController.js deleted file mode 100644 index cd4aa97..0000000 --- a/src/controller/NotFoundController.js +++ /dev/null @@ -1,3 +0,0 @@ -export const notFoundController = (req, res, next) => { - res.status(404).render("pages/notFound"); -}; diff --git a/src/controller/NotFoundController.ts b/src/controller/NotFoundController.ts new file mode 100644 index 0000000..2332860 --- /dev/null +++ b/src/controller/NotFoundController.ts @@ -0,0 +1,9 @@ +import type { Request, Response, NextFunction } from "express"; + +export const notFoundController = ( + req: Request, + res: Response, + next: NextFunction +) => { + res.status(404).render("pages/notFound"); +}; diff --git a/src/controller/ObservationController.js b/src/controller/ObservationController.ts similarity index 75% rename from src/controller/ObservationController.js rename to src/controller/ObservationController.ts index c255b8c..ee52cbe 100644 --- a/src/controller/ObservationController.js +++ b/src/controller/ObservationController.ts @@ -1,36 +1,45 @@ -import { - getAsset, - getBedById, - getPatientId, -} from "../utils/dailyRoundUtils.js"; - -import { BadRequestException } from "../Exception/BadRequestException.js"; -import { NotFoundException } from "../Exception/NotFoundException.js"; -import { ObservationsMap } from "../utils/ObservationsMap.js"; +import { getAsset, getBedById, getPatientId } from "@/utils/dailyRoundUtils"; +import { BadRequestException } from "@/Exception/BadRequestException"; +import { NotFoundException } from "@/Exception/NotFoundException"; +import { ObservationsMap } from "@/utils/ObservationsMap"; import { PrismaClient } from "@prisma/client"; import axios from "axios"; -import { careApi } from "../utils/configs.js"; -import { catchAsync } from "../utils/catchAsync.js"; +import { careApi } from "@/utils/configs"; +import { catchAsync } from "@/utils/catchAsync"; import dayjs from "dayjs"; -import { filterClients } from "../utils/wsUtils.js"; -import { generateHeaders } from "../utils/assetUtils.js"; -import { isValid } from "../utils/ObservationUtils.js"; -import { makeDataDumpToJson } from "./helper/makeDataDump.js"; -import { updateObservationAuto } from "../automation/autoDataExtractor.js"; +import { filterClients } from "@/utils/wsUtils"; +import { generateHeaders } from "@/utils/assetUtils"; +import { isValid } from "@/utils/ObservationUtils"; +import { makeDataDumpToJson } from "@/controller/helper/makeDataDump"; +import { updateObservationAuto } from "@/automation/autoDataExtractor"; +import type { Request, Response } from "express"; +import type { + DailyRoundObservation, + LastObservationData, + Observation, + ObservationStatus, + ObservationType, + ObservationTypeWithWaveformTypes, + StaticObservation, +} from "@/types/observation"; +import { WebSocket } from "@/types/ws"; const prisma = new PrismaClient(); const dailyRoundTag = () => new Date().toISOString() + " [Daily Round] "; -var staticObservations = []; -var activeDevices = []; +var staticObservations: StaticObservation[] = []; +var activeDevices: string[] = []; var lastRequestData = {}; -var logData = []; -var statusData = []; -var lastObservationData = {}; +var logData: { + dateTime: string; + data: Observation[][]; +}[] = []; +var statusData: ObservationStatus[] = []; +var lastObservationData: LastObservationData = {}; // start updating after 1 minutes of starting the middleware -let lastUpdatedToCare = new Date() - 59 * 60 * 1000; +let lastUpdatedToCare = new Date().getTime() - 59 * 60 * 1000; // For testing purposes, setting // let lastUpdatedToCare = new Date() - (4 * 60 * 1000 + 50 * 1000); @@ -40,19 +49,21 @@ const UPDATE_INTERVAL = 60 * 60 * 1000; // const UPDATE_INTERVAL = 5 * 60 * 1000; const DEFAULT_LISTING_LIMIT = 10; -const getTime = (date) => new Date(date.replace(" ", "T").concat("+0530")); +const getTime = (date: string) => + new Date(date.replace(" ", "T").concat("+0530")); -const flattenObservations = (observations) => { +type _Observation = Observation | _Observation[]; +const flattenObservations = (observations: _Observation): Observation[] => { if (Array.isArray(observations)) { return observations.reduce((acc, observation) => { - return acc.concat(flattenObservations(observation)); - }, []); + return (acc as _Observation[]).concat(flattenObservations(observation)); + }, []) as Observation[]; } else { return [observations]; } }; -const addObservation = (observation) => { +const addObservation = (observation: Observation) => { // console.log( // observation["date-time"], // ": ", @@ -87,28 +98,31 @@ const addObservation = (observation) => { device_id: observation.device_id, observations: { [observation.observation_id]: [observation], - }, + } as Record, last_updated: new Date(), }, ]; } }; -const addLogData = (newData) => { +const addLogData = (newData: Observation[][]) => { // Slice the log data to the last DEFAULT_LISTING_LIMIT entries logData = logData.slice(logData.length - DEFAULT_LISTING_LIMIT); logData = [ ...logData, { - dateTime: new Date(), + dateTime: new Date().toISOString(), data: newData, }, ]; }; -const updateLastObservationData = (flattenedObservations, skipEmpty = true) => { +const updateLastObservationData = ( + flattenedObservations: Observation[], + skipEmpty = true +) => { flattenedObservations.forEach((observation) => { - const observationId = + const observationId: ObservationTypeWithWaveformTypes = observation.observation_id === "waveform" ? `waveform_${observation["wave-name"]}` : observation.observation_id; @@ -123,23 +137,23 @@ const updateLastObservationData = (flattenedObservations, skipEmpty = true) => { } lastObservationData[observationId] ??= {}; - lastObservationData[observationId][observation.device_id] = observation; + lastObservationData[observationId]![observation.device_id] = observation; }); }; const updateObservationsToCare = async () => { // console.log(dailyRoundTag() + "updateObservationsToCare called") const now = new Date(); - if (now - lastUpdatedToCare < UPDATE_INTERVAL) { + if (now.getTime() - lastUpdatedToCare < UPDATE_INTERVAL) { // only update once per hour // console.log(dailyRoundTag() + "updateObservationsToCare skipped") return; } - lastUpdatedToCare = now; + lastUpdatedToCare = now.getTime(); - const getValueFromData = (data) => { + const getValueFromData = (data: Observation) => { const observationDate = getTime(data?.["date-time"]); - const stale = now - observationDate > UPDATE_INTERVAL; + const stale = now.getTime() - observationDate.getTime() > UPDATE_INTERVAL; const validData = isValid(data); console.log(data); @@ -171,7 +185,10 @@ const updateObservationsToCare = async () => { console.log(dailyRoundTag() + "Performing daily round"); for (const observation of staticObservations) { try { - if (now - observation.last_updated > UPDATE_INTERVAL) { + if ( + now.getTime() - new Date(observation.last_updated).getTime() > + UPDATE_INTERVAL + ) { console.log( dailyRoundTag() + "Skipping stale observations for device: " + @@ -238,8 +255,8 @@ const updateObservationsToCare = async () => { // const temperature_low_limit = data["body-temperature1"]?.[0]?.["low-limit"]; // const temperature_high_limit = data["body-temperature1"]?.[0]?.["high-limit"]; if ( - data["body-temperature1"]?.[0]?.["low-limit"] < temperature && - temperature < data["body-temperature1"]?.[0]?.["high-limit"] + data["body-temperature1"]?.[0]?.["low-limit"]! < temperature! && + temperature! < data["body-temperature1"]?.[0]?.["high-limit"]! ) { temperature_measured_at = rawValues.temperature_measured_at; } else { @@ -247,10 +264,14 @@ const updateObservationsToCare = async () => { } // populate blood-pressure object if data is valid - const bp = {}; + const bp: { + systolic?: number | null; + diastolic?: number | null; + } = {}; if ( data["blood-pressure"]?.[0]?.status === "final" && - now - getTime(data?.["blood-pressure"]?.[0]?.["date-time"]) < + now.getTime() - + getTime(data?.["blood-pressure"]?.[0]?.["date-time"]).getTime() < UPDATE_INTERVAL // check if data is not stale ) { bp.systolic = data["blood-pressure"]?.[0]?.systolic?.value ?? null; @@ -258,7 +279,7 @@ const updateObservationsToCare = async () => { } const spo2 = getValueFromData(data["SpO2"]?.[0]); - const payload = { + const payload: DailyRoundObservation = { spo2, ventilator_spo2: spo2, resp: getValueFromData(data["respiratory-rate"]?.[0]), @@ -274,7 +295,7 @@ const updateObservationsToCare = async () => { console.table(rawValues); console.table(payload); - const payloadHasData = (payload) => + const payloadHasData = (payload: Record): boolean => Object.entries(payload).some(([key, value]) => { if (value === null || value === undefined) { console.log(key, " | Value ", value); @@ -305,13 +326,21 @@ const updateObservationsToCare = async () => { payload.rounds_type = "AUTOMATED"; try { - const { camera, monitorPreset } = await getBedById(bed_id); + const { camera, monitorPreset } = (await getBedById(bed_id)) ?? {}; + + if (!camera) { + throw new Error("Camera not found for bedId: " + bed_id); + } + + if (!monitorPreset) { + throw new Error("Monitor preset not found for bedId: " + bed_id); + } const cameraParams = { hostname: camera.ipAddress, - username: camera.username, - password: camera.password, - port: camera.port, + username: camera.username!, + password: camera.password!, + port: camera.port!, }; console.log("updateObservationsToCare:cameraParams", cameraParams); @@ -389,11 +418,12 @@ const updateObservationsToCare = async () => { const filterStatusData = () => { const MIN_IN_MS = 60000; statusData = statusData.filter( - (status) => new Date() - new Date(status.time) <= 30 * MIN_IN_MS + (status) => + new Date().getTime() - new Date(status.time).getTime() <= 30 * MIN_IN_MS ); }; -const parseDataAsStatus = (data) => { +const parseDataAsStatus = (data: Observation[][]) => { return { time: new Date(new Date().setSeconds(0, 0)).toISOString(), @@ -405,11 +435,11 @@ const parseDataAsStatus = (data) => { }); return acc; - }, {}), + }, {} as ObservationStatus["status"]), }; }; -const addStatusData = (data) => { +const addStatusData = (data: Observation[][]) => { filterStatusData(); const newStatus = parseDataAsStatus(data); @@ -437,7 +467,7 @@ export class ObservationController { static latestObservation = new ObservationsMap(); - static getObservations(req, res) { + static getObservations(req: Request, res: Response) { const limit = req.query?.limit || DEFAULT_LISTING_LIMIT; const ip = req.query?.ip; @@ -445,11 +475,9 @@ export class ObservationController { return res.json(staticObservations); } // console.log("Filtering"); - const filtered = Object.values(staticObservations).reduce((acc, curr) => { - // console.log("curr", curr); - const latestValue = curr[ip]; - return latestValue; - }, []); + const filtered = staticObservations.filter( + (observation) => observation.device_id === ip + ); // Sort the observation by last updated time. // .sort( // (a, b) => new Date(a.lastObservationAt) - new Date(b.lastObservationAt) @@ -460,15 +488,15 @@ export class ObservationController { return res.json(filtered ?? []); } - static getLogData(req, res) { + static getLogData(req: Request, res: Response) { return res.json(logData); } - static getLastRequestData(req, res) { + static getLastRequestData(req: Request, res: Response) { return res.json(lastRequestData); } - static updateObservations = (req, res) => { + static updateObservations = (req: Request, res: Response) => { // database logic lastRequestData = req.body; // console.log("updateObservations", req.body); @@ -491,16 +519,17 @@ export class ObservationController { this.latestObservation.set(flattenedObservations); filterClients(req.wsInstance.getWss(), "/observations").forEach( - (client) => { + (client: WebSocket) => { const filteredObservations = flattenedObservations?.filter( - (observation) => observation?.device_id === client?.params?.ip + (observation: Observation) => + observation?.device_id === client?.params?.ip ); if ( - lastObservationData["blood-pressure"]?.[client?.params?.ip] && + lastObservationData["blood-pressure"]?.[client?.params?.ip!] && dayjs().diff( dayjs( - lastObservationData["blood-pressure"]?.[client?.params?.ip][ + lastObservationData["blood-pressure"]?.[client?.params?.ip!][ "date-time" ] ), @@ -508,7 +537,7 @@ export class ObservationController { ) < 30 ) { filteredObservations?.push( - lastObservationData["blood-pressure"][client?.params?.ip] + lastObservationData["blood-pressure"][client?.params?.ip!] ); } @@ -518,7 +547,7 @@ export class ObservationController { } ); - flattenedObservations.forEach((observation) => { + flattenedObservations.forEach((observation: Observation) => { addObservation(observation); }); @@ -527,16 +556,16 @@ export class ObservationController { return res.send(req.body); }; - static getTime = async (req, res) => { + static getTime = async (req: Request, res: Response) => { res.send({ time: new Date().toISOString(), }); }; - static getLatestVitals = catchAsync(async (req, res) => { + static getLatestVitals = catchAsync(async (req: Request, res: Response) => { const { device_id } = req.query; console.log(this.latestObservation); - const data = this.latestObservation.get(device_id); + const data = this.latestObservation.get(device_id as string); if (!data) throw new NotFoundException(`No data found with device id ${device_id}`); @@ -547,7 +576,7 @@ export class ObservationController { }); }); - static status = catchAsync(async (req, res) => { + static status = catchAsync(async (req: Request, res: Response) => { filterStatusData(); return res.json(statusData); }); diff --git a/src/controller/OpenidConfig.js b/src/controller/OpenidConfig.js deleted file mode 100644 index 9f7a05c..0000000 --- a/src/controller/OpenidConfig.js +++ /dev/null @@ -1,15 +0,0 @@ -import fs from "fs" -import jose from "node-jose" - -export const openidConfigController = async (req, res, next) => { - /* - This endpoint reads the `keys.json` and serves the public key - */ - - const ks = fs.readFileSync("keys.json"); - - const keyStore = await jose.JWK.asKeyStore(ks.toString()); - - res.statusCode = 200 - res.send(keyStore.toJSON()); // toJson converts the keystore into a Public Key JWK -} \ No newline at end of file diff --git a/src/controller/OpenidConfig.ts b/src/controller/OpenidConfig.ts new file mode 100644 index 0000000..6b560fa --- /dev/null +++ b/src/controller/OpenidConfig.ts @@ -0,0 +1,20 @@ +import fs from "fs"; +import jose from "node-jose"; +import type { Request, Response, NextFunction } from "express"; + +export const openidConfigController = async ( + req: Request, + res: Response, + next: NextFunction +) => { + /* + This endpoint reads the `keys.json` and serves the public key + */ + + const ks = fs.readFileSync("keys.json"); + + const keyStore = await jose.JWK.asKeyStore(ks.toString()); + + res.statusCode = 200; + res.send(keyStore.toJSON()); // toJson converts the keystore into a Public Key JWK +}; diff --git a/src/controller/ServerStatusController.js b/src/controller/ServerStatusController.ts similarity index 57% rename from src/controller/ServerStatusController.js rename to src/controller/ServerStatusController.ts index 16132be..d468891 100644 --- a/src/controller/ServerStatusController.js +++ b/src/controller/ServerStatusController.ts @@ -1,16 +1,19 @@ import pidusage from "pidusage"; import { loadavg } from "os"; -import { eventType } from "../utils/eventTypeConstant.js"; -import { filterClients } from "../utils/wsUtils.js"; +import { eventType } from "@/utils/eventTypeConstant"; +import { filterClients } from "@/utils/wsUtils"; +import expressWs from "express-ws"; +import type { WebSocket } from "@/types/ws"; +import type { Request, Response } from "express"; export class ServerStatusController { - static init(ws) { - const server = ws.getWss("/logger"); - let intervalId; - let clients; + static init(ws: expressWs.Instance) { + const server = ws.getWss(); + let intervalId: NodeJS.Timeout | number | undefined = undefined; + let clients: WebSocket[] = []; server.on("connection", () => { - clients = filterClients(server, "/logger") + clients = filterClients(server, "/logger"); if (!intervalId && clients.length !== 0) { intervalId = setInterval(() => { pidusage(process.pid, (err, stat) => { @@ -21,7 +24,7 @@ export class ServerStatusController { if (!server.clients?.size) { clearInterval(intervalId); - intervalId = null; + intervalId = undefined; } const data = { @@ -35,19 +38,19 @@ export class ServerStatusController { clients.forEach((client) => { client.send(JSON.stringify(data)); }); - }); }, 1000); } - clients?.forEach(client => { - client.on("close", () => { clients = filterClients(server, "/logger") }) - }) - + clients?.forEach((client) => { + client.on("close", () => { + clients = filterClients(server, "/logger"); + }); + }); }); } - static render(req, res) { + static render(req: Request, res: Response) { res.render("pages/serverStatus"); } } diff --git a/src/controller/healthCheckController.js b/src/controller/healthCheckController.js deleted file mode 100644 index 1a09fe7..0000000 --- a/src/controller/healthCheckController.js +++ /dev/null @@ -1,48 +0,0 @@ -import axios from 'axios' -import { PrismaClient } from "@prisma/client" -import { generateHeaders } from "../utils/assetUtils.js"; -import { careApi } from '../utils/configs.js'; - - -const prisma = new PrismaClient() - -export const CareCommunicationCheckController = async (req, res, next) => { - let asset = null - if (req.query.ip || req.query.ext_id) { - asset = await prisma.asset.findFirst({ - where: { - OR: [ - { ipAddress: req.query.ip }, - { externalId: req.query.ext_id } - ], - AND: { - deleted: false - } - } - }) - } else { - asset = await prisma.asset.findFirst({ - where: { - AND: { - deleted: false - } - } - }) - } - if (asset === null) { - return res.status(404).json({ "error": "No active asset found" }) - } - - const value = await axios.get( - `${careApi}/middleware/verify`, - { - headers: await generateHeaders(asset.externalId) - } - ).then(res => { - return res.data - }).catch(error => { - res.status(500) - return { "error": error?.response?.data } - }) - return res.send(value) -}; diff --git a/src/controller/healthCheckController.ts b/src/controller/healthCheckController.ts new file mode 100644 index 0000000..26f49af --- /dev/null +++ b/src/controller/healthCheckController.ts @@ -0,0 +1,52 @@ +import axios, { type AxiosRequestHeaders } from "axios"; +import { PrismaClient } from "@prisma/client"; +import { generateHeaders } from "@/utils/assetUtils"; +import { careApi } from "@/utils/configs"; +import type { Request, Response, NextFunction } from "express"; + +const prisma = new PrismaClient(); + +export const CareCommunicationCheckController = async ( + req: Request, + res: Response, + next: NextFunction +) => { + let asset = null; + if (req.query.ip || req.query.ext_id) { + asset = await prisma.asset.findFirst({ + where: { + OR: [ + { ipAddress: req.query.ip as string }, + { externalId: req.query.ext_id as string }, + ], + AND: { + deleted: false, + }, + }, + }); + } else { + asset = await prisma.asset.findFirst({ + where: { + AND: { + deleted: false, + }, + }, + }); + } + if (asset === null) { + return res.status(404).json({ error: "No active asset found" }); + } + + const value = await axios + .get(`${careApi}/middleware/verify`, { + headers: (await generateHeaders(asset.externalId)) as AxiosRequestHeaders, + }) + .then((res) => { + return res.data; + }) + .catch((error) => { + res.status(500); + return { error: error?.response?.data }; + }); + return res.send(value); +}; diff --git a/src/controller/helper/makeDataDump.js b/src/controller/helper/makeDataDump.ts similarity index 54% rename from src/controller/helper/makeDataDump.js rename to src/controller/helper/makeDataDump.ts index 3e890ce..b31e4f8 100644 --- a/src/controller/helper/makeDataDump.js +++ b/src/controller/helper/makeDataDump.ts @@ -6,16 +6,18 @@ import { s3Endpoint, s3Provider, s3SecretAccessKey, -} from "../../utils/configs.js"; +} from "@/utils/configs"; +import type { DailyRoundObservation } from "@/types/observation"; +import type { OCRObservationV1Sanitized } from "@/types/ocr"; dotenv.config({ path: "./.env" }); export const makeDataDumpToJson = async ( - v1Payload, - v2Payload, - assetExternalId, - patient_id, - consultation_id + v1Payload: DailyRoundObservation, + v2Payload: OCRObservationV1Sanitized, + assetExternalId: string, + patient_id: string, + consultation_id: string ) => { try { const s3 = new AWS.S3({ @@ -33,22 +35,30 @@ export const makeDataDumpToJson = async ( v2Payload: v2Payload, }; + if (!s3BucketName) { + throw new Error("S3 Bucket Name not found"); + } + const params = { Bucket: s3BucketName, Key: `${assetExternalId}--${new Date().getTime()}.json`, Body: JSON.stringify(dataDump), + "Content-Type": "application/json", }; await new Promise((resolve, reject) => { - s3.upload(params, function (err, data) { - if (err) { - console.log("Auto OCR Upload error"); - reject(err); - } else { - console.log("Auto OCR Upload Success"); - resolve(data); + s3.upload( + params, + function (err: Error, data: AWS.S3.ManagedUpload.SendData) { + if (err) { + console.log("Auto OCR Upload error"); + reject(err); + } else { + console.log("Auto OCR Upload Success"); + resolve(data); + } } - }); + ); }); } catch (err) { console.log(err); diff --git a/src/middleware/errorHandler.js b/src/middleware/errorHandler.ts similarity index 69% rename from src/middleware/errorHandler.js rename to src/middleware/errorHandler.ts index 0efbdf6..be485e6 100644 --- a/src/middleware/errorHandler.js +++ b/src/middleware/errorHandler.ts @@ -1,7 +1,8 @@ -import { eventType } from "../utils/eventTypeConstant.js"; -import { filterClients } from "../utils/wsUtils.js"; +import { eventType } from "@/utils/eventTypeConstant"; +import { filterClients } from "@/utils/wsUtils"; +import type { Response, ErrorRequestHandler } from "express"; -const sendDevError = (err, res) => { +const sendDevError = (err: any, res: Response) => { res.status(err.statusCode).json({ status: err.status, error: err, @@ -10,18 +11,17 @@ const sendDevError = (err, res) => { }); }; -const sendProdError = (err, res) => { +const sendProdError = (err: any, res: Response) => { res.status(err.statusCode).json({ status: err.status, message: err.message, }); }; -export const errorHandler = (err, req, res, next) => { +export const errorHandler: ErrorRequestHandler = (err, req, res) => { err.statusCode = err.statusCode || 500; err.status = err.status || "error"; - const env = process.env.NODE_ENV; const data = { diff --git a/src/middleware/getWs.js b/src/middleware/getWs.js deleted file mode 100644 index 039e25b..0000000 --- a/src/middleware/getWs.js +++ /dev/null @@ -1,4 +0,0 @@ -export const getWs = (ws) => (req, res, next) => { - req.wsInstance = ws - next() -} \ No newline at end of file diff --git a/src/middleware/getWs.ts b/src/middleware/getWs.ts new file mode 100644 index 0000000..e519d3e --- /dev/null +++ b/src/middleware/getWs.ts @@ -0,0 +1,15 @@ +import expressWs from "express-ws"; +import type { Request, Response, NextFunction } from "express"; + +declare module "express-serve-static-core" { + export interface Request { + wsInstance: any; + } +} + +export const getWs = + (ws: expressWs.Instance) => + (req: Request, res: Response, next: NextFunction) => { + req.wsInstance = ws; + next(); + }; diff --git a/src/middleware/morganWithWs.js b/src/middleware/morganWithWs.ts similarity index 69% rename from src/middleware/morganWithWs.js rename to src/middleware/morganWithWs.ts index c4c9590..499cce3 100644 --- a/src/middleware/morganWithWs.js +++ b/src/middleware/morganWithWs.ts @@ -1,8 +1,9 @@ import morgan from "morgan"; -import { eventType } from "../utils/eventTypeConstant.js"; -import { filterClients } from "../utils/wsUtils.js"; +import { eventType } from "@/utils/eventTypeConstant"; +import { filterClients } from "@/utils/wsUtils"; +import type { Request } from "express"; -export const morganWithWs = morgan(function (tokens, req, res) { +export const morganWithWs = morgan(function (tokens, req: Request, res) { const data = { time: new Date().toISOString(), method: tokens.method(req, res), diff --git a/src/middleware/validate.js b/src/middleware/validate.ts similarity index 54% rename from src/middleware/validate.js rename to src/middleware/validate.ts index dae077c..4b77ea5 100644 --- a/src/middleware/validate.js +++ b/src/middleware/validate.ts @@ -1,8 +1,8 @@ -import express from "express"; -import { validationResult } from "express-validator"; +import type { Request, Response, NextFunction } from "express"; +import { validationResult, type ValidationChain } from "express-validator"; -export const validate = (validations) => { - return async (req, res, next) => { +export const validate = (validations: ValidationChain[]) => { + return async (req: Request, res: Response, next: NextFunction) => { await Promise.all(validations.map((validation) => validation.run(req))); const errors = validationResult(req); diff --git a/src/router/assetConfigRouter.js b/src/router/assetConfigRouter.ts similarity index 86% rename from src/router/assetConfigRouter.js rename to src/router/assetConfigRouter.ts index e3744c6..97ed647 100644 --- a/src/router/assetConfigRouter.js +++ b/src/router/assetConfigRouter.ts @@ -1,5 +1,5 @@ import express from "express"; -import { AssetConfigController } from "../controller/AssetConfigController.js"; +import { AssetConfigController } from "@/controller/AssetConfigController"; const router = express.Router(); diff --git a/src/router/authRouter.js b/src/router/authRouter.ts similarity index 69% rename from src/router/authRouter.js rename to src/router/authRouter.ts index dca6f4f..1a2966d 100644 --- a/src/router/authRouter.js +++ b/src/router/authRouter.ts @@ -1,5 +1,5 @@ import express from "express"; -import { AuthController } from "../controller/AuthController.js"; +import { AuthController } from "@/controller/AuthController"; const router = express.Router(); diff --git a/src/router/bedRouter.js b/src/router/bedRouter.ts similarity index 85% rename from src/router/bedRouter.js rename to src/router/bedRouter.ts index 198c9ec..f83733e 100644 --- a/src/router/bedRouter.js +++ b/src/router/bedRouter.ts @@ -1,5 +1,5 @@ import express from "express"; -import { BedController } from "../controller/BedController.js"; +import { BedController } from "@/controller/BedController"; const router = express.Router(); diff --git a/src/router/cameraRouter.js b/src/router/cameraRouter.ts similarity index 76% rename from src/router/cameraRouter.js rename to src/router/cameraRouter.ts index 3f59e27..80b0813 100644 --- a/src/router/cameraRouter.js +++ b/src/router/cameraRouter.ts @@ -1,15 +1,13 @@ import express from "express"; - -import { CameraController } from "../controller/CameraController.js"; -import { validate } from "../middleware/validate.js"; +import { CameraController } from "@/controller/CameraController"; +import { validate } from "@/middleware/validate"; import { - baseCameraParamsValidators, setPresetValidators, baseGetCameraParamsValidators, camMoveValidator, gotoPresetValidator, -} from "../Validators/cameraValidators.js"; +} from "@/Validators/cameraValidators"; const router = express.Router(); @@ -31,10 +29,7 @@ router.get( CameraController.getStatus ); -router.post( - "/cameras/status", - CameraController.getCameraStatuses -); +router.post("/cameras/status", CameraController.getCameraStatuses); router.post( "/gotoPreset", diff --git a/src/router/configRouter.js b/src/router/configRouter.ts similarity index 75% rename from src/router/configRouter.js rename to src/router/configRouter.ts index b41f748..979742c 100644 --- a/src/router/configRouter.js +++ b/src/router/configRouter.ts @@ -1,5 +1,5 @@ import express from "express"; -import { ConfigController } from "../controller/ConfigController.js"; +import { ConfigController } from "@/controller/ConfigController"; const router = express.Router(); diff --git a/src/router/healthRouter.js b/src/router/healthRouter.ts similarity index 65% rename from src/router/healthRouter.js rename to src/router/healthRouter.ts index 7b63e2e..89c0373 100644 --- a/src/router/healthRouter.js +++ b/src/router/healthRouter.ts @@ -1,9 +1,8 @@ import express from "express"; -import { CareCommunicationCheckController } from "../controller/healthCheckController.js"; +import { CareCommunicationCheckController } from "@/controller/healthCheckController"; const router = express.Router(); router.get("/health/care/communication", CareCommunicationCheckController); export { router as healthRouter }; - diff --git a/src/router/observationRouter.js b/src/router/observationRouter.ts similarity index 77% rename from src/router/observationRouter.js rename to src/router/observationRouter.ts index 5dd1dd2..8b9d9f7 100644 --- a/src/router/observationRouter.js +++ b/src/router/observationRouter.ts @@ -1,12 +1,11 @@ import { - autoObservationValidator, observationsValidators, vitalsValidator, -} from "../Validators/observationValidators.js"; +} from "@/Validators/observationValidators"; -import { ObservationController } from "../controller/ObservationController.js"; +import { ObservationController } from "@/controller/ObservationController"; import express from "express"; -import { validate } from "../middleware/validate.js"; +import { validate } from "@/middleware/validate"; const router = express.Router(); diff --git a/src/router/serverStatusRouter.js b/src/router/serverStatusRouter.ts similarity index 67% rename from src/router/serverStatusRouter.js rename to src/router/serverStatusRouter.ts index fb705f1..0acb5b1 100644 --- a/src/router/serverStatusRouter.js +++ b/src/router/serverStatusRouter.ts @@ -1,5 +1,5 @@ import express from "express"; -import { ServerStatusController } from "../controller/ServerStatusController.js"; +import { ServerStatusController } from "@/controller/ServerStatusController"; const router = express.Router(); diff --git a/src/server.js b/src/server.ts similarity index 64% rename from src/server.js rename to src/server.ts index f4fcc46..ba2e08b 100644 --- a/src/server.js +++ b/src/server.ts @@ -1,5 +1,5 @@ import * as dotenv from "dotenv"; -dotenv.config({ path: "./.env" }); +dotenv.config(); import cors from "cors"; import path from "path"; @@ -12,31 +12,33 @@ import swaggerUi from "swagger-ui-express"; import * as Sentry from "@sentry/node"; import * as Tracing from "@sentry/tracing"; -import { cameraRouter } from "./router/cameraRouter.js"; -import { configRouter } from "./router/configRouter.js"; -import { assetConfigRouter } from "./router/assetConfigRouter.js"; -import { authRouter } from "./router/authRouter.js"; +import { cameraRouter } from "@/router/cameraRouter"; +import { configRouter } from "@/router/configRouter"; +import { assetConfigRouter } from "@/router/assetConfigRouter"; +import { authRouter } from "@/router/authRouter"; +import { errorHandler } from "@/middleware/errorHandler"; +import { observationRouter } from "@/router/observationRouter"; +import { notFoundController } from "@/controller/NotFoundController"; -import { errorHandler } from "./middleware/errorHandler.js"; -import { observationRouter } from "./router/observationRouter.js"; -import { notFoundController } from "./controller/NotFoundController.js"; +import { swaggerSpec } from "@/swagger/swagger"; +import { morganWithWs } from "@/middleware/morganWithWs"; +import { serverStatusRouter } from "@/router/serverStatusRouter"; +import { healthRouter } from "@/router/healthRouter"; +import { bedRouter } from "@/router/bedRouter"; -import { swaggerSpec } from "./swagger/swagger.js"; -import { morganWithWs } from "./middleware/morganWithWs.js"; -import { serverStatusRouter } from "./router/serverStatusRouter.js"; -import { healthRouter } from "./router/healthRouter.js"; -import { bedRouter } from "./router/bedRouter.js"; +import { ServerStatusController } from "@/controller/ServerStatusController"; +import { getWs } from "@/middleware/getWs"; -import { ServerStatusController } from "./controller/ServerStatusController.js"; -import { getWs } from "./middleware/getWs.js"; +import { openidConfigController } from "@/controller/OpenidConfig"; -import { openidConfigController } from "./controller/OpenidConfig.js"; +import type { WebSocket } from "@/types/ws"; const PORT = process.env.PORT || 8090; -const app = express(); -const ws = enableWs(app); +const appBase = express(); +const ws = enableWs(appBase); +const { app } = ws; app.set("view engine", "ejs"); app.set("views", path.join(path.resolve(), "src/views")); @@ -44,10 +46,12 @@ app.set("views", path.join(path.resolve(), "src/views")); // flash messages app.use( session({ - cookieName: "session", + name: "session", secret: "ufhq7s-o1%^bn7j6wasec04-mjb*zv^&0@$lb3%9%w3t5pq3^3", - httpOnly: true, - maxAge: 1000 * 60 * 30, + cookie: { + httpOnly: true, + maxAge: 1000 * 60 * 30, + }, resave: true, saveUninitialized: true, }) @@ -70,7 +74,7 @@ Sentry.init({ new Tracing.Integrations.Express({ app }), ], environment: process.env.SENTRY_ENV, - tracesSampleRate: parseFloat(process.env.SENTRY_SAMPLE_RATE) || 0.01, + tracesSampleRate: parseFloat(process.env.SENTRY_SAMPLE_RATE || "0.01"), }); app.use(Sentry.Handlers.requestHandler()); @@ -108,10 +112,10 @@ app.use("/beds", bedRouter); app.get("/.well-known/openid-configuration", openidConfigController); -app.ws("/logger", (ws, req) => { +app.ws("/logger", (ws: WebSocket, req) => { ws.route = "/logger"; }); -app.ws("/observations/:ip", (ws, req) => { +app.ws("/observations/:ip", (ws: WebSocket, req) => { ws.route = "/observations"; ws.params = req.params; }); diff --git a/src/swagger/common.js b/src/swagger/common.ts similarity index 100% rename from src/swagger/common.js rename to src/swagger/common.ts diff --git a/src/swagger/swagger.js b/src/swagger/swagger.ts similarity index 86% rename from src/swagger/swagger.js rename to src/swagger/swagger.ts index 11d7f7d..2d0595d 100644 --- a/src/swagger/swagger.js +++ b/src/swagger/swagger.ts @@ -21,8 +21,8 @@ const options = { swaggerDefinition, // Paths to files containing OpenAPI definitions apis: [ - path.join(CUR_DIR, "src/controller/*.js"), - path.join(CUR_DIR, "src/swagger/common.js"), + path.join(CUR_DIR, "src/controller/*.ts"), + path.join(CUR_DIR, "src/swagger/common.ts"), ], }; diff --git a/src/types/camera.ts b/src/types/camera.ts new file mode 100644 index 0000000..f0dad2b --- /dev/null +++ b/src/types/camera.ts @@ -0,0 +1,22 @@ +export interface CameraParams { + useSecure: boolean; + hostname: string; + username: string; + password: string; + port: number; +} + +export interface CameraPreset { + x: number; + y: number; + zoom: number; +} + +export interface CameraStatus { + time: string; + status: { + [device_id: string]: "up" | "down"; + }; +} + +export type CameraAsset = Omit; diff --git a/src/types/external/onvif.d.ts b/src/types/external/onvif.d.ts new file mode 100644 index 0000000..f1c1bd7 --- /dev/null +++ b/src/types/external/onvif.d.ts @@ -0,0 +1,8 @@ +declare module "onvif" { + export class Cam { + constructor(params: any, callback: (err: any) => void); + gotoPreset(params: any, callback: (err: any) => void); + getPresets(params: any, callback: (err: any) => void); + getStatus(params: any, callback: (err: any) => void); + } +} diff --git a/src/types/observation.ts b/src/types/observation.ts new file mode 100644 index 0000000..624cf02 --- /dev/null +++ b/src/types/observation.ts @@ -0,0 +1,148 @@ +export type Observation = + | { + observation_id: + | "heart-rate" + | "ST" + | "SpO2" + | "pulse-rate" + | "respiratory-rate" + | "body-temperature1" + | "body-temperature2"; + device_id: string; + "date-time": string; + "patient-id": string; + "patient-name": string; + status: + | "final" + | "Message-Leads Off" + | "Message-Measurement Invalid" + | "Message-Tachy Cardia" + | "Message-Probe Unplugged" + | null + | undefined; + value: number | null | undefined; + data?: undefined; + unit: string; + interpretation?: "normal" | "low" | "high" | "NA"; + "low-limit": number; + "high-limit": number; + systolic?: undefined; + diastolic?: undefined; + map?: undefined; + } + | { + observation_id: "blood-pressure"; + device_id: string; + "date-time": string; + "patient-id": string; + status: "final" | null | undefined; + value?: undefined; + data?: undefined; + unit?: undefined; + interpretation?: undefined; + "low-limit"?: undefined; + "high-limit"?: undefined; + systolic: { + value: number | null | undefined; + unit: string; + interpretation?: "normal" | "low" | "high" | "NA"; + "low-limit": number; + "high-limit": number; + }; + diastolic: { + value: number | null | undefined; + unit: string; + interpretation?: "normal" | "low" | "high" | "NA"; + "low-limit": number; + "high-limit": number; + }; + map?: { + value: number | null | undefined; + unit: string; + interpretation?: "normal" | "low" | "high" | "NA"; + "low-limit": number; + "high-limit": number; + }; + } + | { + observation_id: "waveform"; + device_id: string; + "date-time": string; + "patient-id": string; + "patient-name": string; + "wave-name": "II" | "Pleth" | "Respiration"; + resolution: string; + "sampling rate": string; + "data-baseline": number; + "data-low-limit": number; + "data-high-limit": number; + data: string; + status?: undefined; + value?: undefined; + unit?: undefined; + interpretation?: undefined; + "low-limit"?: undefined; + "high-limit"?: undefined; + systolic?: undefined; + diastolic?: undefined; + map?: undefined; + } + | { + observation_id: "device-connection"; + device_id: string; + "date-time": string; + "patient-id": string; + "patient-name": string; + status: "Connected" | "Disconnected"; + value?: undefined; + data?: undefined; + unit?: undefined; + interpretation?: undefined; + "low-limit"?: undefined; + "high-limit"?: undefined; + systolic?: undefined; + diastolic?: undefined; + map?: undefined; + }; + +export type ObservationType = Observation["observation_id"]; + +export interface DailyRoundObservation { + spo2?: number | null; + ventilator_spo2?: number | null; + resp?: number | null; + pulse?: number | null; + temperature?: number | null; + temperature_measured_at?: string | null; + bp?: { + systolic?: number | null; + diastolic?: number | null; + } | null; + taken_at?: string | Date | null; + rounds_type?: "AUTOMATED"; +} + +export interface ObservationStatus { + time: string; + status: { + [device_id: string]: "up" | "down"; + }; +} + +export interface StaticObservation { + device_id: string; + observations: Record; + last_updated: Date; +} + +export type ObservationTypeWithWaveformTypes = + | ObservationType + | "waveform_II" + | "waveform_Pleth" + | "waveform_Respiration"; + +export type LastObservationData = { + [observation_id in ObservationTypeWithWaveformTypes]?: { + [device_id: string]: Observation; + }; +}; diff --git a/src/types/ocr.ts b/src/types/ocr.ts new file mode 100644 index 0000000..917b47e --- /dev/null +++ b/src/types/ocr.ts @@ -0,0 +1,20 @@ +export interface OCRObservationV1Raw { + SpO2?: number | string | null; + "Respiratory Rate"?: number | string | null; + "Pulse Rate"?: number | string | null; + Temperature?: number | string | null; + "Blood Pressure"?: number | string | null; +} + +export interface OCRObservationV1Sanitized { + spo2?: number | null; + ventilator_spo2?: number | null; + resp?: number | null; + pulse?: number | null; + temperature?: number | null; + bp?: { + systolic: number | null; + mean: number | null; + diastolic: number | null; + } | null; +} diff --git a/src/types/ws.ts b/src/types/ws.ts new file mode 100644 index 0000000..6e33ad3 --- /dev/null +++ b/src/types/ws.ts @@ -0,0 +1,6 @@ +import type { WebSocket as InitialWebSocket } from "ws"; + +export interface WebSocket extends InitialWebSocket { + route?: string; + params?: Record; +} diff --git a/src/utils/ObservationUtils.js b/src/utils/ObservationUtils.ts similarity index 95% rename from src/utils/ObservationUtils.js rename to src/utils/ObservationUtils.ts index 67e7f7e..6b7c012 100644 --- a/src/utils/ObservationUtils.js +++ b/src/utils/ObservationUtils.ts @@ -1,3 +1,5 @@ +import type { Observation } from "@/types/observation"; + export const messages = [ { message: "Leads Off", @@ -193,23 +195,19 @@ export const messages = [ }, ]; -export const isValid = (observation) => { - if ( - observation === null || - observation === undefined || - observation.status === null || - observation.status === undefined || - observation.value === null || - observation.value === undefined - ) { +export const isValid = (observation: Observation) => { + if (!observation || !observation.status || isNaN(observation.value ?? NaN)) { return false; } + if (observation.status === "final") return true; + const message = observation.status.replace("Message-", ""); const messageObj = messages.find((m) => m.message === message); - if (messageObj.invalid) { + if (messageObj?.invalid) { return false; } + return true; }; diff --git a/src/utils/ObservationsMap.js b/src/utils/ObservationsMap.ts similarity index 79% rename from src/utils/ObservationsMap.js rename to src/utils/ObservationsMap.ts index 08b189b..831d254 100644 --- a/src/utils/ObservationsMap.js +++ b/src/utils/ObservationsMap.ts @@ -1,14 +1,15 @@ import groupBy from "lodash.groupby"; export class ObservationsMap { - #observations = {}; + #observations: Record = {}; constructor() {} - get(id) { + get(id: string) { return this.#observations[id]; } - set(data) { + // TODO: remove any + set(data: any) { if (data) { const newData = groupBy(data, "device_id"); diff --git a/src/utils/assetUtils.js b/src/utils/assetUtils.js deleted file mode 100644 index bcc2004..0000000 --- a/src/utils/assetUtils.js +++ /dev/null @@ -1,11 +0,0 @@ - -import { generateJWT } from "./generateJWT.js" -import { facilityID } from "./configs.js" - -export const generateHeaders = async (asset_id) => { - return { - Accept: "application/json", - Authorization: "Middleware_Bearer " + await generateJWT({ asset_id }), - "X-Facility-Id": facilityID - } -} \ No newline at end of file diff --git a/src/utils/assetUtils.ts b/src/utils/assetUtils.ts new file mode 100644 index 0000000..88b5910 --- /dev/null +++ b/src/utils/assetUtils.ts @@ -0,0 +1,11 @@ +import { generateJWT } from "@/utils/generateJWT"; +import { facilityID } from "@/utils/configs"; +import type { AxiosRequestHeaders } from "axios"; + +export const generateHeaders = async (asset_id: string) => { + return { + Accept: "application/json", + Authorization: "Middleware_Bearer " + (await generateJWT({ asset_id })), + "X-Facility-Id": facilityID, + } as AxiosRequestHeaders; +}; diff --git a/src/utils/catchAsync.js b/src/utils/catchAsync.js deleted file mode 100644 index 60d9674..0000000 --- a/src/utils/catchAsync.js +++ /dev/null @@ -1,2 +0,0 @@ -export const catchAsync = (fn) => (req, res, next) => - fn(req, res, next).catch((err) => next(err)); diff --git a/src/utils/catchAsync.ts b/src/utils/catchAsync.ts new file mode 100644 index 0000000..38b6264 --- /dev/null +++ b/src/utils/catchAsync.ts @@ -0,0 +1,6 @@ +import type { NextFunction, Request, Response } from "express"; + +export const catchAsync = + (fn: Function) => + (request: Request, response: Response, next: NextFunction) => + Promise.resolve(fn(request, response, next)).catch(next); diff --git a/src/utils/configs.js b/src/utils/configs.ts similarity index 74% rename from src/utils/configs.js rename to src/utils/configs.ts index 0e55ba7..0f567d8 100644 --- a/src/utils/configs.js +++ b/src/utils/configs.ts @@ -3,14 +3,17 @@ export const careApi = process.env.CARE_API; // S3 export const s3Provider = process.env.S3_PROVIDER || "AWS"; -export const s3Endpoint = process.env.S3_ENDPOINT || ({ +export const s3Endpoint = + process.env.S3_ENDPOINT || + { AWS: "https://s3.amazonaws.com", GCP: "https://storage.googleapis.com", -})[s3Provider]; + }[s3Provider]; export const s3BucketName = process.env.S3_BUCKET_NAME; export const s3AccessKeyId = process.env.S3_ACCESS_KEY_ID; export const s3SecretAccessKey = process.env.S3_SECRET_ACCESS_KEY; export const saveOCRImages = process.env.SAVE_OCR_IMAGES === "true"; -export const waitBeforeOCRCapture = - parseInt(process.env.WAIT_BEFORE_OCR_CAPTURE) || 0; \ No newline at end of file +export const waitBeforeOCRCapture = parseInt( + process.env.WAIT_BEFORE_OCR_CAPTURE || "0" +); diff --git a/src/utils/dailyRoundUtils.js b/src/utils/dailyRoundUtils.ts similarity index 57% rename from src/utils/dailyRoundUtils.js rename to src/utils/dailyRoundUtils.ts index 31d4e86..2a756c4 100644 --- a/src/utils/dailyRoundUtils.js +++ b/src/utils/dailyRoundUtils.ts @@ -1,28 +1,28 @@ -import { PrismaClient } from "@prisma/client" -import axios from 'axios' -import { generateHeaders } from "./assetUtils.js" -import { careApi } from "./configs.js" +import { Prisma, PrismaClient } from "@prisma/client"; +import axios from "axios"; +import { generateHeaders } from "@/utils/assetUtils"; +import { careApi } from "@/utils/configs"; +import type { AxiosRequestHeaders } from "axios"; +const prisma = new PrismaClient(); -const prisma = new PrismaClient() - -export const getAsset = async (assetIp) => { +export const getAsset = async (assetIp: string) => { return await prisma.asset.findFirst({ where: { ipAddress: { - equals: assetIp + equals: assetIp, }, deleted: { - equals: false - } - } - }) -} + equals: false, + }, + }, + }); +}; -export const getPatientId = async (assetExternalId) => { +export const getPatientId = async (assetExternalId: string) => { return await axios .get(`${careApi}/api/v1/consultation/patient_from_asset/`, { - headers: await generateHeaders(assetExternalId), + headers: (await generateHeaders(assetExternalId)) as AxiosRequestHeaders, }) .then((res) => res.data) .catch((err) => { @@ -33,7 +33,7 @@ export const getPatientId = async (assetExternalId) => { console.log(err?.response?.status, err?.response?.data); return {}; }); -} +}; // export const getAssetsByBedId = async (bedId) => { // return await axios @@ -47,8 +47,11 @@ export const getPatientId = async (assetExternalId) => { // }); // }; - -export const getBedById = async (bedId) => { +export const getBedById = async ( + bedId: string +): Promise> => { return prisma.bed.findFirst({ where: { externalId: bedId, diff --git a/src/utils/eventTypeConstant.js b/src/utils/eventTypeConstant.ts similarity index 100% rename from src/utils/eventTypeConstant.js rename to src/utils/eventTypeConstant.ts diff --git a/src/utils/generateJWT.js b/src/utils/generateJWT.js deleted file mode 100644 index fd0a2a2..0000000 --- a/src/utils/generateJWT.js +++ /dev/null @@ -1,27 +0,0 @@ -import fs from "fs" -import jose from "node-jose" - -export const generateJWT = async (claims ) => { - /* - Creates a JWT token with the given claims with an expiry of 1 min, - This token can be used to call care. - */ - - const JWKeys = fs.readFileSync("keys.json"); - - const keyStore = await jose.JWK.asKeyStore(JWKeys.toString()); - - const [key] = keyStore.all({ use: "sig" }); - - const opt = { compact: true, jwk: key, fields: { typ: "jwt" } }; - - const payload = JSON.stringify({ - exp: Math.floor((Date.now() / 1000 + (60))), - iat: Math.floor(Date.now() / 1000), - ...claims - }); - - const token = await jose.JWS.createSign(opt, key).update(payload).final(); - - return token -} \ No newline at end of file diff --git a/src/utils/generateJWT.ts b/src/utils/generateJWT.ts new file mode 100644 index 0000000..3cd4d5a --- /dev/null +++ b/src/utils/generateJWT.ts @@ -0,0 +1,38 @@ +import fs from "fs"; +import jose from "node-jose"; + +interface JwtPayload { + [key: string]: any; + iss?: string | undefined; + sub?: string | undefined; + aud?: string | string[] | undefined; + exp?: number | undefined; + nbf?: number | undefined; + iat?: number | undefined; + jti?: string | undefined; +} + +export const generateJWT = async (claims: JwtPayload) => { + /* + Creates a JWT token with the given claims with an expiry of 1 min, + This token can be used to call care. + */ + + const JWKeys = fs.readFileSync("keys.json"); + + const keyStore = await jose.JWK.asKeyStore(JWKeys.toString()); + + const [key] = keyStore.all({ use: "sig" }); + + const opt = { compact: true, jwk: key, fields: { typ: "jwt" } }; + + const payload = JSON.stringify({ + exp: Math.floor(Date.now() / 1000 + 60), + iat: Math.floor(Date.now() / 1000), + ...claims, + }); + + const token = await jose.JWS.createSign(opt, key).update(payload).final(); + + return token; +}; diff --git a/src/utils/wsUtils.js b/src/utils/wsUtils.js deleted file mode 100644 index a57a69c..0000000 --- a/src/utils/wsUtils.js +++ /dev/null @@ -1,4 +0,0 @@ -export const filterClients = (ws, path) => { - // console.log("CLEINT", ws.clients) - return Array.from(ws?.clients || []).filter((client) => client.route === path); -} \ No newline at end of file diff --git a/src/utils/wsUtils.ts b/src/utils/wsUtils.ts new file mode 100644 index 0000000..c3381f9 --- /dev/null +++ b/src/utils/wsUtils.ts @@ -0,0 +1,9 @@ +import type { Server } from "ws"; +import type { WebSocket } from "@/types/ws"; + +export const filterClients = (ws: Server, path: string) => { + // console.log("CLEINT", ws.clients) + return Array.from(ws?.clients || []).filter( + (client: WebSocket) => client.route === path + ); +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..445d7cf --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES6", // Set the target ECMAScript version + "module": "commonjs", // Use CommonJS modules for Node.js + "outDir": "./dist", // Specify the output directory for compiled files + "rootDir": "./", // Specify the root directory of your TypeScript source files + + "esModuleInterop": true, // Enable ES Module Interoperability + "strict": true, // Enable strict type checking + "skipLibCheck": true, // Skip type checking of declaration files + "forceConsistentCasingInFileNames": true, // Enforce consistent file name casing + "moduleResolution": "node", // Use Node.js-style module resolution + "allowJs": true, // Allow JavaScript files to be compiled + + "baseUrl": "./", // Set the base URL for module resolution + "paths": { + "@/*": ["src/*"] // Configure path aliases for imports + } + } +}