diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index d36e047d38..b396014ee2 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2171,3 +2171,7 @@ _moderationLogTypes: addCustomEmoji: "カスタム絵文字追加" updateServerSettings: "サーバー設定更新" updateUserNote: "モデレーションノート更新" + deleteDriveFile: "ファイルを削除" + deleteNote: "ノートを削除" + createGlobalAnnouncement: "全体のお知らせを作成" + createUserAnnouncement: "ユーザーへお知らせを作成" diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts index 70f37516a4..31fcb139ea 100644 --- a/packages/backend/src/core/AnnouncementService.ts +++ b/packages/backend/src/core/AnnouncementService.ts @@ -12,6 +12,7 @@ import { bindThis } from '@/decorators.js'; import { Packed } from '@/misc/json-schema.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; @Injectable() export class AnnouncementService { @@ -24,6 +25,7 @@ export class AnnouncementService { private idService: IdService, private globalEventService: GlobalEventService, + private moderationLogService: ModerationLogService, ) { } @@ -58,7 +60,7 @@ export class AnnouncementService { } @bindThis - public async create(values: Partial): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> { + public async create(values: Partial, moderator: MiUser): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> { const announcement = await this.announcementsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), @@ -79,10 +81,21 @@ export class AnnouncementService { this.globalEventService.publishMainStream(values.userId, 'announcementCreated', { announcement: packed, }); + + this.moderationLogService.log(moderator, 'createUserAnnouncement', { + announcementId: announcement.id, + announcement: announcement, + userId: values.userId, + }); } else { this.globalEventService.publishBroadcastStream('announcementCreated', { announcement: packed, }); + + this.moderationLogService.log(moderator, 'createGlobalAnnouncement', { + announcementId: announcement.id, + announcement: announcement, + }); } return { diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts index c2f69bb159..262b36b9a4 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts @@ -81,7 +81,7 @@ export default class extends Endpoint { // eslint- forExistingUsers: ps.forExistingUsers, needConfirmationToRead: ps.needConfirmationToRead, userId: ps.userId, - }); + }, me); return packed; }); diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 60844b25fb..7946e66b82 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -41,6 +41,8 @@ export const moderationLogTypes = [ 'promoteQueue', 'deleteDriveFile', 'deleteNote', + 'createGlobalAnnouncement', + 'createUserAnnouncement', ] as const; export type ModerationLogPayloads = { @@ -92,5 +94,14 @@ export type ModerationLogPayloads = { noteId: string; noteUserId: string; note: any; - } + }; + createGlobalAnnouncement: { + announcementId: string; + announcement: any; + }; + createUserAnnouncement: { + announcementId: string; + announcement: any; + userId: string; + }; }; diff --git a/packages/backend/test/unit/AnnouncementService.ts b/packages/backend/test/unit/AnnouncementService.ts index 721fbb7345..8f61d91ba9 100644 --- a/packages/backend/test/unit/AnnouncementService.ts +++ b/packages/backend/test/unit/AnnouncementService.ts @@ -16,6 +16,7 @@ import { genAidx } from '@/misc/id/aidx.js'; import { CacheService } from '@/core/CacheService.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import type { TestingModule } from '@nestjs/testing'; import type { MockFunctionMetadata } from 'jest-mock'; @@ -29,6 +30,7 @@ describe('AnnouncementService', () => { let announcementsRepository: AnnouncementsRepository; let announcementReadsRepository: AnnouncementReadsRepository; let globalEventService: jest.Mocked; + let moderationLogService: jest.Mocked; function createUser(data: Partial = {}) { const un = secureRndstr(16); @@ -71,8 +73,11 @@ describe('AnnouncementService', () => { publishMainStream: jest.fn(), publishBroadcastStream: jest.fn(), }; - } - if (typeof token === 'function') { + } else if (token === ModerationLogService) { + return { + log: jest.fn(), + }; + } else if (typeof token === 'function') { const mockMetadata = moduleMocker.getMetadata(token) as MockFunctionMetadata; const Mock = moduleMocker.generateFromMetadata(mockMetadata); return new Mock(); @@ -87,6 +92,7 @@ describe('AnnouncementService', () => { announcementsRepository = app.get(DI.announcementsRepository); announcementReadsRepository = app.get(DI.announcementReadsRepository); globalEventService = app.get(GlobalEventService) as jest.Mocked; + moderationLogService = app.get(ModerationLogService) as jest.Mocked; }); afterEach(async () => { @@ -155,10 +161,11 @@ describe('AnnouncementService', () => { describe('create', () => { test('通常', async () => { + const me = await createUser(); const result = await announcementService.create({ title: 'Title', text: 'Text', - }); + }, me); expect(result.raw.title).toBe('Title'); expect(result.packed.title).toBe('Title'); @@ -166,15 +173,17 @@ describe('AnnouncementService', () => { expect(globalEventService.publishBroadcastStream).toHaveBeenCalled(); expect(globalEventService.publishBroadcastStream.mock.lastCall![0]).toBe('announcementCreated'); expect((globalEventService.publishBroadcastStream.mock.lastCall![1] as any).announcement).toBe(result.packed); + expect(moderationLogService.log).toHaveBeenCalled(); }); test('ユーザー指定', async () => { + const me = await createUser(); const user = await createUser(); const result = await announcementService.create({ title: 'Title', text: 'Text', userId: user.id, - }); + }, me); expect(result.raw.title).toBe('Title'); expect(result.packed.title).toBe('Title'); @@ -184,6 +193,7 @@ describe('AnnouncementService', () => { expect(globalEventService.publishMainStream.mock.lastCall![0]).toBe(user.id); expect(globalEventService.publishMainStream.mock.lastCall![1]).toBe('announcementCreated'); expect((globalEventService.publishMainStream.mock.lastCall![2] as any).announcement).toBe(result.packed); + expect(moderationLogService.log).toHaveBeenCalled(); }); }); diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts index 5ca0a36165..346affc6a5 100644 --- a/packages/misskey-js/src/consts.ts +++ b/packages/misskey-js/src/consts.ts @@ -59,6 +59,8 @@ export const moderationLogTypes = [ 'promoteQueue', 'deleteDriveFile', 'deleteNote', + 'createGlobalAnnouncement', + 'createUserAnnouncement', ] as const; export type ModerationLogPayloads = { @@ -110,5 +112,14 @@ export type ModerationLogPayloads = { noteId: string; noteUserId: string; note: any; - } + }; + createGlobalAnnouncement: { + announcementId: string; + announcement: any; + }; + createUserAnnouncement: { + announcementId: string; + announcement: any; + userId: string; + }; };