142 lines
4.5 KiB
TypeScript
142 lines
4.5 KiB
TypeScript
import { connectRedis } from './redis'
|
|
|
|
/**
|
|
* Cache management utilities for dashboard and topic data
|
|
*/
|
|
|
|
export class DashboardCache {
|
|
/**
|
|
* Invalidate dashboard cache for a specific user
|
|
*/
|
|
static async invalidateUserDashboard(userId: string): Promise<void> {
|
|
try {
|
|
const redisClient = await connectRedis()
|
|
const cacheKey = `dashboard:user:${userId}`
|
|
await redisClient.del(cacheKey)
|
|
console.log(`Dashboard cache invalidated for user ${userId}`)
|
|
} catch (error) {
|
|
console.warn('Failed to invalidate dashboard cache:', error)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invalidate dashboard caches for multiple users
|
|
*/
|
|
static async invalidateMultipleUserDashboards(userIds: string[]): Promise<void> {
|
|
try {
|
|
const redisClient = await connectRedis()
|
|
const cacheKeys = userIds.map((userId) => `dashboard:user:${userId}`)
|
|
if (cacheKeys.length > 0) {
|
|
await redisClient.del(cacheKeys)
|
|
console.log(`Dashboard caches invalidated for ${userIds.length} users`)
|
|
}
|
|
} catch (error) {
|
|
console.warn('Failed to invalidate multiple dashboard caches:', error)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get cached dashboard data for a user
|
|
*/
|
|
static async getUserDashboard(userId: string): Promise<any | null> {
|
|
try {
|
|
const redisClient = await connectRedis()
|
|
const cacheKey = `dashboard:user:${userId}`
|
|
const cachedData = await redisClient.get(cacheKey)
|
|
|
|
if (!cachedData) return null
|
|
|
|
// Handle both string and Buffer responses
|
|
const dataString = Buffer.isBuffer(cachedData)
|
|
? cachedData.toString('utf-8')
|
|
: String(cachedData)
|
|
|
|
return JSON.parse(dataString)
|
|
} catch (error) {
|
|
console.warn('Failed to get cached dashboard data:', error)
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set dashboard cache for a user
|
|
*/
|
|
static async setUserDashboard(userId: string, data: any, ttlSeconds = 300): Promise<void> {
|
|
try {
|
|
const redisClient = await connectRedis()
|
|
const cacheKey = `dashboard:user:${userId}`
|
|
await redisClient.setEx(cacheKey, ttlSeconds, JSON.stringify(data))
|
|
console.log(`Dashboard data cached for user ${userId} (TTL: ${ttlSeconds}s)`)
|
|
} catch (error) {
|
|
console.warn('Failed to cache dashboard data:', error)
|
|
}
|
|
}
|
|
}
|
|
|
|
export class TopicCache {
|
|
/**
|
|
* Invalidate topic-specific caches when a topic is modified
|
|
*/
|
|
static async invalidateTopicCaches(topicSlug: string, authorId: string): Promise<void> {
|
|
try {
|
|
const redisClient = await connectRedis()
|
|
|
|
// Invalidate patterns that might be affected
|
|
const cachePatterns = [
|
|
`topic:${topicSlug}`, // Individual topic cache
|
|
`topics:public:*`, // Public topic listings
|
|
`topics:user:${authorId}:*`, // User's topic listings
|
|
`dashboard:user:${authorId}`, // User's dashboard
|
|
]
|
|
|
|
for (const pattern of cachePatterns) {
|
|
if (pattern.includes('*')) {
|
|
// For patterns with wildcards, we need to scan and delete
|
|
const keys = await redisClient.keys(pattern)
|
|
if (keys.length > 0) {
|
|
await redisClient.del(keys)
|
|
console.log(`Invalidated ${keys.length} cache keys matching pattern: ${pattern}`)
|
|
}
|
|
} else {
|
|
// Direct key deletion
|
|
await redisClient.del(pattern)
|
|
console.log(`Cache key deleted: ${pattern}`)
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.warn('Failed to invalidate topic caches:', error)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invalidate public topic listing caches
|
|
*/
|
|
static async invalidatePublicTopicCaches(): Promise<void> {
|
|
try {
|
|
const redisClient = await connectRedis()
|
|
const keys = await redisClient.keys('topics:public:*')
|
|
if (keys.length > 0) {
|
|
await redisClient.del(keys)
|
|
console.log(`Invalidated ${keys.length} public topic cache keys`)
|
|
}
|
|
} catch (error) {
|
|
console.warn('Failed to invalidate public topic caches:', error)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Utility function to generate consistent cache keys
|
|
*/
|
|
export const CacheKeys = {
|
|
userDashboard: (userId: string) => `dashboard:user:${userId}`,
|
|
userTopics: (userId: string, page = 1, limit = 10) =>
|
|
`topics:user:${userId}:page:${page}:limit:${limit}`,
|
|
publicTopics: (page = 1, limit = 10, filters?: string) => {
|
|
const filterKey = filters ? `:filters:${Buffer.from(filters).toString('base64')}` : ''
|
|
return `topics:public:page:${page}:limit:${limit}${filterKey}`
|
|
},
|
|
individualTopic: (slug: string) => `topic:${slug}`,
|
|
relatedTopics: (topicId: string) => `topics:related:${topicId}`,
|
|
}
|