summaryrefslogtreecommitdiff
path: root/packages/backend/src/core
diff options
context:
space:
mode:
authorAcid Chicken (硫酸鶏) <root@acid-chicken.com>2023-04-05 00:41:49 +0900
committerGitHub <noreply@github.com>2023-04-05 00:41:49 +0900
commit7bd0001e763a12c2b2aeb5cf4417f802cd4fbb4c (patch)
tree62ca232417372612f78761f26669b56a80d35733 /packages/backend/src/core
parentMerge branch 'develop' into fix/visibility-widening (diff)
parentenhance(backend): improve cache (diff)
downloadsharkey-7bd0001e763a12c2b2aeb5cf4417f802cd4fbb4c.tar.gz
sharkey-7bd0001e763a12c2b2aeb5cf4417f802cd4fbb4c.tar.bz2
sharkey-7bd0001e763a12c2b2aeb5cf4417f802cd4fbb4c.zip
Merge branch 'develop' into fix/visibility-widening
Diffstat (limited to 'packages/backend/src/core')
-rw-r--r--packages/backend/src/core/CacheService.ts (renamed from packages/backend/src/core/UserCacheService.ts)33
-rw-r--r--packages/backend/src/core/CoreModule.ts12
-rw-r--r--packages/backend/src/core/CustomEmojiService.ts6
-rw-r--r--packages/backend/src/core/FederatedInstanceService.ts6
-rw-r--r--packages/backend/src/core/InstanceActorService.ts12
-rw-r--r--packages/backend/src/core/NoteCreateService.ts6
-rw-r--r--packages/backend/src/core/NoteReadService.ts4
-rw-r--r--packages/backend/src/core/NotificationService.ts109
-rw-r--r--packages/backend/src/core/PushNotificationService.ts12
-rw-r--r--packages/backend/src/core/RelayService.ts8
-rw-r--r--packages/backend/src/core/RoleService.ts34
-rw-r--r--packages/backend/src/core/UserBlockingService.ts6
-rw-r--r--packages/backend/src/core/UserKeypairStoreService.ts6
-rw-r--r--packages/backend/src/core/activitypub/ApDbResolverService.ts20
-rw-r--r--packages/backend/src/core/activitypub/models/ApPersonService.ts14
-rw-r--r--packages/backend/src/core/entities/NotificationEntityService.ts75
-rw-r--r--packages/backend/src/core/entities/UserEntityService.ts38
17 files changed, 193 insertions, 208 deletions
diff --git a/packages/backend/src/core/UserCacheService.ts b/packages/backend/src/core/CacheService.ts
index 631eb44062..887baeb2c2 100644
--- a/packages/backend/src/core/UserCacheService.ts
+++ b/packages/backend/src/core/CacheService.ts
@@ -1,7 +1,7 @@
import { Inject, Injectable } from '@nestjs/common';
import Redis from 'ioredis';
-import type { UsersRepository } from '@/models/index.js';
-import { KVCache } from '@/misc/cache.js';
+import type { UserProfile, UsersRepository } from '@/models/index.js';
+import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
import type { LocalUser, User } from '@/models/entities/User.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
@@ -10,13 +10,18 @@ import { StreamMessages } from '@/server/api/stream/types.js';
import type { OnApplicationShutdown } from '@nestjs/common';
@Injectable()
-export class UserCacheService implements OnApplicationShutdown {
- public userByIdCache: KVCache<User>;
- public localUserByNativeTokenCache: KVCache<LocalUser | null>;
- public localUserByIdCache: KVCache<LocalUser>;
- public uriPersonCache: KVCache<User | null>;
+export class CacheService implements OnApplicationShutdown {
+ public userByIdCache: MemoryKVCache<User>;
+ public localUserByNativeTokenCache: MemoryKVCache<LocalUser | null>;
+ public localUserByIdCache: MemoryKVCache<LocalUser>;
+ public uriPersonCache: MemoryKVCache<User | null>;
+ public userProfileCache: RedisKVCache<UserProfile>;
+ public userMutingsCache: RedisKVCache<string[]>;
constructor(
+ @Inject(DI.redis)
+ private redisClient: Redis.Redis,
+
@Inject(DI.redisSubscriber)
private redisSubscriber: Redis.Redis,
@@ -27,10 +32,12 @@ export class UserCacheService implements OnApplicationShutdown {
) {
//this.onMessage = this.onMessage.bind(this);
- this.userByIdCache = new KVCache<User>(Infinity);
- this.localUserByNativeTokenCache = new KVCache<LocalUser | null>(Infinity);
- this.localUserByIdCache = new KVCache<LocalUser>(Infinity);
- this.uriPersonCache = new KVCache<User | null>(Infinity);
+ this.userByIdCache = new MemoryKVCache<User>(Infinity);
+ this.localUserByNativeTokenCache = new MemoryKVCache<LocalUser | null>(Infinity);
+ this.localUserByIdCache = new MemoryKVCache<LocalUser>(Infinity);
+ this.uriPersonCache = new MemoryKVCache<User | null>(Infinity);
+ this.userProfileCache = new RedisKVCache<UserProfile>(this.redisClient, 'userProfile', 1000 * 60 * 60 * 24, 1000 * 60);
+ this.userMutingsCache = new RedisKVCache<string[]>(this.redisClient, 'userMutings', 1000 * 60 * 60 * 24, 1000 * 60);
this.redisSubscriber.on('message', this.onMessage);
}
@@ -52,7 +59,7 @@ export class UserCacheService implements OnApplicationShutdown {
}
}
if (this.userEntityService.isLocalUser(user)) {
- this.localUserByNativeTokenCache.set(user.token, user);
+ this.localUserByNativeTokenCache.set(user.token!, user);
this.localUserByIdCache.set(user.id, user);
}
break;
@@ -77,7 +84,7 @@ export class UserCacheService implements OnApplicationShutdown {
}
@bindThis
- public findById(userId: User['id']) {
+ public findUserById(userId: User['id']) {
return this.userByIdCache.fetch(userId, () => this.usersRepository.findOneByOrFail({ id: userId }));
}
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index d67e80fc1d..5c867e6cfc 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -38,7 +38,7 @@ import { S3Service } from './S3Service.js';
import { SignupService } from './SignupService.js';
import { TwoFactorAuthenticationService } from './TwoFactorAuthenticationService.js';
import { UserBlockingService } from './UserBlockingService.js';
-import { UserCacheService } from './UserCacheService.js';
+import { CacheService } from './CacheService.js';
import { UserFollowingService } from './UserFollowingService.js';
import { UserKeypairStoreService } from './UserKeypairStoreService.js';
import { UserListService } from './UserListService.js';
@@ -159,7 +159,7 @@ const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service };
const $SignupService: Provider = { provide: 'SignupService', useExisting: SignupService };
const $TwoFactorAuthenticationService: Provider = { provide: 'TwoFactorAuthenticationService', useExisting: TwoFactorAuthenticationService };
const $UserBlockingService: Provider = { provide: 'UserBlockingService', useExisting: UserBlockingService };
-const $UserCacheService: Provider = { provide: 'UserCacheService', useExisting: UserCacheService };
+const $CacheService: Provider = { provide: 'CacheService', useExisting: CacheService };
const $UserFollowingService: Provider = { provide: 'UserFollowingService', useExisting: UserFollowingService };
const $UserKeypairStoreService: Provider = { provide: 'UserKeypairStoreService', useExisting: UserKeypairStoreService };
const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService };
@@ -282,7 +282,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
SignupService,
TwoFactorAuthenticationService,
UserBlockingService,
- UserCacheService,
+ CacheService,
UserFollowingService,
UserKeypairStoreService,
UserListService,
@@ -399,7 +399,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$SignupService,
$TwoFactorAuthenticationService,
$UserBlockingService,
- $UserCacheService,
+ $CacheService,
$UserFollowingService,
$UserKeypairStoreService,
$UserListService,
@@ -517,7 +517,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
SignupService,
TwoFactorAuthenticationService,
UserBlockingService,
- UserCacheService,
+ CacheService,
UserFollowingService,
UserKeypairStoreService,
UserListService,
@@ -633,7 +633,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$SignupService,
$TwoFactorAuthenticationService,
$UserBlockingService,
- $UserCacheService,
+ $CacheService,
$UserFollowingService,
$UserKeypairStoreService,
$UserListService,
diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts
index a62854c61c..1c3b60e5d7 100644
--- a/packages/backend/src/core/CustomEmojiService.ts
+++ b/packages/backend/src/core/CustomEmojiService.ts
@@ -8,7 +8,7 @@ import type { DriveFile } from '@/models/entities/DriveFile.js';
import type { Emoji } from '@/models/entities/Emoji.js';
import type { EmojisRepository, Note } from '@/models/index.js';
import { bindThis } from '@/decorators.js';
-import { KVCache } from '@/misc/cache.js';
+import { MemoryKVCache } from '@/misc/cache.js';
import { UtilityService } from '@/core/UtilityService.js';
import type { Config } from '@/config.js';
import { ReactionService } from '@/core/ReactionService.js';
@@ -16,7 +16,7 @@ import { query } from '@/misc/prelude/url.js';
@Injectable()
export class CustomEmojiService {
- private cache: KVCache<Emoji | null>;
+ private cache: MemoryKVCache<Emoji | null>;
constructor(
@Inject(DI.config)
@@ -34,7 +34,7 @@ export class CustomEmojiService {
private globalEventService: GlobalEventService,
private reactionService: ReactionService,
) {
- this.cache = new KVCache<Emoji | null>(1000 * 60 * 60 * 12);
+ this.cache = new MemoryKVCache<Emoji | null>(1000 * 60 * 60 * 12);
}
@bindThis
diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts
index b85791e43f..2c6d3ac508 100644
--- a/packages/backend/src/core/FederatedInstanceService.ts
+++ b/packages/backend/src/core/FederatedInstanceService.ts
@@ -1,7 +1,7 @@
import { Inject, Injectable } from '@nestjs/common';
import type { InstancesRepository } from '@/models/index.js';
import type { Instance } from '@/models/entities/Instance.js';
-import { KVCache } from '@/misc/cache.js';
+import { MemoryKVCache } from '@/misc/cache.js';
import { IdService } from '@/core/IdService.js';
import { DI } from '@/di-symbols.js';
import { UtilityService } from '@/core/UtilityService.js';
@@ -9,7 +9,7 @@ import { bindThis } from '@/decorators.js';
@Injectable()
export class FederatedInstanceService {
- private cache: KVCache<Instance>;
+ private cache: MemoryKVCache<Instance>;
constructor(
@Inject(DI.instancesRepository)
@@ -18,7 +18,7 @@ export class FederatedInstanceService {
private utilityService: UtilityService,
private idService: IdService,
) {
- this.cache = new KVCache<Instance>(1000 * 60 * 60);
+ this.cache = new MemoryKVCache<Instance>(1000 * 60 * 60);
}
@bindThis
diff --git a/packages/backend/src/core/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts
index ef87051a74..898fb4ce85 100644
--- a/packages/backend/src/core/InstanceActorService.ts
+++ b/packages/backend/src/core/InstanceActorService.ts
@@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { IsNull } from 'typeorm';
import type { LocalUser } from '@/models/entities/User.js';
import type { UsersRepository } from '@/models/index.js';
-import { KVCache } from '@/misc/cache.js';
+import { MemoryCache } from '@/misc/cache.js';
import { DI } from '@/di-symbols.js';
import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
import { bindThis } from '@/decorators.js';
@@ -11,7 +11,7 @@ const ACTOR_USERNAME = 'instance.actor' as const;
@Injectable()
export class InstanceActorService {
- private cache: KVCache<LocalUser>;
+ private cache: MemoryCache<LocalUser>;
constructor(
@Inject(DI.usersRepository)
@@ -19,12 +19,12 @@ export class InstanceActorService {
private createSystemUserService: CreateSystemUserService,
) {
- this.cache = new KVCache<LocalUser>(Infinity);
+ this.cache = new MemoryCache<LocalUser>(Infinity);
}
@bindThis
public async getInstanceActor(): Promise<LocalUser> {
- const cached = this.cache.get(null);
+ const cached = this.cache.get();
if (cached) return cached;
const user = await this.usersRepository.findOneBy({
@@ -33,11 +33,11 @@ export class InstanceActorService {
}) as LocalUser | undefined;
if (user) {
- this.cache.set(null, user);
+ this.cache.set(user);
return user;
} else {
const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME) as LocalUser;
- this.cache.set(null, created);
+ this.cache.set(created);
return created;
}
}
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 7af7099432..83290b310e 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -20,7 +20,7 @@ import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js
import { checkWordMute } from '@/misc/check-word-mute.js';
import type { Channel } from '@/models/entities/Channel.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
-import { KVCache } from '@/misc/cache.js';
+import { MemoryCache } from '@/misc/cache.js';
import type { UserProfile } from '@/models/entities/UserProfile.js';
import { RelayService } from '@/core/RelayService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
@@ -47,7 +47,7 @@ import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import { RoleService } from '@/core/RoleService.js';
import { MetaService } from '@/core/MetaService.js';
-const mutedWordsCache = new KVCache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
+const mutedWordsCache = new MemoryCache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@@ -473,7 +473,7 @@ export class NoteCreateService implements OnApplicationShutdown {
this.incNotesCountOfUser(user);
// Word mute
- mutedWordsCache.fetch(null, () => this.userProfilesRepository.find({
+ mutedWordsCache.fetch(() => this.userProfilesRepository.find({
where: {
enableWordMute: true,
},
diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts
index 1bf0eb918f..7c6808fbd0 100644
--- a/packages/backend/src/core/NoteReadService.ts
+++ b/packages/backend/src/core/NoteReadService.ts
@@ -169,10 +169,6 @@ export class NoteReadService implements OnApplicationShutdown {
this.globalEventService.publishMainStream(userId, 'readAllChannels');
}
});
-
- this.notificationService.readNotificationByQuery(userId, {
- noteId: In([...readMentions.map(n => n.id), ...readSpecifiedNotes.map(n => n.id)]),
- });
}
}
diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts
index 48f2c65847..9c179f9318 100644
--- a/packages/backend/src/core/NotificationService.ts
+++ b/packages/backend/src/core/NotificationService.ts
@@ -1,8 +1,9 @@
import { setTimeout } from 'node:timers/promises';
+import Redis from 'ioredis';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
-import type { MutingsRepository, NotificationsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
+import type { MutingsRepository, UserProfile, UserProfilesRepository, UsersRepository } from '@/models/index.js';
import type { User } from '@/models/entities/User.js';
import type { Notification } from '@/models/entities/Notification.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
@@ -11,21 +12,22 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
import { PushNotificationService } from '@/core/PushNotificationService.js';
import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
import { IdService } from '@/core/IdService.js';
+import { CacheService } from '@/core/CacheService.js';
@Injectable()
export class NotificationService implements OnApplicationShutdown {
#shutdownController = new AbortController();
constructor(
+ @Inject(DI.redis)
+ private redisClient: Redis.Redis,
+
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
- @Inject(DI.notificationsRepository)
- private notificationsRepository: NotificationsRepository,
-
@Inject(DI.mutingsRepository)
private mutingsRepository: MutingsRepository,
@@ -34,54 +36,35 @@ export class NotificationService implements OnApplicationShutdown {
private idService: IdService,
private globalEventService: GlobalEventService,
private pushNotificationService: PushNotificationService,
+ private cacheService: CacheService,
) {
}
@bindThis
- public async readNotification(
+ public async readAllNotification(
userId: User['id'],
- notificationIds: Notification['id'][],
) {
- if (notificationIds.length === 0) return;
-
- // Update documents
- const result = await this.notificationsRepository.update({
- notifieeId: userId,
- id: In(notificationIds),
- isRead: false,
- }, {
- isRead: true,
- });
+ const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${userId}`);
+
+ const latestNotificationIdsRes = await this.redisClient.xrevrange(
+ `notificationTimeline:${userId}`,
+ '+',
+ '-',
+ 'COUNT', 1);
+ const latestNotificationId = latestNotificationIdsRes[0]?.[0];
- if (result.affected === 0) return;
-
- if (!await this.userEntityService.getHasUnreadNotification(userId)) return this.postReadAllNotifications(userId);
- else return this.postReadNotifications(userId, notificationIds);
- }
+ if (latestNotificationId == null) return;
- @bindThis
- public async readNotificationByQuery(
- userId: User['id'],
- query: Record<string, any>,
- ) {
- const notificationIds = await this.notificationsRepository.findBy({
- ...query,
- notifieeId: userId,
- isRead: false,
- }).then(notifications => notifications.map(notification => notification.id));
+ this.redisClient.set(`latestReadNotification:${userId}`, latestNotificationId);
- return this.readNotification(userId, notificationIds);
+ if (latestReadNotificationId == null || (latestReadNotificationId < latestNotificationId)) {
+ return this.postReadAllNotifications(userId);
+ }
}
@bindThis
private postReadAllNotifications(userId: User['id']) {
this.globalEventService.publishMainStream(userId, 'readAllNotifications');
- return this.pushNotificationService.pushNotification(userId, 'readAllNotifications', undefined);
- }
-
- @bindThis
- private postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) {
- return this.pushNotificationService.pushNotification(userId, 'readNotifications', { notificationIds });
}
@bindThis
@@ -90,45 +73,43 @@ export class NotificationService implements OnApplicationShutdown {
type: Notification['type'],
data: Partial<Notification>,
): Promise<Notification | null> {
- if (data.notifierId && (notifieeId === data.notifierId)) {
- return null;
- }
+ const profile = await this.cacheService.userProfileCache.fetch(notifieeId, () => this.userProfilesRepository.findOneByOrFail({ userId: notifieeId }));
+ const isMuted = profile.mutingNotificationTypes.includes(type);
+ if (isMuted) return null;
- const profile = await this.userProfilesRepository.findOneBy({ userId: notifieeId });
+ if (data.notifierId) {
+ if (notifieeId === data.notifierId) {
+ return null;
+ }
- const isMuted = profile?.mutingNotificationTypes.includes(type);
+ const mutings = await this.cacheService.userMutingsCache.fetch(notifieeId, () => this.mutingsRepository.findBy({ muterId: notifieeId }).then(xs => xs.map(x => x.muteeId)));
+ if (mutings.includes(data.notifierId)) {
+ return null;
+ }
+ }
- // Create notification
- const notification = await this.notificationsRepository.insert({
+ const notification = {
id: this.idService.genId(),
createdAt: new Date(),
- notifieeId: notifieeId,
type: type,
- // 相手がこの通知をミュートしているようなら、既読を予めつけておく
- isRead: isMuted,
...data,
- } as Partial<Notification>)
- .then(x => this.notificationsRepository.findOneByOrFail(x.identifiers[0]));
+ } as Notification;
+
+ this.redisClient.xadd(
+ `notificationTimeline:${notifieeId}`,
+ 'MAXLEN', '~', '300',
+ `${this.idService.parse(notification.id).date.getTime()}-*`,
+ 'data', JSON.stringify(notification));
- const packed = await this.notificationEntityService.pack(notification, {});
+ const packed = await this.notificationEntityService.pack(notification, notifieeId, {});
// Publish notification event
this.globalEventService.publishMainStream(notifieeId, 'notification', packed);
// 2秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する
- setTimeout(2000, 'unread note', { signal: this.#shutdownController.signal }).then(async () => {
- const fresh = await this.notificationsRepository.findOneBy({ id: notification.id });
- if (fresh == null) return; // 既に削除されているかもしれない
- if (fresh.isRead) return;
-
- //#region ただしミュートしているユーザーからの通知なら無視
- const mutings = await this.mutingsRepository.findBy({
- muterId: notifieeId,
- });
- if (data.notifierId && mutings.map(m => m.muteeId).includes(data.notifierId)) {
- return;
- }
- //#endregion
+ setTimeout(2000, 'unread notification', { signal: this.#shutdownController.signal }).then(async () => {
+ const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${notifieeId}`);
+ if (latestReadNotificationId && (latestReadNotificationId >= notification.id)) return;
this.globalEventService.publishMainStream(notifieeId, 'unreadNotification', packed);
this.pushNotificationService.pushNotification(notifieeId, 'notification', packed);
diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts
index 32c38ad480..69020f7e84 100644
--- a/packages/backend/src/core/PushNotificationService.ts
+++ b/packages/backend/src/core/PushNotificationService.ts
@@ -15,10 +15,6 @@ type PushNotificationsTypes = {
antenna: { id: string, name: string };
note: Packed<'Note'>;
};
- 'readNotifications': { notificationIds: string[] };
- 'readAllNotifications': undefined;
- 'readAntenna': { antennaId: string };
- 'readAllAntennas': undefined;
};
// Reduce length because push message servers have character limits
@@ -72,14 +68,6 @@ export class PushNotificationService {
});
for (const subscription of subscriptions) {
- // Continue if sendReadMessage is false
- if ([
- 'readNotifications',
- 'readAllNotifications',
- 'readAntenna',
- 'readAllAntennas',
- ].includes(type) && !subscription.sendReadMessage) continue;
-
const pushSubscription = {
endpoint: subscription.endpoint,
keys: {
diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts
index 4537f1b81a..4df7fb3bff 100644
--- a/packages/backend/src/core/RelayService.ts
+++ b/packages/backend/src/core/RelayService.ts
@@ -3,7 +3,7 @@ import { IsNull } from 'typeorm';
import type { LocalUser, User } from '@/models/entities/User.js';
import type { RelaysRepository, UsersRepository } from '@/models/index.js';
import { IdService } from '@/core/IdService.js';
-import { KVCache } from '@/misc/cache.js';
+import { MemoryCache } from '@/misc/cache.js';
import type { Relay } from '@/models/entities/Relay.js';
import { QueueService } from '@/core/QueueService.js';
import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
@@ -16,7 +16,7 @@ const ACTOR_USERNAME = 'relay.actor' as const;
@Injectable()
export class RelayService {
- private relaysCache: KVCache<Relay[]>;
+ private relaysCache: MemoryCache<Relay[]>;
constructor(
@Inject(DI.usersRepository)
@@ -30,7 +30,7 @@ export class RelayService {
private createSystemUserService: CreateSystemUserService,
private apRendererService: ApRendererService,
) {
- this.relaysCache = new KVCache<Relay[]>(1000 * 60 * 10);
+ this.relaysCache = new MemoryCache<Relay[]>(1000 * 60 * 10);
}
@bindThis
@@ -109,7 +109,7 @@ export class RelayService {
public async deliverToRelays(user: { id: User['id']; host: null; }, activity: any): Promise<void> {
if (activity == null) return;
- const relays = await this.relaysCache.fetch(null, () => this.relaysRepository.findBy({
+ const relays = await this.relaysCache.fetch(() => this.relaysRepository.findBy({
status: 'accepted',
}));
if (relays.length === 0) return;
diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts
index 7b63e43cb1..52e6292a1e 100644
--- a/packages/backend/src/core/RoleService.ts
+++ b/packages/backend/src/core/RoleService.ts
@@ -2,12 +2,12 @@ import { Inject, Injectable } from '@nestjs/common';
import Redis from 'ioredis';
import { In } from 'typeorm';
import type { Role, RoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/index.js';
-import { KVCache } from '@/misc/cache.js';
+import { MemoryKVCache, MemoryCache } from '@/misc/cache.js';
import type { User } from '@/models/entities/User.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js';
-import { UserCacheService } from '@/core/UserCacheService.js';
+import { CacheService } from '@/core/CacheService.js';
import type { RoleCondFormulaValue } from '@/models/entities/Role.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { StreamMessages } from '@/server/api/stream/types.js';
@@ -57,8 +57,8 @@ export const DEFAULT_POLICIES: RolePolicies = {
@Injectable()
export class RoleService implements OnApplicationShutdown {
- private rolesCache: KVCache<Role[]>;
- private roleAssignmentByUserIdCache: KVCache<RoleAssignment[]>;
+ private rolesCache: MemoryCache<Role[]>;
+ private roleAssignmentByUserIdCache: MemoryKVCache<RoleAssignment[]>;
public static AlreadyAssignedError = class extends Error {};
public static NotAssignedError = class extends Error {};
@@ -77,15 +77,15 @@ export class RoleService implements OnApplicationShutdown {
private roleAssignmentsRepository: RoleAssignmentsRepository,
private metaService: MetaService,
- private userCacheService: UserCacheService,
+ private cacheService: CacheService,
private userEntityService: UserEntityService,
private globalEventService: GlobalEventService,
private idService: IdService,
) {
//this.onMessage = this.onMessage.bind(this);
- this.rolesCache = new KVCache<Role[]>(Infinity);
- this.roleAssignmentByUserIdCache = new KVCache<RoleAssignment[]>(Infinity);
+ this.rolesCache = new MemoryCache<Role[]>(Infinity);
+ this.roleAssignmentByUserIdCache = new MemoryKVCache<RoleAssignment[]>(Infinity);
this.redisSubscriber.on('message', this.onMessage);
}
@@ -98,7 +98,7 @@ export class RoleService implements OnApplicationShutdown {
const { type, body } = obj.message as StreamMessages['internal']['payload'];
switch (type) {
case 'roleCreated': {
- const cached = this.rolesCache.get(null);
+ const cached = this.rolesCache.get();
if (cached) {
cached.push({
...body,
@@ -110,7 +110,7 @@ export class RoleService implements OnApplicationShutdown {
break;
}
case 'roleUpdated': {
- const cached = this.rolesCache.get(null);
+ const cached = this.rolesCache.get();
if (cached) {
const i = cached.findIndex(x => x.id === body.id);
if (i > -1) {
@@ -125,9 +125,9 @@ export class RoleService implements OnApplicationShutdown {
break;
}
case 'roleDeleted': {
- const cached = this.rolesCache.get(null);
+ const cached = this.rolesCache.get();
if (cached) {
- this.rolesCache.set(null, cached.filter(x => x.id !== body.id));
+ this.rolesCache.set(cached.filter(x => x.id !== body.id));
}
break;
}
@@ -214,9 +214,9 @@ export class RoleService implements OnApplicationShutdown {
// 期限切れのロールを除外
assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now));
const assignedRoleIds = assigns.map(x => x.roleId);
- const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
+ const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
const assignedRoles = roles.filter(r => assignedRoleIds.includes(r.id));
- const user = roles.some(r => r.target === 'conditional') ? await this.userCacheService.findById(userId) : null;
+ const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, r.condFormula));
return [...assignedRoles, ...matchedCondRoles];
}
@@ -231,11 +231,11 @@ export class RoleService implements OnApplicationShutdown {
// 期限切れのロールを除外
assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now));
const assignedRoleIds = assigns.map(x => x.roleId);
- const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
+ const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
const assignedBadgeRoles = roles.filter(r => r.asBadge && assignedRoleIds.includes(r.id));
const badgeCondRoles = roles.filter(r => r.asBadge && (r.target === 'conditional'));
if (badgeCondRoles.length > 0) {
- const user = roles.some(r => r.target === 'conditional') ? await this.userCacheService.findById(userId) : null;
+ const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
const matchedBadgeCondRoles = badgeCondRoles.filter(r => this.evalCond(user!, r.condFormula));
return [...assignedBadgeRoles, ...matchedBadgeCondRoles];
} else {
@@ -301,7 +301,7 @@ export class RoleService implements OnApplicationShutdown {
@bindThis
public async getModeratorIds(includeAdmins = true): Promise<User['id'][]> {
- const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
+ const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
const moderatorRoles = includeAdmins ? roles.filter(r => r.isModerator || r.isAdministrator) : roles.filter(r => r.isModerator);
const assigns = moderatorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({
roleId: In(moderatorRoles.map(r => r.id)),
@@ -321,7 +321,7 @@ export class RoleService implements OnApplicationShutdown {
@bindThis
public async getAdministratorIds(): Promise<User['id'][]> {
- const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
+ const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
const administratorRoles = roles.filter(r => r.isAdministrator);
const assigns = administratorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({
roleId: In(administratorRoles.map(r => r.id)),
diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts
index 33b51537a6..040b6de2ef 100644
--- a/packages/backend/src/core/UserBlockingService.ts
+++ b/packages/backend/src/core/UserBlockingService.ts
@@ -15,7 +15,7 @@ import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { LoggerService } from '@/core/LoggerService.js';
import { WebhookService } from '@/core/WebhookService.js';
import { bindThis } from '@/decorators.js';
-import { KVCache } from '@/misc/cache.js';
+import { MemoryKVCache } from '@/misc/cache.js';
import { StreamMessages } from '@/server/api/stream/types.js';
@Injectable()
@@ -23,7 +23,7 @@ export class UserBlockingService implements OnApplicationShutdown {
private logger: Logger;
// キーがユーザーIDで、値がそのユーザーがブロックしているユーザーのIDのリストなキャッシュ
- private blockingsByUserIdCache: KVCache<User['id'][]>;
+ private blockingsByUserIdCache: MemoryKVCache<User['id'][]>;
constructor(
@Inject(DI.redisSubscriber)
@@ -58,7 +58,7 @@ export class UserBlockingService implements OnApplicationShutdown {
) {
this.logger = this.loggerService.getLogger('user-block');
- this.blockingsByUserIdCache = new KVCache<User['id'][]>(Infinity);
+ this.blockingsByUserIdCache = new MemoryKVCache<User['id'][]>(Infinity);
this.redisSubscriber.on('message', this.onMessage);
}
diff --git a/packages/backend/src/core/UserKeypairStoreService.ts b/packages/backend/src/core/UserKeypairStoreService.ts
index 61c9293f86..872a0335ea 100644
--- a/packages/backend/src/core/UserKeypairStoreService.ts
+++ b/packages/backend/src/core/UserKeypairStoreService.ts
@@ -1,20 +1,20 @@
import { Inject, Injectable } from '@nestjs/common';
import type { User } from '@/models/entities/User.js';
import type { UserKeypairsRepository } from '@/models/index.js';
-import { KVCache } from '@/misc/cache.js';
+import { MemoryKVCache } from '@/misc/cache.js';
import type { UserKeypair } from '@/models/entities/UserKeypair.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
@Injectable()
export class UserKeypairStoreService {
- private cache: KVCache<UserKeypair>;
+ private cache: MemoryKVCache<UserKeypair>;
constructor(
@Inject(DI.userKeypairsRepository)
private userKeypairsRepository: UserKeypairsRepository,
) {
- this.cache = new KVCache<UserKeypair>(Infinity);
+ this.cache = new MemoryKVCache<UserKeypair>(Infinity);
}
@bindThis
diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts
index c3b3875613..4b032be89a 100644
--- a/packages/backend/src/core/activitypub/ApDbResolverService.ts
+++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts
@@ -3,9 +3,9 @@ import escapeRegexp from 'escape-regexp';
import { DI } from '@/di-symbols.js';
import type { NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
-import { KVCache } from '@/misc/cache.js';
+import { MemoryKVCache } from '@/misc/cache.js';
import type { UserPublickey } from '@/models/entities/UserPublickey.js';
-import { UserCacheService } from '@/core/UserCacheService.js';
+import { CacheService } from '@/core/CacheService.js';
import type { Note } from '@/models/entities/Note.js';
import { bindThis } from '@/decorators.js';
import { RemoteUser, User } from '@/models/entities/User.js';
@@ -31,8 +31,8 @@ export type UriParseResult = {
@Injectable()
export class ApDbResolverService {
- private publicKeyCache: KVCache<UserPublickey | null>;
- private publicKeyByUserIdCache: KVCache<UserPublickey | null>;
+ private publicKeyCache: MemoryKVCache<UserPublickey | null>;
+ private publicKeyByUserIdCache: MemoryKVCache<UserPublickey | null>;
constructor(
@Inject(DI.config)
@@ -47,11 +47,11 @@ export class ApDbResolverService {
@Inject(DI.userPublickeysRepository)
private userPublickeysRepository: UserPublickeysRepository,
- private userCacheService: UserCacheService,
+ private cacheService: CacheService,
private apPersonService: ApPersonService,
) {
- this.publicKeyCache = new KVCache<UserPublickey | null>(Infinity);
- this.publicKeyByUserIdCache = new KVCache<UserPublickey | null>(Infinity);
+ this.publicKeyCache = new MemoryKVCache<UserPublickey | null>(Infinity);
+ this.publicKeyByUserIdCache = new MemoryKVCache<UserPublickey | null>(Infinity);
}
@bindThis
@@ -107,11 +107,11 @@ export class ApDbResolverService {
if (parsed.local) {
if (parsed.type !== 'users') return null;
- return await this.userCacheService.userByIdCache.fetchMaybe(parsed.id, () => this.usersRepository.findOneBy({
+ return await this.cacheService.userByIdCache.fetchMaybe(parsed.id, () => this.usersRepository.findOneBy({
id: parsed.id,
}).then(x => x ?? undefined)) ?? null;
} else {
- return await this.userCacheService.uriPersonCache.fetch(parsed.uri, () => this.usersRepository.findOneBy({
+ return await this.cacheService.uriPersonCache.fetch(parsed.uri, () => this.usersRepository.findOneBy({
uri: parsed.uri,
}));
}
@@ -138,7 +138,7 @@ export class ApDbResolverService {
if (key == null) return null;
return {
- user: await this.userCacheService.findById(key.userId) as RemoteUser,
+ user: await this.cacheService.findUserById(key.userId) as RemoteUser,
key,
};
}
diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts
index 41f7eafa41..67e907c271 100644
--- a/packages/backend/src/core/activitypub/models/ApPersonService.ts
+++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts
@@ -8,7 +8,7 @@ import type { Config } from '@/config.js';
import type { RemoteUser } from '@/models/entities/User.js';
import { User } from '@/models/entities/User.js';
import { truncate } from '@/misc/truncate.js';
-import type { UserCacheService } from '@/core/UserCacheService.js';
+import type { CacheService } from '@/core/CacheService.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
import type Logger from '@/logger.js';
@@ -54,7 +54,7 @@ export class ApPersonService implements OnModuleInit {
private metaService: MetaService;
private federatedInstanceService: FederatedInstanceService;
private fetchInstanceMetadataService: FetchInstanceMetadataService;
- private userCacheService: UserCacheService;
+ private cacheService: CacheService;
private apResolverService: ApResolverService;
private apNoteService: ApNoteService;
private apImageService: ApImageService;
@@ -97,7 +97,7 @@ export class ApPersonService implements OnModuleInit {
//private metaService: MetaService,
//private federatedInstanceService: FederatedInstanceService,
//private fetchInstanceMetadataService: FetchInstanceMetadataService,
- //private userCacheService: UserCacheService,
+ //private cacheService: CacheService,
//private apResolverService: ApResolverService,
//private apNoteService: ApNoteService,
//private apImageService: ApImageService,
@@ -118,7 +118,7 @@ export class ApPersonService implements OnModuleInit {
this.metaService = this.moduleRef.get('MetaService');
this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService');
this.fetchInstanceMetadataService = this.moduleRef.get('FetchInstanceMetadataService');
- this.userCacheService = this.moduleRef.get('UserCacheService');
+ this.cacheService = this.moduleRef.get('CacheService');
this.apResolverService = this.moduleRef.get('ApResolverService');
this.apNoteService = this.moduleRef.get('ApNoteService');
this.apImageService = this.moduleRef.get('ApImageService');
@@ -207,14 +207,14 @@ export class ApPersonService implements OnModuleInit {
public async fetchPerson(uri: string, resolver?: Resolver): Promise<User | null> {
if (typeof uri !== 'string') throw new Error('uri is not string');
- const cached = this.userCacheService.uriPersonCache.get(uri);
+ const cached = this.cacheService.uriPersonCache.get(uri);
if (cached) return cached;
// URIがこのサーバーを指しているならデータベースからフェッチ
if (uri.startsWith(this.config.url + '/')) {
const id = uri.split('/').pop();
const u = await this.usersRepository.findOneBy({ id });
- if (u) this.userCacheService.uriPersonCache.set(uri, u);
+ if (u) this.cacheService.uriPersonCache.set(uri, u);
return u;
}
@@ -222,7 +222,7 @@ export class ApPersonService implements OnModuleInit {
const exist = await this.usersRepository.findOneBy({ uri });
if (exist) {
- this.userCacheService.uriPersonCache.set(uri, exist);
+ this.cacheService.uriPersonCache.set(uri, exist);
return exist;
}
//#endregion
diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts
index 70e56cb3d7..6b9a9d3320 100644
--- a/packages/backend/src/core/entities/NotificationEntityService.ts
+++ b/packages/backend/src/core/entities/NotificationEntityService.ts
@@ -1,7 +1,8 @@
import { Inject, Injectable } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
+import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
-import type { AccessTokensRepository, NoteReactionsRepository, NotificationsRepository, User } from '@/models/index.js';
+import type { AccessTokensRepository, NoteReactionsRepository, NotesRepository, User, UsersRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Notification } from '@/models/entities/Notification.js';
import type { Note } from '@/models/entities/Note.js';
@@ -25,8 +26,11 @@ export class NotificationEntityService implements OnModuleInit {
constructor(
private moduleRef: ModuleRef,
- @Inject(DI.notificationsRepository)
- private notificationsRepository: NotificationsRepository,
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
+
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
@Inject(DI.noteReactionsRepository)
private noteReactionsRepository: NoteReactionsRepository,
@@ -48,30 +52,40 @@ export class NotificationEntityService implements OnModuleInit {
@bindThis
public async pack(
- src: Notification['id'] | Notification,
+ src: Notification,
+ meId: User['id'],
+ // eslint-disable-next-line @typescript-eslint/ban-types
options: {
- _hint_?: {
- packedNotes: Map<Note['id'], Packed<'Note'>>;
- };
+
+ },
+ hint?: {
+ packedNotes: Map<Note['id'], Packed<'Note'>>;
+ packedUsers: Map<User['id'], Packed<'User'>>;
},
): Promise<Packed<'Notification'>> {
- const notification = typeof src === 'object' ? src : await this.notificationsRepository.findOneByOrFail({ id: src });
+ const notification = src;
const token = notification.appAccessTokenId ? await this.accessTokensRepository.findOneByOrFail({ id: notification.appAccessTokenId }) : null;
const noteIfNeed = NOTE_REQUIRED_NOTIFICATION_TYPES.has(notification.type) && notification.noteId != null ? (
- options._hint_?.packedNotes != null
- ? options._hint_.packedNotes.get(notification.noteId)
- : this.noteEntityService.pack(notification.note ?? notification.noteId!, { id: notification.notifieeId }, {
+ hint?.packedNotes != null
+ ? hint.packedNotes.get(notification.noteId)
+ : this.noteEntityService.pack(notification.noteId!, { id: meId }, {
detail: true,
})
) : undefined;
+ const userIfNeed = notification.notifierId != null ? (
+ hint?.packedUsers != null
+ ? hint.packedUsers.get(notification.notifierId)
+ : this.userEntityService.pack(notification.notifierId!, { id: meId }, {
+ detail: false,
+ })
+ ) : undefined;
return await awaitAll({
id: notification.id,
- createdAt: notification.createdAt.toISOString(),
+ createdAt: new Date(notification.createdAt).toISOString(),
type: notification.type,
- isRead: notification.isRead,
userId: notification.notifierId,
- user: notification.notifierId ? this.userEntityService.pack(notification.notifier ?? notification.notifierId) : null,
+ ...(userIfNeed != null ? { user: userIfNeed } : {}),
...(noteIfNeed != null ? { note: noteIfNeed } : {}),
...(notification.type === 'reaction' ? {
reaction: notification.reaction,
@@ -87,33 +101,36 @@ export class NotificationEntityService implements OnModuleInit {
});
}
- /**
- * @param notifications you should join "note" property when fetch from DB, and all notifieeId should be same as meId
- */
@bindThis
public async packMany(
notifications: Notification[],
meId: User['id'],
) {
if (notifications.length === 0) return [];
-
- for (const notification of notifications) {
- if (meId !== notification.notifieeId) {
- // because we call note packMany with meId, all notifieeId should be same as meId
- throw new Error('TRY_TO_PACK_ANOTHER_USER_NOTIFICATION');
- }
- }
- const notes = notifications.map(x => x.note).filter(isNotNull);
+ const noteIds = notifications.map(x => x.noteId).filter(isNotNull);
+ const notes = noteIds.length > 0 ? await this.notesRepository.find({
+ where: { id: In(noteIds) },
+ relations: ['user', 'user.avatar', 'user.banner', 'reply', 'reply.user', 'reply.user.avatar', 'reply.user.banner', 'renote', 'renote.user', 'renote.user.avatar', 'renote.user.banner'],
+ }) : [];
const packedNotesArray = await this.noteEntityService.packMany(notes, { id: meId }, {
detail: true,
});
const packedNotes = new Map(packedNotesArray.map(p => [p.id, p]));
- return await Promise.all(notifications.map(x => this.pack(x, {
- _hint_: {
- packedNotes,
- },
+ const userIds = notifications.map(x => x.notifierId).filter(isNotNull);
+ const users = userIds.length > 0 ? await this.usersRepository.find({
+ where: { id: In(userIds) },
+ relations: ['avatar', 'banner'],
+ }) : [];
+ const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }, {
+ detail: false,
+ });
+ const packedUsers = new Map(packedUsersArray.map(p => [p.id, p]));
+
+ return await Promise.all(notifications.map(x => this.pack(x, meId, {}, {
+ packedNotes,
+ packedUsers,
})));
}
}
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index 61fd6f2f66..e8474c7e0e 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -1,5 +1,6 @@
import { Inject, Injectable } from '@nestjs/common';
import { In, Not } from 'typeorm';
+import Redis from 'ioredis';
import Ajv from 'ajv';
import { ModuleRef } from '@nestjs/core';
import { DI } from '@/di-symbols.js';
@@ -8,11 +9,11 @@ import type { Packed } from '@/misc/json-schema.js';
import type { Promiseable } from '@/misc/prelude/await-all.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js';
-import { KVCache } from '@/misc/cache.js';
+import { MemoryKVCache } from '@/misc/cache.js';
import type { Instance } from '@/models/entities/Instance.js';
import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js';
-import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, AnnouncementsRepository, PagesRepository, UserProfile, RenoteMutingsRepository } from '@/models/index.js';
+import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, AnnouncementsRepository, PagesRepository, UserProfile, RenoteMutingsRepository } from '@/models/index.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import type { OnModuleInit } from '@nestjs/common';
@@ -52,7 +53,7 @@ export class UserEntityService implements OnModuleInit {
private customEmojiService: CustomEmojiService;
private antennaService: AntennaService;
private roleService: RoleService;
- private userInstanceCache: KVCache<Instance | null>;
+ private userInstanceCache: MemoryKVCache<Instance | null>;
constructor(
private moduleRef: ModuleRef,
@@ -60,6 +61,9 @@ export class UserEntityService implements OnModuleInit {
@Inject(DI.config)
private config: Config,
+ @Inject(DI.redis)
+ private redisClient: Redis.Redis,
+
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@@ -90,9 +94,6 @@ export class UserEntityService implements OnModuleInit {
@Inject(DI.channelFollowingsRepository)
private channelFollowingsRepository: ChannelFollowingsRepository,
- @Inject(DI.notificationsRepository)
- private notificationsRepository: NotificationsRepository,
-
@Inject(DI.userNotePiningsRepository)
private userNotePiningsRepository: UserNotePiningsRepository,
@@ -118,7 +119,7 @@ export class UserEntityService implements OnModuleInit {
//private antennaService: AntennaService,
//private roleService: RoleService,
) {
- this.userInstanceCache = new KVCache<Instance | null>(1000 * 60 * 60 * 3);
+ this.userInstanceCache = new MemoryKVCache<Instance | null>(1000 * 60 * 60 * 3);
}
onModuleInit() {
@@ -247,21 +248,16 @@ export class UserEntityService implements OnModuleInit {
@bindThis
public async getHasUnreadNotification(userId: User['id']): Promise<boolean> {
- const mute = await this.mutingsRepository.findBy({
- muterId: userId,
- });
- const mutedUserIds = mute.map(m => m.muteeId);
+ const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${userId}`);
+
+ const latestNotificationIdsRes = await this.redisClient.xrevrange(
+ `notificationTimeline:${userId}`,
+ '+',
+ '-',
+ 'COUNT', 1);
+ const latestNotificationId = latestNotificationIdsRes[0]?.[0];
- const count = await this.notificationsRepository.count({
- where: {
- notifieeId: userId,
- ...(mutedUserIds.length > 0 ? { notifierId: Not(In(mutedUserIds)) } : {}),
- isRead: false,
- },
- take: 1,
- });
-
- return count > 0;
+ return latestNotificationId != null && (latestReadNotificationId == null || latestReadNotificationId < latestNotificationId);
}
@bindThis