diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-04-04 14:06:57 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2023-04-04 14:06:57 +0900 |
| commit | 30d699268450af375dabc2226ec4f3196a53f7f7 (patch) | |
| tree | e6e62a4b19f190d2a51e4a5281c051b6911f97a1 /packages/backend/src/server/api/endpoints | |
| parent | build(#10336): Storybook & Chromatic & msw (#10365) (diff) | |
| download | sharkey-30d699268450af375dabc2226ec4f3196a53f7f7.tar.gz sharkey-30d699268450af375dabc2226ec4f3196a53f7f7.tar.bz2 sharkey-30d699268450af375dabc2226ec4f3196a53f7f7.zip | |
perf(backend): 通知をRedisに保存するように
Resolve #10168
Diffstat (limited to 'packages/backend/src/server/api/endpoints')
4 files changed, 35 insertions, 166 deletions
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/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); - }); - } -} |