This repository has no description
2.5 kB
88 lines
1import Redis from 'ioredis';
2import {
3 IProfileService,
4 UserProfile,
5} from 'src/modules/cards/domain/services/IProfileService';
6import { Result, ok } from 'src/shared/core/Result';
7
8export class CachedBlueskyProfileService implements IProfileService {
9 private readonly CACHE_TTL_SECONDS = 3600 * 12; // 12 hours
10 private readonly CACHE_KEY_PREFIX = 'profile:';
11
12 constructor(
13 private readonly profileService: IProfileService,
14 private readonly redis: Redis,
15 ) {}
16
17 async getProfile(
18 userId: string,
19 callerId?: string,
20 ): Promise<Result<UserProfile>> {
21 const cacheKey = this.getCacheKey(userId);
22
23 try {
24 // Try cache first
25 const cached = await this.redis.get(cacheKey);
26 if (cached) {
27 try {
28 const profile = JSON.parse(cached) as UserProfile;
29 return ok(profile);
30 } catch (parseError) {
31 // If JSON parsing fails, continue to fetch fresh data
32 console.warn(
33 `Failed to parse cached profile for ${userId}:`,
34 parseError,
35 );
36 }
37 }
38
39 // Cache miss or parse error - fetch from underlying service
40 const result = await this.profileService.getProfile(userId, callerId);
41
42 if (result.isOk()) {
43 // Cache the successful result
44 try {
45 await this.redis.setex(
46 cacheKey,
47 this.CACHE_TTL_SECONDS,
48 JSON.stringify(result.value),
49 );
50 } catch (cacheError) {
51 // Log cache error but don't fail the request
52 console.warn(`Failed to cache profile for ${userId}:`, cacheError);
53 }
54 }
55
56 return result;
57 } catch (redisError) {
58 // If Redis is down, fall back to direct service call
59 console.warn(
60 `Redis error when fetching profile for ${userId}:`,
61 redisError,
62 );
63 return this.profileService.getProfile(userId, callerId);
64 }
65 }
66
67 private getCacheKey(userId: string): string {
68 return `${this.CACHE_KEY_PREFIX}${userId}`;
69 }
70
71 /**
72 * Invalidate cached profile for a specific user
73 */
74 async invalidateProfile(userId: string): Promise<void> {
75 try {
76 await this.redis.del(this.getCacheKey(userId));
77 } catch (error) {
78 console.warn(`Failed to invalidate profile cache for ${userId}:`, error);
79 }
80 }
81
82 /**
83 * Warm the cache by pre-fetching a profile
84 */
85 async warmCache(userId: string, callerId?: string): Promise<void> {
86 await this.getProfile(userId, callerId);
87 }
88}