Browse Source

set up API for testing

Dr-Swopt 6 months ago
parent
commit
03cf78dae6

+ 543 - 3
package-lock.json

@@ -11,7 +11,14 @@
       "dependencies": {
         "@nestjs/common": "^11.0.1",
         "@nestjs/core": "^11.0.1",
+        "@nestjs/jwt": "^11.0.0",
+        "@nestjs/passport": "^11.0.5",
         "@nestjs/platform-express": "^11.0.1",
+        "@nestjs/platform-socket.io": "^11.1.3",
+        "@nestjs/websockets": "^11.1.3",
+        "bcrypt": "^6.0.0",
+        "passport": "^0.7.0",
+        "passport-jwt": "^4.0.1",
         "reflect-metadata": "^0.2.2",
         "rxjs": "^7.8.1"
       },
@@ -2542,6 +2549,29 @@
         }
       }
     },
+    "node_modules/@nestjs/jwt": {
+      "version": "11.0.0",
+      "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.0.tgz",
+      "integrity": "sha512-v7YRsW3Xi8HNTsO+jeHSEEqelX37TVWgwt+BcxtkG/OfXJEOs6GZdbdza200d6KqId1pJQZ6UPj1F0M6E+mxaA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/jsonwebtoken": "9.0.7",
+        "jsonwebtoken": "9.0.2"
+      },
+      "peerDependencies": {
+        "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0"
+      }
+    },
+    "node_modules/@nestjs/passport": {
+      "version": "11.0.5",
+      "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-11.0.5.tgz",
+      "integrity": "sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@nestjs/common": "^10.0.0 || ^11.0.0",
+        "passport": "^0.5.0 || ^0.6.0 || ^0.7.0"
+      }
+    },
     "node_modules/@nestjs/platform-express": {
       "version": "11.1.3",
       "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.3.tgz",
@@ -2563,6 +2593,25 @@
         "@nestjs/core": "^11.0.0"
       }
     },
+    "node_modules/@nestjs/platform-socket.io": {
+      "version": "11.1.3",
+      "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.1.3.tgz",
+      "integrity": "sha512-jQ+ccprmh3kKolBp+bb97zoaS3vKaiyeNqyctGqV4CSG8P6mXSaaUObWxAsw6Jdgn5YQAVEBWJ6FhvF4s6QZbg==",
+      "license": "MIT",
+      "dependencies": {
+        "socket.io": "4.8.1",
+        "tslib": "2.8.1"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/nest"
+      },
+      "peerDependencies": {
+        "@nestjs/common": "^11.0.0",
+        "@nestjs/websockets": "^11.0.0",
+        "rxjs": "^7.1.0"
+      }
+    },
     "node_modules/@nestjs/schematics": {
       "version": "11.0.5",
       "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.5.tgz",
@@ -2689,6 +2738,29 @@
         }
       }
     },
+    "node_modules/@nestjs/websockets": {
+      "version": "11.1.3",
+      "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.1.3.tgz",
+      "integrity": "sha512-IjhWKfRf0D247JxYIEs8USblJJbcxUsKJpzbCPaZ7TrVy4LrpG3IRQDlSTOw599TRIYP5ixyH9C0+v5DyaI9uA==",
+      "license": "MIT",
+      "dependencies": {
+        "iterare": "1.2.1",
+        "object-hash": "3.0.0",
+        "tslib": "2.8.1"
+      },
+      "peerDependencies": {
+        "@nestjs/common": "^11.0.0",
+        "@nestjs/core": "^11.0.0",
+        "@nestjs/platform-socket.io": "^11.0.0",
+        "reflect-metadata": "^0.1.12 || ^0.2.0",
+        "rxjs": "^7.1.0"
+      },
+      "peerDependenciesMeta": {
+        "@nestjs/platform-socket.io": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@noble/hashes": {
       "version": "1.8.0",
       "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
@@ -2826,6 +2898,12 @@
         "@sinonjs/commons": "^3.0.0"
       }
     },
