diff --git a/keyserver/src/updaters/thread-updaters.js b/keyserver/src/updaters/thread-updaters.js --- a/keyserver/src/updaters/thread-updaters.js +++ b/keyserver/src/updaters/thread-updaters.js @@ -69,6 +69,7 @@ import type { Viewer } from '../session/viewer.js'; import { neynarClient } from '../utils/fc-cache.js'; import { findUserIdentities } from '../utils/identity-utils.js'; +import { redisCache } from '../utils/redis-cache.js'; import RelationshipChangeset from '../utils/relationship-changeset.js'; type UpdateRoleOptions = { @@ -942,13 +943,11 @@ return null; } - const ledChannels = - await neynarClient?.fetchLedFarcasterChannels(farcasterID); - - if ( - !ledChannels || - !ledChannels.some(channel => channel.id === communityFarcasterChannelTag) - ) { + const leadsChannel = await userLeadsChannel( + communityFarcasterChannelTag, + farcasterID, + ); + if (!leadsChannel) { return null; } @@ -962,6 +961,42 @@ return null; } +async function userLeadsChannel( + communityFarcasterChannelTag: string, + farcasterID: string, +) { + const cachedChannelInfo = await redisCache.getChannelInfo( + communityFarcasterChannelTag, + ); + if (cachedChannelInfo) { + return cachedChannelInfo.lead.fid === parseInt(farcasterID); + } + + // In the background, we fetch and cache followed channels + ignorePromiseRejections( + (async () => { + const followedChannels = + await neynarClient?.fetchFollowedFarcasterChannels(farcasterID); + if (followedChannels) { + await Promise.allSettled( + followedChannels.map(followedChannel => + redisCache.setChannelInfo(followedChannel.id, followedChannel), + ), + ); + } + })(), + ); + + const channelInfo = await neynarClient?.fetchFarcasterChannelByName( + communityFarcasterChannelTag, + ); + if (channelInfo) { + return channelInfo.lead.fid === parseInt(farcasterID); + } + + return false; +} + async function toggleMessagePinForThread( viewer: Viewer, request: ToggleMessagePinRequest, diff --git a/lib/utils/neynar-client.js b/lib/utils/neynar-client.js --- a/lib/utils/neynar-client.js +++ b/lib/utils/neynar-client.js @@ -63,16 +63,9 @@ class NeynarClient { apiKey: string; - ledChannelsCache: Map< - string, - { channels: NeynarChannel[], timestamp: number }, - >; - cacheTTL: number; - constructor(apiKey: string, cacheTTL: number = 60000) { + constructor(apiKey: string) { this.apiKey = apiKey; - this.ledChannelsCache = new Map(); - this.cacheTTL = cacheTTL; // Default TTL of 60 seconds } // We're using the term "friend" for a bidirectional follow @@ -160,33 +153,6 @@ return this.fetchFollowedFarcasterChannelsWithFilter(fid, () => true); } - cleanExpiredCacheEntries() { - const now = Date.now(); - for (const [fid, { timestamp }] of this.ledChannelsCache.entries()) { - if (now - timestamp >= this.cacheTTL) { - this.ledChannelsCache.delete(fid); - } - } - } - - async fetchLedFarcasterChannels(fid: string): Promise { - this.cleanExpiredCacheEntries(); - - const cachedEntry = this.ledChannelsCache.get(fid); - if (cachedEntry) { - return cachedEntry.channels; - } - - const channels = await this.fetchFollowedFarcasterChannelsWithFilter( - fid, - channel => channel.lead.fid === parseInt(fid), - ); - - this.ledChannelsCache.set(fid, { channels, timestamp: Date.now() }); - - return channels; - } - async fetchFarcasterChannelByName( channelName: string, ): Promise {