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,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();
|
||||
Reference in New Issue
Block a user