summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api/endpoints/i/notifications.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend/src/server/api/endpoints/i/notifications.ts')
-rw-r--r--packages/backend/src/server/api/endpoints/i/notifications.ts194
1 files changed, 111 insertions, 83 deletions
diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts
index 2b343dabdd..96927dad49 100644
--- a/packages/backend/src/server/api/endpoints/i/notifications.ts
+++ b/packages/backend/src/server/api/endpoints/i/notifications.ts
@@ -1,10 +1,13 @@
import { Brackets } from 'typeorm';
-import { Notifications, Followings, Mutings, Users, UserProfiles } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UsersRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, NotificationsRepository } from '@/models/index.js';
import { notificationTypes } from '@/types.js';
-import read from '@/services/note/read.js';
-import { readNotification } from '../../common/read-notification.js';
-import define from '../../define.js';
-import { makePaginationQuery } from '../../common/make-pagination-query.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueryService } from '@/core/QueryService.js';
+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';
export const meta = {
tags: ['account', 'notifications'],
@@ -49,96 +52,121 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- // includeTypes が空の場合はクエリしない
- if (ps.includeTypes && ps.includeTypes.length === 0) {
- return [];
- }
- // excludeTypes に全指定されている場合はクエリしない
- if (notificationTypes.every(type => ps.excludeTypes?.includes(type))) {
- return [];
- }
- const followingQuery = Followings.createQueryBuilder('following')
- .select('following.followeeId')
- .where('following.followerId = :followerId', { followerId: user.id });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- const mutingQuery = Mutings.createQueryBuilder('muting')
- .select('muting.muteeId')
- .where('muting.muterId = :muterId', { muterId: user.id });
+ @Inject(DI.followingsRepository)
+ private followingsRepository: FollowingsRepository,
- const mutingInstanceQuery = UserProfiles.createQueryBuilder('user_profile')
- .select('user_profile.mutedInstances')
- .where('user_profile.userId = :muterId', { muterId: user.id });
+ @Inject(DI.mutingsRepository)
+ private mutingsRepository: MutingsRepository,
- const suspendedQuery = Users.createQueryBuilder('users')
- .select('users.id')
- .where('users.isSuspended = TRUE');
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- const query = makePaginationQuery(Notifications.createQueryBuilder('notification'), ps.sinceId, ps.untilId)
- .andWhere('notification.notifieeId = :meId', { meId: user.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');
+ @Inject(DI.notificationsRepository)
+ private notificationsRepository: NotificationsRepository,
- // muted users
- query.andWhere(new Brackets(qb => { qb
- .where(`notification.notifierId NOT IN (${ mutingQuery.getQuery() })`)
- .orWhere('notification.notifierId IS NULL');
- }));
- query.setParameters(mutingQuery.getParameters());
+ private notificationEntityService: NotificationEntityService,
+ private notificationService: NotificationService,
+ private queryService: QueryService,
+ private noteReadService: NoteReadService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // includeTypes が空の場合はクエリしない
+ if (ps.includeTypes && ps.includeTypes.length === 0) {
+ return [];
+ }
+ // excludeTypes に全指定されている場合はクエリしない
+ if (notificationTypes.every(type => ps.excludeTypes?.includes(type))) {
+ return [];
+ }
+ const followingQuery = this.followingsRepository.createQueryBuilder('following')
+ .select('following.followeeId')
+ .where('following.followerId = :followerId', { followerId: me.id });
- // muted instances
- query.andWhere(new Brackets(qb => { qb
- .andWhere('notifier.host IS NULL')
- .orWhere(`NOT (( ${mutingInstanceQuery.getQuery()} )::jsonb ? notifier.host)`);
- }));
- query.setParameters(mutingInstanceQuery.getParameters());
+ const mutingQuery = this.mutingsRepository.createQueryBuilder('muting')
+ .select('muting.muteeId')
+ .where('muting.muterId = :muterId', { muterId: me.id });
- // suspended users
- query.andWhere(new Brackets(qb => { qb
- .where(`notification.notifierId NOT IN (${ suspendedQuery.getQuery() })`)
- .orWhere('notification.notifierId IS NULL');
- }));
+ const mutingInstanceQuery = this.userProfilesRepository.createQueryBuilder('user_profile')
+ .select('user_profile.mutedInstances')
+ .where('user_profile.userId = :muterId', { muterId: me.id });
- if (ps.following) {
- query.andWhere(`((notification.notifierId IN (${ followingQuery.getQuery() })) OR (notification.notifierId = :meId))`, { meId: user.id });
- query.setParameters(followingQuery.getParameters());
- }
+ const suspendedQuery = this.usersRepository.createQueryBuilder('users')
+ .select('users.id')
+ .where('users.isSuspended = TRUE');
- if (ps.includeTypes && ps.includeTypes.length > 0) {
- query.andWhere('notification.type IN (:...includeTypes)', { includeTypes: ps.includeTypes });
- } else if (ps.excludeTypes && ps.excludeTypes.length > 0) {
- query.andWhere('notification.type NOT IN (:...excludeTypes)', { excludeTypes: ps.excludeTypes });
- }
+ 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');
- if (ps.unreadOnly) {
- query.andWhere('notification.isRead = false');
- }
+ // 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 notifications = await query.take(ps.limit).getMany();
+ // muted instances
+ query.andWhere(new Brackets(qb => { qb
+ .andWhere('notifier.host IS NULL')
+ .orWhere(`NOT (( ${mutingInstanceQuery.getQuery()} )::jsonb ? notifier.host)`);
+ }));
+ query.setParameters(mutingInstanceQuery.getParameters());
- // Mark all as read
- if (notifications.length > 0 && ps.markAsRead) {
- readNotification(user.id, notifications.map(x => x.id));
- }
+ // suspended users
+ query.andWhere(new Brackets(qb => { qb
+ .where(`notification.notifierId NOT IN (${ suspendedQuery.getQuery() })`)
+ .orWhere('notification.notifierId IS NULL');
+ }));
- const notes = notifications.filter(notification => ['mention', 'reply', 'quote'].includes(notification.type)).map(notification => notification.note!);
+ if (ps.following) {
+ query.andWhere(`((notification.notifierId IN (${ followingQuery.getQuery() })) OR (notification.notifierId = :meId))`, { meId: me.id });
+ query.setParameters(followingQuery.getParameters());
+ }
- if (notes.length > 0) {
- read(user.id, notes);
- }
+ if (ps.includeTypes && ps.includeTypes.length > 0) {
+ query.andWhere('notification.type IN (:...includeTypes)', { includeTypes: ps.includeTypes });
+ } else if (ps.excludeTypes && ps.excludeTypes.length > 0) {
+ query.andWhere('notification.type NOT IN (:...excludeTypes)', { excludeTypes: ps.excludeTypes });
+ }
- return await Notifications.packMany(notifications, user.id);
-});
+ if (ps.unreadOnly) {
+ query.andWhere('notification.isRead = false');
+ }
+
+ 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));
+ }
+
+ const notes = notifications.filter(notification => ['mention', 'reply', 'quote'].includes(notification.type)).map(notification => notification.note!);
+
+ if (notes.length > 0) {
+ this.noteReadService.read(me.id, notes);
+ }
+
+ return await this.notificationEntityService.packMany(notifications, me.id);
+ });
+ }
+}