diff --git a/backend/package-lock.json b/backend/package-lock.json index 80cf59a..7ecd772 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -19,7 +19,7 @@ "express-async-handler": "^1.2.0", "express-validator": "^7.0.1", "firebase": "^12.5.0", - "groq-sdk": "^0.4.0", + "groq-sdk": "^0.37.0", "http-errors": "^2.0.0", "module-alias": "^2.2.3", "mongodb": "^5.9.2", @@ -252,34 +252,34 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.983.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.983.0.tgz", - "integrity": "sha512-V40PT2irPh3lj+Z95tZI6batVrjaTrWEOXRNVBuoZSgpM3Ak1jiE9ZXwVLkMcbb9/GH4xVpB3EsGM7gbxmgFLQ==", + "version": "3.992.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.992.0.tgz", + "integrity": "sha512-6xfXGCvnWGgy5zZAse64Ru2G2qLKnPY7h8tchlsmGWVcJOWgz7iM3jmsWsQiJ79zH9A8HAPHU+ZD8TYYkwC+0Q==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/credential-provider-node": "^3.972.5", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/credential-provider-node": "^3.972.9", "@aws-sdk/middleware-bucket-endpoint": "^3.972.3", "@aws-sdk/middleware-expect-continue": "^3.972.3", - "@aws-sdk/middleware-flexible-checksums": "^3.972.4", + "@aws-sdk/middleware-flexible-checksums": "^3.972.8", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-location-constraint": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-sdk-s3": "^3.972.6", + "@aws-sdk/middleware-sdk-s3": "^3.972.10", "@aws-sdk/middleware-ssec": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/middleware-user-agent": "^3.972.10", "@aws-sdk/region-config-resolver": "^3.972.3", - "@aws-sdk/signature-v4-multi-region": "3.983.0", + "@aws-sdk/signature-v4-multi-region": "3.992.0", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.983.0", + "@aws-sdk/util-endpoints": "3.992.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.4", + "@aws-sdk/util-user-agent-node": "^3.972.8", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.0", + "@smithy/core": "^3.23.0", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/eventstream-serde-config-resolver": "^4.3.8", "@smithy/eventstream-serde-node": "^4.2.8", @@ -290,25 +290,25 @@ "@smithy/invalid-dependency": "^4.2.8", "@smithy/md5-js": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.12", - "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.28", - "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", - "@smithy/util-stream": "^4.5.10", + "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.8", "tslib": "^2.6.2" @@ -318,44 +318,44 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.982.0.tgz", - "integrity": "sha512-qJrIiivmvujdGqJ0ldSUvhN3k3N7GtPesoOI1BSt0fNXovVnMz4C/JmnkhZihU7hJhDvxJaBROLYTU+lpild4w==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.990.0.tgz", + "integrity": "sha512-xTEaPjZwOqVjGbLOP7qzwbdOWJOo1ne2mUhTZwEBBkPvNk4aXB/vcYwWwrjoSWUqtit4+GDbO75ePc/S6TUJYQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.6", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/middleware-user-agent": "^3.972.10", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.982.0", + "@aws-sdk/util-endpoints": "3.990.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.4", + "@aws-sdk/util-user-agent-node": "^3.972.8", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.0", + "@smithy/core": "^3.23.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.12", - "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.28", - "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -367,9 +367,9 @@ } }, "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz", - "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz", + "integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", @@ -383,19 +383,19 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.973.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.6.tgz", - "integrity": "sha512-pz4ZOw3BLG0NdF25HoB9ymSYyPbMiIjwQJ2aROXRhAzt+b+EOxStfFv8s5iZyP6Kiw7aYhyWxj5G3NhmkoOTKw==", + "version": "3.973.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.10.tgz", + "integrity": "sha512-4u/FbyyT3JqzfsESI70iFg6e2yp87MB5kS2qcxIA66m52VSTN1fvuvbCY1h/LKq1LvuxIrlJ1ItcyjvcKoaPLg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.4", - "@smithy/core": "^3.22.0", + "@smithy/core": "^3.23.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", @@ -420,12 +420,12 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.4.tgz", - "integrity": "sha512-/8dnc7+XNMmViEom2xsNdArQxQPSgy4Z/lm6qaFPTrMFesT1bV3PsBhb19n09nmxHdrtQskYmViddUIjUQElXg==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.8.tgz", + "integrity": "sha512-r91OOPAcHnLCSxaeu/lzZAVRCZ/CtTNuwmJkUwpwSDshUrP7bkX1OmFn2nUMWd9kN53Q4cEo8b7226G4olt2Mg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", @@ -436,20 +436,20 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.6.tgz", - "integrity": "sha512-5ERWqRljiZv44AIdvIRQ3k+EAV0Sq2WeJHvXuK7gL7bovSxOf8Al7MLH7Eh3rdovH4KHFnlIty7J71mzvQBl5Q==", + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.10.tgz", + "integrity": "sha512-DTtuyXSWB+KetzLcWaSahLJCtTUe/3SXtlGp4ik9PCe9xD6swHEkG8n8/BNsQ9dsihb9nhFvuUB4DpdBGDcvVg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/types": "^3.973.1", "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.10", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", + "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" }, "engines": { @@ -457,19 +457,19 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.4.tgz", - "integrity": "sha512-eRUg+3HaUKuXWn/lEMirdiA5HOKmEl8hEHVuszIDt2MMBUKgVX5XNGmb3XmbgU17h6DZ+RtjbxQpjhz3SbTjZg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/credential-provider-env": "^3.972.4", - "@aws-sdk/credential-provider-http": "^3.972.6", - "@aws-sdk/credential-provider-login": "^3.972.4", - "@aws-sdk/credential-provider-process": "^3.972.4", - "@aws-sdk/credential-provider-sso": "^3.972.4", - "@aws-sdk/credential-provider-web-identity": "^3.972.4", - "@aws-sdk/nested-clients": "3.982.0", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.8.tgz", + "integrity": "sha512-n2dMn21gvbBIEh00E8Nb+j01U/9rSqFIamWRdGm/mE5e+vHQ9g0cBNdrYFlM6AAiryKVHZmShWT9D1JAWJ3ISw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/credential-provider-env": "^3.972.8", + "@aws-sdk/credential-provider-http": "^3.972.10", + "@aws-sdk/credential-provider-login": "^3.972.8", + "@aws-sdk/credential-provider-process": "^3.972.8", + "@aws-sdk/credential-provider-sso": "^3.972.8", + "@aws-sdk/credential-provider-web-identity": "^3.972.8", + "@aws-sdk/nested-clients": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", @@ -482,13 +482,13 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.4.tgz", - "integrity": "sha512-nLGjXuvWWDlQAp505xIONI7Gam0vw2p7Qu3P6on/W2q7rjJXtYjtpHbcsaOjJ/pAju3eTvEQuSuRedcRHVQIAQ==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.8.tgz", + "integrity": "sha512-rMFuVids8ICge/X9DF5pRdGMIvkVhDV9IQFQ8aTYk6iF0rl9jOUa1C3kjepxiXUlpgJQT++sLZkT9n0TMLHhQw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/nested-clients": "3.982.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/nested-clients": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", @@ -501,17 +501,17 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.5.tgz", - "integrity": "sha512-VWXKgSISQCI2GKN3zakTNHSiZ0+mux7v6YHmmbLQp/o3fvYUQJmKGcLZZzg2GFA+tGGBStplra9VFNf/WwxpYg==", + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.9.tgz", + "integrity": "sha512-LfJfO0ClRAq2WsSnA9JuUsNyIicD2eyputxSlSL0EiMrtxOxELLRG6ZVYDf/a1HCepaYPXeakH4y8D5OLCauag==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.4", - "@aws-sdk/credential-provider-http": "^3.972.6", - "@aws-sdk/credential-provider-ini": "^3.972.4", - "@aws-sdk/credential-provider-process": "^3.972.4", - "@aws-sdk/credential-provider-sso": "^3.972.4", - "@aws-sdk/credential-provider-web-identity": "^3.972.4", + "@aws-sdk/credential-provider-env": "^3.972.8", + "@aws-sdk/credential-provider-http": "^3.972.10", + "@aws-sdk/credential-provider-ini": "^3.972.8", + "@aws-sdk/credential-provider-process": "^3.972.8", + "@aws-sdk/credential-provider-sso": "^3.972.8", + "@aws-sdk/credential-provider-web-identity": "^3.972.8", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", @@ -524,12 +524,12 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.4.tgz", - "integrity": "sha512-TCZpWUnBQN1YPk6grvd5x419OfXjHvhj5Oj44GYb84dOVChpg/+2VoEj+YVA4F4E/6huQPNnX7UYbTtxJqgihw==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.8.tgz", + "integrity": "sha512-6cg26ffFltxM51OOS8NH7oE41EccaYiNlbd5VgUYwhiGCySLfHoGuGrLm2rMB4zhy+IO5nWIIG0HiodX8zdvHA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -541,14 +541,14 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.4.tgz", - "integrity": "sha512-wzsGwv9mKlwJ3vHLyembBvGE/5nPUIwRR2I51B1cBV4Cb4ql9nIIfpmHzm050XYTY5fqTOKJQnhLj7zj89VG8g==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.8.tgz", + "integrity": "sha512-35kqmFOVU1n26SNv+U37sM8b2TzG8LyqAcd6iM9gprqxyHEh/8IM3gzN4Jzufs3qM6IrH8e43ryZWYdvfVzzKQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.982.0", - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/token-providers": "3.982.0", + "@aws-sdk/client-sso": "3.990.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/token-providers": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -560,13 +560,13 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.4.tgz", - "integrity": "sha512-hIzw2XzrG8jzsUSEatehmpkd5rWzASg5IHUfA+m01k/RtvfAML7ZJVVohuKdhAYx+wV2AThLiQJVzqn7F0khrw==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.8.tgz", + "integrity": "sha512-CZhN1bOc1J3ubQPqbmr5b4KaMJBgdDvYsmEIZuX++wFlzmZsKj1bwkaiTEb5U2V7kXuzLlpF5HJSOM9eY/6nGA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/nested-clients": "3.982.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/nested-clients": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -611,15 +611,15 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.4.tgz", - "integrity": "sha512-xOxsUkF3O3BtIe3tf54OpPo94eZepjFm3z0Dd2TZKbsPxMiRTFXurC04wJ58o/wPW9YHVO9VqZik3MfoPfrKlw==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.8.tgz", + "integrity": "sha512-Hn6gumcN/3/8Fzo9z7N1pA2PRfE8S+qAqdb4g3MqzXjIOIe+VxD7edO/DKAJ1YH11639EGQIHBz0wdOb5btjtw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "^3.973.6", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/crc64-nvme": "3.972.0", "@aws-sdk/types": "^3.973.1", "@smithy/is-array-buffer": "^4.2.0", @@ -627,7 +627,7 @@ "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.10", + "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -695,23 +695,23 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.6.tgz", - "integrity": "sha512-Xq7wM6kbgJN1UO++8dvH/efPb1nTwWqFCpZCR7RCLOETP7xAUAhVo7JmsCnML5Di/iC4Oo5VrJ4QmkYcMZniLw==", + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.10.tgz", + "integrity": "sha512-wLkB4bshbBtsAiC2WwlHzOWXu1fx3ftL63fQl0DxEda48Q6B8bcHydZppE3KjEIpPyiNOllByfSnb07cYpIgmw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-arn-parser": "^3.972.2", - "@smithy/core": "^3.22.0", + "@smithy/core": "^3.23.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.10", + "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -734,15 +734,15 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.6.tgz", - "integrity": "sha512-TehLN8W/kivl0U9HcS+keryElEWORROpghDXZBLfnb40DXM7hx/i+7OOjkogXQOF3QtUraJVRkHQ07bPhrWKlw==", + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.10.tgz", + "integrity": "sha512-bBEL8CAqPQkI91ZM5a9xnFAzedpzH6NYCOtNyLarRAzTUTFN2DKqaC60ugBa7pnU1jSi4mA7WAXBsrod7nJltg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.982.0", - "@smithy/core": "^3.22.0", + "@aws-sdk/util-endpoints": "3.990.0", + "@smithy/core": "^3.23.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -752,9 +752,9 @@ } }, "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/util-endpoints": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz", - "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz", + "integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", @@ -768,44 +768,44 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.982.0.tgz", - "integrity": "sha512-VVkaH27digrJfdVrT64rjkllvOp4oRiZuuJvrylLXAKl18ujToJR7AqpDldL/LS63RVne3QWIpkygIymxFtliQ==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.990.0.tgz", + "integrity": "sha512-3NA0s66vsy8g7hPh36ZsUgO4SiMyrhwcYvuuNK1PezO52vX3hXDW4pQrC6OQLGKGJV0o6tbEyQtXb/mPs8zg8w==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.6", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/middleware-user-agent": "^3.972.10", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.982.0", + "@aws-sdk/util-endpoints": "3.990.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.4", + "@aws-sdk/util-user-agent-node": "^3.972.8", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.0", + "@smithy/core": "^3.23.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.12", - "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.28", - "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -817,9 +817,9 @@ } }, "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz", - "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz", + "integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", @@ -849,17 +849,17 @@ } }, "node_modules/@aws-sdk/s3-request-presigner": { - "version": "3.983.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.983.0.tgz", - "integrity": "sha512-O/PMdYcCVuBnOyuYglNvaNbh8RWMSE0zBwA8H6m+2VZbtLdALvyXgW5m6Q7wbnKdj+a4bJXh3AStDq6a82TA6Q==", + "version": "3.992.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.992.0.tgz", + "integrity": "sha512-TuMT/J+MlkNLM1GfMssJG7ikCbAPpthGDUf9ZHoJqW6k2+NY5n1MLHh+iFScvKWpTJmWrkwFLAnQjPPniaK5gw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/signature-v4-multi-region": "3.983.0", + "@aws-sdk/signature-v4-multi-region": "3.992.0", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-format-url": "^3.972.3", - "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-endpoint": "^4.4.14", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -868,12 +868,12 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.983.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.983.0.tgz", - "integrity": "sha512-11FCcxI/WKRufKDdPgKPXtrhjDArhkOPb4mf66rICZUnPHlD8Cb7cjZZS/eFC+iuwoHBosrxo0hYsvK3s7DxGw==", + "version": "3.992.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.992.0.tgz", + "integrity": "sha512-jWoaM89xH2cYOY6O+PWMa0yqjzKlE61Ehea1hJe34kHg9QvZOkcSA5OT9CNaFXsAvafeAAHBhSE8XlDiNaJFuw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "^3.972.6", + "@aws-sdk/middleware-sdk-s3": "^3.972.10", "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", @@ -885,13 +885,13 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.982.0.tgz", - "integrity": "sha512-v3M0KYp2TVHYHNBT7jHD9lLTWAdS9CaWJ2jboRKt0WAB65bA7iUEpR+k4VqKYtpQN4+8kKSc4w+K6kUNZkHKQw==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.990.0.tgz", + "integrity": "sha512-L3BtUb2v9XmYgQdfGBzbBtKMXaP5fV973y3Qdxeevs6oUTVXFmi/mV1+LnScA/1wVPJC9/hlK+1o5vbt7cG7EQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/nested-clients": "3.982.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/nested-clients": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -928,9 +928,9 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.983.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.983.0.tgz", - "integrity": "sha512-t/VbL2X3gvDEjC4gdySOeFFOZGQEBKwa23pRHeB7hBLBZ119BB/2OEFtTFWKyp3bnMQgxpeVeGS7/hxk6wpKJw==", + "version": "3.992.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.992.0.tgz", + "integrity": "sha512-FHgdMVbTZ2Lu7hEIoGYfkd5UazNSsAgPcupEnh15vsWKFKhuw6w/6tM1k/yNaa7l1wx0Wt1UuK0m+gQ0BJpuvg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", @@ -983,12 +983,12 @@ } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.4.tgz", - "integrity": "sha512-3WFCBLiM8QiHDfosQq3Py+lIMgWlFWwFQliUHUqwEiRqLnKyhgbU3AKa7AWJF7lW2Oc/2kFNY4MlAYVnVc0i8A==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.8.tgz", + "integrity": "sha512-XJZuT0LWsFCW1C8dEpPAXSa7h6Pb3krr2y//1X0Zidpcl0vmgY5nL/X0JuBZlntpBzaN3+U4hvKjuijyiiR8zw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/middleware-user-agent": "^3.972.10", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", @@ -1201,9 +1201,9 @@ } }, "node_modules/@firebase/ai": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-2.7.0.tgz", - "integrity": "sha512-PwpCz+TtAMWICM7uQNO0mkSPpUKwrMV4NSwHkbVKDvPKoaQmSlO96vIz+Suw2Ao1EaUUsxYb5LGImHWt/fSnRQ==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-2.8.0.tgz", + "integrity": "sha512-grWYGFPsSo+pt+6CYeKR0kWnUfoLLS3xgWPvNrhAS5EPxl6xWq7+HjDZqX24yLneETyl45AVgDsTbVgxeWeRfg==", "license": "Apache-2.0", "dependencies": { "@firebase/app-check-interop-types": "0.3.3", @@ -1259,9 +1259,9 @@ "license": "Apache-2.0" }, "node_modules/@firebase/app": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.7.tgz", - "integrity": "sha512-o3ZfnOx0AWBD5n/36p2zPoB0rDDxQP8H/A60zDLvvfRLtW8b3LfCyV97GKpJaAVV1JMMl/BC89EDzMyzxFZxTw==", + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.8.tgz", + "integrity": "sha512-WiE9uCGRLUnShdjb9iP20sA3ToWrBbNXr14/N5mow7Nls9dmKgfGaGX5cynLvrltxq2OrDLh1VDNaUgsnS/k/g==", "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.7.0", @@ -1325,12 +1325,12 @@ "license": "Apache-2.0" }, "node_modules/@firebase/app-compat": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.7.tgz", - "integrity": "sha512-MO+jfap8IBZQ+K8L2QCiHObyMgpYHrxo4Hc7iJgfb9hjGRW/z1y6LWVdT9wBBK+VJ7cRP2DjAiWQP+thu53hHA==", + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.8.tgz", + "integrity": "sha512-4De6SUZ36zozl9kh5rZSxKWULpgty27rMzZ6x+xkoo7+NWyhWyFdsdvhFsWhTw/9GGj0wXIcbTjwHYCUIUuHyg==", "license": "Apache-2.0", "dependencies": { - "@firebase/app": "0.14.7", + "@firebase/app": "0.14.8", "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", @@ -1480,9 +1480,9 @@ } }, "node_modules/@firebase/firestore": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.10.0.tgz", - "integrity": "sha512-fgF6EbpoagGWh5Vwfu/7/jYgBFwUCwTlPNVF/aSjHcoEDRXpRsIqVfAFTp1LD+dWAUcAKEK3h+osk8spMJXtxA==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.11.0.tgz", + "integrity": "sha512-Zb88s8rssBd0J2Tt+NUXMPt2sf+Dq7meatKiJf5t9oto1kZ8w9gK59Koe1uPVbaKfdgBp++N/z0I4G/HamyEhg==", "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.7.0", @@ -1501,13 +1501,13 @@ } }, "node_modules/@firebase/firestore-compat": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.4.tgz", - "integrity": "sha512-JvxxIgi+D5v9BecjLA1YomdyF7LA6CXhJuVK10b4GtRrB3m2O2hT1jJWbKYZYHUAjTaajkvnos+4U5VNxqkI2w==", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.5.tgz", + "integrity": "sha512-yVX1CkVvqBI4qbA56uZo42xFA4TNU0ICQ+9AFDvYq9U9Xu6iAx9lFDAk/tN+NGereQQXXCSnpISwc/oxsQqPLA==", "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.7.0", - "@firebase/firestore": "4.10.0", + "@firebase/firestore": "4.11.0", "@firebase/firestore-types": "3.0.3", "@firebase/util": "1.13.0", "tslib": "^2.1.0" @@ -1927,9 +1927,9 @@ } }, "node_modules/@mongodb-js/saslprep": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.5.tgz", - "integrity": "sha512-k64Lbyb7ycCSXHSLzxVdb2xsKGPMvYZfCICXvDsI8Z65CeWQzTEKS4YmGbnqw+U9RBvLPTsB6UCmwkgsDTGWIw==", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz", + "integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==", "license": "MIT", "optional": true, "dependencies": { @@ -2098,9 +2098,9 @@ } }, "node_modules/@smithy/core": { - "version": "3.22.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.22.1.tgz", - "integrity": "sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g==", + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.2.tgz", + "integrity": "sha512-HaaH4VbGie4t0+9nY3tNBRSxVTr96wzIqexUa6C2qx3MPePAuz7lIxPxYtt1Wc//SPfJLNoZJzfdt0B6ksj2jA==", "license": "Apache-2.0", "dependencies": { "@smithy/middleware-serde": "^4.2.9", @@ -2109,7 +2109,7 @@ "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.11", + "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" @@ -2318,12 +2318,12 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.13.tgz", - "integrity": "sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w==", + "version": "4.4.16", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.16.tgz", + "integrity": "sha512-L5GICFCSsNhbJ5JSKeWFGFy16Q2OhoBizb3X2DrxaJwXSEujVvjG9Jt386dpQn2t7jINglQl0b4K/Su69BdbMA==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.22.1", + "@smithy/core": "^3.23.2", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -2337,15 +2337,15 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.4.30", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.30.tgz", - "integrity": "sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg==", + "version": "4.4.33", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.33.tgz", + "integrity": "sha512-jLqZOdJhtIL4lnA9hXnAG6GgnJlo1sD3FqsTxm9wSfjviqgWesY/TMBVnT84yr4O0Vfe0jWoXlfFbzsBVph3WA==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", - "@smithy/smithy-client": "^4.11.2", + "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -2399,9 +2399,9 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.9.tgz", - "integrity": "sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w==", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.10.tgz", + "integrity": "sha512-u4YeUwOWRZaHbWaebvrs3UhwQwj+2VNmcVCwXcYTvPIuVyM7Ex1ftAj+fdbG/P4AkBwLq/+SKn+ydOI4ZJE9PA==", "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^4.2.8", @@ -2512,17 +2512,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.2.tgz", - "integrity": "sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A==", + "version": "4.11.5", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.5.tgz", + "integrity": "sha512-xixwBRqoeP2IUgcAl3U9dvJXc+qJum4lzo3maaJxifsZxKUYLfVfCXvhT4/jD01sRrHg5zjd1cw2Zmjr4/SuKQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.22.1", - "@smithy/middleware-endpoint": "^4.4.13", + "@smithy/core": "^3.23.2", + "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.11", + "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" }, "engines": { @@ -2619,13 +2619,13 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.29", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.29.tgz", - "integrity": "sha512-nIGy3DNRmOjaYaaKcQDzmWsro9uxlaqUOhZDHQed9MW/GmkBZPtnU70Pu1+GT9IBmUXwRdDuiyaeiy9Xtpn3+Q==", + "version": "4.3.32", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.32.tgz", + "integrity": "sha512-092sjYfFMQ/iaPH798LY/OJFBcYu0sSK34Oy9vdixhsU36zlZu8OcYjF3TD4e2ARupyK7xaxPXl+T0VIJTEkkg==", "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.11.2", + "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -2634,16 +2634,16 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.32", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.32.tgz", - "integrity": "sha512-7dtFff6pu5fsjqrVve0YMhrnzJtccCWDacNKOkiZjJ++fmjGExmmSu341x+WU6Oc1IccL7lDuaUj7SfrHpWc5Q==", + "version": "4.2.35", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.35.tgz", + "integrity": "sha512-miz/ggz87M8VuM29y7jJZMYkn7+IErM5p5UgKIf8OtqVs/h2bXr1Bt3uTsREsI/4nK8a0PQERbAPsVPVNIsG7Q==", "license": "Apache-2.0", "dependencies": { "@smithy/config-resolver": "^4.4.6", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.11.2", + "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -2705,13 +2705,13 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.11.tgz", - "integrity": "sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA==", + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.12.tgz", + "integrity": "sha512-D8tgkrmhAX/UNeCZbqbEO3uqyghUnEmmoO9YEvRuwxjlkKKUE7FOgCJnqpTlQPe9MApdWPky58mNQQHbnCzoNg==", "license": "Apache-2.0", "dependencies": { "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.9", + "@smithy/node-http-handler": "^4.4.10", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", @@ -2939,9 +2939,9 @@ } }, "node_modules/@types/node": { - "version": "25.2.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.0.tgz", - "integrity": "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==", + "version": "25.2.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", + "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -3770,9 +3770,9 @@ "license": "MIT" }, "node_modules/bowser": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", - "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", "license": "MIT" }, "node_modules/brace-expansion": { @@ -5289,19 +5289,25 @@ } }, "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5737,26 +5743,26 @@ } }, "node_modules/firebase": { - "version": "12.8.0", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-12.8.0.tgz", - "integrity": "sha512-S1tCIR3ENecee0tY2cfTHfMkXqkitHfbsvqpCtvsT0Zi9vDB7A4CodAjHfHCjVvu/XtGy1LHLjOasVcF10rCVw==", + "version": "12.9.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-12.9.0.tgz", + "integrity": "sha512-CwwTYoqZg6KxygPOaaJqIc4aoLvo0RCRrXoln9GoxLE8QyAwTydBaSLGVlR4WPcuOgN3OEL0tJLT1H4IU/dv7w==", "license": "Apache-2.0", "dependencies": { - "@firebase/ai": "2.7.0", + "@firebase/ai": "2.8.0", "@firebase/analytics": "0.10.19", "@firebase/analytics-compat": "0.2.25", - "@firebase/app": "0.14.7", + "@firebase/app": "0.14.8", "@firebase/app-check": "0.11.0", "@firebase/app-check-compat": "0.4.0", - "@firebase/app-compat": "0.5.7", + "@firebase/app-compat": "0.5.8", "@firebase/app-types": "0.9.3", "@firebase/auth": "1.12.0", "@firebase/auth-compat": "0.6.2", "@firebase/data-connect": "0.3.12", "@firebase/database": "1.1.0", "@firebase/database-compat": "2.1.0", - "@firebase/firestore": "4.10.0", - "@firebase/firestore-compat": "0.4.4", + "@firebase/firestore": "4.11.0", + "@firebase/firestore-compat": "0.4.5", "@firebase/functions": "0.13.1", "@firebase/functions-compat": "0.4.1", "@firebase/installations": "0.6.19", @@ -5845,15 +5851,6 @@ "node": ">= 12.20" } }, - "node_modules/formdata-node/node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "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", - "engines": { - "node": ">= 14" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -6141,9 +6138,9 @@ "license": "MIT" }, "node_modules/groq-sdk": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/groq-sdk/-/groq-sdk-0.4.0.tgz", - "integrity": "sha512-h79q9sv4hcOBESR05N5eqHlGhAug9H9lr3EIiB+37ysWWekeG+KYQDK2lIIHYCm6O9LzgZzO/VdLdPP298+T0w==", + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/groq-sdk/-/groq-sdk-0.37.0.tgz", + "integrity": "sha512-lT72pcT8b/X5XrzdKf+rWVzUGW1OQSKESmL8fFN5cTbsf02gq6oFam4SVeNtzELt9cYE2Pt3pdGgSImuTbHFDg==", "license": "Apache-2.0", "dependencies": { "@types/node": "^18.11.18", @@ -6152,8 +6149,7 @@ "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7", - "web-streams-polyfill": "^3.2.1" + "node-fetch": "^2.6.7" } }, "node_modules/groq-sdk/node_modules/@types/node": { @@ -7381,6 +7377,35 @@ "node": ">=10.5.0" } }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/node-fetch": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", @@ -7963,9 +7988,9 @@ } }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -8301,9 +8326,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -9220,12 +9245,12 @@ } }, "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "version": "4.0.0-beta.3", + "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", "engines": { - "node": ">= 8" + "node": ">= 14" } }, "node_modules/web-vitals": { diff --git a/backend/package.json b/backend/package.json index 735e91c..5951f82 100644 --- a/backend/package.json +++ b/backend/package.json @@ -10,7 +10,7 @@ "express-async-handler": "^1.2.0", "express-validator": "^7.0.1", "firebase": "^12.5.0", - "groq-sdk": "^0.4.0", + "groq-sdk": "^0.37.0", "http-errors": "^2.0.0", "module-alias": "^2.2.3", "mongodb": "^5.9.2", @@ -26,7 +26,7 @@ "format": "npm run check-git-hooks && prettier --write .", "lint-fix": "npm run check-git-hooks && (eslint --fix --cache --report-unused-disable-directives . || true) && prettier --write .", "lint-check": "npm run check-git-hooks && eslint --cache --report-unused-disable-directives . && prettier --check .", - "prepare": "is-ci || (cd .. && husky)", + "prepare": "node -e \"process.exit(process.env.CI || process.env.VERCEL ? 0 : 1)\" || (cd .. && husky)", "check-git-hooks": "[ \"$CI\" = \"true\" ] || (cd .. && node .secret-scan/secret-scan.js -- --check-git-hooks)" }, "keywords": [], diff --git a/backend/src/app.ts b/backend/src/app.ts index 3e832e5..2546eb2 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -13,6 +13,7 @@ import errorHandler from "../src/middlewares/errorHandler"; import { logger } from "../src/middlewares/logger"; import tipRouter from "../src/routes/tipRoutes"; import profileRoutes from "../src/routes/profileRoutes"; +import groqRoutes from "../src/routes/groqRoutes"; const app = express(); @@ -42,6 +43,7 @@ app.use("/api/questions/leetcode", leetcodeQuestionRouter); app.use("/api/questions/interview", interviewQuestionRouter); app.use("/api/tips", tipRouter); app.use("/api/articles", articleRouter); +app.use("/api/email", groqRoutes); /** * Error handler; all errors thrown by server are handled here. diff --git a/backend/src/controllers/groqController.ts b/backend/src/controllers/groqController.ts new file mode 100644 index 0000000..80c056d --- /dev/null +++ b/backend/src/controllers/groqController.ts @@ -0,0 +1,315 @@ +import { RequestHandler } from "express"; +import asyncHandler from "express-async-handler"; +import createHttpError from "http-errors"; +import { validationResult, matchedData } from "express-validator"; +import { Groq } from "groq-sdk"; +import validationErrorParser from "../util/validationErrorParser"; +import User from "../models/User"; +import Student from "../models/Student"; +import Alumni from "../models/Alumni"; + +interface StudentData { + name: string; + school?: string; + fieldOfInterest?: string[]; + projects?: string[]; + hobbies?: string[]; + skills?: string[]; + companiesOfInterest?: string[]; + major?: string; + classLevel?: string; + organizations?: string[]; +} + +interface AlumniData { + name: string; + position?: string; + company?: string; + organizations?: string[]; + specializations?: string[]; + hobbies?: string[]; + skills?: string[]; +} + +interface Similarity { + category: string; + description: string; +} + +interface SimilarityResponse { + similarities: Similarity[]; + summary: string; +} + +function getGroqClient() { + const groqApiKey = process.env.GROQ_API_KEY; + + if (!groqApiKey) { + throw createHttpError(500, "Groq API key not configured"); + } + + return new Groq({ apiKey: groqApiKey }); +} + +export async function analyzeSimilarities( + student: StudentData, + alumni: AlumniData, +): Promise { + const groq = getGroqClient(); + + const prompt = ` + You are an expert career mentor analyzing similarities between a student and an alumni. + + STUDENT PROFILE: + - School: ${student.school || "Not provided"} + - Major: ${student.major || "Not provided"} + - Class Level: ${student.classLevel || "Not provided"} + - Field of Interest: ${student.fieldOfInterest?.join(", ") || "Not provided"} + - Skills: ${student.skills?.join(", ") || "Not provided"} + - Hobbies: ${student.hobbies?.join(", ") || "Not provided"} + - Projects: ${student.projects?.join(", ") || "Not provided"} + - Companies of Interest: ${student.companiesOfInterest?.join(", ") || "Not provided"} + + ALUMNI PROFILE: + - Position: ${alumni.position || "Not provided"} + - Company: ${alumni.company || "Not provided"} + - Specializations: ${alumni.specializations?.join(", ") || "Not provided"} + - Skills: ${alumni.skills?.join(", ") || "Not provided"} + - Hobbies: ${alumni.hobbies?.join(", ") || "Not provided"} + - Organizations: ${alumni.organizations?.join(", ") || "Not provided"} + + Provide info for the user about the key similarities between the student and alumni in one single + bullet point for each similarity. Be sure to reference the user/student as "you" and the alumni + as "this alumni". KEEP EACH DESCRIPTION SPECIFIC TO THE STUDEN AND AT MAX 20 WORDS. + Focus on the following categories of similarity: + 1. Shared skills + 2. Overlapping interests + 3. Similar career goals + 4. Shared hobbies and passions + 5. Similar Educational backgrounds + + Respond in the following JSON format (no markdown, pure JSON): + { + "similarities": [ + { + "category": "Category Name", + "description": "Brief description of the similarity" + } + ], + "summary": "A brief summary of overall similarity and potential mentorship value" + }`; + + try { + const message = await groq.chat.completions.create({ + model: "llama-3.1-8b-instant", + max_tokens: 1024, + response_format: { type: "json_object" }, + messages: [ + { + role: "user", + content: prompt, + }, + ], + }); + + const responseText = message.choices[0].message.content || ""; + + const jsonMatch = responseText.match(/\{[\s\S]*\}/); + if (!jsonMatch) { + throw createHttpError(500, "Failed to parse Groq response"); + } + + let similarities: SimilarityResponse; + try { + similarities = JSON.parse(responseText); + } catch (e) { + console.error("JSON.parse failed on Groq content:", responseText); + throw createHttpError(500, "Groq returned invalid JSON"); + } + return similarities; + } catch (error) { + console.error("error in analyze similarities: ", error); + if (error instanceof createHttpError.HttpError) { + throw error; + } + throw createHttpError( + 500, + `Error calling Groq API: ${error instanceof Error ? error.message : "Unknown error"}`, + ); + } +} + +// Helper to calculate shared interests +const calculateSharedInterests = ( + student: StudentData, + alumni: AlumniData, +): string[] => { + const shared: string[] = []; + + // 1. Compare Student Field of Interest vs Alumni Specializations/Industry + if (student.fieldOfInterest && alumni.specializations) { + const studentFields = student.fieldOfInterest.map((s: string) => + s.toLowerCase(), + ); + const alumniSpecs = alumni.specializations.map((s: string) => + s.toLowerCase(), + ); + + const common = studentFields.filter((f: string) => + alumniSpecs.some((s: string) => s.includes(f) || f.includes(s)), + ); + shared.push(...common); + } + + // 2. Compare Hobbies + if (student.hobbies && alumni.hobbies) { + const studentHobbies = student.hobbies.map((s: string) => s.toLowerCase()); + const alumniHobbies = alumni.hobbies.map((s: string) => s.toLowerCase()); + + const common = studentHobbies.filter((h: string) => + alumniHobbies.includes(h), + ); + shared.push(...common); + } + + // 3. Compare Skills + if (student.skills && alumni.skills) { + const studentSkills = student.skills.map((s: string) => s.toLowerCase()); + const alumniSkills = alumni.skills.map((s: string) => s.toLowerCase()); + + const common = studentSkills.filter((s: string) => + alumniSkills.includes(s), + ); + shared.push(...common); + } + + // 4. Compare Organizations (if student organizations existed in model, but for now we look at general matches if name mentions logic) + // Since User model has organizations for both, check that + if (student.organizations && alumni.organizations) { + const studentOrgs = student.organizations.map((s: string) => + s.toLowerCase(), + ); + const alumniOrgs = alumni.organizations.map((s: string) => s.toLowerCase()); + + const common = studentOrgs.filter((o: string) => alumniOrgs.includes(o)); + shared.push(...common); + } + + return [...new Set(shared)]; // unique items +}; + +export const generateEmail: RequestHandler = asyncHandler( + async (req, res, next) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return next(createHttpError(400, validationErrorParser(errors))); + } + + const { studentId, alumniId, tone, purpose } = matchedData(req); + const groq = getGroqClient(); + + // 1. Fetch Student and Alumni Data + // We need both the User document (for name, etc) and the specific Student/Alumni document (for details) + // However, the User model contains most things now based on my earlier read, but Student/Alumni specific models exist too. + // Let's rely on the User model as the base, and fetch specific profiles if needed. + // Actually, User.ts seems to contain most fields (organizations, specializations, etc) for Alumni, and (major, school) for Student. + // But Student.ts has fieldOfInterest. Let's fetch both to be safe. + + const studentUser = await User.findById(studentId); + const alumniUser = await User.findById(alumniId).populate("company"); + + if (!studentUser || !alumniUser) { + return next(createHttpError(404, "Student or Alumni not found")); + } + + // Fetch specialized docs if needed (Student model has fieldOfInterest) + const studentProfile = await Student.findOne({ userId: studentId }); + const alumniProfile = await Alumni.findOne({ userId: alumniId }); + + // Merge data for processing + const studentData = { + ...studentUser.toObject(), + ...studentProfile?.toObject(), + // Ensure arrays exist + fieldOfInterest: + studentProfile?.fieldOfInterest || studentUser.fieldOfInterest || [], + hobbies: studentProfile?.hobbies || studentUser.hobbies || [], + skills: studentProfile?.skills || studentUser.skills || [], + projects: studentProfile?.projects || studentUser.projects || [], + }; + + const alumniData = { + ...alumniUser.toObject(), + ...alumniProfile?.toObject(), + organizations: + alumniProfile?.organizations || alumniUser.organizations || [], + specializations: + alumniProfile?.specializations || alumniUser.specializations || [], + hobbies: alumniProfile?.hobbies || alumniUser.hobbies || [], + skills: alumniProfile?.skills || alumniUser.skills || [], + }; + + // 2. Calculate Shared Interests + const sharedInterests = calculateSharedInterests(studentData, alumniData); + + // 3. Construct Prompt + const prompt = ` + Write a personalized email from a student to an alumnus. + + **Student Details:** + - Name: ${studentData.name} + - Major: ${studentData.major || "Undecided"} + - School: ${studentData.school || "University"} + + **Alumni Details:** + - Name: ${alumniData.name} + - Position: ${alumniData.position || "Professional"} + - Company: ${(alumniData.company as any)?.name || "their company" /* eslint-disable-line @typescript-eslint/no-explicit-any */} + + **Shared Interests/Common Ground:** + ${sharedInterests.length > 0 ? sharedInterests.join(", ") : "None specifically found, focus on their career path."} + + **User Options:** + - Tone: ${tone || "Professional"} + - Purpose: ${purpose || "To ask for a coffee chat to learn more about their career."} + + **Instructions:** + - Keep it concise (under 150 words). + - Use the shared interests to build rapport if available. + - Be polite and respectful. + - Output ONLY the email body text. Do not include subject line or placeholders like "[Insert Name]". + `; + + // 4. Call Groq + try { + const chatCompletion = await groq.chat.completions.create({ + messages: [ + { + role: "system", + content: + "You are a helpful career assistant helping students network with alumni.", + }, + { + role: "user", + content: prompt, + }, + ], + model: "openai/gpt-oss-120b", + temperature: 0.7, + max_tokens: 300, + }); + + const emailContent = chatCompletion.choices[0]?.message?.content || ""; + + res.status(200).json({ email: emailContent, sharedInterests }); + } catch (error) { + console.error("Groq API Error:", error); + return next( + createHttpError( + 500, + "Failed to generate email. Please try again later.", + ), + ); + } + }, +); diff --git a/backend/src/controllers/userController.ts b/backend/src/controllers/userController.ts index d7cdf37..84b1d0a 100644 --- a/backend/src/controllers/userController.ts +++ b/backend/src/controllers/userController.ts @@ -4,7 +4,7 @@ import asyncHandler from "express-async-handler"; import createHttpError from "http-errors"; import validationErrorParser from "../util/validationErrorParser"; import Company from "../models/Company"; -import { analyzeSimilarities } from "../controllers/SimilarityController"; +import { analyzeSimilarities } from "../controllers/groqController"; interface BaseUserResponse { _id?: string; diff --git a/backend/src/routes/groqRoutes.ts b/backend/src/routes/groqRoutes.ts new file mode 100644 index 0000000..ecff2f4 --- /dev/null +++ b/backend/src/routes/groqRoutes.ts @@ -0,0 +1,13 @@ +import express from "express"; +import * as GroqController from "../controllers/groqController"; +import { generateEmailValidator } from "../validators/groqValidator"; + +const router = express.Router(); + +router.post( + "/generate-email", + generateEmailValidator, + GroqController.generateEmail, +); + +export default router; diff --git a/backend/src/validators/groqValidator.ts b/backend/src/validators/groqValidator.ts new file mode 100644 index 0000000..0255601 --- /dev/null +++ b/backend/src/validators/groqValidator.ts @@ -0,0 +1,25 @@ +import { body } from "express-validator"; + +export const generateEmailValidator = [ + body("studentId") + .exists() + .withMessage("Student ID is required.") + .isString() + .withMessage("Invalid Student ID format."), + body("alumniId") + .exists() + .withMessage("Alumni ID is required.") + .isString() + .withMessage("Invalid Alumni ID format."), + body("tone") + .optional() + .isString() + .isIn(["Professional", "Friendly", "Enthusiastic"]) + .withMessage("Invalid tone."), + body("purpose") + .optional() + .isString() + .trim() + .isLength({ max: 500 }) + .withMessage("Purpose must be less than 500 characters."), +]; diff --git a/frontend/src/api/email.ts b/frontend/src/api/email.ts new file mode 100644 index 0000000..7aaeb05 --- /dev/null +++ b/frontend/src/api/email.ts @@ -0,0 +1,31 @@ +import { APIResult, post, handleAPIError } from "./requests"; + +export interface GenerateEmailRequest { + studentId: string; + alumniId: string; + tone?: string; + purpose?: string; +} + +export interface GenerateEmailResponse { + email: string; + sharedInterests: string[]; +} + +/** + * Generate a personalized email to an alumni. + * + * @param data Request data (studentId, alumniId, tone, purpose) + * @returns Generated email and shared interests + */ +export async function generateEmail( + data: GenerateEmailRequest +): Promise> { + try { + const response = await post("/api/email/generate-email", data); + const json = (await response.json()) as GenerateEmailResponse; + return { success: true, data: json }; + } catch (error) { + return handleAPIError(error); + } +} diff --git a/frontend/src/pages/Alumni.tsx b/frontend/src/pages/Alumni.tsx index 1dba3b2..eb237a3 100644 --- a/frontend/src/pages/Alumni.tsx +++ b/frontend/src/pages/Alumni.tsx @@ -1,48 +1,101 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useEffect, useState, useRef } from "react"; import { useParams, useNavigate } from "react-router-dom"; import { FaArrowLeft, FaLinkedin } from "react-icons/fa"; -import { LuMail, LuBuilding2, LuBriefcase } from "react-icons/lu"; +import { LuMail, LuBuilding2, LuBriefcase, LuWand } from "react-icons/lu"; import { FiPhone } from "react-icons/fi"; +import { FaRegCopy } from "react-icons/fa"; +import { generateEmail } from "../api/email"; +import { useAuth } from "../contexts/useAuth"; +import Modal from "../components/public/Modal"; import { getAlumniById, getSimilarities } from "../api/users"; import { APIResult } from "../api/requests"; import { Alumni } from "../types/User"; import { ProgressSpinner } from "primereact/progressspinner"; -import { useAuth } from "../contexts/useAuth"; import { Similarity } from "../types/Similarity"; +import {Toast} from "primereact/toast"; const AlumniProfile: React.FC = () => { const { id } = useParams<{ id: string }>(); + const toast = useRef(null); console.log("route param id: ", id); const navigate = useNavigate(); const [alumni, setAlumni] = useState(null); const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); const { user} = useAuth(); const [similarities, setSimilarities] = useState(null); const [similaritySummary, setSimilaritySummary] = useState(null); const [similarityLoading, setSimilarityLoading] = useState(false); + // Email Generation State + const [isEmailModalOpen, setIsEmailModalOpen] = useState(false); + const [tone, setTone] = useState("Professional"); + const [purpose, setPurpose] = useState(""); + const [generatedEmail, setGeneratedEmail] = useState(""); + const [sharedInterests, setSharedInterests] = useState([]); + const [generating, setGenerating] = useState(false); + + const handleGenerate = async () => { + if (!user?._id || !id) return; + setGenerating(true); + setGeneratedEmail(""); + + const result = await generateEmail({ + studentId: user._id, + alumniId: id, + tone, + purpose + }); + + if (result.success) { + setGeneratedEmail(result.data.email); + setSharedInterests(result.data.sharedInterests); + } else { + toast.current?.show({ + severity: "error", + summary: "Error", + detail: result.error || "Failed to generate email" + }); + } + setGenerating(false); + }; + + const copyToClipboard = () => { + navigator.clipboard.writeText(generatedEmail); + toast.current?.show({ + severity: "success", + summary: "Copied", + detail: "Email text copied to clipboard" + }); + }; + const handleAlumniUpdate = useCallback(() => { - if (!id) return; - setLoading(true); - getAlumniById(id) - .then((result: APIResult) => { - if (result.success) { - setAlumni(result.data); - setError(null); - } else { - setError(result.error); - console.log(error); - } - }) - .catch((e) => setError(e instanceof Error ? e.message : "Unknown error")) - .finally(() => setLoading(false)); - }, [id]); + if (!id) return; + setLoading(true); + getAlumniById(id) + .then((result: APIResult) => { + if (result.success) { + setAlumni(result.data); + } else { + toast.current?.clear(); + toast.current?.show({ + severity: "error", + summary: "Error", + detail: "Failed to fetch alumni profile: " + result.error, + }); + } + }) + .catch(() => toast.current?.show({ + severity: "error", + summary: "Error", + detail: "Unexpected error occurred.", + })) + .finally(() => setLoading(false)); + }, [id]); // Initial company fetch useEffect(() => { @@ -88,31 +141,37 @@ const AlumniProfile: React.FC = () => { ); return ( -
- {/* Display Spinner While Loading */} - {loading && ( -
- +
+ {!alumni ? ( +
+ Alumni not found.
- )} - {/* When Finished Loading */} - {!loading && ( -
-
- {/* Back Button to Exit Profile */} - -
-
-
-
+ ) : ( + <> + {/* Display Spinner While Loading */} + {loading && ( +
+ +
+ )} + {/* When Finished Loading */} + {!loading && ( +
+
+ {/* Back Button to Exit Profile */} + +
+
+
+
{/* Left Column - Profile Image */}
@@ -133,21 +192,30 @@ const AlumniProfile: React.FC = () => { )}
- + {/* Right Column - User Information */}
{/* Basic Information */}
-

- Basic Information -

+
+

+ Basic Information +

+ +

{alumni.name}

-
+
- + {/* LinkedIn */}
- - + + {/* Alumni-specific Information */} -
-
-

- Professional Information -

-
-
- -

- {alumni.company?.name || "Not specified"} -

-
- -
- -

- {alumni.position || "Not specified"} -

-
+
+
+

+ Professional Information +

+
+
+ +

+ {alumni.company?.name || "Not specified"} +

-
-
+ +
- {Array.isArray(alumni.organizations) && alumni.organizations.length > 0 ? ( -
- {alumni.organizations.map((organization, index) => ( +

+ {alumni.position || "Not specified"} +

+
+
+
+
+ + {Array.isArray(alumni.organizations) && alumni.organizations.length > 0 ? ( +
+ {alumni.organizations.map((organization, index) => ( { {organization.charAt(0).toUpperCase() + organization.slice(1).toLowerCase()} ))} -
- ) : ( +
+ ) : (

Not specified

- )} -
- -
- - {Array.isArray(alumni.specializations) && alumni.specializations.length > 0 ? ( -
- {alumni.specializations.map((specialization, index) => ( + )} +
+ +
+ + {Array.isArray(alumni.specializations) && alumni.specializations.length > 0 ? ( +
+ {alumni.specializations.map((specialization, index) => ( { {specialization.charAt(0).toUpperCase() + specialization.slice(1).toLowerCase()} ))} -
- ) : ( +
+ ) : (

Not specified

- )} -
- -
- - {Array.isArray(alumni.hobbies) && alumni.hobbies.length > 0 ? ( -
- {alumni.hobbies.map((hobby, index) => ( + )} +
+ +
+ + {Array.isArray(alumni.hobbies) && alumni.hobbies.length > 0 ? ( +
+ {alumni.hobbies.map((hobby, index) => ( { {hobby.charAt(0).toUpperCase() + hobby.slice(1).toLowerCase()} ))} -
- ) : ( +
+ ) : (

Not specified

- )} -
- -
- - {Array.isArray(alumni.skills) && alumni.skills.length > 0 ? ( -
- {alumni.skills.map((skill, index) => ( + )} +
+ +
+ + {Array.isArray(alumni.skills) && alumni.skills.length > 0 ? ( +
+ {alumni.skills.map((skill, index) => ( {
+ +
-
+
+ )} + )} -
+ + {/* Email Generation Modal */} + setIsEmailModalOpen(false)} + className="w-full max-w-2xl p-6 rounded-xl" + useOverlay + > +
+

+ + Personalize Email +

+

+ Generate a personalized outreach email to {alumni?.name} based on your shared interests. +

+ +
+
+ + +
+
+ + setPurpose(e.target.value)} + placeholder="e.g. Ask for resume advice" + className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none" + /> +
+
+ + + + {generatedEmail && ( +
+
+ Generated Draft + +
+