summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-03-16 14:24:11 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2023-03-16 14:24:11 +0900
commit89e2c302dd0f41abb3f49700d63fb1f8cdf7e84a (patch)
tree7ab94563de1f978f840f846f6df1d9c22c08bfd5 /packages
parentupdate tabler-icons (diff)
downloadsharkey-89e2c302dd0f41abb3f49700d63fb1f8cdf7e84a.tar.gz
sharkey-89e2c302dd0f41abb3f49700d63fb1f8cdf7e84a.tar.bz2
sharkey-89e2c302dd0f41abb3f49700d63fb1f8cdf7e84a.zip
refactor(backend): integrate CreateNotificationService to NotificationService
Diffstat (limited to 'packages')
-rw-r--r--packages/backend/src/core/AchievementService.ts6
-rw-r--r--packages/backend/src/core/CoreModule.ts8
-rw-r--r--packages/backend/src/core/CreateNotificationService.ts125
-rw-r--r--packages/backend/src/core/NoteCreateService.ts16
-rw-r--r--packages/backend/src/core/NotificationService.ts114
-rw-r--r--packages/backend/src/core/ReactionService.ts52
-rw-r--r--packages/backend/src/core/UserFollowingService.ts102
-rw-r--r--packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts8
-rw-r--r--packages/backend/src/server/api/endpoints/notes/polls/vote.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/notifications/create.ts6
10 files changed, 205 insertions, 234 deletions
diff --git a/packages/backend/src/core/AchievementService.ts b/packages/backend/src/core/AchievementService.ts
index 2ebee0f7e0..1ca38d8bb0 100644
--- a/packages/backend/src/core/AchievementService.ts
+++ b/packages/backend/src/core/AchievementService.ts
@@ -3,7 +3,7 @@ import type { UserProfilesRepository, UsersRepository } from '@/models/index.js'
import type { User } from '@/models/entities/User.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
-import { CreateNotificationService } from '@/core/CreateNotificationService.js';
+import { NotificationService } from '@/core/NotificationService.js';
export const ACHIEVEMENT_TYPES = [
'notes1',
@@ -90,7 +90,7 @@ export class AchievementService {
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
- private createNotificationService: CreateNotificationService,
+ private notificationService: NotificationService,
) {
}
@@ -114,7 +114,7 @@ export class AchievementService {
}],
});
- this.createNotificationService.createNotification(userId, 'achievementEarned', {
+ this.notificationService.createNotification(userId, 'achievementEarned', {
achievement: type,
});
}
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index 1fd2d15004..d67e80fc1d 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -5,7 +5,6 @@ import { AntennaService } from './AntennaService.js';
import { AppLockService } from './AppLockService.js';
import { AchievementService } from './AchievementService.js';
import { CaptchaService } from './CaptchaService.js';
-import { CreateNotificationService } from './CreateNotificationService.js';
import { CreateSystemUserService } from './CreateSystemUserService.js';
import { CustomEmojiService } from './CustomEmojiService.js';
import { DeleteAccountService } from './DeleteAccountService.js';
@@ -126,7 +125,6 @@ const $AntennaService: Provider = { provide: 'AntennaService', useExisting: Ante
const $AppLockService: Provider = { provide: 'AppLockService', useExisting: AppLockService };
const $AchievementService: Provider = { provide: 'AchievementService', useExisting: AchievementService };
const $CaptchaService: Provider = { provide: 'CaptchaService', useExisting: CaptchaService };
-const $CreateNotificationService: Provider = { provide: 'CreateNotificationService', useExisting: CreateNotificationService };
const $CreateSystemUserService: Provider = { provide: 'CreateSystemUserService', useExisting: CreateSystemUserService };
const $CustomEmojiService: Provider = { provide: 'CustomEmojiService', useExisting: CustomEmojiService };
const $DeleteAccountService: Provider = { provide: 'DeleteAccountService', useExisting: DeleteAccountService };
@@ -250,7 +248,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
AppLockService,
AchievementService,
CaptchaService,
- CreateNotificationService,
CreateSystemUserService,
CustomEmojiService,
DeleteAccountService,
@@ -368,7 +365,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$AppLockService,
$AchievementService,
$CaptchaService,
- $CreateNotificationService,
$CreateSystemUserService,
$CustomEmojiService,
$DeleteAccountService,
@@ -487,7 +483,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
AppLockService,
AchievementService,
CaptchaService,
- CreateNotificationService,
CreateSystemUserService,
CustomEmojiService,
DeleteAccountService,
@@ -604,7 +599,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$AppLockService,
$AchievementService,
$CaptchaService,
- $CreateNotificationService,
$CreateSystemUserService,
$CustomEmojiService,
$DeleteAccountService,
@@ -714,4 +708,4 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
//#endregion
],
})
-export class CoreModule {}
+export class CoreModule { }
diff --git a/packages/backend/src/core/CreateNotificationService.ts b/packages/backend/src/core/CreateNotificationService.ts
deleted file mode 100644
index eba7171fb6..0000000000
--- a/packages/backend/src/core/CreateNotificationService.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-import { setTimeout } from 'node:timers/promises';
-import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
-import type { MutingsRepository, NotificationsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
-import type { User } from '@/models/entities/User.js';
-import type { Notification } from '@/models/entities/Notification.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
-import { IdService } from '@/core/IdService.js';
-import { DI } from '@/di-symbols.js';
-import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
-import { PushNotificationService } from '@/core/PushNotificationService.js';
-import { bindThis } from '@/decorators.js';
-
-@Injectable()
-export class CreateNotificationService implements OnApplicationShutdown {
- #shutdownController = new AbortController();
-
- constructor(
- @Inject(DI.usersRepository)
- private usersRepository: UsersRepository,
-
- @Inject(DI.userProfilesRepository)
- private userProfilesRepository: UserProfilesRepository,
-
- @Inject(DI.notificationsRepository)
- private notificationsRepository: NotificationsRepository,
-
- @Inject(DI.mutingsRepository)
- private mutingsRepository: MutingsRepository,
-
- private notificationEntityService: NotificationEntityService,
- private idService: IdService,
- private globalEventService: GlobalEventService,
- private pushNotificationService: PushNotificationService,
- ) {
- }
-
- @bindThis
- public async createNotification(
- notifieeId: User['id'],
- type: Notification['type'],
- data: Partial<Notification>,
- ): Promise<Notification | null> {
- if (data.notifierId && (notifieeId === data.notifierId)) {
- return null;
- }
-
- const profile = await this.userProfilesRepository.findOneBy({ userId: notifieeId });
-
- const isMuted = profile?.mutingNotificationTypes.includes(type);
-
- // Create notification
- const notification = await this.notificationsRepository.insert({
- 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]));
-
- const packed = await this.notificationEntityService.pack(notification, {});
-
- // 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
-
- this.globalEventService.publishMainStream(notifieeId, 'unreadNotification', packed);
- this.pushNotificationService.pushNotification(notifieeId, 'notification', packed);
-
- if (type === 'follow') this.emailNotificationFollow(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! }));
- if (type === 'receiveFollowRequest') this.emailNotificationReceiveFollowRequest(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! }));
- }, () => { /* aborted, ignore it */ });
-
- return notification;
- }
-
- // TODO
- //const locales = await import('../../../../locales/index.js');
-
- // TODO: locale ファイルをクライアント用とサーバー用で分けたい
-
- @bindThis
- private async emailNotificationFollow(userId: User['id'], follower: User) {
- /*
- const userProfile = await UserProfiles.findOneByOrFail({ userId: userId });
- if (!userProfile.email || !userProfile.emailNotificationTypes.includes('follow')) return;
- const locale = locales[userProfile.lang ?? 'ja-JP'];
- const i18n = new I18n(locale);
- // TODO: render user information html
- sendEmail(userProfile.email, i18n.t('_email._follow.title'), `${follower.name} (@${Acct.toString(follower)})`, `${follower.name} (@${Acct.toString(follower)})`);
- */
- }
-
- @bindThis
- private async emailNotificationReceiveFollowRequest(userId: User['id'], follower: User) {
- /*
- const userProfile = await UserProfiles.findOneByOrFail({ userId: userId });
- if (!userProfile.email || !userProfile.emailNotificationTypes.includes('receiveFollowRequest')) return;
- const locale = locales[userProfile.lang ?? 'ja-JP'];
- const i18n = new I18n(locale);
- // TODO: render user information html
- sendEmail(userProfile.email, i18n.t('_email._receiveFollowRequest.title'), `${follower.name} (@${Acct.toString(follower)})`, `${follower.name} (@${Acct.toString(follower)})`);
- */
- }
-
- onApplicationShutdown(signal?: string | undefined): void {
- this.#shutdownController.abort();
- }
-}
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 5a4df69b62..2fc2a3d54f 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -30,7 +30,7 @@ import PerUserNotesChart from '@/core/chart/charts/per-user-notes.js';
import InstanceChart from '@/core/chart/charts/instance.js';
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
-import { CreateNotificationService } from '@/core/CreateNotificationService.js';
+import { NotificationService } from '@/core/NotificationService.js';
import { WebhookService } from '@/core/WebhookService.js';
import { HashtagService } from '@/core/HashtagService.js';
import { AntennaService } from '@/core/AntennaService.js';
@@ -60,7 +60,7 @@ class NotificationManager {
constructor(
private mutingsRepository: MutingsRepository,
- private createNotificationService: CreateNotificationService,
+ private notificationService: NotificationService,
notifier: { id: User['id']; },
note: Note,
) {
@@ -101,7 +101,7 @@ class NotificationManager {
// 通知される側のユーザーが通知する側のユーザーをミュートしていない限りは通知する
if (!mentioneesMutedUserIds.includes(this.notifier.id)) {
- this.createNotificationService.createNotification(x.target, x.reason, {
+ this.notificationService.createNotification(x.target, x.reason, {
notifierId: this.notifier.id,
noteId: this.note.id,
});
@@ -183,7 +183,7 @@ export class NoteCreateService implements OnApplicationShutdown {
private globalEventService: GlobalEventService,
private queueService: QueueService,
private noteReadService: NoteReadService,
- private createNotificationService: CreateNotificationService,
+ private notificationService: NotificationService,
private relayService: RelayService,
private federatedInstanceService: FederatedInstanceService,
private hashtagService: HashtagService,
@@ -198,7 +198,7 @@ export class NoteCreateService implements OnApplicationShutdown {
private perUserNotesChart: PerUserNotesChart,
private activeUsersChart: ActiveUsersChart,
private instanceChart: InstanceChart,
- ) {}
+ ) { }
@bindThis
public async create(user: {
@@ -391,7 +391,7 @@ export class NoteCreateService implements OnApplicationShutdown {
// 投稿を作成
try {
if (insert.hasPoll) {
- // Start transaction
+ // Start transaction
await this.db.transaction(async transactionalEntityManager => {
await transactionalEntityManager.insert(Note, insert);
@@ -414,7 +414,7 @@ export class NoteCreateService implements OnApplicationShutdown {
return insert;
} catch (e) {
- // duplicate key error
+ // duplicate key error
if (isDuplicateKeyValueError(e)) {
const err = new Error('Duplicated note');
err.name = 'duplicated';
@@ -558,7 +558,7 @@ export class NoteCreateService implements OnApplicationShutdown {
}
});
- const nm = new NotificationManager(this.mutingsRepository, this.createNotificationService, user, note);
+ const nm = new NotificationManager(this.mutingsRepository, this.notificationService, user, note);
await this.createMentionedEvents(mentionedUsers, note, nm);
diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts
index 88173c2307..00bca4c0c4 100644
--- a/packages/backend/src/core/NotificationService.ts
+++ b/packages/backend/src/core/NotificationService.ts
@@ -1,21 +1,36 @@
-import { Inject, Injectable } from '@nestjs/common';
+import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
-import type { NotificationsRepository } from '@/models/index.js';
+import type { MutingsRepository, NotificationsRepository, 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';
import { bindThis } from '@/decorators.js';
-import { GlobalEventService } from './GlobalEventService.js';
-import { PushNotificationService } from './PushNotificationService.js';
+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';
@Injectable()
-export class NotificationService {
+export class NotificationService implements OnApplicationShutdown {
+ #shutdownController = new AbortController();
+
constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
+
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+
@Inject(DI.notificationsRepository)
private notificationsRepository: NotificationsRepository,
+ @Inject(DI.mutingsRepository)
+ private mutingsRepository: MutingsRepository,
+
+ private notificationEntityService: NotificationEntityService,
private userEntityService: UserEntityService,
+ private idService: IdService,
private globalEventService: GlobalEventService,
private pushNotificationService: PushNotificationService,
) {
@@ -67,4 +82,93 @@ export class NotificationService {
private postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) {
return this.pushNotificationService.pushNotification(userId, 'readNotifications', { notificationIds });
}
+
+ @bindThis
+ public async createNotification(
+ notifieeId: User['id'],
+ type: Notification['type'],
+ data: Partial<Notification>,
+ ): Promise<Notification | null> {
+ if (data.notifierId && (notifieeId === data.notifierId)) {
+ return null;
+ }
+
+ const profile = await this.userProfilesRepository.findOneBy({ userId: notifieeId });
+
+ const isMuted = profile?.mutingNotificationTypes.includes(type);
+
+ // Create notification
+ const notification = await this.notificationsRepository.insert({
+ 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]));
+
+ const packed = await this.notificationEntityService.pack(notification, {});
+
+ // 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
+
+ this.globalEventService.publishMainStream(notifieeId, 'unreadNotification', packed);
+ this.pushNotificationService.pushNotification(notifieeId, 'notification', packed);
+
+ if (type === 'follow') this.emailNotificationFollow(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! }));
+ if (type === 'receiveFollowRequest') this.emailNotificationReceiveFollowRequest(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! }));
+ }, () => { /* aborted, ignore it */ });
+
+ return notification;
+ }
+
+ // TODO
+ //const locales = await import('../../../../locales/index.js');
+
+ // TODO: locale ファイルをクライアント用とサーバー用で分けたい
+
+ @bindThis
+ private async emailNotificationFollow(userId: User['id'], follower: User) {
+ /*
+ const userProfile = await UserProfiles.findOneByOrFail({ userId: userId });
+ if (!userProfile.email || !userProfile.emailNotificationTypes.includes('follow')) return;
+ const locale = locales[userProfile.lang ?? 'ja-JP'];
+ const i18n = new I18n(locale);
+ // TODO: render user information html
+ sendEmail(userProfile.email, i18n.t('_email._follow.title'), `${follower.name} (@${Acct.toString(follower)})`, `${follower.name} (@${Acct.toString(follower)})`);
+ */
+ }
+
+ @bindThis
+ private async emailNotificationReceiveFollowRequest(userId: User['id'], follower: User) {
+ /*
+ const userProfile = await UserProfiles.findOneByOrFail({ userId: userId });
+ if (!userProfile.email || !userProfile.emailNotificationTypes.includes('receiveFollowRequest')) return;
+ const locale = locales[userProfile.lang ?? 'ja-JP'];
+ const i18n = new I18n(locale);
+ // TODO: render user information html
+ sendEmail(userProfile.email, i18n.t('_email._receiveFollowRequest.title'), `${follower.name} (@${Acct.toString(follower)})`, `${follower.name} (@${Acct.toString(follower)})`);
+ */
+ }
+
+ onApplicationShutdown(signal?: string | undefined): void {
+ this.#shutdownController.abort();
+ }
}
diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts
index 3e644018d7..271ba79176 100644
--- a/packages/backend/src/core/ReactionService.ts
+++ b/packages/backend/src/core/ReactionService.ts
@@ -9,7 +9,7 @@ import { IdService } from '@/core/IdService.js';
import type { NoteReaction } from '@/models/entities/NoteReaction.js';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
-import { CreateNotificationService } from '@/core/CreateNotificationService.js';
+import { NotificationService } from '@/core/NotificationService.js';
import PerUserReactionsChart from '@/core/chart/charts/per-user-reactions.js';
import { emojiRegex } from '@/misc/emoji-regex.js';
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
@@ -79,7 +79,7 @@ export class ReactionService {
private globalEventService: GlobalEventService,
private apRendererService: ApRendererService,
private apDeliverManagerService: ApDeliverManagerService,
- private createNotificationService: CreateNotificationService,
+ private notificationService: NotificationService,
private perUserReactionsChart: PerUserReactionsChart,
) {
}
@@ -93,19 +93,19 @@ export class ReactionService {
throw new IdentifiableError('e70412a4-7197-4726-8e74-f3e0deb92aa7');
}
}
-
+
// check visibility
if (!await this.noteEntityService.isVisibleForMe(note, user.id)) {
throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.');
}
-
+
if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote') && (user.host != null))) {
reaction = '❤️';
} else {
// TODO: cache
reaction = await this.toDbReaction(reaction, user.host);
}
-
+
const record: NoteReaction = {
id: this.idService.genId(),
createdAt: new Date(),
@@ -113,7 +113,7 @@ export class ReactionService {
userId: user.id,
reaction,
};
-
+
// Create reaction
try {
await this.noteReactionsRepository.insert(record);
@@ -123,7 +123,7 @@ export class ReactionService {
noteId: note.id,
userId: user.id,
});
-
+
if (exists.reaction !== reaction) {
// 別のリアクションがすでにされていたら置き換える
await this.delete(user, note);
@@ -136,7 +136,7 @@ export class ReactionService {
throw e;
}
}
-
+
// Increment reactions count
const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`;
await this.notesRepository.createQueryBuilder().update()
@@ -146,12 +146,12 @@ export class ReactionService {
})
.where('id = :id', { id: note.id })
.execute();
-
+
this.perUserReactionsChart.update(user, note);
-
+
// カスタム絵文字リアクションだったら絵文字情報も送る
const decodedReaction = this.decodeReaction(reaction);
-
+
const emoji = await this.emojisRepository.findOne({
where: {
name: decodedReaction.name,
@@ -159,7 +159,7 @@ export class ReactionService {
},
select: ['name', 'host', 'originalUrl', 'publicUrl'],
});
-
+
this.globalEventService.publishNoteStream(note.id, 'reacted', {
reaction: decodedReaction.reaction,
emoji: emoji != null ? {
@@ -169,16 +169,16 @@ export class ReactionService {
} : null,
userId: user.id,
});
-
+
// リアクションされたユーザーがローカルユーザーなら通知を作成
if (note.userHost === null) {
- this.createNotificationService.createNotification(note.userId, 'reaction', {
+ this.notificationService.createNotification(note.userId, 'reaction', {
notifierId: user.id,
noteId: note.id,
reaction: reaction,
});
}
-
+
//#region 配信
if (this.userEntityService.isLocalUser(user) && !note.localOnly) {
const content = this.apRendererService.addContext(await this.apRendererService.renderLike(record, note));
@@ -187,7 +187,7 @@ export class ReactionService {
const reactee = await this.usersRepository.findOneBy({ id: note.userId });
dm.addDirectRecipe(reactee as RemoteUser);
}
-
+
if (['public', 'home', 'followers'].includes(note.visibility)) {
dm.addFollowersRecipe();
} else if (note.visibility === 'specified') {
@@ -196,7 +196,7 @@ export class ReactionService {
dm.addDirectRecipe(u as RemoteUser);
}
}
-
+
dm.execute();
}
//#endregion
@@ -209,18 +209,18 @@ export class ReactionService {
noteId: note.id,
userId: user.id,
});
-
+
if (exist == null) {
throw new IdentifiableError('60527ec9-b4cb-4a88-a6bd-32d3ad26817d', 'not reacted');
}
-
+
// Delete reaction
const result = await this.noteReactionsRepository.delete(exist.id);
-
+
if (result.affected !== 1) {
throw new IdentifiableError('60527ec9-b4cb-4a88-a6bd-32d3ad26817d', 'not reacted');
}
-
+
// Decrement reactions count
const sql = `jsonb_set("reactions", '{${exist.reaction}}', (COALESCE("reactions"->>'${exist.reaction}', '0')::int - 1)::text::jsonb)`;
await this.notesRepository.createQueryBuilder().update()
@@ -229,14 +229,14 @@ export class ReactionService {
})
.where('id = :id', { id: note.id })
.execute();
-
+
if (!user.isBot) this.notesRepository.decrement({ id: note.id }, 'score', 1);
-
+
this.globalEventService.publishNoteStream(note.id, 'unreacted', {
reaction: this.decodeReaction(exist.reaction).reaction,
userId: user.id,
});
-
+
//#region 配信
if (this.userEntityService.isLocalUser(user) && !note.localOnly) {
const content = this.apRendererService.addContext(this.apRendererService.renderUndo(await this.apRendererService.renderLike(exist, note), user));
@@ -250,7 +250,7 @@ export class ReactionService {
}
//#endregion
}
-
+
@bindThis
public async getFallbackReaction(): Promise<string> {
const meta = await this.metaService.fetch();
@@ -300,7 +300,7 @@ export class ReactionService {
// Unicode絵文字
const match = emojiRegex.exec(reaction);
if (match) {
- // 合字を含む1つの絵文字
+ // 合字を含む1つの絵文字
const unicode = match[0];
// 異体字セレクタ除去
diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index 9f09c34d4b..1c85504353 100644
--- a/packages/backend/src/core/UserFollowingService.ts
+++ b/packages/backend/src/core/UserFollowingService.ts
@@ -10,7 +10,7 @@ import type { Packed } from '@/misc/json-schema.js';
import InstanceChart from '@/core/chart/charts/instance.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { WebhookService } from '@/core/WebhookService.js';
-import { CreateNotificationService } from '@/core/CreateNotificationService.js';
+import { NotificationService } from '@/core/NotificationService.js';
import { DI } from '@/di-symbols.js';
import type { FollowingsRepository, FollowRequestsRepository, InstancesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
@@ -57,7 +57,7 @@ export class UserFollowingService {
private idService: IdService,
private queueService: QueueService,
private globalEventService: GlobalEventService,
- private createNotificationService: CreateNotificationService,
+ private notificationService: NotificationService,
private federatedInstanceService: FederatedInstanceService,
private webhookService: WebhookService,
private apRendererService: ApRendererService,
@@ -145,15 +145,15 @@ export class UserFollowingService {
},
): Promise<void> {
if (follower.id === followee.id) return;
-
+
let alreadyFollowed = false as boolean;
-
+
await this.followingsRepository.insert({
id: this.idService.genId(),
createdAt: new Date(),
followerId: follower.id,
followeeId: followee.id,
-
+
// 非正規化
followerHost: follower.host,
followerInbox: this.userEntityService.isRemoteUser(follower) ? follower.inbox : null,
@@ -169,35 +169,35 @@ export class UserFollowingService {
throw err;
}
});
-
+
const req = await this.followRequestsRepository.findOneBy({
followeeId: followee.id,
followerId: follower.id,
});
-
+
if (req) {
await this.followRequestsRepository.delete({
followeeId: followee.id,
followerId: follower.id,
});
-
+
// 通知を作成
- this.createNotificationService.createNotification(follower.id, 'followRequestAccepted', {
+ this.notificationService.createNotification(follower.id, 'followRequestAccepted', {
notifierId: followee.id,
});
}
-
+
if (alreadyFollowed) return;
this.globalEventService.publishInternalEvent('follow', { followerId: follower.id, followeeId: followee.id });
-
+
//#region Increment counts
await Promise.all([
this.usersRepository.increment({ id: follower.id }, 'followingCount', 1),
this.usersRepository.increment({ id: followee.id }, 'followersCount', 1),
]);
//#endregion
-
+
//#region Update instance stats
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
this.federatedInstanceService.fetch(follower.host).then(i => {
@@ -211,9 +211,9 @@ export class UserFollowingService {
});
}
//#endregion
-
+
this.perUserFollowingChart.update(follower, followee, true);
-
+
// Publish follow event
if (this.userEntityService.isLocalUser(follower)) {
this.userEntityService.pack(followee.id, follower, {
@@ -221,7 +221,7 @@ export class UserFollowingService {
}).then(async packed => {
this.globalEventService.publishUserEvent(follower.id, 'follow', packed as Packed<'UserDetailedNotMe'>);
this.globalEventService.publishMainStream(follower.id, 'follow', packed as Packed<'UserDetailedNotMe'>);
-
+
const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('follow'));
for (const webhook of webhooks) {
this.queueService.webhookDeliver(webhook, 'follow', {
@@ -230,12 +230,12 @@ export class UserFollowingService {
}
});
}
-
+
// Publish followed event
if (this.userEntityService.isLocalUser(followee)) {
this.userEntityService.pack(follower.id, followee).then(async packed => {
this.globalEventService.publishMainStream(followee.id, 'followed', packed);
-
+
const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === followee.id && x.on.includes('followed'));
for (const webhook of webhooks) {
this.queueService.webhookDeliver(webhook, 'followed', {
@@ -243,9 +243,9 @@ export class UserFollowingService {
});
}
});
-
+
// 通知を作成
- this.createNotificationService.createNotification(followee.id, 'follow', {
+ this.notificationService.createNotification(followee.id, 'follow', {
notifierId: follower.id,
});
}
@@ -265,16 +265,16 @@ export class UserFollowingService {
followerId: follower.id,
followeeId: followee.id,
});
-
+
if (following == null) {
logger.warn('フォロー解除がリクエストされましたがフォローしていませんでした');
return;
}
-
+
await this.followingsRepository.delete(following.id);
-
+
this.decrementFollowing(follower, followee);
-
+
// Publish unfollow event
if (!silent && this.userEntityService.isLocalUser(follower)) {
this.userEntityService.pack(followee.id, follower, {
@@ -282,7 +282,7 @@ export class UserFollowingService {
}).then(async packed => {
this.globalEventService.publishUserEvent(follower.id, 'unfollow', packed);
this.globalEventService.publishMainStream(follower.id, 'unfollow', packed);
-
+
const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow'));
for (const webhook of webhooks) {
this.queueService.webhookDeliver(webhook, 'unfollow', {
@@ -291,33 +291,33 @@ export class UserFollowingService {
}
});
}
-
+
if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower, followee), follower));
this.queueService.deliver(follower, content, followee.inbox, false);
}
-
+
if (this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower)) {
// local user has null host
const content = this.apRendererService.addContext(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower, followee), followee));
this.queueService.deliver(followee, content, follower.inbox, false);
}
}
-
+
@bindThis
private async decrementFollowing(
- follower: {id: User['id']; host: User['host']; },
+ follower: { id: User['id']; host: User['host']; },
followee: { id: User['id']; host: User['host']; },
): Promise<void> {
this.globalEventService.publishInternalEvent('unfollow', { followerId: follower.id, followeeId: followee.id });
-
+
//#region Decrement following / followers counts
await Promise.all([
this.usersRepository.decrement({ id: follower.id }, 'followingCount', 1),
this.usersRepository.decrement({ id: followee.id }, 'followersCount', 1),
]);
//#endregion
-
+
//#region Update instance stats
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
this.federatedInstanceService.fetch(follower.host).then(i => {
@@ -331,7 +331,7 @@ export class UserFollowingService {
});
}
//#endregion
-
+
this.perUserFollowingChart.update(follower, followee, false);
}
@@ -346,23 +346,23 @@ export class UserFollowingService {
requestId?: string,
): Promise<void> {
if (follower.id === followee.id) return;
-
+
// check blocking
const [blocking, blocked] = await Promise.all([
this.userBlockingService.checkBlocked(follower.id, followee.id),
this.userBlockingService.checkBlocked(followee.id, follower.id),
]);
-
+
if (blocking) throw new Error('blocking');
if (blocked) throw new Error('blocked');
-
+
const followRequest = await this.followRequestsRepository.insert({
id: this.idService.genId(),
createdAt: new Date(),
followerId: follower.id,
followeeId: followee.id,
requestId,
-
+
// 非正規化
followerHost: follower.host,
followerInbox: this.userEntityService.isRemoteUser(follower) ? follower.inbox : undefined,
@@ -371,22 +371,22 @@ export class UserFollowingService {
followeeInbox: this.userEntityService.isRemoteUser(followee) ? followee.inbox : undefined,
followeeSharedInbox: this.userEntityService.isRemoteUser(followee) ? followee.sharedInbox : undefined,
}).then(x => this.followRequestsRepository.findOneByOrFail(x.identifiers[0]));
-
+
// Publish receiveRequest event
if (this.userEntityService.isLocalUser(followee)) {
this.userEntityService.pack(follower.id, followee).then(packed => this.globalEventService.publishMainStream(followee.id, 'receiveFollowRequest', packed));
-
+
this.userEntityService.pack(followee.id, followee, {
detail: true,
}).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed));
-
+
// 通知を作成
- this.createNotificationService.createNotification(followee.id, 'receiveFollowRequest', {
+ this.notificationService.createNotification(followee.id, 'receiveFollowRequest', {
notifierId: follower.id,
followRequestId: followRequest.id,
});
}
-
+
if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
const content = this.apRendererService.addContext(this.apRendererService.renderFollow(follower, followee));
this.queueService.deliver(follower, content, followee.inbox, false);
@@ -404,26 +404,26 @@ export class UserFollowingService {
): Promise<void> {
if (this.userEntityService.isRemoteUser(followee)) {
const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower, followee), follower));
-
+
if (this.userEntityService.isLocalUser(follower)) { // 本来このチェックは不要だけどTSに怒られるので
this.queueService.deliver(follower, content, followee.inbox, false);
}
}
-
+
const request = await this.followRequestsRepository.findOneBy({
followeeId: followee.id,
followerId: follower.id,
});
-
+
if (request == null) {
throw new IdentifiableError('17447091-ce07-46dd-b331-c1fd4f15b1e7', 'request not found');
}
-
+
await this.followRequestsRepository.delete({
followeeId: followee.id,
followerId: follower.id,
});
-
+
this.userEntityService.pack(followee.id, followee, {
detail: true,
}).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed));
@@ -440,18 +440,18 @@ export class UserFollowingService {
followeeId: followee.id,
followerId: follower.id,
});
-
+
if (request == null) {
throw new IdentifiableError('8884c2dd-5795-4ac9-b27e-6a01d38190f9', 'No follow request.');
}
-
+
await this.insertFollowingDoc(followee, follower);
-
+
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, request.requestId!), followee));
this.queueService.deliver(followee, content, follower.inbox, false);
}
-
+
this.userEntityService.pack(followee.id, followee, {
detail: true,
}).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed));
@@ -466,13 +466,13 @@ export class UserFollowingService {
const requests = await this.followRequestsRepository.findBy({
followeeId: user.id,
});
-
+
for (const request of requests) {
const follower = await this.usersRepository.findOneByOrFail({ id: request.followerId });
this.acceptFollowRequest(user, follower);
}
}
-
+
/**
* API following/request/reject
*/
diff --git a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts
index 037dfa1a53..501ed4090a 100644
--- a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts
+++ b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts
@@ -3,11 +3,11 @@ import { DI } from '@/di-symbols.js';
import type { PollVotesRepository, NotesRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
import type Logger from '@/logger.js';
-import { CreateNotificationService } from '@/core/CreateNotificationService.js';
+import { NotificationService } from '@/core/NotificationService.js';
+import { bindThis } from '@/decorators.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type Bull from 'bull';
import type { EndedPollNotificationJobData } from '../types.js';
-import { bindThis } from '@/decorators.js';
@Injectable()
export class EndedPollNotificationProcessorService {
@@ -23,7 +23,7 @@ export class EndedPollNotificationProcessorService {
@Inject(DI.pollVotesRepository)
private pollVotesRepository: PollVotesRepository,
- private createNotificationService: CreateNotificationService,
+ private notificationService: NotificationService,
private queueLoggerService: QueueLoggerService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('ended-poll-notification');
@@ -47,7 +47,7 @@ export class EndedPollNotificationProcessorService {
const userIds = [...new Set([note.userId, ...votes.map(v => v.userId)])];
for (const userId of userIds) {
- this.createNotificationService.createNotification(userId, 'pollEnded', {
+ this.notificationService.createNotification(userId, 'pollEnded', {
noteId: note.id,
});
}
diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
index 1bbd79fe1e..2a44dc537e 100644
--- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
+++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
@@ -8,7 +8,6 @@ import { QueueService } from '@/core/QueueService.js';
import { PollService } from '@/core/PollService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
-import { CreateNotificationService } from '@/core/CreateNotificationService.js';
import { DI } from '@/di-symbols.js';
import { UserBlockingService } from '@/core/UserBlockingService.js';
import { ApiError } from '../../../error.js';
@@ -89,7 +88,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private pollService: PollService,
private apRendererService: ApRendererService,
private globalEventService: GlobalEventService,
- private createNotificationService: CreateNotificationService,
private userBlockingService: UserBlockingService,
) {
super(meta, paramDef, async (ps, me) => {
diff --git a/packages/backend/src/server/api/endpoints/notifications/create.ts b/packages/backend/src/server/api/endpoints/notifications/create.ts
index 2e63eee263..4102a924ad 100644
--- a/packages/backend/src/server/api/endpoints/notifications/create.ts
+++ b/packages/backend/src/server/api/endpoints/notifications/create.ts
@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import { CreateNotificationService } from '@/core/CreateNotificationService.js';
+import { NotificationService } from '@/core/NotificationService.js';
export const meta = {
tags: ['notifications'],
@@ -27,10 +27,10 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
- private createNotificationService: CreateNotificationService,
+ private notificationService: NotificationService,
) {
super(meta, paramDef, async (ps, user, token) => {
- this.createNotificationService.createNotification(user.id, 'app', {
+ this.notificationService.createNotification(user.id, 'app', {
appAccessTokenId: token ? token.id : null,
customBody: ps.body,
customHeader: ps.header,