Tribute organizer! Consul Caesar.js decided to modernize the storage system in the application. It's time for Redis - the most advanced tribute warehouse that allows for lightning-fast storage and retrieval of data across the entire legion!
Imagine a magical warehouse that:
Redis is like a network of warehouses spread across all provinces, accessible to the entire legion!
1# Installing Redis (Docker)
2docker run --name redis-legionaries -p 6379:6379 -d redis:latest redis-server --appendonly yes
3
4# Installing NestJS modules
5npm install redis @nestjs/cache-manager cache-manager-redis-store1// redis.config.ts
2import { ConfigService } from '@nestjs/config';
3import { redisStore } from 'cache-manager-redis-store';
4
5export const redisConfigFactory = async (configService: ConfigService) => {
6 return {
7 store: redisStore,
8 host: configService.get('REDIS_HOST', 'localhost'),
9 port: configService.get('REDIS_PORT', 6379),
10 password: configService.get('REDIS_PASSWORD'),
11 db: configService.get('REDIS_DB', 0),
12 ttl: configService.get('CACHE_TTL', 300),
13 max: configService.get('CACHE_MAX_ITEMS', 1000),
14
15 // Additional Redis options
16 retryDelayOnFailover: 100,
17 enableReadyCheck: true,
18 maxRetriesPerRequest: null,
19 lazyConnect: true,
20
21 // Connection pooling settings
22 family: 4,
23 keepAlive: true,
24 connectTimeout: 10000,
25 commandTimeout: 5000,
26 };
27};
28
29// redis.module.ts
30import { CacheModule } from '@nestjs/cache-manager';
31import { Module, Global } from '@nestjs/common';
32
33@Global()
34@Module({
35 imports: [
36 CacheModule.registerAsync({
37 imports: [ConfigModule],
38 useFactory: redisConfigFactory,
39 inject: [ConfigService],
40 }),
41 ],
42 exports: [CacheModule],
43})
44export class RedisModule {}1// redis-session.service.ts
2import { Injectable, Inject } from '@nestjs/common';
3import { CACHE_MANAGER } from '@nestjs/cache-manager';
4import { Cache } from 'cache-manager';
5
6interface LegionarySession {
7 legionariesId: number;
8 userName: string;
9 currentLegion: string;
10 loginTime: Date;
11 lastActivity: Date;
12 permissions: string[];
13 tributeAccess: number[];
14}
15
16@Injectable()
17export class RedisSessionService {
18 constructor(
19 @Inject(CACHE_MANAGER) private cacheManager: Cache,
20 ) {}
21
22 async createSession(sessionId: string, sessionData: LegionarySession): Promise<void> {
23 const sessionKey = `session:${sessionId}`;
24
25 // Session expires after 24 hours
26 await this.cacheManager.set(sessionKey, sessionData, 86400);
27
28 // Add to the index of active sessions
29 await this.addToActiveSessions(sessionData.legionariesId, sessionId);
30
31 console.log(` Session created for legionary: ${sessionData.userName}`);
32 }
33
34 async getSession(sessionId: string): Promise<LegionarySession | null> {
35 const sessionKey = `session:${sessionId}`;
36 const session = await this.cacheManager.get<LegionarySession>(sessionKey);
37
38 if (session) {
39 // Update last activity
40 session.lastActivity = new Date();
41 await this.cacheManager.set(sessionKey, session, 86400);
42 }
43
44 return session;
45 }
46
47 async updateSession(sessionId: string, updates: Partial<LegionarySession>): Promise<void> {
48 const session = await this.getSession(sessionId);
49 if (!session) return;
50
51 const updatedSession = { ...session, ...updates };
52 const sessionKey = `session:${sessionId}`;
53
54 await this.cacheManager.set(sessionKey, updatedSession, 86400);
55 }
56
57 async destroySession(sessionId: string): Promise<void> {
58 const session = await this.getSession(sessionId);
59 const sessionKey = `session:${sessionId}`;
60
61 if (session) {
62 await this.removeFromActiveSessions(session.legionariesId, sessionId);
63 }
64
65 await this.cacheManager.del(sessionKey);
66 console.log(`Session ended: ${sessionId}`);
67 }
68
69 async getActiveSessions(legionariesId: number): Promise<string[]> {
70 const key = `active_sessions:${legionariesId}`;
71 const sessions = await this.cacheManager.get<string[]>(key);
72 return sessions || [];
73 }
74
75 private async addToActiveSessions(legionariesId: number, sessionId: string): Promise<void> {
76 const key = `active_sessions:${legionariesId}`;
77 const sessions = await this.getActiveSessions(legionariesId);
78
79 if (!sessions.includes(sessionId)) {
80 sessions.push(sessionId);
81 await this.cacheManager.set(key, sessions, 86400);
82 }
83 }
84
85 private async removeFromActiveSessions(legionariesId: number, sessionId: string): Promise<void> {
86 const key = `active_sessions:${legionariesId}`;
87 const sessions = await this.getActiveSessions(legionariesId);
88
89 const filteredSessions = sessions.filter(s => s !== sessionId);
90 if (filteredSessions.length > 0) {
91 await this.cacheManager.set(key, filteredSessions, 86400);
92 } else {
93 await this.cacheManager.del(key);
94 }
95 }
96
97 // Cleaning up inactive sessions
98 @Cron('0 */15 * * * *') // Every 15 minutes
99 async cleanupInactiveSessions(): Promise<void> {
100 // This function would require direct access to Redis
101 // to iterate over all session:* keys
102 console.log('🧹 Cleaning up inactive sessions...');
103 }
104}1// redis-pubsub.service.ts
2import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
3import { ConfigService } from '@nestjs/config';
4import Redis from 'ioredis';
5
6@Injectable()
7export class RedisPubSubService implements OnModuleInit, OnModuleDestroy {
8 private publisher: Redis;
9 private subscriber: Redis;
10 private eventHandlers = new Map<string, Function[]>();
11
12 constructor(private configService: ConfigService) {}
13
14 async onModuleInit() {
15 const redisConfig = {
16 host: this.configService.get('REDIS_HOST', 'localhost'),
17 port: this.configService.get('REDIS_PORT', 6379),
18 password: this.configService.get('REDIS_PASSWORD'),
19 retryDelayOnFailover: 100,
20 enableReadyCheck: true,
21 };
22
23 this.publisher = new Redis(redisConfig);
24 this.subscriber = new Redis(redisConfig);
25
26 this.subscriber.on('message', (channel, message) => {
27 this.handleMessage(channel, message);
28 });
29
30 console.log('Redis Pub/Sub connected!');
31 }
32
33 async onModuleDestroy() {
34 await this.publisher.quit();
35 await this.subscriber.quit();
36 }
37
38 // Sending a message to the entire legion
39 async publishLegionMessage(event: string, data: any): Promise<void> {
40 const channel = `legion::${event}`;
41 const message = JSON.stringify({
42 timestamp: new Date().toISOString(),
43 event,
44 data,
45 source: 'legion-command',
46 });
47
48 await this.publisher.publish(channel, message);
49 console.log(`Message sent to the legion: ${event}`);
50 }
51
52 // Sending a message to a specific cohort
53 async publishLegionMessage(cohortId: string, event: string, data: any): Promise<void> {
54 const channel = `cohort:${cohortId}:${event}`;
55 const message = JSON.stringify({
56 timestamp: new Date().toISOString(),
57 event,
58 data,
59 targetLegion: cohortId,
60 });
61
62 await this.publisher.publish(channel, message);
63 console.log(`Message sent to cohort ${cohortId}: ${event}`);
64 }
65
66 // Listening for specific events
67 async subscribe(pattern: string, handler: (event: string, data: any) => void): Promise<void> {
68 await this.subscriber.psubscribe(pattern);
69
70 if (!this.eventHandlers.has(pattern)) {
71 this.eventHandlers.set(pattern, []);
72 }
73 this.eventHandlers.get(pattern)!.push(handler);
74 }
75
76 private handleMessage(channel: string, message: string): void {
77 try {
78 const parsedMessage = JSON.parse(message);
79
80 // Find all matching handlers
81 this.eventHandlers.forEach((handlers, pattern) => {
82 if (this.matchPattern(channel, pattern)) {
83 handlers.forEach(handler => {
84 try {
85 handler(parsedMessage.event, parsedMessage.data);
86 } catch (error) {
87 console.error(`Error in handler for ${channel}:`, error);
88 }
89 });
90 }
91 });
92 } catch (error) {
93 console.error('Error parsing message:', error);
94 }
95 }
96
97 private matchPattern(channel: string, pattern: string): boolean {
98 // Simple pattern matching implementation
99 const regexPattern = pattern.replace('*', '.*').replace('?', '.');
100 const regex = new RegExp(`^${regexPattern}$`);
101 return regex.test(channel);
102 }
103}
104
105// Usage in a service
106@Injectable()
107export class LegionCommunicationService implements OnModuleInit {
108 constructor(private pubSub: RedisPubSubService) {}
109
110 async onModuleInit() {
111 // Listen for legion events
112 await this.pubSub.subscribe('legion::*', (event, data) => {
113 this.handleLegionEvent(event, data);
114 });
115
116 // Listen for events of a specific cohort
117 const myLegionId = process.env.COHORT_ID || 'praetoria';
118 await this.pubSub.subscribe(`cohort:${myLegionId}:*`, (event, data) => {
119 this.handleLegionEvent(event, data);
120 });
121 }
122
123 async announceTributeFound(tributeData: any): Promise<void> {
124 await this.pubSub.publishLegionMessage('tribute_found', {
125 tribute: tributeData,
126 location: tributeData.coordinates,
127 finder: tributeData.discoveredBy,
128 timestamp: new Date(),
129 });
130 }
131
132 async requestBackup(cohortId: string, coordinates: any): Promise<void> {
133 await this.pubSub.publishLegionMessage('backup_request', {
134 requestingLegion: cohortId,
135 location: coordinates,
136 urgency: 'high',
137 reason: 'Enemy cohorts detected',
138 });
139 }
140
141 async sendDirectOrder(targetLegionId: string, order: any): Promise<void> {
142 await this.pubSub.publishLegionMessage(targetLegionId, 'direct_order', {
143 order: order.type,
144 details: order.details,
145 deadline: order.deadline,
146 priority: order.priority,
147 });
148 }
149
150 private handleLegionEvent(event: string, data: any): void {
151 switch (event) {
152 case 'tribute_found':
153 console.log(` New tribute found: ${data.tribute.name}`);
154 this.notifyLegionAboutTribute(data);
155 break;
156
157 case 'backup_request':
158 console.log(`Backup request from ${data.requestingLegion}`);
159 this.evaluateBackupRequest(data);
160 break;
161
162 case 'weather_alert':
163 console.log(` Weather alert: ${data.severity}`);
164 this.handleWeatherAlert(data);
165 break;
166 }
167 }
168
169 private handleLegionEvent(event: string, data: any): void {
170 switch (event) {
171 case 'direct_order':
172 console.log(` New order: ${data.order}`);
173 this.processDirectOrder(data);
174 break;
175
176 case 'supply_request':
177 console.log(` Supply request: ${data.supplies}`);
178 this.handleSupplyRequest(data);
179 break;
180 }
181 }
182}1// redis-queue.service.ts
2@Injectable()
3export class RedisQueueService {
4 constructor(
5 @Inject(CACHE_MANAGER) private cacheManager: Cache,
6 ) {}
7
8 // Add a task to the queue
9 async enqueueTask(queueName: string, task: any): Promise<void> {
10 const queueKey = `queue:${queueName}`;
11 const taskData = {
12 id: `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
13 ...task,
14 enqueuedAt: new Date().toISOString(),
15 };
16
17 // Using Redis LPUSH (add to the beginning of the list)
18 await this.pushToQueue(queueKey, taskData);
19 console.log(` Task added to queue ${queueName}: ${taskData.id}`);
20 }
21
22 // Get a task from the queue (FIFO)
23 async dequeueTask(queueName: string): Promise<any | null> {
24 const queueKey = `queue:${queueName}`;
25
26 // RPOP - remove from the end of the list (FIFO)
27 const taskData = await this.popFromQueue(queueKey);
28
29 if (taskData) {
30 console.log(`✅ Task retrieved from queue ${queueName}: ${taskData.id}`);
31 }
32
33 return taskData;
34 }
35
36 // Check queue length
37 async getQueueLength(queueName: string): Promise<number> {
38 const queueKey = `queue:${queueName}`;
39 // Here we would need a direct Redis LLEN command
40 return 0; // placeholder
41
42 // Get all tasks without removing them
43 async peekQueue(queueName: string, limit: number = 10): Promise<any[]> {
44 const queueKey = `queue:${queueName}`;
45 // Here we would need a direct Redis LRANGE command
46 return []; // placeholder
47
48 // Helper implementations (require direct Redis access)
49 private async pushToQueue(queueKey: string, data: any): Promise<void> {
50 // In reality we would use: redis.lpush(queueKey, JSON.stringify(data))
51 const currentQueue = await this.cacheManager.get<any[]>(queueKey) || [];
52 currentQueue.unshift(data);
53 await this.cacheManager.set(queueKey, currentQueue);
54 }
55
56 private async popFromQueue(queueKey: string): Promise<any | null> {
57 // In reality we would use: redis.rpop(queueKey)
58 const currentQueue = await this.cacheManager.get<any[]>(queueKey) || [];
59 const task = currentQueue.pop();
60 await this.cacheManager.set(queueKey, currentQueue);
61 return task || null;
62 }
63}
64
65// Specialized queues
66@Injectable()
67export class TributeQueueService extends RedisQueueService {
68 async addTributeHuntTask(huntData: any): Promise<void> {
69 await this.enqueueTask('tribute_hunts', {
70 type: 'tribute_hunt',
71 priority: huntData.resourceValue > 10000 ? 'high' : 'normal',
72 ...huntData,
73 });
74 }
75
76 async addMapAnalysisTask(mapData: any): Promise<void> {
77 await this.enqueueTask('map_analysis', {
78 type: 'map_analysis',
79 priority: 'normal',
80 ...mapData,
81 });
82 }
83
84 async getNextTributeTask(): Promise<any> {
85 return await this.dequeueTask('tribute_hunts');
86 }
87
88 async getNextMapTask(): Promise<any> {
89 return await this.dequeueTask('map_analysis');
90 }
91}1// redis-sets.service.ts
2@Injectable()
3export class RedisSetsService {
4 constructor(
5 @Inject(CACHE_MANAGER) private cacheManager: Cache,
6 ) {}
7
8 // Managing the active cohort of a cohort
9 async addMemberToLegion(cohortId: string, legionariesId: string): Promise<void> {
10 const setKey = `cohort:${cohortId}:legion`;
11 const currentLegion = await this.cacheManager.get<Set<string>>(setKey) || new Set();
12
13 if (currentLegion instanceof Set) {
14 currentLegion.add(legionariesId);
15 } else {
16 // If data is not a Set, convert it
17 const newSet = new Set(Array.isArray(currentLegion) ? currentLegion : []);
18 newSet.add(legionariesId);
19 await this.cacheManager.set(setKey, newSet);
20 return;
21 }
22
23 await this.cacheManager.set(setKey, currentLegion);
24 console.log(`Legionary ${legionariesId} added to the cohort ${cohortId}`);
25 }
26
27 async removeMemberFromLegion(cohortId: string, legionariesId: string): Promise<void> {
28 const setKey = `cohort:${cohortId}:legion`;
29 const currentLegion = await this.cacheManager.get<Set<string>>(setKey) || new Set();
30
31 if (currentLegion instanceof Set) {
32 currentLegion.delete(legionariesId);
33 await this.cacheManager.set(setKey, currentLegion);
34 }
35
36 console.log(`Legionary ${legionariesId} removed from the cohort ${cohortId}`);
37 }
38
39 async getLegions(cohortId: string): Promise<string[]> {
40 const setKey = `cohort:${cohortId}:legion`;
41 const legion = await this.cacheManager.get<Set<string>>(setKey) || new Set();
42 return Array.from(legion);
43 }
44
45 async isLegionMember(cohortId: string, legionariesId: string): Promise<boolean> {
46 const legion = await this.getLegions(cohortId);
47 return legion.includes(legionariesId);
48 }
49
50 // Managing visited islands
51 async markIslandVisited(legionId: string, islandId: string): Promise<void> {
52 const setKey = `legion:${legionId}:visited_islands`;
53 const visitedIslands = await this.cacheManager.get<Set<string>>(setKey) || new Set();
54
55 if (visitedIslands instanceof Set) {
56 visitedIslands.add(islandId);
57 } else {
58 const newSet = new Set(Array.isArray(visitedIslands) ? visitedIslands : []);
59 newSet.add(islandId);
60 await this.cacheManager.set(setKey, newSet);
61 return;
62 }
63
64 await this.cacheManager.set(setKey, visitedIslands);
65 console.log(`Island ${islandId} marked as visited by legion ${legionId}`);
66 }
67
68 async getVisitedIslands(legionId: string): Promise<string[]> {
69 const setKey = `legion:${legionId}:visited_islands`;
70 const islands = await this.cacheManager.get<Set<string>>(setKey) || new Set();
71 return Array.from(islands);
72 }
73
74 async hasVisitedIsland(legionId: string, islandId: string): Promise<boolean> {
75 const visitedIslands = await this.getVisitedIslands(legionId);
76 return visitedIslands.includes(islandId);
77 }
78
79 // Set operations - intersections, differences
80 async getCommonLegionMembers(cohortId1: string, cohortId2: string): Promise<string[]> {
81 const legion1 = new Set(await this.getLegions(cohortId1));
82 const legion2 = new Set(await this.getLegions(cohortId2));
83
84 const intersection = new Set([...legion1].filter(x => legion2.has(x)));
85 return Array.from(intersection);
86 }
87
88 async getLegionDifference(cohortId1: string, cohortId2: string): Promise<string[]> {
89 const legion1 = new Set(await this.getLegions(cohortId1));
90 const legion2 = new Set(await this.getLegions(cohortId2));
91
92 const difference = new Set([...legion1].filter(x => !legion2.has(x)));
93 return Array.from(difference);
94 }
95}1// redis-health.service.ts
2@Injectable()
3export class RedisHealthService {
4 constructor(
5 @Inject(CACHE_MANAGER) private cacheManager: Cache,
6 ) {}
7
8 async checkRedisHealth(): Promise<{
9 status: 'healthy' | 'unhealthy';
10 latency: number;
11 memoryUsage?: string;
12 connectedClients?: number;
13 error?: string;
14 }> {
15 const startTime = Date.now();
16
17 try {
18 // Test basic connectivity
19 const testKey = 'health_check_' + Date.now();
20 const testValue = 'ping';
21
22 await this.cacheManager.set(testKey, testValue, 60);
23 const retrieved = await this.cacheManager.get(testKey);
24 await this.cacheManager.del(testKey);
25
26 if (retrieved !== testValue) {
27 throw new Error('Redis read/write test failed');
28 }
29
30 const latency = Date.now() - startTime;
31
32 return {
33 status: 'healthy',
34 latency,
35 // In a real implementation we would add more metrics
36 };
37
38 } catch (error) {
39 return {
40 status: 'unhealthy',
41 latency: Date.now() - startTime,
42 error: error.message,
43 };
44 }
45 }
46
47 @Cron('*/30 * * * * *') // Every 30 seconds
48 async monitorRedisHealth(): Promise<void> {
49 const health = await this.checkRedisHealth();
50
51 if (health.status === 'unhealthy') {
52 console.error(' Redis is not working!', health.error);
53 // Here you can add alerts, e.g., webhook to Slack
54 } else if (health.latency > 1000) {
55 console.warn(` Redis is running slowly: ${health.latency}ms`);
56 }
57 }
58
59 async getRedisStats(): Promise<any> {
60 // In a real implementation we would use the Redis INFO command
61 return {
62 uptime: '24 hours',
63 memoryUsage: '256MB',
64 connectedClients: 42,
65 totalConnections: 1337,
66 keyspaceHits: 98765,
67 keyspaceMisses: 1234,
68 };
69 }
70}1// redis-advanced.config.ts
2export const redisAdvancedConfig = {
3 // Connection settings
4 host: process.env.REDIS_HOST || 'localhost',
5 port: parseInt(process.env.REDIS_PORT) || 6379,
6 password: process.env.REDIS_PASSWORD,
7 db: parseInt(process.env.REDIS_DB) || 0,
8
9 // Performance settings
10 maxRetriesPerRequest: 3,
11 retryDelayOnFailover: 100,
12 enableReadyCheck: true,
13 lazyConnect: true,
14
15 // Memory management
16 maxmemory: '256mb',
17 'maxmemory-policy': 'allkeys-lru',
18
19 // Persistence
20 save: '900 1 300 10 60 10000', // RDB snapshots
21 appendonly: 'yes', // AOF persistence
22 'appendfsync': 'everysec',
23
24 // Networking
25 'tcp-keepalive': 300,
26 timeout: 300,
27
28 // Security
29 requirepass: process.env.REDIS_PASSWORD,
30 'protected-mode': 'yes',
31
32 // Logging
33 loglevel: 'notice',
34 syslog: 'yes',
35};Redis is a powerful tool that will transform your application into a modern legion! With Redis you can build a scalable, high-performance application that handles thousands of concurrent legionaries.