+    "node_modules/@socket.io/component-emitter": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
+      "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
+      "license": "MIT"
+    },
     "node_modules/@swc/cli": {
       "version": "0.6.0",
       "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.6.0.tgz",
@@ -3261,6 +3339,15 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@types/cors": {
+      "version": "2.8.19",
+      "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
+      "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
     "node_modules/@types/eslint": {
       "version": "9.6.1",
       "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
@@ -3384,6 +3471,15 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@types/jsonwebtoken": {
+      "version": "9.0.7",
+      "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz",
+      "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
     "node_modules/@types/methods": {
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
@@ -3402,7 +3498,6 @@
       "version": "22.15.32",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.32.tgz",
       "integrity": "sha512-3jigKqgSjsH6gYZv2nEsqdXfZqIFGAV36XYYjf9KGZ3PSG+IhLecqPnI310RvjutyMwifE2hhhNEklOUrvx/wA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "undici-types": "~6.21.0"
@@ -4933,6 +5028,29 @@
       ],
       "license": "MIT"
     },
+    "node_modules/base64id": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
+      "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
+      "license": "MIT",
+      "engines": {
+        "node": "^4.5.0 || >= 5.9"
+      }
+    },
+    "node_modules/bcrypt": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz",
+      "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "dependencies": {
+        "node-addon-api": "^8.3.0",
+        "node-gyp-build": "^4.8.4"
+      },
+      "engines": {
+        "node": ">= 18"
+      }
+    },
     "node_modules/bin-version": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-6.0.0.tgz",
@@ -5115,6 +5233,12 @@
         "node": "*"
       }
     },
+    "node_modules/buffer-equal-constant-time": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+      "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+      "license": "BSD-3-Clause"
+    },
     "node_modules/buffer-from": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -5887,6 +6011,15 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/ecdsa-sig-formatter": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+      "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "node_modules/ee-first": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -5945,6 +6078,95 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/engine.io": {
+      "version": "6.6.4",
+      "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz",
+      "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/cors": "^2.8.12",
+        "@types/node": ">=10.0.0",
+        "accepts": "~1.3.4",
+        "base64id": "2.0.0",
+        "cookie": "~0.7.2",
+        "cors": "~2.8.5",
+        "debug": "~4.3.1",
+        "engine.io-parser": "~5.2.1",
+        "ws": "~8.17.1"
+      },
+      "engines": {
+        "node": ">=10.2.0"
+      }
+    },
+    "node_modules/engine.io-parser": {
+      "version": "5.2.3",
+      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+      "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/engine.io/node_modules/accepts": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/engine.io/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/engine.io/node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/engine.io/node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/engine.io/node_modules/negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
     "node_modules/enhanced-resolve": {
       "version": "5.18.1",
       "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
@@ -8396,6 +8618,49 @@
         "graceful-fs": "^4.1.6"
       }
     },
+    "node_modules/jsonwebtoken": {
+      "version": "9.0.2",
+      "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+      "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+      "license": "MIT",
+      "dependencies": {
+        "jws": "^3.2.2",
+        "lodash.includes": "^4.3.0",
+        "lodash.isboolean": "^3.0.3",
+        "lodash.isinteger": "^4.0.4",
+        "lodash.isnumber": "^3.0.3",
+        "lodash.isplainobject": "^4.0.6",
+        "lodash.isstring": "^4.0.1",
+        "lodash.once": "^4.0.0",
+        "ms": "^2.1.1",
+        "semver": "^7.5.4"
+      },
+      "engines": {
+        "node": ">=12",
+        "npm": ">=6"
+      }
+    },
+    "node_modules/jwa": {
+      "version": "1.4.2",
+      "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
+      "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
+      "license": "MIT",
+      "dependencies": {
+        "buffer-equal-constant-time": "^1.0.1",
+        "ecdsa-sig-formatter": "1.0.11",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/jws": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+      "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+      "license": "MIT",
+      "dependencies": {
+        "jwa": "^1.4.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "node_modules/keyv": {
       "version": "4.5.4",
       "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -8509,6 +8774,42 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/lodash.includes": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+      "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isboolean": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+      "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isinteger": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+      "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isnumber": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+      "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isplainobject": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+      "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isstring": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+      "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
+      "license": "MIT"
+    },
     "node_modules/lodash.memoize": {
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -8523,6 +8824,12 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/lodash.once": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+      "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
+      "license": "MIT"
+    },
     "node_modules/log-symbols": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@@ -8911,6 +9218,15 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/node-addon-api": {
+      "version": "8.4.0",
+      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.4.0.tgz",
+      "integrity": "sha512-D9DI/gXHvVmjHS08SVch0Em8G5S1P+QWtU31appcKT/8wFSPRcdHadIFSAntdMMVM5zz+/DL+bL/gz3UDppqtg==",
+      "license": "MIT",
+      "engines": {
+        "node": "^18 || ^20 || >= 21"
+      }
+    },
     "node_modules/node-emoji": {
       "version": "1.11.0",
       "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz",
@@ -8921,6 +9237,17 @@
         "lodash": "^4.17.21"
       }
     },
