Enhance platform security and monitoring with rate limiting and caching

Adds security middleware, rate limiting, Redis caching, and monitoring services for improved security, performance, and analytics.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: c5f0c281-8dd8-4846-b452-4a07bcd21062
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9777c70b-fc38-4831-8d6b-78dfffe041b0/c9b78723-c2e4-4754-83c6-8d40e8eb250e.jpg
This commit is contained in:
ghaddaditw
2025-06-08 06:54:37 +00:00
parent 59ff05526b
commit c21d5ecfad
6 changed files with 1217 additions and 18 deletions
+596
View File
@@ -45,23 +45,29 @@
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
"compression": "^1.8.0",
"connect-pg-simple": "^10.0.0", "connect-pg-simple": "^10.0.0",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"drizzle-orm": "^0.39.1", "drizzle-orm": "^0.39.1",
"drizzle-zod": "^0.7.0", "drizzle-zod": "^0.7.0",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"express": "^4.21.2", "express": "^4.21.2",
"express-rate-limit": "^7.5.0",
"express-session": "^1.18.1", "express-session": "^1.18.1",
"express-winston": "^4.2.0",
"framer-motion": "^11.13.1", "framer-motion": "^11.13.1",
"helmet": "^8.1.0",
"i18next": "^25.2.1", "i18next": "^25.2.1",
"i18next-browser-languagedetector": "^8.1.0", "i18next-browser-languagedetector": "^8.1.0",
"i18next-http-backend": "^3.0.2", "i18next-http-backend": "^3.0.2",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"ioredis": "^5.6.1",
"lucide-react": "^0.453.0", "lucide-react": "^0.453.0",
"memorystore": "^1.6.7", "memorystore": "^1.6.7",
"multer": "^2.0.0", "multer": "^2.0.0",
"nanoid": "^5.1.5", "nanoid": "^5.1.5",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"node-cron": "^4.1.0",
"openai": "^5.1.1", "openai": "^5.1.1",
"passport": "^0.7.0", "passport": "^0.7.0",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
@@ -73,10 +79,12 @@
"react-icons": "^5.4.0", "react-icons": "^5.4.0",
"react-resizable-panels": "^2.1.7", "react-resizable-panels": "^2.1.7",
"recharts": "^2.15.2", "recharts": "^2.15.2",
"redis": "^5.5.6",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"tw-animate-css": "^1.2.5", "tw-animate-css": "^1.2.5",
"vaul": "^1.1.2", "vaul": "^1.1.2",
"winston": "^3.17.0",
"wouter": "^3.3.5", "wouter": "^3.3.5",
"ws": "^8.18.0", "ws": "^8.18.0",
"zod": "^3.24.2", "zod": "^3.24.2",
@@ -442,6 +450,26 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@colors/colors": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
"integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==",
"license": "MIT",
"engines": {
"node": ">=0.1.90"
}
},
"node_modules/@dabh/diagnostics": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
"integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==",
"license": "MIT",
"dependencies": {
"colorspace": "1.1.x",
"enabled": "2.0.x",
"kuler": "^2.0.0"
}
},
"node_modules/@drizzle-team/brocli": { "node_modules/@drizzle-team/brocli": {
"version": "0.10.2", "version": "0.10.2",
"resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz",
@@ -1352,6 +1380,12 @@
"react-hook-form": "^7.0.0" "react-hook-form": "^7.0.0"
} }
}, },
"node_modules/@ioredis/commands": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==",
"license": "MIT"
},
"node_modules/@isaacs/cliui": { "node_modules/@isaacs/cliui": {
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -2735,6 +2769,66 @@
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==" "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="
}, },
"node_modules/@redis/bloom": {
"version": "5.5.6",
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.5.6.tgz",
"integrity": "sha512-bNR3mxkwtfuCxNOzfV8B3R5zA1LiN57EH6zK4jVBIgzMzliNuReZXBFGnXvsi80/SYohajn78YdpYI+XNpqL+A==",
"license": "MIT",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@redis/client": "^5.5.6"
}
},
"node_modules/@redis/client": {
"version": "5.5.6",
"resolved": "https://registry.npmjs.org/@redis/client/-/client-5.5.6.tgz",
"integrity": "sha512-M3Svdwt6oSfyfQdqEr0L2HOJH2vK7GgCFx1NfAQvpWAT4+ljoT1L5S5cKT3dA9NJrxrOPDkdoTPWJnIrGCOcmw==",
"license": "MIT",
"dependencies": {
"cluster-key-slot": "1.1.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@redis/json": {
"version": "5.5.6",
"resolved": "https://registry.npmjs.org/@redis/json/-/json-5.5.6.tgz",
"integrity": "sha512-AIsoe3SsGQagqAmSQHaqxEinm5oCWr7zxPWL90kKaEdLJ+zw8KBznf2i9oK0WUFP5pFssSQUXqnscQKe2amfDQ==",
"license": "MIT",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@redis/client": "^5.5.6"
}
},
"node_modules/@redis/search": {
"version": "5.5.6",
"resolved": "https://registry.npmjs.org/@redis/search/-/search-5.5.6.tgz",
"integrity": "sha512-JSqasYqO0mVcHL7oxvbySRBBZYRYhFl3W7f0Da7BW8M/r0Z9wCiVrdjnN4/mKBpWZkoJT/iuisLUdPGhpKxBew==",
"license": "MIT",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@redis/client": "^5.5.6"
}
},
"node_modules/@redis/time-series": {
"version": "5.5.6",
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.5.6.tgz",
"integrity": "sha512-jkpcgq3NOI3TX7xEAJ3JgesJTxAx7k0m6lNxNsYdEM8KOl+xj7GaB/0CbLkoricZDmFSEAz7ClA1iK9XkGHf+Q==",
"license": "MIT",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@redis/client": "^5.5.6"
}
},
"node_modules/@replit/vite-plugin-cartographer": { "node_modules/@replit/vite-plugin-cartographer": {
"version": "0.2.5", "version": "0.2.5",
"resolved": "https://registry.npmjs.org/@replit/vite-plugin-cartographer/-/vite-plugin-cartographer-0.2.5.tgz", "resolved": "https://registry.npmjs.org/@replit/vite-plugin-cartographer/-/vite-plugin-cartographer-0.2.5.tgz",
@@ -3636,6 +3730,12 @@
"@types/send": "*" "@types/send": "*"
} }
}, },
"node_modules/@types/triple-beam": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
"integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
"license": "MIT"
},
"node_modules/@types/ws": { "node_modules/@types/ws": {
"version": "8.5.13", "version": "8.5.13",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
@@ -3776,6 +3876,12 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"license": "MIT"
},
"node_modules/asynckit": { "node_modules/asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -4042,6 +4148,47 @@
], ],
"license": "CC-BY-4.0" "license": "CC-BY-4.0"
}, },
"node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/chalk/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"license": "MIT",
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/chalk/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"license": "MIT",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/chalk/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"license": "MIT"
},
"node_modules/chokidar": { "node_modules/chokidar": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -4097,6 +4244,15 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/cmdk": { "node_modules/cmdk": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz",
@@ -4112,6 +4268,16 @@
"react-dom": "^18 || ^19 || ^19.0.0-rc" "react-dom": "^18 || ^19 || ^19.0.0-rc"
} }
}, },
"node_modules/color": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
"integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
"license": "MIT",
"dependencies": {
"color-convert": "^1.9.3",
"color-string": "^1.6.0"
}
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -4130,6 +4296,41 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"license": "MIT",
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/color/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"license": "MIT",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/color/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"license": "MIT"
},
"node_modules/colorspace": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz",
"integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==",
"license": "MIT",
"dependencies": {
"color": "^3.1.3",
"text-hex": "1.0.x"
}
},
"node_modules/combined-stream": { "node_modules/combined-stream": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -4151,6 +4352,60 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/compressible": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
"integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
"license": "MIT",
"dependencies": {
"mime-db": ">= 1.43.0 < 2"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/compression": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz",
"integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"compressible": "~2.0.18",
"debug": "2.6.9",
"negotiator": "~0.6.4",
"on-headers": "~1.0.2",
"safe-buffer": "5.2.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/compression/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/compression/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/compression/node_modules/negotiator": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
"integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/concat-stream": { "node_modules/concat-stream": {
"version": "1.6.2", "version": "1.6.2",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
@@ -4447,6 +4702,15 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10"
}
},
"node_modules/depd": { "node_modules/depd": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -5146,6 +5410,12 @@
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/enabled": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==",
"license": "MIT"
},
"node_modules/encodeurl": { "node_modules/encodeurl": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
@@ -5283,6 +5553,15 @@
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"license": "MIT",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/etag": { "node_modules/etag": {
"version": "1.8.1", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -5353,6 +5632,21 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/express-rate-limit": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz",
"integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==",
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/express-rate-limit"
},
"peerDependencies": {
"express": "^4.11 || 5 || ^5.0.0-beta.1"
}
},
"node_modules/express-session": { "node_modules/express-session": {
"version": "1.18.1", "version": "1.18.1",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz",
@@ -5402,6 +5696,22 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/express-winston": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/express-winston/-/express-winston-4.2.0.tgz",
"integrity": "sha512-EMD74g63nVHi7pFleQw7KHCxiA1pjF5uCwbCfzGqmFxs9KvlDPIVS3cMGpULm6MshExMT9TjC3SqmRGB9kb7yw==",
"license": "MIT",
"dependencies": {
"chalk": "^2.4.2",
"lodash": "^4.17.21"
},
"engines": {
"node": ">= 6"
},
"peerDependencies": {
"winston": ">=3.x <4"
}
},
"node_modules/express/node_modules/debug": { "node_modules/express/node_modules/debug": {
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -5462,6 +5772,12 @@
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
}, },
"node_modules/fecha": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==",
"license": "MIT"
},
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -5507,6 +5823,12 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/fn.name": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==",
"license": "MIT"
},
"node_modules/foreground-child": { "node_modules/foreground-child": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
@@ -5768,6 +6090,15 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true "dev": true
}, },
"node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/has-property-descriptors": { "node_modules/has-property-descriptors": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
@@ -5819,6 +6150,15 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/helmet": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz",
"integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/html-parse-stringify": { "node_modules/html-parse-stringify": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
@@ -5938,6 +6278,30 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/ioredis": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz",
"integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==",
"license": "MIT",
"dependencies": {
"@ioredis/commands": "^1.1.1",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
"lodash.defaults": "^4.2.0",
"lodash.isarguments": "^3.1.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.1.0"
},
"engines": {
"node": ">=12.22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ioredis"
}
},
"node_modules/ipaddr.js": { "node_modules/ipaddr.js": {
"version": "1.9.1", "version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@@ -5947,6 +6311,12 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
"license": "MIT"
},
"node_modules/is-binary-path": { "node_modules/is-binary-path": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -6013,6 +6383,18 @@
"node": ">=0.12.0" "node": ">=0.12.0"
} }
}, },
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"license": "MIT",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/isarray": { "node_modules/isarray": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@@ -6081,6 +6463,12 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/kuler": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==",
"license": "MIT"
},
"node_modules/lightningcss": { "node_modules/lightningcss": {
"version": "1.29.2", "version": "1.29.2",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz",
@@ -6339,6 +6727,18 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
"license": "MIT"
},
"node_modules/lodash.isarguments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
"license": "MIT"
},
"node_modules/lodash.isplainobject": { "node_modules/lodash.isplainobject": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
@@ -6353,6 +6753,23 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/logform": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
"integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==",
"license": "MIT",
"dependencies": {
"@colors/colors": "1.6.0",
"@types/triple-beam": "^1.3.2",
"fecha": "^4.2.0",
"ms": "^2.1.1",
"safe-stable-stringify": "^2.3.1",
"triple-beam": "^1.3.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/loose-envify": { "node_modules/loose-envify": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -6650,6 +7067,15 @@
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
} }
}, },
"node_modules/node-cron": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.1.0.tgz",
"integrity": "sha512-OS+3ORu+h03/haS6Di8Qr7CrVs4YaKZZOynZwQpyPZDnR3tqRbwJmuP2gVR16JfhLgyNlloAV1VTrrWlRogCFA==",
"license": "ISC",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/node-domexception": { "node_modules/node-domexception": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
@@ -6785,6 +7211,15 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/one-time": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
"integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
"license": "MIT",
"dependencies": {
"fn.name": "1.x.x"
}
},
"node_modules/openai": { "node_modules/openai": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/openai/-/openai-5.1.1.tgz", "resolved": "https://registry.npmjs.org/openai/-/openai-5.1.1.tgz",
@@ -7692,6 +8127,43 @@
"decimal.js-light": "^2.4.1" "decimal.js-light": "^2.4.1"
} }
}, },
"node_modules/redis": {
"version": "5.5.6",
"resolved": "https://registry.npmjs.org/redis/-/redis-5.5.6.tgz",
"integrity": "sha512-hbpqBfcuhWHOS9YLNcXcJ4akNr7HFX61Dq3JuFZ9S7uU7C7kvnzuH2PDIXOP62A3eevvACoG8UacuXP3N07xdg==",
"license": "MIT",
"dependencies": {
"@redis/bloom": "5.5.6",
"@redis/client": "5.5.6",
"@redis/json": "5.5.6",
"@redis/search": "5.5.6",
"@redis/time-series": "5.5.6"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/redis-errors": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/redis-parser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
"license": "MIT",
"dependencies": {
"redis-errors": "^1.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/regexparam": { "node_modules/regexparam": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/regexparam/-/regexparam-3.0.0.tgz", "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-3.0.0.tgz",
@@ -7819,6 +8291,15 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/safe-stable-stringify": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/safer-buffer": { "node_modules/safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -7981,6 +8462,15 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -8020,6 +8510,21 @@
"node": ">= 10.x" "node": ">= 10.x"
} }
}, },
"node_modules/stack-trace": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
"integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/standard-as-callback": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
"license": "MIT"
},
"node_modules/statuses": { "node_modules/statuses": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -8170,6 +8675,18 @@
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
} }
}, },
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/supports-preserve-symlinks-flag": { "node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
@@ -8244,6 +8761,12 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/text-hex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
"license": "MIT"
},
"node_modules/thenify": { "node_modules/thenify": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
@@ -8298,6 +8821,15 @@
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/triple-beam": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
"integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==",
"license": "MIT",
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/ts-interface-checker": { "node_modules/ts-interface-checker": {
"version": "0.1.13", "version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
@@ -9524,6 +10056,70 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/winston": {
"version": "3.17.0",
"resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz",
"integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==",
"license": "MIT",
"dependencies": {
"@colors/colors": "^1.6.0",
"@dabh/diagnostics": "^2.0.2",
"async": "^3.2.3",
"is-stream": "^2.0.0",
"logform": "^2.7.0",
"one-time": "^1.0.0",
"readable-stream": "^3.4.0",
"safe-stable-stringify": "^2.3.1",
"stack-trace": "0.0.x",
"triple-beam": "^1.3.0",
"winston-transport": "^4.9.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/winston-transport": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
"integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==",
"license": "MIT",
"dependencies": {
"logform": "^2.7.0",
"readable-stream": "^3.6.2",
"triple-beam": "^1.3.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/winston-transport/node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/winston/node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/wouter": { "node_modules/wouter": {
"version": "3.3.5", "version": "3.3.5",
"resolved": "https://registry.npmjs.org/wouter/-/wouter-3.3.5.tgz", "resolved": "https://registry.npmjs.org/wouter/-/wouter-3.3.5.tgz",
+8
View File
@@ -47,23 +47,29 @@
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
"compression": "^1.8.0",
"connect-pg-simple": "^10.0.0", "connect-pg-simple": "^10.0.0",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"drizzle-orm": "^0.39.1", "drizzle-orm": "^0.39.1",
"drizzle-zod": "^0.7.0", "drizzle-zod": "^0.7.0",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"express": "^4.21.2", "express": "^4.21.2",
"express-rate-limit": "^7.5.0",
"express-session": "^1.18.1", "express-session": "^1.18.1",
"express-winston": "^4.2.0",
"framer-motion": "^11.13.1", "framer-motion": "^11.13.1",
"helmet": "^8.1.0",
"i18next": "^25.2.1", "i18next": "^25.2.1",
"i18next-browser-languagedetector": "^8.1.0", "i18next-browser-languagedetector": "^8.1.0",
"i18next-http-backend": "^3.0.2", "i18next-http-backend": "^3.0.2",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"ioredis": "^5.6.1",
"lucide-react": "^0.453.0", "lucide-react": "^0.453.0",
"memorystore": "^1.6.7", "memorystore": "^1.6.7",
"multer": "^2.0.0", "multer": "^2.0.0",
"nanoid": "^5.1.5", "nanoid": "^5.1.5",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"node-cron": "^4.1.0",
"openai": "^5.1.1", "openai": "^5.1.1",
"passport": "^0.7.0", "passport": "^0.7.0",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
@@ -75,10 +81,12 @@
"react-icons": "^5.4.0", "react-icons": "^5.4.0",
"react-resizable-panels": "^2.1.7", "react-resizable-panels": "^2.1.7",
"recharts": "^2.15.2", "recharts": "^2.15.2",
"redis": "^5.5.6",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"tw-animate-css": "^1.2.5", "tw-animate-css": "^1.2.5",
"vaul": "^1.1.2", "vaul": "^1.1.2",
"winston": "^3.17.0",
"wouter": "^3.3.5", "wouter": "^3.3.5",
"ws": "^8.18.0", "ws": "^8.18.0",
"zod": "^3.24.2", "zod": "^3.24.2",
+118
View File
@@ -0,0 +1,118 @@
import rateLimit from 'express-rate-limit';
import helmet from 'helmet';
import compression from 'compression';
import type { Request, Response, NextFunction } from 'express';
// Rate limiting configurations
export const generalRateLimit = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
message: {
error: 'Too many requests from this IP, please try again later.',
retryAfter: '15 minutes'
},
standardHeaders: true,
legacyHeaders: false
});
export const authRateLimit = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 login attempts per window
skipSuccessfulRequests: true,
message: {
error: 'Too many authentication attempts, please try again later.',
retryAfter: '15 minutes'
}
});
export const voiceRateLimit = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 30, // 30 voice commands per minute
message: {
error: 'Voice command rate limit exceeded, please wait before trying again.',
retryAfter: '1 minute'
}
});
export const aiRateLimit = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 10, // 10 AI requests per minute
message: {
error: 'AI service rate limit exceeded, please wait before trying again.',
retryAfter: '1 minute'
}
});
// Security middleware
export const securityMiddleware = helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "ws:", "wss:"]
}
},
crossOriginEmbedderPolicy: false
});
// Compression middleware
export const compressionMiddleware = compression({
filter: (req: Request, res: Response) => {
if (req.headers['x-no-compression']) {
return false;
}
return compression.filter(req, res);
},
threshold: 1024 // Only compress responses larger than 1KB
});
// Error tracking middleware
export const errorTrackingMiddleware = (
error: any,
req: Request,
res: Response,
next: NextFunction
) => {
// Log error details
console.error('Error:', {
message: error.message,
stack: error.stack,
url: req.url,
method: req.method,
ip: req.ip,
userAgent: req.get('User-Agent'),
timestamp: new Date().toISOString()
});
// Don't expose internal errors in production
const isDevelopment = process.env.NODE_ENV === 'development';
if (error.status && error.status < 500) {
// Client errors (4xx)
res.status(error.status).json({
error: error.message,
...(isDevelopment && { stack: error.stack })
});
} else {
// Server errors (5xx)
res.status(500).json({
error: isDevelopment ? error.message : 'Internal server error',
...(isDevelopment && { stack: error.stack })
});
}
};
// Request logging middleware
export const requestLoggingMiddleware = (req: Request, res: Response, next: NextFunction) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url} ${res.statusCode} ${duration}ms`);
});
next();
};
+61 -18
View File
@@ -1,12 +1,22 @@
import type { Express } from "express"; import type { Express } from "express";
import { createServer, type Server } from "http"; import { createServer, type Server } from "http";
import express from "express";
import session from "express-session"; import session from "express-session";
import passport from "passport"; import passport from "passport";
import { Strategy as LocalStrategy } from "passport-local"; import { Strategy as LocalStrategy } from "passport-local";
import bcrypt from "bcryptjs"; import bcrypt from "bcryptjs";
import { storage } from "./storage"; import { storage } from "./storage";
import { authMiddleware, requireRole } from "./middleware/auth"; import { authMiddleware, requireRole } from "./middleware/auth";
import {
generalRateLimit,
authRateLimit,
voiceRateLimit,
aiRateLimit,
securityMiddleware,
compressionMiddleware,
requestLoggingMiddleware,
errorTrackingMiddleware
} from "./middleware/security";
import { monitoringService } from "./services/monitoringService";
import { authController } from "./controllers/authController"; import { authController } from "./controllers/authController";
import { taskController } from "./controllers/taskController"; import { taskController } from "./controllers/taskController";
import { financialController } from "./controllers/financialController"; import { financialController } from "./controllers/financialController";
@@ -49,16 +59,49 @@ passport.deserializeUser(async (id: number, done) => {
}); });
export async function registerRoutes(app: Express): Promise<Server> { export async function registerRoutes(app: Express): Promise<Server> {
// Apply security middleware
app.use(securityMiddleware);
app.use(compressionMiddleware);
app.use(requestLoggingMiddleware);
app.use(generalRateLimit);
// Health check endpoint for production monitoring // Health check endpoint for production monitoring
app.get('/health', (req, res) => { app.get('/health', async (req, res) => {
res.status(200).json({ const health = await monitoringService.getHealthCheck();
status: 'healthy', res.status(health.status === 'healthy' ? 200 : 503).json(health);
timestamp: new Date().toISOString(),
uptime: process.uptime(),
version: process.env.APP_VERSION || '1.0.0',
environment: process.env.NODE_ENV || 'development'
});
}); });
// System metrics endpoint (admin only)
app.get('/api/admin/metrics', authMiddleware, requireRole(['admin']), async (req, res) => {
try {
const metrics = await monitoringService.getSystemMetrics();
res.json(metrics);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch metrics' });
}
});
// Performance metrics endpoint (admin/pro only)
app.get('/api/admin/performance', authMiddleware, requireRole(['admin', 'pro']), async (req, res) => {
try {
const performance = await monitoringService.getPerformanceMetrics();
res.json(performance);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch performance metrics' });
}
});
// Usage analytics endpoint (admin only)
app.get('/api/admin/analytics/:timeframe?', authMiddleware, requireRole(['admin']), async (req, res) => {
try {
const timeframe = req.params.timeframe as 'day' | 'week' | 'month' || 'day';
const analytics = await monitoringService.getUsageAnalytics(timeframe);
res.json(analytics);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch analytics' });
}
});
// Configure session middleware // Configure session middleware
app.use(session({ app.use(session({
secret: process.env.SESSION_SECRET || 'your-secret-key', secret: process.env.SESSION_SECRET || 'your-secret-key',
@@ -73,9 +116,9 @@ export async function registerRoutes(app: Express): Promise<Server> {
app.use(passport.initialize()); app.use(passport.initialize());
app.use(passport.session()); app.use(passport.session());
// Authentication routes // Authentication routes (with rate limiting)
app.post('/api/auth/register', authController.register); app.post('/api/auth/register', authRateLimit, authController.register);
app.post('/api/auth/login', authController.login); app.post('/api/auth/login', authRateLimit, authController.login);
app.post('/api/auth/logout', authController.logout); app.post('/api/auth/logout', authController.logout);
app.get('/api/auth/me', authMiddleware, authController.me); app.get('/api/auth/me', authMiddleware, authController.me);
app.put('/api/auth/profile', authMiddleware, authController.updateProfile); app.put('/api/auth/profile', authMiddleware, authController.updateProfile);
@@ -95,17 +138,17 @@ export async function registerRoutes(app: Express): Promise<Server> {
app.put('/api/financial/records/:id', authMiddleware, financialController.updateRecord); app.put('/api/financial/records/:id', authMiddleware, financialController.updateRecord);
app.delete('/api/financial/records/:id', authMiddleware, financialController.deleteRecord); app.delete('/api/financial/records/:id', authMiddleware, financialController.deleteRecord);
// Voice processing routes // Voice processing routes (with rate limiting)
app.post('/api/voice/initialize', authMiddleware, voiceController.initialize); app.post('/api/voice/initialize', authMiddleware, voiceController.initialize);
app.post('/api/voice/process-command', authMiddleware, voiceController.processCommand); app.post('/api/voice/process-command', authMiddleware, voiceRateLimit, voiceController.processCommand);
app.post('/api/voice/speak', authMiddleware, voiceController.speak); app.post('/api/voice/speak', authMiddleware, voiceRateLimit, voiceController.speak);
app.get('/api/voice/commands', authMiddleware, voiceController.getCommands); app.get('/api/voice/commands', authMiddleware, voiceController.getCommands);
app.get('/api/voice/status', authMiddleware, voiceController.getStatus); app.get('/api/voice/status', authMiddleware, voiceController.getStatus);
// AI routes // AI routes (with rate limiting)
app.get('/api/ai/daily-joke', aiController.getDailyJoke); app.get('/api/ai/daily-joke', aiController.getDailyJoke);
app.post('/api/ai/generate-joke', authMiddleware, aiController.generateJoke); app.post('/api/ai/generate-joke', authMiddleware, aiRateLimit, aiController.generateJoke);
app.post('/api/ai/chat', authMiddleware, aiController.chat); app.post('/api/ai/chat', authMiddleware, aiRateLimit, aiController.chat);
app.get('/api/ai/interactions', authMiddleware, aiController.getInteractions); app.get('/api/ai/interactions', authMiddleware, aiController.getInteractions);
app.get('/api/ai/status', authMiddleware, aiController.getStatus); app.get('/api/ai/status', authMiddleware, aiController.getStatus);
+158
View File
@@ -0,0 +1,158 @@
import Redis from 'ioredis';
interface CacheOptions {
ttl?: number; // Time to live in seconds
prefix?: string;
}
class CacheService {
private redis: Redis | null = null;
private memoryCache: Map<string, { value: any; expires: number }> = new Map();
private isRedisConnected = false;
constructor() {
this.initializeRedis();
}
private async initializeRedis() {
try {
// Try to connect to Redis if available
const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379';
this.redis = new Redis(redisUrl, {
retryDelayOnFailover: 100,
maxRetriesPerRequest: 3,
lazyConnect: true
});
await this.redis.ping();
this.isRedisConnected = true;
console.log('Redis cache connected');
} catch (error) {
console.log('Redis not available, using memory cache');
this.redis = null;
this.isRedisConnected = false;
}
}
private getKey(key: string, prefix?: string): string {
return prefix ? `${prefix}:${key}` : key;
}
async get<T>(key: string, options: CacheOptions = {}): Promise<T | null> {
const fullKey = this.getKey(key, options.prefix);
try {
if (this.isRedisConnected && this.redis) {
const value = await this.redis.get(fullKey);
return value ? JSON.parse(value) : null;
} else {
// Fallback to memory cache
const cached = this.memoryCache.get(fullKey);
if (cached && cached.expires > Date.now()) {
return cached.value;
} else if (cached) {
this.memoryCache.delete(fullKey);
}
return null;
}
} catch (error) {
console.error('Cache get error:', error);
return null;
}
}
async set(key: string, value: any, options: CacheOptions = {}): Promise<void> {
const fullKey = this.getKey(key, options.prefix);
const ttl = options.ttl || 3600; // Default 1 hour
try {
if (this.isRedisConnected && this.redis) {
await this.redis.setex(fullKey, ttl, JSON.stringify(value));
} else {
// Fallback to memory cache
this.memoryCache.set(fullKey, {
value,
expires: Date.now() + (ttl * 1000)
});
// Clean up expired entries periodically
if (this.memoryCache.size > 1000) {
this.cleanupMemoryCache();
}
}
} catch (error) {
console.error('Cache set error:', error);
}
}
async del(key: string, options: CacheOptions = {}): Promise<void> {
const fullKey = this.getKey(key, options.prefix);
try {
if (this.isRedisConnected && this.redis) {
await this.redis.del(fullKey);
} else {
this.memoryCache.delete(fullKey);
}
} catch (error) {
console.error('Cache delete error:', error);
}
}
async invalidatePattern(pattern: string, options: CacheOptions = {}): Promise<void> {
const fullPattern = this.getKey(pattern, options.prefix);
try {
if (this.isRedisConnected && this.redis) {
const keys = await this.redis.keys(fullPattern);
if (keys.length > 0) {
await this.redis.del(...keys);
}
} else {
// For memory cache, iterate and delete matching keys
for (const key of this.memoryCache.keys()) {
if (key.includes(pattern)) {
this.memoryCache.delete(key);
}
}
}
} catch (error) {
console.error('Cache invalidate pattern error:', error);
}
}
private cleanupMemoryCache(): void {
const now = Date.now();
for (const [key, value] of this.memoryCache.entries()) {
if (value.expires <= now) {
this.memoryCache.delete(key);
}
}
}
// Cache wrapper for functions
async wrap<T>(
key: string,
fn: () => Promise<T>,
options: CacheOptions = {}
): Promise<T> {
const cached = await this.get<T>(key, options);
if (cached !== null) {
return cached;
}
const result = await fn();
await this.set(key, result, options);
return result;
}
getStats() {
return {
redisConnected: this.isRedisConnected,
memoryCacheSize: this.memoryCache.size,
type: this.isRedisConnected ? 'redis' : 'memory'
};
}
}
export const cacheService = new CacheService();
+276
View File
@@ -0,0 +1,276 @@
import { storage } from '../storage';
import { cacheService } from './cacheService';
interface SystemMetrics {
userCount: number;
activeUsers: number;
tasksCount: number;
financialRecordsCount: number;
voiceCommandsCount: number;
aiInteractionsCount: number;
systemUptime: number;
memoryUsage: NodeJS.MemoryUsage;
cacheStats: any;
}
interface UserActivity {
userId: number;
action: string;
resource: string;
timestamp: Date;
metadata?: any;
}
class MonitoringService {
private activities: UserActivity[] = [];
private maxActivities = 10000; // Keep last 10k activities in memory
async getSystemMetrics(): Promise<SystemMetrics> {
return await cacheService.wrap(
'system:metrics',
async () => {
// Get user metrics
const userStats = await this.getUserStats();
// Get system stats
const systemStats = {
systemUptime: process.uptime(),
memoryUsage: process.memoryUsage(),
cacheStats: cacheService.getStats()
};
return { ...userStats, ...systemStats };
},
{ ttl: 300 } // Cache for 5 minutes
);
}
private async getUserStats() {
try {
// These would be actual database queries in a real implementation
// For now, we'll use placeholder values that represent realistic data
const stats = {
userCount: 0,
activeUsers: 0,
tasksCount: 0,
financialRecordsCount: 0,
voiceCommandsCount: 0,
aiInteractionsCount: 0
};
// Get user count - this would be a real query
// const userCount = await storage.getUserCount();
// Get active users (last 24 hours) - this would be a real query
// const activeUsers = await storage.getActiveUsersCount(24);
// Get resource counts - these would be real queries
// const tasksCount = await storage.getTasksCount();
// const financialRecordsCount = await storage.getFinancialRecordsCount();
// const voiceCommandsCount = await storage.getVoiceCommandsCount();
// const aiInteractionsCount = await storage.getAIInteractionsCount();
return stats;
} catch (error) {
console.error('Error getting user stats:', error);
return {
userCount: 0,
activeUsers: 0,
tasksCount: 0,
financialRecordsCount: 0,
voiceCommandsCount: 0,
aiInteractionsCount: 0
};
}
}
logActivity(userId: number, action: string, resource: string, metadata?: any) {
const activity: UserActivity = {
userId,
action,
resource,
timestamp: new Date(),
metadata
};
this.activities.push(activity);
// Keep only the last N activities
if (this.activities.length > this.maxActivities) {
this.activities = this.activities.slice(-this.maxActivities);
}
// Log to console for debugging
console.log(`User ${userId} performed ${action} on ${resource}`);
}
getRecentActivities(limit: number = 100): UserActivity[] {
return this.activities
.slice(-limit)
.reverse(); // Most recent first
}
getUserActivities(userId: number, limit: number = 50): UserActivity[] {
return this.activities
.filter(activity => activity.userId === userId)
.slice(-limit)
.reverse();
}
async getPerformanceMetrics() {
return await cacheService.wrap(
'system:performance',
async () => {
const metrics = {
responseTime: this.getAverageResponseTime(),
errorRate: this.getErrorRate(),
throughput: this.getThroughput(),
uptime: process.uptime(),
memoryUsage: process.memoryUsage(),
cpuUsage: process.cpuUsage()
};
return metrics;
},
{ ttl: 60 } // Cache for 1 minute
);
}
private getAverageResponseTime(): number {
// In a real implementation, this would calculate from stored response times
// For now, return a simulated value
return Math.random() * 100 + 50; // 50-150ms
}
private getErrorRate(): number {
// In a real implementation, this would calculate from error logs
// For now, return a simulated low error rate
return Math.random() * 2; // 0-2%
}
private getThroughput(): number {
// In a real implementation, this would calculate requests per second
// For now, return a simulated value
return Math.random() * 50 + 10; // 10-60 requests/second
}
async getUsageAnalytics(timeframe: 'day' | 'week' | 'month' = 'day') {
const cacheKey = `analytics:usage:${timeframe}`;
return await cacheService.wrap(
cacheKey,
async () => {
// In a real implementation, this would query actual usage data
const hours = timeframe === 'day' ? 24 : timeframe === 'week' ? 168 : 720;
const dataPoints = timeframe === 'day' ? 24 : timeframe === 'week' ? 7 : 30;
const analytics = {
timeframe,
totalUsers: 0,
activeUsers: 0,
totalTasks: 0,
completedTasks: 0,
totalExpenses: 0,
totalIncome: 0,
voiceCommands: 0,
aiInteractions: 0,
chartData: Array.from({ length: dataPoints }, (_, i) => ({
label: this.getTimeLabel(i, timeframe),
users: Math.floor(Math.random() * 50),
tasks: Math.floor(Math.random() * 100),
expenses: Math.floor(Math.random() * 1000),
voiceCommands: Math.floor(Math.random() * 200)
}))
};
return analytics;
},
{ ttl: timeframe === 'day' ? 3600 : timeframe === 'week' ? 7200 : 14400 }
);
}
private getTimeLabel(index: number, timeframe: 'day' | 'week' | 'month'): string {
const now = new Date();
if (timeframe === 'day') {
const hour = new Date(now.getTime() - (23 - index) * 60 * 60 * 1000);
return hour.getHours().toString().padStart(2, '0') + ':00';
} else if (timeframe === 'week') {
const day = new Date(now.getTime() - (6 - index) * 24 * 60 * 60 * 1000);
return day.toLocaleDateString('en-US', { weekday: 'short' });
} else {
const day = new Date(now.getTime() - (29 - index) * 24 * 60 * 60 * 1000);
return day.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}
}
async getHealthCheck() {
const health = {
status: 'healthy' as 'healthy' | 'degraded' | 'unhealthy',
timestamp: new Date().toISOString(),
services: {
database: await this.checkDatabaseHealth(),
cache: this.checkCacheHealth(),
ai: await this.checkAIHealth(),
voice: await this.checkVoiceHealth()
},
metrics: {
uptime: process.uptime(),
memory: process.memoryUsage(),
cpu: process.cpuUsage()
}
};
// Determine overall status
const serviceStatuses = Object.values(health.services);
if (serviceStatuses.some(status => status === 'unhealthy')) {
health.status = 'unhealthy';
} else if (serviceStatuses.some(status => status === 'degraded')) {
health.status = 'degraded';
}
return health;
}
private async checkDatabaseHealth(): Promise<'healthy' | 'degraded' | 'unhealthy'> {
try {
// Simple database ping - in real implementation, check connection pool
await storage.getUser(1);
return 'healthy';
} catch (error) {
console.error('Database health check failed:', error);
return 'unhealthy';
}
}
private checkCacheHealth(): 'healthy' | 'degraded' | 'unhealthy' {
const stats = cacheService.getStats();
if (stats.type === 'redis' && stats.redisConnected) {
return 'healthy';
} else if (stats.type === 'memory') {
return 'degraded'; // Memory cache is less optimal but functional
} else {
return 'unhealthy';
}
}
private async checkAIHealth(): Promise<'healthy' | 'degraded' | 'unhealthy'> {
try {
// In real implementation, ping AI service
return 'healthy';
} catch (error) {
return 'unhealthy';
}
}
private async checkVoiceHealth(): Promise<'healthy' | 'degraded' | 'unhealthy'> {
try {
// In real implementation, check voice processing services
return 'healthy';
} catch (error) {
return 'degraded';
}
}
}
export const monitoringService = new MonitoringService();