From 841e6ff901a5a11769bd419ccf8b17c0085d938c Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 24 Sep 2023 15:40:38 +0900
Subject: [PATCH] improve moderation log

---
 locales/ja-JP.yml                             |  2 +
 packages/backend/src/core/DriveService.ts     | 51 ++++++++++++++++++
 .../api/endpoints/drive/files/update.ts       | 54 ++-----------------
 packages/backend/src/types.ts                 | 10 ++++
 packages/misskey-js/etc/misskey-js.api.md     |  8 ++-
 packages/misskey-js/src/consts.ts             | 10 ++++
 packages/misskey-js/src/entities.ts           |  6 +++
 7 files changed, 90 insertions(+), 51 deletions(-)

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 13ab6755e6..69c48a5e64 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -2184,3 +2184,5 @@ _moderationLogTypes:
   resetPassword: "パスワードをリセット"
   suspendRemoteInstance: "リモートサーバーを停止"
   unsuspendRemoteInstance: "リモートサーバーを再開"
+  markSensitiveDriveFile: "ファイルをセンシティブ付与"
+  unmarkSensitiveDriveFile: "ファイルをセンシティブ解除"
diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts
index 2ff062142c..0409a4f53b 100644
--- a/packages/backend/src/core/DriveService.ts
+++ b/packages/backend/src/core/DriveService.ts
@@ -649,6 +649,57 @@ export class DriveService {
 		return file;
 	}
 
+	@bindThis
+	public async update(file: MiDriveFile, values: Partial<MiDriveFile>, updater: MiUser) {
+		const alwaysMarkNsfw = (await this.roleService.getUserPolicies(file.userId)).alwaysMarkNsfw;
+
+		if (values.name && !this.driveFileEntityService.validateFileName(file.name)) {
+			throw new Error('invalid filename');
+		}
+
+		if (values.isSensitive !== undefined && values.isSensitive !== file.isSensitive && alwaysMarkNsfw && !values.isSensitive) {
+			throw new Error('cannot unmark nsfw');
+		}
+
+		if (values.folderId != null) {
+			const folder = await this.driveFoldersRepository.findOneBy({
+				id: values.folderId,
+				userId: file.userId!,
+			});
+
+			if (folder == null) {
+				throw new Error('folder-not-found');
+			}
+		}
+
+		await this.driveFilesRepository.update(file.id, values);
+
+		const fileObj = await this.driveFileEntityService.pack(file, { self: true });
+
+		// Publish fileUpdated event
+		if (file.userId) {
+			this.globalEventService.publishDriveStream(file.userId, 'fileUpdated', fileObj);
+		}
+
+		if (await this.roleService.isModerator(updater) && (file.userId !== updater.id)) {
+			if (values.isSensitive !== undefined && values.isSensitive !== file.isSensitive) {
+				if (values.isSensitive) {
+					this.moderationLogService.log(updater, 'markSensitiveDriveFile', {
+						fileId: file.id,
+						fileUserId: file.userId,
+					});
+				} else {
+					this.moderationLogService.log(updater, 'unmarkSensitiveDriveFile', {
+						fileId: file.id,
+						fileUserId: file.userId,
+					});
+				}
+			}
+		}
+
+		return fileObj;
+	}
+
 	@bindThis
 	public async deleteFile(file: MiDriveFile, isExpired = false, deleter?: MiUser) {
 		if (file.storedInternal) {
diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts
index d26ed63474..7db88e152c 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/update.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts
@@ -4,12 +4,11 @@
  */
 
 import { Inject, Injectable } from '@nestjs/common';
-import type { DriveFilesRepository, DriveFoldersRepository } from '@/models/_.js';
+import type { DriveFilesRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { DI } from '@/di-symbols.js';
 import { RoleService } from '@/core/RoleService.js';
+import { DriveService } from '@/core/DriveService.js';
 import { ApiError } from '../../../error.js';
 
 export const meta = {
@@ -77,16 +76,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		@Inject(DI.driveFilesRepository)
 		private driveFilesRepository: DriveFilesRepository,
 
-		@Inject(DI.driveFoldersRepository)
-		private driveFoldersRepository: DriveFoldersRepository,
-
-		private driveFileEntityService: DriveFileEntityService,
+		private driveService: DriveService,
 		private roleService: RoleService,
-		private globalEventService: GlobalEventService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
-			const alwaysMarkNsfw = (await this.roleService.getUserPolicies(me.id)).alwaysMarkNsfw;
 			if (file == null) {
 				throw new ApiError(meta.errors.noSuchFile);
 			}
@@ -95,47 +89,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw new ApiError(meta.errors.accessDenied);
 			}
 
-			if (ps.name) file.name = ps.name;
-			if (!this.driveFileEntityService.validateFileName(file.name)) {
-				throw new ApiError(meta.errors.invalidFileName);
-			}
-
-			if (ps.comment !== undefined) file.comment = ps.comment;
-
-			if (ps.isSensitive !== undefined && ps.isSensitive !== file.isSensitive && alwaysMarkNsfw && !ps.isSensitive) {
-				throw new ApiError(meta.errors.restrictedByRole);
-			}
-
-			if (ps.isSensitive !== undefined) file.isSensitive = ps.isSensitive;
-
-			if (ps.folderId !== undefined) {
-				if (ps.folderId === null) {
-					file.folderId = null;
-				} else {
-					const folder = await this.driveFoldersRepository.findOneBy({
-						id: ps.folderId,
-						userId: me.id,
-					});
-
-					if (folder == null) {
-						throw new ApiError(meta.errors.noSuchFolder);
-					}
-
-					file.folderId = folder.id;
-				}
-			}
-
-			await this.driveFilesRepository.update(file.id, {
-				name: file.name,
-				comment: file.comment,
-				folderId: file.folderId,
-				isSensitive: file.isSensitive,
-			});
-
-			const fileObj = await this.driveFileEntityService.pack(file, { self: true });
-
-			// Publish fileUpdated event
-			this.globalEventService.publishDriveStream(me.id, 'fileUpdated', fileObj);
+			const fileObj = await this.driveService.update(file, ps, me);
 
 			return fileObj;
 		});
diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts
index ea78bb919a..16654edd88 100644
--- a/packages/backend/src/types.ts
+++ b/packages/backend/src/types.ts
@@ -52,6 +52,8 @@ export const moderationLogTypes = [
 	'resetPassword',
 	'suspendRemoteInstance',
 	'unsuspendRemoteInstance',
+	'markSensitiveDriveFile',
+	'unmarkSensitiveDriveFile',
 ] as const;
 
 export type ModerationLogPayloads = {
@@ -152,4 +154,12 @@ export type ModerationLogPayloads = {
 		id: string;
 		host: string;
 	};
+	markSensitiveDriveFile: {
+		fileId: string;
+		fileUserId: string | null;
+	};
+	unmarkSensitiveDriveFile: {
+		fileId: string;
+		fileUserId: string | null;
+	};
 };
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index d8b6aa44d8..b3806754a8 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -2595,10 +2595,16 @@ type ModerationLog = {
 } | {
     type: 'unsuspendRemoteInstance';
     info: ModerationLogPayloads['unsuspendRemoteInstance'];
+} | {
+    type: 'markSensitiveDriveFile';
+    info: ModerationLogPayloads['markSensitiveDriveFile'];
+} | {
+    type: 'unmarkSensitiveDriveFile';
+    info: ModerationLogPayloads['unmarkSensitiveDriveFile'];
 });
 
 // @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"];
