From 2d2eefe3d44ad7118bbe39d5acb1301ed802522e Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 14 Nov 2023 07:58:18 +0900
Subject: [PATCH] =?UTF-8?q?feat:=20=E3=83=A1=E3=83=BC=E3=83=AB=E3=82=A2?=
 =?UTF-8?q?=E3=83=89=E3=83=AC=E3=82=B9=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=97?=
 =?UTF-8?q?=E3=81=A6=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC=E5=90=8D=E3=82=92?=
 =?UTF-8?q?=E5=89=B2=E3=82=8A=E5=87=BA=E3=81=99=E6=A9=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Resolve #10158
---
 CHANGELOG.md                                  |  2 +-
 .../backend/src/server/api/EndpointsModule.ts |  4 ++
 packages/backend/src/server/api/endpoints.ts  |  2 +
 .../endpoints/admin/accounts/find-by-email.ts | 61 +++++++++++++++++++
 packages/frontend/src/pages/admin/index.vue   | 16 +++--
 packages/frontend/src/scripts/lookup-user.ts  | 23 +++++++
 6 files changed, 102 insertions(+), 6 deletions(-)
 create mode 100644 packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 87d89e2fc7..c286fc3efc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,7 +15,7 @@
 ## 2023.x.x (unreleased)
 
 ### General
--
+- Feat: コントロールパネルの「照会」から、入力されたメールアドレスを持つユーザーを検索できるようになりました
 
 ### Client
 - Fix: アイコンデコレーションが複数の場所で見切れている問題を修正
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index 23067a9b26..6d813ae04e 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -10,6 +10,7 @@ import * as ep___admin_meta from './endpoints/admin/meta.js';
 import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
 import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js';
 import * as ep___admin_accounts_delete from './endpoints/admin/accounts/delete.js';
+import * as ep___admin_accounts_findByEmail from './endpoints/admin/accounts/find-by-email.js';
 import * as ep___admin_ad_create from './endpoints/admin/ad/create.js';
 import * as ep___admin_ad_delete from './endpoints/admin/ad/delete.js';
 import * as ep___admin_ad_list from './endpoints/admin/ad/list.js';
@@ -368,6 +369,7 @@ const $admin_meta: Provider = { provide: 'ep:admin/meta', useClass: ep___admin_m
 const $admin_abuseUserReports: Provider = { provide: 'ep:admin/abuse-user-reports', useClass: ep___admin_abuseUserReports.default };
 const $admin_accounts_create: Provider = { provide: 'ep:admin/accounts/create', useClass: ep___admin_accounts_create.default };
 const $admin_accounts_delete: Provider = { provide: 'ep:admin/accounts/delete', useClass: ep___admin_accounts_delete.default };
+const $admin_accounts_findByEmail: Provider = { provide: 'ep:admin/accounts/find-by-email', useClass: ep___admin_accounts_findByEmail.default };
 const $admin_ad_create: Provider = { provide: 'ep:admin/ad/create', useClass: ep___admin_ad_create.default };
 const $admin_ad_delete: Provider = { provide: 'ep:admin/ad/delete', useClass: ep___admin_ad_delete.default };
 const $admin_ad_list: Provider = { provide: 'ep:admin/ad/list', useClass: ep___admin_ad_list.default };
@@ -730,6 +732,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$admin_abuseUserReports,
 		$admin_accounts_create,
 		$admin_accounts_delete,
+		$admin_accounts_findByEmail,
 		$admin_ad_create,
 		$admin_ad_delete,
 		$admin_ad_list,
@@ -1086,6 +1089,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$admin_abuseUserReports,
 		$admin_accounts_create,
 		$admin_accounts_delete,
+		$admin_accounts_findByEmail,
 		$admin_ad_create,
 		$admin_ad_delete,
 		$admin_ad_list,
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index af798fd166..ef4bd3e2b5 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -10,6 +10,7 @@ import * as ep___admin_meta from './endpoints/admin/meta.js';
 import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
 import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js';
 import * as ep___admin_accounts_delete from './endpoints/admin/accounts/delete.js';
+import * as ep___admin_accounts_findByEmail from './endpoints/admin/accounts/find-by-email.js';
 import * as ep___admin_ad_create from './endpoints/admin/ad/create.js';
 import * as ep___admin_ad_delete from './endpoints/admin/ad/delete.js';
 import * as ep___admin_ad_list from './endpoints/admin/ad/list.js';
@@ -366,6 +367,7 @@ const eps = [
 	['admin/abuse-user-reports', ep___admin_abuseUserReports],
 	['admin/accounts/create', ep___admin_accounts_create],
 	['admin/accounts/delete', ep___admin_accounts_delete],
+	['admin/accounts/find-by-email', ep___admin_accounts_findByEmail],
 	['admin/ad/create', ep___admin_ad_create],
 	['admin/ad/delete', ep___admin_ad_delete],
 	['admin/ad/list', ep___admin_ad_list],
diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts b/packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts
new file mode 100644
index 0000000000..686341582b
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts
@@ -0,0 +1,61 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { UserProfilesRepository } from '@/models/_.js';
+import { DI } from '@/di-symbols.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+	tags: ['admin'],
+
+	requireCredential: true,
+	requireAdmin: true,
+
+	errors: {
+		userNotFound: {
+			message: 'No such user who has the email address.',
+			code: 'USER_NOT_FOUND',
+			id: 'cb865949-8af5-4062-a88c-ef55e8786d1d',
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		email: { type: 'string' },
+	},
+	required: ['email'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		@Inject(DI.userProfilesRepository)
+		private userProfilesRepository: UserProfilesRepository,
+
+		private userEntityService: UserEntityService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const profile = await this.userProfilesRepository.findOne({
+				where: { email: ps.email },
+				relations: ['user'],
+			});
+
+			if (profile == null) {
+				throw new ApiError(meta.errors.userNotFound);
+			}
+
+			const res = await this.userEntityService.pack(profile.user!, null, {
+				detail: true,
+			});
+
+			return res;
+		});
+	}
+}
diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue
index b304edbf57..cc0cdf7466 100644
--- a/packages/frontend/src/pages/admin/index.vue
+++ b/packages/frontend/src/pages/admin/index.vue
@@ -34,7 +34,7 @@ import MkSuperMenu from '@/components/MkSuperMenu.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import { instance } from '@/instance.js';
 import * as os from '@/os.js';