+    "node_modules/node-gyp-build": {
+      "version": "4.8.4",
+      "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
+      "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
+      "license": "MIT",
+      "bin": {
+        "node-gyp-build": "bin.js",
+        "node-gyp-build-optional": "optional.js",
+        "node-gyp-build-test": "build-test.js"
+      }
+    },
     "node_modules/node-int64": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@@ -8980,6 +9307,15 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/object-hash": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+      "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/object-inspect": {
       "version": "1.13.4",
       "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -9204,6 +9540,42 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/passport": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz",
+      "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==",
+      "license": "MIT",
+      "dependencies": {
+        "passport-strategy": "1.x.x",
+        "pause": "0.0.1",
+        "utils-merge": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4.0"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/jaredhanson"
+      }
+    },
+    "node_modules/passport-jwt": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz",
+      "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==",
+      "license": "MIT",
+      "dependencies": {
+        "jsonwebtoken": "^9.0.0",
+        "passport-strategy": "^1.0.0"
+      }
+    },
+    "node_modules/passport-strategy": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
+      "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==",
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
     "node_modules/path-exists": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -9287,6 +9659,11 @@
         "node": ">=8"
       }
     },
+    "node_modules/pause": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
+      "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
+    },
     "node_modules/peek-readable": {
       "version": "5.4.2",
       "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.4.2.tgz",
@@ -9943,7 +10320,6 @@
       "version": "7.7.2",
       "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
       "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
-      "dev": true,
       "license": "ISC",
       "bin": {
         "semver": "bin/semver.js"
@@ -10159,6 +10535,141 @@
         "node": ">=8"
       }
     },
