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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,15 @@
import { Injectable } from "@nestjs/common";
import { DataSource } from "typeorm";
import { logger } from "@azure/identity";
import { Account } from "./entity/account.entity";
import { AUTO_INCREMENT_START } from "../../constants";
import { Term } from "./entity/term.entity";
import { insertEntities } from "../../common/repository";
import { Context } from "../../common/log";
@Injectable()
export class DeleteRepositoryService {
// クエリログにコメントを出力するかどうか
private readonly isCommentOut = process.env.STAGE !== "local";
constructor(private dataSource: DataSource) {}
/**
@ -54,4 +58,35 @@ export class DeleteRepositoryService {
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
);
});
}
}