From e877942175d22a38ec09536b167ca3ac3e77a507 Mon Sep 17 00:00:00 2001 From: "maruyama.t" Date: Thu, 1 Feb 2024 06:06:10 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20711:=20Repository=E3=83=AD?= =?UTF-8?q?=E3=83=83=E3=82=AF=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3523: Repositoryロック対応](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3523) LicensesRepository - allocateLicense 割当先ユーザ取得しロックする処理を追加 WorkflowRepository - createtWorkflows authorの存在確認時にロックを追加 - updatetWorkflows authorの存在確認時にロックを追加 UserGroup&UserGroupMemberのロックを追加 AccountsRepository - updateAccountInfo プライマリ/セカンダリ管理者ユーザーの存在チェックのロック (行ロック横展開1で修正されていた) TasksRepository - create タスクの所有者の存在確認とロックを追加 - checkout 対象ユーザの存在確認とロックを追加 - changeCheckoutPermission 対象ユーザの存在確認とロックを追加 UserGroupsRepository - createTypistGroup 対象ユーザ達のロックを追加 - updateTypistGroup 対象ユーザ達のロックを追加 ## レビューポイント ラフスケッチの、 ``` 競合ケース E-3. Typistが削除条件判定を行った直後に、チェックアウト候補に削除ユーザーが含まれるTypistGroupが割り当てられる TypistGroupに割り当たっている時点で削除条件を満たさないので、このケースはないはず ``` ここは未対応でよい認識か。 ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば --- .../licenses/licenses.repository.service.ts | 15 +++++ .../tasks/tasks.repository.service.ts | 32 ++++++++++ .../user_groups.repository.service.ts | 2 + .../workflows/workflows.repository.service.ts | 63 ++++++++++--------- 4 files changed, 82 insertions(+), 30 deletions(-) diff --git a/dictation_server/src/repositories/licenses/licenses.repository.service.ts b/dictation_server/src/repositories/licenses/licenses.repository.service.ts index 2a0c328..6270b43 100644 --- a/dictation_server/src/repositories/licenses/licenses.repository.service.ts +++ b/dictation_server/src/repositories/licenses/licenses.repository.service.ts @@ -39,6 +39,8 @@ import { updateEntity, } from '../../common/repository'; import { Context } from '../../common/log'; +import { User } from '../users/entity/user.entity'; +import { UserNotFoundError } from '../users/errors/types'; @Injectable() export class LicensesRepositoryService { @@ -559,6 +561,19 @@ export class LicensesRepositoryService { accountId: number, ): Promise { await this.dataSource.transaction(async (entityManager) => { + // 対象ユーザの存在チェック + const userRepo = entityManager.getRepository(User); + const user = await userRepo.findOne({ + where: { + id: userId, + }, + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, + }); + if (!user) { + throw new UserNotFoundError(`User not exist. userId: ${userId}`); + } + const licenseRepo = entityManager.getRepository(License); const licenseAllocationHistoryRepo = entityManager.getRepository( LicenseAllocationHistory, diff --git a/dictation_server/src/repositories/tasks/tasks.repository.service.ts b/dictation_server/src/repositories/tasks/tasks.repository.service.ts index 9ac0e48..824a85c 100644 --- a/dictation_server/src/repositories/tasks/tasks.repository.service.ts +++ b/dictation_server/src/repositories/tasks/tasks.repository.service.ts @@ -48,6 +48,7 @@ import { deleteEntity, } from '../../common/repository'; import { Context } from '../../common/log'; +import { UserNotFoundError } from '../users/errors/types'; @Injectable() export class TasksRepositoryService { @@ -167,6 +168,20 @@ export class TasksRepositoryService { permittedSourceStatus: TaskStatus[], ): Promise { await this.dataSource.transaction(async (entityManager) => { + // 対象ユーザの存在確認 + const userRepo = entityManager.getRepository(User); + const user = await userRepo.findOne({ + where: { + id: user_id, + }, + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, + }); + if (!user) { + throw new TypistUserNotFoundError( + `Typist user not exists. user_id:${user_id}`, + ); + } const taskRepo = entityManager.getRepository(Task); // 指定した音声ファイルIDに紐づくTaskの中でStatusが[Uploaded,Inprogress,Pending]であるものを取得 const task = await taskRepo.findOne({ @@ -846,6 +861,22 @@ export class TasksRepositoryService { const createdEntity = await this.dataSource.transaction( async (entityManager) => { + // タスクの所有者の存在確認 + const userRepo = entityManager.getRepository(User); + const user = await userRepo.findOne({ + where: { + id: owner_user_id, + account_id: account_id, + }, + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, + }); + if (!user) { + throw new UserNotFoundError( + `User not exists. owner_user_id:${owner_user_id}`, + ); + } + const audioFileRepo = entityManager.getRepository(AudioFile); const newAudioFile = audioFileRepo.create(audioFile); const savedAudioFile = await insertEntity( @@ -967,6 +998,7 @@ export class TasksRepositoryService { deleted_at: IsNull(), }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); // idはユニークであるため取得件数の一致でユーザーの存在を確認 if (typistUserIds.length !== userRecords.length) { 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 eca0bc7..e7db5cf 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 @@ -123,6 +123,7 @@ export class UserGroupsRepositoryService { email_verified: true, }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); if (userRecords.length !== typistIds.length) { throw new TypistIdInvalidError( @@ -189,6 +190,7 @@ export class UserGroupsRepositoryService { email_verified: true, }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); if (userRecords.length !== typistIds.length) { throw new TypistIdInvalidError( diff --git a/dictation_server/src/repositories/workflows/workflows.repository.service.ts b/dictation_server/src/repositories/workflows/workflows.repository.service.ts index cf28006..de5e38e 100644 --- a/dictation_server/src/repositories/workflows/workflows.repository.service.ts +++ b/dictation_server/src/repositories/workflows/workflows.repository.service.ts @@ -87,6 +87,7 @@ export class WorkflowsRepositoryService { const author = await userRepo.findOne({ where: { account_id: accountId, id: authorId, email_verified: true }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); if (!author) { throw new UserNotFoundError( @@ -227,6 +228,37 @@ export class WorkflowsRepositoryService { ): Promise { return await this.dataSource.transaction(async (entityManager) => { const workflowRepo = entityManager.getRepository(Workflow); + // authorの存在確認 + const userRepo = entityManager.getRepository(User); + const author = await userRepo.findOne({ + where: { account_id: accountId, id: authorId, email_verified: true }, + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, + }); + if (!author) { + throw new UserNotFoundError( + `author not found or email not verified. id: ${authorId}`, + ); + } + + // ルーティング候補ユーザーの存在確認 + const typistIds = typists.flatMap((typist) => + typist.typistId ? [typist.typistId] : [], + ); + const typistUsers = await userRepo.find({ + where: { + account_id: accountId, + id: In(typistIds), + email_verified: true, + }, + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, + }); + if (typistUsers.length !== typistIds.length) { + throw new UserNotFoundError( + `typist not found or email not verified. ids: ${typistIds}`, + ); + } // ワークフローの存在確認 const targetWorkflow = await workflowRepo.findOne({ @@ -239,18 +271,6 @@ export class WorkflowsRepositoryService { ); } - // authorの存在確認 - const userRepo = entityManager.getRepository(User); - const author = await userRepo.findOne({ - where: { account_id: accountId, id: authorId, email_verified: true }, - comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, - }); - if (!author) { - throw new UserNotFoundError( - `author not found or email not verified. id: ${authorId}`, - ); - } - // worktypeの存在確認 if (worktypeId !== undefined) { const worktypeRepo = entityManager.getRepository(Worktype); @@ -279,24 +299,6 @@ export class WorkflowsRepositoryService { } } - // ルーティング候補ユーザーの存在確認 - const typistIds = typists.flatMap((typist) => - typist.typistId ? [typist.typistId] : [], - ); - const typistUsers = await userRepo.find({ - where: { - account_id: accountId, - id: In(typistIds), - email_verified: true, - }, - comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, - }); - if (typistUsers.length !== typistIds.length) { - throw new UserNotFoundError( - `typist not found or email not verified. ids: ${typistIds}`, - ); - } - // ルーティング候補ユーザーグループの存在確認 const groupIds = typists.flatMap((typist) => { return typist.typistGroupId ? [typist.typistGroupId] : []; @@ -305,6 +307,7 @@ export class WorkflowsRepositoryService { const typistGroups = await userGroupRepo.find({ where: { account_id: accountId, id: In(groupIds) }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); if (typistGroups.length !== groupIds.length) { throw new TypistGroupNotExistError(