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:
@@ -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
@@ -1,12 +1,22 @@
|
||||
import type { Express } from "express";
|
||||
import { createServer, type Server } from "http";
|
||||
import express from "express";
|
||||
import session from "express-session";
|
||||
import passport from "passport";
|
||||
import { Strategy as LocalStrategy } from "passport-local";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { storage } from "./storage";
|
||||
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 { taskController } from "./controllers/taskController";
|
||||
import { financialController } from "./controllers/financialController";
|
||||
@@ -49,16 +59,49 @@ passport.deserializeUser(async (id: number, done) => {
|
||||
});
|
||||
|
||||
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
|
||||
app.get('/health', (req, res) => {
|
||||
res.status(200).json({
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: process.uptime(),
|
||||
version: process.env.APP_VERSION || '1.0.0',
|
||||
environment: process.env.NODE_ENV || 'development'
|
||||
});
|
||||
app.get('/health', async (req, res) => {
|
||||
const health = await monitoringService.getHealthCheck();
|
||||
res.status(health.status === 'healthy' ? 200 : 503).json(health);
|
||||
});
|
||||
|
||||
// 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
|
||||
app.use(session({
|
||||
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.session());
|
||||
|
||||
// Authentication routes
|
||||
app.post('/api/auth/register', authController.register);
|
||||
app.post('/api/auth/login', authController.login);
|
||||
// Authentication routes (with rate limiting)
|
||||
app.post('/api/auth/register', authRateLimit, authController.register);
|
||||
app.post('/api/auth/login', authRateLimit, authController.login);
|
||||
app.post('/api/auth/logout', authController.logout);
|
||||
app.get('/api/auth/me', authMiddleware, authController.me);
|
||||
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.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/process-command', authMiddleware, voiceController.processCommand);
|
||||
app.post('/api/voice/speak', authMiddleware, voiceController.speak);
|
||||
app.post('/api/voice/process-command', authMiddleware, voiceRateLimit, voiceController.processCommand);
|
||||
app.post('/api/voice/speak', authMiddleware, voiceRateLimit, voiceController.speak);
|
||||
app.get('/api/voice/commands', authMiddleware, voiceController.getCommands);
|
||||
app.get('/api/voice/status', authMiddleware, voiceController.getStatus);
|
||||
|
||||
// AI routes
|
||||
// AI routes (with rate limiting)
|
||||
app.get('/api/ai/daily-joke', aiController.getDailyJoke);
|
||||
app.post('/api/ai/generate-joke', authMiddleware, aiController.generateJoke);
|
||||
app.post('/api/ai/chat', authMiddleware, aiController.chat);
|
||||
app.post('/api/ai/generate-joke', authMiddleware, aiRateLimit, aiController.generateJoke);
|
||||
app.post('/api/ai/chat', authMiddleware, aiRateLimit, aiController.chat);
|
||||
app.get('/api/ai/interactions', authMiddleware, aiController.getInteractions);
|
||||
app.get('/api/ai/status', authMiddleware, aiController.getStatus);
|
||||
|
||||
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user