mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-07-07 12:36:57 +00:00
re-type userFollowingsCache to match the others
This commit is contained in:
parent
0c84d73294
commit
853b548a43
17 changed files with 47 additions and 56 deletions
|
@ -130,7 +130,8 @@ export class AntennaService implements OnApplicationShutdown {
|
|||
}
|
||||
|
||||
if (note.visibility === 'followers') {
|
||||
const isFollowing = Object.hasOwn(await this.cacheService.userFollowingsCache.fetch(antenna.userId), note.userId);
|
||||
const followings = await this.cacheService.userFollowingsCache.fetch(antenna.userId);
|
||||
const isFollowing = followings.has(note.userId);
|
||||
if (!isFollowing && antenna.userId !== note.userId) return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import { In, IsNull } from 'typeorm';
|
||||
import type { BlockingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository, MiFollowing, MiNote } from '@/models/_.js';
|
||||
import type { BlockingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository, MiNote } from '@/models/_.js';
|
||||
import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
|
||||
import { QuantumKVCache } from '@/misc/QuantumKVCache.js';
|
||||
import type { MiLocalUser, MiUser } from '@/models/User.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { GlobalEvents, InternalEventTypes } from '@/core/GlobalEventService.js';
|
||||
import type { InternalEventTypes } from '@/core/GlobalEventService.js';
|
||||
import { InternalEventService } from '@/core/InternalEventService.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
|
@ -46,7 +46,7 @@ export class CacheService implements OnApplicationShutdown {
|
|||
public userBlockingCache: QuantumKVCache<Set<string>>;
|
||||
public userBlockedCache: QuantumKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
|
||||
public renoteMutingsCache: QuantumKVCache<Set<string>>;
|
||||
public userFollowingsCache: QuantumKVCache<Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>;
|
||||
public userFollowingsCache: QuantumKVCache<Map<string, { withReplies: boolean }>>;
|
||||
protected userFollowStatsCache = new MemoryKVCache<FollowStats>(1000 * 60 * 10); // 10 minutes
|
||||
protected translationsCache: RedisKVCache<CachedTranslationEntity>;
|
||||
|
||||
|
@ -110,15 +110,9 @@ export class CacheService implements OnApplicationShutdown {
|
|||
fetcher: (key) => this.renoteMutingsRepository.find({ where: { muterId: key }, select: ['muteeId'] }).then(xs => new Set(xs.map(x => x.muteeId))),
|
||||
});
|
||||
|
||||
this.userFollowingsCache = new QuantumKVCache<Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>(this.internalEventService, 'userFollowings', {
|
||||
this.userFollowingsCache = new QuantumKVCache<Map<string, { withReplies: boolean }>>(this.internalEventService, 'userFollowings', {
|
||||
lifetime: 1000 * 60 * 30, // 30m
|
||||
fetcher: (key) => this.followingsRepository.find({ where: { followerId: key }, select: ['followeeId', 'withReplies'] }).then(xs => {
|
||||
const obj: Record<string, Pick<MiFollowing, 'withReplies'> | undefined> = {};
|
||||
for (const x of xs) {
|
||||
obj[x.followeeId] = { withReplies: x.withReplies };
|
||||
}
|
||||
return obj;
|
||||
}),
|
||||
fetcher: (key) => this.followingsRepository.find({ where: { followerId: key }, select: ['followeeId', 'withReplies'] }).then(xs => new Map(xs.map(f => [f.followeeId, { withReplies: f.withReplies }]))),
|
||||
});
|
||||
|
||||
this.translationsCache = new RedisKVCache<CachedTranslationEntity>(this.redisClient, 'translations', {
|
||||
|
@ -305,14 +299,14 @@ export class CacheService implements OnApplicationShutdown {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public async getUserFollowings(userIds: Iterable<string>): Promise<Map<string, Set<string>>> {
|
||||
const followings = new Map<string, Set<string>>();
|
||||
public async getUserFollowings(userIds: Iterable<string>): Promise<Map<string, Map<string, { withReplies: boolean }>>> {
|
||||
const followings = new Map<string, Map<string, { withReplies: boolean }>>();
|
||||
|
||||
const toFetch: string[] = [];
|
||||
for (const userId of userIds) {
|
||||
const fromCache = this.userFollowingsCache.get(userId);
|
||||
if (fromCache) {
|
||||
followings.set(userId, new Set(Object.keys(fromCache)));
|
||||
followings.set(userId, fromCache);
|
||||
} else {
|
||||
toFetch.push(userId);
|
||||
}
|
||||
|
@ -331,25 +325,25 @@ export class CacheService implements OnApplicationShutdown {
|
|||
})
|
||||
.getMany();
|
||||
|
||||
const toCache = new Map<string, Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>();
|
||||
const toCache = new Map<string, Map<string, { withReplies: boolean }>>();
|
||||
|
||||
// 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);
|
||||
let cacheMap = toCache.get(followerId);
|
||||
if (!cacheMap) {
|
||||
cacheMap = new Map();
|
||||
toCache.set(followerId, cacheMap);
|
||||
}
|
||||
cacheSet[followeeId] = { withReplies };
|
||||
cacheMap.set(followeeId, { withReplies });
|
||||
|
||||
// Queue for return
|
||||
let returnSet = followings.get(followerId);
|
||||
if (!returnSet) {
|
||||
returnSet = new Set();
|
||||
returnSet = new Map();
|
||||
followings.set(followerId, returnSet);
|
||||
}
|
||||
returnSet.add(followeeId);
|
||||
returnSet.set(followeeId, { withReplies });
|
||||
}
|
||||
|
||||
// Update cache to speed up future calls
|
||||
|
|
|
@ -113,27 +113,27 @@ export class NotificationService implements OnApplicationShutdown {
|
|||
}
|
||||
|
||||
if (recieveConfig?.type === 'following') {
|
||||
const isFollowing = await this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => Object.hasOwn(followings, notifierId));
|
||||
const isFollowing = await this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => followings.has(notifierId));
|
||||
if (!isFollowing) {
|
||||
return null;
|
||||
}
|
||||
} else if (recieveConfig?.type === 'follower') {
|
||||
const isFollower = await this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => Object.hasOwn(followings, notifieeId));
|
||||
const isFollower = await this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => followings.has(notifieeId));
|
||||
if (!isFollower) {
|
||||
return null;
|
||||
}
|
||||
} else if (recieveConfig?.type === 'mutualFollow') {
|
||||
const [isFollowing, isFollower] = await Promise.all([
|
||||
this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => Object.hasOwn(followings, notifierId)),
|
||||
this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => Object.hasOwn(followings, notifieeId)),
|
||||
this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => followings.has(notifierId)),
|
||||
this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => followings.has(notifieeId)),
|
||||
]);
|
||||
if (!(isFollowing && isFollower)) {
|
||||
return null;
|
||||
}
|
||||
} else if (recieveConfig?.type === 'followingOrFollower') {
|
||||
const [isFollowing, isFollower] = await Promise.all([
|
||||
this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => Object.hasOwn(followings, notifierId)),
|
||||
this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => Object.hasOwn(followings, notifieeId)),
|
||||
this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => followings.has(notifierId)),
|
||||
this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => followings.has(notifieeId)),
|
||||
]);
|
||||
if (!isFollowing && !isFollower) {
|
||||
return null;
|
||||
|
|
|
@ -133,7 +133,7 @@ export class NoteEntityService implements OnModuleInit {
|
|||
|
||||
@bindThis
|
||||
public async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null, hint?: {
|
||||
myFollowing?: ReadonlySet<string>,
|
||||
myFollowing?: ReadonlyMap<string, { withReplies: boolean }>,
|
||||
myBlockers?: ReadonlySet<string>,
|
||||
}): Promise<void> {
|
||||
if (meId === packedNote.userId) return;
|
||||
|
@ -193,7 +193,7 @@ export class NoteEntityService implements OnModuleInit {
|
|||
} else {
|
||||
const isFollowing = hint?.myFollowing
|
||||
? hint.myFollowing.has(packedNote.userId)
|
||||
: (await this.cacheService.userFollowingsCache.fetch(meId))[packedNote.userId] != null;
|
||||
: (await this.cacheService.userFollowingsCache.fetch(meId)).has(packedNote.userId);
|
||||
|
||||
hide = !isFollowing;
|
||||
}
|
||||
|
@ -358,14 +358,10 @@ export class NoteEntityService implements OnModuleInit {
|
|||
: this.cacheService.userBlockingCache.fetch(meId).then((ids) => ids.has(note.userId)),
|
||||
hint?.myFollowing
|
||||
? hint.myFollowing.has(note.userId)
|
||||
: this.followingsRepository.existsBy({
|
||||
followeeId: note.userId,
|
||||
followerId: meId,
|
||||
}),
|
||||
: this.cacheService.userFollowingsCache.fetch(meId).then(ids => ids.has(note.userId)),
|
||||
hint?.me !== undefined
|
||||
? (hint.me?.host ?? null)
|
||||
: this.cacheService.userByIdCache.fetch(meId, () => this.usersRepository.findOneByOrFail({ id: meId }))
|
||||
.then(me => me.host),
|
||||
: this.cacheService.findUserById(meId).then(me => me.host),
|
||||
]);
|
||||
|
||||
if (blocked) return false;
|
||||
|
@ -420,7 +416,7 @@ export class NoteEntityService implements OnModuleInit {
|
|||
packedFiles: Map<MiNote['fileIds'][number], Packed<'DriveFile'> | null>;
|
||||
packedUsers: Map<MiUser['id'], Packed<'UserLite'>>;
|
||||
mentionHandles: Record<string, string | undefined>;
|
||||
userFollowings: Map<string, Set<string>>;
|
||||
userFollowings: Map<string, Map<string, { withReplies: boolean }>>;
|
||||
userBlockers: Map<string, Set<string>>;
|
||||
polls: Map<string, MiPoll>;
|
||||
pollVotes: Map<string, Map<string, MiPollVote[]>>;
|
||||
|
|
|
@ -164,7 +164,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
excludeBots: !ps.withBots,
|
||||
noteFilter: note => {
|
||||
if (note.reply && note.reply.visibility === 'followers') {
|
||||
if (!Object.hasOwn(followings, note.reply.userId) && note.reply.userId !== me.id) return false;
|
||||
if (!followings.has(note.reply.userId) && note.reply.userId !== me.id) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -115,7 +115,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
excludePureRenotes: !ps.withRenotes,
|
||||
noteFilter: note => {
|
||||
if (note.reply && note.reply.visibility === 'followers') {
|
||||
if (!Object.hasOwn(followings, note.reply.userId) && note.reply.userId !== me.id) return false;
|
||||
if (!followings.has(note.reply.userId) && note.reply.userId !== me.id) return false;
|
||||
}
|
||||
if (!ps.withBots && note.user?.isBot) return false;
|
||||
|
||||
|
|
|
@ -134,7 +134,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
if (ps.withReplies) redisTimelines.push(`userTimelineWithReplies:${ps.userId}`);
|
||||
if (ps.withChannelNotes) redisTimelines.push(`userTimelineWithChannel:${ps.userId}`);
|
||||
|
||||
const isFollowing = me && Object.hasOwn(await this.cacheService.userFollowingsCache.fetch(me.id), ps.userId);
|
||||
const isFollowing = me && (await this.cacheService.userFollowingsCache.fetch(me.id)).has(ps.userId);
|
||||
|
||||
const timeline = await this.fanoutTimelineEndpointService.timeline({
|
||||
untilId,
|
||||
|
|
|
@ -36,7 +36,7 @@ export default class Connection {
|
|||
private channels = new Map<string, Channel>();
|
||||
private subscribingNotes = new Map<string, number>();
|
||||
public userProfile: MiUserProfile | null = null;
|
||||
public following: Record<string, Pick<MiFollowing, 'withReplies'> | undefined> = {};
|
||||
public following: Map<string, { withReplies: boolean }> = new Map();
|
||||
public followingChannels: Set<string> = new Set();
|
||||
public userIdsWhoMeMuting: Set<string> = new Set();
|
||||
public userIdsWhoBlockingMe: Set<string> = new Set();
|
||||
|
|
|
@ -70,7 +70,7 @@ export default abstract class Channel {
|
|||
if (!this.user) return false;
|
||||
if (this.user.id === note.userId) return true;
|
||||
if (note.visibility === 'followers') {
|
||||
return this.following[note.userId] != null;
|
||||
return this.following.has(note.userId);
|
||||
}
|
||||
if (!note.visibleUserIds) return false;
|
||||
return note.visibleUserIds.includes(this.user.id);
|
||||
|
@ -84,7 +84,7 @@ export default abstract class Channel {
|
|||
if (note.user.requireSigninToViewContents && !this.user) return true;
|
||||
|
||||
// 流れてきたNoteがインスタンスミュートしたインスタンスが関わる
|
||||
if (isInstanceMuted(note, this.userMutedInstances) && !this.following[note.userId]) return true;
|
||||
if (isInstanceMuted(note, this.userMutedInstances) && !this.following.has(note.userId)) return true;
|
||||
|
||||
// 流れてきたNoteがミュートしているユーザーが関わる
|
||||
if (isUserRelated(note, this.userIdsWhoMeMuting)) return true;
|
||||
|
@ -101,7 +101,7 @@ export default abstract class Channel {
|
|||
if (note.user.isSilenced || note.user.instance?.isSilenced) {
|
||||
if (this.user == null) return true;
|
||||
if (this.user.id === note.userId) return false;
|
||||
if (this.following[note.userId] == null) return true;
|
||||
if (!this.following.has(note.userId)) return true;
|
||||
}
|
||||
|
||||
// TODO muted threads
|
||||
|
|
|
@ -62,7 +62,7 @@ class BubbleTimelineChannel extends Channel {
|
|||
const reply = note.reply;
|
||||
// 自分のフォローしていないユーザーの visibility: followers な投稿への返信は弾く
|
||||
if (!this.isNoteVisibleToMe(reply)) return;
|
||||
if (!this.following[note.userId]?.withReplies) {
|
||||
if (!this.following.get(note.userId)?.withReplies) {
|
||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||
if (reply.userId !== this.user?.id && !isMe && reply.userId !== note.userId) return;
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ class GlobalTimelineChannel extends Channel {
|
|||
const reply = note.reply;
|
||||
// 自分のフォローしていないユーザーの visibility: followers な投稿への返信は弾く
|
||||
if (!this.isNoteVisibleToMe(reply)) return;
|
||||
if (!this.following[note.userId]?.withReplies) {
|
||||
if (!this.following.get(note.userId)?.withReplies) {
|
||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||
if (reply.userId !== this.user?.id && !isMe && reply.userId !== note.userId) return;
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ class HomeTimelineChannel extends Channel {
|
|||
if (!this.followingChannels.has(note.channelId)) return;
|
||||
} else {
|
||||
// その投稿のユーザーをフォローしていなかったら弾く
|
||||
if (!isMe && !Object.hasOwn(this.following, note.userId)) return;
|
||||
if (!isMe && !this.following.has(note.userId)) return;
|
||||
}
|
||||
|
||||
if (this.isNoteMutedOrBlocked(note)) return;
|
||||
|
@ -57,7 +57,7 @@ class HomeTimelineChannel extends Channel {
|
|||
const reply = note.reply;
|
||||
// 自分のフォローしていないユーザーの visibility: followers な投稿への返信は弾く
|
||||
if (!this.isNoteVisibleToMe(reply)) return;
|
||||
if (!this.following[note.userId]?.withReplies) {
|
||||
if (!this.following.get(note.userId)?.withReplies) {
|
||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||
if (reply.userId !== this.user!.id && !isMe && reply.userId !== note.userId) return;
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ class HybridTimelineChannel extends Channel {
|
|||
// フォローしているチャンネルの投稿 の場合だけ
|
||||
if (!(
|
||||
(note.channelId == null && isMe) ||
|
||||
(note.channelId == null && Object.hasOwn(this.following, note.userId)) ||
|
||||
(note.channelId == null && this.following.has(note.userId)) ||
|
||||
(note.channelId == null && (note.user.host == null && note.visibility === 'public')) ||
|
||||
(note.channelId != null && this.followingChannels.has(note.channelId))
|
||||
)) return;
|
||||
|
@ -74,7 +74,7 @@ class HybridTimelineChannel extends Channel {
|
|||
const reply = note.reply;
|
||||
// 自分のフォローしていないユーザーの visibility: followers な投稿への返信は弾く
|
||||
if (!this.isNoteVisibleToMe(reply)) return;
|
||||
if (!this.following[note.userId]?.withReplies && !this.withReplies) {
|
||||
if (!this.following.get(note.userId)?.withReplies && !this.withReplies) {
|
||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||
if (reply.userId !== this.user!.id && !isMe && reply.userId !== note.userId) return;
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ class LocalTimelineChannel extends Channel {
|
|||
const reply = note.reply;
|
||||
// 自分のフォローしていないユーザーの visibility: followers な投稿への返信は弾く
|
||||
if (!this.isNoteVisibleToMe(reply)) return;
|
||||
if (!this.following[note.userId]?.withReplies) {
|
||||
if (!this.following.get(note.userId)?.withReplies) {
|
||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||
if (reply.userId !== this.user?.id && !isMe && reply.userId !== note.userId) return;
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ class RoleTimelineChannel extends Channel {
|
|||
const reply = note.reply;
|
||||
// 自分のフォローしていないユーザーの visibility: followers な投稿への返信は弾く
|
||||
if (!this.isNoteVisibleToMe(reply)) return;
|
||||
if (!this.following[note.userId]?.withReplies) {
|
||||
if (!this.following.get(note.userId)?.withReplies) {
|
||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||
if (reply.userId !== this.user?.id && !isMe && reply.userId !== note.userId) return;
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ class UserListChannel extends Channel {
|
|||
const reply = note.reply;
|
||||
// 自分のフォローしていないユーザーの visibility: followers な投稿への返信は弾く
|
||||
if (!this.isNoteVisibleToMe(reply)) return;
|
||||
if (!this.following[note.userId]?.withReplies) {
|
||||
if (!this.following.get(note.userId)?.withReplies) {
|
||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||
if (reply.userId !== this.user!.id && !isMe && reply.userId !== note.userId) return;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import * as Redis from 'ioredis';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { FakeInternalEventService } from './FakeInternalEventService.js';
|
||||
import type { BlockingsRepository, FollowingsRepository, MiFollowing, MiUser, MiUserProfile, MutingsRepository, RenoteMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||
import type { BlockingsRepository, FollowingsRepository, MiUser, MiUserProfile, MutingsRepository, RenoteMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||
import type { MiLocalUser } from '@/models/User.js';
|
||||
import { MemoryKVCache, MemorySingleCache, RedisKVCache, RedisSingleCache } from '@/misc/cache.js';
|
||||
import { QuantumKVCache, QuantumKVOpts } from '@/misc/QuantumKVCache.js';
|
||||
|
@ -106,7 +106,7 @@ export class NoOpCacheService extends CacheService {
|
|||
onSet: this.renoteMutingsCache.onSet,
|
||||
onDelete: this.renoteMutingsCache.onDelete,
|
||||
});
|
||||
this.userFollowingsCache = new NoOpQuantumKVCache<Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>({
|
||||
this.userFollowingsCache = new NoOpQuantumKVCache<Map<string, { withReplies: boolean }>>({
|
||||
internalEventService: fakeInternalEventService,
|
||||
fetcher: this.userFollowingsCache.fetcher,
|
||||
onSet: this.userFollowingsCache.onSet,
|
||||
|
|
Loading…
Add table
Reference in a new issue