Merged PR 765: データ削除ツール作成+動作確認

## 概要
[Task3569: データ削除ツール作成+動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3569)

- ADB2Cからのユーザー削除が100件ごとにしか削除できていなかったので、修正しました。
  - 取得が100件まででそのユーザーに対して削除処理をしていたので100件までの削除になっていました。
  - 対応として、100件づつの削除をユーザーが全削除されるまで実行するようにしました。

## レビューポイント
- 対応方法として適切でしょうか?
- ループで制限を設けていますが、MAX値として適切でしょうか?

## UIの変更
- なし

## 動作確認状況
- ローカルで順に実行できることを確認
- 実際の削除は別途develop環境で実施します。
This commit is contained in:
makabe.t 2024-02-22 07:33:55 +00:00
parent e3ee9412c9
commit dc52ec2022
6 changed files with 80 additions and 26 deletions

View File

@ -3107,9 +3107,9 @@
} }
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.8.2", "version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"devOptional": true, "devOptional": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
@ -12333,9 +12333,9 @@
} }
}, },
"acorn": { "acorn": {
"version": "8.8.2", "version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"devOptional": true "devOptional": true
}, },
"acorn-import-assertions": { "acorn-import-assertions": {

View File

@ -11,6 +11,7 @@ import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
import { Request } from "express"; import { Request } from "express";
import { DeleteService } from "./delete.service"; import { DeleteService } from "./delete.service";
import { DeleteResponse } from "./types/types"; import { DeleteResponse } from "./types/types";
import { makeContext } from "src/common/log";
@ApiTags("delete") @ApiTags("delete")
@Controller("delete") @Controller("delete")
@ -33,7 +34,9 @@ export class DeleteController {
}) })
@Post() @Post()
async deleteData(): Promise<{}> { async deleteData(): Promise<{}> {
await this.deleteService.deleteData(); const context = makeContext("tool", "delete");
await this.deleteService.deleteData(context);
return {}; return {};
} }
} }

View File

