From bf1156426eea0bd11a5926ba7883c870bad2e144 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 6 Jun 2025 00:08:34 -0400 Subject: [PATCH] add CacheService.getUserFollowings and CacheService.getUserBlockers --- packages/backend/src/core/CacheService.ts | 110 +++++++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts index f04b18c02b..6e979130a0 100644 --- a/packages/backend/src/core/CacheService.ts +++ b/packages/backend/src/core/CacheService.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; -import { IsNull } from 'typeorm'; +import { In, IsNull } from 'typeorm'; import type { BlockingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository, MiFollowing, MiNote } from '@/models/_.js'; import { MemoryKVCache, QuantumKVCache, RedisKVCache } from '@/misc/cache.js'; import type { MiLocalUser, MiUser } from '@/models/User.js'; @@ -293,6 +293,114 @@ export class CacheService implements OnApplicationShutdown { }); } + @bindThis + public async getUserFollowings(userIds: Iterable): Promise>> { + const followings = new Map>(); + + const toFetch: string[] = []; + for (const userId of userIds) { + const fromCache = this.userFollowingsCache.get(userId); + if (fromCache) { + followings.set(userId, new Set(Object.keys(fromCache))); + } else { + toFetch.push(userId); + } + } + + if (toFetch.length > 0) { + const fetchedFollowings = await this.followingsRepository + .createQueryBuilder('following') + .select([ + 'following.followerId', + 'following.followeeId', + 'following.withReplies', + ]) + .where({ + followerId: In(toFetch), + }) + .getMany(); + + const toCache = new Map | undefined>>(); + + // Pivot to a map + for (const { followerId, followeeId, withReplies } of fetchedFollowings) { + // Queue for cache + let cacheSet = toCache.get(followerId); + if (!cacheSet) { + cacheSet = {}; + toCache.set(followerId, cacheSet); + } + cacheSet[followeeId] = { withReplies }; + + // Queue for return + let returnSet = followings.get(followerId); + if (!returnSet) { + returnSet = new Set(); + followings.set(followerId, returnSet); + } + returnSet.add(followeeId); + } + + // Update cache to speed up future calls + await this.userFollowingsCache.setMany(toCache.entries()); + } + + return followings; + } + + @bindThis + public async getUserBlockers(userIds: Iterable): Promise>> { + const blockers = new Map>(); + + const toFetch: string[] = []; + for (const userId of userIds) { + const fromCache = this.userBlockedCache.get(userId); + if (fromCache) { + blockers.set(userId, fromCache); + } else { + toFetch.push(userId); + } + } + + if (toFetch.length > 0) { + const fetchedBlockers = await this.blockingsRepository.createQueryBuilder('blocking') + .select([ + 'blocking.blockerId', + 'blocking.blockeeId', + ]) + .where({ + blockeeId: In(toFetch), + }) + .getMany(); + + const toCache = new Map>(); + + // Pivot to a map + for (const { blockerId, blockeeId } of fetchedBlockers) { + // Queue for cache + let cacheSet = toCache.get(blockeeId); + if (!cacheSet) { + cacheSet = new Set(); + toCache.set(blockeeId, cacheSet); + } + cacheSet.add(blockerId); + + // Queue for return + let returnSet = blockers.get(blockeeId); + if (!returnSet) { + returnSet = new Set(); + blockers.set(blockeeId, returnSet); + } + returnSet.add(blockerId); + } + + // Update cache to speed up future calls + await this.userBlockedCache.setMany(toCache.entries()); + } + + return blockers; + } + @bindThis public dispose(): void { this.internalEventService.off('userChangeSuspendedState', this.onUserEvent);