diff --git a/package-lock.json b/package-lock.json index 4a4a44a6..2c68d1f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "@nestjs/jwt": "^11.0.0", "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^10.4.18", + "@nestjs/platform-socket.io": "^10.4.22", "@nestjs/schedule": "^6.1.0", "@nestjs/swagger": "^7.4.2", "@nestjs/terminus": "^11.1.1", @@ -41,6 +42,7 @@ "@opentelemetry/exporter-prometheus": "^0.203.0", "@opentelemetry/instrumentation": "^0.203.0", "@opentelemetry/sdk-node": "^0.203.0", + "@types/express-session": "^1.18.2", "@types/fluent-ffmpeg": "^2.1.27", "@types/handlebars": "^4.0.40", "@types/multer": "^1.4.12", @@ -57,9 +59,11 @@ "cache-manager-redis-store": "^3.0.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", + "connect-redis": "^9.0.0", "crypto": "^1.0.1", "dataloader": "^2.2.3", "express": "^5.2.1", + "express-session": "^1.19.0", "fast-xml-parser": "^5.2.5", "fluent-ffmpeg": "^2.1.3", "graphql": "^16.12.0", @@ -331,7 +335,6 @@ "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.27.3.tgz", "integrity": "sha512-IjLt0gd3L4jlOfilxVXTifn42FnVffMgDC04RJK1KDZpmkBWLv0XC92MVVmkxrFZNS/7l3xWgP/I3nqtX1sQHw==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", @@ -347,7 +350,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -356,8 +358,7 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@apollo/cache-control-types": { "version": "1.0.3", @@ -665,15 +666,6 @@ "node": ">= 0.6" } }, - "node_modules/@apollo/server/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/@apollo/server/node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -2071,7 +2063,6 @@ "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.3.1" } @@ -2148,6 +2139,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -2665,7 +2657,6 @@ "resolved": "https://registry.npmjs.org/@browserbasehq/sdk/-/sdk-2.6.0.tgz", "integrity": "sha512-83iXP5D7xMm8Wyn66TUaUrgoByCmAJuoMoZQI3sGg3JAiMlTfnCIMqyVBoNSaItaPIkaCnrsj6LiusmXV2X9YA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", @@ -2681,7 +2672,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -2690,8 +2680,7 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@browserbasehq/stagehand": { "version": "1.14.0", @@ -2727,8 +2716,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@colors/colors": { "version": "1.5.0", @@ -3033,6 +3021,7 @@ "resolved": "https://registry.npmjs.org/@elastic/elasticsearch/-/elasticsearch-8.19.1.tgz", "integrity": "sha512-+1j9NnQVOX+lbWB8LhCM7IkUmjU05Y4+BmSLfusq0msCsQb1Va+OUKFCoOXjCJqQrcgdRdQCjYYyolQ/npQALQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@elastic/transport": "^8.9.6", "apache-arrow": "18.x - 21.x", @@ -3257,6 +3246,7 @@ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", @@ -3296,6 +3286,7 @@ "resolved": "https://registry.npmjs.org/@huggingface/inference/-/inference-4.13.13.tgz", "integrity": "sha512-kxa3hkQEgD/B2x6QTZQBnu4Wx/Uc7pOAqmLS8T3VkTM4weshJzaeYH/nEDKGuChUKJza0IglytRViSCiT8KLjg==", "license": "MIT", + "peer": true, "dependencies": { "@huggingface/jinja": "^0.5.5", "@huggingface/tasks": "^0.19.85" @@ -3393,7 +3384,6 @@ "resolved": "https://registry.npmjs.org/@ibm-cloud/watsonx-ai/-/watsonx-ai-1.7.8.tgz", "integrity": "sha512-UpU/hTHRrCwzkqV1+H/CGbHIGRKty6SX1Aea2CBbyRonsEPIPQtFfhz8FHhs+o6Ca/TCupHNlcLAQUFektZmEQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@types/node": "^18.0.0", "extend": "3.0.2", @@ -3409,7 +3399,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -3418,8 +3407,7 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@img/colour": { "version": "1.0.0", @@ -5269,7 +5257,6 @@ "https://github.com/sponsors/ctavan" ], "license": "MIT", - "peer": true, "bin": { "uuid": "dist/bin/uuid" } @@ -5476,19 +5463,6 @@ "ioredis": ">=5.0.0" } }, - "node_modules/@nestjs-modules/ioredis/node_modules/@nestjs/axios": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.1.3.tgz", - "integrity": "sha512-RZ/63c1tMxGLqyG3iOCVt7A72oy4x1eM6QEhd4KzCYpaVWW0igq0WSREeRoEZhIxRcZfDfIIkvsOMiM7yfVGZQ==", - "license": "MIT", - "optional": true, - "peer": true, - "peerDependencies": { - "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", - "axios": "^1.3.1", - "rxjs": "^6.0.0 || ^7.0.0" - } - }, "node_modules/@nestjs-modules/ioredis/node_modules/@nestjs/terminus": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/@nestjs/terminus/-/terminus-10.2.0.tgz", @@ -5560,32 +5534,6 @@ } } }, - "node_modules/@nestjs-modules/ioredis/node_modules/@nestjs/typeorm": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-10.0.2.tgz", - "integrity": "sha512-H738bJyydK4SQkRCTeh1aFBxoO1E9xdL/HaLGThwrqN95os5mEyAtK7BLADOS+vldP4jDZ2VQPLj4epWwRqCeQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "uuid": "9.0.1" - }, - "peerDependencies": { - "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", - "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", - "reflect-metadata": "^0.1.13 || ^0.2.0", - "rxjs": "^7.2.0", - "typeorm": "^0.3.0" - } - }, - "node_modules/@nestjs-modules/ioredis/node_modules/reflect-metadata": { - "version": "0.1.14", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", - "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", - "license": "Apache-2.0", - "optional": true, - "peer": true - }, "node_modules/@nestjs-modules/ioredis/node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -5596,7 +5544,6 @@ ], "license": "MIT", "optional": true, - "peer": true, "bin": { "uuid": "dist/bin/uuid" } @@ -5639,6 +5586,7 @@ "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-4.0.1.tgz", "integrity": "sha512-68pFJgu+/AZbWkGu65Z3r55bTsCPlgyKaV4BSG8yUAD72q1PPuyVRgUwFv6BxdnibTUHlyxm06FmYWNC+bjN7A==", "license": "MIT", + "peer": true, "peerDependencies": { "@nestjs/common": "^10.0.0 || ^11.0.0", "axios": "^1.3.1", @@ -5895,6 +5843,7 @@ "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.22.tgz", "integrity": "sha512-fxJ4v85nDHaqT1PmfNCQ37b/jcv2OojtXTaK1P2uAXhzLf9qq6WNUOFvxBrV4fhQek1EQoT1o9oj5xAZmv3NRw==", "license": "MIT", + "peer": true, "dependencies": { "file-type": "20.4.1", "iterare": "1.2.1", @@ -5953,6 +5902,7 @@ "integrity": "sha512-6IX9+VwjiKtCjx+mXVPncpkQ5ZjKfmssOZPFexmT+6T9H9wZ3svpYACAo7+9e7Nr9DZSoRZw3pffkJP7Z0UjaA==", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@nuxtjs/opencollective": "0.3.2", "fast-safe-stringify": "2.1.1", @@ -6014,6 +5964,7 @@ "resolved": "https://registry.npmjs.org/@nestjs/graphql/-/graphql-12.2.2.tgz", "integrity": "sha512-lUDy/1uqbRA1kBKpXcmY0aHhcPbfeG52Wg5+9Jzd1d57dwSjCAmuO+mWy5jz9ugopVCZeK0S/kdAMvA+r9fNdA==", "license": "MIT", + "peer": true, "dependencies": { "@graphql-tools/merge": "9.0.11", "@graphql-tools/schema": "10.0.10", @@ -6223,6 +6174,7 @@ "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.22.tgz", "integrity": "sha512-ySSq7Py/DFozzZdNDH67m/vHoeVdphDniWBnl6q5QVoXldDdrZIHLXLRMPayTDh5A95nt7jjJzmD4qpTbNQ6tA==", "license": "MIT", + "peer": true, "dependencies": { "body-parser": "1.20.4", "cors": "2.8.5", @@ -6413,15 +6365,6 @@ "node": ">= 0.6" } }, - "node_modules/@nestjs/platform-express/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/@nestjs/platform-express/node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -6467,6 +6410,94 @@ "node": ">= 0.8.0" } }, + "node_modules/@nestjs/platform-socket.io": { + "version": "10.4.22", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.4.22.tgz", + "integrity": "sha512-xxGw3R0Ihr51/Omq23z3//bKmCXyVKaikxbH0/pkwqMsQrxkUv9NabNUZ22b4Jnlwwi02X+zlwo8GRa9u8oV9g==", + "license": "MIT", + "dependencies": { + "socket.io": "4.8.1", + "tslib": "2.8.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/websockets": "^10.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/platform-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/@nestjs/platform-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/@nestjs/platform-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/@nestjs/platform-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/@nestjs/platform-socket.io/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/@nestjs/schedule": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-6.1.1.tgz", @@ -6689,6 +6720,7 @@ "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz", "integrity": "sha512-SOeUQl70Lb2OfhGkvnh4KXWlsd+zA08RuuQgT7kKbzivngxzSo1Oc7Usu5VxCxACQC9wc2l9esOHILSJeK7rJA==", "license": "MIT", + "peer": true, "peerDependencies": { "@nestjs/common": "^10.0.0 || ^11.0.0", "@nestjs/core": "^10.0.0 || ^11.0.0", @@ -6702,6 +6734,7 @@ "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.4.22.tgz", "integrity": "sha512-OLd4i0Faq7vgdtB5vVUrJ54hWEtcXy9poJ6n7kbbh/5ms+KffUl+wwGsbe7uSXLrkoyI8xXU6fZPkFArI+XiRg==", "license": "MIT", + "peer": true, "dependencies": { "iterare": "1.2.1", "object-hash": "3.0.0", @@ -6822,6 +6855,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -7754,62 +7788,72 @@ "license": "BSD-3-Clause" }, "node_modules/@redis/bloom": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", - "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.11.0.tgz", + "integrity": "sha512-KYiVilAhAFN3057afUb/tfYJpsEyTkQB+tQcn5gVVA7DgcNOAj8lLxe4j8ov8BF6I9C1Fe/kwlbuAICcTMX8Lw==", "license": "MIT", + "engines": { + "node": ">= 18" + }, "peerDependencies": { - "@redis/client": "^1.0.0" + "@redis/client": "^5.11.0" } }, "node_modules/@redis/client": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", - "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.11.0.tgz", + "integrity": "sha512-GHoprlNQD51Xq2Ztd94HHV94MdFZQ3CVrpA04Fz8MVoHM0B7SlbmPEVIjwTbcv58z8QyjnrOuikS0rWF03k5dQ==", "license": "MIT", + "peer": true, "dependencies": { - "cluster-key-slot": "1.1.2", - "generic-pool": "3.9.0", - "yallist": "4.0.0" + "cluster-key-slot": "1.1.2" }, "engines": { - "node": ">=14" - } - }, - "node_modules/@redis/graph": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", - "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", - "license": "MIT", + "node": ">= 18" + }, "peerDependencies": { - "@redis/client": "^1.0.0" + "@node-rs/xxhash": "^1.1.0" + }, + "peerDependenciesMeta": { + "@node-rs/xxhash": { + "optional": true + } } }, "node_modules/@redis/json": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", - "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.11.0.tgz", + "integrity": "sha512-1iAy9kAtcD0quB21RbPTbUqqy+T2Uu2JxucwE+B4A+VaDbIRvpZR6DMqV8Iqaws2YxJYB3GC5JVNzPYio2ErUg==", "license": "MIT", + "engines": { + "node": ">= 18" + }, "peerDependencies": { - "@redis/client": "^1.0.0" + "@redis/client": "^5.11.0" } }, "node_modules/@redis/search": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", - "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.11.0.tgz", + "integrity": "sha512-g1l7f3Rnyk/xI99oGHIgWHSKFl45Re5YTIcO8j/JE8olz389yUFyz2+A6nqVy/Zi031VgPDWscbbgOk8hlhZ3g==", "license": "MIT", + "engines": { + "node": ">= 18" + }, "peerDependencies": { - "@redis/client": "^1.0.0" + "@redis/client": "^5.11.0" } }, "node_modules/@redis/time-series": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", - "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.11.0.tgz", + "integrity": "sha512-TWFeOcU4xkj0DkndnOyhtxvX1KWD+78UHT3XX3x3XRBUGWeQrKo3jqzDsZwxbggUgf9yLJr/akFHXru66X5UQA==", "license": "MIT", + "engines": { + "node": ">= 18" + }, "peerDependencies": { - "@redis/client": "^1.0.0" + "@redis/client": "^5.11.0" } }, "node_modules/@sideway/address": { @@ -7984,7 +8028,6 @@ "integrity": "sha512-8janZoJw85nJmQZc4L8TuePp2pk1nxLgkxIR0TUjKJ5Dkj5oelB9WtiSSGXCQvNsJl0VSTvK/2ueMXxvpa9GVw==", "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "@aws-crypto/crc32": "3.0.0", "@smithy/types": "^2.12.0", @@ -7998,7 +8041,6 @@ "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "@aws-crypto/util": "^3.0.0", "@aws-sdk/types": "^3.222.0", @@ -8010,8 +8052,7 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "license": "0BSD", - "optional": true, - "peer": true + "optional": true }, "node_modules/@smithy/eventstream-codec/node_modules/@aws-crypto/util": { "version": "3.0.0", @@ -8019,7 +8060,6 @@ "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-utf8-browser": "^3.0.0", @@ -8031,8 +8071,7 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "license": "0BSD", - "optional": true, - "peer": true + "optional": true }, "node_modules/@smithy/eventstream-codec/node_modules/@smithy/types": { "version": "2.12.0", @@ -8040,7 +8079,6 @@ "integrity": "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==", "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8054,7 +8092,6 @@ "integrity": "sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ==", "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8465,7 +8502,6 @@ "integrity": "sha512-Xy5XK1AFWW2nlY/biWZXu6/krgbaf2dg0q492D8M5qthsnU2H+UgFeZLbM76FnH7s6RO/xhQRkj+T6KBO3JzgQ==", "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "@smithy/types": "^2.12.0", "tslib": "^2.6.2" @@ -8480,7 +8516,6 @@ "integrity": "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==", "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8546,7 +8581,6 @@ "integrity": "sha512-ui/NlpILU+6HAQBfJX8BBsDXuKSNrjTSuOYArRblcrErwKFutjrCNb/OExfVRyj9+26F9J+ZmfWT+fKWuDrH3Q==", "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "@smithy/types": "^2.12.0", @@ -8566,7 +8600,6 @@ "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8580,7 +8613,6 @@ "integrity": "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==", "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8594,7 +8626,6 @@ "integrity": "sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ==", "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8608,7 +8639,6 @@ "integrity": "sha512-L1qpleXf9QD6LwLCJ5jddGkgWyuSvWBkJwWAZ6kFkdifdso+sk3L3O1HdmPvCdnCK3IS4qWyPxev01QMnfHSBw==", "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "@smithy/types": "^2.12.0", "tslib": "^2.6.2" @@ -8623,7 +8653,6 @@ "integrity": "sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA==", "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -9156,7 +9185,6 @@ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/ms": "*" } @@ -9167,6 +9195,7 @@ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -9213,6 +9242,15 @@ "@types/send": "*" } }, + "node_modules/@types/express-session": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.2.tgz", + "integrity": "sha512-k+I0BxwVXsnEU2hV77cCobC08kIsn4y44C3gC0b46uxZVMaXA04lSPgRLR/bSL2w0t0ShJiG8o4jPzRG/nscFg==", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/express/node_modules/@types/express-serve-static-core": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", @@ -9367,6 +9405,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -9533,8 +9572,7 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/uuid": { "version": "10.0.0", @@ -9610,6 +9648,7 @@ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -10047,7 +10086,6 @@ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "license": "MIT", - "peer": true, "dependencies": { "event-target-shim": "^5.0.0" }, @@ -10088,6 +10126,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -10110,7 +10149,6 @@ "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" }, @@ -10146,7 +10184,6 @@ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", "license": "MIT", - "peer": true, "dependencies": { "humanize-ms": "^1.2.1" }, @@ -10160,6 +10197,7 @@ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -10478,6 +10516,7 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", @@ -11024,6 +11063,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -11089,6 +11129,7 @@ "resolved": "https://registry.npmjs.org/bull/-/bull-4.16.5.tgz", "integrity": "sha512-lDsx2BzkKe7gkCYiT5Acj02DpTwDznl/VNN7Psn7M3USPG7Vs/BaClZJJTAG+ufAR9++N1/NiUTdaFBWDIl5TQ==", "license": "MIT", + "peer": true, "dependencies": { "cron-parser": "^4.9.0", "get-port": "^5.1.1", @@ -11136,6 +11177,7 @@ "resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-7.2.8.tgz", "integrity": "sha512-0HDaDLBBY/maa/LmUVAr70XUOwsiQD+jyzCBjmUErYZUKdMS9dT59PqW59PpVqfGM7ve6H0J6307JTpkCYefHQ==", "license": "MIT", + "peer": true, "dependencies": { "@cacheable/utils": "^2.3.3", "keyv": "^5.5.5" @@ -11168,6 +11210,83 @@ "node": ">= 16.18.0" } }, + "node_modules/cache-manager-redis-store/node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/cache-manager-redis-store/node_modules/@redis/client": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", + "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", + "license": "MIT", + "peer": true, + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/cache-manager-redis-store/node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/cache-manager-redis-store/node_modules/@redis/json": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/cache-manager-redis-store/node_modules/@redis/search": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/cache-manager-redis-store/node_modules/@redis/time-series": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/cache-manager-redis-store/node_modules/redis": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.1.tgz", + "integrity": "sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==", + "license": "MIT", + "workspaces": [ + "./packages/*" + ], + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.6.1", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.7", + "@redis/search": "1.2.0", + "@redis/time-series": "1.1.0" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -11410,13 +11529,15 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/class-validator": { "version": "0.14.3", "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.3.tgz", "integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==", "license": "MIT", + "peer": true, "dependencies": { "@types/validator": "^13.15.3", "libphonenumber-js": "^1.11.1", @@ -11777,6 +11898,19 @@ "typedarray": "^0.0.6" } }, + "node_modules/connect-redis": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-9.0.0.tgz", + "integrity": "sha512-QwzyvUePTMvEzG1hy45gZYw3X3YHrjmEdSkayURlcZft7hqadQ3X39wYkmCqblK2rGlw+XItELYt6GnyG6DEIQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "express-session": ">=1", + "redis": ">=5" + } + }, "node_modules/consola": { "version": "2.15.3", "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", @@ -11921,6 +12055,7 @@ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -12146,7 +12281,6 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -12201,6 +12335,7 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -12353,6 +12488,7 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=12" }, @@ -12526,15 +12662,6 @@ "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/engine.io/node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", @@ -12626,8 +12753,7 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.1", @@ -12691,6 +12817,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -12747,6 +12874,7 @@ "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -13067,7 +13195,6 @@ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -13212,6 +13339,51 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-session": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.19.0.tgz", + "integrity": "sha512-0csaMkGq+vaiZTmSMMGkfdCOabYv192VbytFypcvI0MANrp+4i/7yEkJ0sbAEhycQjntaKGzYfjfXQyVb7BHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "cookie": "~0.7.2", + "cookie-signature": "~1.0.7", + "debug": "~2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.1.0", + "parseurl": "~1.3.3", + "safe-buffer": "~5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/express-session/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express-session/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/express/node_modules/body-parser": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", @@ -13294,8 +13466,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/external-editor": { "version": "3.1.0", @@ -13408,6 +13579,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" @@ -13848,8 +14020,7 @@ "version": "1.7.2", "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/form-data/node_modules/mime-db": { "version": "1.52.0", @@ -13877,7 +14048,6 @@ "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", "license": "MIT", - "peer": true, "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" @@ -14247,6 +14417,7 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.0.tgz", "integrity": "sha512-uSisMYERbaB9bkA9M4/4dnqyktaEkf1kMHNKq/7DHyxVeWqHQ2mBmVqm5u6/FVHwF3iCNalKcg82Zfl+tffWoA==", "license": "MIT", + "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -14314,6 +14485,7 @@ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "license": "MIT", + "peer": true, "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", @@ -14478,7 +14650,6 @@ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "license": "MIT", - "peer": true, "dependencies": { "ms": "^2.0.0" } @@ -14504,7 +14675,6 @@ "resolved": "https://registry.npmjs.org/ibm-cloud-sdk-core/-/ibm-cloud-sdk-core-5.4.8.tgz", "integrity": "sha512-tLMlZv13cV6S1UPj/bhv8XfV9Z1BDDs/4DxHKWnCw7QlJMzmGdHLPX386x9nrFMQMPZ48eAH+Thsa06tzUZkaA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@types/debug": "^4.1.12", "@types/node": "^18.19.80", @@ -14531,7 +14701,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -14541,7 +14710,6 @@ "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", "license": "MIT", - "peer": true, "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", @@ -14572,15 +14740,13 @@ "url": "https://feross.org/support" } ], - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/ibm-cloud-sdk-core/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", - "peer": true, "engines": { "node": ">= 0.6" } @@ -14590,7 +14756,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", - "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -14603,7 +14768,6 @@ "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", "license": "MIT", - "peer": true, "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" @@ -14621,7 +14785,6 @@ "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", "license": "MIT", - "peer": true, "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" @@ -14638,8 +14801,7 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/iconv-lite": { "version": "0.4.24", @@ -14665,6 +14827,7 @@ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "devOptional": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 4" } @@ -14857,6 +15020,7 @@ "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.9.3.tgz", "integrity": "sha512-VI5tMCdeoxZWU5vjHWsiE/Su76JGhBvWF1MJnV9ZtGltHk9BmD48oDq8Tj8haZ85aceXZMxLNDQZRVo5QKNgXA==", "license": "MIT", + "peer": true, "dependencies": { "@ioredis/commands": "1.5.0", "cluster-key-slot": "^1.1.0", @@ -15144,8 +15308,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", @@ -15264,6 +15427,7 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -16656,6 +16820,7 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", "license": "MIT", + "peer": true, "dependencies": { "@keyv/serialize": "^1.1.1" } @@ -17212,7 +17377,8 @@ "version": "4.17.23", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.camelcase": { "version": "4.3.0", @@ -17997,7 +18163,6 @@ "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", "license": "MIT", - "peer": true, "bin": { "mustache": "bin/mustache" } @@ -18022,6 +18187,15 @@ "dev": true, "license": "MIT" }, + "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/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -18113,7 +18287,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=10.5.0" } @@ -18267,6 +18440,15 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -18404,7 +18586,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -18413,8 +18594,7 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/openapi-types": { "version": "12.1.3", @@ -18695,6 +18875,7 @@ "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", "license": "MIT", + "peer": true, "dependencies": { "passport-strategy": "1.x.x", "pause": "0.0.1", @@ -18809,6 +18990,7 @@ "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.1.tgz", "integrity": "sha512-v6ZJ/efsBpGrGGknjtq9J/oC8tZWq0KWL5vQrk2GlzLEQPUDB1ex+13Rmidl1neNN358Jn9EHZw5y07FFtaC7A==", "license": "MIT", + "peer": true, "dependencies": { "debug": "^3.1.0", "node-ensure": "^0.0.0" @@ -18831,7 +19013,6 @@ "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" }, @@ -18845,6 +19026,7 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", @@ -19062,7 +19244,6 @@ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", "license": "Apache-2.0", - "peer": true, "dependencies": { "playwright-core": "1.58.2" }, @@ -19081,7 +19262,6 @@ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", "license": "Apache-2.0", - "peer": true, "bin": { "playwright-core": "cli.js" }, @@ -19099,7 +19279,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } @@ -19233,6 +19412,7 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -19276,7 +19456,6 @@ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6.0" } @@ -19362,7 +19541,6 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", "license": "MIT", - "peer": true, "dependencies": { "punycode": "^2.3.1" }, @@ -19434,8 +19612,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/queue-microtask": { "version": "1.2.3", @@ -19457,6 +19634,15 @@ ], "license": "MIT" }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -19547,7 +19733,6 @@ "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", "license": "MIT", - "peer": true, "dependencies": { "readable-stream": "^4.7.0" }, @@ -19578,7 +19763,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -19589,7 +19773,6 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.x" } @@ -19612,15 +19795,13 @@ "url": "https://feross.org/support" } ], - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", "license": "MIT", - "peer": true, "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", @@ -19659,20 +19840,20 @@ } }, "node_modules/redis": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.1.tgz", - "integrity": "sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==", + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-5.11.0.tgz", + "integrity": "sha512-YwXjATVDT+AuxcyfOwZn046aml9jMlQPvU1VXIlLDVAExe0u93aTfPYSeRgG4p9Q/Jlkj+LXJ1XEoFV+j2JKcQ==", "license": "MIT", - "workspaces": [ - "./packages/*" - ], + "peer": true, "dependencies": { - "@redis/bloom": "1.2.0", - "@redis/client": "1.6.1", - "@redis/graph": "1.1.1", - "@redis/json": "1.0.7", - "@redis/search": "1.2.0", - "@redis/time-series": "1.1.0" + "@redis/bloom": "5.11.0", + "@redis/client": "5.11.0", + "@redis/json": "5.11.0", + "@redis/search": "5.11.0", + "@redis/time-series": "5.11.0" + }, + "engines": { + "node": ">= 18" } }, "node_modules/redis-errors": { @@ -19749,8 +19930,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/resolve": { "version": "1.22.11", @@ -19840,7 +20020,6 @@ "resolved": "https://registry.npmjs.org/retry-axios/-/retry-axios-2.6.0.tgz", "integrity": "sha512-pOLi+Gdll3JekwuFjXO3fTq+L9lzMQGcSq7M5gIjExcl3Gu1hd4XXuf5o3+LuSBsaULQH7DiNbsqPd1chVpQGQ==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=10.7.0" }, @@ -19999,6 +20178,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -20077,6 +20257,7 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -20589,15 +20770,6 @@ "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/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -21504,7 +21676,6 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -21520,7 +21691,6 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 4.0.0" } @@ -21686,6 +21856,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -21910,6 +22081,7 @@ "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.28.tgz", "integrity": "sha512-6GH7wXhtfq2D33ZuRXYwIsl/qM5685WZcODZb7noOOcRMteM9KF2x2ap3H0EBjnSV0VO4gNAfJT5Ukp0PkOlvg==", "license": "MIT", + "peer": true, "dependencies": { "@sqltools/formatter": "^1.2.5", "ansis": "^4.2.0", @@ -22093,6 +22265,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -22135,6 +22308,18 @@ "node": ">=8" } }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "license": "MIT", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/uint8array-extras": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", @@ -22250,7 +22435,6 @@ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "license": "MIT", - "peer": true, "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -22430,7 +22614,6 @@ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", "license": "MIT", - "peer": true, "engines": { "node": ">= 14" } @@ -22517,7 +22700,6 @@ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -22532,7 +22714,6 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } @@ -22543,7 +22724,6 @@ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.x" } @@ -22554,7 +22734,6 @@ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -22565,7 +22744,6 @@ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -22579,7 +22757,6 @@ "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -22783,7 +22960,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -22938,6 +23114,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -22947,7 +23124,6 @@ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", "license": "ISC", - "peer": true, "peerDependencies": { "zod": "^3.25 || ^4" } diff --git a/package.json b/package.json index c24c92f4..245ee928 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "@nestjs/jwt": "^11.0.0", "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^10.4.18", + "@nestjs/platform-socket.io": "^10.4.22", "@nestjs/schedule": "^6.1.0", "@nestjs/swagger": "^7.4.2", "@nestjs/terminus": "^11.1.1", @@ -61,6 +62,7 @@ "@opentelemetry/exporter-prometheus": "^0.203.0", "@opentelemetry/instrumentation": "^0.203.0", "@opentelemetry/sdk-node": "^0.203.0", + "@types/express-session": "^1.18.2", "@types/fluent-ffmpeg": "^2.1.27", "@types/handlebars": "^4.0.40", "@types/multer": "^1.4.12", @@ -77,9 +79,11 @@ "cache-manager-redis-store": "^3.0.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", + "connect-redis": "^9.0.0", "crypto": "^1.0.1", "dataloader": "^2.2.3", "express": "^5.2.1", + "express-session": "^1.19.0", "fast-xml-parser": "^5.2.5", "fluent-ffmpeg": "^2.1.3", "graphql": "^16.12.0", diff --git a/src/app.module.ts b/src/app.module.ts index 5a91461a..0273f4fb 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -6,8 +6,6 @@ import { APP_INTERCEPTOR } from '@nestjs/core'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { MonitoringModule } from './monitoring/monitoring.module'; -import { CachingModule } from './caching/caching.module'; -import { SecurityModule } from './security/security.module'; import { MonitoringInterceptor } from './common/interceptors/monitoring.interceptor'; import { TypeOrmMonitoringLogger } from './monitoring/logging/typeorm-logger'; import { MetricsCollectionService } from './monitoring/metrics/metrics-collection.service'; @@ -26,9 +24,11 @@ import { BullModule } from '@nestjs/bull'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { CacheModule } from '@nestjs/cache-manager'; import { RateLimitingModule } from './rate-limiting/services/rate-limiting.module'; -import * as redisStore from 'cache-manager-redis-store'; import { envValidationSchema } from './config/env.validation'; import { HealthModule } from './health/health.module'; +import { cacheConfig } from './config/cache.config'; +import { SessionModule } from './session/session.module'; +import { createBullRedisClient } from './common/utils/bull-redis.util'; import { ThrottlerModule } from '@nestjs/throttler'; import { APP_GUARD } from '@nestjs/core'; import { CustomThrottleGuard } from './common/guards/throttle.guard'; @@ -61,8 +61,9 @@ import { CustomThrottleGuard } from './common/guards/throttle.guard'; BullModule.forRoot({ redis: { host: process.env.REDIS_HOST || 'localhost', - port: parseInt(process.env.REDIS_PORT || '6379'), + port: parseInt(process.env.REDIS_PORT || '6379', 10), }, + createClient: createBullRedisClient, }), CacheModule.register({ isGlobal: true, @@ -70,6 +71,7 @@ import { CustomThrottleGuard } from './common/guards/throttle.guard'; host: process.env.REDIS_HOST || 'localhost', port: parseInt(process.env.REDIS_PORT || '6379'), }), + SessionModule, ThrottlerModule.forRootAsync({ imports: [ConfigModule], useFactory: () => ({ diff --git a/src/assessment/assessment.module.ts b/src/assessment/assessment.module.ts index 897fb2a6..50a1df71 100644 --- a/src/assessment/assessment.module.ts +++ b/src/assessment/assessment.module.ts @@ -19,5 +19,6 @@ import { Module } from '@nestjs/common'; ScoreCalculationService, FeedbackGenerationService, ], + exports: [AssessmentsService], }) export class AssessmentsModule {} diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 0f6aed3e..353477a6 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Post, Body, UseGuards, Req } from '@nestjs/common'; +import { Controller, Post, Body, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { Throttle } from '@nestjs/throttler'; import { AuthService } from './auth.service'; @@ -44,7 +44,7 @@ export class AuthController { @ApiBearerAuth() @ApiOperation({ summary: 'Logout user (invalidate refresh token)' }) async logout(@CurrentUser() user: any) { - return this.authService.logout(user.userId); + return this.authService.logout(user.userId, user.sessionId); } @Post('forgot-password') diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 38993d07..c9560fe9 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -6,11 +6,13 @@ import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from '../users/users.module'; import { JwtStrategy } from './strategies/jwt.strategy'; +import { SessionModule } from '../session/session.module'; @Module({ imports: [ ConfigModule, UsersModule, + SessionModule, PassportModule, JwtModule.registerAsync({ imports: [ConfigModule], diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 97b853f1..d6dc4ca9 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -1,15 +1,11 @@ -import { - Injectable, - UnauthorizedException, - BadRequestException, - NotFoundException, -} from '@nestjs/common'; +import { Injectable, UnauthorizedException, BadRequestException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { ConfigService } from '@nestjs/config'; import { UsersService } from '../users/users.service'; import { RegisterDto, LoginDto, ResetPasswordDto, ChangePasswordDto } from './dto/auth.dto'; import * as bcrypt from 'bcryptjs'; import { randomBytes } from 'crypto'; +import { SessionService } from '../session/session.service'; @Injectable() export class AuthService { @@ -17,6 +13,7 @@ export class AuthService { private readonly usersService: UsersService, private readonly jwtService: JwtService, private readonly configService: ConfigService, + private readonly sessionService: SessionService, ) {} async register(registerDto: RegisterDto) { @@ -36,8 +33,8 @@ export class AuthService { // TODO: Send verification email // await this.emailService.sendVerificationEmail(user.email, verificationToken); - // Generate tokens - const { accessToken, refreshToken } = await this.generateTokens(user); + const sessionId = await this.sessionService.createSession(user.id, { type: 'auth-register' }); + const { accessToken, refreshToken } = await this.generateTokens(user, sessionId); // Save refresh token const hashedRefreshToken = await bcrypt.hash(refreshToken, 10); @@ -79,8 +76,8 @@ export class AuthService { // Update last login await this.usersService.updateLastLogin(user.id); - // Generate tokens - const { accessToken, refreshToken } = await this.generateTokens(user); + const sessionId = await this.sessionService.createSession(user.id, { type: 'auth-login' }); + const { accessToken, refreshToken } = await this.generateTokens(user, sessionId); // Save refresh token const hashedRefreshToken = await bcrypt.hash(refreshToken, 10); @@ -106,34 +103,55 @@ export class AuthService { const payload = this.jwtService.verify(refreshToken, { secret: this.configService.get('JWT_REFRESH_SECRET') || 'refresh-secret-key', }); - - // Find user - const user = await this.usersService.findOne(payload.sub); - if (!user || !user.refreshToken) { - throw new UnauthorizedException('Invalid refresh token'); - } - - // Verify stored refresh token - const isRefreshTokenValid = await bcrypt.compare(refreshToken, user.refreshToken); - if (!isRefreshTokenValid) { - throw new UnauthorizedException('Invalid refresh token'); - } - - // Generate new tokens - const tokens = await this.generateTokens(user); - - // Update refresh token - const hashedRefreshToken = await bcrypt.hash(tokens.refreshToken, 10); - await this.usersService.updateRefreshToken(user.id, hashedRefreshToken); - - return tokens; - } catch (error) { + return this.sessionService.withLock(`refresh:${payload.sub}`, async () => { + // Find user + const user = await this.usersService.findOne(payload.sub); + if (!user || !user.refreshToken) { + throw new UnauthorizedException('Invalid refresh token'); + } + + // Verify stored refresh token + const isRefreshTokenValid = await bcrypt.compare(refreshToken, user.refreshToken); + if (!isRefreshTokenValid) { + throw new UnauthorizedException('Invalid refresh token'); + } + + let sessionId = payload.sid as string | undefined; + if (sessionId) { + const session = await this.sessionService.getSession(sessionId); + if (!session) { + sessionId = await this.sessionService.createSession(user.id, { type: 'auth-refresh' }); + } else { + await this.sessionService.touchSession(sessionId, { + lastRefreshAt: Date.now(), + }); + } + } else { + sessionId = await this.sessionService.createSession(user.id, { type: 'auth-refresh' }); + } + + // Generate new tokens + const tokens = await this.generateTokens(user, sessionId); + + // Update refresh token + const hashedRefreshToken = await bcrypt.hash(tokens.refreshToken, 10); + await this.usersService.updateRefreshToken(user.id, hashedRefreshToken); + + return tokens; + }); + } catch { throw new UnauthorizedException('Invalid refresh token'); } } - async logout(userId: string) { - await this.usersService.updateRefreshToken(userId, null); + async logout(userId: string, sessionId?: string) { + await this.sessionService.withLock(`logout:${userId}`, async () => { + if (sessionId) { + await this.sessionService.removeSession(sessionId); + } + await this.usersService.updateRefreshToken(userId, null); + }); + return { message: 'Logout successful' }; } @@ -215,11 +233,12 @@ export class AuthService { return { message: 'Email verified successfully' }; } - private async generateTokens(user: any) { + private async generateTokens(user: any, sessionId: string) { const payload = { sub: user.id, email: user.email, role: user.role, + sid: sessionId, }; const [accessToken, refreshToken] = await Promise.all([ diff --git a/src/auth/strategies/jwt.strategy.ts b/src/auth/strategies/jwt.strategy.ts index 03c01c4b..e42d092c 100644 --- a/src/auth/strategies/jwt.strategy.ts +++ b/src/auth/strategies/jwt.strategy.ts @@ -6,7 +6,9 @@ import { ConfigService } from '@nestjs/config'; export interface JwtPayload { sub: string; email: string; - roles: string[]; + role?: string; + roles?: string[]; + sid?: string; } @Injectable() @@ -20,10 +22,13 @@ export class JwtStrategy extends PassportStrategy(Strategy) { } async validate(payload: JwtPayload) { + const roles = payload.roles || (payload.role ? [payload.role] : []); + return { userId: payload.sub, email: payload.email, - roles: payload.roles || [], + roles, + sessionId: payload.sid, }; } } diff --git a/src/common/utils/bull-redis.util.ts b/src/common/utils/bull-redis.util.ts new file mode 100644 index 00000000..f09c2553 --- /dev/null +++ b/src/common/utils/bull-redis.util.ts @@ -0,0 +1,19 @@ +import Redis, { RedisOptions } from 'ioredis'; + +export const createBullRedisClient = (type: string, redisOpts?: RedisOptions) => { + const options: RedisOptions = { + ...(redisOpts ?? {}), + }; + + if (type !== 'client') { + options.enableReadyCheck = false; + options.maxRetriesPerRequest = null; + } + + const client = new Redis(options); + client.on('error', () => { + // Avoid unhandled error events while Redis is unavailable. + }); + + return client; +}; diff --git a/src/config/cache.config.ts b/src/config/cache.config.ts index 9aa7e445..7ccf1544 100644 --- a/src/config/cache.config.ts +++ b/src/config/cache.config.ts @@ -1,8 +1,26 @@ import { redisStore } from 'cache-manager-redis-store'; export const cacheConfig = { + isGlobal: true, store: redisStore, host: process.env.REDIS_HOST || 'localhost', port: parseInt(process.env.REDIS_PORT || '6379', 10), - ttl: parseInt(process.env.REDIS_TTL || '60', 10), // default TTL in seconds + ttl: parseInt(process.env.REDIS_TTL || '60', 10), +}; + +export const sessionConfig = { + secret: process.env.SESSION_SECRET || 'teachlink-session-secret', + name: process.env.SESSION_COOKIE_NAME || 'teachlink.sid', + prefix: process.env.SESSION_PREFIX || 'sess:', + ttlSeconds: parseInt(process.env.SESSION_TTL_SECONDS || '604800', 10), + cookieMaxAgeMs: parseInt(process.env.SESSION_COOKIE_MAX_AGE_MS || '604800000', 10), + secureCookies: process.env.NODE_ENV === 'production', + stickySessionsRequired: (process.env.STICKY_SESSIONS_REQUIRED || 'true') === 'true', + trustProxy: (process.env.TRUST_PROXY || 'true') === 'true', +}; + +export const distributedLockConfig = { + ttlMs: parseInt(process.env.SESSION_LOCK_TTL_MS || '5000', 10), + maxRetries: parseInt(process.env.SESSION_LOCK_MAX_RETRIES || '5', 10), + retryDelayMs: parseInt(process.env.SESSION_LOCK_RETRY_DELAY_MS || '120', 10), }; diff --git a/src/graphql/schema/schema.graphql b/src/graphql/schema/schema.graphql new file mode 100644 index 00000000..ad559fe3 --- /dev/null +++ b/src/graphql/schema/schema.graphql @@ -0,0 +1,187 @@ +# ------------------------------------------------------ +# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) +# ------------------------------------------------------ + +type AssessmentType { + createdAt: DateTime! + description: String + durationMinutes: Int! + id: ID! + questions: [QuestionType] + title: String! +} + +input CourseFilterInput { + instructorId: String + search: String + status: String +} + +type CourseType { + createdAt: DateTime! + description: String! + id: ID! + instructor: UserType + price: Float! + status: String! + thumbnailUrl: String + title: String! + updatedAt: DateTime! +} + +input CreateAssessmentInput { + description: String + durationMinutes: Int! + questions: [QuestionInput!]! + title: String! +} + +input CreateCourseInput { + description: String! + instructorId: String! + price: Float! + thumbnailUrl: String + title: String! +} + +input CreateUserInput { + email: String! + firstName: String! + lastName: String! + password: String! + role: UserRole + username: String +} + +""" +A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date-time format. +""" +scalar DateTime + +type Mutation { + createAssessment(input: CreateAssessmentInput!): AssessmentType! + createCourse(input: CreateCourseInput!): CourseType! + createUser(input: CreateUserInput!): UserType! + deleteAssessment(id: ID!): Boolean! + deleteCourse(id: ID!): Boolean! + deleteUser(id: ID!): Boolean! + updateAssessment(id: ID!, input: UpdateAssessmentInput!): AssessmentType! + updateCourse(id: ID!, input: UpdateCourseInput!): CourseType! + updateUser(id: ID!, input: UpdateUserInput!): UserType! +} + +type Query { + assessment(id: ID!): AssessmentType + assessments: [AssessmentType!]! + course(id: ID!): CourseType + courses(filter: CourseFilterInput): [CourseType!]! + me: UserType! + myCourses: [CourseType!]! + user(id: ID!): UserType + users(filter: UserFilterInput): [UserType!]! +} + +input QuestionInput { + correctAnswer: String + options: [String!] + points: Int! + prompt: String! + type: String! +} + +type QuestionType { + correctAnswer: String + id: ID! + options: [String] + points: Int! + prompt: String! + type: String! +} + +type Subscription { + """Subscribe to new assessment creation events""" + assessmentCreated: AssessmentType! + + """Subscribe to assessment deletion events""" + assessmentDeleted: AssessmentType! + + """Subscribe to assessment update events""" + assessmentUpdated: AssessmentType! + + """Subscribe to new course creation events""" + courseCreated: CourseType! + + """Subscribe to course deletion events""" + courseDeleted: CourseType! + + """Subscribe to course update events""" + courseUpdated: CourseType! + + """Subscribe to new user creation events""" + userCreated: UserType! + + """Subscribe to user deletion events""" + userDeleted: UserType! + + """Subscribe to user update events""" + userUpdated: UserType! +} + +input UpdateAssessmentInput { + description: String + durationMinutes: Int + title: String +} + +input UpdateCourseInput { + description: String + price: Float + status: String + thumbnailUrl: String + title: String +} + +input UpdateUserInput { + firstName: String + lastName: String + profilePicture: String + status: UserStatus + username: String +} + +input UserFilterInput { + role: String + search: String + status: String +} + +"""User role in the system""" +enum UserRole { + ADMIN + STUDENT + TEACHER +} + +"""User account status""" +enum UserStatus { + ACTIVE + INACTIVE + SUSPENDED +} + +type UserType { + courses: [CourseType] + createdAt: DateTime! + email: String! + firstName: String! + id: ID! + isEmailVerified: Boolean! + lastLoginAt: DateTime + lastName: String! + profilePicture: String + role: UserRole! + status: UserStatus! + tenantId: String + updatedAt: DateTime! + username: String +} \ No newline at end of file diff --git a/src/health/health.controller.ts b/src/health/health.controller.ts index 466aad4a..8cdb34d2 100644 --- a/src/health/health.controller.ts +++ b/src/health/health.controller.ts @@ -11,6 +11,10 @@ export class HealthController { host: process.env.REDIS_HOST, port: Number(process.env.REDIS_PORT), }); + + this.redis.on('error', () => { + // Health endpoint handles Redis failures explicitly in checkHealth. + }); } @Get() diff --git a/src/main.ts b/src/main.ts index bb699394..828bb943 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,14 +1,49 @@ import { NestFactory } from '@nestjs/core'; import { ValidationPipe, Logger } from '@nestjs/common'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; +import cluster from 'node:cluster'; +import { cpus } from 'node:os'; +import session from 'express-session'; +import { RedisStore } from 'connect-redis'; +import Redis from 'ioredis'; import { AppModule } from './app.module'; import { GlobalExceptionFilter } from './common/interceptors/global-exception.filter'; import { ResponseTransformInterceptor } from './common/interceptors/response-transform.interceptor'; +import { sessionConfig } from './config/cache.config'; +import { SESSION_REDIS_CLIENT } from './session/session.constants'; -async function bootstrap() { +async function bootstrapWorker() { const logger = new Logger('Bootstrap'); const app = await NestFactory.create(AppModule); + const redisClient = app.get(SESSION_REDIS_CLIENT); + + if (sessionConfig.trustProxy) { + const expressApp = app.getHttpAdapter().getInstance(); + expressApp.set('trust proxy', 1); + } + + app.use( + session({ + store: new RedisStore({ + client: redisClient, + prefix: sessionConfig.prefix, + ttl: sessionConfig.ttlSeconds, + }), + name: sessionConfig.name, + secret: sessionConfig.secret, + resave: false, + saveUninitialized: false, + rolling: true, + cookie: { + maxAge: sessionConfig.cookieMaxAgeMs, + httpOnly: true, + sameSite: 'lax', + secure: sessionConfig.secureCookies, + }, + }), + ); + // ─── Global Exception Filter ────────────────────────────────────────────── app.useGlobalFilters(new GlobalExceptionFilter()); @@ -52,7 +87,37 @@ async function bootstrap() { const port = process.env.PORT || 3000; await app.listen(port); + if (sessionConfig.stickySessionsRequired) { + logger.log( + 'Sticky sessions are enabled by policy. Configure LB cookie affinity on teachlink.sid.', + ); + } + logger.log(`🚀 TeachLink API running on http://localhost:${port}`); logger.log(`📚 Swagger docs available at http://localhost:${port}/api`); } + +async function bootstrap() { + const logger = new Logger('Cluster'); + const clusterModeEnabled = (process.env.CLUSTER_MODE || 'false') === 'true'; + + if (clusterModeEnabled && cluster.isPrimary) { + const workerCount = parseInt(process.env.CLUSTER_WORKERS || `${cpus().length}`, 10); + + logger.log(`Primary process started in cluster mode with ${workerCount} workers.`); + + for (let i = 0; i < workerCount; i += 1) { + cluster.fork(); + } + + cluster.on('exit', () => { + cluster.fork(); + }); + + return; + } + + await bootstrapWorker(); +} + bootstrap(); diff --git a/src/messaging/messaging.module.ts b/src/messaging/messaging.module.ts index 155f897c..3cff0671 100644 --- a/src/messaging/messaging.module.ts +++ b/src/messaging/messaging.module.ts @@ -6,14 +6,16 @@ import { EventBusService } from './event-bus/event-bus.service'; import { ServiceDiscoveryService } from './discovery/service-discovery.service'; import { CircuitBreakerService } from './circuit-breaker/circuit-breaker.service'; import { TracingService } from './tracing/tracing.service'; +import { createBullRedisClient } from '../common/utils/bull-redis.util'; @Module({ imports: [ BullModule.forRoot({ redis: { host: process.env.REDIS_HOST || 'localhost', - port: parseInt(process.env.REDIS_PORT || '6379'), + port: parseInt(process.env.REDIS_PORT || '6379', 10), }, + createClient: createBullRedisClient, }), BullModule.registerQueue({ name: 'message-queue', diff --git a/src/orchestration/locks/distributed-lock.service.ts b/src/orchestration/locks/distributed-lock.service.ts index 9ab27074..219c9dff 100644 --- a/src/orchestration/locks/distributed-lock.service.ts +++ b/src/orchestration/locks/distributed-lock.service.ts @@ -5,6 +5,12 @@ import Redis from 'ioredis'; export class DistributedLockService { private redis = new Redis(process.env.REDIS_URL); + constructor() { + this.redis.on('error', () => { + // Prevent unhandled error events during Redis outages. + }); + } + async acquireLock(key: string, ttl = 5000): Promise { const result = await this.redis.set(key, 'locked', 'PX', ttl, 'NX'); return result === 'OK'; diff --git a/src/rate-limiting/services/distrubutes.service.ts b/src/rate-limiting/services/distrubutes.service.ts index d07ecabc..c6d13431 100644 --- a/src/rate-limiting/services/distrubutes.service.ts +++ b/src/rate-limiting/services/distrubutes.service.ts @@ -7,6 +7,9 @@ export class DistributedLimiterService { constructor() { this.redis = new Redis(process.env.REDIS_URL); + this.redis.on('error', () => { + // Prevent unhandled error events during Redis outages. + }); } async slidingWindowCheck(key: string, limit: number, windowInSeconds: number): Promise { diff --git a/src/session/session.constants.ts b/src/session/session.constants.ts new file mode 100644 index 00000000..e6b07e8a --- /dev/null +++ b/src/session/session.constants.ts @@ -0,0 +1 @@ +export const SESSION_REDIS_CLIENT = 'SESSION_REDIS_CLIENT'; diff --git a/src/session/session.module.ts b/src/session/session.module.ts new file mode 100644 index 00000000..130b61d4 --- /dev/null +++ b/src/session/session.module.ts @@ -0,0 +1,34 @@ +import { Global, Module } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import Redis from 'ioredis'; +import { SESSION_REDIS_CLIENT } from './session.constants'; +import { SessionService } from './session.service'; + +@Global() +@Module({ + imports: [ConfigModule], + providers: [ + { + provide: SESSION_REDIS_CLIENT, + inject: [ConfigService], + useFactory: (configService: ConfigService) => { + const client = new Redis({ + host: configService.get('REDIS_HOST') || 'localhost', + port: parseInt(configService.get('REDIS_PORT') || '6379', 10), + lazyConnect: false, + maxRetriesPerRequest: null, + enableReadyCheck: true, + }); + + client.on('error', () => { + // Prevent unhandled error events when Redis is temporarily unavailable. + }); + + return client; + }, + }, + SessionService, + ], + exports: [SESSION_REDIS_CLIENT, SessionService], +}) +export class SessionModule {} diff --git a/src/session/session.service.ts b/src/session/session.service.ts new file mode 100644 index 00000000..bc97118b --- /dev/null +++ b/src/session/session.service.ts @@ -0,0 +1,220 @@ +import { Inject, Injectable, Logger, OnModuleDestroy } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import Redis from 'ioredis'; +import { randomUUID } from 'crypto'; +import { SESSION_REDIS_CLIENT } from './session.constants'; + +interface SessionRecord { + sid: string; + userId: string; + metadata: Record; + version: number; + createdAt: number; + updatedAt: number; +} + +@Injectable() +export class SessionService implements OnModuleDestroy { + private readonly logger = new Logger(SessionService.name); + private readonly sessionPrefix: string; + private readonly legacySessionPrefix: string; + private readonly sessionTtlSeconds: number; + private readonly lockTtlMs: number; + private readonly lockRetries: number; + private readonly lockRetryDelayMs: number; + + constructor( + @Inject(SESSION_REDIS_CLIENT) private readonly redis: Redis, + private readonly configService: ConfigService, + ) { + this.sessionPrefix = this.configService.get('AUTH_SESSION_PREFIX') || 'auth:sess:'; + this.legacySessionPrefix = + this.configService.get('AUTH_SESSION_LEGACY_PREFIX') || 'session:'; + this.sessionTtlSeconds = parseInt( + this.configService.get('AUTH_SESSION_TTL_SECONDS') || '604800', + 10, + ); + this.lockTtlMs = parseInt(this.configService.get('SESSION_LOCK_TTL_MS') || '5000', 10); + this.lockRetries = parseInt( + this.configService.get('SESSION_LOCK_MAX_RETRIES') || '5', + 10, + ); + this.lockRetryDelayMs = parseInt( + this.configService.get('SESSION_LOCK_RETRY_DELAY_MS') || '120', + 10, + ); + } + + async onModuleDestroy(): Promise { + if (this.redis.status !== 'end') { + await this.redis.quit(); + } + } + + async createSession(userId: string, metadata: Record = {}): Promise { + const sid = randomUUID(); + const now = Date.now(); + const session: SessionRecord = { + sid, + userId, + metadata, + version: 1, + createdAt: now, + updatedAt: now, + }; + + await this.redis.set( + this.sessionKey(sid), + JSON.stringify(session), + 'EX', + this.sessionTtlSeconds, + ); + return sid; + } + + async getSession(sid: string): Promise { + const data = await this.redis.get(this.sessionKey(sid)); + if (!data) { + return this.migrateLegacySessionIfNeeded(sid); + } + + try { + return JSON.parse(data) as SessionRecord; + } catch { + this.logger.warn(`Invalid session payload for sid=${sid}`); + return null; + } + } + + async touchSession(sid: string, metadataPatch: Record = {}): Promise { + const session = await this.getSession(sid); + if (!session) { + return; + } + + const nextSession: SessionRecord = { + ...session, + metadata: { + ...session.metadata, + ...metadataPatch, + }, + updatedAt: Date.now(), + version: session.version + 1, + }; + + await this.redis + .multi() + .set(this.sessionKey(sid), JSON.stringify(nextSession)) + .expire(this.sessionKey(sid), this.sessionTtlSeconds) + .exec(); + } + + async removeSession(sid: string): Promise { + await this.redis.del(this.sessionKey(sid)); + } + + async migrateSession(oldSid: string, newSid = randomUUID()): Promise { + const existing = await this.getSession(oldSid); + if (!existing) { + return newSid; + } + + const migrated: SessionRecord = { + ...existing, + sid: newSid, + updatedAt: Date.now(), + version: existing.version + 1, + }; + + await this.redis + .multi() + .set(this.sessionKey(newSid), JSON.stringify(migrated), 'EX', this.sessionTtlSeconds) + .del(this.sessionKey(oldSid)) + .exec(); + + return newSid; + } + + async withLock(lockName: string, handler: () => Promise): Promise { + const lockKey = `lock:${lockName}`; + const lockToken = randomUUID(); + let locked = false; + + for (let attempt = 0; attempt <= this.lockRetries; attempt += 1) { + const response = await this.redis.set(lockKey, lockToken, 'PX', this.lockTtlMs, 'NX'); + if (response === 'OK') { + locked = true; + break; + } + + if (attempt < this.lockRetries) { + await this.delay(this.lockRetryDelayMs); + } + } + + if (!locked) { + throw new Error(`Could not acquire lock: ${lockName}`); + } + + try { + return await handler(); + } finally { + await this.releaseLock(lockKey, lockToken); + } + } + + private async migrateLegacySessionIfNeeded(sid: string): Promise { + const legacyKey = `${this.legacySessionPrefix}${sid}`; + const currentKey = this.sessionKey(sid); + + if (legacyKey === currentKey) { + return null; + } + + const legacyData = await this.redis.get(legacyKey); + if (!legacyData) { + return null; + } + + const now = Date.now(); + const migrated: SessionRecord = { + sid, + userId: 'unknown', + metadata: { + source: 'legacy', + payload: legacyData, + }, + version: 1, + createdAt: now, + updatedAt: now, + }; + + await this.redis + .multi() + .set(currentKey, JSON.stringify(migrated), 'EX', this.sessionTtlSeconds) + .del(legacyKey) + .exec(); + + this.logger.log(`Migrated legacy session sid=${sid} to prefix=${this.sessionPrefix}`); + return migrated; + } + + private async releaseLock(lockKey: string, lockToken: string): Promise { + const releaseScript = ` + if redis.call('GET', KEYS[1]) == ARGV[1] then + return redis.call('DEL', KEYS[1]) + end + return 0 + `; + + await this.redis.eval(releaseScript, 1, lockKey, lockToken); + } + + private sessionKey(sid: string): string { + return `${this.sessionPrefix}${sid}`; + } + + private async delay(ms: number): Promise { + await new Promise((resolve) => setTimeout(resolve, ms)); + } +}