@ -3,6 +3,7 @@ import { DeleteRepositoryService } from "../../repositories/delete/delete.reposi
import { makeErrorResponse } from "../../common/errors/makeErrorResponse"; import { makeErrorResponse } from "../../common/errors/makeErrorResponse";
import { AdB2cService } from "../../gateways/adb2c/adb2c.service"; import { AdB2cService } from "../../gateways/adb2c/adb2c.service";
import { BlobstorageService } from "../../gateways/blobstorage/blobstorage.service"; import { BlobstorageService } from "../../gateways/blobstorage/blobstorage.service";
import { Context } from "../../common/log";
@Injectable() @Injectable()
export class DeleteService { export class DeleteService {
@ -17,21 +18,34 @@ export class DeleteService {
* *
* @returns data * @returns data
*/ */
async deleteData(): Promise<void> { async deleteData(context: Context): Promise<void> {
this.logger.log(`[IN] ${this.deleteData.name}`); this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.deleteData.name}`
);
try { try {
// BlobStorageからデータを削除する // BlobStorageからデータを削除する
await this.blobstorageService.deleteContainers(); await this.blobstorageService.deleteContainers(context);
// 100件ずつのユーザー取得なのですべて削除するまでループする
for (let i = 0; i < 500; i++) {
// ADB2Cからユーザ情報を取得する // ADB2Cからユーザ情報を取得する
const users = await this.adB2cService.getUsers(); const { users, hasNext } = await this.adB2cService.getUsers(context);
// ユーザーがいない場合はループを抜ける
if (!hasNext) {
break;
}
const externalIds = users.map((user) => user.id); const externalIds = users.map((user) => user.id);
await this.adB2cService.deleteUsers(externalIds); await this.adB2cService.deleteUsers(context, externalIds);
}
// データベースからデータを削除する // データベースからデータを削除する
await this.deleteRepositoryService.deleteData(); await this.deleteRepositoryService.deleteData();
// AutoIncrementの値をリセットする // AutoIncrementの値をリセットする
await this.deleteRepositoryService.resetAutoIncrement(); await this.deleteRepositoryService.resetAutoIncrement();
// 初期データを挿入する
await this.deleteRepositoryService.insertInitData(context);
} catch (e) { } catch (e) {
this.logger.error(`error=${e}`); this.logger.error(`error=${e}`);
if (e instanceof Error) { if (e instanceof Error) {

View File

@ -30,14 +30,10 @@ export const isConflictError = (arg: unknown): arg is ConflictError => {
export class AdB2cService { export class AdB2cService {
private readonly logger = new Logger(AdB2cService.name); private readonly logger = new Logger(AdB2cService.name);
private readonly tenantName: string; private readonly tenantName: string;
private readonly flowName: string;
private readonly ttl: number;
private graphClient: Client; private graphClient: Client;
constructor(private readonly configService: ConfigService) { constructor(private readonly configService: ConfigService) {
this.tenantName = this.configService.getOrThrow<string>("TENANT_NAME"); this.tenantName = this.configService.getOrThrow<string>("TENANT_NAME");
this.flowName = this.configService.getOrThrow<string>("SIGNIN_FLOW_NAME");
this.ttl = this.configService.getOrThrow<number>("ADB2C_CACHE_TTL");
// ADB2Cへの認証情報 // ADB2Cへの認証情報
const credential = new ClientSecretCredential( const credential = new ClientSecretCredential(
@ -111,8 +107,10 @@ export class AdB2cService {
* @param externalIds * @param externalIds
* @returns users * @returns users
*/ */
async getUsers(): Promise<AdB2cUser[]> { async getUsers(
this.logger.log(`[IN] ${this.getUsers.name}`); context: Context
): Promise<{ users: AdB2cUser[]; hasNext: boolean }> {
this.logger.log(`[IN] [${context.getTrackingId()}] ${this.getUsers.name}`);
try { try {
const res: AdB2cResponse = await this.graphClient const res: AdB2cResponse = await this.graphClient
@ -121,7 +119,7 @@ export class AdB2cService {
.filter(`creationType eq 'LocalAccount'`) .filter(`creationType eq 'LocalAccount'`)
.get(); .get();
return res.value; return { users: res.value, hasNext: !!res["@odata.nextLink"] };
} catch (e) { } catch (e) {
this.logger.error(`error=${e}`); this.logger.error(`error=${e}`);
const { statusCode } = e; const { statusCode } = e;
@ -177,9 +175,11 @@ export class AdB2cService {
* Azure AD B2Cからユーザ情報を削除する * Azure AD B2Cからユーザ情報を削除する
* @param externalIds ID * @param externalIds ID
*/ */
async deleteUsers(externalIds: string[]): Promise<void> { async deleteUsers(context: Context, externalIds: string[]): Promise<void> {
this.logger.log( this.logger.log(
`[IN]${this.deleteUsers.name} | params: { externalIds: ${externalIds} };` `[IN] [${context.getTrackingId()}] ${
this.deleteUsers.name
} | params: { externalIds: ${externalIds} };`
); );
try { try {

View File

@ -89,8 +89,10 @@ export class BlobstorageService {
* *
* @returns containers * @returns containers
*/ */
async deleteContainers(): Promise<void> { async deleteContainers(context: Context): Promise<void> {
this.logger.log(`[IN] ${this.deleteContainers.name}`); this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.deleteContainers.name}`
);
try { try {
for await (const container of this.blobServiceClientAU.listContainers({ for await (const container of this.blobServiceClientAU.listContainers({

View File

@ -1,11 +1,15 @@
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { DataSource } from "typeorm"; import { DataSource } from "typeorm";
import { logger } from "@azure/identity"; import { logger } from "@azure/identity";
import { Account } from "./entity/account.entity";
import { AUTO_INCREMENT_START } from "../../constants"; import { AUTO_INCREMENT_START } from "../../constants";
import { Term } from "./entity/term.entity";
import { insertEntities } from "../../common/repository";
import { Context } from "../../common/log";
@Injectable() @Injectable()
export class DeleteRepositoryService { export class DeleteRepositoryService {
// クエリログにコメントを出力するかどうか
private readonly isCommentOut = process.env.STAGE !== "local";
constructor(private dataSource: DataSource) {} constructor(private dataSource: DataSource) {}
/** /**
@ -54,4 +58,35 @@ export class DeleteRepositoryService {
await queryRunner.release(); await queryRunner.release();
} }
} }
/**
*
* @returns data
*/
async insertInitData(context: Context): Promise<void> {
await this.dataSource.transaction(async (entityManager) => {
const termRepo = entityManager.getRepository(Term);
// ワークフローのデータ作成
const newTarmDpa = new Term();
newTarmDpa.document_type = "DPA";
newTarmDpa.version = "V0.1";
const newTarmEula = new Term();
newTarmEula.document_type = "EULA";
newTarmEula.version = "V0.1";
const newTarmPrivacyNotice = new Term();
newTarmPrivacyNotice.document_type = "PrivacyNotice";
newTarmPrivacyNotice.version = "V0.1";
const initTerms = [newTarmDpa, newTarmEula, newTarmPrivacyNotice];
await insertEntities(
Term,
termRepo,
initTerms,
this.isCommentOut,
context
);
});
}
} }