From 8f77350089eebcf4fe07eb9fadd7cff9ea9fef11 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 23 Sep 2023 12:38:20 +0900 Subject: enhance: PWAのshort_nameを設定可能に MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/server/api/endpoints/admin/meta.ts | 1 + packages/backend/src/server/api/endpoints/admin/update-meta.ts | 5 +++++ 2 files changed, 6 insertions(+) (limited to 'packages/backend/src/server/api/endpoints/admin') diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index fe9c134d84..c3ba07cdd0 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -321,6 +321,7 @@ export default class extends Endpoint { // eslint- maintainerEmail: instance.maintainerEmail, version: this.config.version, name: instance.name, + shortName: instance.shortName, uri: this.config.url, description: instance.description, langs: instance.langs, diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 614e0a95d7..eabf1f306c 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -44,6 +44,7 @@ export const paramDef = { backgroundImageUrl: { type: 'string', nullable: true }, logoImageUrl: { type: 'string', nullable: true }, name: { type: 'string', nullable: true }, + shortName: { type: 'string', nullable: true }, description: { type: 'string', nullable: true }, defaultLightTheme: { type: 'string', nullable: true }, defaultDarkTheme: { type: 'string', nullable: true }, @@ -188,6 +189,10 @@ export default class extends Endpoint { // eslint- set.name = ps.name; } + if (ps.shortName !== undefined) { + set.shortName = ps.shortName; + } + if (ps.description !== undefined) { set.description = ps.description; } -- cgit v1.2.3-freya From 9e4d3ebe5f67244c18e1fb03d6d0264bff226b76 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 23 Sep 2023 18:28:16 +0900 Subject: enhance(backend): refine moderation log (#10939) * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Update DriveService.ts --- CHANGELOG.md | 1 + locales/index.d.ts | 15 ++++ locales/ja-JP.yml | 15 ++++ packages/backend/src/core/AnnouncementService.ts | 15 +++- packages/backend/src/core/DriveService.ts | 23 +++++-- packages/backend/src/core/ModerationLogService.ts | 5 +- packages/backend/src/core/NoteDeleteService.ts | 12 +++- packages/backend/src/core/RoleService.ts | 46 ++++++++++++- .../api/endpoints/admin/announcements/create.ts | 2 +- .../src/server/api/endpoints/admin/emoji/add.ts | 2 +- .../src/server/api/endpoints/admin/queue/clear.ts | 2 +- .../server/api/endpoints/admin/queue/promote.ts | 2 +- .../src/server/api/endpoints/admin/roles/assign.ts | 2 +- .../server/api/endpoints/admin/roles/unassign.ts | 2 +- .../src/server/api/endpoints/admin/roles/update.ts | 15 ++-- .../api/endpoints/admin/show-moderation-logs.ts | 10 +++ .../src/server/api/endpoints/admin/suspend-user.ts | 2 +- .../server/api/endpoints/admin/unsuspend-user.ts | 2 +- .../src/server/api/endpoints/admin/update-meta.ts | 10 ++- .../server/api/endpoints/admin/update-user-note.ts | 11 +++ .../src/server/api/endpoints/drive/files/delete.ts | 6 +- .../src/server/api/endpoints/notes/delete.ts | 2 +- packages/backend/src/types.ts | 79 ++++++++++++++++++++++ packages/backend/test/unit/AnnouncementService.ts | 18 +++-- packages/frontend/src/pages/admin/index.vue | 5 ++ .../frontend/src/pages/admin/modlog.ModLog.vue | 57 ++++++++++++++++ packages/frontend/src/pages/admin/modlog.vue | 67 ++++++++++++++++++ packages/frontend/src/router.ts | 4 ++ packages/misskey-js/etc/misskey-js.api.md | 48 ++++++++++++- packages/misskey-js/src/consts.ts | 79 ++++++++++++++++++++++ packages/misskey-js/src/entities.ts | 42 ++++++++++++ packages/misskey-js/src/index.ts | 1 + 32 files changed, 563 insertions(+), 39 deletions(-) create mode 100644 packages/frontend/src/pages/admin/modlog.ModLog.vue create mode 100644 packages/frontend/src/pages/admin/modlog.vue (limited to 'packages/backend/src/server/api/endpoints/admin') diff --git a/CHANGELOG.md b/CHANGELOG.md index dc99ee33fe..62810ebf44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ - Feat: 二要素認証でパスキーをサポートするようになりました - Feat: 指定したユーザーが投稿したときに通知できるようになりました - Feat: プロフィールでのリンク検証 +- Feat: モデレーションログ機能 - Feat: 通知をテストできるようになりました - Feat: PWAのアイコンが設定できるようになりました - Enhance: サーバー名の略称が設定できるようになりました diff --git a/locales/index.d.ts b/locales/index.d.ts index da60550193..fd99f10b69 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -421,6 +421,7 @@ export interface Locale { "moderation": string; "moderationNote": string; "addModerationNote": string; + "moderationLogs": string; "nUsersMentioned": string; "securityKeyAndPasskey": string; "securityKey": string; @@ -2248,6 +2249,20 @@ export interface Locale { "mention": string; }; }; + "_moderationLogTypes": { + "assignRole": string; + "unassignRole": string; + "updateRole": string; + "suspend": string; + "unsuspend": string; + "addCustomEmoji": string; + "updateServerSettings": string; + "updateUserNote": string; + "deleteDriveFile": string; + "deleteNote": string; + "createGlobalAnnouncement": string; + "createUserAnnouncement": string; + }; } declare const locales: { [lang: string]: Locale; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 0869c0c455..b396014ee2 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -418,6 +418,7 @@ moderator: "モデレーター" moderation: "モデレーション" moderationNote: "モデレーションノート" addModerationNote: "モデレーションノートを追加する" +moderationLogs: "モデログ" nUsersMentioned: "{n}人が投稿" securityKeyAndPasskey: "セキュリティキー・パスキー" securityKey: "セキュリティキー" @@ -2160,3 +2161,17 @@ _webhookSettings: renote: "Renoteされたとき" reaction: "リアクションがあったとき" mention: "メンションされたとき" + +_moderationLogTypes: + assignRole: "ロールへアサイン" + unassignRole: "ロールのアサイン解除" + updateRole: "ロール設定更新" + suspend: "凍結" + unsuspend: "凍結解除" + 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/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index e015d3dc41..2ff062142c 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -42,6 +42,7 @@ import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; import { correctFilename } from '@/misc/correct-filename.js'; import { isMimeImage } from '@/misc/is-mime-image.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; type AddFileArgs = { /** User who wish to add file */ @@ -119,6 +120,7 @@ export class DriveService { private globalEventService: GlobalEventService, private queueService: QueueService, private roleService: RoleService, + private moderationLogService: ModerationLogService, private driveChart: DriveChart, private perUserDriveChart: PerUserDriveChart, private instanceChart: InstanceChart, @@ -648,7 +650,7 @@ export class DriveService { } @bindThis - public async deleteFile(file: MiDriveFile, isExpired = false) { + public async deleteFile(file: MiDriveFile, isExpired = false, deleter?: MiUser) { if (file.storedInternal) { this.internalStorageService.del(file.accessKey!); @@ -671,11 +673,11 @@ export class DriveService { } } - this.deletePostProcess(file, isExpired); + this.deletePostProcess(file, isExpired, deleter); } @bindThis - public async deleteFileSync(file: MiDriveFile, isExpired = false) { + public async deleteFileSync(file: MiDriveFile, isExpired = false, deleter?: MiUser) { if (file.storedInternal) { this.internalStorageService.del(file.accessKey!); @@ -702,11 +704,11 @@ export class DriveService { await Promise.all(promises); } - this.deletePostProcess(file, isExpired); + this.deletePostProcess(file, isExpired, deleter); } @bindThis - private async deletePostProcess(file: MiDriveFile, isExpired = false) { + private async deletePostProcess(file: MiDriveFile, isExpired = false, deleter?: MiUser) { // リモートファイル期限切れ削除後は直リンクにする if (isExpired && file.userHost !== null && file.uri != null) { this.driveFilesRepository.update(file.id, { @@ -733,6 +735,17 @@ export class DriveService { this.instanceChart.updateDrive(file, false); } } + + if (file.userId) { + this.globalEventService.publishDriveStream(file.userId, 'fileDeleted', file.id); + } + + if (deleter && await this.roleService.isModerator(deleter) && (file.userId !== deleter.id)) { + this.moderationLogService.log(deleter, 'deleteDriveFile', { + fileId: file.id, + fileUserId: file.userId, + }); + } } @bindThis diff --git a/packages/backend/src/core/ModerationLogService.ts b/packages/backend/src/core/ModerationLogService.ts index b0e5b794d0..f7f9063d92 100644 --- a/packages/backend/src/core/ModerationLogService.ts +++ b/packages/backend/src/core/ModerationLogService.ts @@ -9,6 +9,7 @@ import type { ModerationLogsRepository } from '@/models/_.js'; import type { MiUser } from '@/models/User.js'; import { IdService } from '@/core/IdService.js'; import { bindThis } from '@/decorators.js'; +import { ModerationLogPayloads, moderationLogTypes } from '@/types.js'; @Injectable() export class ModerationLogService { @@ -21,13 +22,13 @@ export class ModerationLogService { } @bindThis - public async insertModerationLog(moderator: { id: MiUser['id'] }, type: string, info?: Record) { + public async log(moderator: { id: MiUser['id'] }, type: T, info?: ModerationLogPayloads[T]) { await this.moderationLogsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), userId: moderator.id, type: type, - info: info ?? {}, + info: (info as any) ?? {}, }); } } diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index 69fff36a02..c99f92b9cb 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -23,6 +23,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { bindThis } from '@/decorators.js'; import { MetaService } from '@/core/MetaService.js'; import { SearchService } from '@/core/SearchService.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; @Injectable() export class NoteDeleteService { @@ -48,6 +49,7 @@ export class NoteDeleteService { private apDeliverManagerService: ApDeliverManagerService, private metaService: MetaService, private searchService: SearchService, + private moderationLogService: ModerationLogService, private notesChart: NotesChart, private perUserNotesChart: PerUserNotesChart, private instanceChart: InstanceChart, @@ -58,7 +60,7 @@ export class NoteDeleteService { * @param user 投稿者 * @param note 投稿 */ - async delete(user: { id: MiUser['id']; uri: MiUser['uri']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote, quiet = false) { + async delete(user: { id: MiUser['id']; uri: MiUser['uri']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote, quiet = false, deleter?: MiUser) { const deletedAt = new Date(); const cascadingNotes = await this.findCascadingNotes(note); @@ -131,6 +133,14 @@ export class NoteDeleteService { id: note.id, userId: user.id, }); + + if (deleter && (note.userId !== deleter.id)) { + this.moderationLogService.log(deleter, 'deleteNote', { + noteId: note.id, + noteUserId: note.userId, + note: note, + }); + } } @bindThis diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 518f283695..39f21ecec4 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -18,6 +18,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { StreamMessages } from '@/server/api/stream/types.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; import type { Packed } from '@/misc/json-schema.js'; import type { OnApplicationShutdown } from '@nestjs/common'; @@ -98,6 +99,7 @@ export class RoleService implements OnApplicationShutdown { private userEntityService: UserEntityService, private globalEventService: GlobalEventService, private idService: IdService, + private moderationLogService: ModerationLogService, ) { //this.onMessage = this.onMessage.bind(this); @@ -374,9 +376,11 @@ export class RoleService implements OnApplicationShutdown { } @bindThis - public async assign(userId: MiUser['id'], roleId: MiRole['id'], expiresAt: Date | null = null): Promise { + public async assign(userId: MiUser['id'], roleId: MiRole['id'], expiresAt: Date | null = null, moderator?: MiUser): Promise { const now = new Date(); + const role = await this.rolesRepository.findOneByOrFail({ id: roleId }); + const existing = await this.roleAssignmentsRepository.findOneBy({ roleId: roleId, userId: userId, @@ -406,10 +410,19 @@ export class RoleService implements OnApplicationShutdown { }); this.globalEventService.publishInternalEvent('userRoleAssigned', created); + + if (moderator) { + this.moderationLogService.log(moderator, 'assignRole', { + roleId: roleId, + roleName: role.name, + userId: userId, + expiresAt: expiresAt ? expiresAt.toISOString() : null, + }); + } } @bindThis - public async unassign(userId: MiUser['id'], roleId: MiRole['id']): Promise { + public async unassign(userId: MiUser['id'], roleId: MiRole['id'], moderator?: MiUser): Promise { const now = new Date(); const existing = await this.roleAssignmentsRepository.findOneBy({ roleId, userId }); @@ -430,6 +443,15 @@ export class RoleService implements OnApplicationShutdown { }); this.globalEventService.publishInternalEvent('userRoleUnassigned', existing); + + if (moderator) { + const role = await this.rolesRepository.findOneByOrFail({ id: roleId }); + this.moderationLogService.log(moderator, 'unassignRole', { + roleId: roleId, + roleName: role.name, + userId: userId, + }); + } } @bindThis @@ -451,6 +473,26 @@ export class RoleService implements OnApplicationShutdown { redisPipeline.exec(); } + @bindThis + public async update(role: MiRole, params: Partial, moderator?: MiUser): Promise { + const date = new Date(); + await this.rolesRepository.update(role.id, { + updatedAt: date, + ...params, + }); + + const updated = await this.rolesRepository.findOneByOrFail({ id: role.id }); + this.globalEventService.publishInternalEvent('roleUpdated', updated); + + if (moderator) { + this.moderationLogService.log(moderator, 'updateRole', { + roleId: role.id, + before: role, + after: updated, + }); + } + } + @bindThis public dispose(): void { this.redisForSub.off('message', this.onMessage); 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/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index 7bd920c312..fc297c4702 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -79,7 +79,7 @@ export default class extends Endpoint { // eslint- roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [], }); - this.moderationLogService.insertModerationLog(me, 'addEmoji', { + this.moderationLogService.log(me, 'addCustomEmoji', { emojiId: emoji.id, }); diff --git a/packages/backend/src/server/api/endpoints/admin/queue/clear.ts b/packages/backend/src/server/api/endpoints/admin/queue/clear.ts index b61c580034..c9142e9885 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/clear.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/clear.ts @@ -30,7 +30,7 @@ export default class extends Endpoint { // eslint- super(meta, paramDef, async (ps, me) => { this.queueService.destroy(); - this.moderationLogService.insertModerationLog(me, 'clearQueue'); + this.moderationLogService.log(me, 'clearQueue'); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/queue/promote.ts b/packages/backend/src/server/api/endpoints/admin/queue/promote.ts index 8d16cddd00..0cba5b4e25 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/promote.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/promote.ts @@ -70,7 +70,7 @@ export default class extends Endpoint { // eslint- break; } - this.moderationLogService.insertModerationLog(me, 'promoteQueue'); + this.moderationLogService.log(me, 'promoteQueue'); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/roles/assign.ts b/packages/backend/src/server/api/endpoints/admin/roles/assign.ts index 9a005982d4..a0f3edd867 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/assign.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/assign.ts @@ -83,7 +83,7 @@ export default class extends Endpoint { // eslint- return; } - await this.roleService.assign(user.id, role.id, ps.expiresAt ? new Date(ps.expiresAt) : null); + await this.roleService.assign(user.id, role.id, ps.expiresAt ? new Date(ps.expiresAt) : null, me); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/roles/unassign.ts b/packages/backend/src/server/api/endpoints/admin/roles/unassign.ts index 0a79296c05..4c27583111 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/unassign.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/unassign.ts @@ -81,7 +81,7 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchUser); } - await this.roleService.unassign(user.id, role.id); + await this.roleService.unassign(user.id, role.id, me); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/roles/update.ts b/packages/backend/src/server/api/endpoints/admin/roles/update.ts index 65fdb4b4b4..e4e59e487c 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/update.ts @@ -9,6 +9,7 @@ import type { RolesRepository } from '@/models/_.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '@/server/api/error.js'; +import { RoleService } from '@/core/RoleService.js'; export const meta = { tags: ['admin', 'role'], @@ -70,16 +71,16 @@ export default class extends Endpoint { // eslint- @Inject(DI.rolesRepository) private rolesRepository: RolesRepository, - private globalEventService: GlobalEventService, + private roleService: RoleService, ) { - super(meta, paramDef, async (ps) => { - const roleExist = await this.rolesRepository.exist({ where: { id: ps.roleId } }); - if (!roleExist) { + super(meta, paramDef, async (ps, me) => { + const role = await this.rolesRepository.findOneBy({ id: ps.roleId }); + if (role == null) { throw new ApiError(meta.errors.noSuchRole); } const date = new Date(); - await this.rolesRepository.update(ps.roleId, { + await this.roleService.update(role, { updatedAt: date, name: ps.name, description: ps.description, @@ -95,9 +96,7 @@ export default class extends Endpoint { // eslint- canEditMembersByModerator: ps.canEditMembersByModerator, displayOrder: ps.displayOrder, policies: ps.policies, - }); - const updated = await this.rolesRepository.findOneByOrFail({ id: ps.roleId }); - this.globalEventService.publishInternalEvent('roleUpdated', updated); + }, me); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts index d5f97ab149..f87a5a3574 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts @@ -62,6 +62,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, + type: { type: 'string', nullable: true }, + userId: { type: 'string', format: 'misskey:id', nullable: true }, }, required: [], } as const; @@ -78,6 +80,14 @@ export default class extends Endpoint { // eslint- super(meta, paramDef, async (ps, me) => { const query = this.queryService.makePaginationQuery(this.moderationLogsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId); + if (ps.type != null) { + query.andWhere('report.type = :type', { type: ps.type }); + } + + if (ps.userId != null) { + query.andWhere('report.userId = :userId', { userId: ps.userId }); + } + const reports = await query.limit(ps.limit).getMany(); return await this.moderationLogEntityService.packMany(reports); 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 bcf12fa4e8..89199f8bff 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -60,7 +60,7 @@ export default class extends Endpoint { // eslint- isSuspended: true, }); - this.moderationLogService.insertModerationLog(me, 'suspend', { + this.moderationLogService.log(me, 'suspend', { targetId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts index 59e89e15bd..a2779148ed 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts @@ -45,7 +45,7 @@ export default class extends Endpoint { // eslint- isSuspended: false, }); - this.moderationLogService.insertModerationLog(me, 'unsuspend', { + this.moderationLogService.log(me, 'unsuspend', { targetId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index eabf1f306c..ea6ebdd1fe 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -441,8 +441,16 @@ export default class extends Endpoint { // eslint- set.manifestJsonOverride = ps.manifestJsonOverride; } + const before = await this.metaService.fetch(true); + await this.metaService.update(set); - this.moderationLogService.insertModerationLog(me, 'updateMeta'); + + const after = await this.metaService.fetch(true); + + this.moderationLogService.log(me, 'updateServerSettings', { + before, + after, + }); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/update-user-note.ts b/packages/backend/src/server/api/endpoints/admin/update-user-note.ts index c86a43977e..2e9fd5ad29 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-user-note.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-user-note.ts @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UserProfilesRepository, UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; export const meta = { tags: ['admin'], @@ -32,6 +33,8 @@ export default class extends Endpoint { // eslint- @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, + + private moderationLogService: ModerationLogService, ) { super(meta, paramDef, async (ps, me) => { const user = await this.usersRepository.findOneBy({ id: ps.userId }); @@ -40,9 +43,17 @@ export default class extends Endpoint { // eslint- throw new Error('user not found'); } + const currentProfile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + await this.userProfilesRepository.update({ userId: user.id }, { moderationNote: ps.text, }); + + this.moderationLogService.log(me, 'updateUserNote', { + userId: user.id, + before: currentProfile.moderationNote, + after: ps.text, + }); }); } } diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts index d7fdc81cdb..7b67a31e08 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts @@ -65,11 +65,7 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.accessDenied); } - // Delete - await this.driveService.deleteFile(file); - - // Publish fileDeleted event - this.globalEventService.publishDriveStream(me.id, 'fileDeleted', file.id); + await this.driveService.deleteFile(file, me); }); } } diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts index 74062a58f5..55aaaf4f78 100644 --- a/packages/backend/src/server/api/endpoints/notes/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/delete.ts @@ -70,7 +70,7 @@ export default class extends Endpoint { // eslint- } // この操作を行うのが投稿者とは限らない(例えばモデレーター)ため - await this.noteDeleteService.delete(await this.usersRepository.findOneByOrFail({ id: note.userId }), note); + await this.noteDeleteService.delete(await this.usersRepository.findOneByOrFail({ id: note.userId }), note, false, me); }); } } diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 0a28d88d08..7946e66b82 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -26,3 +26,82 @@ export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const; export const ffVisibility = ['public', 'followers', 'private'] as const; + +export const moderationLogTypes = [ + 'updateServerSettings', + 'suspend', + 'unsuspend', + 'updateUserNote', + 'addCustomEmoji', + 'assignRole', + 'unassignRole', + 'updateRole', + 'deleteRole', + 'clearQueue', + 'promoteQueue', + 'deleteDriveFile', + 'deleteNote', + 'createGlobalAnnouncement', + 'createUserAnnouncement', +] as const; + +export type ModerationLogPayloads = { + updateServerSettings: { + before: any | null; + after: any | null; + }; + suspend: { + targetId: string; + }; + unsuspend: { + targetId: string; + }; + updateUserNote: { + userId: string; + before: string | null; + after: string | null; + }; + addCustomEmoji: { + emojiId: string; + }; + assignRole: { + userId: string; + roleId: string; + roleName: string; + expiresAt: string | null; + }; + unassignRole: { + userId: string; + roleId: string; + roleName: string; + }; + updateRole: { + roleId: string; + before: any; + after: any; + }; + deleteRole: { + roleId: string; + roleName: string; + }; + clearQueue: Record; + promoteQueue: Record; + deleteDriveFile: { + fileId: string; + fileUserId: string | null; + }; + deleteNote: { + 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/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue index ab4e7620dd..944ba7b950 100644 --- a/packages/frontend/src/pages/admin/index.vue +++ b/packages/frontend/src/pages/admin/index.vue @@ -145,6 +145,11 @@ const menuDef = $computed(() => [{ text: i18n.ts.abuseReports, to: '/admin/abuses', active: currentPage?.route.name === 'abuses', + }, { + icon: 'ti ti-list-search', + text: i18n.ts.moderationLogs, + to: '/admin/modlog', + active: currentPage?.route.name === 'modlog', }], }, { title: i18n.ts.settings, diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue new file mode 100644 index 0000000000..3a474f73a8 --- /dev/null +++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue @@ -0,0 +1,57 @@ + + + + + + + diff --git a/packages/frontend/src/pages/admin/modlog.vue b/packages/frontend/src/pages/admin/modlog.vue new file mode 100644 index 0000000000..da043f1b8f --- /dev/null +++ b/packages/frontend/src/pages/admin/modlog.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index e658477bbc..415d2f1974 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -395,6 +395,10 @@ export const routes = [{ path: '/abuses', name: 'abuses', component: page(() => import('./pages/admin/abuses.vue')), + }, { + path: '/modlog', + name: 'modlog', + component: page(() => import('./pages/admin/modlog.vue')), }, { path: '/settings', name: 'settings', diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 5cd679bce5..804733c066 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -2278,7 +2278,8 @@ declare namespace entities { Invite, InviteLimit, UserSorting, - OriginType + OriginType, + ModerationLog } } export { entities } @@ -2516,6 +2517,50 @@ type MessagingMessage = { groupId: UserGroup['id'] | null; }; +// @public (undocumented) +type ModerationLog = { + id: ID; + createdAt: DateString; + userId: User['id']; + user: UserDetailed | null; +} & ({ + type: 'updateServerSettings'; + info: ModerationLogPayloads['updateServerSettings']; +} | { + type: 'suspend'; + info: ModerationLogPayloads['suspend']; +} | { + type: 'unsuspend'; + info: ModerationLogPayloads['unsuspend']; +} | { + type: 'updateUserNote'; + info: ModerationLogPayloads['updateUserNote']; +} | { + type: 'addCustomEmoji'; + info: ModerationLogPayloads['addCustomEmoji']; +} | { + type: 'assignRole'; + info: ModerationLogPayloads['assignRole']; +} | { + type: 'unassignRole'; + info: ModerationLogPayloads['unassignRole']; +} | { + type: 'updateRole'; + info: ModerationLogPayloads['updateRole']; +} | { + type: 'deleteRole'; + info: ModerationLogPayloads['deleteRole']; +} | { + type: 'clearQueue'; + info: ModerationLogPayloads['clearQueue']; +} | { + type: 'promoteQueue'; + info: ModerationLogPayloads['promoteQueue']; +}); + +// @public (undocumented) +export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "assignRole", "unassignRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement"]; + // @public (undocumented) export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"]; @@ -2861,6 +2906,7 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u // src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts // src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts // src/api.types.ts:631:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts +// src/entities.ts:579:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts // src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts index 6cf6dc07e7..346affc6a5 100644 --- a/packages/misskey-js/src/consts.ts +++ b/packages/misskey-js/src/consts.ts @@ -44,3 +44,82 @@ export const permissions = [ 'read:flash-likes', 'write:flash-likes', ]; + +export const moderationLogTypes = [ + 'updateServerSettings', + 'suspend', + 'unsuspend', + 'updateUserNote', + 'addCustomEmoji', + 'assignRole', + 'unassignRole', + 'updateRole', + 'deleteRole', + 'clearQueue', + 'promoteQueue', + 'deleteDriveFile', + 'deleteNote', + 'createGlobalAnnouncement', + 'createUserAnnouncement', +] as const; + +export type ModerationLogPayloads = { + updateServerSettings: { + before: any | null; + after: any | null; + }; + suspend: { + targetId: string; + }; + unsuspend: { + targetId: string; + }; + updateUserNote: { + userId: string; + before: string | null; + after: string | null; + }; + addCustomEmoji: { + emojiId: string; + }; + assignRole: { + userId: string; + roleId: string; + roleName: string; + expiresAt: string | null; + }; + unassignRole: { + userId: string; + roleId: string; + roleName: string; + }; + updateRole: { + roleId: string; + before: any; + after: any; + }; + deleteRole: { + roleId: string; + roleName: string; + }; + clearQueue: Record; + promoteQueue: Record; + deleteDriveFile: { + fileId: string; + fileUserId: string | null; + }; + deleteNote: { + noteId: string; + noteUserId: string; + note: any; + }; + createGlobalAnnouncement: { + announcementId: string; + announcement: any; + }; + createUserAnnouncement: { + announcementId: string; + announcement: any; + userId: string; + }; +}; diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index 034201f9b9..0e6604cbaa 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -1,3 +1,5 @@ +import { ModerationLogPayloads } from './consts.js'; + export type ID = string; export type DateString = string; @@ -566,3 +568,43 @@ export type UserSorting = | '+updatedAt' | '-updatedAt'; export type OriginType = 'combined' | 'local' | 'remote'; + +export type ModerationLog = { + id: ID; + createdAt: DateString; + userId: User['id']; + user: UserDetailed | null; +} & ({ + type: 'updateServerSettings'; + info: ModerationLogPayloads['updateServerSettings']; +} | { + type: 'suspend'; + info: ModerationLogPayloads['suspend']; +} | { + type: 'unsuspend'; + info: ModerationLogPayloads['unsuspend']; +} | { + type: 'updateUserNote'; + info: ModerationLogPayloads['updateUserNote']; +} | { + type: 'addCustomEmoji'; + info: ModerationLogPayloads['addCustomEmoji']; +} | { + type: 'assignRole'; + info: ModerationLogPayloads['assignRole']; +} | { + type: 'unassignRole'; + info: ModerationLogPayloads['unassignRole']; +} | { + type: 'updateRole'; + info: ModerationLogPayloads['updateRole']; +} | { + type: 'deleteRole'; + info: ModerationLogPayloads['deleteRole']; +} | { + type: 'clearQueue'; + info: ModerationLogPayloads['clearQueue']; +} | { + type: 'promoteQueue'; + info: ModerationLogPayloads['promoteQueue']; +}); diff --git a/packages/misskey-js/src/index.ts b/packages/misskey-js/src/index.ts index ae4dd31fe0..e78501fdfd 100644 --- a/packages/misskey-js/src/index.ts +++ b/packages/misskey-js/src/index.ts @@ -17,6 +17,7 @@ export const notificationTypes = consts.notificationTypes; export const noteVisibilities = consts.noteVisibilities; export const mutedNoteReasons = consts.mutedNoteReasons; export const ffVisibility = consts.ffVisibility; +export const moderationLogTypes = consts.moderationLogTypes; // api extractor not supported yet //export * as api from './api.js'; -- cgit v1.2.3-freya From 19bc9c20a651be62286573377348f4c61ae96587 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 23 Sep 2023 20:50:02 +0900 Subject: improve moderation log --- locales/ja-JP.yml | 3 +++ .../api/endpoints/admin/federation/update-instance.ts | 18 +++++++++++++++++- .../src/server/api/endpoints/admin/reset-password.ts | 9 ++++++++- packages/backend/src/types.ts | 14 ++++++++++++++ packages/misskey-js/etc/misskey-js.api.md | 11 ++++++++++- packages/misskey-js/src/consts.ts | 14 ++++++++++++++ packages/misskey-js/src/entities.ts | 9 +++++++++ 7 files changed, 75 insertions(+), 3 deletions(-) (limited to 'packages/backend/src/server/api/endpoints/admin') diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index b396014ee2..47bbb0aa55 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2175,3 +2175,6 @@ _moderationLogTypes: deleteNote: "ノートを削除" createGlobalAnnouncement: "全体のお知らせを作成" createUserAnnouncement: "ユーザーへお知らせを作成" + resetPassword: "パスワードをリセット" + suspendRemoteInstance: "リモートサーバーを停止" + unsuspendRemoteInstance: "リモートサーバーを再開" diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts index fbb91837f2..357bf83e87 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts @@ -9,6 +9,7 @@ import type { InstancesRepository } from '@/models/_.js'; import { UtilityService } from '@/core/UtilityService.js'; import { DI } from '@/di-symbols.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; export const meta = { tags: ['admin'], @@ -34,6 +35,7 @@ export default class extends Endpoint { // eslint- private utilityService: UtilityService, private federatedInstanceService: FederatedInstanceService, + private moderationLogService: ModerationLogService, ) { super(meta, paramDef, async (ps, me) => { const instance = await this.instancesRepository.findOneBy({ host: this.utilityService.toPuny(ps.host) }); @@ -42,9 +44,23 @@ export default class extends Endpoint { // eslint- throw new Error('instance not found'); } - this.federatedInstanceService.update(instance.id, { + await this.federatedInstanceService.update(instance.id, { isSuspended: ps.isSuspended, }); + + if (instance.isSuspended !== ps.isSuspended) { + if (ps.isSuspended) { + this.moderationLogService.log(me, 'suspendRemoteInstance', { + id: instance.id, + host: instance.host, + }); + } else { + this.moderationLogService.log(me, 'unsuspendRemoteInstance', { + id: instance.id, + host: instance.host, + }); + } + } }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts index 0dd4fb4126..6ce7583276 100644 --- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts @@ -9,6 +9,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository, UserProfilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; export const meta = { tags: ['admin'], @@ -46,8 +47,10 @@ export default class extends Endpoint { // eslint- @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, + + private moderationLogService: ModerationLogService, ) { - super(meta, paramDef, async (ps) => { + super(meta, paramDef, async (ps, me) => { const user = await this.usersRepository.findOneBy({ id: ps.userId }); if (user == null) { @@ -69,6 +72,10 @@ export default class extends Endpoint { // eslint- password: hash, }); + this.moderationLogService.log(me, 'resetPassword', { + targetId: user.id, + }); + return { password: passwd, }; diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 7946e66b82..0a53e77b79 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -43,6 +43,9 @@ export const moderationLogTypes = [ 'deleteNote', 'createGlobalAnnouncement', 'createUserAnnouncement', + 'resetPassword', + 'suspendRemoteInstance', + 'unsuspendRemoteInstance', ] as const; export type ModerationLogPayloads = { @@ -104,4 +107,15 @@ export type ModerationLogPayloads = { announcement: any; userId: string; }; + resetPassword: { + targetId: string; + }; + suspendRemoteInstance: { + id: string; + host: string; + }; + unsuspendRemoteInstance: { + id: string; + host: string; + }; }; diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 804733c066..adedea8755 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -2556,10 +2556,19 @@ type ModerationLog = { } | { type: 'promoteQueue'; info: ModerationLogPayloads['promoteQueue']; +} | { + type: 'resetPassword'; + info: ModerationLogPayloads['resetPassword']; +} | { + type: 'suspendRemoteInstance'; + info: ModerationLogPayloads['suspendRemoteInstance']; +} | { + type: 'unsuspendRemoteInstance'; + info: ModerationLogPayloads['unsuspendRemoteInstance']; }); // @public (undocumented) -export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "assignRole", "unassignRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement"]; +export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "assignRole", "unassignRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance"]; // @public (undocumented) export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"]; diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts index 346affc6a5..a8962ab3d3 100644 --- a/packages/misskey-js/src/consts.ts +++ b/packages/misskey-js/src/consts.ts @@ -61,6 +61,9 @@ export const moderationLogTypes = [ 'deleteNote', 'createGlobalAnnouncement', 'createUserAnnouncement', + 'resetPassword', + 'suspendRemoteInstance', + 'unsuspendRemoteInstance', ] as const; export type ModerationLogPayloads = { @@ -122,4 +125,15 @@ export type ModerationLogPayloads = { announcement: any; userId: string; }; + resetPassword: { + targetId: string; + }; + suspendRemoteInstance: { + id: string; + host: string; + }; + unsuspendRemoteInstance: { + id: string; + host: string; + }; }; diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index 0e6604cbaa..a1fc8befb4 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -607,4 +607,13 @@ export type ModerationLog = { } | { type: 'promoteQueue'; info: ModerationLogPayloads['promoteQueue']; +} | { + type: 'resetPassword'; + info: ModerationLogPayloads['resetPassword']; +} | { + type: 'suspendRemoteInstance'; + info: ModerationLogPayloads['suspendRemoteInstance']; +} | { + type: 'unsuspendRemoteInstance'; + info: ModerationLogPayloads['unsuspendRemoteInstance']; }); -- cgit v1.2.3-freya From 2ad3b1fd74f20698be13a4beb5ee4b818ea0f090 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 24 Sep 2023 10:33:30 +0900 Subject: improve moderation log --- locales/index.d.ts | 3 +++ packages/backend/src/core/RoleService.ts | 13 +++++++++++++ .../backend/src/server/api/endpoints/admin/roles/delete.ts | 11 ++++------- packages/backend/src/types.ts | 2 +- packages/misskey-js/src/consts.ts | 2 +- 5 files changed, 22 insertions(+), 9 deletions(-) (limited to 'packages/backend/src/server/api/endpoints/admin') diff --git a/locales/index.d.ts b/locales/index.d.ts index fd99f10b69..1e5396ad63 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2262,6 +2262,9 @@ export interface Locale { "deleteNote": string; "createGlobalAnnouncement": string; "createUserAnnouncement": string; + "resetPassword": string; + "suspendRemoteInstance": string; + "unsuspendRemoteInstance": string; }; } declare const locales: { diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 39f21ecec4..dea6dc68cd 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -493,6 +493,19 @@ export class RoleService implements OnApplicationShutdown { } } + @bindThis + public async delete(role: MiRole, moderator?: MiUser): Promise { + await this.rolesRepository.delete({ id: role.id }); + this.globalEventService.publishInternalEvent('roleDeleted', role); + + if (moderator) { + this.moderationLogService.log(moderator, 'deleteRole', { + roleId: role.id, + role: role, + }); + } + } + @bindThis public dispose(): void { this.redisForSub.off('message', this.onMessage); diff --git a/packages/backend/src/server/api/endpoints/admin/roles/delete.ts b/packages/backend/src/server/api/endpoints/admin/roles/delete.ts index 6e012f6428..7b989050eb 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/delete.ts @@ -6,9 +6,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { RolesRepository } from '@/models/_.js'; -import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '@/server/api/error.js'; +import { RoleService } from '@/core/RoleService.js'; export const meta = { tags: ['admin', 'role'], @@ -41,17 +41,14 @@ export default class extends Endpoint { // eslint- @Inject(DI.rolesRepository) private rolesRepository: RolesRepository, - private globalEventService: GlobalEventService, + private roleService: RoleService, ) { - super(meta, paramDef, async (ps) => { + super(meta, paramDef, async (ps, me) => { const role = await this.rolesRepository.findOneBy({ id: ps.roleId }); if (role == null) { throw new ApiError(meta.errors.noSuchRole); } - await this.rolesRepository.delete({ - id: ps.roleId, - }); - this.globalEventService.publishInternalEvent('roleDeleted', role); + await this.roleService.delete(role, me); }); } } diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 0a53e77b79..a58bb9585a 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -85,7 +85,7 @@ export type ModerationLogPayloads = { }; deleteRole: { roleId: string; - roleName: string; + role: any; }; clearQueue: Record; promoteQueue: Record; diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts index a8962ab3d3..a5c8c2ba00 100644 --- a/packages/misskey-js/src/consts.ts +++ b/packages/misskey-js/src/consts.ts @@ -103,7 +103,7 @@ export type ModerationLogPayloads = { }; deleteRole: { roleId: string; - roleName: string; + role: any; }; clearQueue: Record; promoteQueue: Record; -- cgit v1.2.3-freya From ed983a5baf581b28b063edfa25e0d80349e9318e Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 24 Sep 2023 10:46:09 +0900 Subject: improve moderation log --- locales/index.d.ts | 4 ++ locales/ja-JP.yml | 4 ++ packages/backend/src/core/AnnouncementService.ts | 77 +++++++++++++++++++--- .../api/endpoints/admin/announcements/delete.ts | 5 +- .../api/endpoints/admin/announcements/update.ts | 7 +- packages/backend/src/types.ts | 22 +++++++ packages/misskey-js/etc/misskey-js.api.md | 26 +++++++- packages/misskey-js/src/consts.ts | 22 +++++++ packages/misskey-js/src/entities.ts | 24 +++++++ 9 files changed, 177 insertions(+), 14 deletions(-) (limited to 'packages/backend/src/server/api/endpoints/admin') diff --git a/locales/index.d.ts b/locales/index.d.ts index 1e5396ad63..c2f50dd54a 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2262,6 +2262,10 @@ export interface Locale { "deleteNote": string; "createGlobalAnnouncement": string; "createUserAnnouncement": string; + "updateGlobalAnnouncement": string; + "updateUserAnnouncement": string; + "deleteGlobalAnnouncement": string; + "deleteUserAnnouncement": string; "resetPassword": string; "suspendRemoteInstance": string; "unsuspendRemoteInstance": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 47bbb0aa55..43a3394264 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2175,6 +2175,10 @@ _moderationLogTypes: deleteNote: "ノートを削除" createGlobalAnnouncement: "全体のお知らせを作成" createUserAnnouncement: "ユーザーへお知らせを作成" + updateGlobalAnnouncement: "全体のお知らせを更新" + updateUserAnnouncement: "ユーザーのお知らせを更新" + deleteGlobalAnnouncement: "全体のお知らせを削除" + deleteUserAnnouncement: "ユーザーのお知らせを削除" resetPassword: "パスワードをリセット" suspendRemoteInstance: "リモートサーバーを停止" unsuspendRemoteInstance: "リモートサーバーを再開" diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts index 31fcb139ea..2b4877788a 100644 --- a/packages/backend/src/core/AnnouncementService.ts +++ b/packages/backend/src/core/AnnouncementService.ts @@ -60,7 +60,7 @@ export class AnnouncementService { } @bindThis - public async create(values: Partial, moderator: MiUser): 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(), @@ -82,20 +82,24 @@ export class AnnouncementService { announcement: packed, }); - this.moderationLogService.log(moderator, 'createUserAnnouncement', { - announcementId: announcement.id, - announcement: announcement, - userId: values.userId, - }); + if (moderator) { + 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, - }); + if (moderator) { + this.moderationLogService.log(moderator, 'createGlobalAnnouncement', { + announcementId: announcement.id, + announcement: announcement, + }); + } } return { @@ -104,6 +108,59 @@ export class AnnouncementService { }; } + @bindThis + public async update(announcement: MiAnnouncement, values: Partial, moderator?: MiUser): Promise { + await this.announcementsRepository.update(announcement.id, { + updatedAt: new Date(), + title: values.title, + text: values.text, + /* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 空の文字列の場合、nullを渡すようにするため */ + imageUrl: values.imageUrl || null, + display: values.display, + icon: values.icon, + forExistingUsers: values.forExistingUsers, + needConfirmationToRead: values.needConfirmationToRead, + isActive: values.isActive, + }); + + const after = await this.announcementsRepository.findOneByOrFail({ id: announcement.id }); + + if (moderator) { + if (announcement.userId) { + this.moderationLogService.log(moderator, 'updateUserAnnouncement', { + announcementId: announcement.id, + before: announcement, + after: after, + }); + } else { + this.moderationLogService.log(moderator, 'updateGlobalAnnouncement', { + announcementId: announcement.id, + before: announcement, + after: after, + }); + } + } + } + + @bindThis + public async delete(announcement: MiAnnouncement, moderator?: MiUser): Promise { + await this.announcementsRepository.delete(announcement.id); + + if (moderator) { + if (announcement.userId) { + this.moderationLogService.log(moderator, 'deleteUserAnnouncement', { + announcementId: announcement.id, + announcement: announcement, + }); + } else { + this.moderationLogService.log(moderator, 'deleteGlobalAnnouncement', { + announcementId: announcement.id, + announcement: announcement, + }); + } + } + } + @bindThis public async read(user: MiUser, announcementId: MiAnnouncement['id']): Promise { try { diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts index 80eb6d7a80..80ec281253 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { AnnouncementsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; +import { AnnouncementService } from '@/core/AnnouncementService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -37,13 +38,15 @@ export default class extends Endpoint { // eslint- constructor( @Inject(DI.announcementsRepository) private announcementsRepository: AnnouncementsRepository, + + private announcementService: AnnouncementService, ) { super(meta, paramDef, async (ps, me) => { const announcement = await this.announcementsRepository.findOneBy({ id: ps.id }); if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement); - await this.announcementsRepository.delete(announcement.id); + await this.announcementService.delete(announcement, me); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts index 782928048b..d36590c264 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { AnnouncementsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; +import { AnnouncementService } from '@/core/AnnouncementService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -45,13 +46,15 @@ export default class extends Endpoint { // eslint- constructor( @Inject(DI.announcementsRepository) private announcementsRepository: AnnouncementsRepository, + + private announcementService: AnnouncementService, ) { super(meta, paramDef, async (ps, me) => { const announcement = await this.announcementsRepository.findOneBy({ id: ps.id }); if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement); - await this.announcementsRepository.update(announcement.id, { + await this.announcementService.update(announcement, { updatedAt: new Date(), title: ps.title, text: ps.text, @@ -62,7 +65,7 @@ export default class extends Endpoint { // eslint- forExistingUsers: ps.forExistingUsers, needConfirmationToRead: ps.needConfirmationToRead, isActive: ps.isActive, - }); + }, me); }); } } diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index a58bb9585a..b85388d6e4 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -43,6 +43,10 @@ export const moderationLogTypes = [ 'deleteNote', 'createGlobalAnnouncement', 'createUserAnnouncement', + 'updateGlobalAnnouncement', + 'updateUserAnnouncement', + 'deleteGlobalAnnouncement', + 'deleteUserAnnouncement', 'resetPassword', 'suspendRemoteInstance', 'unsuspendRemoteInstance', @@ -107,6 +111,24 @@ export type ModerationLogPayloads = { announcement: any; userId: string; }; + updateGlobalAnnouncement: { + announcementId: string; + before: any; + after: any; + }; + updateUserAnnouncement: { + announcementId: string; + before: any; + after: any; + }; + deleteGlobalAnnouncement: { + announcementId: string; + announcement: any; + }; + deleteUserAnnouncement: { + announcementId: string; + announcement: any; + }; resetPassword: { targetId: string; }; diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index adedea8755..b5d07a394c 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -2556,6 +2556,30 @@ type ModerationLog = { } | { type: 'promoteQueue'; info: ModerationLogPayloads['promoteQueue']; +} | { + type: 'deleteDriveFile'; + info: ModerationLogPayloads['deleteDriveFile']; +} | { + type: 'deleteNote'; + info: ModerationLogPayloads['deleteNote']; +} | { + type: 'createGlobalAnnouncement'; + info: ModerationLogPayloads['createGlobalAnnouncement']; +} | { + type: 'createUserAnnouncement'; + info: ModerationLogPayloads['createUserAnnouncement']; +} | { + type: 'updateGlobalAnnouncement'; + info: ModerationLogPayloads['updateGlobalAnnouncement']; +} | { + type: 'updateUserAnnouncement'; + info: ModerationLogPayloads['updateUserAnnouncement']; +} | { + type: 'deleteGlobalAnnouncement'; + info: ModerationLogPayloads['deleteGlobalAnnouncement']; +} | { + type: 'deleteUserAnnouncement'; + info: ModerationLogPayloads['deleteUserAnnouncement']; } | { type: 'resetPassword'; info: ModerationLogPayloads['resetPassword']; @@ -2568,7 +2592,7 @@ type ModerationLog = { }); // @public (undocumented) -export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "assignRole", "unassignRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance"]; +export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "assignRole", "unassignRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance"]; // @public (undocumented) export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"]; diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts index a5c8c2ba00..dd4fd2609f 100644 --- a/packages/misskey-js/src/consts.ts +++ b/packages/misskey-js/src/consts.ts @@ -61,6 +61,10 @@ export const moderationLogTypes = [ 'deleteNote', 'createGlobalAnnouncement', 'createUserAnnouncement', + 'updateGlobalAnnouncement', + 'updateUserAnnouncement', + 'deleteGlobalAnnouncement', + 'deleteUserAnnouncement', 'resetPassword', 'suspendRemoteInstance', 'unsuspendRemoteInstance', @@ -125,6 +129,24 @@ export type ModerationLogPayloads = { announcement: any; userId: string; }; + updateGlobalAnnouncement: { + announcementId: string; + before: any; + after: any; + }; + updateUserAnnouncement: { + announcementId: string; + before: any; + after: any; + }; + deleteGlobalAnnouncement: { + announcementId: string; + announcement: any; + }; + deleteUserAnnouncement: { + announcementId: string; + announcement: any; + }; resetPassword: { targetId: string; }; diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index a1fc8befb4..b157eb59f6 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -607,6 +607,30 @@ export type ModerationLog = { } | { type: 'promoteQueue'; info: ModerationLogPayloads['promoteQueue']; +} | { + type: 'deleteDriveFile'; + info: ModerationLogPayloads['deleteDriveFile']; +} | { + type: 'deleteNote'; + info: ModerationLogPayloads['deleteNote']; +} | { + type: 'createGlobalAnnouncement'; + info: ModerationLogPayloads['createGlobalAnnouncement']; +} | { + type: 'createUserAnnouncement'; + info: ModerationLogPayloads['createUserAnnouncement']; +} | { + type: 'updateGlobalAnnouncement'; + info: ModerationLogPayloads['updateGlobalAnnouncement']; +} | { + type: 'updateUserAnnouncement'; + info: ModerationLogPayloads['updateUserAnnouncement']; +} | { + type: 'deleteGlobalAnnouncement'; + info: ModerationLogPayloads['deleteGlobalAnnouncement']; +} | { + type: 'deleteUserAnnouncement'; + info: ModerationLogPayloads['deleteUserAnnouncement']; } | { type: 'resetPassword'; info: ModerationLogPayloads['resetPassword']; -- cgit v1.2.3-freya From 8e5a90589d9203a9f5abb2c4d67151c126915b11 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 24 Sep 2023 10:57:24 +0900 Subject: improve moderation log --- locales/index.d.ts | 2 ++ locales/ja-JP.yml | 2 ++ packages/backend/src/core/CustomEmojiService.ts | 41 +++++++++++++++++++--- .../src/server/api/endpoints/admin/emoji/add.ts | 8 +---- .../api/endpoints/admin/emoji/delete-bulk.ts | 2 +- .../src/server/api/endpoints/admin/emoji/delete.ts | 2 +- .../src/server/api/endpoints/admin/emoji/update.ts | 2 +- packages/backend/src/types.ts | 12 +++++++ packages/misskey-js/etc/misskey-js.api.md | 8 ++++- packages/misskey-js/src/consts.ts | 12 +++++++ packages/misskey-js/src/entities.ts | 6 ++++ 11 files changed, 81 insertions(+), 16 deletions(-) (limited to 'packages/backend/src/server/api/endpoints/admin') diff --git a/locales/index.d.ts b/locales/index.d.ts index c2f50dd54a..0ca5919dd4 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2256,6 +2256,8 @@ export interface Locale { "suspend": string; "unsuspend": string; "addCustomEmoji": string; + "updateCustomEmoji": string; + "deleteCustomEmoji": string; "updateServerSettings": string; "updateUserNote": string; "deleteDriveFile": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 43a3394264..13ab6755e6 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2169,6 +2169,8 @@ _moderationLogTypes: suspend: "凍結" unsuspend: "凍結解除" addCustomEmoji: "カスタム絵文字追加" + updateCustomEmoji: "カスタム絵文字更新" + deleteCustomEmoji: "カスタム絵文字削除" updateServerSettings: "サーバー設定更新" updateUserNote: "モデレーションノート更新" deleteDriveFile: "ファイルを削除" diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index aa5490eba7..b14a8666e6 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -12,12 +12,13 @@ import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; import type { MiEmoji } from '@/models/Emoji.js'; -import type { EmojisRepository, MiRole } from '@/models/_.js'; +import type { EmojisRepository, MiRole, MiUser } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js'; import { UtilityService } from '@/core/UtilityService.js'; import { query } from '@/misc/prelude/url.js'; import type { Serialized } from '@/server/api/stream/types.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/; @@ -36,6 +37,7 @@ export class CustomEmojiService implements OnApplicationShutdown { private utilityService: UtilityService, private idService: IdService, private emojiEntityService: EmojiEntityService, + private moderationLogService: ModerationLogService, private globalEventService: GlobalEventService, ) { this.cache = new MemoryKVCache(1000 * 60 * 60 * 12); @@ -66,7 +68,7 @@ export class CustomEmojiService implements OnApplicationShutdown { isSensitive: boolean; localOnly: boolean; roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][]; - }): Promise { + }, moderator?: MiUser): Promise { const emoji = await this.emojisRepository.insert({ id: this.idService.genId(), updatedAt: new Date(), @@ -89,6 +91,13 @@ export class CustomEmojiService implements OnApplicationShutdown { this.globalEventService.publishBroadcastStream('emojiAdded', { emoji: await this.emojiEntityService.packDetailed(emoji.id), }); + + if (moderator) { + this.moderationLogService.log(moderator, 'addCustomEmoji', { + emojiId: emoji.id, + emoji: emoji, + }); + } } return emoji; @@ -104,7 +113,7 @@ export class CustomEmojiService implements OnApplicationShutdown { isSensitive?: boolean; localOnly?: boolean; roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][]; - }): Promise { + }, moderator?: MiUser): Promise { const emoji = await this.emojisRepository.findOneByOrFail({ id: id }); const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() }); if (sameNameEmoji != null && sameNameEmoji.id !== id) throw new Error('name already exists'); @@ -140,6 +149,14 @@ export class CustomEmojiService implements OnApplicationShutdown { emoji: updated, }); } + + if (moderator) { + this.moderationLogService.log(moderator, 'updateCustomEmoji', { + emojiId: emoji.id, + before: emoji, + after: updated, + }); + } } @bindThis @@ -231,7 +248,7 @@ export class CustomEmojiService implements OnApplicationShutdown { } @bindThis - public async delete(id: MiEmoji['id']) { + public async delete(id: MiEmoji['id'], moderator?: MiUser) { const emoji = await this.emojisRepository.findOneByOrFail({ id: id }); await this.emojisRepository.delete(emoji.id); @@ -241,16 +258,30 @@ export class CustomEmojiService implements OnApplicationShutdown { this.globalEventService.publishBroadcastStream('emojiDeleted', { emojis: [await this.emojiEntityService.packDetailed(emoji)], }); + + if (moderator) { + this.moderationLogService.log(moderator, 'deleteCustomEmoji', { + emojiId: emoji.id, + emoji: emoji, + }); + } } @bindThis - public async deleteBulk(ids: MiEmoji['id'][]) { + public async deleteBulk(ids: MiEmoji['id'][], moderator?: MiUser) { const emojis = await this.emojisRepository.findBy({ id: In(ids), }); for (const emoji of emojis) { await this.emojisRepository.delete(emoji.id); + + if (moderator) { + this.moderationLogService.log(moderator, 'deleteCustomEmoji', { + emojiId: emoji.id, + emoji: emoji, + }); + } } this.localEmojisCache.refresh(); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index fc297c4702..24d3a8a943 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -8,7 +8,6 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { DriveFilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; -import { ModerationLogService } from '@/core/ModerationLogService.js'; import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; import { ApiError } from '../../../error.js'; @@ -61,7 +60,6 @@ export default class extends Endpoint { // eslint- private customEmojiService: CustomEmojiService, private emojiEntityService: EmojiEntityService, - private moderationLogService: ModerationLogService, ) { super(meta, paramDef, async (ps, me) => { const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); @@ -77,11 +75,7 @@ export default class extends Endpoint { // eslint- isSensitive: ps.isSensitive ?? false, localOnly: ps.localOnly ?? false, roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [], - }); - - this.moderationLogService.log(me, 'addCustomEmoji', { - emojiId: emoji.id, - }); + }, me); return this.emojiEntityService.packDetailed(emoji); }); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts index 4221913049..e6c1bf317f 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts @@ -30,7 +30,7 @@ export default class extends Endpoint { // eslint- private customEmojiService: CustomEmojiService, ) { super(meta, paramDef, async (ps, me) => { - await this.customEmojiService.deleteBulk(ps.ids); + await this.customEmojiService.deleteBulk(ps.ids, me); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts index f020e22182..58aa0b9950 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts @@ -36,7 +36,7 @@ export default class extends Endpoint { // eslint- private customEmojiService: CustomEmojiService, ) { super(meta, paramDef, async (ps, me) => { - await this.customEmojiService.delete(ps.id); + await this.customEmojiService.delete(ps.id, me); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts index f01be9e27a..2d69857408 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -84,7 +84,7 @@ export default class extends Endpoint { // eslint- isSensitive: ps.isSensitive, localOnly: ps.localOnly, roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction, - }); + }, me); }); } } diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index b85388d6e4..ea78bb919a 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -33,6 +33,8 @@ export const moderationLogTypes = [ 'unsuspend', 'updateUserNote', 'addCustomEmoji', + 'updateCustomEmoji', + 'deleteCustomEmoji', 'assignRole', 'unassignRole', 'updateRole', @@ -70,6 +72,16 @@ export type ModerationLogPayloads = { }; addCustomEmoji: { emojiId: string; + emoji: any; + }; + updateCustomEmoji: { + emojiId: string; + before: any; + after: any; + }; + deleteCustomEmoji: { + emojiId: string; + emoji: any; }; assignRole: { userId: string; diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index b5d07a394c..d8b6aa44d8 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -2538,6 +2538,12 @@ type ModerationLog = { } | { type: 'addCustomEmoji'; info: ModerationLogPayloads['addCustomEmoji']; +} | { + type: 'updateCustomEmoji'; + info: ModerationLogPayloads['updateCustomEmoji']; +} | { + type: 'deleteCustomEmoji'; + info: ModerationLogPayloads['deleteCustomEmoji']; } | { type: 'assignRole'; info: ModerationLogPayloads['assignRole']; @@ -2592,7 +2598,7 @@ type ModerationLog = { }); // @public (undocumented) -export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "assignRole", "unassignRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance"]; +export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance"]; // @public (undocumented) export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"]; diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts index dd4fd2609f..462ad16cc8 100644 --- a/packages/misskey-js/src/consts.ts +++ b/packages/misskey-js/src/consts.ts @@ -51,6 +51,8 @@ export const moderationLogTypes = [ 'unsuspend', 'updateUserNote', 'addCustomEmoji', + 'updateCustomEmoji', + 'deleteCustomEmoji', 'assignRole', 'unassignRole', 'updateRole', @@ -88,6 +90,16 @@ export type ModerationLogPayloads = { }; addCustomEmoji: { emojiId: string; + emoji: any; + }; + updateCustomEmoji: { + emojiId: string; + before: any; + after: any; + }; + deleteCustomEmoji: { + emojiId: string; + emoji: any; }; assignRole: { userId: string; diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index b157eb59f6..e6a97f0209 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -589,6 +589,12 @@ export type ModerationLog = { } | { type: 'addCustomEmoji'; info: ModerationLogPayloads['addCustomEmoji']; +} | { + type: 'updateCustomEmoji'; + info: ModerationLogPayloads['updateCustomEmoji']; +} | { + type: 'deleteCustomEmoji'; + info: ModerationLogPayloads['deleteCustomEmoji']; } | { type: 'assignRole'; info: ModerationLogPayloads['assignRole']; -- cgit v1.2.3-freya From 5318532a8d1208b4f286856e98ff199bfdeacb9c Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 25 Sep 2023 10:29:12 +0900 Subject: enhance: improve moderation log --- CHANGELOG.md | 4 +- locales/index.d.ts | 5 ++- locales/ja-JP.yml | 5 ++- packages/backend/src/core/AnnouncementService.ts | 12 +++++- packages/backend/src/core/CustomEmojiService.ts | 7 ++-- packages/backend/src/core/DriveService.ts | 8 ++++ packages/backend/src/core/NoteDeleteService.ts | 3 ++ packages/backend/src/core/RoleService.ts | 46 ++++++++++++++++++++- .../server/api/endpoints/admin/reset-password.ts | 4 +- .../src/server/api/endpoints/admin/roles/create.ts | 35 ++-------------- .../src/server/api/endpoints/admin/roles/update.ts | 2 - .../src/server/api/endpoints/admin/suspend-user.ts | 4 +- .../server/api/endpoints/admin/unsuspend-user.ts | 4 +- .../server/api/endpoints/admin/update-user-note.ts | 2 + packages/backend/src/types.ts | 36 ++++++++++++++-- .../frontend/src/pages/admin/modlog.ModLog.vue | 48 ++++++++++++++++++---- packages/misskey-js/etc/misskey-js.api.md | 5 ++- packages/misskey-js/src/consts.ts | 36 ++++++++++++++-- packages/misskey-js/src/entities.ts | 3 ++ 19 files changed, 209 insertions(+), 60 deletions(-) (limited to 'packages/backend/src/server/api/endpoints/admin') diff --git a/CHANGELOG.md b/CHANGELOG.md index 37d5029575..ae5d90fff1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ ## (unreleased) ### General -- +- Enhance: モデレーションログ機能の強化 ### Client - @@ -22,7 +22,7 @@ ### Server - Fix: お知らせのページネーションが機能しない -## 2023.9.0 (unreleased) +## 2023.9.0 ### Note - meilisearchを使用する場合、v1.2以上が必要です diff --git a/locales/index.d.ts b/locales/index.d.ts index aa63c03856..5e24ecffa5 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1123,6 +1123,7 @@ export interface Locale { "unnotifyNotes": string; "authentication": string; "authenticationRequiredToContinue": string; + "dateAndTime": string; "_announcement": { "forExistingUsers": string; "forExistingUsersDescription": string; @@ -2250,9 +2251,11 @@ export interface Locale { }; }; "_moderationLogTypes": { + "createRole": string; + "deleteRole": string; + "updateRole": string; "assignRole": string; "unassignRole": string; - "updateRole": string; "suspend": string; "unsuspend": string; "addCustomEmoji": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 69c48a5e64..1af73c6201 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1120,6 +1120,7 @@ notifyNotes: "投稿を通知" unnotifyNotes: "投稿の通知を解除" authentication: "認証" authenticationRequiredToContinue: "続けるには認証を行ってください" +dateAndTime: "日時" _announcement: forExistingUsers: "既存ユーザーのみ" @@ -2163,9 +2164,11 @@ _webhookSettings: mention: "メンションされたとき" _moderationLogTypes: + createRole: "ロールを作成" + deleteRole: "ロールを削除" + updateRole: "ロールを更新" assignRole: "ロールへアサイン" unassignRole: "ロールのアサイン解除" - updateRole: "ロール設定更新" suspend: "凍結" unsuspend: "凍結解除" addCustomEmoji: "カスタム絵文字追加" diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts index 2b4877788a..ddacc0936f 100644 --- a/packages/backend/src/core/AnnouncementService.ts +++ b/packages/backend/src/core/AnnouncementService.ts @@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Brackets } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { MiUser } from '@/models/User.js'; -import type { AnnouncementReadsRepository, AnnouncementsRepository, MiAnnouncement, MiAnnouncementRead } from '@/models/_.js'; +import type { AnnouncementReadsRepository, AnnouncementsRepository, MiAnnouncement, MiAnnouncementRead, UsersRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { Packed } from '@/misc/json-schema.js'; import { IdService } from '@/core/IdService.js'; @@ -23,6 +23,9 @@ export class AnnouncementService { @Inject(DI.announcementReadsRepository) private announcementReadsRepository: AnnouncementReadsRepository, + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + private idService: IdService, private globalEventService: GlobalEventService, private moderationLogService: ModerationLogService, @@ -83,10 +86,13 @@ export class AnnouncementService { }); if (moderator) { + const user = await this.usersRepository.findOneByOrFail({ id: values.userId }); this.moderationLogService.log(moderator, 'createUserAnnouncement', { announcementId: announcement.id, announcement: announcement, userId: values.userId, + userUsername: user.username, + userHost: user.host, }); } } else { @@ -127,10 +133,14 @@ export class AnnouncementService { if (moderator) { if (announcement.userId) { + const user = await this.usersRepository.findOneByOrFail({ id: announcement.userId }); this.moderationLogService.log(moderator, 'updateUserAnnouncement', { announcementId: announcement.id, before: announcement, after: after, + userId: announcement.userId, + userUsername: user.username, + userHost: user.host, }); } else { this.moderationLogService.log(moderator, 'updateGlobalAnnouncement', { diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index b14a8666e6..1b545a124e 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -134,11 +134,11 @@ export class CustomEmojiService implements OnApplicationShutdown { this.localEmojisCache.refresh(); - const updated = await this.emojiEntityService.packDetailed(emoji.id); + const packed = await this.emojiEntityService.packDetailed(emoji.id); if (emoji.name === data.name) { this.globalEventService.publishBroadcastStream('emojiUpdated', { - emojis: [updated], + emojis: [packed], }); } else { this.globalEventService.publishBroadcastStream('emojiDeleted', { @@ -146,11 +146,12 @@ export class CustomEmojiService implements OnApplicationShutdown { }); this.globalEventService.publishBroadcastStream('emojiAdded', { - emoji: updated, + emoji: packed, }); } if (moderator) { + const updated = await this.emojisRepository.findOneByOrFail({ id: id }); this.moderationLogService.log(moderator, 'updateCustomEmoji', { emojiId: emoji.id, before: emoji, diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 366205f586..cecbec9638 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -686,15 +686,20 @@ export class DriveService { if (await this.roleService.isModerator(updater) && (file.userId !== updater.id)) { if (values.isSensitive !== undefined && values.isSensitive !== file.isSensitive) { + const user = file.userId ? await this.usersRepository.findOneByOrFail({ id: file.userId }) : null; if (values.isSensitive) { this.moderationLogService.log(updater, 'markSensitiveDriveFile', { fileId: file.id, fileUserId: file.userId, + fileUserUsername: user?.username ?? null, + fileUserHost: user?.host ?? null, }); } else { this.moderationLogService.log(updater, 'unmarkSensitiveDriveFile', { fileId: file.id, fileUserId: file.userId, + fileUserUsername: user?.username ?? null, + fileUserHost: user?.host ?? null, }); } } @@ -795,9 +800,12 @@ export class DriveService { } if (deleter && await this.roleService.isModerator(deleter) && (file.userId !== deleter.id)) { + const user = file.userId ? await this.usersRepository.findOneByOrFail({ id: file.userId }) : null; this.moderationLogService.log(deleter, 'deleteDriveFile', { fileId: file.id, fileUserId: file.userId, + fileUserUsername: user?.username ?? null, + fileUserHost: user?.host ?? null, }); } } diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index c99f92b9cb..87979f22ac 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -135,9 +135,12 @@ export class NoteDeleteService { }); if (deleter && (note.userId !== deleter.id)) { + const user = await this.usersRepository.findOneByOrFail({ id: note.userId }); this.moderationLogService.log(deleter, 'deleteNote', { noteId: note.id, noteUserId: note.userId, + noteUserUsername: user.username, + noteUserHost: user.host, note: note, }); } diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index dea6dc68cd..934b7d676b 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -412,10 +412,13 @@ export class RoleService implements OnApplicationShutdown { this.globalEventService.publishInternalEvent('userRoleAssigned', created); if (moderator) { + const user = await this.usersRepository.findOneByOrFail({ id: userId }); this.moderationLogService.log(moderator, 'assignRole', { roleId: roleId, roleName: role.name, userId: userId, + userUsername: user.username, + userHost: user.host, expiresAt: expiresAt ? expiresAt.toISOString() : null, }); } @@ -445,11 +448,16 @@ export class RoleService implements OnApplicationShutdown { this.globalEventService.publishInternalEvent('userRoleUnassigned', existing); if (moderator) { - const role = await this.rolesRepository.findOneByOrFail({ id: roleId }); + const [user, role] = await Promise.all([ + this.usersRepository.findOneByOrFail({ id: userId }), + this.rolesRepository.findOneByOrFail({ id: roleId }), + ]); this.moderationLogService.log(moderator, 'unassignRole', { roleId: roleId, roleName: role.name, userId: userId, + userUsername: user.username, + userHost: user.host, }); } } @@ -473,6 +481,42 @@ export class RoleService implements OnApplicationShutdown { redisPipeline.exec(); } + @bindThis + public async create(values: Partial, moderator?: MiUser): Promise { + const date = new Date(); + const created = await this.rolesRepository.insert({ + id: this.idService.genId(), + createdAt: date, + updatedAt: date, + lastUsedAt: date, + name: values.name, + description: values.description, + color: values.color, + iconUrl: values.iconUrl, + target: values.target, + condFormula: values.condFormula, + isPublic: values.isPublic, + isAdministrator: values.isAdministrator, + isModerator: values.isModerator, + isExplorable: values.isExplorable, + asBadge: values.asBadge, + canEditMembersByModerator: values.canEditMembersByModerator, + displayOrder: values.displayOrder, + policies: values.policies, + }).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0])); + + this.globalEventService.publishInternalEvent('roleCreated', created); + + if (moderator) { + this.moderationLogService.log(moderator, 'createRole', { + roleId: created.id, + role: created, + }); + } + + return created; + } + @bindThis public async update(role: MiRole, params: Partial, moderator?: MiUser): Promise { const date = new Date(); diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts index 6ce7583276..13e9c30ed8 100644 --- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts @@ -73,7 +73,9 @@ export default class extends Endpoint { // eslint- }); this.moderationLogService.log(me, 'resetPassword', { - targetId: user.id, + userId: user.id, + userUsername: user.username, + userHost: user.host, }); return { diff --git a/packages/backend/src/server/api/endpoints/admin/roles/create.ts b/packages/backend/src/server/api/endpoints/admin/roles/create.ts index f567b0d387..8451b1955f 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/create.ts @@ -5,11 +5,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { RolesRepository } from '@/models/_.js'; -import { GlobalEventService } from '@/core/GlobalEventService.js'; -import { DI } from '@/di-symbols.js'; -import { IdService } from '@/core/IdService.js'; import { RoleEntityService } from '@/core/entities/RoleEntityService.js'; +import { RoleService } from '@/core/RoleService.js'; export const meta = { tags: ['admin', 'role'], @@ -58,37 +55,11 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.rolesRepository) - private rolesRepository: RolesRepository, - - private globalEventService: GlobalEventService, - private idService: IdService, private roleEntityService: RoleEntityService, + private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { - const date = new Date(); - const created = await this.rolesRepository.insert({ - id: this.idService.genId(), - createdAt: date, - updatedAt: date, - lastUsedAt: date, - name: ps.name, - description: ps.description, - color: ps.color, - iconUrl: ps.iconUrl, - target: ps.target, - condFormula: ps.condFormula, - isPublic: ps.isPublic, - isAdministrator: ps.isAdministrator, - isModerator: ps.isModerator, - isExplorable: ps.isExplorable, - asBadge: ps.asBadge, - canEditMembersByModerator: ps.canEditMembersByModerator, - displayOrder: ps.displayOrder, - policies: ps.policies, - }).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0])); - - this.globalEventService.publishInternalEvent('roleCreated', created); + const created = await this.roleService.create(ps, me); return await this.roleEntityService.pack(created, me); }); diff --git a/packages/backend/src/server/api/endpoints/admin/roles/update.ts b/packages/backend/src/server/api/endpoints/admin/roles/update.ts index e4e59e487c..6031e2363e 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/update.ts @@ -79,9 +79,7 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchRole); } - const date = new Date(); await this.roleService.update(role, { - updatedAt: date, name: ps.name, description: ps.description, color: ps.color, 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 89199f8bff..9464f4b677 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -61,7 +61,9 @@ export default class extends Endpoint { // eslint- }); this.moderationLogService.log(me, 'suspend', { - targetId: user.id, + userId: user.id, + userUsername: user.username, + userHost: user.host, }); (async () => { diff --git a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts index a2779148ed..5e523bbc31 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts @@ -46,7 +46,9 @@ export default class extends Endpoint { // eslint- }); this.moderationLogService.log(me, 'unsuspend', { - targetId: user.id, + userId: user.id, + userUsername: user.username, + userHost: user.host, }); this.userSuspendService.doPostUnsuspend(user); diff --git a/packages/backend/src/server/api/endpoints/admin/update-user-note.ts b/packages/backend/src/server/api/endpoints/admin/update-user-note.ts index 2e9fd5ad29..bfccc2a2a5 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-user-note.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-user-note.ts @@ -51,6 +51,8 @@ export default class extends Endpoint { // eslint- this.moderationLogService.log(me, 'updateUserNote', { userId: user.id, + userUsername: user.username, + userHost: user.host, before: currentProfile.moderationNote, after: ps.text, }); diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 16654edd88..b458c0fbcb 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -37,6 +37,7 @@ export const moderationLogTypes = [ 'deleteCustomEmoji', 'assignRole', 'unassignRole', + 'createRole', 'updateRole', 'deleteRole', 'clearQueue', @@ -62,13 +63,19 @@ export type ModerationLogPayloads = { after: any | null; }; suspend: { - targetId: string; + userId: string; + userUsername: string; + userHost: string | null; }; unsuspend: { - targetId: string; + userId: string; + userUsername: string; + userHost: string | null; }; updateUserNote: { userId: string; + userUsername: string; + userHost: string | null; before: string | null; after: string | null; }; @@ -87,15 +94,23 @@ export type ModerationLogPayloads = { }; assignRole: { userId: string; + userUsername: string; + userHost: string | null; roleId: string; roleName: string; expiresAt: string | null; }; unassignRole: { userId: string; + userUsername: string; + userHost: string | null; roleId: string; roleName: string; }; + createRole: { + roleId: string; + role: any; + }; updateRole: { roleId: string; before: any; @@ -110,10 +125,14 @@ export type ModerationLogPayloads = { deleteDriveFile: { fileId: string; fileUserId: string | null; + fileUserUsername: string | null; + fileUserHost: string | null; }; deleteNote: { noteId: string; noteUserId: string; + noteUserUsername: string; + noteUserHost: string | null; note: any; }; createGlobalAnnouncement: { @@ -124,6 +143,8 @@ export type ModerationLogPayloads = { announcementId: string; announcement: any; userId: string; + userUsername: string; + userHost: string | null; }; updateGlobalAnnouncement: { announcementId: string; @@ -134,6 +155,9 @@ export type ModerationLogPayloads = { announcementId: string; before: any; after: any; + userId: string; + userUsername: string; + userHost: string | null; }; deleteGlobalAnnouncement: { announcementId: string; @@ -144,7 +168,9 @@ export type ModerationLogPayloads = { announcement: any; }; resetPassword: { - targetId: string; + userId: string; + userUsername: string; + userHost: string | null; }; suspendRemoteInstance: { id: string; @@ -157,9 +183,13 @@ export type ModerationLogPayloads = { markSensitiveDriveFile: { fileId: string; fileUserId: string | null; + fileUserUsername: string | null; + fileUserHost: string | null; }; unmarkSensitiveDriveFile: { fileId: string; fileUserId: string | null; + fileUserUsername: string | null; + fileUserHost: string | null; }; }; diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue index ce7b89f8f7..f0de026ad8 100644 --- a/packages/frontend/src/pages/admin/modlog.ModLog.vue +++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue @@ -5,33 +5,61 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index b3806754a8..f72532f297 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -2550,6 +2550,9 @@ type ModerationLog = { } | { type: 'unassignRole'; info: ModerationLogPayloads['unassignRole']; +} | { + type: 'createRole'; + info: ModerationLogPayloads['createRole']; } | { type: 'updateRole'; info: ModerationLogPayloads['updateRole']; @@ -2604,7 +2607,7 @@ type ModerationLog = { }); // @public (undocumented) -export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "markSensitiveDriveFile", "unmarkSensitiveDriveFile"]; +export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "markSensitiveDriveFile", "unmarkSensitiveDriveFile"]; // @public (undocumented) export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"]; diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts index 63137dcc83..d2751c447e 100644 --- a/packages/misskey-js/src/consts.ts +++ b/packages/misskey-js/src/consts.ts @@ -55,6 +55,7 @@ export const moderationLogTypes = [ 'deleteCustomEmoji', 'assignRole', 'unassignRole', + 'createRole', 'updateRole', 'deleteRole', 'clearQueue', @@ -80,13 +81,19 @@ export type ModerationLogPayloads = { after: any | null; }; suspend: { - targetId: string; + userId: string; + userUsername: string; + userHost: string | null; }; unsuspend: { - targetId: string; + userId: string; + userUsername: string; + userHost: string | null; }; updateUserNote: { userId: string; + userUsername: string; + userHost: string | null; before: string | null; after: string | null; }; @@ -105,15 +112,23 @@ export type ModerationLogPayloads = { }; assignRole: { userId: string; + userUsername: string; + userHost: string | null; roleId: string; roleName: string; expiresAt: string | null; }; unassignRole: { userId: string; + userUsername: string; + userHost: string | null; roleId: string; roleName: string; }; + createRole: { + roleId: string; + role: any; + }; updateRole: { roleId: string; before: any; @@ -128,10 +143,14 @@ export type ModerationLogPayloads = { deleteDriveFile: { fileId: string; fileUserId: string | null; + fileUserUsername: string | null; + fileUserHost: string | null; }; deleteNote: { noteId: string; noteUserId: string; + noteUserUsername: string; + noteUserHost: string | null; note: any; }; createGlobalAnnouncement: { @@ -142,6 +161,8 @@ export type ModerationLogPayloads = { announcementId: string; announcement: any; userId: string; + userUsername: string; + userHost: string | null; }; updateGlobalAnnouncement: { announcementId: string; @@ -152,6 +173,9 @@ export type ModerationLogPayloads = { announcementId: string; before: any; after: any; + userId: string; + userUsername: string; + userHost: string | null; }; deleteGlobalAnnouncement: { announcementId: string; @@ -162,7 +186,9 @@ export type ModerationLogPayloads = { announcement: any; }; resetPassword: { - targetId: string; + userId: string; + userUsername: string; + userHost: string | null; }; suspendRemoteInstance: { id: string; @@ -175,9 +201,13 @@ export type ModerationLogPayloads = { markSensitiveDriveFile: { fileId: string; fileUserId: string | null; + fileUserUsername: string | null; + fileUserHost: string | null; }; unmarkSensitiveDriveFile: { fileId: string; fileUserId: string | null; + fileUserUsername: string | null; + fileUserHost: string | null; }; }; diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index f377f1a5ed..41c9bdef6e 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -601,6 +601,9 @@ export type ModerationLog = { } | { type: 'unassignRole'; info: ModerationLogPayloads['unassignRole']; +} | { + type: 'createRole'; + info: ModerationLogPayloads['createRole']; } | { type: 'updateRole'; info: ModerationLogPayloads['updateRole']; -- cgit v1.2.3-freya From bd19d75c9c49d601cbf29e2fdcd67f3864b74175 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 25 Sep 2023 16:03:43 +0900 Subject: enhance: improve moderation log --- locales/index.d.ts | 1 + locales/ja-JP.yml | 1 + .../src/server/api/endpoints/admin/resolve-abuse-user-report.ts | 8 ++++++++ packages/backend/src/types.ts | 6 ++++++ packages/frontend/src/pages/admin/modlog.ModLog.vue | 5 +++++ packages/misskey-js/etc/misskey-js.api.md | 2 +- packages/misskey-js/src/consts.ts | 6 ++++++ 7 files changed, 28 insertions(+), 1 deletion(-) (limited to 'packages/backend/src/server/api/endpoints/admin') diff --git a/locales/index.d.ts b/locales/index.d.ts index 5e24ecffa5..4d8123eb5d 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2276,6 +2276,7 @@ export interface Locale { "unsuspendRemoteInstance": string; "markSensitiveDriveFile": string; "unmarkSensitiveDriveFile": string; + "resolveAbuseReport": string; }; } declare const locales: { diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 1af73c6201..647f5fb5f0 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2189,3 +2189,4 @@ _moderationLogTypes: unsuspendRemoteInstance: "リモートサーバーを再開" markSensitiveDriveFile: "ファイルをセンシティブ付与" unmarkSensitiveDriveFile: "ファイルをセンシティブ解除" + resolveAbuseReport: "通報を解決" diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts index 8667640a67..fb5ac7a335 100644 --- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts +++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts @@ -10,6 +10,7 @@ import { InstanceActorService } from '@/core/InstanceActorService.js'; import { QueueService } from '@/core/QueueService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { DI } from '@/di-symbols.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; export const meta = { tags: ['admin'], @@ -41,6 +42,7 @@ export default class extends Endpoint { // eslint- private queueService: QueueService, private instanceActorService: InstanceActorService, private apRendererService: ApRendererService, + private moderationLogService: ModerationLogService, ) { super(meta, paramDef, async (ps, me) => { const report = await this.abuseUserReportsRepository.findOneBy({ id: ps.reportId }); @@ -61,6 +63,12 @@ export default class extends Endpoint { // eslint- assigneeId: me.id, forwarded: ps.forward && report.targetUserHost != null, }); + + this.moderationLogService.log(me, 'resolveAbuseReport', { + reportId: report.id, + report: report, + forwarded: ps.forward && report.targetUserHost != null, + }); }); } } diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index b458c0fbcb..35ea710f9e 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -55,6 +55,7 @@ export const moderationLogTypes = [ 'unsuspendRemoteInstance', 'markSensitiveDriveFile', 'unmarkSensitiveDriveFile', + 'resolveAbuseReport', ] as const; export type ModerationLogPayloads = { @@ -192,4 +193,9 @@ export type ModerationLogPayloads = { fileUserUsername: string | null; fileUserHost: string | null; }; + resolveAbuseReport: { + reportId: string; + report: any; + forwarded: boolean; + }; }; diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue index f0de026ad8..14f94479f1 100644 --- a/packages/frontend/src/pages/admin/modlog.ModLog.vue +++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue @@ -75,6 +75,11 @@ SPDX-License-Identifier: AGPL-3.0-only + +
+ raw +
{{ JSON5.stringify(log, null, '\t') }}
+
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index f72532f297..0686354ff4 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -2607,7 +2607,7 @@ type ModerationLog = { }); // @public (undocumented) -export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "markSensitiveDriveFile", "unmarkSensitiveDriveFile"]; +export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport"]; // @public (undocumented) export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"]; diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts index d2751c447e..aedfb5570e 100644 --- a/packages/misskey-js/src/consts.ts +++ b/packages/misskey-js/src/consts.ts @@ -73,6 +73,7 @@ export const moderationLogTypes = [ 'unsuspendRemoteInstance', 'markSensitiveDriveFile', 'unmarkSensitiveDriveFile', + 'resolveAbuseReport', ] as const; export type ModerationLogPayloads = { @@ -210,4 +211,9 @@ export type ModerationLogPayloads = { fileUserUsername: string | null; fileUserHost: string | null; }; + resolveAbuseReport: { + reportId: string; + report: any; + forwarded: boolean; + }; }; -- cgit v1.2.3-freya