+    "node_modules/socket.io": {
+      "version": "4.8.1",
+      "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
+      "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
+      "license": "MIT",
+      "dependencies": {
+        "accepts": "~1.3.4",
+        "base64id": "~2.0.0",
+        "cors": "~2.8.5",
+        "debug": "~4.3.2",
+        "engine.io": "~6.6.0",
+        "socket.io-adapter": "~2.5.2",
+        "socket.io-parser": "~4.2.4"
+      },
+      "engines": {
+        "node": ">=10.2.0"
+      }
+    },
+    "node_modules/socket.io-adapter": {
+      "version": "2.5.5",
+      "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
+      "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "~4.3.4",
+        "ws": "~8.17.1"
+      }
+    },
+    "node_modules/socket.io-adapter/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/socket.io-parser": {
+      "version": "4.2.4",
+      "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
+      "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
+      "license": "MIT",
+      "dependencies": {
+        "@socket.io/component-emitter": "~3.1.0",
+        "debug": "~4.3.1"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/socket.io-parser/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/socket.io/node_modules/accepts": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/socket.io/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/socket.io/node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/socket.io/node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/socket.io/node_modules/negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
     "node_modules/sort-keys": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
@@ -11222,7 +11733,6 @@
       "version": "6.21.0",
       "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
       "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/universalify": {
@@ -11291,6 +11801,15 @@
       "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
       "license": "MIT"
     },
+    "node_modules/utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
     "node_modules/v8-compile-cache-lib": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
@@ -11702,6 +12221,27 @@
       "dev": true,
       "license": "ISC"
     },
+    "node_modules/ws": {
+      "version": "8.17.1",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+      "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/xtend": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

+ 7 - 0
package.json

@@ -22,7 +22,14 @@
   "dependencies": {
     "@nestjs/common": "^11.0.1",
     "@nestjs/core": "^11.0.1",
+    "@nestjs/jwt": "^11.0.0",
+    "@nestjs/passport": "^11.0.5",
     "@nestjs/platform-express": "^11.0.1",
+    "@nestjs/platform-socket.io": "^11.1.3",
+    "@nestjs/websockets": "^11.1.3",
+    "bcrypt": "^6.0.0",
+    "passport": "^0.7.0",
+    "passport-jwt": "^4.0.1",
     "reflect-metadata": "^0.2.2",
     "rxjs": "^7.8.1"
   },

+ 4 - 3
src/app.controller.ts

@@ -1,12 +1,13 @@
 import { Controller, Get } from '@nestjs/common';
-import { AppService } from './app.service';
+import { GeneralService } from './services/general.service';
 
 @Controller()
 export class AppController {
-  constructor(private readonly appService: AppService) {}
+  constructor(private readonly service: GeneralService) { }
 
   @Get()
   getHello(): string {
-    return this.appService.getHello();
+    return this.service.getHello();
   }
+
 }

+ 5 - 3
src/app.module.ts

@@ -1,10 +1,12 @@
 import { Module } from '@nestjs/common';
 import { AppController } from './app.controller';
-import { AppService } from './app.service';
+import { GeneralService } from './services/general.service';
+import { AuthModule } from './auth/auth.module';
+import { AttendanceModule } from './attendance/attendance.module';
 
 @Module({
-  imports: [],
+  imports: [AuthModule, AttendanceModule],
   controllers: [AppController],
-  providers: [AppService],
+  providers: [GeneralService],
 })
 export class AppModule {}

+ 40 - 0
src/attendance/attendance.controller.ts

@@ -0,0 +1,40 @@
+import {
+    Controller,
+    Post,
+    Body,
+    UseGuards,
+    Request,
+    Logger,
+} from '@nestjs/common';
+import { JwtAuthGuard } from 'src/common/guards/jwt-auth.guard';
+import { AttendancePayload } from 'src/interface/interface';
+import { AttendanceService } from 'src/services/attendance.service';
+
+@Controller('attendance')
+export class AttendanceController {
+    private logger: Logger = new Logger(`Attendance Controller`)
+    private service: AttendanceService
+
+    constructor(attendanceService: AttendanceService) {
+        this.service = attendanceService
+    }
+
+    @UseGuards(JwtAuthGuard)
+    @Post()
+    submitAttendance(
+        @Request() req,
+        @Body() body: AttendancePayload
+    ) {
+        const user = req.user; // ← comes from the token
+        const { date } = body;
+        this.service.emit({ name: user.name, date: date })
+        return {
+            message: `Attendance received for ${user.name} on ${new Date(date).toDateString()}`,
+            user: {
+                id: user.sub,
+                name: user.name,
+                email: user.email,
+            },
+        };
+    }
+}

+ 10 - 0
src/attendance/attendance.module.ts

@@ -0,0 +1,10 @@
+import { Module } from '@nestjs/common';
+import { AttendanceController } from './attendance.controller';
+import { AttendanceService } from 'src/services/attendance.service';
+
+@Module({
+  controllers: [AttendanceController],
+  exports: [], // if you have services to share with other modules
+  providers: [AttendanceService]
+})
+export class AttendanceModule {}

+ 25 - 0
src/auth/auth.controller.ts

@@ -0,0 +1,25 @@
+import { Body, Controller, Logger, Post } from '@nestjs/common';
+import { LoginPayload, User } from 'src/interface/interface';
+import { AuthService } from 'src/services/auth.service';
+
+@Controller('auth')
+export class AuthController {
+    private logger: Logger = new Logger(`Auth Controller`)
+    constructor(private authService: AuthService) { }
+
+    @Post('register')
+    async register(@Body() body: { name: string, email: string; password: string }) {
+        this.logger.log(`Registering ${body.name}`)
+        let user: User = body
+        return this.authService.register(user);
+    }
+
+    @Post('login')
+    login(@Body() body: { email?: string; password?: string }) {
+        if (!body.email || !body.password) {
+            this.logger.error('Missing email or password');
+        } else {
+            return this.authService.login(body as LoginPayload);
+        }
+    }
+}

+ 20 - 0
src/auth/auth.module.ts

@@ -0,0 +1,20 @@
+import { Module } from '@nestjs/common';
+import { AuthController } from './auth.controller';
+import { JwtModule } from '@nestjs/jwt';
+import { UsersService } from 'src/services/user.service';
+import { AuthService } from 'src/services/auth.service';
+import { PassportModule } from '@nestjs/passport';
+import { JwtStrategy } from './jwt.strategy';
+
+@Module({
+  imports: [
+    PassportModule,
+    JwtModule.register({
+      secret: 'dev-secret', // Use ENV later
+      signOptions: { expiresIn: '1d' },
+    }),
+  ],
+  controllers: [AuthController],
+  providers: [AuthService, UsersService, JwtStrategy],
+})
+export class AuthModule {}

+ 19 - 0
src/auth/jwt.strategy.ts

@@ -0,0 +1,19 @@
+// src/auth/jwt.strategy.ts
+import { Injectable } from '@nestjs/common';
+import { PassportStrategy } from '@nestjs/passport';
+import { ExtractJwt, Strategy } from 'passport-jwt';
+
+@Injectable()
+export class JwtStrategy extends PassportStrategy(Strategy) {
+  constructor() {
+    super({
+      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
+      secretOrKey: 'dev-secret', // should match the one used in JwtModule
+    });
+  }
+
+  async validate(payload: any) {
+    // This runs if token is valid
+    return { sub: payload.sub, email: payload.email, name: payload.name };
+  }
+}

+ 6 - 0
src/common/guards/jwt-auth.guard.ts

@@ -0,0 +1,6 @@
+// src/common/guards/jwt-auth.guard.ts
+import { Injectable } from '@nestjs/common';
+import { AuthGuard } from '@nestjs/passport';
+
+@Injectable()
+export class JwtAuthGuard extends AuthGuard('jwt') {}

+ 3 - 0
src/config.ts

@@ -0,0 +1,3 @@
+export const serverConfig = {
+    exposedUrl: `https://05d8-60-53-251-228.ngrok-free.app`
+}

+ 45 - 0
src/gateway/socket.gateway.ts

@@ -0,0 +1,45 @@
+
+// monitoring.gateway.ts
+import { WebSocketGateway, WebSocketServer, OnGatewayConnection, OnGatewayDisconnect, SubscribeMessage, MessageBody, WsResponse } from '@nestjs/websockets';
+import { Server } from 'socket.io';
+import { from, map, Observable, Subject, Subscription } from 'rxjs';
+import * as _ from 'lodash'
+import { Logger } from '@nestjs/common';
+@WebSocketGateway({
+    cors: {
+        origins: '*'
+    },
+    namespace: `ws`
+})
+export class SocketGateway implements OnGatewayConnection, OnGatewayDisconnect {
+    @WebSocketServer()
+    server: Server;
+    logger: Logger = new Logger(`Socket`)
+    private messageResponseSubject: Subject<any> = new Subject()
+
+    constructor() {
+        
+    }
+
+    emit(message: any) {
+        this.messageResponseSubject.next(message)
+    }
+
+    // Emit latest changes to UI. Just one way street for now
+    @SubscribeMessage(`message`)
+    messageHandler(@MessageBody() data: any): Observable<WsResponse<any>> {
+        console.log(`Received message: ${data.action}`)
+        return this.messageResponseSubject.pipe(map(connection => ({ event: 'message', data: connection })))
+    }
+
+
+    // this two methods are non essential. as they are used to keep track of the local ui port 3000, not keeping track of the actual clients 
+    handleConnection(client: any) {
+        console.log('UI connected:', client.id);
+    }
+
+    handleDisconnect(client: any) {
+        console.log('UI disconnected:', client.id);
+    }
+
+}

+ 25 - 0
src/interface/interface.ts

@@ -0,0 +1,25 @@
+export interface RegisteredUser extends User {
+    id: string,
+}
+
+export interface User {
+    name: string,
+    email: string,
+    password: string
+} 
+
+export interface LoginPayload {
+    email: string,
+    password: string
+}
+
+export interface AttendancePayload {
+    name: string
+    date: Date
+}
+
+export interface PaymentPayload {
+    name: string
+    date: Date
+    verified: boolean
+}

+ 2 - 0
src/main.ts

@@ -1,8 +1,10 @@
 import { NestFactory } from '@nestjs/core';
 import { AppModule } from './app.module';
+import { serverConfig } from './config';
 
 async function bootstrap() {
   const app = await NestFactory.create(AppModule);
   await app.listen(process.env.PORT ?? 3000);
+  console.log(`Server started at localhost:3000 $$ Exposed at ${serverConfig.exposedUrl}`)
 }
 bootstrap();

+ 41 - 0
src/payment/payment.controller.ts

@@ -0,0 +1,41 @@
+import {
+    Controller,
+    Post,
+    Body,
+    UseGuards,
+    Request,
+    Logger,
+} from '@nestjs/common';
+import { JwtAuthGuard } from 'src/common/guards/jwt-auth.guard';
+import { PaymentPayload } from 'src/interface/interface';
+import { PaymentService } from 'src/services/payment.service';
+
+@Controller('payment')
+export class PaymentController {
+    private logger: Logger = new Logger(`Payment Controller`)
+    private service: PaymentService
+
+    constructor(attendanceService: PaymentService) {
+        this.service = attendanceService
+    }
+
+    @UseGuards(JwtAuthGuard)
+    @Post()
+    submitAttendance(
+        @Request() req,
+        @Body() body: PaymentPayload
+    ) {
+        const user = req.user; // ← comes from the token
+        const { date } = body;
+        this.service.emit({ name: user.name, date: date, verified: body.verified})
+
+        return {
+            message: `Payment received for ${user.name} on ${new Date(date).toDateString()}`,
+            user: {
+                id: user.sub,
+                name: user.name,
+                email: user.email,
+            },
+        };
+    }
+}

+ 10 - 0
src/payment/payment.module.ts

@@ -0,0 +1,10 @@
+import { Module } from '@nestjs/common';
+import { PaymentController } from './payment.controller';
+import { PaymentService } from 'src/services/payment.service';
+
+@Module({
+  controllers: [PaymentController],
+  exports: [], // if you have services to share with other modules
+  providers: [PaymentService]
+})
+export class PaymentModule {}

+ 15 - 0
src/services/attendance.service.ts

@@ -0,0 +1,15 @@
+import { Injectable } from '@nestjs/common';
+import { AttendancePayload } from 'src/interface/interface';
+
+@Injectable()
+export class AttendanceService {
+    attendanceRecord: AttendancePayload[] = []
+    constructor() {
+        // need to pipe the socket obs here so that the value can be updated to UI in real time
+    }
+
+    public emit(attendance: AttendancePayload) {
+        console.log(`Processing attendance for ${attendance.name} at ${attendance.date}`)
+        this.attendanceRecord.push(attendance)
+    }
+}

+ 31 - 0
src/services/auth.service.ts

@@ -0,0 +1,31 @@
+import { Injectable, Logger } from '@nestjs/common';
+import { JwtService } from '@nestjs/jwt';
+import { UsersService } from './user.service';
+import { LoginPayload, RegisteredUser, User } from 'src/interface/interface';
+
+@Injectable()
+export class AuthService {
+    private logger: Logger = new Logger(`AuthService`)
+    constructor(
+        private usersService: UsersService,
+        private jwtService: JwtService
+    ) { }
+
+    async register(user: User): Promise<any> {
+        const registeredUser: RegisteredUser = await this.usersService.createUser(user);
+        return { message: 'User registered', id: registeredUser.id };
+    }
+
+    async login(login: LoginPayload) {
+        return new Promise((resolve, reject) => {
+            this.usersService.validateUser(login).then((user) => {
+                const payload = { name: user.name, email: user.email, sub: user.id };
+                let response = { access_token: this.jwtService.sign(payload) }
+                resolve(response)
+            }).catch((error) => {
+                this.logger.error('Invalid credentials')
+                reject(error)
+            })
+        })
+    }
+}

+ 1 - 1
src/app.service.ts → src/services/general.service.ts

@@ -1,7 +1,7 @@
 import { Injectable } from '@nestjs/common';
 
 @Injectable()
-export class AppService {
+export class GeneralService {
   getHello(): string {
     return 'Hello World!';
   }

+ 15 - 0
src/services/payment.service.ts

@@ -0,0 +1,15 @@
+import { Injectable } from '@nestjs/common';
+import { AttendancePayload, PaymentPayload } from 'src/interface/interface';
+
+@Injectable()
+export class PaymentService {
+    paymentRecord: PaymentPayload[] = []
+    constructor() {
+        // need to pipe the socket obs here so that the value can be updated to UI in real time
+    }
+
+    public emit(payment: PaymentPayload) {
+        console.log(`Processing payment for ${payment.name} at ${payment.date}`)
+        this.paymentRecord.push(payment)
+    }
+}

+ 62 - 0
src/services/user.service.ts

@@ -0,0 +1,62 @@
+import { Injectable, Logger } from '@nestjs/common';
+import * as bcrypt from 'bcrypt';
+import { LoginPayload, RegisteredUser, User } from 'src/interface/interface';
+
+
+@Injectable()
+export class UsersService {
+    private logger: Logger = new Logger(`UsersService`)
+    private users: RegisteredUser[] = [];
+    private idCounter = 1;
+
+    public async createUser(user: User): Promise<RegisteredUser> {
+        return new Promise(async (resolve, reject) => {
+            const existing = this.users.find(u => u.email === user.email);
+            if (existing) {
+                reject('User already exists');
+            } else {
+                const hashedPassword = await bcrypt.hash(user.password, 10);
+                const newUser: RegisteredUser = {
+                    id: (this.idCounter++).toString(),
+                    name: user.name,
+                    email: user.email,
+                    password: hashedPassword,
+                };
+                this.users.push(newUser);
+                this.logger.log(`Current users count: ${this.users.length}`)
+                resolve(newUser)
+            }
+        })
+    }
+
+    public async findByEmail(email: string): Promise<RegisteredUser | undefined> {
+        return this.users.find(user => user.email === email);
+    }
+
+    public async validateUser(login: LoginPayload): Promise<Omit<RegisteredUser, 'password'>> {
+        return new Promise((resolve, reject) => {
+            this.findByEmail(login.email)
+                .then(user => {
+                    if (!user) return reject('No such user');
+
+                    // console.log('Comparing passwords...');
+                    // console.log('Login.password:', login.password, typeof login.password);
+                    // console.log('User.password:', user.password, typeof user.password);
+
+                    if (typeof login.password !== 'string' || typeof user.password !== 'string') {
+                        return reject('Password and hash must be strings');
+                    }
+
+                    bcrypt.compare(login.password, user.password, (err, isMatch) => {
+                        if (err) return reject(err);
+                        if (!isMatch) return reject(`Password doesn't match`);
+
+                        const { password, ...safeUser } = user;
+                        resolve(safeUser);
+                    });
+                })
+                .catch(err => reject(err));
+        });
+    }
+
+}