From 6c6970c70a854e4cfa2d51855ffd16d25edaa273 Mon Sep 17 00:00:00 2001 From: "maruyama.t" Date: Tue, 12 Dec 2023 09:28:40 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20622:=20delete=E3=81=A7=E3=82=B3?= =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3289: deleteでコメントを追加できるようにする](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3289) delete処理にSQLコメントを挿入する形にリファクタ レビューポイント repository/* に存在するdelete文は全て置き換えたはずだが、漏れはなさそうか --- .../src/common/repository/index.ts | 128 +++++++++++++++ .../accounts/accounts.repository.service.ts | 146 ++++++++++++------ .../tasks/tasks.repository.service.ts | 43 ++++-- .../user_groups.repository.service.ts | 13 +- .../users/users.repository.service.ts | 13 +- .../workflows/workflows.repository.service.ts | 20 ++- .../worktypes/worktypes.repository.service.ts | 17 +- 7 files changed, 302 insertions(+), 78 deletions(-) create mode 100644 dictation_server/src/common/repository/index.ts diff --git a/dictation_server/src/common/repository/index.ts b/dictation_server/src/common/repository/index.ts new file mode 100644 index 0000000..eda3501 --- /dev/null +++ b/dictation_server/src/common/repository/index.ts @@ -0,0 +1,128 @@ +import { + ObjectLiteral, + Repository, + EntityTarget, + UpdateResult, + DeleteResult, + UpdateQueryBuilder, + Brackets, + FindOptionsWhere, +} from 'typeorm'; +import { Context } from '../log'; + +/** + * VS Code上で型解析エラーが発生するため、typeorm内の型定義と同一の型定義をここに記述する + */ +type QueryDeepPartialEntity = _QueryDeepPartialEntity< + ObjectLiteral extends T ? unknown : T +>; +type _QueryDeepPartialEntity = { + [P in keyof T]?: + | (T[P] extends Array + ? Array<_QueryDeepPartialEntity> + : T[P] extends ReadonlyArray + ? ReadonlyArray<_QueryDeepPartialEntity> + : _QueryDeepPartialEntity) + | (() => string); +}; + +const insertEntity = async ( + entity: EntityTarget, + repository: Repository, + value: QueryDeepPartialEntity, + isCommentOut: boolean, + // context: Context, +): Promise => { + let query = repository.createQueryBuilder().insert().into(entity); + if (isCommentOut) { + query = query.comment( + `context.getTrackingId()_${new Date().toUTCString()}`, + ); + } + const result = await query.values(value).execute(); + + // 結果をもとにセレクトする + const inserted = await repository.findOne({ + where: { id: result.identifiers[0].id }, + }); + if (!inserted) { + throw new Error('Failed to insert entity'); + } + return inserted; +}; + +const insertEntities = async ( + entity: EntityTarget, + repository: Repository, + values: QueryDeepPartialEntity[], + isCommentOut: boolean, + // context: Context, +): Promise => { + let query = repository.createQueryBuilder().insert().into(entity); + if (isCommentOut) { + query = query.comment( + `context.getTrackingId()_${new Date().toUTCString()}`, + ); + } + const result = await query.values(values).execute(); + + // 挿入するレコードが0で、結果も0であれば、からの配列を返す + if (values.length === 0 && result.identifiers.length === 0) { + return []; + } + + // 挿入するレコード数と挿入されたレコード数が一致しない場合はエラー + if (result.identifiers.length !== values.length) { + throw new Error('Failed to insert entities'); + } + const where: FindOptionsWhere[] = result.identifiers.map((i) => { + return { id: i.id }; + }); + + // 結果をもとにセレクトする + const inserted = await repository.find({ + where, + }); + if (!inserted) { + throw new Error('Failed to insert entity'); + } + return inserted; +}; + +const updateEntity = async ( + repository: Repository, + criteria: + | string + | ((qb: UpdateQueryBuilder) => string) + | Brackets + | ObjectLiteral + | ObjectLiteral[], + values: QueryDeepPartialEntity, + isCommentOut: boolean, + // context: Context, +): Promise => { + let query = repository.createQueryBuilder().update(); + if (isCommentOut) { + query = query.comment( + `context.getTrackingId()_${new Date().toUTCString()}`, + ); + } + return await query.set(values).where(criteria).execute(); +}; + +const deleteEntity = async ( + repository: Repository, + criteria: string | Brackets | ObjectLiteral | ObjectLiteral[], + isCommentOut: boolean, + // context: Context, +): Promise => { + let query = repository.createQueryBuilder().delete(); + if (isCommentOut) { + query = query.comment( + `context.getTrackingId()_${new Date().toUTCString()}`, + ); + } + return await query.where(criteria).execute(); +}; + +export { insertEntity, insertEntities, updateEntity, deleteEntity }; diff --git a/dictation_server/src/repositories/accounts/accounts.repository.service.ts b/dictation_server/src/repositories/accounts/accounts.repository.service.ts index d7762bd..465f8f9 100644 --- a/dictation_server/src/repositories/accounts/accounts.repository.service.ts +++ b/dictation_server/src/repositories/accounts/accounts.repository.service.ts @@ -57,11 +57,12 @@ import { AudioOptionItem } from '../audio_option_items/entity/audio_option_item. import { UserGroup } from '../user_groups/entity/user_group.entity'; import { UserGroupMember } from '../user_groups/entity/user_group_member.entity'; import { TemplateFile } from '../template_files/entity/template_file.entity'; - +import { deleteEntity } from '../../common/repository'; @Injectable() export class AccountsRepositoryService { constructor(private dataSource: DataSource) {} - + // クエリログにコメントを出力するかどうか + private readonly isCommentOut = process.env.STAGE !== 'local'; /** * 管理ユーザー無しでアカウントを作成する * @param companyName @@ -196,13 +197,15 @@ export class AccountsRepositoryService { const usersRepo = entityManager.getRepository(User); const sortCriteriaRepo = entityManager.getRepository(SortCriteria); // ソート条件を削除 - await sortCriteriaRepo.delete({ - user_id: userId, - }); + await deleteEntity( + sortCriteriaRepo, + { user_id: userId }, + this.isCommentOut, + ); // プライマリ管理者を削除 - await usersRepo.delete({ id: userId }); + await deleteEntity(usersRepo, { id: userId }, this.isCommentOut); // アカウントを削除 - await accountsRepo.delete({ id: accountId }); + await deleteEntity(accountsRepo, { id: accountId }, this.isCommentOut); }); } @@ -741,7 +744,11 @@ export class AccountsRepositoryService { }, ); // 発行時に発行されたライセンスを削除する - await licenseRepo.delete({ order_id: targetOrder.id }); + await deleteEntity( + licenseRepo, + { order_id: targetOrder.id }, + this.isCommentOut, + ); }); } @@ -1017,13 +1024,14 @@ export class AccountsRepositoryService { // アカウントを削除 const accountRepo = entityManager.getRepository(Account); - await accountRepo.delete({ id: accountId }); - + await deleteEntity(accountRepo, { id: accountId }, this.isCommentOut); // ライセンス系(card_license_issue以外)のテーブルのレコードを削除する const orderRepo = entityManager.getRepository(LicenseOrder); - await orderRepo.delete({ - from_account_id: accountId, - }); + await deleteEntity( + orderRepo, + { from_account_id: accountId }, + this.isCommentOut, + ); const licenseRepo = entityManager.getRepository(License); const targetLicenses = await licenseRepo.find({ where: { @@ -1031,18 +1039,24 @@ export class AccountsRepositoryService { }, }); const cardLicenseRepo = entityManager.getRepository(CardLicense); - await cardLicenseRepo.delete({ - license_id: In(targetLicenses.map((license) => license.id)), - }); - await licenseRepo.delete({ - account_id: accountId, - }); + await deleteEntity( + cardLicenseRepo, + { license_id: In(targetLicenses.map((license) => license.id)) }, + this.isCommentOut, + ); + await deleteEntity( + licenseRepo, + { account_id: accountId }, + this.isCommentOut, + ); const LicenseAllocationHistoryRepo = entityManager.getRepository( LicenseAllocationHistory, ); - await LicenseAllocationHistoryRepo.delete({ - account_id: accountId, - }); + await deleteEntity( + LicenseAllocationHistoryRepo, + { account_id: accountId }, + this.isCommentOut, + ); // ワークタイプ系のテーブルのレコードを削除する const worktypeRepo = entityManager.getRepository(Worktype); @@ -1051,10 +1065,16 @@ export class AccountsRepositoryService { }); const optionItemRepo = entityManager.getRepository(OptionItem); - await optionItemRepo.delete({ - worktype_id: In(taggerWorktypes.map((worktype) => worktype.id)), - }); - await worktypeRepo.delete({ account_id: accountId }); + await deleteEntity( + optionItemRepo, + { worktype_id: In(taggerWorktypes.map((worktype) => worktype.id)) }, + this.isCommentOut, + ); + await deleteEntity( + worktypeRepo, + { account_id: accountId }, + this.isCommentOut, + ); // タスク系のテーブルのレコードを削除する const taskRepo = entityManager.getRepository(Task); @@ -1065,12 +1085,16 @@ export class AccountsRepositoryService { }); const checkoutPermissionRepo = entityManager.getRepository(CheckoutPermission); - await checkoutPermissionRepo.delete({ - task_id: In(targetTasks.map((task) => task.id)), - }); - await taskRepo.delete({ - account_id: accountId, - }); + await deleteEntity( + checkoutPermissionRepo, + { task_id: In(targetTasks.map((task) => task.id)) }, + this.isCommentOut, + ); + await deleteEntity( + taskRepo, + { account_id: accountId }, + this.isCommentOut, + ); // オーディオファイル系のテーブルのレコードを削除する const audioFileRepo = entityManager.getRepository(AudioFile); @@ -1080,12 +1104,18 @@ export class AccountsRepositoryService { }, }); const audioOptionItemsRepo = entityManager.getRepository(AudioOptionItem); - await audioOptionItemsRepo.delete({ - audio_file_id: In(targetaudioFiles.map((audioFile) => audioFile.id)), - }); - await audioFileRepo.delete({ - account_id: accountId, - }); + await deleteEntity( + audioOptionItemsRepo, + { + audio_file_id: In(targetaudioFiles.map((audioFile) => audioFile.id)), + }, + this.isCommentOut, + ); + await deleteEntity( + audioFileRepo, + { account_id: accountId }, + this.isCommentOut, + ); // ユーザーグループ系のテーブルのレコードを削除する const userGroupRepo = entityManager.getRepository(UserGroup); @@ -1095,28 +1125,42 @@ export class AccountsRepositoryService { }, }); const userGroupMemberRepo = entityManager.getRepository(UserGroupMember); - await userGroupMemberRepo.delete({ - user_group_id: In(targetUserGroup.map((userGroup) => userGroup.id)), - }); - await userGroupRepo.delete({ - account_id: accountId, - }); + await deleteEntity( + userGroupMemberRepo, + { + user_group_id: In(targetUserGroup.map((userGroup) => userGroup.id)), + }, + this.isCommentOut, + ); + await deleteEntity( + userGroupRepo, + { account_id: accountId }, + this.isCommentOut, + ); // テンプレートファイルテーブルのレコードを削除する const templateFileRepo = entityManager.getRepository(TemplateFile); - await templateFileRepo.delete({ account_id: accountId }); + await deleteEntity( + templateFileRepo, + { account_id: accountId }, + this.isCommentOut, + ); // ユーザテーブルのレコードを削除する const userRepo = entityManager.getRepository(User); - await userRepo.delete({ - account_id: accountId, - }); + await deleteEntity( + userRepo, + { account_id: accountId }, + this.isCommentOut, + ); // ソート条件のテーブルのレコードを削除する const sortCriteriaRepo = entityManager.getRepository(SortCriteria); - await sortCriteriaRepo.delete({ - user_id: In(users.map((user) => user.id)), - }); + await deleteEntity( + sortCriteriaRepo, + { user_id: In(users.map((user) => user.id)) }, + this.isCommentOut, + ); return users; }); } diff --git a/dictation_server/src/repositories/tasks/tasks.repository.service.ts b/dictation_server/src/repositories/tasks/tasks.repository.service.ts index d543609..693f0e2 100644 --- a/dictation_server/src/repositories/tasks/tasks.repository.service.ts +++ b/dictation_server/src/repositories/tasks/tasks.repository.service.ts @@ -41,9 +41,12 @@ import { TaskStatus, isTaskStatus } from '../../common/types/taskStatus'; import { SortCriteria } from '../sort_criteria/entity/sort_criteria.entity'; import { Workflow } from '../workflows/entity/workflow.entity'; import { Worktype } from '../worktypes/entity/worktype.entity'; +import { deleteEntity } from '../../common/repository'; @Injectable() export class TasksRepositoryService { + //クエリログにコメントを出力するかどうか + private readonly isCommentOut = process.env.STAGE !== 'local'; constructor(private dataSource: DataSource) {} /** @@ -255,9 +258,13 @@ export class TasksRepositoryService { ); //対象のタスクに紐づくチェックアウト権限レコードを削除 - await checkoutRepo.delete({ - task_id: task.id, - }); + await deleteEntity( + checkoutRepo, + { + task_id: task.id, + }, + this.isCommentOut, + ); //対象のタスクチェックアウト権限を自身のユーザーIDで作成 await checkoutRepo.save({ @@ -374,9 +381,13 @@ export class TasksRepositoryService { // 対象タスクの文字起こし候補を削除 /* 対象タスクがInprogress,Pendingの状態の場合、文字起こし担当者個人指定のレコードのみだが、ユーザーIDの指定はしない (データの不整合があっても問題ないように)*/ - await checkoutPermissionRepo.delete({ - task_id: task.id, - }); + await deleteEntity( + checkoutPermissionRepo, + { + task_id: task.id, + }, + this.isCommentOut, + ); }); } @@ -908,9 +919,13 @@ export class TasksRepositoryService { // 当該タスクに紐づく既存checkoutPermissionをdelete const checkoutPermissionRepo = entityManager.getRepository(CheckoutPermission); - await checkoutPermissionRepo.delete({ - task_id: taskRecord.id, - }); + await deleteEntity( + checkoutPermissionRepo, + { + task_id: taskRecord.id, + }, + this.isCommentOut, + ); // 当該タスクに紐づく新規checkoutPermissionをinsert const checkoutPermissions: CheckoutPermission[] = assignees.map( @@ -1217,9 +1232,13 @@ export class TasksRepositoryService { entityManager.getRepository(CheckoutPermission); // 当該タスクに紐づく既存checkoutPermissionをdelete - await checkoutPermissionRepo.delete({ - task_id: task.id, - }); + await deleteEntity( + checkoutPermissionRepo, + { + task_id: task.id, + }, + this.isCommentOut, + ); // ルーティング候補ユーザーのチェックアウト権限を作成 const typistPermissions = typistUsers.map((typistUser) => { diff --git a/dictation_server/src/repositories/user_groups/user_groups.repository.service.ts b/dictation_server/src/repositories/user_groups/user_groups.repository.service.ts index 061e9e9..e4fa49e 100644 --- a/dictation_server/src/repositories/user_groups/user_groups.repository.service.ts +++ b/dictation_server/src/repositories/user_groups/user_groups.repository.service.ts @@ -5,9 +5,12 @@ import { UserGroupMember } from './entity/user_group_member.entity'; import { User } from '../users/entity/user.entity'; import { TypistGroupNotExistError, TypistIdInvalidError } from './errors/types'; import { USER_ROLES } from '../../constants'; +import { deleteEntity } from '../../common/repository'; @Injectable() export class UserGroupsRepositoryService { + //クエリログにコメントを出力するかどうか + private readonly isCommentOut = process.env.STAGE !== 'local'; constructor(private dataSource: DataSource) {} async getUserGroups(account_id: number): Promise { @@ -184,9 +187,13 @@ export class UserGroupsRepositoryService { await userGroupRepo.save(typistGroup); // user_group_membersテーブルから対象のタイピストグループのユーザーを削除する - await userGroupMemberRepo.delete({ - user_group_id: typistGroupId, - }); + await deleteEntity( + userGroupMemberRepo, + { + user_group_id: typistGroupId, + }, + this.isCommentOut, + ); const typistGroupMembers = userRecords.map((typist) => { return { diff --git a/dictation_server/src/repositories/users/users.repository.service.ts b/dictation_server/src/repositories/users/users.repository.service.ts index 8894656..3efbc50 100644 --- a/dictation_server/src/repositories/users/users.repository.service.ts +++ b/dictation_server/src/repositories/users/users.repository.service.ts @@ -36,9 +36,12 @@ import { Account } from '../accounts/entity/account.entity'; import { Workflow } from '../workflows/entity/workflow.entity'; import { Worktype } from '../worktypes/entity/worktype.entity'; import { Context } from '../../common/log'; +import { deleteEntity } from '../../common/repository'; @Injectable() export class UsersRepositoryService { + //クエリログにコメントを出力するかどうか + private readonly isCommentOut = process.env.STAGE !== 'local'; constructor(private dataSource: DataSource) {} /** @@ -433,11 +436,13 @@ export class UsersRepositoryService { const usersRepo = entityManager.getRepository(User); const sortCriteriaRepo = entityManager.getRepository(SortCriteria); // ソート条件を削除 - await sortCriteriaRepo.delete({ - user_id: userId, - }); + await deleteEntity( + sortCriteriaRepo, + { user_id: userId }, + this.isCommentOut, + ); // プライマリ管理者を削除 - await usersRepo.delete({ id: userId }); + await deleteEntity(usersRepo, { id: userId }, this.isCommentOut); }); } diff --git a/dictation_server/src/repositories/workflows/workflows.repository.service.ts b/dictation_server/src/repositories/workflows/workflows.repository.service.ts index a1ea660..d26c5a7 100644 --- a/dictation_server/src/repositories/workflows/workflows.repository.service.ts +++ b/dictation_server/src/repositories/workflows/workflows.repository.service.ts @@ -15,9 +15,12 @@ import { AuthorIdAndWorktypeIdPairAlreadyExistsError, WorkflowNotFoundError, } from './errors/types'; +import { deleteEntity } from '../../common/repository'; @Injectable() export class WorkflowsRepositoryService { + //クエリログにコメントを出力するかどうか + private readonly isCommentOut = process.env.STAGE !== 'local'; constructor(private dataSource: DataSource) {} /** @@ -274,8 +277,12 @@ export class WorkflowsRepositoryService { const workflowTypistsRepo = entityManager.getRepository(DbWorkflowTypist); // 既存データの削除 - await workflowTypistsRepo.delete({ workflow_id: workflowId }); - await workflowRepo.delete(workflowId); + await deleteEntity( + workflowTypistsRepo, + { workflow_id: workflowId }, + this.isCommentOut, + ); + await deleteEntity(workflowRepo, { id: workflowId }, this.isCommentOut); { // ワークフローの重複確認 @@ -336,9 +343,12 @@ export class WorkflowsRepositoryService { `workflow not found. id: ${workflowId}`, ); } - - await workflowTypistsRepo.delete({ workflow_id: workflowId }); - await workflowRepo.delete(workflowId); + await deleteEntity( + workflowTypistsRepo, + { workflow_id: workflowId }, + this.isCommentOut, + ); + await deleteEntity(workflowRepo, { id: workflowId }, this.isCommentOut); }); } diff --git a/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts b/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts index 38016c1..ff419cb 100644 --- a/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts +++ b/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts @@ -17,9 +17,12 @@ import { PostWorktypeOptionItem } from '../../features/accounts/types/types'; import { AccountNotFoundError } from '../accounts/errors/types'; import { Account } from '../accounts/entity/account.entity'; import { Workflow } from '../workflows/entity/workflow.entity'; +import { deleteEntity } from '../../common/repository'; @Injectable() export class WorktypesRepositoryService { + //クエリログにコメントを出力するかどうか + private readonly isCommentOut = process.env.STAGE !== 'local'; constructor(private dataSource: DataSource) {} /** @@ -203,10 +206,14 @@ export class WorktypesRepositoryService { // ワークタイプに紐づくオプションアイテムを削除 const optionItemRepo = entityManager.getRepository(OptionItem); - await optionItemRepo.delete({ worktype_id: id }); + await deleteEntity( + optionItemRepo, + { worktype_id: id }, + this.isCommentOut, + ); // ワークタイプを削除 - await worktypeRepo.delete({ id: id }); + await deleteEntity(worktypeRepo, { id: id }, this.isCommentOut); }); } @@ -281,7 +288,11 @@ export class WorktypesRepositoryService { }); // ワークタイプに紐づくオプションアイテムを削除してから再登録 - await optionItemRepo.delete({ worktype_id: worktypeId }); + await deleteEntity( + optionItemRepo, + { worktype_id: worktypeId }, + this.isCommentOut, + ); await optionItemRepo.save(optionItems); }); }