This commit is contained in:
syuilo 2023-09-23 18:14:56 +09:00
parent 7352387045
commit a4701edb41
6 changed files with 57 additions and 8 deletions

View File

@ -2171,3 +2171,7 @@ _moderationLogTypes:
addCustomEmoji: "カスタム絵文字追加" addCustomEmoji: "カスタム絵文字追加"
updateServerSettings: "サーバー設定更新" updateServerSettings: "サーバー設定更新"
updateUserNote: "モデレーションノート更新" updateUserNote: "モデレーションノート更新"
deleteDriveFile: "ファイルを削除"
deleteNote: "ノートを削除"
createGlobalAnnouncement: "全体のお知らせを作成"
createUserAnnouncement: "ユーザーへお知らせを作成"

View File

@ -12,6 +12,7 @@ import { bindThis } from '@/decorators.js';
import { Packed } from '@/misc/json-schema.js'; import { Packed } from '@/misc/json-schema.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
@Injectable() @Injectable()
export class AnnouncementService { export class AnnouncementService {
@ -24,6 +25,7 @@ export class AnnouncementService {
private idService: IdService, private idService: IdService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private moderationLogService: ModerationLogService,
) { ) {
} }
@ -58,7 +60,7 @@ export class AnnouncementService {
} }
@bindThis @bindThis
public async create(values: Partial<MiAnnouncement>): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> { public async create(values: Partial<MiAnnouncement>, moderator: MiUser): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> {
const announcement = await this.announcementsRepository.insert({ const announcement = await this.announcementsRepository.insert({
id: this.idService.genId(), id: this.idService.genId(),
createdAt: new Date(), createdAt: new Date(),
@ -79,10 +81,21 @@ export class AnnouncementService {
this.globalEventService.publishMainStream(values.userId, 'announcementCreated', { this.globalEventService.publishMainStream(values.userId, 'announcementCreated', {
announcement: packed, announcement: packed,
}); });
this.moderationLogService.log(moderator, 'createUserAnnouncement', {
announcementId: announcement.id,
announcement: announcement,
userId: values.userId,
});
} else { } else {
this.globalEventService.publishBroadcastStream('announcementCreated', { this.globalEventService.publishBroadcastStream('announcementCreated', {
announcement: packed, announcement: packed,
}); });
this.moderationLogService.log(moderator, 'createGlobalAnnouncement', {
announcementId: announcement.id,
announcement: announcement,
});
} }
return { return {

View File

@ -81,7 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
forExistingUsers: ps.forExistingUsers, forExistingUsers: ps.forExistingUsers,
needConfirmationToRead: ps.needConfirmationToRead, needConfirmationToRead: ps.needConfirmationToRead,
userId: ps.userId, userId: ps.userId,
}); }, me);
return packed; return packed;
}); });

View File

@ -41,6 +41,8 @@ export const moderationLogTypes = [
'promoteQueue', 'promoteQueue',
'deleteDriveFile', 'deleteDriveFile',
'deleteNote', 'deleteNote',
'createGlobalAnnouncement',
'createUserAnnouncement',
] as const; ] as const;
export type ModerationLogPayloads = { export type ModerationLogPayloads = {
@ -92,5 +94,14 @@ export type ModerationLogPayloads = {
noteId: string; noteId: string;
noteUserId: string; noteUserId: string;
note: any; note: any;
} };
createGlobalAnnouncement: {
announcementId: string;
announcement: any;
};
createUserAnnouncement: {
announcementId: string;
announcement: any;
userId: string;
};
}; };

View File

