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/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/update-user-note.ts b/packages/backend/src/server/api/endpoints/admin/update-user-note.ts index f3dfea3c74..813b42c216 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 @@ -43,7 +43,7 @@ export default class extends Endpoint { // eslint- throw new Error('user not found'); } - const currentProfile = await this.userProfilesRepository.findOneBy({ userId: user.id }); + const currentProfile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); await this.userProfilesRepository.update({ userId: user.id }, { moderationNote: ps.text, @@ -51,7 +51,7 @@ export default class extends Endpoint { // eslint- this.moderationLogService.log(me, 'userNoteUpdated', { userId: user.id, - before: currentProfile?.moderationNote, + before: currentProfile.moderationNote, after: ps.text, }); }); diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index bbcf513fb5..97e4b228b4 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -27,7 +27,7 @@ export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const; export const ffVisibility = ['public', 'followers', 'private'] as const; -export const moderationLogTypes = ['updateMeta', 'suspend', 'unsuspend', 'userNoteUpdated', 'addEmoji', 'roleAssigned', 'roleUnassigned', 'roleUpdated', 'roleDeleted'] as const; +export const moderationLogTypes = ['updateMeta', 'suspend', 'unsuspend', 'userNoteUpdated', 'addEmoji', 'roleAssigned', 'roleUnassigned', 'roleUpdated', 'roleDeleted', 'clearQueue', 'promoteQueue'] as const; export type ModerationLogPayloads = { updateMeta: { @@ -68,4 +68,6 @@ export type ModerationLogPayloads = { roleId: string; roleName: string; }; + clearQueue: {}; + promoteQueue: {}; }; diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue index 450dc491f6..f62ac36e0c 100644 --- a/packages/frontend/src/pages/admin/modlog.ModLog.vue +++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue @@ -14,7 +14,14 @@ SPDX-License-Identifier: AGPL-3.0-only
-
{{ i18n.ts.user }}: {{ log.userId }}
+
{{ i18n.ts.moderator }}: {{ log.userId }}
+ + +
diff --git a/packages/frontend/src/pages/admin/modlog.vue b/packages/frontend/src/pages/admin/modlog.vue index cedc38d650..ba310c1c4e 100644 --- a/packages/frontend/src/pages/admin/modlog.vue +++ b/packages/frontend/src/pages/admin/modlog.vue @@ -11,35 +11,16 @@ SPDX-License-Identifier: AGPL-3.0-only
- - + + - - - - - - - - - - - - + + +
-
@@ -59,25 +40,22 @@ import { computed } from 'vue'; import XHeader from './_header_.vue'; import XModLog from './modlog.ModLog.vue'; import MkSelect from '@/components/MkSelect.vue'; +import MkInput from '@/components/MkInput.vue'; import MkPagination from '@/components/MkPagination.vue'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; let logs = $shallowRef>(); -let state = $ref('unresolved'); -let reporterOrigin = $ref('combined'); -let targetUserOrigin = $ref('combined'); -let searchUsername = $ref(''); -let searchHost = $ref(''); +let type = $ref(null); +let moderatorId = $ref(''); const pagination = { endpoint: 'admin/show-moderation-logs' as const, limit: 30, params: computed(() => ({ - state, - reporterOrigin, - targetUserOrigin, + type, + userId: moderatorId === '' ? null : moderatorId, })), }; diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 8837e81fc5..72f8143037 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -2521,11 +2521,42 @@ type MessagingMessage = { type ModerationLog = { id: ID; createdAt: DateString; - type: string; - info: Record; userId: User['id']; user: UserDetailed | null; -}; +} & ({ + type: 'updateMeta'; + info: ModerationLogPayloads['updateMeta']; +} | { + type: 'suspend'; + info: ModerationLogPayloads['suspend']; +} | { + type: 'unsuspend'; + info: ModerationLogPayloads['unsuspend']; +} | { + type: 'userNoteUpdated'; + info: ModerationLogPayloads['userNoteUpdated']; +} | { + type: 'addEmoji'; + info: ModerationLogPayloads['addEmoji']; +} | { + type: 'roleAssigned'; + info: ModerationLogPayloads['roleAssigned']; +} | { + type: 'roleUnassigned'; + info: ModerationLogPayloads['roleUnassigned']; +} | { + type: 'roleUpdated'; + info: ModerationLogPayloads['roleUpdated']; +} | { + type: 'roleDeleted'; + info: ModerationLogPayloads['roleDeleted']; +} | { + type: 'clearQueue'; + info: ModerationLogPayloads['clearQueue']; +} | { + type: 'promoteQueue'; + info: ModerationLogPayloads['promoteQueue']; +}); // @public (undocumented) export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"]; @@ -2872,6 +2903,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..f0c5c821a0 100644 --- a/packages/misskey-js/src/consts.ts +++ b/packages/misskey-js/src/consts.ts @@ -44,3 +44,48 @@ export const permissions = [ 'read:flash-likes', 'write:flash-likes', ]; + +export const moderationLogTypes = ['updateMeta', 'suspend', 'unsuspend', 'userNoteUpdated', 'addEmoji', 'roleAssigned', 'roleUnassigned', 'roleUpdated', 'roleDeleted', 'clearQueue', 'promoteQueue'] as const; + +export type ModerationLogPayloads = { + updateMeta: { + before: any | null; + after: any | null; + }; + suspend: { + targetId: string; + }; + unsuspend: { + targetId: string; + }; + userNoteUpdated: { + userId: string; + before: string | null; + after: string | null; + }; + addEmoji: { + emojiId: string; + }; + roleAssigned: { + userId: string; + roleId: string; + roleName: string; + expiresAt: string | null; + }; + roleUnassigned: { + userId: string; + roleId: string; + roleName: string; + }; + roleUpdated: { + roleId: string; + before: any; + after: any; + }; + roleDeleted: { + roleId: string; + roleName: string; + }; + clearQueue: {}; + promoteQueue: {}; +}; diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index 56ea93fd8a..858e3176b4 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -1,3 +1,5 @@ +import { ModerationLogPayloads, moderationLogTypes } from './consts.js'; + export type ID = string; export type DateString = string; @@ -570,8 +572,39 @@ export type OriginType = 'combined' | 'local' | 'remote'; export type ModerationLog = { id: ID; createdAt: DateString; - type: string; - info: Record; userId: User['id']; user: UserDetailed | null; -}; +} & ({ + type: 'updateMeta'; + info: ModerationLogPayloads['updateMeta']; +} | { + type: 'suspend'; + info: ModerationLogPayloads['suspend']; +} | { + type: 'unsuspend'; + info: ModerationLogPayloads['unsuspend']; +} | { + type: 'userNoteUpdated'; + info: ModerationLogPayloads['userNoteUpdated']; +} | { + type: 'addEmoji'; + info: ModerationLogPayloads['addEmoji']; +} | { + type: 'roleAssigned'; + info: ModerationLogPayloads['roleAssigned']; +} | { + type: 'roleUnassigned'; + info: ModerationLogPayloads['roleUnassigned']; +} | { + type: 'roleUpdated'; + info: ModerationLogPayloads['roleUpdated']; +} | { + type: 'roleDeleted'; + info: ModerationLogPayloads['roleDeleted']; +} | { + type: 'clearQueue'; + info: ModerationLogPayloads['clearQueue']; +} | { + type: 'promoteQueue'; + info: ModerationLogPayloads['promoteQueue']; +});