summaryrefslogtreecommitdiff
path: root/packages/backend/src
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
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')
-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
-rw-r--r--packages/backend/src/di-symbols.ts1
-rw-r--r--packages/backend/src/misc/cache.ts105
-rw-r--r--packages/backend/src/models/RepositoryModule.ts10
-rw-r--r--packages/backend/src/models/entities/Notification.ts134
-rw-r--r--packages/backend/src/models/index.ts3
-rw-r--r--packages/backend/src/models/json-schema/notification.ts4
-rw-r--r--packages/backend/src/postgres.ts2
-rw-r--r--packages/backend/src/queue/processors/CleanProcessorService.ts9
-rw-r--r--packages/backend/src/queue/processors/DeliverProcessorService.ts10
-rw-r--r--packages/backend/src/server/NodeinfoServerService.ts8
-rw-r--r--packages/backend/src/server/api/AuthenticateService.ts14
-rw-r--r--packages/backend/src/server/api/EndpointsModule.ts4
-rw-r--r--packages/backend/src/server/api/endpoints.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/suspend-user.ts16
-rw-r--r--packages/backend/src/server/api/endpoints/i/notifications.ts106
-rw-r--r--packages/backend/src/server/api/endpoints/i/regenerate-token.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/i/update.ts8
-rw-r--r--packages/backend/src/server/api/endpoints/mute/create.ts3
-rw-r--r--packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts22
-rw-r--r--packages/backend/src/server/api/endpoints/notifications/read.ts57
-rw-r--r--packages/backend/src/server/api/endpoints/renote-mute/create.ts2
-rw-r--r--packages/backend/src/server/api/stream/index.ts3
-rw-r--r--packages/backend/src/server/api/stream/types.ts2
40 files changed, 370 insertions, 558 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
diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts
index f2ab6cb864..56ce755a1a 100644
--- a/packages/backend/src/di-symbols.ts
+++ b/packages/backend/src/di-symbols.ts
@@ -33,7 +33,6 @@ export const DI = {
emojisRepository: Symbol('emojisRepository'),
driveFilesRepository: Symbol('driveFilesRepository'),
driveFoldersRepository: Symbol('driveFoldersRepository'),
- notificationsRepository: Symbol('notificationsRepository'),
metasRepository: Symbol('metasRepository'),
mutingsRepository: Symbol('mutingsRepository'),
renoteMutingsRepository: Symbol('renoteMutingsRepository'),
diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts
index b249cf4480..870dfd237c 100644
--- a/packages/backend/src/misc/cache.ts
+++ b/packages/backend/src/misc/cache.ts
@@ -1,18 +1,103 @@
+import Redis from 'ioredis';
import { bindThis } from '@/decorators.js';
+// redis通すとDateのインスタンスはstringに変換されるので
+type Serialized<T> = {
+ [K in keyof T]:
+ T[K] extends Date
+ ? string
+ : T[K] extends (Date | null)
+ ? (string | null)
+ : T[K] extends Record<string, any>
+ ? Serialized<T[K]>
+ : T[K];
+};
+
+export class RedisKVCache<T> {
+ private redisClient: Redis.Redis;
+ private name: string;
+ private lifetime: number;
+ private memoryCache: MemoryKVCache<T>;
+
+ constructor(redisClient: RedisKVCache<never>['redisClient'], name: RedisKVCache<never>['name'], lifetime: RedisKVCache<never>['lifetime'], memoryCacheLifetime: number) {
+ this.redisClient = redisClient;
+ this.name = name;
+ this.lifetime = lifetime;
+ this.memoryCache = new MemoryKVCache(memoryCacheLifetime);
+ }
+
+ @bindThis
+ public async set(key: string, value: T): Promise<void> {
+ this.memoryCache.set(key, value);
+ if (this.lifetime === Infinity) {
+ await this.redisClient.set(
+ `kvcache:${this.name}:${key}`,
+ JSON.stringify(value),
+ );
+ } else {
+ await this.redisClient.set(
+ `kvcache:${this.name}:${key}`,
+ JSON.stringify(value),
+ 'ex', Math.round(this.lifetime / 1000),
+ );
+ }
+ }
+
+ @bindThis
+ public async get(key: string): Promise<Serialized<T> | T | undefined> {
+ const memoryCached = this.memoryCache.get(key);
+ if (memoryCached !== undefined) return memoryCached;
+
+ const cached = await this.redisClient.get(`kvcache:${this.name}:${key}`);
+ if (cached == null) return undefined;
+ return JSON.parse(cached);
+ }
+
+ @bindThis
+ public async delete(key: string): Promise<void> {
+ this.memoryCache.delete(key);
+ await this.redisClient.del(`kvcache:${this.name}:${key}`);
+ }
+
+ /**
+ * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
+ * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
+ */
+ @bindThis
+ public async fetch(key: string, fetcher: () => Promise<T>, validator?: (cachedValue: Serialized<T> | T) => boolean): Promise<Serialized<T> | T> {
+ const cachedValue = await this.get(key);
+ if (cachedValue !== undefined) {
+ if (validator) {
+ if (validator(cachedValue)) {
+ // Cache HIT
+ return cachedValue;
+ }
+ } else {
+ // Cache HIT
+ return cachedValue;
+ }
+ }
+
+ // Cache MISS
+ const value = await fetcher();
+ this.set(key, value);
+ return value;
+ }
+}
+
// TODO: メモリ節約のためあまり参照されないキーを定期的に削除できるようにする?
-export class KVCache<T> {
- public cache: Map<string | null, { date: number; value: T; }>;
+export class MemoryKVCache<T> {
+ public cache: Map<string, { date: number; value: T; }>;
private lifetime: number;
- constructor(lifetime: KVCache<never>['lifetime']) {
+ constructor(lifetime: MemoryKVCache<never>['lifetime']) {
this.cache = new Map();
this.lifetime = lifetime;
}
@bindThis
- public set(key: string | null, value: T): void {
+ public set(key: string, value: T): void {
this.cache.set(key, {
date: Date.now(),
value,
@@ -20,7 +105,7 @@ export class KVCache<T> {
}
@bindThis
- public get(key: string | null): T | undefined {
+ public get(key: string): T | undefined {
const cached = this.cache.get(key);
if (cached == null) return undefined;
if ((Date.now() - cached.date) > this.lifetime) {
@@ -31,7 +116,7 @@ export class KVCache<T> {
}
@bindThis
- public delete(key: string | null) {
+ public delete(key: string) {
this.cache.delete(key);
}
@@ -40,7 +125,7 @@ export class KVCache<T> {
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
*/
@bindThis
- public async fetch(key: string | null, fetcher: () => Promise<T>, validator?: (cachedValue: T) => boolean): Promise<T> {
+ public async fetch(key: string, fetcher: () => Promise<T>, validator?: (cachedValue: T) => boolean): Promise<T> {
const cachedValue = this.get(key);
if (cachedValue !== undefined) {
if (validator) {
@@ -65,7 +150,7 @@ export class KVCache<T> {
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
*/
@bindThis
- public async fetchMaybe(key: string | null, fetcher: () => Promise<T | undefined>, validator?: (cachedValue: T) => boolean): Promise<T | undefined> {
+ public async fetchMaybe(key: string, fetcher: () => Promise<T | undefined>, validator?: (cachedValue: T) => boolean): Promise<T | undefined> {
const cachedValue = this.get(key);
if (cachedValue !== undefined) {
if (validator) {
@@ -88,12 +173,12 @@ export class KVCache<T> {
}
}
-export class Cache<T> {
+export class MemoryCache<T> {
private cachedAt: number | null = null;
private value: T | undefined;
private lifetime: number;
- constructor(lifetime: Cache<never>['lifetime']) {
+ constructor(lifetime: MemoryCache<never>['lifetime']) {
this.lifetime = lifetime;
}
diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts
index b74ee3689c..7be7b81904 100644
--- a/packages/backend/src/models/RepositoryModule.ts
+++ b/packages/backend/src/models/RepositoryModule.ts
@@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite } from './index.js';
+import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite } from './index.js';
import type { DataSource } from 'typeorm';
import type { Provider } from '@nestjs/common';
@@ -172,12 +172,6 @@ const $driveFoldersRepository: Provider = {
inject: [DI.db],
};
-const $notificationsRepository: Provider = {
- provide: DI.notificationsRepository,
- useFactory: (db: DataSource) => db.getRepository(Notification),
- inject: [DI.db],
-};
-
const $metasRepository: Provider = {
provide: DI.metasRepository,
useFactory: (db: DataSource) => db.getRepository(Meta),
@@ -426,7 +420,6 @@ const $roleAssignmentsRepository: Provider = {
$emojisRepository,
$driveFilesRepository,
$driveFoldersRepository,
- $notificationsRepository,
$metasRepository,
$mutingsRepository,
$renoteMutingsRepository,
@@ -493,7 +486,6 @@ const $roleAssignmentsRepository: Provider = {
$emojisRepository,
$driveFilesRepository,
$driveFoldersRepository,
- $notificationsRepository,
$metasRepository,
$mutingsRepository,
$renoteMutingsRepository,
diff --git a/packages/backend/src/models/entities/Notification.ts b/packages/backend/src/models/entities/Notification.ts
index 51117efba5..aa6f997124 100644
--- a/packages/backend/src/models/entities/Notification.ts
+++ b/packages/backend/src/models/entities/Notification.ts
@@ -1,54 +1,19 @@
-import { Entity, Index, JoinColumn, ManyToOne, Column, PrimaryColumn } from 'typeorm';
-import { notificationTypes, obsoleteNotificationTypes } from '@/types.js';
-import { id } from '../id.js';
+import { notificationTypes } from '@/types.js';
import { User } from './User.js';
import { Note } from './Note.js';
import { FollowRequest } from './FollowRequest.js';
import { AccessToken } from './AccessToken.js';
-@Entity()
-export class Notification {
- @PrimaryColumn(id())
- public id: string;
+export type Notification = {
+ id: string;
- @Index()
- @Column('timestamp with time zone', {
- comment: 'The created date of the Notification.',
- })
- public createdAt: Date;
-
- /**
- * 通知の受信者
- */
- @Index()
- @Column({
- ...id(),
- comment: 'The ID of recipient user of the Notification.',
- })
- public notifieeId: User['id'];
-
- @ManyToOne(type => User, {
- onDelete: 'CASCADE',
- })
- @JoinColumn()
- public notifiee: User | null;
+ // RedisのためDateではなくstring
+ createdAt: string;
/**
* 通知の送信者(initiator)
*/
- @Index()
- @Column({
- ...id(),
- nullable: true,
- comment: 'The ID of sender user of the Notification.',
- })
- public notifierId: User['id'] | null;
-
- @ManyToOne(type => User, {
- onDelete: 'CASCADE',
- })
- @JoinColumn()
- public notifier: User | null;
+ notifierId: User['id'] | null;
/**
* 通知の種類。
@@ -64,104 +29,37 @@ export class Notification {
* achievementEarned - 実績を獲得
* app - アプリ通知
*/
- @Index()
- @Column('enum', {
- enum: [
- ...notificationTypes,
- ...obsoleteNotificationTypes,
- ],
- comment: 'The type of the Notification.',
- })
- public type: typeof notificationTypes[number];
+ type: typeof notificationTypes[number];
- /**
- * 通知が読まれたかどうか
- */
- @Index()
- @Column('boolean', {
- default: false,
- comment: 'Whether the Notification is read.',
- })
- public isRead: boolean;
-
- @Column({
- ...id(),
- nullable: true,
- })
- public noteId: Note['id'] | null;
-
- @ManyToOne(type => Note, {
- onDelete: 'CASCADE',
- })
- @JoinColumn()
- public note: Note | null;
+ noteId: Note['id'] | null;
- @Column({
- ...id(),
- nullable: true,
- })
- public followRequestId: FollowRequest['id'] | null;
+ followRequestId: FollowRequest['id'] | null;
- @ManyToOne(type => FollowRequest, {
- onDelete: 'CASCADE',
- })
- @JoinColumn()
- public followRequest: FollowRequest | null;
+ reaction: string | null;
- @Column('varchar', {
- length: 128, nullable: true,
- })
- public reaction: string | null;
+ choice: number | null;
- @Column('integer', {
- nullable: true,
- })
- public choice: number | null;
-
- @Column('varchar', {
- length: 128, nullable: true,
- })
- public achievement: string | null;
+ achievement: string | null;
/**
* アプリ通知のbody
*/
- @Column('varchar', {
- length: 2048, nullable: true,
- })
- public customBody: string | null;
+ customBody: string | null;
/**
* アプリ通知のheader
* (省略時はアプリ名で表示されることを期待)
*/
- @Column('varchar', {
- length: 256, nullable: true,
- })
- public customHeader: string | null;
+ customHeader: string | null;
/**
* アプリ通知のicon(URL)
* (省略時はアプリアイコンで表示されることを期待)
*/
- @Column('varchar', {
- length: 1024, nullable: true,
- })
- public customIcon: string | null;
+ customIcon: string | null;
/**
* アプリ通知のアプリ(のトークン)
*/
- @Index()
- @Column({
- ...id(),
- nullable: true,
- })
- public appAccessTokenId: AccessToken['id'] | null;
-
- @ManyToOne(type => AccessToken, {
- onDelete: 'CASCADE',
- })
- @JoinColumn()
- public appAccessToken: AccessToken | null;
+ appAccessTokenId: AccessToken['id'] | null;
}
diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts
index c4c9717ed5..48d6e15f2a 100644
--- a/packages/backend/src/models/index.ts
+++ b/packages/backend/src/models/index.ts
@@ -32,7 +32,6 @@ import { NoteFavorite } from '@/models/entities/NoteFavorite.js';
import { NoteReaction } from '@/models/entities/NoteReaction.js';
import { NoteThreadMuting } from '@/models/entities/NoteThreadMuting.js';
import { NoteUnread } from '@/models/entities/NoteUnread.js';
-import { Notification } from '@/models/entities/Notification.js';
import { Page } from '@/models/entities/Page.js';
import { PageLike } from '@/models/entities/PageLike.js';
import { PasswordResetRequest } from '@/models/entities/PasswordResetRequest.js';
@@ -100,7 +99,6 @@ export {
NoteReaction,
NoteThreadMuting,
NoteUnread,
- Notification,
Page,
PageLike,
PasswordResetRequest,
@@ -167,7 +165,6 @@ export type NoteFavoritesRepository = Repository<NoteFavorite>;
export type NoteReactionsRepository = Repository<NoteReaction>;
export type NoteThreadMutingsRepository = Repository<NoteThreadMuting>;
export type NoteUnreadsRepository = Repository<NoteUnread>;
-export type NotificationsRepository = Repository<Notification>;
export type PagesRepository = Repository<Page>;
export type PageLikesRepository = Repository<PageLike>;
export type PasswordResetRequestsRepository = Repository<PasswordResetRequest>;
diff --git a/packages/backend/src/models/json-schema/notification.ts b/packages/backend/src/models/json-schema/notification.ts
index d3f2405cdd..e88ca61ba0 100644
--- a/packages/backend/src/models/json-schema/notification.ts
+++ b/packages/backend/src/models/json-schema/notification.ts
@@ -14,10 +14,6 @@ export const packedNotificationSchema = {
optional: false, nullable: false,
format: 'date-time',
},
- isRead: {
- type: 'boolean',
- optional: false, nullable: false,
- },
type: {
type: 'string',
optional: false, nullable: false,
diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts
index 024aa114fc..efeca46b49 100644
--- a/packages/backend/src/postgres.ts
+++ b/packages/backend/src/postgres.ts
@@ -40,7 +40,6 @@ import { NoteFavorite } from '@/models/entities/NoteFavorite.js';
import { NoteReaction } from '@/models/entities/NoteReaction.js';
import { NoteThreadMuting } from '@/models/entities/NoteThreadMuting.js';
import { NoteUnread } from '@/models/entities/NoteUnread.js';
-import { Notification } from '@/models/entities/Notification.js';
import { Page } from '@/models/entities/Page.js';
import { PageLike } from '@/models/entities/PageLike.js';
import { PasswordResetRequest } from '@/models/entities/PasswordResetRequest.js';
@@ -155,7 +154,6 @@ export const entities = [
DriveFolder,
Poll,
PollVote,
- Notification,
Emoji,
Hashtag,
SwSubscription,
diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts
index 3feb86f86f..1936e8df23 100644
--- a/packages/backend/src/queue/processors/CleanProcessorService.ts
+++ b/packages/backend/src/queue/processors/CleanProcessorService.ts
@@ -1,7 +1,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { In, LessThan } from 'typeorm';
import { DI } from '@/di-symbols.js';
-import type { AntennasRepository, MutedNotesRepository, NotificationsRepository, RoleAssignmentsRepository, UserIpsRepository } from '@/models/index.js';
+import type { AntennasRepository, MutedNotesRepository, RoleAssignmentsRepository, UserIpsRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
import type Logger from '@/logger.js';
import { bindThis } from '@/decorators.js';
@@ -20,9 +20,6 @@ export class CleanProcessorService {
@Inject(DI.userIpsRepository)
private userIpsRepository: UserIpsRepository,
- @Inject(DI.notificationsRepository)
- private notificationsRepository: NotificationsRepository,
-
@Inject(DI.mutedNotesRepository)
private mutedNotesRepository: MutedNotesRepository,
@@ -46,10 +43,6 @@ export class CleanProcessorService {
createdAt: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 90))),
});
- this.notificationsRepository.delete({
- createdAt: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 90))),
- });
-
this.mutedNotesRepository.delete({
id: LessThan(this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 90)))),
reason: 'word',
diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts
index f637bf8818..a9af22ad09 100644
--- a/packages/backend/src/queue/processors/DeliverProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts
@@ -7,7 +7,7 @@ import { MetaService } from '@/core/MetaService.js';
import { ApRequestService } from '@/core/activitypub/ApRequestService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js';
-import { KVCache } from '@/misc/cache.js';
+import { MemoryCache } from '@/misc/cache.js';
import type { Instance } from '@/models/entities/Instance.js';
import InstanceChart from '@/core/chart/charts/instance.js';
import ApRequestChart from '@/core/chart/charts/ap-request.js';
@@ -22,7 +22,7 @@ import type { DeliverJobData } from '../types.js';
@Injectable()
export class DeliverProcessorService {
private logger: Logger;
- private suspendedHostsCache: KVCache<Instance[]>;
+ private suspendedHostsCache: MemoryCache<Instance[]>;
private latest: string | null;
constructor(
@@ -46,7 +46,7 @@ export class DeliverProcessorService {
private queueLoggerService: QueueLoggerService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('deliver');
- this.suspendedHostsCache = new KVCache<Instance[]>(1000 * 60 * 60);
+ this.suspendedHostsCache = new MemoryCache<Instance[]>(1000 * 60 * 60);
}
@bindThis
@@ -60,14 +60,14 @@ export class DeliverProcessorService {
}
// isSuspendedなら中断
- let suspendedHosts = this.suspendedHostsCache.get(null);
+ let suspendedHosts = this.suspendedHostsCache.get();
if (suspendedHosts == null) {
suspendedHosts = await this.instancesRepository.find({
where: {
isSuspended: true,
},
});
- this.suspendedHostsCache.set(null, suspendedHosts);
+ this.suspendedHostsCache.set(suspendedHosts);
}
if (suspendedHosts.map(x => x.host).includes(this.utilityService.toPuny(host))) {
return 'skip (suspended)';
diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts
index 86019d4166..66c1faaac2 100644
--- a/packages/backend/src/server/NodeinfoServerService.ts
+++ b/packages/backend/src/server/NodeinfoServerService.ts
@@ -4,7 +4,7 @@ import type { NotesRepository, UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
import { MetaService } from '@/core/MetaService.js';
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
-import { KVCache } from '@/misc/cache.js';
+import { MemoryCache } from '@/misc/cache.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
import NotesChart from '@/core/chart/charts/notes.js';
@@ -118,17 +118,17 @@ export class NodeinfoServerService {
};
};
- const cache = new KVCache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10);
+ const cache = new MemoryCache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10);
fastify.get(nodeinfo2_1path, async (request, reply) => {
- const base = await cache.fetch(null, () => nodeinfo2());
+ const base = await cache.fetch(() => nodeinfo2());
reply.header('Cache-Control', 'public, max-age=600');
return { version: '2.1', ...base };
});
fastify.get(nodeinfo2_0path, async (request, reply) => {
- const base = await cache.fetch(null, () => nodeinfo2());
+ const base = await cache.fetch(() => nodeinfo2());
delete (base as any).software.repository;
diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts
index a1895e3705..6548c475b2 100644
--- a/packages/backend/src/server/api/AuthenticateService.ts
+++ b/packages/backend/src/server/api/AuthenticateService.ts
@@ -3,9 +3,9 @@ import { DI } from '@/di-symbols.js';
import type { AccessTokensRepository, AppsRepository, UsersRepository } from '@/models/index.js';
import type { LocalUser } from '@/models/entities/User.js';
import type { AccessToken } from '@/models/entities/AccessToken.js';
-import { KVCache } from '@/misc/cache.js';
+import { MemoryKVCache } from '@/misc/cache.js';
import type { App } from '@/models/entities/App.js';
-import { UserCacheService } from '@/core/UserCacheService.js';
+import { CacheService } from '@/core/CacheService.js';
import isNativeToken from '@/misc/is-native-token.js';
import { bindThis } from '@/decorators.js';
@@ -18,7 +18,7 @@ export class AuthenticationError extends Error {
@Injectable()
export class AuthenticateService {
- private appCache: KVCache<App>;
+ private appCache: MemoryKVCache<App>;
constructor(
@Inject(DI.usersRepository)
@@ -30,9 +30,9 @@ export class AuthenticateService {
@Inject(DI.appsRepository)
private appsRepository: AppsRepository,
- private userCacheService: UserCacheService,
+ private cacheService: CacheService,
) {
- this.appCache = new KVCache<App>(Infinity);
+ this.appCache = new MemoryKVCache<App>(Infinity);
}
@bindThis
@@ -42,7 +42,7 @@ export class AuthenticateService {
}
if (isNativeToken(token)) {
- const user = await this.userCacheService.localUserByNativeTokenCache.fetch(token,
+ const user = await this.cacheService.localUserByNativeTokenCache.fetch(token,
() => this.usersRepository.findOneBy({ token }) as Promise<LocalUser | null>);
if (user == null) {
@@ -67,7 +67,7 @@ export class AuthenticateService {
lastUsedAt: new Date(),
});
- const user = await this.userCacheService.localUserByIdCache.fetch(accessToken.userId,
+ const user = await this.cacheService.localUserByIdCache.fetch(accessToken.userId,
() => this.usersRepository.findOneBy({
id: accessToken.userId,
}) as Promise<LocalUser>);
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index f39643abeb..cab2477414 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -268,7 +268,6 @@ import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
import * as ep___notifications_create from './endpoints/notifications/create.js';
import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
-import * as ep___notifications_read from './endpoints/notifications/read.js';
import * as ep___pagePush from './endpoints/page-push.js';
import * as ep___pages_create from './endpoints/pages/create.js';
import * as ep___pages_delete from './endpoints/pages/delete.js';
@@ -600,7 +599,6 @@ const $notes_unrenote: Provider = { provide: 'ep:notes/unrenote', useClass: ep__
const $notes_userListTimeline: Provider = { provide: 'ep:notes/user-list-timeline', useClass: ep___notes_userListTimeline.default };
const $notifications_create: Provider = { provide: 'ep:notifications/create', useClass: ep___notifications_create.default };
const $notifications_markAllAsRead: Provider = { provide: 'ep:notifications/mark-all-as-read', useClass: ep___notifications_markAllAsRead.default };
-const $notifications_read: Provider = { provide: 'ep:notifications/read', useClass: ep___notifications_read.default };
const $pagePush: Provider = { provide: 'ep:page-push', useClass: ep___pagePush.default };
const $pages_create: Provider = { provide: 'ep:pages/create', useClass: ep___pages_create.default };
const $pages_delete: Provider = { provide: 'ep:pages/delete', useClass: ep___pages_delete.default };
@@ -936,7 +934,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$notes_userListTimeline,
$notifications_create,
$notifications_markAllAsRead,
- $notifications_read,
$pagePush,
$pages_create,
$pages_delete,
@@ -1266,7 +1263,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$notes_userListTimeline,
$notifications_create,
$notifications_markAllAsRead,
- $notifications_read,
$pagePush,
$pages_create,
$pages_delete,
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index 16b20c1a4d..e33c2349cd 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -268,7 +268,6 @@ import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
import * as ep___notifications_create from './endpoints/notifications/create.js';
import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
-import * as ep___notifications_read from './endpoints/notifications/read.js';
import * as ep___pagePush from './endpoints/page-push.js';
import * as ep___pages_create from './endpoints/pages/create.js';
import * as ep___pages_delete from './endpoints/pages/delete.js';
@@ -598,7 +597,6 @@ const eps = [
['notes/user-list-timeline', ep___notes_userListTimeline],
['notifications/create', ep___notifications_create],
['notifications/mark-all-as-read', ep___notifications_markAllAsRead],
- ['notifications/read', ep___notifications_read],
['page-push', ep___pagePush],
['pages/create', ep___pages_create],
['pages/delete', ep___pages_delete],
diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
index 3ad6c7c484..770b61850a 100644
--- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
@@ -1,6 +1,6 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { UsersRepository, FollowingsRepository, NotificationsRepository } from '@/models/index.js';
+import type { UsersRepository, FollowingsRepository } from '@/models/index.js';
import type { User } from '@/models/entities/User.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
@@ -36,9 +36,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
- @Inject(DI.notificationsRepository)
- private notificationsRepository: NotificationsRepository,
-
private userEntityService: UserEntityService,
private userFollowingService: UserFollowingService,
private userSuspendService: UserSuspendService,
@@ -73,7 +70,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
(async () => {
await this.userSuspendService.doPostSuspend(user).catch(e => {});
await this.unFollowAll(user).catch(e => {});
- await this.readAllNotify(user).catch(e => {});
})();
});
}
@@ -96,14 +92,4 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
await this.userFollowingService.unfollow(follower, followee, true);
}
}
-
- @bindThis
- private async readAllNotify(notifier: User) {
- await this.notificationsRepository.update({
- notifierId: notifier.id,
- isRead: false,
- }, {
- isRead: true,
- });
- }
}
diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts
index e3897d38bd..f27b4e86d4 100644
--- a/packages/backend/src/server/api/endpoints/i/notifications.ts
+++ b/packages/backend/src/server/api/endpoints/i/notifications.ts
@@ -1,6 +1,7 @@
-import { Brackets } from 'typeorm';
+import { Brackets, In } from 'typeorm';
+import Redis from 'ioredis';
import { Inject, Injectable } from '@nestjs/common';
-import type { UsersRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, NotificationsRepository } from '@/models/index.js';
+import type { UsersRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, NotesRepository } from '@/models/index.js';
import { obsoleteNotificationTypes, notificationTypes } from '@/types.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { QueryService } from '@/core/QueryService.js';
@@ -8,6 +9,8 @@ import { NoteReadService } from '@/core/NoteReadService.js';
import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
import { NotificationService } from '@/core/NotificationService.js';
import { DI } from '@/di-symbols.js';
+import { IdService } from '@/core/IdService.js';
+import { Notification } from '@/models/entities/Notification.js';
export const meta = {
tags: ['account', 'notifications'],
@@ -38,8 +41,6 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
- following: { type: 'boolean', default: false },
- unreadOnly: { type: 'boolean', default: false },
markAsRead: { type: 'boolean', default: true },
// 後方互換のため、廃止された通知タイプも受け付ける
includeTypes: { type: 'array', items: {
@@ -56,21 +57,22 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
+ @Inject(DI.redis)
+ private redisClient: Redis.Redis,
+
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
- @Inject(DI.followingsRepository)
- private followingsRepository: FollowingsRepository,
-
@Inject(DI.mutingsRepository)
private mutingsRepository: MutingsRepository,
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
- @Inject(DI.notificationsRepository)
- private notificationsRepository: NotificationsRepository,
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
+ private idService: IdService,
private notificationEntityService: NotificationEntityService,
private notificationService: NotificationService,
private queryService: QueryService,
@@ -89,85 +91,39 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const includeTypes = ps.includeTypes && ps.includeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][];
const excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][];
- const followingQuery = this.followingsRepository.createQueryBuilder('following')
- .select('following.followeeId')
- .where('following.followerId = :followerId', { followerId: me.id });
-
- const mutingQuery = this.mutingsRepository.createQueryBuilder('muting')
- .select('muting.muteeId')
- .where('muting.muterId = :muterId', { muterId: me.id });
-
- const mutingInstanceQuery = this.userProfilesRepository.createQueryBuilder('user_profile')
- .select('user_profile.mutedInstances')
- .where('user_profile.userId = :muterId', { muterId: me.id });
-
- const suspendedQuery = this.usersRepository.createQueryBuilder('users')
- .select('users.id')
- .where('users.isSuspended = TRUE');
-
- const query = this.queryService.makePaginationQuery(this.notificationsRepository.createQueryBuilder('notification'), ps.sinceId, ps.untilId)
- .andWhere('notification.notifieeId = :meId', { meId: me.id })
- .leftJoinAndSelect('notification.notifier', 'notifier')
- .leftJoinAndSelect('notification.note', 'note')
- .leftJoinAndSelect('notifier.avatar', 'notifierAvatar')
- .leftJoinAndSelect('notifier.banner', 'notifierBanner')
- .leftJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
- .leftJoinAndSelect('note.reply', 'reply')
- .leftJoinAndSelect('note.renote', 'renote')
- .leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
- .leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
-
- // muted users
- query.andWhere(new Brackets(qb => { qb
- .where(`notification.notifierId NOT IN (${ mutingQuery.getQuery() })`)
- .orWhere('notification.notifierId IS NULL');
- }));
- query.setParameters(mutingQuery.getParameters());
+ const notificationsRes = await this.redisClient.xrevrange(
+ `notificationTimeline:${me.id}`,
+ ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : '+',
+ '-',
+ 'COUNT', ps.limit + 1); // untilIdに指定したものも含まれるため+1
- // muted instances
- query.andWhere(new Brackets(qb => { qb
- .andWhere('notifier.host IS NULL')
- .orWhere(`NOT (( ${mutingInstanceQuery.getQuery()} )::jsonb ? notifier.host)`);
- }));
- query.setParameters(mutingInstanceQuery.getParameters());
-
- // suspended users
- query.andWhere(new Brackets(qb => { qb
- .where(`notification.notifierId NOT IN (${ suspendedQuery.getQuery() })`)
- .orWhere('notification.notifierId IS NULL');
- }));
-
- if (ps.following) {
- query.andWhere(`((notification.notifierId IN (${ followingQuery.getQuery() })) OR (notification.notifierId = :meId))`, { meId: me.id });
- query.setParameters(followingQuery.getParameters());
+ if (notificationsRes.length === 0) {
+ return [];
}
+ let notifications = notificationsRes.map(x => JSON.parse(x[1][1])).filter(x => x.id !== ps.untilId) as Notification[];
+
if (includeTypes && includeTypes.length > 0) {
- query.andWhere('notification.type IN (:...includeTypes)', { includeTypes });
+ notifications = notifications.filter(notification => includeTypes.includes(notification.type));
} else if (excludeTypes && excludeTypes.length > 0) {
- query.andWhere('notification.type NOT IN (:...excludeTypes)', { excludeTypes });
+ notifications = notifications.filter(notification => !excludeTypes.includes(notification.type));
}
- if (ps.unreadOnly) {
- query.andWhere('notification.isRead = false');
+ if (notifications.length === 0) {
+ return [];
}
- const notifications = await query.take(ps.limit).getMany();
-
// Mark all as read
- if (notifications.length > 0 && ps.markAsRead) {
- this.notificationService.readNotification(me.id, notifications.map(x => x.id));
+ if (ps.markAsRead) {
+ this.notificationService.readAllNotification(me.id);
}
- const notes = notifications.filter(notification => ['mention', 'reply', 'quote'].includes(notification.type)).map(notification => notification.note!);
+ const noteIds = notifications
+ .filter(notification => ['mention', 'reply', 'quote'].includes(notification.type))
+ .map(notification => notification.noteId!);
- if (notes.length > 0) {
+ if (noteIds.length > 0) {
+ const notes = await this.notesRepository.findBy({ id: In(noteIds) });
this.noteReadService.read(me.id, notes);
}
diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
index f942f43cc8..786e64374c 100644
--- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
+++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
@@ -34,7 +34,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
) {
super(meta, paramDef, async (ps, me) => {
const freshUser = await this.usersRepository.findOneByOrFail({ id: me.id });
- const oldToken = freshUser.token;
+ const oldToken = freshUser.token!;
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index b1eaab3908..46b16e9dce 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -18,6 +18,7 @@ import { AccountUpdateService } from '@/core/AccountUpdateService.js';
import { HashtagService } from '@/core/HashtagService.js';
import { DI } from '@/di-symbols.js';
import { RoleService } from '@/core/RoleService.js';
+import { CacheService } from '@/core/CacheService.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -152,6 +153,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private accountUpdateService: AccountUpdateService,
private hashtagService: HashtagService,
private roleService: RoleService,
+ private cacheService: CacheService,
) {
super(meta, paramDef, async (ps, _user, token) => {
const user = await this.usersRepository.findOneByOrFail({ id: _user.id });
@@ -276,9 +278,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
includeSecrets: isSecure,
});
+ const updatedProfile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
+
+ this.cacheService.userProfileCache.set(user.id, updatedProfile);
+
// Publish meUpdated event
this.globalEventService.publishMainStream(user.id, 'meUpdated', iObj);
- this.globalEventService.publishUserEvent(user.id, 'updateUserProfile', await this.userProfilesRepository.findOneByOrFail({ userId: user.id }));
+ this.globalEventService.publishUserEvent(user.id, 'updateUserProfile', updatedProfile);
// 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認
if (user.isLocked && ps.isLocked === false) {
diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts
index 9099eea52e..fd062e1cab 100644
--- a/packages/backend/src/server/api/endpoints/mute/create.ts
+++ b/packages/backend/src/server/api/endpoints/mute/create.ts
@@ -7,6 +7,7 @@ import type { Muting } from '@/models/entities/Muting.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
import { GetterService } from '@/server/api/GetterService.js';
+import { CacheService } from '@/core/CacheService.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -65,6 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private globalEventService: GlobalEventService,
private getterService: GetterService,
private idService: IdService,
+ private cacheService: CacheService,
) {
super(meta, paramDef, async (ps, me) => {
const muter = me;
@@ -103,6 +105,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
muteeId: mutee.id,
} as Muting);
+ this.cacheService.userMutingsCache.delete(muter.id);
this.globalEventService.publishUserEvent(me.id, 'mute', mutee);
});
}
diff --git a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
index 09134cf48f..9ba6079189 100644
--- a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
+++ b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
@@ -1,9 +1,7 @@
import { Inject, Injectable } from '@nestjs/common';
-import type { NotificationsRepository } from '@/models/index.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
-import { PushNotificationService } from '@/core/PushNotificationService.js';
import { DI } from '@/di-symbols.js';
+import { NotificationService } from '@/core/NotificationService.js';
export const meta = {
tags: ['notifications', 'account'],
@@ -23,24 +21,10 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
- @Inject(DI.notificationsRepository)
- private notificationsRepository: NotificationsRepository,
-
- private globalEventService: GlobalEventService,
- private pushNotificationService: PushNotificationService,
+ private notificationService: NotificationService,
) {
super(meta, paramDef, async (ps, me) => {
- // Update documents
- await this.notificationsRepository.update({
- notifieeId: me.id,
- isRead: false,
- }, {
- isRead: true,
- });
-
- // 全ての通知を読みましたよというイベントを発行
- this.globalEventService.publishMainStream(me.id, 'readAllNotifications');
- this.pushNotificationService.pushNotification(me.id, 'readAllNotifications', undefined);
+ this.notificationService.readAllNotification(me.id);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/notifications/read.ts b/packages/backend/src/server/api/endpoints/notifications/read.ts
deleted file mode 100644
index 6262c47fd0..0000000000
--- a/packages/backend/src/server/api/endpoints/notifications/read.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { Injectable } from '@nestjs/common';
-import { Endpoint } from '@/server/api/endpoint-base.js';
-import { NotificationService } from '@/core/NotificationService.js';
-
-export const meta = {
- tags: ['notifications', 'account'],
-
- requireCredential: true,
-
- kind: 'write:notifications',
-
- description: 'Mark a notification as read.',
-
- errors: {
- noSuchNotification: {
- message: 'No such notification.',
- code: 'NO_SUCH_NOTIFICATION',
- id: 'efa929d5-05b5-47d1-beec-e6a4dbed011e',
- },
- },
-} as const;
-
-export const paramDef = {
- oneOf: [
- {
- type: 'object',
- properties: {
- notificationId: { type: 'string', format: 'misskey:id' },
- },
- required: ['notificationId'],
- },
- {
- type: 'object',
- properties: {
- notificationIds: {
- type: 'array',
- items: { type: 'string', format: 'misskey:id' },
- maxItems: 100,
- },
- },
- required: ['notificationIds'],
- },
- ],
-} as const;
-
-// eslint-disable-next-line import/no-default-export
-@Injectable()
-export default class extends Endpoint<typeof meta, typeof paramDef> {
- constructor(
- private notificationService: NotificationService,
- ) {
- super(meta, paramDef, async (ps, me) => {
- if ('notificationId' in ps) return this.notificationService.readNotification(me.id, [ps.notificationId]);
- return this.notificationService.readNotification(me.id, ps.notificationIds);
- });
- }
-}
diff --git a/packages/backend/src/server/api/endpoints/renote-mute/create.ts b/packages/backend/src/server/api/endpoints/renote-mute/create.ts
index 051a005b67..b285269617 100644
--- a/packages/backend/src/server/api/endpoints/renote-mute/create.ts
+++ b/packages/backend/src/server/api/endpoints/renote-mute/create.ts
@@ -92,8 +92,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
muterId: muter.id,
muteeId: mutee.id,
} as RenoteMuting);
-
- // publishUserEvent(user.id, 'mute', mutee);
});
}
}
diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts
index 7c6eb9a20a..f1f8bfd3a2 100644
--- a/packages/backend/src/server/api/stream/index.ts
+++ b/packages/backend/src/server/api/stream/index.ts
@@ -195,8 +195,7 @@ export default class Connection {
@bindThis
private onReadNotification(payload: any) {
- if (!payload.id) return;
- this.notificationService.readNotification(this.user!.id, [payload.id]);
+ this.notificationService.readAllNotification(this.user!.id);
}
/**
diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts
index b8f50e0546..1e6e51e76d 100644
--- a/packages/backend/src/server/api/stream/types.ts
+++ b/packages/backend/src/server/api/stream/types.ts
@@ -19,7 +19,7 @@ import type { EventEmitter } from 'events';
//#region Stream type-body definitions
export interface InternalStreamTypes {
userChangeSuspendedState: { id: User['id']; isSuspended: User['isSuspended']; };
- userTokenRegenerated: { id: User['id']; oldToken: User['token']; newToken: User['token']; };
+ userTokenRegenerated: { id: User['id']; oldToken: string; newToken: string; };
remoteUserUpdated: { id: User['id']; };
follow: { followerId: User['id']; followeeId: User['id']; };
unfollow: { followerId: User['id']; followeeId: User['id']; };