+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"];
 
 // @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 462ad16cc8..63137dcc83 100644
--- a/packages/misskey-js/src/consts.ts
+++ b/packages/misskey-js/src/consts.ts
@@ -70,6 +70,8 @@ export const moderationLogTypes = [
 	'resetPassword',
 	'suspendRemoteInstance',
 	'unsuspendRemoteInstance',
+	'markSensitiveDriveFile',
+	'unmarkSensitiveDriveFile',
 ] as const;
 
 export type ModerationLogPayloads = {
@@ -170,4 +172,12 @@ export type ModerationLogPayloads = {
 		id: string;
 		host: string;
 	};
+	markSensitiveDriveFile: {
+		fileId: string;
+		fileUserId: string | null;
+	};
+	unmarkSensitiveDriveFile: {
+		fileId: string;
+		fileUserId: string | null;
+	};
 };
diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts
index e6a97f0209..f377f1a5ed 100644
--- a/packages/misskey-js/src/entities.ts
+++ b/packages/misskey-js/src/entities.ts
@@ -646,4 +646,10 @@ export type ModerationLog = {
 } | {
 	type: 'unsuspendRemoteInstance';
 	info: ModerationLogPayloads['unsuspendRemoteInstance'];
+} | {
+	type: 'markSensitiveDriveFile';
+	info: ModerationLogPayloads['markSensitiveDriveFile'];
+} | {
+	type: 'unmarkSensitiveDriveFile';
+	info: ModerationLogPayloads['unmarkSensitiveDriveFile'];
 });