@ -16,6 +16,7 @@ import { genAidx } from '@/misc/id/aidx.js';
import { CacheService } from '@/core/CacheService.js'; import { CacheService } from '@/core/CacheService.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { secureRndstr } from '@/misc/secure-rndstr.js'; import { secureRndstr } from '@/misc/secure-rndstr.js';
import type { TestingModule } from '@nestjs/testing'; import type { TestingModule } from '@nestjs/testing';
import type { MockFunctionMetadata } from 'jest-mock'; import type { MockFunctionMetadata } from 'jest-mock';
@ -29,6 +30,7 @@ describe('AnnouncementService', () => {
let announcementsRepository: AnnouncementsRepository; let announcementsRepository: AnnouncementsRepository;
let announcementReadsRepository: AnnouncementReadsRepository; let announcementReadsRepository: AnnouncementReadsRepository;
let globalEventService: jest.Mocked<GlobalEventService>; let globalEventService: jest.Mocked<GlobalEventService>;
let moderationLogService: jest.Mocked<ModerationLogService>;
function createUser(data: Partial<MiUser> = {}) { function createUser(data: Partial<MiUser> = {}) {
const un = secureRndstr(16); const un = secureRndstr(16);
@ -71,8 +73,11 @@ describe('AnnouncementService', () => {
publishMainStream: jest.fn(), publishMainStream: jest.fn(),
publishBroadcastStream: jest.fn(), publishBroadcastStream: jest.fn(),
}; };
} } else if (token === ModerationLogService) {
if (typeof token === 'function') { return {
log: jest.fn(),
};
} else if (typeof token === 'function') {
const mockMetadata = moduleMocker.getMetadata(token) as MockFunctionMetadata<any, any>; const mockMetadata = moduleMocker.getMetadata(token) as MockFunctionMetadata<any, any>;
const Mock = moduleMocker.generateFromMetadata(mockMetadata); const Mock = moduleMocker.generateFromMetadata(mockMetadata);
return new Mock(); return new Mock();
@ -87,6 +92,7 @@ describe('AnnouncementService', () => {
announcementsRepository = app.get<AnnouncementsRepository>(DI.announcementsRepository); announcementsRepository = app.get<AnnouncementsRepository>(DI.announcementsRepository);
announcementReadsRepository = app.get<AnnouncementReadsRepository>(DI.announcementReadsRepository); announcementReadsRepository = app.get<AnnouncementReadsRepository>(DI.announcementReadsRepository);
globalEventService = app.get<GlobalEventService>(GlobalEventService) as jest.Mocked<GlobalEventService>; globalEventService = app.get<GlobalEventService>(GlobalEventService) as jest.Mocked<GlobalEventService>;
moderationLogService = app.get<ModerationLogService>(ModerationLogService) as jest.Mocked<ModerationLogService>;
}); });
afterEach(async () => { afterEach(async () => {
@ -155,10 +161,11 @@ describe('AnnouncementService', () => {
describe('create', () => { describe('create', () => {
test('通常', async () => { test('通常', async () => {
const me = await createUser();
const result = await announcementService.create({ const result = await announcementService.create({
title: 'Title', title: 'Title',
text: 'Text', text: 'Text',
}); }, me);
expect(result.raw.title).toBe('Title'); expect(result.raw.title).toBe('Title');
expect(result.packed.title).toBe('Title'); expect(result.packed.title).toBe('Title');
@ -166,15 +173,17 @@ describe('AnnouncementService', () => {
expect(globalEventService.publishBroadcastStream).toHaveBeenCalled(); expect(globalEventService.publishBroadcastStream).toHaveBeenCalled();
expect(globalEventService.publishBroadcastStream.mock.lastCall![0]).toBe('announcementCreated'); expect(globalEventService.publishBroadcastStream.mock.lastCall![0]).toBe('announcementCreated');
expect((globalEventService.publishBroadcastStream.mock.lastCall![1] as any).announcement).toBe(result.packed); expect((globalEventService.publishBroadcastStream.mock.lastCall![1] as any).announcement).toBe(result.packed);
expect(moderationLogService.log).toHaveBeenCalled();
}); });
test('ユーザー指定', async () => { test('ユーザー指定', async () => {
const me = await createUser();
const user = await createUser(); const user = await createUser();
const result = await announcementService.create({ const result = await announcementService.create({
title: 'Title', title: 'Title',
text: 'Text', text: 'Text',
userId: user.id, userId: user.id,
}); }, me);
expect(result.raw.title).toBe('Title'); expect(result.raw.title).toBe('Title');
expect(result.packed.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![0]).toBe(user.id);
expect(globalEventService.publishMainStream.mock.lastCall![1]).toBe('announcementCreated'); expect(globalEventService.publishMainStream.mock.lastCall![1]).toBe('announcementCreated');
expect((globalEventService.publishMainStream.mock.lastCall![2] as any).announcement).toBe(result.packed); expect((globalEventService.publishMainStream.mock.lastCall![2] as any).announcement).toBe(result.packed);
expect(moderationLogService.log).toHaveBeenCalled();
}); });
}); });

View File

@ -59,6 +59,8 @@ export const moderationLogTypes = [
'promoteQueue', 'promoteQueue',
'deleteDriveFile', 'deleteDriveFile',
'deleteNote', 'deleteNote',
'createGlobalAnnouncement',
'createUserAnnouncement',
] as const; ] as const;
export type ModerationLogPayloads = { export type ModerationLogPayloads = {
@ -110,5 +112,14 @@ export type ModerationLogPayloads = {
noteId: string; noteId: string;
noteUserId: string; noteUserId: string;
note: any; note: any;
} };
createGlobalAnnouncement: {
announcementId: string;
announcement: any;
};
createUserAnnouncement: {
announcementId: string;
announcement: any;
userId: string;
};
}; };