-import { lookupUser } from '@/scripts/lookup-user.js';
+import { lookupUser, lookupUserByEmail } from '@/scripts/lookup-user.js';
 import { useRouter } from '@/router.js';
 import { definePageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
 
@@ -264,7 +264,7 @@ provideMetadataReceiver((info) => {
 	}
 });
 
-const invite = () => {
+function invite() {
 	os.api('admin/invite/create').then(x => {
 		os.alert({
 			type: 'info',
@@ -276,15 +276,21 @@ const invite = () => {
 			text: err,
 		});
 	});
-};
+}
 
-const lookup = (ev) => {
+function lookup(ev: MouseEvent) {
 	os.popupMenu([{
 		text: i18n.ts.user,
 		icon: 'ti ti-user',
 		action: () => {
 			lookupUser();
 		},
+	}, {
+		text: `${i18n.ts.user} (${i18n.ts.email})`,
+		icon: 'ti ti-user',
+		action: () => {
+			lookupUserByEmail();
+		},
 	}, {
 		text: i18n.ts.note,
 		icon: 'ti ti-pencil',
@@ -304,7 +310,7 @@ const lookup = (ev) => {
 			alert('TODO');
 		},
 	}], ev.currentTarget ?? ev.target);
-};
+}
 
 const headerActions = $computed(() => []);
 
diff --git a/packages/frontend/src/scripts/lookup-user.ts b/packages/frontend/src/scripts/lookup-user.ts
index 3dbc03f777..a35fe898e4 100644
--- a/packages/frontend/src/scripts/lookup-user.ts
+++ b/packages/frontend/src/scripts/lookup-user.ts
@@ -39,3 +39,26 @@ export async function lookupUser() {
 		notFound();
 	});
 }
+
+export async function lookupUserByEmail() {
+	const { canceled, result } = await os.inputText({
+		title: i18n.ts.emailAddress,
+		type: 'email',
+	});
+	if (canceled) return;
+
+	try {
+		const user = await os.apiWithDialog('admin/accounts/find-by-email', { email: result });
+
+		os.pageWindow(`/admin/user/${user.id}`);
+	} catch (err) {
+		if (err.code === 'USER_NOT_FOUND') {
+			os.alert({
+				type: 'error',
+				text: i18n.ts.noSuchUser,
+			});
+		} else {
+			throw err;
+		}
+	}
+}