diff --git a/locales/index.d.ts b/locales/index.d.ts index f7d875d2c8..6adc66d3cd 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -10529,6 +10529,10 @@ export interface Locale extends ILocale { * ギャラリーの投稿を削除 */ "deleteGalleryPost": string; + /** + * チャットルームを削除 + */ + "deleteChatRoom": string; /** * プロキシアカウントの説明を更新 */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index efaa279454..55bb4c577b 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2791,6 +2791,7 @@ _moderationLogTypes: deletePage: "ページを削除" deleteFlash: "Playを削除" deleteGalleryPost: "ギャラリーの投稿を削除" + deleteChatRoom: "チャットルームを削除" updateProxyAccountDescription: "プロキシアカウントの説明を更新" _fileViewer: diff --git a/packages/backend/src/core/ChatService.ts b/packages/backend/src/core/ChatService.ts index 4c010b2ef7..35819a4bc2 100644 --- a/packages/backend/src/core/ChatService.ts +++ b/packages/backend/src/core/ChatService.ts @@ -27,6 +27,7 @@ import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { emojiRegex } from '@/misc/emoji-regex.js'; import { NotificationService } from '@/core/NotificationService.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; const MAX_ROOM_MEMBERS = 30; const MAX_REACTIONS_PER_MESSAGE = 100; @@ -75,6 +76,7 @@ export class ChatService { private roleService: RoleService, private userFollowingService: UserFollowingService, private customEmojiService: CustomEmojiService, + private moderationLogService: ModerationLogService, ) { } @@ -285,6 +287,20 @@ export class ChatService { return this.chatMessagesRepository.findOneBy({ id: messageId, fromUserId: userId }); } + @bindThis + public async hasPermissionToViewRoomTimeline(meId: MiUser['id'], room: MiChatRoom) { + if (await this.isRoomMember(room, meId)) { + return true; + } else { + const iAmModerator = await this.roleService.isModerator({ id: meId }); + if (iAmModerator) { + return true; + } + + return false; + } + } + @bindThis public async deleteMessage(message: MiChatMessage) { await this.chatMessagesRepository.delete(message.id); @@ -493,8 +509,29 @@ export class ChatService { } @bindThis - public async deleteRoom(room: MiChatRoom) { + public async hasPermissionToDeleteRoom(meId: MiUser['id'], room: MiChatRoom) { + if (room.ownerId === meId) { + return true; + } + + const iAmModerator = await this.roleService.isModerator({ id: meId }); + if (iAmModerator) { + return true; + } + + return false; + } + + @bindThis + public async deleteRoom(room: MiChatRoom, moderator?: MiUser) { await this.chatRoomsRepository.delete(room.id); + + if (moderator) { + this.moderationLogService.log(moderator, 'deleteChatRoom', { + roomId: room.id, + room: room, + }); + } } @bindThis diff --git a/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts b/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts index ccc0030403..7aef35db04 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts @@ -59,7 +59,7 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchRoom); } - if (!(await this.chatService.isRoomMember(room, me.id))) { + if (!await this.chatService.hasPermissionToViewRoomTimeline(me.id, room)) { throw new ApiError(meta.errors.noSuchRoom); } diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/delete.ts b/packages/backend/src/server/api/endpoints/chat/rooms/delete.ts index 2ef0a778f1..1d77a06dd8 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/delete.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/delete.ts @@ -42,11 +42,16 @@ export default class extends Endpoint { // eslint- private chatService: ChatService, ) { super(meta, paramDef, async (ps, me) => { - const room = await this.chatService.findMyRoomById(me.id, ps.roomId); + const room = await this.chatService.findRoomById(ps.roomId); if (room == null) { throw new ApiError(meta.errors.noSuchRoom); } - await this.chatService.deleteRoom(room); + + if (!await this.chatService.hasPermissionToDeleteRoom(me.id, room)) { + throw new ApiError(meta.errors.noSuchRoom); + } + + await this.chatService.deleteRoom(room, me); }); } } diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 4e215c93c6..5d5f1e3b71 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -124,6 +124,7 @@ export const moderationLogTypes = [ 'deletePage', 'deleteFlash', 'deleteGalleryPost', + 'deleteChatRoom', 'updateProxyAccountDescription', ] as const; @@ -377,6 +378,10 @@ export type ModerationLogPayloads = { postUserUsername: string; post: any; }; + deleteChatRoom: { + roomId: string; + room: any; + }; updateProxyAccountDescription: { before: string | null; after: string | null; diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue index 9bbe5f2e42..7ab9417267 100644 --- a/packages/frontend/src/pages/admin/modlog.ModLog.vue +++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue @@ -40,6 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only 'deletePage', 'deleteFlash', 'deleteGalleryPost', + 'deleteChatRoom', ].includes(log.type) }" >{{ i18n.ts._moderationLogTypes[log.type] }} @@ -80,6 +81,7 @@ SPDX-License-Identifier: AGPL-3.0-only : @{{ log.info.pageUserUsername }} : @{{ log.info.flashUserUsername }} : @{{ log.info.postUserUsername }} + : @{{ log.info.room.name }}