Spaces:
Runtime error
Runtime error
| // Story 7.3: Redis caching service for featured creators | |
| // Implements stale-while-revalidate pattern from Story 7.2 | |
| import { redis } from '@/lib/redis'; | |
| import type { FeaturedCreator, CachedFeaturedCreators } from '../types/discovery.types'; | |
| // Dual-key caching strategy (same pattern as trending) | |
| const CACHE_TTL_PRIMARY = 7200; // 2 hours in seconds | |
| const CACHE_TTL_STALE = 14400; // 4 hours in seconds | |
| const CACHE_KEY = 'featured:creators:v1'; | |
| const CACHE_KEY_STALE_SUFFIX = ':stale'; | |
| /** | |
| * Get cached featured creators with stale-while-revalidate pattern | |
| * | |
| * Flow: | |
| * 1. Check primary cache first (2-hour TTL) | |
| * 2. If miss, check stale cache (4-hour TTL) | |
| * 3. If stale hit, return stale data (caller should trigger background refresh) | |
| * 4. If both miss, return null | |
| * | |
| * Returns: { data, isStale } where isStale indicates if serving from stale cache | |
| */ | |
| export async function getCachedFeaturedCreators(): Promise<{ data: FeaturedCreator[]; isStale: boolean } | null> { | |
| try { | |
| // Try primary cache first | |
| const primaryCached = await redis.get<CachedFeaturedCreators>(CACHE_KEY); | |
| if (primaryCached) { | |
| return { data: primaryCached.data, isStale: false }; | |
| } | |
| // Primary cache miss - try stale cache | |
| const staleCached = await redis.get<CachedFeaturedCreators>( | |
| `${CACHE_KEY}${CACHE_KEY_STALE_SUFFIX}` | |
| ); | |
| if (staleCached) { | |
| // Serving stale data - caller should trigger background revalidation | |
| return { data: staleCached.data, isStale: true }; | |
| } | |
| // Both caches missed | |
| return null; | |
| } catch (error) { | |
| console.error('Error reading from Redis cache:', error); | |
| // Gracefully degrade to database on Redis failure | |
| return null; | |
| } | |
| } | |
| /** | |
| * Set cached featured creators with dual-key strategy | |
| * Writes to both primary (2h TTL) and stale (4h TTL) caches | |
| */ | |
| export async function setCachedFeaturedCreators(data: FeaturedCreator[]): Promise<void> { | |
| try { | |
| const now = Date.now(); | |
| const cachedData: CachedFeaturedCreators = { | |
| data, | |
| cachedAt: now, | |
| expiresAt: now + (CACHE_TTL_PRIMARY * 1000), | |
| }; | |
| // Write to primary cache (2 hour TTL) | |
| await redis.set(CACHE_KEY, cachedData, { | |
| ex: CACHE_TTL_PRIMARY, | |
| }); | |
| // Write to stale cache (4 hour TTL) | |
| await redis.set( | |
| `${CACHE_KEY}${CACHE_KEY_STALE_SUFFIX}`, | |
| cachedData, | |
| { | |
| ex: CACHE_TTL_STALE, | |
| } | |
| ); | |
| } catch (error) { | |
| console.error('Error writing to Redis cache:', error); | |
| // Don't throw - caching is optional, app should work without it | |
| } | |
| } | |
| /** | |
| * Invalidate featured creators cache (both primary and stale) | |
| * Useful for manual cache busting or after data updates | |
| */ | |
| export async function invalidateFeaturedCreatorsCache(): Promise<void> { | |
| try { | |
| await redis.del(CACHE_KEY); | |
| await redis.del(`${CACHE_KEY}${CACHE_KEY_STALE_SUFFIX}`); | |
| } catch (error) { | |
| console.error('Error invalidating Redis cache:', error); | |
| } | |
| } | |
| /** | |
| * Check if primary cache exists and is fresh | |
| */ | |
| export async function isCacheFresh(): Promise<boolean> { | |
| try { | |
| const cached = await redis.get<CachedFeaturedCreators>(CACHE_KEY); | |
| if (!cached) { | |
| return false; | |
| } | |
| const now = Date.now(); | |
| return now <= cached.expiresAt; | |
| } catch (error) { | |
| console.error('Error checking cache freshness:', error); | |
| return false; | |
| } | |
| } | |