a cache for slack profile pictures and emojis

feat: implement longer cache times with lazy updating of users

dunkirk.sh fa624030 ef84672f

verified
Changed files
+104 -2
src
+101 -2
src/cache.ts
··· 49 49 private analyticsCache: Map<string, { data: any; timestamp: number }> = new Map(); 50 50 private analyticsCacheTTL = 30000; // 30 second cache for faster updates 51 51 52 + // Background user update queue to avoid Slack API limits 53 + private userUpdateQueue: Set<string> = new Set(); 54 + private isProcessingQueue = false; 55 + private slackWrapper?: any; // Will be injected after construction 56 + 52 57 /** 53 58 * Creates a new Cache instance 54 59 * @param dbPath Path to SQLite database file ··· 66 71 67 72 this.initDatabase(); 68 73 this.setupPurgeSchedule(); 69 - 74 + this.startQueueProcessor(); 75 + 70 76 // Run migrations 71 77 this.runMigrations(); 72 78 } ··· 322 328 } 323 329 324 330 /** 331 + * Sets the Slack wrapper for user updates 332 + * @param slackWrapper SlackWrapper instance for API calls 333 + */ 334 + setSlackWrapper(slackWrapper: any) { 335 + this.slackWrapper = slackWrapper; 336 + } 337 + 338 + /** 339 + * Adds a user to the background update queue 340 + * @param userId User ID to queue for update 341 + * @private 342 + */ 343 + private queueUserUpdate(userId: string) { 344 + this.userUpdateQueue.add(userId.toUpperCase()); 345 + } 346 + 347 + /** 348 + * Starts the background queue processor 349 + * @private 350 + */ 351 + private startQueueProcessor() { 352 + // Process queue every 30 seconds to respect Slack API limits 353 + setInterval(async () => { 354 + await this.processUserUpdateQueue(); 355 + }, 30 * 1000); 356 + } 357 + 358 + /** 359 + * Processes the user update queue with rate limiting 360 + * @private 361 + */ 362 + private async processUserUpdateQueue() { 363 + if (this.isProcessingQueue || this.userUpdateQueue.size === 0 || !this.slackWrapper) { 364 + return; 365 + } 366 + 367 + this.isProcessingQueue = true; 368 + 369 + try { 370 + // Process up to 3 users at a time to respect API limits 371 + const usersToUpdate = Array.from(this.userUpdateQueue).slice(0, 3); 372 + 373 + for (const userId of usersToUpdate) { 374 + try { 375 + console.log(`Background updating user: ${userId}`); 376 + const slackUser = await this.slackWrapper.getUserInfo(userId); 377 + 378 + // Update user in cache with fresh data 379 + await this.insertUser( 380 + slackUser.id, 381 + slackUser.real_name || slackUser.name || "Unknown", 382 + slackUser.profile?.pronouns || "", 383 + slackUser.profile?.image_512 || slackUser.profile?.image_192 || "" 384 + ); 385 + 386 + // Remove from queue after successful update 387 + this.userUpdateQueue.delete(userId); 388 + } catch (error) { 389 + console.warn(`Failed to update user ${userId}:`, error); 390 + // Remove from queue even if failed to prevent infinite retry 391 + this.userUpdateQueue.delete(userId); 392 + } 393 + } 394 + } catch (error) { 395 + console.error("Error processing user update queue:", error); 396 + } finally { 397 + this.isProcessingQueue = false; 398 + } 399 + } 400 + 401 + /** 325 402 * Inserts a user into the cache 326 403 * @param userId Unique identifier for the user 327 404 * @param imageUrl URL of the user's image ··· 480 557 return null; 481 558 } 482 559 483 - if (new Date(result.expiration).getTime() < Date.now()) { 560 + const now = Date.now(); 561 + const expiration = new Date(result.expiration).getTime(); 562 + 563 + // If user is expired, remove and return null 564 + if (expiration < now) { 484 565 this.db.run("DELETE FROM users WHERE userId = ?", [userId]); 485 566 return null; 567 + } 568 + 569 + // Touch-to-refresh: if user is older than 24 hours, extend TTL and queue for background update 570 + const twentyFourHoursAgo = now - (24 * 60 * 60 * 1000); 571 + const userAge = expiration - (7 * 24 * 60 * 60 * 1000); // When user was originally cached 572 + 573 + if (userAge < twentyFourHoursAgo) { 574 + // Extend TTL by another 7 days from now 575 + const newExpiration = now + (7 * 24 * 60 * 60 * 1000); 576 + this.db.run("UPDATE users SET expiration = ? WHERE userId = ?", [ 577 + newExpiration, 578 + userId.toUpperCase() 579 + ]); 580 + 581 + // Queue for background update to get fresh data 582 + this.queueUserUpdate(userId); 583 + 584 + console.log(`Touch-refresh: Extended TTL for user ${userId} and queued for update`); 486 585 } 487 586 488 587 return {
+3
src/index.ts
··· 71 71 }, 72 72 ); 73 73 74 + // Inject SlackWrapper into cache for background user updates 75 + cache.setSlackWrapper(slackApp); 76 + 74 77 // Cache maintenance is now handled automatically by cache.ts scheduled tasks 75 78 76 79 // Start the server