From 8a76b2adb5315fc78216dafe535634da4e3e7041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=8E=E8=97=A4=20=E5=BF=AB=E6=96=97?= Date: Fri, 27 Jan 2023 02:42:10 +0000 Subject: [PATCH 01/13] Set up CI with Azure Pipelines [skip ci] --- azure-pipelines.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000..69a7957 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,19 @@ +# Starter pipeline +# Start with a minimal pipeline that you can customize to build and deploy your code. +# Add steps that build, run tests, deploy, and more: +# https://aka.ms/yaml + +trigger: +- feature/1330/static-web-apps-test + +pool: + vmImage: ubuntu-latest + +steps: +- task: AzureStaticWebApp@0 + inputs: + app_location: '/dictation_client' + app_build_command: 'npm run build' + output_location: 'build' + skip_api_build: true + azure_static_web_apps_api_token: '$(deployment_token)' \ No newline at end of file From 3dc0dd4fdb1febe7f7ef87cbb5533639e96011bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=8E=E8=97=A4=20=E5=BF=AB=E6=96=97?= Date: Fri, 27 Jan 2023 02:44:34 +0000 Subject: [PATCH 02/13] Deleted azure-pipelines.yml --- azure-pipelines.yml | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 69a7957..0000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,19 +0,0 @@ -# Starter pipeline -# Start with a minimal pipeline that you can customize to build and deploy your code. -# Add steps that build, run tests, deploy, and more: -# https://aka.ms/yaml - -trigger: -- feature/1330/static-web-apps-test - -pool: - vmImage: ubuntu-latest - -steps: -- task: AzureStaticWebApp@0 - inputs: - app_location: '/dictation_client' - app_build_command: 'npm run build' - output_location: 'build' - skip_api_build: true - azure_static_web_apps_api_token: '$(deployment_token)' \ No newline at end of file From 7793ecdb1e65d4977deaba1c4e99264f05c151da Mon Sep 17 00:00:00 2001 From: "oura.a" Date: Thu, 12 Oct 2023 18:08:55 +0900 Subject: [PATCH 03/13] =?UTF-8?q?=E3=83=AA=E3=83=9D=E3=82=B8=E3=83=88?= =?UTF-8?q?=E3=83=AA=E3=82=B5=E3=83=BC=E3=83=93=E3=82=B9=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/repositories/terms/terms.repository.service.ts | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 dictation_server/src/repositories/terms/terms.repository.service.ts diff --git a/dictation_server/src/repositories/terms/terms.repository.service.ts b/dictation_server/src/repositories/terms/terms.repository.service.ts new file mode 100644 index 0000000..a57c1cf --- /dev/null +++ b/dictation_server/src/repositories/terms/terms.repository.service.ts @@ -0,0 +1,7 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource, EntityManager } from 'typeorm'; + +@Injectable() +export class TermsRepositoryService { + constructor(private dataSource: DataSource) {} +} From 18adadce289e2833957961e7bbd21fd635717712 Mon Sep 17 00:00:00 2001 From: "oura.a" Date: Thu, 12 Oct 2023 18:09:58 +0900 Subject: [PATCH 04/13] =?UTF-8?q?Revert=20"=E3=83=AA=E3=83=9D=E3=82=B8?= =?UTF-8?q?=E3=83=88=E3=83=AA=E3=82=B5=E3=83=BC=E3=83=93=E3=82=B9=E4=BD=9C?= =?UTF-8?q?=E6=88=90"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 7793ecdb1e65d4977deaba1c4e99264f05c151da. --- .../src/repositories/terms/terms.repository.service.ts | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 dictation_server/src/repositories/terms/terms.repository.service.ts diff --git a/dictation_server/src/repositories/terms/terms.repository.service.ts b/dictation_server/src/repositories/terms/terms.repository.service.ts deleted file mode 100644 index a57c1cf..0000000 --- a/dictation_server/src/repositories/terms/terms.repository.service.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { DataSource, EntityManager } from 'typeorm'; - -@Injectable() -export class TermsRepositoryService { - constructor(private dataSource: DataSource) {} -} From c844837aecd7fe1904a7b16bc459defe089ad220 Mon Sep 17 00:00:00 2001 From: "oura.a" Date: Mon, 23 Oct 2023 06:53:23 +0000 Subject: [PATCH 05/13] Merged PR 514: Revert "Merge branch 'develop' into main" Revert "Merge branch 'develop' into main" Reverted commit `463b372c`. --- dictation_server/src/features/auth/auth.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dictation_server/src/features/auth/auth.service.ts b/dictation_server/src/features/auth/auth.service.ts index ca0b032..1454960 100644 --- a/dictation_server/src/features/auth/auth.service.ts +++ b/dictation_server/src/features/auth/auth.service.ts @@ -142,8 +142,8 @@ export class AuthService { { //ユーザーの属しているアカウントの管理者にユーザーが設定されていればadminをセットする role: `${role} ${ - user.account.primary_admin_user_id == user.id || - user.account.secondary_admin_user_id == user.id + user.account.primary_admin_user_id === user.id || + user.account.secondary_admin_user_id === user.id ? ADMIN_ROLES.ADMIN : ADMIN_ROLES.STANDARD }`, From 81d966ad1f234dfeba32f659841f0962528923a2 Mon Sep 17 00:00:00 2001 From: "oura.a" Date: Mon, 23 Oct 2023 06:54:00 +0000 Subject: [PATCH 06/13] Merged PR 515: Revert "Merge branch 'develop' into main" Revert "Merge branch 'develop' into main" Reverted commit `815082d5`. --- azure-pipelines-staging.yml | 11 ++++------- .../repositories/accounts/entity/account.entity.ts | 8 ++++---- .../entity/checkout_permission.entity.ts | 4 ++-- .../repositories/licenses/entity/license.entity.ts | 12 ++++++------ .../src/repositories/tasks/entity/task.entity.ts | 4 ++-- .../repositories/workflows/entity/workflow.entity.ts | 4 ++-- .../workflows/entity/workflow_typists.entity.ts | 4 ++-- 7 files changed, 22 insertions(+), 25 deletions(-) diff --git a/azure-pipelines-staging.yml b/azure-pipelines-staging.yml index 46175dc..22dd085 100644 --- a/azure-pipelines-staging.yml +++ b/azure-pipelines-staging.yml @@ -76,13 +76,10 @@ jobs: ADB2C_CLIENT_SECRET: $(adb2c-client-secret) MAIL_FROM: xxxxxx APP_DOMAIN: xxxxxxxxx - EMAIL_CONFIRM_LIFETIME: 0 - TENANT_NAME: xxxxxxxxxxxx - SIGNIN_FLOW_NAME: xxxxxxxxxxxx - STORAGE_TOKEN_EXPIRE_TIME: 0 - REFRESH_TOKEN_LIFETIME_WEB: 0 - REFRESH_TOKEN_LIFETIME_DEFAULT: 0 - ACCESS_TOKEN_LIFETIME_WEB: 0 + EMAIL_CONFIRM_LIFETIME : 0 + TENANT_NAME : xxxxxxxxxxxx + SIGNIN_FLOW_NAME : xxxxxxxxxxxx + STORAGE_TOKEN_EXPIRE_TIME : 0 - task: Docker@0 displayName: build inputs: diff --git a/dictation_server/src/repositories/accounts/entity/account.entity.ts b/dictation_server/src/repositories/accounts/entity/account.entity.ts index 7663f47..74a0a0a 100644 --- a/dictation_server/src/repositories/accounts/entity/account.entity.ts +++ b/dictation_server/src/repositories/accounts/entity/account.entity.ts @@ -13,7 +13,7 @@ export class Account { @PrimaryGeneratedColumn() id: number; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'unsigned big int' }) parent_account_id: number | null; @Column() @@ -34,13 +34,13 @@ export class Account { @Column({ default: false }) verified: boolean; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'unsigned big int' }) primary_admin_user_id: number | null; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'unsigned big int' }) secondary_admin_user_id: number | null; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'unsigned big int' }) active_worktype_id: number | null; @Column({ nullable: true, type: 'datetime' }) diff --git a/dictation_server/src/repositories/checkout_permissions/entity/checkout_permission.entity.ts b/dictation_server/src/repositories/checkout_permissions/entity/checkout_permission.entity.ts index 2ff148e..1269c1f 100644 --- a/dictation_server/src/repositories/checkout_permissions/entity/checkout_permission.entity.ts +++ b/dictation_server/src/repositories/checkout_permissions/entity/checkout_permission.entity.ts @@ -18,10 +18,10 @@ export class CheckoutPermission { @Column({}) task_id: number; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'unsigned big int' }) user_id: number | null; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'unsigned big int' }) user_group_id: number | null; @OneToOne(() => User, (user) => user.id) diff --git a/dictation_server/src/repositories/licenses/entity/license.entity.ts b/dictation_server/src/repositories/licenses/entity/license.entity.ts index c3adf94..b8e2cd2 100644 --- a/dictation_server/src/repositories/licenses/entity/license.entity.ts +++ b/dictation_server/src/repositories/licenses/entity/license.entity.ts @@ -79,16 +79,16 @@ export class License { @Column() status: string; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'unsigned big int' }) allocated_user_id: number | null; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'unsigned big int' }) order_id: number | null; @Column({ nullable: true, type: 'datetime' }) deleted_at: Date | null; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'unsigned big int' }) delete_order_id: number | null; @Column({ nullable: true, type: 'datetime' }) @@ -244,16 +244,16 @@ export class LicenseArchive { @Column() status: string; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'unsigned big int' }) allocated_user_id: number | null; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'unsigned big int' }) order_id: number | null; @Column({ nullable: true, type: 'datetime' }) deleted_at: Date | null; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'unsigned big int' }) delete_order_id: number | null; @Column({ nullable: true, type: 'datetime' }) diff --git a/dictation_server/src/repositories/tasks/entity/task.entity.ts b/dictation_server/src/repositories/tasks/entity/task.entity.ts index 27da363..c734e1f 100644 --- a/dictation_server/src/repositories/tasks/entity/task.entity.ts +++ b/dictation_server/src/repositories/tasks/entity/task.entity.ts @@ -26,11 +26,11 @@ export class Task { audio_file_id: number; @Column() status: string; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'unsigned big int' }) typist_user_id: number | null; @Column() priority: string; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'unsigned big int' }) template_file_id: number | null; @Column({ nullable: true, type: 'datetime' }) started_at: Date | null; diff --git a/dictation_server/src/repositories/workflows/entity/workflow.entity.ts b/dictation_server/src/repositories/workflows/entity/workflow.entity.ts index 52af3e2..6c51d96 100644 --- a/dictation_server/src/repositories/workflows/entity/workflow.entity.ts +++ b/dictation_server/src/repositories/workflows/entity/workflow.entity.ts @@ -24,10 +24,10 @@ export class Workflow { @Column() author_id: number; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'unsigned big int' }) worktype_id: number | null; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'unsigned big int' }) template_id: number | null; @Column({ nullable: true, type: 'datetime' }) diff --git a/dictation_server/src/repositories/workflows/entity/workflow_typists.entity.ts b/dictation_server/src/repositories/workflows/entity/workflow_typists.entity.ts index 31f8cbd..f92d02e 100644 --- a/dictation_server/src/repositories/workflows/entity/workflow_typists.entity.ts +++ b/dictation_server/src/repositories/workflows/entity/workflow_typists.entity.ts @@ -19,10 +19,10 @@ export class WorkflowTypist { @Column() workflow_id: number; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'unsigned big int' }) typist_id: number | null; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'unsigned big int' }) typist_group_id: number | null; @Column({ nullable: true, type: 'datetime' }) From b2f21cf4f8b85f708b373f3e1c1f7ae35832fd75 Mon Sep 17 00:00:00 2001 From: "oura.a" Date: Mon, 23 Oct 2023 06:54:48 +0000 Subject: [PATCH 07/13] Merged PR 516: Revert "Merge branch 'develop' into main" Revert "Merge branch 'develop' into main" Reverted commit `279fecec`. --- DockerfileServerDictation.dockerfile | 2 - azure-pipelines-production.yml | 77 +- azure-pipelines-staging.yml | 124 +- dictation_client/src/App.tsx | 9 +- dictation_client/src/AppRouter.tsx | 4 - dictation_client/src/api/api.ts | 603 +-------- dictation_client/src/app/store.ts | 2 - dictation_client/src/common/errors/code.ts | 3 - dictation_client/src/common/errors/utils.ts | 18 - dictation_client/src/common/msalConfig.ts | 2 +- dictation_client/src/common/token.ts | 13 - .../src/components/auth/constants.ts | 6 - .../src/features/account/operations.ts | 2 +- .../src/features/login/loginSlice.ts | 14 +- .../src/features/login/operations.ts | 7 +- .../src/features/login/selectors.ts | 4 - dictation_client/src/features/login/state.ts | 1 - .../src/features/signup/operations.ts | 41 - .../src/features/signup/selectors.ts | 3 - .../src/features/signup/signupSlice.ts | 16 +- dictation_client/src/features/signup/state.ts | 1 - .../src/features/terms/constants.ts | 8 - dictation_client/src/features/terms/index.ts | 4 - .../src/features/terms/operations.ts | 158 --- .../src/features/terms/selectors.ts | 20 - dictation_client/src/features/terms/state.ts | 15 - .../src/features/terms/termsSlice.ts | 64 - .../src/features/user/userSlice.ts | 11 +- .../src/features/workflow/operations.ts | 157 +-- .../src/features/workflow/selectors.ts | 9 - .../src/features/workflow/state.ts | 1 - .../src/features/workflow/workflowSlice.ts | 50 +- .../features/workflow/worktype/operations.ts | 72 -- .../src/pages/AccountPage/index.tsx | 11 +- dictation_client/src/pages/AuthPage/index.tsx | 78 -- .../src/pages/LoginPage/index.tsx | 74 +- .../src/pages/SignupPage/signupConfirm.tsx | 6 +- .../src/pages/SignupPage/signupInput.tsx | 16 +- .../src/pages/TermsPage/index.tsx | 188 --- .../src/pages/UserListPage/popup.tsx | 6 +- .../src/pages/UserListPage/updatePopup.tsx | 4 +- .../src/pages/WorkTypeIdSettingPage/index.tsx | 23 +- .../pages/WorkflowPage/editworkflowPopup.tsx | 313 ----- .../src/pages/WorkflowPage/index.tsx | 59 +- dictation_client/src/styles/app.module.scss | 86 +- .../src/styles/app.module.scss.d.ts | 10 +- dictation_client/src/translation/de.json | 14 +- dictation_client/src/translation/en.json | 14 +- dictation_client/src/translation/es.json | 14 +- dictation_client/src/translation/fr.json | 14 +- ...-delete-foreign-key-for-account-delete.sql | 33 - .../046-insert_initial_data_terms.sql | 9 - dictation_server/src/api/odms/openapi.json | 85 +- dictation_server/src/common/error/code.ts | 2 - dictation_server/src/common/error/message.ts | 2 - .../src/common/guards/role/roleguards.ts | 17 +- dictation_server/src/common/jwt/jwt.ts | 4 +- .../src/common/password/password.ts | 3 +- dictation_server/src/common/test/modules.ts | 8 +- dictation_server/src/common/test/utility.ts | 40 +- dictation_server/src/constants/index.ts | 11 +- .../accounts/accounts.controller.spec.ts | 6 +- .../features/accounts/accounts.controller.ts | 446 +------ .../src/features/accounts/accounts.module.ts | 3 +- .../accounts/accounts.service.spec.ts | 1083 +++-------------- .../src/features/accounts/accounts.service.ts | 244 +--- .../accounts/test/accounts.service.mock.ts | 39 +- .../src/features/accounts/test/utility.ts | 36 +- .../src/features/accounts/types/types.ts | 14 +- .../src/features/auth/auth.controller.spec.ts | 7 - .../src/features/auth/auth.controller.ts | 12 - .../src/features/auth/auth.module.ts | 8 +- .../src/features/auth/auth.service.spec.ts | 169 +-- .../src/features/auth/auth.service.ts | 84 +- .../features/auth/test/auth.service.mock.ts | 19 +- .../src/features/auth/test/utility.ts | 18 - .../src/features/auth/types/types.ts | 8 - .../src/features/files/files.controller.ts | 120 +- .../src/features/files/files.service.spec.ts | 92 +- .../src/features/files/files.service.ts | 65 +- .../features/files/test/files.service.mock.ts | 18 +- .../src/features/files/test/utility.ts | 2 +- .../features/licenses/licenses.controller.ts | 108 +- .../licenses/licenses.service.spec.ts | 194 ++- .../src/features/licenses/licenses.service.ts | 20 +- .../licenses/test/liscense.service.mock.ts | 6 +- .../src/features/licenses/test/utility.ts | 20 +- .../src/features/licenses/types/types.ts | 4 +- .../notification/notification.controller.ts | 17 +- .../test/notification.service.mock.ts | 4 +- .../src/features/tasks/tasks.controller.ts | 135 +- .../src/features/tasks/tasks.service.spec.ts | 349 +++--- .../src/features/tasks/tasks.service.ts | 58 +- .../features/tasks/test/tasks.service.mock.ts | 54 +- .../src/features/tasks/test/utility.ts | 6 +- .../src/features/tasks/types/convert.ts | 12 +- .../templates/templates.controller.ts | 27 +- .../templates/templates.service.spec.ts | 14 +- .../src/features/templates/test/utility.ts | 3 - .../features/terms/terms.controller.spec.ts | 7 +- .../src/features/terms/terms.controller.ts | 11 +- .../src/features/terms/terms.module.ts | 4 +- .../src/features/terms/terms.service.spec.ts | 96 +- .../src/features/terms/terms.service.ts | 44 +- .../src/features/terms/types/types.ts | 13 +- .../features/users/test/users.service.mock.ts | 12 +- .../src/features/users/test/utility.ts | 2 +- .../features/users/users.controller.spec.ts | 6 +- .../src/features/users/users.controller.ts | 177 +-- .../src/features/users/users.module.ts | 3 +- .../src/features/users/users.service.spec.ts | 460 +++---- .../src/features/users/users.service.ts | 195 +-- .../src/features/workflows/test/utility.ts | 9 +- .../workflows/workflows.controller.ts | 85 +- .../workflows/workflows.service.spec.ts | 496 +------- .../features/workflows/workflows.service.ts | 110 +- .../src/gateways/adb2c/adb2c.service.ts | 13 +- .../blobstorage/blobstorage.service.ts | 22 +- .../notificationhub.service.ts | 4 +- .../src/gateways/sendgrid/sendgrid.service.ts | 27 +- dictation_server/src/main.ts | 10 +- .../accounts/accounts.repository.service.ts | 156 +-- .../accounts/entity/account.entity.ts | 40 +- .../audio_files/entity/audio_file.entity.ts | 10 +- .../entity/audio_option_item.entity.ts | 2 +- .../entity/checkout_permission.entity.ts | 14 +- .../licenses/entity/license.entity.ts | 193 ++- .../licenses/licenses.repository.service.ts | 11 +- .../repositories/tasks/entity/task.entity.ts | 28 +- .../tasks/tasks.repository.service.ts | 8 +- .../entity/template_file.entity.ts | 10 +- .../repositories/terms/entity/term.entity.ts | 37 - .../terms/terms.repository.module.ts | 11 - .../terms/terms.repository.service.ts | 47 - .../user_groups/entity/user_group.entity.ts | 28 +- .../entity/user_group_member.entity.ts | 30 +- .../repositories/users/entity/user.entity.ts | 81 +- .../src/repositories/users/errors/types.ts | 4 - .../users/users.repository.service.ts | 142 +-- .../workflows/entity/workflow.entity.ts | 34 +- .../entity/workflow_typists.entity.ts | 32 +- .../repositories/workflows/errors/types.ts | 4 +- .../workflows/workflows.repository.service.ts | 50 +- .../worktypes/entity/option_item.entity.ts | 22 +- .../worktypes/entity/worktype.entity.ts | 26 +- .../repositories/worktypes/errors/types.ts | 2 - .../worktypes/worktypes.repository.service.ts | 56 +- dictation_server/tsconfig.json | 2 +- 148 files changed, 1700 insertions(+), 7375 deletions(-) delete mode 100644 dictation_client/src/features/terms/constants.ts delete mode 100644 dictation_client/src/features/terms/index.ts delete mode 100644 dictation_client/src/features/terms/operations.ts delete mode 100644 dictation_client/src/features/terms/selectors.ts delete mode 100644 dictation_client/src/features/terms/state.ts delete mode 100644 dictation_client/src/features/terms/termsSlice.ts delete mode 100644 dictation_client/src/pages/AuthPage/index.tsx delete mode 100644 dictation_client/src/pages/TermsPage/index.tsx delete mode 100644 dictation_client/src/pages/WorkflowPage/editworkflowPopup.tsx delete mode 100644 dictation_server/db/migrations/045-delete-foreign-key-for-account-delete.sql delete mode 100644 dictation_server/db/migrations/046-insert_initial_data_terms.sql delete mode 100644 dictation_server/src/features/auth/test/utility.ts delete mode 100644 dictation_server/src/repositories/terms/entity/term.entity.ts delete mode 100644 dictation_server/src/repositories/terms/terms.repository.module.ts delete mode 100644 dictation_server/src/repositories/terms/terms.repository.service.ts diff --git a/DockerfileServerDictation.dockerfile b/DockerfileServerDictation.dockerfile index 2efefee..aa666ba 100644 --- a/DockerfileServerDictation.dockerfile +++ b/DockerfileServerDictation.dockerfile @@ -20,6 +20,4 @@ RUN mkdir build \ COPY --from=build-container app/dictation_server/dist/ dist/ COPY --from=build-container app/dictation_server/.env ./ COPY --from=build-container app/dictation_server/node_modules/ node_modules/ -ARG BUILD_VERSION -ENV BUILD_VERSION=${BUILD_VERSION} CMD ["node", "./dist/main.js" ] diff --git a/azure-pipelines-production.yml b/azure-pipelines-production.yml index 1359ec5..45eba43 100644 --- a/azure-pipelines-production.yml +++ b/azure-pipelines-production.yml @@ -1,4 +1,5 @@ -# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConenctionを作成しておくこと +# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConenctionを作成し、 +# 環境変数 AZURE_SERVICE_CONNECTION の値としてServiceConenction名を設定しておくこと # また、環境変数 STATIC_DICTATION_DEPLOYMENT_TOKEN の値として静的WebAppsのデプロイトークンを設定しておくこと trigger: tags: @@ -25,40 +26,43 @@ jobs: fi displayName: 'タグが付けられたCommitがmainブランチに存在するか確認' - job: backend_deploy - dependsOn: initialize - condition: succeeded('initialize') displayName: Backend Deploy pool: - vmImage: ubuntu-latest + name: odms-deploy-pipeline steps: - checkout: self clean: true fetchDepth: 1 - - task: AzureWebAppContainer@1 + - task: AzureRmWebAppDeployment@4 inputs: - azureSubscription: 'omds-service-connection-prod' - appName: 'app-odms-dictation-prod' - deployToSlotOrASE: true - resourceGroupName: 'odms-prod-rg' - slotName: 'staging' - containers: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation:$(Build.SourceVersion)' - # TODO: stagingパイプライン実装時、staging用のイメージに変更する + ConnectionType: 'AzureRM' + azureSubscription: $(AZURE_SERVICE_CONNECTION) + appType: 'webAppContainer' + WebAppName: 'app-odms-dictation-prod' + ResourceGroupName: 'prod-application-rg' + DockerNamespace: 'crodmsregistrymaintenance.azurecr.io' + DockerRepository: '$(Build.Repository.Name)/staging/dictation' + DockerImageTag: '$(Build.SourceVersion)' - job: frontend_deploy - dependsOn: backend_deploy - condition: succeeded('backend_deploy') displayName: Deploy Frontend Files variables: storageAccountName: saomdspipeline containerName: staging pool: - vmImage: ubuntu-latest + name: odms-deploy-pipeline steps: - checkout: self clean: true fetchDepth: 1 + - task: AzureKeyVault@2 + displayName: 'Azure Key Vault: kv-odms-secret-prod' + inputs: + ConnectedServiceName: $(AZURE_SERVICE_CONNECTION) + KeyVaultName: kv-odms-secret-prod + SecretsFilter: '*' - task: AzureCLI@2 inputs: - azureSubscription: 'omds-service-connection-prod' + azureSubscription: $(AZURE_SERVICE_CONNECTION) scriptType: 'bash' scriptLocation: 'inlineScript' inlineScript: | @@ -84,42 +88,15 @@ jobs: is_static_export: false verbose: false azure_static_web_apps_api_token: $(STATIC_DICTATION_DEPLOYMENT_TOKEN) -- job: smoke_test - dependsOn: frontend_deploy - condition: succeeded('frontend_deploy') - displayName: 'smoke test' - pool: - name: odms-deploy-pipeline - steps: - - checkout: self - clean: true - fetchDepth: 1 - # スモークテスト用にjobを確保 -- job: swap_slot - dependsOn: smoke_test - condition: succeeded('smoke_test') - displayName: 'Swap Staging and Production' - pool: - name: odms-deploy-pipeline - steps: - - checkout: self - clean: true - fetchDepth: 1 - - task: AzureAppServiceManage@0 - displayName: 'Azure App Service Manage: app-odms-dictation-prod' - inputs: - azureSubscription: 'omds-service-connection-prod' - action: 'Swap Slots' - WebAppName: 'app-odms-dictation-prod' - ResourceGroupName: 'odms-prod-rg' - SourceSlot: 'staging' - SwapWithProduction: true - job: migration - dependsOn: swap_slot - condition: succeeded('swap_slot') + condition: succeeded('initialize') displayName: DB migration + dependsOn: + - initialize + - backend_deploy + - frontend_deploy pool: - name: odms-deploy-pipeline + name: db-migrate-pipelines steps: - checkout: self clean: true @@ -127,7 +104,7 @@ jobs: - task: AzureKeyVault@2 displayName: 'Azure Key Vault: kv-odms-secret-prod' inputs: - ConnectedServiceName: 'omds-service-connection-prod' + ConnectedServiceName: $(AZURE_SERVICE_CONNECTION) KeyVaultName: kv-odms-secret-prod - task: CmdLine@2 displayName: migration diff --git a/azure-pipelines-staging.yml b/azure-pipelines-staging.yml index 22dd085..78965ab 100644 --- a/azure-pipelines-staging.yml +++ b/azure-pipelines-staging.yml @@ -1,4 +1,5 @@ -# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConenctionを作成しておくこと +# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConenctionを作成し、 +# 環境変数 AZURE_SERVICE_CONNECTION の値としてServiceConenction名を設定しておくこと # また、環境変数 STATIC_DICTATION_DEPLOYMENT_TOKEN の値として静的WebAppsのデプロイトークンを設定しておくこと trigger: branches: @@ -46,7 +47,7 @@ jobs: - task: AzureKeyVault@2 displayName: 'Azure Key Vault: kv-odms-secret-stg' inputs: - ConnectedServiceName: 'omds-service-connection-stg' + ConnectedServiceName: $(AZURE_SERVICE_CONNECTION) KeyVaultName: kv-odms-secret-stg SecretsFilter: '*' - task: Bash@3 @@ -58,7 +59,6 @@ jobs: npm run test env: JWT_PUBLIC_KEY: $(token-public-key) - JWT_PRIVATE_KEY: $(token-private-key) SENDGRID_API_KEY: $(sendgrid-api-key) NOTIFICATION_HUB_NAME: $(notification-hub-name) NOTIFICATION_HUB_CONNECT_STRING: $(notification-hub-connect-string) @@ -74,31 +74,43 @@ jobs: ADB2C_TENANT_ID: $(adb2c-tenant-id) ADB2C_CLIENT_ID: $(adb2c-client-id) ADB2C_CLIENT_SECRET: $(adb2c-client-secret) - MAIL_FROM: xxxxxx - APP_DOMAIN: xxxxxxxxx - EMAIL_CONFIRM_LIFETIME : 0 - TENANT_NAME : xxxxxxxxxxxx - SIGNIN_FLOW_NAME : xxxxxxxxxxxx - STORAGE_TOKEN_EXPIRE_TIME : 0 - task: Docker@0 displayName: build inputs: - azureSubscriptionEndpoint: 'omds-service-connection-stg' + azureSubscriptionEndpoint: $(AZURE_SERVICE_CONNECTION) azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}' dockerFile: DockerfileServerDictation.dockerfile - imageName: odmscloud/staging/dictation:$(Build.SourceVersion) - buildArguments: | - BUILD_VERSION=$(Build.SourceVersion) + imageName: $(Build.Repository.Name)/staging/dictation:$(Build.SourceVersion) - task: Docker@0 displayName: push inputs: - azureSubscriptionEndpoint: 'omds-service-connection-stg' + azureSubscriptionEndpoint: $(AZURE_SERVICE_CONNECTION) azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}' action: Push an image - imageName: odmscloud/staging/dictation:$(Build.SourceVersion) -- job: frontend_build + imageName: $(Build.Repository.Name)/staging/dictation:$(Build.SourceVersion) +- job: backend_deploy dependsOn: backend_build condition: succeeded('backend_build') + displayName: Backend Deploy + pool: + name: odms-deploy-pipeline + steps: + - checkout: self + clean: true + fetchDepth: 1 + - task: AzureRmWebAppDeployment@4 + inputs: + ConnectionType: 'AzureRM' + azureSubscription: $(AZURE_SERVICE_CONNECTION) + appType: 'webAppContainer' + WebAppName: 'app-odms-dictation-stg' + ResourceGroupName: 'stg-application-rg' + DockerNamespace: 'crodmsregistrymaintenance.azurecr.io' + DockerRepository: '$(Build.Repository.Name)/staging/dictation' + DockerImageTag: '$(Build.SourceVersion)' +- job: frontend_build + dependsOn: initialize + condition: succeeded('initialize') displayName: Build Frontend Files variables: storageAccountName: saomdspipeline @@ -129,7 +141,7 @@ jobs: replaceExistingArchive: true - task: AzureCLI@2 inputs: - azureSubscription: 'omds-service-connection-stg' + azureSubscription: $(AZURE_SERVICE_CONNECTION) scriptType: 'bash' scriptLocation: 'inlineScript' inlineScript: | @@ -139,22 +151,10 @@ jobs: --container-name $(containerName) \ --name $(Build.SourceVersion).zip \ --type block \ - --overwrite \ --file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip -- job: backend_deploy +- job: frontend_deploy dependsOn: frontend_build condition: succeeded('frontend_build') - displayName: Backend Deploy - pool: - name: odms-deploy-pipeline - steps: - - checkout: self - clean: true - fetchDepth: 1 - # TODO: Productionと同様にデプロイを行う -- job: frontend_deploy - dependsOn: backend_deploy - condition: succeeded('backend_deploy') displayName: Deploy Frontend Files variables: storageAccountName: saomdspipeline @@ -165,15 +165,69 @@ jobs: - checkout: self clean: true fetchDepth: 1 - # TODO: Productionと同様にデプロイを行う + - task: AzureKeyVault@2 + displayName: 'Azure Key Vault: kv-odms-secret-stg' + inputs: + ConnectedServiceName: $(AZURE_SERVICE_CONNECTION) + KeyVaultName: kv-odms-secret-stg + SecretsFilter: '*' + - task: AzureCLI@2 + inputs: + azureSubscription: $(AZURE_SERVICE_CONNECTION) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + az storage blob download \ + --auth-mode login \ + --account-name $(storageAccountName) \ + --container-name $(containerName) \ + --name $(Build.SourceVersion).zip \ + --file $(Build.SourcesDirectory)/$(Build.SourceVersion).zip + - task: Bash@3 + displayName: Bash Script + inputs: + targetType: inline + script: unzip $(Build.SourcesDirectory)/$(Build.SourceVersion).zip -d $(Build.SourcesDirectory)/$(Build.SourceVersion) + - task: AzureStaticWebApp@0 + displayName: 'Static Web App: ' + inputs: + workingDirectory: '$(Build.SourcesDirectory)' + app_location: '/$(Build.SourceVersion)' + config_file_location: /dictation_client + skip_app_build: true + skip_api_build: true + is_static_export: false + verbose: false + azure_static_web_apps_api_token: $(STATIC_DICTATION_DEPLOYMENT_TOKEN) - job: migration - dependsOn: frontend_deploy - condition: succeeded('frontend_deploy') + condition: succeeded('initialize') displayName: DB migration + dependsOn: + - initialize + - backend_deploy + - frontend_deploy pool: - name: odms-deploy-pipeline + name: db-migrate-pipelines steps: - checkout: self clean: true fetchDepth: 1 - # TODO: Productionと同様にマイグレーションを行う \ No newline at end of file + - task: AzureKeyVault@2 + displayName: 'Azure Key Vault: kv-odms-secret-stg' + inputs: + ConnectedServiceName: $(AZURE_SERVICE_CONNECTION) + KeyVaultName: kv-odms-secret-stg + - task: CmdLine@2 + displayName: migration + inputs: + script: >2 + # DB接続情報書き換え + sed -i -e "s/DB_NAME/$(db-name)/g" ./dictation_server/db/dbconfig.yml + sed -i -e "s/DB_PASS/$(db-pass)/g" ./dictation_server/db/dbconfig.yml + sed -i -e "s/DB_USERNAME/$(db-user)/g" ./dictation_server/db/dbconfig.yml + sed -i -e "s/DB_PORT/$(db-port)/g" ./dictation_server/db/dbconfig.yml + sed -i -e "s/DB_HOST/$(db-host)/g" ./dictation_server/db/dbconfig.yml + sql-migrate --version + cat ./dictation_server/db/dbconfig.yml + # migration実行 + sql-migrate up -config=./dictation_server/db/dbconfig.yml -env=ci \ No newline at end of file diff --git a/dictation_client/src/App.tsx b/dictation_client/src/App.tsx index d9717a9..39bfec6 100644 --- a/dictation_client/src/App.tsx +++ b/dictation_client/src/App.tsx @@ -11,7 +11,6 @@ import { useTranslation } from "react-i18next"; import Snackbar from "components/snackbar"; import { selectSnackber } from "features/ui/selectors"; import { closeSnackbar } from "features/ui/uiSlice"; -import { UNAUTHORIZED_TO_CONTINUE_ERROR_CODES } from "components/auth/constants"; const App = (): JSX.Element => { const dispatch = useDispatch(); @@ -22,12 +21,8 @@ const App = (): JSX.Element => { useEffect(() => { const id = globalAxios.interceptors.response.use( (response: AxiosResponse) => response, - (e: AxiosError<{ code?: string }>) => { - if ( - e?.response?.status === 401 && - e?.response?.data?.code && - !UNAUTHORIZED_TO_CONTINUE_ERROR_CODES.includes(e.response.data.code) - ) { + (e: AxiosError) => { + if (e?.response?.status === 401) { dispatch(clearToken()); instance.logoutRedirect({ postLogoutRedirectUri: "/?logout=true", diff --git a/dictation_client/src/AppRouter.tsx b/dictation_client/src/AppRouter.tsx index 7abc3ca..cf6e1d3 100644 --- a/dictation_client/src/AppRouter.tsx +++ b/dictation_client/src/AppRouter.tsx @@ -1,6 +1,5 @@ import { Route, Routes } from "react-router-dom"; import TopPage from "pages/TopPage"; -import AuthPage from "pages/AuthPage"; import LoginPage from "pages/LoginPage"; import SamplePage from "pages/SamplePage"; import { AuthErrorPage } from "pages/ErrorPage"; @@ -21,21 +20,18 @@ import WorkflowPage from "pages/WorkflowPage"; import TypistGroupSettingPage from "pages/TypistGroupSettingPage"; import WorktypeIdSettingPage from "pages/WorkTypeIdSettingPage"; import AccountPage from "pages/AccountPage"; -import AcceptToUsePage from "pages/TermsPage"; import { TemplateFilePage } from "pages/TemplateFilePage"; import { AccountDeleteSuccess } from "pages/AccountPage/accountDeleteSuccess"; const AppRouter: React.FC = () => ( } /> - } /> } /> } /> } /> - } /> } /> } /> } /> diff --git a/dictation_client/src/api/api.ts b/dictation_client/src/api/api.ts index 680a7e6..36a1050 100644 --- a/dictation_client/src/api/api.ts +++ b/dictation_client/src/api/api.ts @@ -127,7 +127,7 @@ export interface AllocatableLicenseInfo { * @type {string} * @memberof AllocatableLicenseInfo */ - 'expiryDate'?: string; + 'expiryDate': string; } /** * @@ -442,17 +442,11 @@ export interface CreateAccountRequest { */ 'adminPassword': string; /** - * 同意済み利用規約のバージョン(EULA) + * 同意済み利用規約のバージョン * @type {string} * @memberof CreateAccountRequest */ - 'acceptedEulaVersion': string; - /** - * 同意済み利用規約のバージョン(DPA) - * @type {string} - * @memberof CreateAccountRequest - */ - 'acceptedDpaVersion': string; + 'acceptedTermsVersion': string; /** * reCAPTCHA Token * @type {string} @@ -536,7 +530,7 @@ export interface CreateTypistGroupRequest { */ export interface CreateWorkflowsRequest { /** - * Authorの内部ID + * Authornの内部ID * @type {number} * @memberof CreateWorkflowsRequest */ @@ -649,32 +643,6 @@ export interface ErrorResponse { */ 'code': string; } -/** - * - * @export - * @interface GetAccountInfoMinimalAccessRequest - */ -export interface GetAccountInfoMinimalAccessRequest { - /** - * idトークン - * @type {string} - * @memberof GetAccountInfoMinimalAccessRequest - */ - 'idToken': string; -} -/** - * - * @export - * @interface GetAccountInfoMinimalAccessResponse - */ -export interface GetAccountInfoMinimalAccessResponse { - /** - * 階層 - * @type {number} - * @memberof GetAccountInfoMinimalAccessResponse - */ - 'tier': number; -} /** * * @export @@ -1026,19 +994,6 @@ export interface GetTemplatesResponse { */ 'templates': Array; } -/** - * - * @export - * @interface GetTermsInfoResponse - */ -export interface GetTermsInfoResponse { - /** - * - * @type {Array} - * @memberof GetTermsInfoResponse - */ - 'termsInfo': Array; -} /** * * @export @@ -1829,25 +1784,6 @@ export interface TemplateUploadLocationResponse { */ 'url': string; } -/** - * - * @export - * @interface TermInfo - */ -export interface TermInfo { - /** - * 利用規約種別 - * @type {string} - * @memberof TermInfo - */ - 'documentType': string; - /** - * バージョン - * @type {string} - * @memberof TermInfo - */ - 'version': string; -} /** * * @export @@ -1924,31 +1860,6 @@ export interface TypistGroup { */ 'name': string; } -/** - * - * @export - * @interface UpdateAcceptedVersionRequest - */ -export interface UpdateAcceptedVersionRequest { - /** - * IDトークン - * @type {string} - * @memberof UpdateAcceptedVersionRequest - */ - 'idToken': string; - /** - * 更新バージョン(EULA) - * @type {string} - * @memberof UpdateAcceptedVersionRequest - */ - 'acceptedEULAVersion': string; - /** - * 更新バージョン(DPA) - * @type {string} - * @memberof UpdateAcceptedVersionRequest - */ - 'acceptedDPAVersion'?: string; -} /** * * @export @@ -2012,37 +1923,6 @@ export interface UpdateTypistGroupRequest { */ 'typistIds': Array; } -/** - * - * @export - * @interface UpdateWorkflowRequest - */ -export interface UpdateWorkflowRequest { - /** - * Authorの内部ID - * @type {number} - * @memberof UpdateWorkflowRequest - */ - 'authorId': number; - /** - * Worktypeの内部ID - * @type {number} - * @memberof UpdateWorkflowRequest - */ - 'worktypeId'?: number; - /** - * テンプレートの内部ID - * @type {number} - * @memberof UpdateWorkflowRequest - */ - 'templateId'?: number; - /** - * ルーティング候補のタイピストユーザー/タイピストグループ - * @type {Array} - * @memberof UpdateWorkflowRequest - */ - 'typists': Array; -} /** * * @export @@ -2528,9 +2408,9 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat * @param {*} [options] Override http request option. * @throws {RequiredError} */ - deleteAccountAndData: async (deleteAccountRequest: DeleteAccountRequest, options: AxiosRequestConfig = {}): Promise => { + deleteAccount: async (deleteAccountRequest: DeleteAccountRequest, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'deleteAccountRequest' is not null or undefined - assertParamExists('deleteAccountAndData', 'deleteAccountRequest', deleteAccountRequest) + assertParamExists('deleteAccount', 'deleteAccountRequest', deleteAccountRequest) const localVarPath = `/accounts/delete`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -2561,80 +2441,6 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat options: localVarRequestOptions, }; }, - /** - * - * @summary - * @param {number} id Worktypeの内部ID - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - deleteWorktype: async (id: number, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'id' is not null or undefined - assertParamExists('deleteWorktype', 'id', id) - const localVarPath = `/accounts/worktypes/{id}/delete` - .replace(`{${"id"}}`, encodeURIComponent(String(id))); - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication bearer required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, - /** - * - * @summary - * @param {GetAccountInfoMinimalAccessRequest} getAccountInfoMinimalAccessRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getAccountInfoMinimalAccess: async (getAccountInfoMinimalAccessRequest: GetAccountInfoMinimalAccessRequest, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'getAccountInfoMinimalAccessRequest' is not null or undefined - assertParamExists('getAccountInfoMinimalAccess', 'getAccountInfoMinimalAccessRequest', getAccountInfoMinimalAccessRequest) - const localVarPath = `/accounts/minimal-access`; - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - - - localVarHeaderParameter['Content-Type'] = 'application/json'; - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - localVarRequestOptions.data = serializeDataIfNeeded(getAccountInfoMinimalAccessRequest, localVarRequestOptions, configuration) - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, /** * ログインしているユーザーのアカウント配下のAuthor一覧を取得します * @summary @@ -3374,30 +3180,8 @@ export const AccountsApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async deleteAccountAndData(deleteAccountRequest: DeleteAccountRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAccountAndData(deleteAccountRequest, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, - /** - * - * @summary - * @param {number} id Worktypeの内部ID - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async deleteWorktype(id: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.deleteWorktype(id, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, - /** - * - * @summary - * @param {GetAccountInfoMinimalAccessRequest} getAccountInfoMinimalAccessRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async getAccountInfoMinimalAccess(getAccountInfoMinimalAccessRequest: GetAccountInfoMinimalAccessRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAccountInfoMinimalAccess(getAccountInfoMinimalAccessRequest, options); + async deleteAccount(deleteAccountRequest: DeleteAccountRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAccount(deleteAccountRequest, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -3662,28 +3446,8 @@ export const AccountsApiFactory = function (configuration?: Configuration, baseP * @param {*} [options] Override http request option. * @throws {RequiredError} */ - deleteAccountAndData(deleteAccountRequest: DeleteAccountRequest, options?: any): AxiosPromise { - return localVarFp.deleteAccountAndData(deleteAccountRequest, options).then((request) => request(axios, basePath)); - }, - /** - * - * @summary - * @param {number} id Worktypeの内部ID - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - deleteWorktype(id: number, options?: any): AxiosPromise { - return localVarFp.deleteWorktype(id, options).then((request) => request(axios, basePath)); - }, - /** - * - * @summary - * @param {GetAccountInfoMinimalAccessRequest} getAccountInfoMinimalAccessRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getAccountInfoMinimalAccess(getAccountInfoMinimalAccessRequest: GetAccountInfoMinimalAccessRequest, options?: any): AxiosPromise { - return localVarFp.getAccountInfoMinimalAccess(getAccountInfoMinimalAccessRequest, options).then((request) => request(axios, basePath)); + deleteAccount(deleteAccountRequest: DeleteAccountRequest, options?: any): AxiosPromise { + return localVarFp.deleteAccount(deleteAccountRequest, options).then((request) => request(axios, basePath)); }, /** * ログインしているユーザーのアカウント配下のAuthor一覧を取得します @@ -3943,32 +3707,8 @@ export class AccountsApi extends BaseAPI { * @throws {RequiredError} * @memberof AccountsApi */ - public deleteAccountAndData(deleteAccountRequest: DeleteAccountRequest, options?: AxiosRequestConfig) { - return AccountsApiFp(this.configuration).deleteAccountAndData(deleteAccountRequest, options).then((request) => request(this.axios, this.basePath)); - } - - /** - * - * @summary - * @param {number} id Worktypeの内部ID - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof AccountsApi - */ - public deleteWorktype(id: number, options?: AxiosRequestConfig) { - return AccountsApiFp(this.configuration).deleteWorktype(id, options).then((request) => request(this.axios, this.basePath)); - } - - /** - * - * @summary - * @param {GetAccountInfoMinimalAccessRequest} getAccountInfoMinimalAccessRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof AccountsApi - */ - public getAccountInfoMinimalAccess(getAccountInfoMinimalAccessRequest: GetAccountInfoMinimalAccessRequest, options?: AxiosRequestConfig) { - return AccountsApiFp(this.configuration).getAccountInfoMinimalAccess(getAccountInfoMinimalAccessRequest, options).then((request) => request(this.axios, this.basePath)); + public deleteAccount(deleteAccountRequest: DeleteAccountRequest, options?: AxiosRequestConfig) { + return AccountsApiFp(this.configuration).deleteAccount(deleteAccountRequest, options).then((request) => request(this.axios, this.basePath)); } /** @@ -6241,105 +5981,6 @@ export class TemplatesApi extends BaseAPI { -/** - * TermsApi - axios parameter creator - * @export - */ -export const TermsApiAxiosParamCreator = function (configuration?: Configuration) { - return { - /** - * - * @summary - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getTermsInfo: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/terms`; - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, - } -}; - -/** - * TermsApi - functional programming interface - * @export - */ -export const TermsApiFp = function(configuration?: Configuration) { - const localVarAxiosParamCreator = TermsApiAxiosParamCreator(configuration) - return { - /** - * - * @summary - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async getTermsInfo(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getTermsInfo(options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, - } -}; - -/** - * TermsApi - factory interface - * @export - */ -export const TermsApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { - const localVarFp = TermsApiFp(configuration) - return { - /** - * - * @summary - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getTermsInfo(options?: any): AxiosPromise { - return localVarFp.getTermsInfo(options).then((request) => request(axios, basePath)); - }, - }; -}; - -/** - * TermsApi - object-oriented interface - * @export - * @class TermsApi - * @extends {BaseAPI} - */ -export class TermsApi extends BaseAPI { - /** - * - * @summary - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof TermsApi - */ - public getTermsInfo(options?: AxiosRequestConfig) { - return TermsApiFp(this.configuration).getTermsInfo(options).then((request) => request(this.axios, this.basePath)); - } -} - - - /** * UsersApi - axios parameter creator * @export @@ -6640,42 +6281,6 @@ export const UsersApiAxiosParamCreator = function (configuration?: Configuration options: localVarRequestOptions, }; }, - /** - * 利用規約同意バージョンを更新 - * @summary - * @param {UpdateAcceptedVersionRequest} updateAcceptedVersionRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - updateAcceptedVersion: async (updateAcceptedVersionRequest: UpdateAcceptedVersionRequest, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'updateAcceptedVersionRequest' is not null or undefined - assertParamExists('updateAcceptedVersion', 'updateAcceptedVersionRequest', updateAcceptedVersionRequest) - const localVarPath = `/users/accepted-version`; - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - - - localVarHeaderParameter['Content-Type'] = 'application/json'; - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - localVarRequestOptions.data = serializeDataIfNeeded(updateAcceptedVersionRequest, localVarRequestOptions, configuration) - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, /** * ログインしているユーザーのタスクソート条件を更新します * @summary @@ -6851,17 +6456,6 @@ export const UsersApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.signup(signupRequest, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, - /** - * 利用規約同意バージョンを更新 - * @summary - * @param {UpdateAcceptedVersionRequest} updateAcceptedVersionRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async updateAcceptedVersion(updateAcceptedVersionRequest: UpdateAcceptedVersionRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.updateAcceptedVersion(updateAcceptedVersionRequest, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, /** * ログインしているユーザーのタスクソート条件を更新します * @summary @@ -6971,16 +6565,6 @@ export const UsersApiFactory = function (configuration?: Configuration, basePath signup(signupRequest: SignupRequest, options?: any): AxiosPromise { return localVarFp.signup(signupRequest, options).then((request) => request(axios, basePath)); }, - /** - * 利用規約同意バージョンを更新 - * @summary - * @param {UpdateAcceptedVersionRequest} updateAcceptedVersionRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - updateAcceptedVersion(updateAcceptedVersionRequest: UpdateAcceptedVersionRequest, options?: any): AxiosPromise { - return localVarFp.updateAcceptedVersion(updateAcceptedVersionRequest, options).then((request) => request(axios, basePath)); - }, /** * ログインしているユーザーのタスクソート条件を更新します * @summary @@ -7104,18 +6688,6 @@ export class UsersApi extends BaseAPI { return UsersApiFp(this.configuration).signup(signupRequest, options).then((request) => request(this.axios, this.basePath)); } - /** - * 利用規約同意バージョンを更新 - * @summary - * @param {UpdateAcceptedVersionRequest} updateAcceptedVersionRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof UsersApi - */ - public updateAcceptedVersion(updateAcceptedVersionRequest: UpdateAcceptedVersionRequest, options?: AxiosRequestConfig) { - return UsersApiFp(this.configuration).updateAcceptedVersion(updateAcceptedVersionRequest, options).then((request) => request(this.axios, this.basePath)); - } - /** * ログインしているユーザーのタスクソート条件を更新します * @summary @@ -7189,44 +6761,6 @@ export const WorkflowsApiAxiosParamCreator = function (configuration?: Configura options: localVarRequestOptions, }; }, - /** - * アカウント内のワークフローを削除します - * @summary - * @param {number} workflowId ワークフローの内部ID - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - deleteWorkflow: async (workflowId: number, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'workflowId' is not null or undefined - assertParamExists('deleteWorkflow', 'workflowId', workflowId) - const localVarPath = `/workflows/{workflowId}/delete` - .replace(`{${"workflowId"}}`, encodeURIComponent(String(workflowId))); - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication bearer required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, /** * アカウント内のワークフローの一覧を取得します * @summary @@ -7256,50 +6790,6 @@ export const WorkflowsApiAxiosParamCreator = function (configuration?: Configura let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, - /** - * アカウント内のワークフローを編集します - * @summary - * @param {number} workflowId ワークフローの内部ID - * @param {UpdateWorkflowRequest} updateWorkflowRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - updateWorkflow: async (workflowId: number, updateWorkflowRequest: UpdateWorkflowRequest, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'workflowId' is not null or undefined - assertParamExists('updateWorkflow', 'workflowId', workflowId) - // verify required parameter 'updateWorkflowRequest' is not null or undefined - assertParamExists('updateWorkflow', 'updateWorkflowRequest', updateWorkflowRequest) - const localVarPath = `/workflows/{workflowId}` - .replace(`{${"workflowId"}}`, encodeURIComponent(String(workflowId))); - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication bearer required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - - - localVarHeaderParameter['Content-Type'] = 'application/json'; - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - localVarRequestOptions.data = serializeDataIfNeeded(updateWorkflowRequest, localVarRequestOptions, configuration) - return { url: toPathString(localVarUrlObj), options: localVarRequestOptions, @@ -7326,17 +6816,6 @@ export const WorkflowsApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.createWorkflows(createWorkflowsRequest, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, - /** - * アカウント内のワークフローを削除します - * @summary - * @param {number} workflowId ワークフローの内部ID - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async deleteWorkflow(workflowId: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.deleteWorkflow(workflowId, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, /** * アカウント内のワークフローの一覧を取得します * @summary @@ -7347,18 +6826,6 @@ export const WorkflowsApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getWorkflows(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, - /** - * アカウント内のワークフローを編集します - * @summary - * @param {number} workflowId ワークフローの内部ID - * @param {UpdateWorkflowRequest} updateWorkflowRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async updateWorkflow(workflowId: number, updateWorkflowRequest: UpdateWorkflowRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.updateWorkflow(workflowId, updateWorkflowRequest, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, } }; @@ -7379,16 +6846,6 @@ export const WorkflowsApiFactory = function (configuration?: Configuration, base createWorkflows(createWorkflowsRequest: CreateWorkflowsRequest, options?: any): AxiosPromise { return localVarFp.createWorkflows(createWorkflowsRequest, options).then((request) => request(axios, basePath)); }, - /** - * アカウント内のワークフローを削除します - * @summary - * @param {number} workflowId ワークフローの内部ID - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - deleteWorkflow(workflowId: number, options?: any): AxiosPromise { - return localVarFp.deleteWorkflow(workflowId, options).then((request) => request(axios, basePath)); - }, /** * アカウント内のワークフローの一覧を取得します * @summary @@ -7398,17 +6855,6 @@ export const WorkflowsApiFactory = function (configuration?: Configuration, base getWorkflows(options?: any): AxiosPromise { return localVarFp.getWorkflows(options).then((request) => request(axios, basePath)); }, - /** - * アカウント内のワークフローを編集します - * @summary - * @param {number} workflowId ワークフローの内部ID - * @param {UpdateWorkflowRequest} updateWorkflowRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - updateWorkflow(workflowId: number, updateWorkflowRequest: UpdateWorkflowRequest, options?: any): AxiosPromise { - return localVarFp.updateWorkflow(workflowId, updateWorkflowRequest, options).then((request) => request(axios, basePath)); - }, }; }; @@ -7431,18 +6877,6 @@ export class WorkflowsApi extends BaseAPI { return WorkflowsApiFp(this.configuration).createWorkflows(createWorkflowsRequest, options).then((request) => request(this.axios, this.basePath)); } - /** - * アカウント内のワークフローを削除します - * @summary - * @param {number} workflowId ワークフローの内部ID - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof WorkflowsApi - */ - public deleteWorkflow(workflowId: number, options?: AxiosRequestConfig) { - return WorkflowsApiFp(this.configuration).deleteWorkflow(workflowId, options).then((request) => request(this.axios, this.basePath)); - } - /** * アカウント内のワークフローの一覧を取得します * @summary @@ -7453,19 +6887,6 @@ export class WorkflowsApi extends BaseAPI { public getWorkflows(options?: AxiosRequestConfig) { return WorkflowsApiFp(this.configuration).getWorkflows(options).then((request) => request(this.axios, this.basePath)); } - - /** - * アカウント内のワークフローを編集します - * @summary - * @param {number} workflowId ワークフローの内部ID - * @param {UpdateWorkflowRequest} updateWorkflowRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof WorkflowsApi - */ - public updateWorkflow(workflowId: number, updateWorkflowRequest: UpdateWorkflowRequest, options?: AxiosRequestConfig) { - return WorkflowsApiFp(this.configuration).updateWorkflow(workflowId, updateWorkflowRequest, options).then((request) => request(this.axios, this.basePath)); - } } diff --git a/dictation_client/src/app/store.ts b/dictation_client/src/app/store.ts index fe711ab..e48a4ba 100644 --- a/dictation_client/src/app/store.ts +++ b/dictation_client/src/app/store.ts @@ -18,7 +18,6 @@ import worktype from "features/workflow/worktype/worktypeSlice"; import account from "features/account/accountSlice"; import template from "features/workflow/template/templateSlice"; import workflow from "features/workflow/workflowSlice"; -import terms from "features/terms/termsSlice"; export const store = configureStore({ reducer: { @@ -41,7 +40,6 @@ export const store = configureStore({ account, template, workflow, - terms, }, }); diff --git a/dictation_client/src/common/errors/code.ts b/dictation_client/src/common/errors/code.ts index 559a39e..41368a5 100644 --- a/dictation_client/src/common/errors/code.ts +++ b/dictation_client/src/common/errors/code.ts @@ -32,7 +32,6 @@ export const errorCodes = [ "E010206", // DBのTierが想定外の値エラー "E010207", // ユーザーのRole変更不可エラー "E010208", // ユーザーの暗号化パスワード不足エラー - "E010209", // ユーザーの同意済み利用規約バージョンが最新でないエラー "E010301", // メールアドレス登録済みエラー "E010302", // authorId重複エラー "E010401", // PONumber重複エラー @@ -56,7 +55,5 @@ export const errorCodes = [ "E011001", // ワークタイプ重複エラー "E011002", // ワークタイプ登録上限超過エラー "E011003", // ワークタイプ不在エラー - "E011004", // ワークタイプ使用中エラー "E013001", // ワークフローのAuthorIDとWorktypeIDのペア重複エラー - "E013002", // ワークフロー不在エラー ] as const; diff --git a/dictation_client/src/common/errors/utils.ts b/dictation_client/src/common/errors/utils.ts index 3dd2410..8f756ca 100644 --- a/dictation_client/src/common/errors/utils.ts +++ b/dictation_client/src/common/errors/utils.ts @@ -81,21 +81,3 @@ const isErrorResponse = (error: unknown): error is ErrorResponse => { const isErrorCode = (errorCode: string): errorCode is ErrorCodeType => errorCodes.includes(errorCode as ErrorCodeType); - -export const isErrorObject = ( - data: unknown -): data is { error: ErrorObject } => { - if ( - data && - typeof data === "object" && - "error" in data && - typeof (data as { error: ErrorObject }).error === "object" && - typeof (data as { error: ErrorObject }).error.message === "string" && - typeof (data as { error: ErrorObject }).error.code === "string" && - (typeof (data as { error: ErrorObject }).error.statusCode === "number" || - (data as { error: ErrorObject }).error.statusCode === undefined) - ) { - return true; - } - return false; -}; diff --git a/dictation_client/src/common/msalConfig.ts b/dictation_client/src/common/msalConfig.ts index 6a28910..e90e744 100644 --- a/dictation_client/src/common/msalConfig.ts +++ b/dictation_client/src/common/msalConfig.ts @@ -5,7 +5,7 @@ export const msalConfig: Configuration = { clientId: import.meta.env.VITE_B2C_CLIENTID, authority: import.meta.env.VITE_B2C_AUTHORITY, knownAuthorities: [import.meta.env.VITE_B2C_KNOWNAUTHORITIES], - redirectUri: `${globalThis.location.origin}/auth`, + redirectUri: `${globalThis.location.origin}/login`, navigateToLoginRequestUrl: false, }, cache: { diff --git a/dictation_client/src/common/token.ts b/dictation_client/src/common/token.ts index c41442f..587a0ea 100644 --- a/dictation_client/src/common/token.ts +++ b/dictation_client/src/common/token.ts @@ -62,16 +62,3 @@ export const isIdToken = (arg: any): arg is IdToken => { return true; }; - -export const getIdTokenFromLocalStorage = ( - localStorageKeyforIdToken: string -): string | null => { - const idTokenString = localStorage.getItem(localStorageKeyforIdToken); - if (idTokenString) { - const idTokenObject = JSON.parse(idTokenString); - if (isIdToken(idTokenObject)) { - return idTokenObject.secret; - } - } - return null; -}; diff --git a/dictation_client/src/components/auth/constants.ts b/dictation_client/src/components/auth/constants.ts index ee22851..d000d87 100644 --- a/dictation_client/src/components/auth/constants.ts +++ b/dictation_client/src/components/auth/constants.ts @@ -28,9 +28,3 @@ export const TIERS = { TIER4: "4", TIER5: "5", } as const; - -/** - * 401エラー時にログアウトさせずに処理を継続するエラーコード - * @const {string[]} - */ -export const UNAUTHORIZED_TO_CONTINUE_ERROR_CODES = ["E010209"]; diff --git a/dictation_client/src/features/account/operations.ts b/dictation_client/src/features/account/operations.ts index 292680b..03b242f 100644 --- a/dictation_client/src/features/account/operations.ts +++ b/dictation_client/src/features/account/operations.ts @@ -130,7 +130,7 @@ export const deleteAccountAsync = createAsyncThunk< const accountApi = new AccountsApi(config); try { - await accountApi.deleteAccountAndData(deleteAccounRequest, { + await accountApi.deleteAccount(deleteAccounRequest, { headers: { authorization: `Bearer ${accessToken}` }, }); diff --git a/dictation_client/src/features/login/loginSlice.ts b/dictation_client/src/features/login/loginSlice.ts index 312bae1..322a3ce 100644 --- a/dictation_client/src/features/login/loginSlice.ts +++ b/dictation_client/src/features/login/loginSlice.ts @@ -1,26 +1,17 @@ -import { PayloadAction, createSlice } from "@reduxjs/toolkit"; +import { createSlice } from "@reduxjs/toolkit"; import { LoginState } from "./state"; import { loginAsync } from "./operations"; const initialState: LoginState = { apps: { LoginApiCallStatus: "none", - localStorageKeyforIdToken: null, }, }; export const loginSlice = createSlice({ name: "login", initialState, - reducers: { - changeLocalStorageKeyforIdToken: ( - state, - action: PayloadAction<{ localStorageKeyforIdToken: string }> - ) => { - const { localStorageKeyforIdToken } = action.payload; - state.apps.localStorageKeyforIdToken = localStorageKeyforIdToken; - }, - }, + reducers: {}, extraReducers: (builder) => { builder.addCase(loginAsync.pending, (state) => { state.apps.LoginApiCallStatus = "pending"; @@ -34,5 +25,4 @@ export const loginSlice = createSlice({ }, }); -export const { changeLocalStorageKeyforIdToken } = loginSlice.actions; export default loginSlice.reducer; diff --git a/dictation_client/src/features/login/operations.ts b/dictation_client/src/features/login/operations.ts index 0ac9edd..61ddece 100644 --- a/dictation_client/src/features/login/operations.ts +++ b/dictation_client/src/features/login/operations.ts @@ -3,7 +3,6 @@ import type { RootState } from "app/store"; import { setToken } from "features/auth/authSlice"; import { AuthApi } from "../../api/api"; import { Configuration } from "../../api/configuration"; -import { ErrorObject, createErrorObject } from "../../common/errors"; export const loginAsync = createAsyncThunk< { @@ -15,7 +14,7 @@ export const loginAsync = createAsyncThunk< { // rejectした時の返却値の型 rejectValue: { - error: ErrorObject; + /* Empty Object */ }; } >("login/loginAsync", async (args, thunkApi) => { @@ -42,8 +41,6 @@ export const loginAsync = createAsyncThunk< return {}; } catch (e) { - // e ⇒ errorObjectに変換" - const error = createErrorObject(e); - return thunkApi.rejectWithValue({ error }); + return thunkApi.rejectWithValue({}); } }); diff --git a/dictation_client/src/features/login/selectors.ts b/dictation_client/src/features/login/selectors.ts index d0ded8e..9615b35 100644 --- a/dictation_client/src/features/login/selectors.ts +++ b/dictation_client/src/features/login/selectors.ts @@ -4,7 +4,3 @@ export const selectLoginApiCallStatus = ( state: RootState ): "fulfilled" | "rejected" | "none" | "pending" => state.login.apps.LoginApiCallStatus; - -export const selectLocalStorageKeyforIdToken = ( - state: RootState -): string | null => state.login.apps.localStorageKeyforIdToken; diff --git a/dictation_client/src/features/login/state.ts b/dictation_client/src/features/login/state.ts index 98fa599..98a61ad 100644 --- a/dictation_client/src/features/login/state.ts +++ b/dictation_client/src/features/login/state.ts @@ -4,5 +4,4 @@ export interface LoginState { export interface Apps { LoginApiCallStatus: "fulfilled" | "rejected" | "none" | "pending"; - localStorageKeyforIdToken: string | null; } diff --git a/dictation_client/src/features/signup/operations.ts b/dictation_client/src/features/signup/operations.ts index 2557b0d..67e505e 100644 --- a/dictation_client/src/features/signup/operations.ts +++ b/dictation_client/src/features/signup/operations.ts @@ -3,12 +3,10 @@ import type { RootState } from "app/store"; import { ErrorObject, createErrorObject } from "common/errors"; import { getTranslationID } from "translation"; import { closeSnackbar, openSnackbar } from "features/ui/uiSlice"; -import { TERMS_DOCUMENT_TYPE } from "features/terms/constants"; import { AccountsApi, CreateAccountRequest, GetDealersResponse, - TermsApi, } from "../../api/api"; import { Configuration } from "../../api/configuration"; @@ -95,42 +93,3 @@ export const getDealersAsync = createAsyncThunk< return thunkApi.rejectWithValue({ error }); } }); - -export const getLatestEulaVersionAsync = createAsyncThunk< - string, - void, - { - // rejectした時の返却値の型 - rejectValue: { - error: ErrorObject; - }; - } ->("login/getLatestEulaVersionAsync", async (args, thunkApi) => { - // apiのConfigurationを取得する - const { getState } = thunkApi; - const state = getState() as RootState; - const { configuration } = state.auth; - const config = new Configuration(configuration); - const termsApi = new TermsApi(config); - - try { - const termsInfo = await termsApi.getTermsInfo(); - const latestEulaVersion = termsInfo.data.termsInfo.find( - (val) => val.documentType === TERMS_DOCUMENT_TYPE.EULA - ); - if (!latestEulaVersion) { - throw new Error("EULA info is not found"); - } - return latestEulaVersion.version; - } catch (e) { - const error = createErrorObject(e); - thunkApi.dispatch( - openSnackbar({ - level: "error", - message: getTranslationID("common.message.internalServerError"), - }) - ); - - return thunkApi.rejectWithValue({ error }); - } -}); diff --git a/dictation_client/src/features/signup/selectors.ts b/dictation_client/src/features/signup/selectors.ts index 823347f..87c3118 100644 --- a/dictation_client/src/features/signup/selectors.ts +++ b/dictation_client/src/features/signup/selectors.ts @@ -72,6 +72,3 @@ export const selectSelectedDealer = (state: RootState) => { const { dealer } = state.signup.apps; return dealers.find((x: Dealer) => x.id === dealer); }; - -export const selectEulaVersion = (state: RootState) => - state.signup.domain.eulaVersion; diff --git a/dictation_client/src/features/signup/signupSlice.ts b/dictation_client/src/features/signup/signupSlice.ts index 0f6bd83..5b03e63 100644 --- a/dictation_client/src/features/signup/signupSlice.ts +++ b/dictation_client/src/features/signup/signupSlice.ts @@ -1,10 +1,6 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { SignupState } from "./state"; -import { - getDealersAsync, - getLatestEulaVersionAsync, - signupAsync, -} from "./operations"; +import { getDealersAsync, signupAsync } from "./operations"; const initialState: SignupState = { apps: { @@ -19,7 +15,6 @@ const initialState: SignupState = { }, domain: { dealers: [], - eulaVersion: "", }, }; @@ -79,15 +74,6 @@ export const signupSlice = createSlice({ builder.addCase(getDealersAsync.rejected, () => { // }); - builder.addCase(getLatestEulaVersionAsync.pending, () => { - // - }); - builder.addCase(getLatestEulaVersionAsync.fulfilled, (state, action) => { - state.domain.eulaVersion = action.payload; - }); - builder.addCase(getLatestEulaVersionAsync.rejected, () => { - // - }); }, }); export const { diff --git a/dictation_client/src/features/signup/state.ts b/dictation_client/src/features/signup/state.ts index 36850ad..164ab04 100644 --- a/dictation_client/src/features/signup/state.ts +++ b/dictation_client/src/features/signup/state.ts @@ -18,5 +18,4 @@ export interface Apps { export interface Domain { dealers: Dealer[]; - eulaVersion: string; } diff --git a/dictation_client/src/features/terms/constants.ts b/dictation_client/src/features/terms/constants.ts deleted file mode 100644 index dd78e43..0000000 --- a/dictation_client/src/features/terms/constants.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * 利用規約の種類 - * @const {string[]} - */ -export const TERMS_DOCUMENT_TYPE = { - DPA: "DPA", - EULA: "EULA", -} as const; diff --git a/dictation_client/src/features/terms/index.ts b/dictation_client/src/features/terms/index.ts deleted file mode 100644 index 8692ec6..0000000 --- a/dictation_client/src/features/terms/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./termsSlice"; -export * from "./state"; -export * from "./operations"; -export * from "./selectors"; diff --git a/dictation_client/src/features/terms/operations.ts b/dictation_client/src/features/terms/operations.ts deleted file mode 100644 index 511dfe9..0000000 --- a/dictation_client/src/features/terms/operations.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { createAsyncThunk } from "@reduxjs/toolkit"; -import type { RootState } from "app/store"; -import { ErrorObject, createErrorObject } from "common/errors"; -import { getTranslationID } from "translation"; -import { openSnackbar } from "features/ui/uiSlice"; -import { getIdTokenFromLocalStorage } from "common/token"; -import { TIERS } from "components/auth/constants"; -import { - UsersApi, - GetAccountInfoMinimalAccessResponse, - AccountsApi, - TermsApi, - GetTermsInfoResponse, -} from "../../api/api"; -import { Configuration } from "../../api/configuration"; - -export const getAccountInfoMinimalAccessAsync = createAsyncThunk< - GetAccountInfoMinimalAccessResponse, - { - localStorageKeyforIdToken: string; - }, - { - // rejectした時の返却値の型 - rejectValue: { - error: ErrorObject; - }; - } ->("accept/getAccountInfoMinimalAccessAsync", async (args, thunkApi) => { - const { localStorageKeyforIdToken } = args; - // apiのConfigurationを取得する - const { getState } = thunkApi; - const state = getState() as RootState; - const { configuration, accessToken } = state.auth; - const config = new Configuration(configuration); - const accountApi = new AccountsApi(config); - - try { - // IDトークンの取得 - const idToken = getIdTokenFromLocalStorage(localStorageKeyforIdToken); - - // IDトークンが取得できない場合エラーとする - if (!idToken) { - throw new Error("Unable to retrieve the ID token."); - } - const res = await accountApi.getAccountInfoMinimalAccess( - { idToken }, - { - headers: { authorization: `Bearer ${accessToken}` }, - } - ); - return res.data; - } catch (e) { - const error = createErrorObject(e); - thunkApi.dispatch( - openSnackbar({ - level: "error", - message: getTranslationID("common.message.internalServerError"), - }) - ); - return thunkApi.rejectWithValue({ error }); - } -}); - -export const getTermsInfoAsync = createAsyncThunk< - GetTermsInfoResponse, - void, - { - // rejectした時の返却値の型 - rejectValue: { - error: ErrorObject; - }; - } ->("accept/getTermsInfoAsync", async (_args, thunkApi) => { - // apiのConfigurationを取得する - const { getState } = thunkApi; - const state = getState() as RootState; - const { configuration, accessToken } = state.auth; - const config = new Configuration(configuration); - const termsApi = new TermsApi(config); - - try { - const termsInfo = await termsApi.getTermsInfo({ - headers: { authorization: `Bearer ${accessToken}` }, - }); - - return termsInfo.data; - } catch (e) { - // e ⇒ errorObjectに変換" - const error = createErrorObject(e); - thunkApi.dispatch( - openSnackbar({ - level: "error", - message: getTranslationID("common.message.internalServerError"), - }) - ); - return thunkApi.rejectWithValue({ error }); - } -}); - -export const updateAcceptedVersionAsync = createAsyncThunk< - { - /* Empty Object */ - }, - { - tier: number; - localStorageKeyforIdToken: string; - updateAccceptVersions: { - acceptedVerDPA: string; - acceptedVerEULA: string; - }; - }, - { - // rejectした時の返却値の型 - rejectValue: { - error: ErrorObject; - }; - } ->("accept/UpdateAcceptedVersionAsync", async (args, thunkApi) => { - const { tier, localStorageKeyforIdToken, updateAccceptVersions } = args; - // apiのConfigurationを取得する - const { getState } = thunkApi; - const state = getState() as RootState; - const { configuration, accessToken } = state.auth; - const config = new Configuration(configuration); - const userApi = new UsersApi(config); - - try { - // IDトークンの取得 - const idToken = getIdTokenFromLocalStorage(localStorageKeyforIdToken); - - // IDトークンが取得できない場合エラーとする - if (!idToken) { - throw new Error("Unable to retrieve the ID token."); - } - await userApi.updateAcceptedVersion( - { - idToken, - acceptedEULAVersion: updateAccceptVersions.acceptedVerEULA, - acceptedDPAVersion: !(TIERS.TIER5 === tier.toString()) - ? updateAccceptVersions.acceptedVerDPA - : undefined, - }, - { - headers: { authorization: `Bearer ${accessToken}` }, - } - ); - return {}; - } catch (e) { - const error = createErrorObject(e); - thunkApi.dispatch( - openSnackbar({ - level: "error", - message: getTranslationID("common.message.internalServerError"), - }) - ); - return thunkApi.rejectWithValue({ error }); - } -}); diff --git a/dictation_client/src/features/terms/selectors.ts b/dictation_client/src/features/terms/selectors.ts deleted file mode 100644 index 5cd00f7..0000000 --- a/dictation_client/src/features/terms/selectors.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { RootState } from "app/store"; -import { TERMS_DOCUMENT_TYPE } from "features/terms/constants"; - -export const selectTermVersions = (state: RootState) => { - const { termsInfo } = state.terms.domain; - - const acceptedVerDPA = - termsInfo.find( - (termInfo) => termInfo.documentType === TERMS_DOCUMENT_TYPE.DPA - )?.version || ""; - - const acceptedVerEULA = - termsInfo.find( - (termInfo) => termInfo.documentType === TERMS_DOCUMENT_TYPE.EULA - )?.version || ""; - - return { acceptedVerDPA, acceptedVerEULA }; -}; - -export const selectTier = (state: RootState) => state.terms.domain.tier; diff --git a/dictation_client/src/features/terms/state.ts b/dictation_client/src/features/terms/state.ts deleted file mode 100644 index 0c724dc..0000000 --- a/dictation_client/src/features/terms/state.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { TermInfo } from "../../api/api"; - -export interface AcceptState { - domain: Domain; - apps: Apps; -} - -export interface Domain { - tier: number; - termsInfo: TermInfo[]; -} - -export interface Apps { - isLoading: boolean; -} diff --git a/dictation_client/src/features/terms/termsSlice.ts b/dictation_client/src/features/terms/termsSlice.ts deleted file mode 100644 index 7f88271..0000000 --- a/dictation_client/src/features/terms/termsSlice.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { createSlice } from "@reduxjs/toolkit"; -import { AcceptState } from "./state"; -import { - getAccountInfoMinimalAccessAsync, - getTermsInfoAsync, - updateAcceptedVersionAsync, -} from "./operations"; - -const initialState: AcceptState = { - domain: { - tier: 0, - termsInfo: [ - { - documentType: "", - version: "", - }, - ], - }, - apps: { - isLoading: false, - }, -}; - -export const termsSlice = createSlice({ - name: "terms", - initialState, - reducers: {}, - extraReducers: (builder) => { - builder.addCase(getAccountInfoMinimalAccessAsync.pending, (state) => { - state.apps.isLoading = true; - }); - builder.addCase( - getAccountInfoMinimalAccessAsync.fulfilled, - (state, actions) => { - state.apps.isLoading = false; - state.domain.tier = actions.payload.tier; - } - ); - builder.addCase(getAccountInfoMinimalAccessAsync.rejected, (state) => { - state.apps.isLoading = false; - }); - builder.addCase(getTermsInfoAsync.pending, (state) => { - state.apps.isLoading = true; - }); - builder.addCase(getTermsInfoAsync.fulfilled, (state, actions) => { - state.apps.isLoading = false; - state.domain.termsInfo = actions.payload.termsInfo; - }); - builder.addCase(getTermsInfoAsync.rejected, (state) => { - state.apps.isLoading = false; - }); - builder.addCase(updateAcceptedVersionAsync.pending, (state) => { - state.apps.isLoading = true; - }); - builder.addCase(updateAcceptedVersionAsync.fulfilled, (state) => { - state.apps.isLoading = false; - }); - builder.addCase(updateAcceptedVersionAsync.rejected, (state) => { - state.apps.isLoading = false; - }); - }, -}); - -export default termsSlice.reducer; diff --git a/dictation_client/src/features/user/userSlice.ts b/dictation_client/src/features/user/userSlice.ts index 52ffbc4..6d64137 100644 --- a/dictation_client/src/features/user/userSlice.ts +++ b/dictation_client/src/features/user/userSlice.ts @@ -87,7 +87,7 @@ export const userSlice = createSlice({ action: PayloadAction<{ authorId: string | undefined }> ) => { const { authorId } = action.payload; - state.apps.addUser.authorId = authorId?.toUpperCase(); + state.apps.addUser.authorId = authorId; }, changeAutoRenew: (state, action: PayloadAction<{ autoRenew: boolean }>) => { const { autoRenew } = action.payload; @@ -144,7 +144,7 @@ export const userSlice = createSlice({ state.apps.updateUser.name = user.name; state.apps.updateUser.email = user.email; state.apps.updateUser.role = user.role as RoleType; - state.apps.updateUser.authorId = user.authorId?.toUpperCase(); + state.apps.updateUser.authorId = user.authorId; state.apps.updateUser.encryption = user.encryption; state.apps.updateUser.encryptionPassword = undefined; state.apps.updateUser.prompt = user.prompt; @@ -156,7 +156,7 @@ export const userSlice = createSlice({ state.apps.selectedUser.name = user.name; state.apps.selectedUser.email = user.email; state.apps.selectedUser.role = user.role as RoleType; - state.apps.selectedUser.authorId = user.authorId?.toUpperCase(); + state.apps.selectedUser.authorId = user.authorId; state.apps.selectedUser.encryption = user.encryption; state.apps.selectedUser.encryptionPassword = undefined; state.apps.selectedUser.prompt = user.prompt; @@ -175,7 +175,7 @@ export const userSlice = createSlice({ action: PayloadAction<{ authorId: string }> ) => { const { authorId } = action.payload; - state.apps.updateUser.authorId = authorId.toUpperCase(); + state.apps.updateUser.authorId = authorId; }, changeUpdateEncryption: ( state, @@ -243,8 +243,7 @@ export const userSlice = createSlice({ state.apps.licenseAllocateUser.id = selectedUser.id; state.apps.licenseAllocateUser.name = selectedUser.name; state.apps.licenseAllocateUser.email = selectedUser.email; - state.apps.licenseAllocateUser.authorId = - selectedUser.authorId.toUpperCase(); + state.apps.licenseAllocateUser.authorId = selectedUser.authorId; state.apps.licenseAllocateUser.licenseStatus = selectedUser.licenseStatus; state.apps.licenseAllocateUser.expiration = selectedUser.expiration; state.apps.licenseAllocateUser.remaining = selectedUser.remaining; diff --git a/dictation_client/src/features/workflow/operations.ts b/dictation_client/src/features/workflow/operations.ts index 01a9815..76e84a5 100644 --- a/dictation_client/src/features/workflow/operations.ts +++ b/dictation_client/src/features/workflow/operations.ts @@ -16,6 +16,7 @@ import type { RootState } from "app/store"; import { ErrorObject, createErrorObject } from "common/errors"; import { openSnackbar } from "features/ui/uiSlice"; import { getTranslationID } from "translation"; +import { WorkflowRelations } from "./state"; export const listWorkflowAsync = createAsyncThunk< GetWorkflowsResponse, @@ -140,105 +141,6 @@ export const createWorkflowAsync = createAsyncThunk< } }); -export const updateWorkflowAsync = createAsyncThunk< - { - /* Empty Object */ - }, - void, - { - // rejectした時の返却値の型 - rejectValue: { - error: ErrorObject; - }; - } ->("workflow/updateWorkflowAsync", async (args, thunkApi) => { - // apiのConfigurationを取得する - const { getState } = thunkApi; - const state = getState() as RootState; - const { configuration, accessToken } = state.auth; - const config = new Configuration(configuration); - const workflowsApi = new WorkflowsApi(config); - const { - selectedWorkflow, - selectedAssignees, - authorId, - templateId, - worktypeId, - } = state.workflow.apps; - - try { - if (selectedWorkflow === undefined) { - throw new Error("selectedWorkflow is not found"); - } - - if (authorId === undefined) { - throw new Error("authorId is not found"); - } - // 選択されたタイピストを取得し、リクエスト用の型に変換する - const typists = selectedAssignees.map( - (item): WorkflowTypist => ({ - typistId: item.typistUserId, - typistGroupId: item.typistGroupId, - }) - ); - await workflowsApi.updateWorkflow( - selectedWorkflow.id, - { - authorId, - typists, - templateId, - worktypeId, - }, - { - headers: { authorization: `Bearer ${accessToken}` }, - } - ); - thunkApi.dispatch( - openSnackbar({ - level: "info", - message: getTranslationID("common.message.success"), - }) - ); - return {}; - } catch (e) { - // e ⇒ errorObjectに変換" - const error = createErrorObject(e); - const { code, statusCode } = error; - - if (statusCode === 400) { - // AuthorIDとWorktypeIDが一致するものが既に存在する場合 - if (code === "E013001") { - thunkApi.dispatch( - openSnackbar({ - level: "error", - message: getTranslationID( - "workflowPage.message.workflowConflictError" - ), - }) - ); - return thunkApi.rejectWithValue({ error }); - } - - // パラメータが存在しない場合 - thunkApi.dispatch( - openSnackbar({ - level: "error", - message: getTranslationID("workflowPage.message.saveFailedError"), - }) - ); - return thunkApi.rejectWithValue({ error }); - } - // その他のエラー - thunkApi.dispatch( - openSnackbar({ - level: "error", - message: getTranslationID("common.message.internalServerError"), - }) - ); - return thunkApi.rejectWithValue({ error }); - } -}); - export const getworkflowRelationsAsync = createAsyncThunk< { authors: Author[]; @@ -309,60 +211,3 @@ export const getworkflowRelationsAsync = createAsyncThunk< return thunkApi.rejectWithValue({ error }); } }); - -export const deleteWorkflowAsync = createAsyncThunk< - { - /* Empty Object */ - }, - { workflowId: number }, - { - // rejectした時の返却値の型 - rejectValue: { - error: ErrorObject; - }; - } ->("workflow/deleteWorkflowAsync", async (args, thunkApi) => { - const { workflowId } = args; - // apiのConfigurationを取得する - const { getState } = thunkApi; - const state = getState() as RootState; - const { configuration, accessToken } = state.auth; - const config = new Configuration(configuration); - const workflowsApi = new WorkflowsApi(config); - - try { - await workflowsApi.deleteWorkflow(workflowId, { - headers: { authorization: `Bearer ${accessToken}` }, - }); - - thunkApi.dispatch( - openSnackbar({ - level: "info", - message: getTranslationID("common.message.success"), - }) - ); - return {}; - } catch (e) { - // e ⇒ errorObjectに変換" - const error = createErrorObject(e); - - // ワークフローが削除済みの場合は成功扱いとする - if (error.code === "E013002") { - thunkApi.dispatch( - openSnackbar({ - level: "info", - message: getTranslationID("common.message.success"), - }) - ); - return {}; - } - - thunkApi.dispatch( - openSnackbar({ - level: "error", - message: getTranslationID("common.message.internalServerError"), - }) - ); - return thunkApi.rejectWithValue({ error }); - } -}); diff --git a/dictation_client/src/features/workflow/selectors.ts b/dictation_client/src/features/workflow/selectors.ts index 685d81e..a71ba4a 100644 --- a/dictation_client/src/features/workflow/selectors.ts +++ b/dictation_client/src/features/workflow/selectors.ts @@ -36,15 +36,6 @@ export const selectWorkflowAssinee = (state: RootState) => { ), }; }; -export const selectSelectedWorkflow = (state: RootState) => - state.workflow.apps.selectedWorkflow; -export const selectAuthorId = (state: RootState) => - state.workflow.apps.authorId; -export const selectWorktypeId = (state: RootState) => - state.workflow.apps.worktypeId; -export const selectTemplateId = (state: RootState) => - state.workflow.apps.templateId; - export const selectIsAddLoading = (state: RootState) => state.workflow.apps.isAddLoading; diff --git a/dictation_client/src/features/workflow/state.ts b/dictation_client/src/features/workflow/state.ts index 6bb892a..e99a186 100644 --- a/dictation_client/src/features/workflow/state.ts +++ b/dictation_client/src/features/workflow/state.ts @@ -12,7 +12,6 @@ export interface Apps { authorId?: number; worktypeId?: number; templateId?: number; - selectedWorkflow?: Workflow; } export interface Domain { diff --git a/dictation_client/src/features/workflow/workflowSlice.ts b/dictation_client/src/features/workflow/workflowSlice.ts index 737f5cf..bb6aa9f 100644 --- a/dictation_client/src/features/workflow/workflowSlice.ts +++ b/dictation_client/src/features/workflow/workflowSlice.ts @@ -3,9 +3,7 @@ import { Assignee } from "api"; import { createWorkflowAsync, getworkflowRelationsAsync, - deleteWorkflowAsync, listWorkflowAsync, - updateWorkflowAsync, } from "./operations"; import { WorkflowState } from "./state"; @@ -23,17 +21,12 @@ export const workflowSlice = createSlice({ initialState, reducers: { clearWorkflow: (state) => { - state.apps.selectedWorkflow = undefined; state.apps.selectedAssignees = []; state.apps.authorId = undefined; state.apps.worktypeId = undefined; state.apps.templateId = undefined; state.domain.workflowRelations = undefined; }, - setAssignees: (state, action: PayloadAction<{ assignees: Assignee[] }>) => { - const { assignees } = action.payload; - state.apps.selectedAssignees = assignees; - }, addAssignee: (state, action: PayloadAction<{ assignee: Assignee }>) => { const { assignee } = action.payload; const { selectedAssignees } = state.apps; @@ -62,35 +55,18 @@ export const workflowSlice = createSlice({ x.typistGroupId !== assignee.typistGroupId ); }, - changeAuthor: ( - state, - action: PayloadAction<{ authorId?: number | undefined }> - ) => { + changeAuthor: (state, action: PayloadAction<{ authorId: number }>) => { const { authorId } = action.payload; state.apps.authorId = authorId; }, - changeWorktype: ( - state, - action: PayloadAction<{ worktypeId?: number | undefined }> - ) => { + changeWorktype: (state, action: PayloadAction<{ worktypeId?: number }>) => { const { worktypeId } = action.payload; state.apps.worktypeId = worktypeId; }, - changeTemplate: ( - state, - action: PayloadAction<{ templateId?: number | undefined }> - ) => { + changeTemplate: (state, action: PayloadAction<{ templateId?: number }>) => { const { templateId } = action.payload; state.apps.templateId = templateId; }, - changeSelectedWorkflow: ( - state, - action: PayloadAction<{ workflowId: number }> - ) => { - const { workflowId } = action.payload; - const workflow = state.domain.workflows?.find((x) => x.id === workflowId); - state.apps.selectedWorkflow = workflow; - }, }, extraReducers: (builder) => { builder.addCase(listWorkflowAsync.pending, (state) => { @@ -151,35 +127,15 @@ export const workflowSlice = createSlice({ builder.addCase(createWorkflowAsync.rejected, (state) => { state.apps.isAddLoading = false; }); - builder.addCase(updateWorkflowAsync.pending, (state) => { - state.apps.isAddLoading = true; - }); - builder.addCase(updateWorkflowAsync.fulfilled, (state) => { - state.apps.isAddLoading = false; - }); - builder.addCase(updateWorkflowAsync.rejected, (state) => { - state.apps.isAddLoading = false; - }); - builder.addCase(deleteWorkflowAsync.pending, (state) => { - state.apps.isLoading = true; - }); - builder.addCase(deleteWorkflowAsync.fulfilled, (state) => { - state.apps.isLoading = false; - }); - builder.addCase(deleteWorkflowAsync.rejected, (state) => { - state.apps.isLoading = false; - }); }, }); export const { - setAssignees, addAssignee, removeAssignee, changeAuthor, changeWorktype, changeTemplate, - changeSelectedWorkflow, clearWorkflow, } = workflowSlice.actions; diff --git a/dictation_client/src/features/workflow/worktype/operations.ts b/dictation_client/src/features/workflow/worktype/operations.ts index fa173c1..0e45d33 100644 --- a/dictation_client/src/features/workflow/worktype/operations.ts +++ b/dictation_client/src/features/workflow/worktype/operations.ts @@ -342,75 +342,3 @@ export const updateActiveWorktypeAsync = createAsyncThunk< return thunkApi.rejectWithValue({ error }); } }); - -export const deleteWorktypeAsync = createAsyncThunk< - { - /* Empty Object */ - }, - { worktypeId: number }, - { - // rejectした時の返却値の型 - rejectValue: { - error: ErrorObject; - }; - } ->("workflow/deleteWorktypeAsync", async (args, thunkApi) => { - const { worktypeId } = args; - // apiのConfigurationを取得する - const { getState } = thunkApi; - const state = getState() as RootState; - const { configuration, accessToken } = state.auth; - const config = new Configuration(configuration); - const accountsApi = new AccountsApi(config); - - try { - await accountsApi.deleteWorktype(worktypeId, { - headers: { authorization: `Bearer ${accessToken}` }, - }); - - thunkApi.dispatch( - openSnackbar({ - level: "info", - message: getTranslationID("common.message.success"), - }) - ); - return {}; - } catch (e) { - // e ⇒ errorObjectに変換" - const error = createErrorObject(e); - - if (error.statusCode === 400) { - if (error.code === "E011003") { - // ワークタイプが削除済みの場合は成功扱いとする - thunkApi.dispatch( - openSnackbar({ - level: "info", - message: getTranslationID("common.message.success"), - }) - ); - return {}; - } - - if (error.code === "E011004") { - // ワークタイプがワークフローで使用中の場合は削除できない - thunkApi.dispatch( - openSnackbar({ - level: "error", - message: getTranslationID( - "worktypeIdSetting.message.worktypeInUseError" - ), - }) - ); - return {}; - } - } - - thunkApi.dispatch( - openSnackbar({ - level: "error", - message: getTranslationID("common.message.internalServerError"), - }) - ); - return thunkApi.rejectWithValue({ error }); - } -}); diff --git a/dictation_client/src/pages/AccountPage/index.tsx b/dictation_client/src/pages/AccountPage/index.tsx index d136510..d40d452 100644 --- a/dictation_client/src/pages/AccountPage/index.tsx +++ b/dictation_client/src/pages/AccountPage/index.tsx @@ -149,8 +149,7 @@ const AccountPage: React.FC = (): JSX.Element => { {isTier5 && !viewInfo.account.parentAccountName && (
{ dispatch( changePrimaryAdministrator({ @@ -304,8 +303,8 @@ const AccountPage: React.FC = (): JSX.Element => {
{ - const [t] = useTranslation(); - const dispatch: AppDispatch = useDispatch(); - const navigate = useNavigate(); - const updateAccceptVersions = useSelector(selectTermVersions); - const localStorageKeyforIdToken = useSelector( - selectLocalStorageKeyforIdToken - ); - const tier = useSelector(selectTier); - - const [isCheckedEula, setIsCheckedEula] = useState(false); - const [isCheckedDpa, setIsCheckedDpa] = useState(false); - - const [isClickedEulaLink, setIsClickedEulaLink] = useState(false); - const [isClickedDpaLink, setIsClickedDpaLink] = useState(false); - - // 画面起動時 - useEffect(() => { - dispatch(getTermsInfoAsync()); - if (localStorageKeyforIdToken) { - dispatch(getAccountInfoMinimalAccessAsync({ localStorageKeyforIdToken })); - } else { - // ログイン画面を経由していないため、トップページに遷移する - navigate("/"); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - // ユーザーが第5階層であるかどうかを判定する(アクセストークンから自分の階層を取得できないので自前で作成) - const isTier5 = () => TIERS.TIER5.includes(tier.toString()); - - // ボタン押下可否判定ロジック - const canClickButton = () => { - if (isTier5()) { - return isCheckedEula; - } - return isCheckedEula && isCheckedDpa; - }; - - // ボタン押下時処理 - const onAcceptTermsOfUse = useCallback(async () => { - if ( - localStorageKeyforIdToken && - updateAccceptVersions.acceptedVerDPA !== "" && - updateAccceptVersions.acceptedVerEULA !== "" - ) { - const { meta } = await dispatch( - updateAcceptedVersionAsync({ - tier, - localStorageKeyforIdToken, - updateAccceptVersions, - }) - ); - - // 同意済バージョンが更新できたら、再度トークン生成を行う - if (meta.requestStatus === "fulfilled") { - navigate("/login"); - } - } - }, [ - navigate, - localStorageKeyforIdToken, - updateAccceptVersions, - tier, - dispatch, - ]); - - return ( -
-
- -
-
-
-

- {t(getTranslationID("termsPage.label.title"))} -

-
-
-
-
-
- -
-

- {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} - setIsClickedEulaLink(true)} - > - {t(getTranslationID("termsPage.label.linkOfEula"))} - - {` ${t(getTranslationID("termsPage.label.forOdds"))}`} -

-

- -

-
- {/* 第五階層以外の場合はEulaのリンクをあわせて表示する */} - {!isTier5() && ( -
-

- {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} - setIsClickedDpaLink(true)} - > - {t(getTranslationID("termsPage.label.linkOfDpa"))} - - {` ${t(getTranslationID("termsPage.label.forOdds"))}`} -

-

- -

-
- )} -
-

- -

-
-
-
-
-
-
-
-
- ); -}; - -export default TermsPage; diff --git a/dictation_client/src/pages/UserListPage/popup.tsx b/dictation_client/src/pages/UserListPage/popup.tsx index 50ed98f..35c8f0f 100644 --- a/dictation_client/src/pages/UserListPage/popup.tsx +++ b/dictation_client/src/pages/UserListPage/popup.tsx @@ -200,11 +200,7 @@ export const UserAddPopup: React.FC = (props) => { className={styles.formInput} value={addUser.authorId ?? undefined} onChange={(e) => { - dispatch( - changeAuthorId({ - authorId: e.target.value.toUpperCase(), - }) - ); + dispatch(changeAuthorId({ authorId: e.target.value })); }} /> {isPushCreateButton && hasErrorEmptyAuthorId && ( diff --git a/dictation_client/src/pages/UserListPage/updatePopup.tsx b/dictation_client/src/pages/UserListPage/updatePopup.tsx index fa5944e..00ce988 100644 --- a/dictation_client/src/pages/UserListPage/updatePopup.tsx +++ b/dictation_client/src/pages/UserListPage/updatePopup.tsx @@ -184,9 +184,7 @@ export const UserUpdatePopup: React.FC = (props) => { className={styles.formInput} onChange={(e) => { dispatch( - changeUpdateAuthorId({ - authorId: e.target.value.toUpperCase(), - }) + changeUpdateAuthorId({ authorId: e.target.value }) ); }} /> diff --git a/dictation_client/src/pages/WorkTypeIdSettingPage/index.tsx b/dictation_client/src/pages/WorkTypeIdSettingPage/index.tsx index 0f4c03f..2532f5a 100644 --- a/dictation_client/src/pages/WorkTypeIdSettingPage/index.tsx +++ b/dictation_client/src/pages/WorkTypeIdSettingPage/index.tsx @@ -1,7 +1,7 @@ import { UpdateTokenTimer } from "components/auth/updateTokenTimer"; import Footer from "components/footer"; import Header from "components/header"; -import React, { useCallback, useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { getTranslationID } from "translation"; import styles from "styles/app.module.scss"; import undo from "assets/images/undo.svg"; @@ -18,7 +18,6 @@ import { selectIsLoading, selectWorktypes, selectActiveWorktypeId, - deleteWorktypeAsync, } from "features/workflow/worktype"; import { AppDispatch } from "app/store"; import { AddWorktypeIdPopup } from "./addWorktypeIdPopup"; @@ -87,23 +86,6 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedActiveWorktypeId]); - // 削除ボタン押下時の処理 - const onDeleteWoktype = useCallback( - async (worktypeId: number) => { - if ( - /* eslint-disable-next-line no-alert */ - !window.confirm(t(getTranslationID("common.message.dialogConfirm"))) - ) { - return; - } - const { meta } = await dispatch(deleteWorktypeAsync({ worktypeId })); - if (meta.requestStatus === "fulfilled") { - dispatch(listWorktypesAsync()); - } - }, - [dispatch, t] - ); - return ( <> {
  • - {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} onDeleteWoktype(worktype.id)} + // onClick={} > {t(getTranslationID("common.label.delete"))} diff --git a/dictation_client/src/pages/WorkflowPage/editworkflowPopup.tsx b/dictation_client/src/pages/WorkflowPage/editworkflowPopup.tsx deleted file mode 100644 index 47f7054..0000000 --- a/dictation_client/src/pages/WorkflowPage/editworkflowPopup.tsx +++ /dev/null @@ -1,313 +0,0 @@ -import React, { useCallback, useEffect, useState } from "react"; -import { AppDispatch } from "app/store"; -import progress_activit from "assets/images/progress_activit.svg"; -import { - addAssignee, - removeAssignee, - changeAuthor, - changeTemplate, - changeWorktype, - clearWorkflow, - selectIsAddLoading, - selectWorkflowAssinee, - selectWorkflowError, - selectWorkflowRelations, - selectSelectedWorkflow, - selectAuthorId, - selectWorktypeId, - setAssignees, - selectTemplateId, -} from "features/workflow"; -import { - getworkflowRelationsAsync, - listWorkflowAsync, - updateWorkflowAsync, -} from "features/workflow/operations"; -import { useTranslation } from "react-i18next"; -import { useDispatch, useSelector } from "react-redux"; -import styles from "styles/app.module.scss"; -import { getTranslationID } from "translation"; -import close from "../../assets/images/close.svg"; - -interface EditWorkflowPopupProps { - onClose: () => void; -} - -export const EditWorkflowPopup: React.FC = ( - props -): JSX.Element => { - const { onClose } = props; - const dispatch: AppDispatch = useDispatch(); - const [t] = useTranslation(); - // 保存ボタンを押したかどうか - const [isPushEditButton, setIsPushEditButton] = useState(false); - - const workflow = useSelector(selectSelectedWorkflow); - const authorId = useSelector(selectAuthorId); - const worktypeId = useSelector(selectWorktypeId); - const templateId = useSelector(selectTemplateId); - - const workflowRelations = useSelector(selectWorkflowRelations); - const { poolAssignees, selectedAssignees } = useSelector( - selectWorkflowAssinee - ); - const isLoading = useSelector(selectIsAddLoading); - const { hasAuthorIdEmptyError, hasSelectedWorkflowAssineeEmptyError } = - useSelector(selectWorkflowError); - useEffect(() => { - dispatch(getworkflowRelationsAsync()); - // ポップアップのアンマウント時に初期化を行う - return () => { - dispatch(clearWorkflow()); - setIsPushEditButton(false); - }; - }, [dispatch]); - - useEffect(() => { - dispatch(changeAuthor({ authorId: workflow?.author?.id })); - dispatch(changeWorktype({ worktypeId: workflow?.worktype?.id })); - dispatch(changeTemplate({ templateId: workflow?.template?.id })); - dispatch(setAssignees({ assignees: workflow?.typists ?? [] })); - }, [dispatch, workflow]); - - const changeWorktypeId = useCallback( - (target: string) => { - // 空文字の場合はundefinedをdispatchする - if (target === "") { - dispatch(changeWorktype({ worktypeId: undefined })); - } else if (!Number.isNaN(Number(target))) { - dispatch(changeWorktype({ worktypeId: Number(target) })); - } - }, - [dispatch] - ); - - const changeTemplateId = useCallback( - (target: string) => { - // 空文字の場合はundefinedをdispatchする - if (target === "") { - dispatch(changeTemplate({ templateId: undefined })); - } else if (!Number.isNaN(Number(target))) { - dispatch(changeTemplate({ templateId: Number(target) })); - } - }, - [dispatch] - ); - - const changeAuthorId = useCallback( - (target: string) => { - if (!Number.isNaN(target)) { - dispatch(changeAuthor({ authorId: Number(target) })); - } - }, - [dispatch] - ); - - // 保存ボタン押下時の処理 - const handleSave = useCallback(async () => { - setIsPushEditButton(true); - // エラーチェック - if (hasAuthorIdEmptyError || hasSelectedWorkflowAssineeEmptyError) { - return; - } - const { meta } = await dispatch(updateWorkflowAsync()); - if (meta.requestStatus === "fulfilled") { - onClose(); - dispatch(listWorkflowAsync()); - } - }, [ - dispatch, - hasAuthorIdEmptyError, - hasSelectedWorkflowAssineeEmptyError, - onClose, - ]); - - return ( -
    -
    -

    - {t(getTranslationID("workflowPage.label.editRoutingRule"))} - {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions */} - close -

    -
    -
    -
    -
    {t(getTranslationID("workflowPage.label.authorID"))}
    -
    - - {isPushEditButton && hasAuthorIdEmptyError && ( - - {t(getTranslationID("workflowPage.message.inputEmptyError"))} - - )} -
    -
    - {t(getTranslationID("workflowPage.label.worktypeOptional"))} -
    -
    - -
    -
    - {t(getTranslationID("typistGroupSetting.label.transcriptionist"))} -
    -
    -
      -
    • - {t(getTranslationID("workflowPage.label.selected"))} -
    • - {selectedAssignees?.map((x) => { - const key = `${x.typistName}_${ - x.typistUserId ?? x.typistGroupId - }`; - return ( -
    • - { - dispatch(removeAssignee({ assignee: x })); - }} - /> - -
    • - ); - })} -
    -

    -

      -
    • - {t(getTranslationID("workflowPage.label.pool"))} -
    • - {poolAssignees?.map((x) => { - const key = `${x.typistName}_${ - x.typistUserId ?? x.typistGroupId - }`; - return ( -
    • - dispatch(addAssignee({ assignee: x }))} - /> - -
    • - ); - })} -
    - {isPushEditButton && hasSelectedWorkflowAssineeEmptyError && ( - - {t( - getTranslationID( - "workflowPage.message.selectedTypistEmptyError" - ) - )} - - )} -
    -
    - {t(getTranslationID("workflowPage.label.templateOptional"))} -
    -
    - -
    -
    - - {isLoading && ( - Loading - )} -
    -
    -
    -
    -
    - ); -}; diff --git a/dictation_client/src/pages/WorkflowPage/index.tsx b/dictation_client/src/pages/WorkflowPage/index.tsx index 16f5c49..0ea3e51 100644 --- a/dictation_client/src/pages/WorkflowPage/index.tsx +++ b/dictation_client/src/pages/WorkflowPage/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import Header from "components/header"; import Footer from "components/footer"; import styles from "styles/app.module.scss"; @@ -10,50 +10,23 @@ import groupSettingImg from "assets/images/group_setting.svg"; import { AppDispatch } from "app/store"; import { useTranslation } from "react-i18next"; import { useDispatch, useSelector } from "react-redux"; -import { - deleteWorkflowAsync, - listWorkflowAsync, - changeSelectedWorkflow, - selectIsLoading, - selectWorkflows, -} from "features/workflow"; +import { listWorkflowAsync } from "features/workflow/operations"; +import { selectIsLoading, selectWorkflows } from "features/workflow"; import progress_activit from "assets/images/progress_activit.svg"; import { getTranslationID } from "translation"; import { AddWorkflowPopup } from "./addworkflowPopup"; -import { EditWorkflowPopup } from "./editworkflowPopup"; const WorkflowPage: React.FC = (): JSX.Element => { const dispatch: AppDispatch = useDispatch(); const [t] = useTranslation(); // 追加Popupの表示制御 const [isShowAddPopup, setIsShowAddPopup] = useState(false); - // 編集Popupの表示制御 - const [isShowEditPopup, setIsShowEditPopup] = useState(false); - const workflows = useSelector(selectWorkflows); const isLoading = useSelector(selectIsLoading); useEffect(() => { dispatch(listWorkflowAsync()); }, [dispatch]); - - // ワークフロー削除 - const onDeleteWorkflow = useCallback( - async (workflowId: number) => { - if ( - /* eslint-disable-next-line no-alert */ - !window.confirm(t(getTranslationID("common.message.dialogConfirm"))) - ) { - return; - } - const { meta } = await dispatch(deleteWorkflowAsync({ workflowId })); - if (meta.requestStatus === "fulfilled") { - dispatch(listWorkflowAsync()); - } - }, - [dispatch, t] - ); - return ( <> {isShowAddPopup && ( @@ -63,13 +36,6 @@ const WorkflowPage: React.FC = (): JSX.Element => { }} /> )} - {isShowEditPopup && ( - { - setIsShowEditPopup(false); - }} - /> - )}
    @@ -170,29 +136,14 @@ const WorkflowPage: React.FC = (): JSX.Element => {
    • - {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} - { - dispatch( - changeSelectedWorkflow({ - workflowId: workflow.id, - }) - ); - setIsShowEditPopup(true); - }} - > + {t( getTranslationID("workflowPage.label.editRule") )}
    • - {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} - { - onDeleteWorkflow(workflow.id); - }} - > + {t(getTranslationID("common.label.delete"))}
    • diff --git a/dictation_client/src/styles/app.module.scss b/dictation_client/src/styles/app.module.scss index b6728b0..89d6d34 100644 --- a/dictation_client/src/styles/app.module.scss +++ b/dictation_client/src/styles/app.module.scss @@ -293,31 +293,6 @@ h5 { width: 1.4rem; vertical-align: top; } -.accountSignout { - display: inline-block; - margin-left: 1rem; - color: #999999; - font-size: 0.8rem; - cursor: pointer; - -moz-transition: all 0.3s ease-out; - -ms-transition: all 0.3s ease-out; - -webkit-transition: all 0.3s ease-out; - transition: all 0.3s ease-out; -} -.accountSignout .accountIcon { - opacity: 0.5; - margin-right: 0.2rem; - -moz-transition: all 0.3s ease-out; - -ms-transition: all 0.3s ease-out; - -webkit-transition: all 0.3s ease-out; - transition: all 0.3s ease-out; -} -.accountSignout:hover { - color: #333333; -} -.accountSignout:hover .accountIcon { - opacity: 1; -} _:-ms-lang(x)::-ms-backdrop, .header { @@ -672,7 +647,6 @@ h3 + .brCrumb .tlIcon { padding: 0.6rem 0; margin-right: 1rem; cursor: pointer; - white-space: pre-line; } .form label:has(input:disabled) { cursor: default; @@ -896,24 +870,6 @@ h3 + .brCrumb .tlIcon { text-align: right; word-break: break-all; } -.listDocument { - margin-bottom: 3rem; -} -.listDocument li { - padding: 0 0 0 1.5rem; - margin-bottom: 1rem; - position: relative; -} -.listDocument li::before { - content: ""; - width: 0.5rem; - height: 0.5rem; - background: #333333; - border-radius: 50%; - position: absolute; - top: 0.2rem; - left: 0; -} .boxFlex { display: flex; @@ -1472,8 +1428,7 @@ _:-ms-lang(x)::-ms-backdrop, .license > div, .dictation > div, .partners > div, -.workflow > div, -.support > div { +.workflow > div { padding: 0 2rem; position: relative; } @@ -1482,8 +1437,7 @@ _:-ms-lang(x)::-ms-backdrop, .license > div .icLoading, .dictation > div .icLoading, .partners > div .icLoading, -.workflow > div .icLoading, -.support > div .icLoading { +.workflow > div .icLoading { top: 5.5rem; left: calc(50% - 25px); } @@ -1492,8 +1446,7 @@ _:-ms-lang(x)::-ms-backdrop, .license .table tr.tableHeader th.clm0, .dictation .table tr.tableHeader th.clm0, .partners .table tr.tableHeader th.clm0, -.workflow .table tr.tableHeader th.clm0, -.support .table tr.tableHeader th.clm0 { +.workflow .table tr.tableHeader th.clm0 { width: 0px; padding: 0 0; } @@ -1502,8 +1455,7 @@ _:-ms-lang(x)::-ms-backdrop, .license .table tr:not(.tableHeader), .dictation .table tr:not(.tableHeader), .partners .table tr:not(.tableHeader), -.workflow .table tr:not(.tableHeader), -.support .table tr:not(.tableHeader) { +.workflow .table tr:not(.tableHeader) { position: relative; } .account .table tr:not(.tableHeader):hover .menuInTable, @@ -1511,8 +1463,7 @@ _:-ms-lang(x)::-ms-backdrop, .license .table tr:not(.tableHeader):hover .menuInTable, .dictation .table tr:not(.tableHeader):hover .menuInTable, .partners .table tr:not(.tableHeader):hover .menuInTable, -.workflow .table tr:not(.tableHeader):hover .menuInTable, -.support .table tr:not(.tableHeader):hover .menuInTable { +.workflow .table tr:not(.tableHeader):hover .menuInTable { opacity: 1; } .account .table tr:not(.tableHeader).isSelected, @@ -1520,8 +1471,7 @@ _:-ms-lang(x)::-ms-backdrop, .license .table tr:not(.tableHeader).isSelected, .dictation .table tr:not(.tableHeader).isSelected, .partners .table tr:not(.tableHeader).isSelected, -.workflow .table tr:not(.tableHeader).isSelected, -.support .table tr:not(.tableHeader).isSelected { +.workflow .table tr:not(.tableHeader).isSelected { background: #0084b2; color: #ffffff; } @@ -1530,8 +1480,7 @@ _:-ms-lang(x)::-ms-backdrop, .license .table tr:not(.tableHeader).isSelected:hover, .dictation .table tr:not(.tableHeader).isSelected:hover, .partners .table tr:not(.tableHeader).isSelected:hover, -.workflow .table tr:not(.tableHeader).isSelected:hover, -.support .table tr:not(.tableHeader).isSelected:hover { +.workflow .table tr:not(.tableHeader).isSelected:hover { color: #ffffff; } .account .table tr:not(.tableHeader).isSelected .menuInTable, @@ -1539,8 +1488,7 @@ _:-ms-lang(x)::-ms-backdrop, .license .table tr:not(.tableHeader).isSelected .menuInTable, .dictation .table tr:not(.tableHeader).isSelected .menuInTable, .partners .table tr:not(.tableHeader).isSelected .menuInTable, -.workflow .table tr:not(.tableHeader).isSelected .menuInTable, -.support .table tr:not(.tableHeader).isSelected .menuInTable { +.workflow .table tr:not(.tableHeader).isSelected .menuInTable { display: block; } .account .table td, @@ -1548,8 +1496,7 @@ _:-ms-lang(x)::-ms-backdrop, .license .table td, .dictation .table td, .partners .table td, -.workflow .table td, -.support .table td { +.workflow .table td { max-width: 300px; overflow: hidden; text-overflow: ellipsis; @@ -1561,8 +1508,7 @@ _:-ms-lang(x)::-ms-backdrop, .license .table td.clm0, .dictation .table td.clm0, .partners .table td.clm0, -.workflow .table td.clm0, -.support .table td.clm0 { +.workflow .table td.clm0 { width: 0px; padding: 0 0; overflow: visible; @@ -1575,8 +1521,7 @@ _:-ms-lang(x)::-ms-backdrop, .license .table.user, .dictation .table.user, .partners .table.user, -.workflow .table.user, -.support .table.user { +.workflow .table.user { margin-bottom: 5rem; } .account .table.user th::after, @@ -1584,8 +1529,7 @@ _:-ms-lang(x)::-ms-backdrop, .license .table.user th::after, .dictation .table.user th::after, .partners .table.user th::after, -.workflow .table.user th::after, -.support .table.user th::after { +.workflow .table.user th::after { display: none; } .account .table.user tr:not(.tableHeader) td, @@ -1593,8 +1537,7 @@ _:-ms-lang(x)::-ms-backdrop, .license .table.user tr:not(.tableHeader) td, .dictation .table.user tr:not(.tableHeader) td, .partners .table.user tr:not(.tableHeader) td, -.workflow .table.user tr:not(.tableHeader) td, -.support .table.user tr:not(.tableHeader) td { +.workflow .table.user tr:not(.tableHeader) td { padding-bottom: 2rem; vertical-align: top; } @@ -2557,9 +2500,6 @@ tr.isSelected .menuInTable li a.isDisable { padding: 0 3rem; } -.txContents { - padding: 3rem; -} .txNormal { font-size: 16px; line-height: 1.7; diff --git a/dictation_client/src/styles/app.module.scss.d.ts b/dictation_client/src/styles/app.module.scss.d.ts index 6c4bd76..7bb6792 100644 --- a/dictation_client/src/styles/app.module.scss.d.ts +++ b/dictation_client/src/styles/app.module.scss.d.ts @@ -7,7 +7,6 @@ declare const classNames: { readonly isActive: "isActive"; readonly accountInfo: "accountInfo"; readonly accountIcon: "accountIcon"; - readonly accountSignout: "accountSignout"; readonly main: "main"; readonly mainSmall: "mainSmall"; readonly mainLogin: "mainLogin"; @@ -46,10 +45,10 @@ declare const classNames: { readonly formBack: "formBack"; readonly formButtonTx: "formButtonTx"; readonly formDone: "formDone"; + readonly formDelete: "formDelete"; readonly formTrash: "formTrash"; readonly listVertical: "listVertical"; readonly listHeader: "listHeader"; - readonly listDocument: "listDocument"; readonly boxFlex: "boxFlex"; readonly aru: "aru"; readonly btw: "btw"; @@ -102,11 +101,11 @@ declare const classNames: { readonly dictation: "dictation"; readonly partners: "partners"; readonly workflow: "workflow"; - readonly support: "support"; readonly clm0: "clm0"; readonly menuInTable: "menuInTable"; readonly isSelected: "isSelected"; - readonly alignRight: "alignRight"; + readonly formCheckToggle: "formCheckToggle"; + readonly toggleBase: "toggleBase"; readonly menuAction: "menuAction"; readonly inTable: "inTable"; readonly menuLink: "menuLink"; @@ -198,6 +197,7 @@ declare const classNames: { readonly worktype: "worktype"; readonly selectMenu: "selectMenu"; readonly alignLeft: "alignLeft"; + readonly alignRight: "alignRight"; readonly floatNone: "floatNone"; readonly floatLeft: "floatLeft"; readonly floatRight: "floatRight"; @@ -217,8 +217,8 @@ declare const classNames: { readonly paddSide1: "paddSide1"; readonly paddSide2: "paddSide2"; readonly paddSide3: "paddSide3"; - readonly txContents: "txContents"; readonly txIcon: "txIcon"; readonly txWswrap: "txWswrap"; + readonly required: "required"; }; export = classNames; diff --git a/dictation_client/src/translation/de.json b/dictation_client/src/translation/de.json index b757f77..35d433e 100644 --- a/dictation_client/src/translation/de.json +++ b/dictation_client/src/translation/de.json @@ -358,7 +358,6 @@ "label": { "title": "Arbeitsablauf", "addRoutingRule": "(de)Add Routing Rule", - "editRoutingRule": "(de)Edit Routing Rule", "templateSetting": "(de)Template Setting", "worktypeIdSetting": "(de)WorktypeID Setting", "typistGroupSetting": "(de)Transcriptionist Group Setting", @@ -429,8 +428,7 @@ "optionItemInvalidError": "(de)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。", "optionItemSaveFailedError": "(de)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください", "optionItemIncorrectError": "(de)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください", - "updateActiveWorktypeFailedError": "(de)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください", - "worktypeInUseError": "(de)このWorktype IDはルーティングルールで使用されているため削除できません。" + "updateActiveWorktypeFailedError": "(de)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください" } }, "templateFilePage": { @@ -499,15 +497,5 @@ "message": "(de)Your account has been deleted. Thank you for using our services.", "backToTopPageLink": "(de)Back to TOP Page" } - }, - "termsPage": { - "label": { - "title": "(de)Terms of Use has updated. Please confirm again.", - "linkOfEula": "(de)Click here to read the terms of use.", - "linkOfDpa": "(de)Click here to read the terms of use.", - "checkBoxForConsent": "(de)Yes, I agree to the terms of use.", - "forOdds": "(de)for ODDS.", - "button": "(de)Continue" - } } } diff --git a/dictation_client/src/translation/en.json b/dictation_client/src/translation/en.json index a58f19d..6e08976 100644 --- a/dictation_client/src/translation/en.json +++ b/dictation_client/src/translation/en.json @@ -358,7 +358,6 @@ "label": { "title": "Workflow", "addRoutingRule": "Add Routing Rule", - "editRoutingRule": "Edit Routing Rule", "templateSetting": "Template Setting", "worktypeIdSetting": "WorktypeID Setting", "typistGroupSetting": "Transcriptionist Group Setting", @@ -429,8 +428,7 @@ "optionItemInvalidError": "Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。", "optionItemSaveFailedError": "オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください", "optionItemIncorrectError": "入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください", - "updateActiveWorktypeFailedError": "Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください", - "worktypeInUseError": "このWorktype IDはルーティングルールで使用されているため削除できません。" + "updateActiveWorktypeFailedError": "Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください" } }, "templateFilePage": { @@ -499,15 +497,5 @@ "message": "Your account has been deleted. Thank you for using our services.", "backToTopPageLink": "Back to TOP Page" } - }, - "termsPage": { - "label": { - "title": "Terms of Use has updated. Please confirm again.", - "linkOfEula": "Click here to read the terms of use.", - "linkOfDpa": "Click here to read the terms of use.", - "checkBoxForConsent": "Yes, I agree to the terms of use.", - "forOdds": "for ODDS.", - "button": "Continue" - } } } diff --git a/dictation_client/src/translation/es.json b/dictation_client/src/translation/es.json index 9ba29ad..0bb32b5 100644 --- a/dictation_client/src/translation/es.json +++ b/dictation_client/src/translation/es.json @@ -358,7 +358,6 @@ "label": { "title": "flujo de trabajo", "addRoutingRule": "(es)Add Routing Rule", - "editRoutingRule": "(es)Edit Routing Rule", "templateSetting": "(es)Template Setting", "worktypeIdSetting": "(es)WorktypeID Setting", "typistGroupSetting": "(es)Transcriptionist Group Setting", @@ -429,8 +428,7 @@ "optionItemInvalidError": "(es)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。", "optionItemSaveFailedError": "(es)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください", "optionItemIncorrectError": "(es)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください", - "updateActiveWorktypeFailedError": "(es)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください", - "worktypeInUseError": "(es)このWorktype IDはルーティングルールで使用されているため削除できません。" + "updateActiveWorktypeFailedError": "(es)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください" } }, "templateFilePage": { @@ -499,15 +497,5 @@ "message": "(es)Your account has been deleted. Thank you for using our services.", "backToTopPageLink": "(es)Back to TOP Page" } - }, - "termsPage": { - "label": { - "title": "(es)Terms of Use has updated. Please confirm again.", - "linkOfEula": "(es)Click here to read the terms of use.", - "linkOfDpa": "(es)Click here to read the terms of use.", - "checkBoxForConsent": "(es)Yes, I agree to the terms of use.", - "forOdds": "(es)for ODDS.", - "button": "(es)Continue" - } } } diff --git a/dictation_client/src/translation/fr.json b/dictation_client/src/translation/fr.json index 5a7731e..f5bf23d 100644 --- a/dictation_client/src/translation/fr.json +++ b/dictation_client/src/translation/fr.json @@ -358,7 +358,6 @@ "label": { "title": "Flux de travail", "addRoutingRule": "(fr)Add Routing Rule", - "editRoutingRule": "(fr)Edit Routing Rule", "templateSetting": "(fr)Template Setting", "worktypeIdSetting": "(fr)WorktypeID Setting", "typistGroupSetting": "(fr)Transcriptionist Group Setting", @@ -429,8 +428,7 @@ "optionItemInvalidError": "(fr)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。", "optionItemSaveFailedError": "(fr)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください", "optionItemIncorrectError": "(fr)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください", - "updateActiveWorktypeFailedError": "(fr)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください", - "worktypeInUseError": "(fr)このWorktype IDはルーティングルールで使用されているため削除できません。" + "updateActiveWorktypeFailedError": "(fr)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください" } }, "templateFilePage": { @@ -499,15 +497,5 @@ "message": "(fr)Your account has been deleted. Thank you for using our services.", "backToTopPageLink": "(fr)Back to TOP Page" } - }, - "termsPage": { - "label": { - "title": "(fr)Terms of Use has updated. Please confirm again.", - "linkOfEula": "(fr)Click here to read the terms of use.", - "linkOfDpa": "(fr)Click here to read the terms of use.", - "checkBoxForConsent": "(fr)Yes, I agree to the terms of use.", - "forOdds": "(fr)for ODDS.", - "button": "(fr)Continue" - } } } diff --git a/dictation_server/db/migrations/045-delete-foreign-key-for-account-delete.sql b/dictation_server/db/migrations/045-delete-foreign-key-for-account-delete.sql deleted file mode 100644 index ccb3995..0000000 --- a/dictation_server/db/migrations/045-delete-foreign-key-for-account-delete.sql +++ /dev/null @@ -1,33 +0,0 @@ --- +migrate Up -ALTER TABLE `checkout_permission` DROP FOREIGN KEY `checkout_permission_fk_task_id`; -ALTER TABLE `tasks` DROP FOREIGN KEY `tasks_fk_account_id`; -ALTER TABLE `template_files` DROP FOREIGN KEY `template_files_fk_account_id`; -ALTER TABLE `option_items` DROP FOREIGN KEY `option_items_fk_worktype_id`; -ALTER TABLE `worktypes` DROP FOREIGN KEY `worktypes_fk_account_id`; -ALTER TABLE `audio_option_items` DROP FOREIGN KEY `audio_option_items_fk_audio_file_id`; -ALTER TABLE `audio_files` DROP FOREIGN KEY `audio_files_fk_account_id`; -ALTER TABLE `user_group_member` DROP FOREIGN KEY `user_group_member_fk_user_group_id`; -ALTER TABLE `user_group` DROP FOREIGN KEY `user_group_fk_account_id`; -ALTER TABLE `license_allocation_history` DROP FOREIGN KEY `license_allocation_history_fk_account_id`; -ALTER TABLE `card_licenses` DROP FOREIGN KEY `card_licenses_fk_license_id`; -ALTER TABLE `licenses` DROP FOREIGN KEY `licenses_fk_account_id`; -ALTER TABLE `license_orders` DROP FOREIGN KEY `license_orders_fk_from_account_id`; -ALTER TABLE `sort_criteria` DROP FOREIGN KEY `sort_criteria_fk_user_id`; -ALTER TABLE `users` DROP FOREIGN KEY `users_fk_account_id`; - --- +migrate Down -ALTER TABLE `checkout_permission` ADD CONSTRAINT `checkout_permission_fk_task_id` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `tasks` ADD CONSTRAINT `tasks_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `template_files` ADD CONSTRAINT `template_files_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `option_items` ADD CONSTRAINT `option_items_fk_worktype_id` FOREIGN KEY (`worktype_id`) REFERENCES `worktypes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `worktypes` ADD CONSTRAINT `worktypes_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `audio_option_items` ADD CONSTRAINT `audio_option_items_fk_audio_file_id` FOREIGN KEY (`audio_file_id`) REFERENCES `audio_files` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `audio_files` ADD CONSTRAINT `audio_files_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `user_group_member` ADD CONSTRAINT `user_group_member_fk_user_group_id` FOREIGN KEY (`user_group_id`) REFERENCES `user_group` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `user_group` ADD CONSTRAINT `user_group_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `license_allocation_history` ADD CONSTRAINT `license_allocation_history_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `card_licenses` ADD CONSTRAINT `card_licenses_fk_license_id` FOREIGN KEY (`license_id`) REFERENCES `licenses` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `licenses` ADD CONSTRAINT `licenses_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `license_orders` ADD CONSTRAINT `license_orders_fk_from_account_id` FOREIGN KEY (`from_account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `sort_criteria` ADD CONSTRAINT `sort_criteria_fk_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `users` ADD CONSTRAINT `users_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; \ No newline at end of file diff --git a/dictation_server/db/migrations/046-insert_initial_data_terms.sql b/dictation_server/db/migrations/046-insert_initial_data_terms.sql deleted file mode 100644 index faa2e3d..0000000 --- a/dictation_server/db/migrations/046-insert_initial_data_terms.sql +++ /dev/null @@ -1,9 +0,0 @@ --- +migrate Up -insert into terms(terms.document_type, terms.version) values('EURA', 'V0.1'); -insert into terms(terms.document_type, terms.version) values('DPA', 'V0.1'); -commit; - --- +migrate Down -delete from terms where terms.document_type = 'EURA' and terms.version = 'V0.1'; -delete from terms where terms.document_type = 'DPA' and terms.version = 'V0.1'; -commit; diff --git a/dictation_server/src/api/odms/openapi.json b/dictation_server/src/api/odms/openapi.json index 993efb4..7a56d16 100644 --- a/dictation_server/src/api/odms/openapi.json +++ b/dictation_server/src/api/odms/openapi.json @@ -990,59 +990,6 @@ "security": [{ "bearer": [] }] } }, - "/accounts/worktypes/{id}/delete": { - "post": { - "operationId": "deleteWorktype", - "summary": "", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "description": "Worktypeの内部ID", - "schema": { "type": "number" } - } - ], - "responses": { - "200": { - "description": "成功時のレスポンス", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DeleteWorktypeResponse" - } - } - } - }, - "400": { - "description": "指定WorktypeIDが削除済み / 指定WorktypeIDがWorkflowで使用中", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ErrorResponse" } - } - } - }, - "401": { - "description": "認証エラー", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ErrorResponse" } - } - } - }, - "500": { - "description": "想定外のサーバーエラー", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ErrorResponse" } - } - } - } - }, - "tags": ["accounts"], - "security": [{ "bearer": [] }] - } - }, "/accounts/worktypes/{id}/option-items": { "get": { "operationId": "getOptionItems", @@ -3209,14 +3156,6 @@ } } }, - "400": { - "description": "パラメータ不正エラー", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ErrorResponse" } - } - } - }, "401": { "description": "認証エラー", "content": { @@ -3290,7 +3229,7 @@ } }, "/terms": { - "get": { + "post": { "operationId": "getTermsInfo", "summary": "", "parameters": [], @@ -3759,7 +3698,6 @@ "required": ["worktypeId"] }, "UpdateWorktypeResponse": { "type": "object", "properties": {} }, - "DeleteWorktypeResponse": { "type": "object", "properties": {} }, "GetWorktypeOptionItem": { "type": "object", "properties": { @@ -4465,7 +4403,7 @@ "licenseId": { "type": "number" }, "expiryDate": { "format": "date-time", "type": "string" } }, - "required": ["licenseId"] + "required": ["licenseId", "expiryDate"] }, "GetAllocatableLicensesResponse": { "type": "object", @@ -4623,24 +4561,7 @@ "required": ["pns", "handler"] }, "RegisterResponse": { "type": "object", "properties": {} }, - "TermInfo": { - "type": "object", - "properties": { - "documentType": { "type": "string", "description": "利用規約種別" }, - "version": { "type": "string", "description": "バージョン" } - }, - "required": ["documentType", "version"] - }, - "GetTermsInfoResponse": { - "type": "object", - "properties": { - "termsInfo": { - "type": "array", - "items": { "$ref": "#/components/schemas/TermInfo" } - } - }, - "required": ["termsInfo"] - } + "GetTermsInfoResponse": { "type": "object", "properties": {} } } } } diff --git a/dictation_server/src/common/error/code.ts b/dictation_server/src/common/error/code.ts index 9b271e8..448aa82 100644 --- a/dictation_server/src/common/error/code.ts +++ b/dictation_server/src/common/error/code.ts @@ -32,7 +32,6 @@ export const ErrorCodes = [ 'E010206', // DBのTierが想定外の値エラー 'E010207', // ユーザーのRole変更不可エラー 'E010208', // ユーザーの暗号化パスワード不足エラー - 'E010209', // ユーザーの同意済み利用規約バージョンが最新でないエラー 'E010301', // メールアドレス登録済みエラー 'E010302', // authorId重複エラー 'E010401', // PONumber重複エラー @@ -57,7 +56,6 @@ export const ErrorCodes = [ 'E011001', // ワークタイプ重複エラー 'E011002', // ワークタイプ登録上限超過エラー 'E011003', // ワークタイプ不在エラー - 'E011004', // ワークタイプ使用中エラー 'E012001', // テンプレートファイル不在エラー 'E013001', // ワークフローのAuthorIDとWorktypeIDのペア重複エラー 'E013002', // ワークフロー不在エラー diff --git a/dictation_server/src/common/error/message.ts b/dictation_server/src/common/error/message.ts index 11b0c1d..eeee5b3 100644 --- a/dictation_server/src/common/error/message.ts +++ b/dictation_server/src/common/error/message.ts @@ -21,7 +21,6 @@ export const errors: Errors = { E010206: 'Tier from DB is unexpected value Error.', E010207: 'User role change not allowed Error.', E010208: 'User encryption password not found Error.', - E010209: 'Accepted term not latest Error.', E010301: 'This email user already created Error', E010302: 'This AuthorId already used Error', E010401: 'This PoNumber already used Error', @@ -46,7 +45,6 @@ export const errors: Errors = { E011001: 'This WorkTypeID already used Error', E011002: 'WorkTypeID create limit exceeded Error', E011003: 'WorkTypeID not found Error', - E011004: 'WorkTypeID is in use Error', E012001: 'Template file not found Error', E013001: 'AuthorId and WorktypeId pair already exists Error', E013002: 'Workflow not found Error', diff --git a/dictation_server/src/common/guards/role/roleguards.ts b/dictation_server/src/common/guards/role/roleguards.ts index 6670bed..c3ba25d 100644 --- a/dictation_server/src/common/guards/role/roleguards.ts +++ b/dictation_server/src/common/guards/role/roleguards.ts @@ -92,15 +92,13 @@ export class RoleGuard implements CanActivate { * @returns true/false */ checkRole(role: string): boolean { - const settings = this.settings; - if (!settings || !settings.roles) { - return true; - } + const { roles } = this.settings; + const userRoles = role.split(' '); // Role毎にAccessTokenの権限チェックを行う - for (let i = 0; i < settings.roles.length; i++) { - const role = settings.roles[i]; + for (let i = 0; i < roles.length; i++) { + const role = roles[i]; let isValid = false; if (Array.isArray(role)) { isValid = role.every((x) => userRoles.includes(x)); @@ -174,12 +172,9 @@ export class RoleGuard implements CanActivate { * @returns true/false */ checkTier(tier: number): boolean { - const settings = this.settings; - if (!settings || !settings.tiers) { - return true; - } + const { tiers } = this.settings; // 宣言された階層中にパラメータの内容が含まれていればtrue - return settings.tiers.includes(tier as (typeof TIERS)[keyof typeof TIERS]); + return tiers.includes(tier as (typeof TIERS)[keyof typeof TIERS]); } } diff --git a/dictation_server/src/common/jwt/jwt.ts b/dictation_server/src/common/jwt/jwt.ts index 2e97368..f50f01d 100644 --- a/dictation_server/src/common/jwt/jwt.ts +++ b/dictation_server/src/common/jwt/jwt.ts @@ -132,7 +132,7 @@ export const getPrivateKey = (configService: ConfigService): string => { return ( // 開発環境用に改行コードを置換する // 本番環境では\\nが含まれないため、置換が行われない想定 - configService.getOrThrow('JWT_PRIVATE_KEY').replace(/\\n/g, '\n') + configService.get('JWT_PRIVATE_KEY')?.replace(/\\n/g, '\n') ?? '' ); }; @@ -140,6 +140,6 @@ export const getPublicKey = (configService: ConfigService): string => { return ( // 開発環境用に改行コードを置換する // 本番環境では\\nが含まれないため、置換が行われない想定 - configService.getOrThrow('JWT_PUBLIC_KEY').replace(/\\n/g, '\n') + configService.get('JWT_PUBLIC_KEY')?.replace(/\\n/g, '\n') ?? '' ); }; diff --git a/dictation_server/src/common/password/password.ts b/dictation_server/src/common/password/password.ts index f68bb3c..76265e2 100644 --- a/dictation_server/src/common/password/password.ts +++ b/dictation_server/src/common/password/password.ts @@ -15,9 +15,10 @@ export const makePassword = (): string => { // autoGeneratedPasswordが以上の条件を満たせばvalidがtrueになる let valid = false; - let autoGeneratedPassword: string = ''; + let autoGeneratedPassword: string; while (!valid) { + autoGeneratedPassword = ''; // パスワードをランダムに決定 while (autoGeneratedPassword.length < passLength) { // 上で決定したcharsの中からランダムに1文字ずつ追加 diff --git a/dictation_server/src/common/test/modules.ts b/dictation_server/src/common/test/modules.ts index aec0d61..05f8e77 100644 --- a/dictation_server/src/common/test/modules.ts +++ b/dictation_server/src/common/test/modules.ts @@ -34,13 +34,10 @@ import { TemplatesService } from '../../features/templates/templates.service'; import { TemplatesModule } from '../../features/templates/templates.module'; import { WorkflowsService } from '../../features/workflows/workflows.service'; import { WorkflowsModule } from '../../features/workflows/workflows.module'; -import { TermsService } from '../../features/terms/terms.service'; -import { TermsRepositoryModule } from '../../repositories/terms/terms.repository.module'; -import { TermsModule } from '../../features/terms/terms.module'; export const makeTestingModule = async ( datasource: DataSource, -): Promise => { +): Promise => { try { const module: TestingModule = await Test.createTestingModule({ imports: [ @@ -59,7 +56,6 @@ export const makeTestingModule = async ( LicensesModule, TemplatesModule, WorkflowsModule, - TermsModule, AccountsRepositoryModule, UsersRepositoryModule, LicensesRepositoryModule, @@ -75,7 +71,6 @@ export const makeTestingModule = async ( AuthGuardsModule, SortCriteriaRepositoryModule, WorktypesRepositoryModule, - TermsRepositoryModule, ], providers: [ AuthService, @@ -87,7 +82,6 @@ export const makeTestingModule = async ( LicensesService, TemplatesService, WorkflowsService, - TermsService, ], }) .useMocker(async (token) => { diff --git a/dictation_server/src/common/test/utility.ts b/dictation_server/src/common/test/utility.ts index 00b19d7..4722e9f 100644 --- a/dictation_server/src/common/test/utility.ts +++ b/dictation_server/src/common/test/utility.ts @@ -3,7 +3,6 @@ import { DataSource } from 'typeorm'; import { User, UserArchive } from '../../repositories/users/entity/user.entity'; import { Account } from '../../repositories/accounts/entity/account.entity'; import { ADMIN_ROLES, USER_ROLES } from '../../constants'; -import { License } from '../../repositories/licenses/entity/license.entity'; type InitialTestDBState = { tier1Accounts: { account: Account; users: User[] }[]; @@ -58,11 +57,11 @@ export const makeHierarchicalAccounts = async ( } // 第2階層を作成 { - const tier1 = state.tier1Accounts.slice().shift(); + const { account: tier1 } = state.tier1Accounts.slice().shift(); { const { account, admin } = await makeTestAccount(datasource, { tier: 2, - parent_account_id: tier1?.account.id, + parent_account_id: tier1.id, company_name: 'OMDS_US', }); state.tier2Accounts.push({ @@ -73,7 +72,7 @@ export const makeHierarchicalAccounts = async ( { const { account, admin } = await makeTestAccount(datasource, { tier: 2, - parent_account_id: tier1?.account.id, + parent_account_id: tier1.id, company_name: 'OMDS_EU', }); state.tier2Accounts.push({ @@ -202,7 +201,7 @@ export const makeTestAccount = async ( } // Accountの管理者を設定する - let secondaryAdminUserId: number | null = null; + let secondaryAdminUserId = null; if (isPrimaryAdminNotExist && !isSecondaryAdminNotExist) { secondaryAdminUserId = userId; } @@ -225,9 +224,6 @@ export const makeTestAccount = async ( id: userId, }, }); - if (!account || !admin) { - throw new Error('Unexpected null'); - } return { account: account, @@ -267,9 +263,7 @@ export const makeTestSimpleAccount = async ( id: result.id, }, }); - if (!account) { - throw new Error('Unexpected null'); - } + return account; }; @@ -305,15 +299,11 @@ export const makeTestUser = async ( }); const result = identifiers.pop() as User; - const user = await datasource.getRepository(User).findOne({ + return await datasource.getRepository(User).findOne({ where: { id: result.id, }, }); - if (!user) { - throw new Error('Unexpected null'); - } - return user; }; /** @@ -322,10 +312,7 @@ export const makeTestUser = async ( * @param id アカウントID * @returns 該当アカウント */ -export const getAccount = async ( - dataSource: DataSource, - id: number, -): Promise => { +export const getAccount = async (dataSource: DataSource, id: number) => { return await dataSource.getRepository(Account).findOne({ where: { id: id }, }); @@ -366,7 +353,7 @@ export const getUserFromExternalId = async ( export const getUser = async ( datasource: DataSource, id: number, -): Promise => { +): Promise => { const user = await datasource.getRepository(User).findOne({ where: { id: id, @@ -394,14 +381,3 @@ export const getUserArchive = async ( ): Promise => { return await dataSource.getRepository(UserArchive).find(); }; -export const getLicenses = async ( - datasource: DataSource, - account_id: number, -): Promise => { - const licenses = await datasource.getRepository(License).find({ - where: { - account_id: account_id, - }, - }); - return licenses; -}; diff --git a/dictation_server/src/constants/index.ts b/dictation_server/src/constants/index.ts index 1f5ffed..8e527f4 100644 --- a/dictation_server/src/constants/index.ts +++ b/dictation_server/src/constants/index.ts @@ -244,7 +244,7 @@ export const OPTION_ITEM_VALUE_TYPE = { * @const {string[]} */ export const ADB2C_SIGN_IN_TYPE = { - EMAILADDRESS: 'emailAddress', + EAMILADDRESS: 'emailAddress', } as const; /** @@ -252,12 +252,3 @@ export const ADB2C_SIGN_IN_TYPE = { * @const {string} */ export const MANUAL_RECOVERY_REQUIRED = '[MANUAL_RECOVERY_REQUIRED]'; - -/** - * 利用規約種別 - * @const {string[]} - */ -export const TERM_TYPE = { - EULA: 'EULA', - DPA: 'DPA', -} as const; diff --git a/dictation_server/src/features/accounts/accounts.controller.spec.ts b/dictation_server/src/features/accounts/accounts.controller.spec.ts index 2761b0e..b2bbf73 100644 --- a/dictation_server/src/features/accounts/accounts.controller.spec.ts +++ b/dictation_server/src/features/accounts/accounts.controller.spec.ts @@ -2,12 +2,10 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AccountsController } from './accounts.controller'; import { AccountsService } from './accounts.service'; import { ConfigModule } from '@nestjs/config'; -import { AuthService } from '../auth/auth.service'; describe('AccountsController', () => { let controller: AccountsController; const mockAccountService = {}; - const mockAuthService = {}; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -18,12 +16,10 @@ describe('AccountsController', () => { }), ], controllers: [AccountsController], - providers: [AccountsService, AuthService], + providers: [AccountsService], }) .overrideProvider(AccountsService) .useValue(mockAccountService) - .overrideProvider(AuthService) - .useValue(mockAuthService) .compile(); controller = module.get(AccountsController); diff --git a/dictation_server/src/features/accounts/accounts.controller.ts b/dictation_server/src/features/accounts/accounts.controller.ts index cba3bb2..92206af 100644 --- a/dictation_server/src/features/accounts/accounts.controller.ts +++ b/dictation_server/src/features/accounts/accounts.controller.ts @@ -8,7 +8,6 @@ import { UseGuards, Param, Query, - HttpException, } from '@nestjs/common'; import { ApiOperation, @@ -66,8 +65,6 @@ import { GetAuthorsResponse, GetAccountInfoMinimalAccessRequest, GetAccountInfoMinimalAccessResponse, - DeleteWorktypeRequestParam, - DeleteWorktypeResponse, } from './types/types'; import { USER_ROLES, ADMIN_ROLES, TIERS } from '../../constants'; import { AuthGuard } from '../../common/guards/auth/authguards'; @@ -77,15 +74,12 @@ import { AccessToken } from '../../common/token'; import jwt from 'jsonwebtoken'; import { makeContext } from '../../common/log'; import { v4 as uuidv4 } from 'uuid'; -import { AuthService } from '../auth/auth.service'; -import { makeErrorResponse } from '../../common/error/makeErrorResponse'; @ApiTags('accounts') @Controller('accounts') export class AccountsController { constructor( private readonly accountService: AccountsService, //private readonly cryptoService: CryptoService, - private readonly authService: AuthService, ) {} @Post() @@ -200,26 +194,14 @@ export class AccountsController { @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) @Get('me') async getMyAccount(@Req() req: Request): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; - const context = makeContext(userId); + // アクセストークン取得 + const accessToken = retrieveAuthorizationToken(req); + const payload = jwt.decode(accessToken, { json: true }) as AccessToken; + const context = makeContext(payload.userId); //アカウントID取得処理 const accountInfo = await this.accountService.getAccountInfo( context, - userId, + payload.userId, ); return accountInfo; } @@ -249,21 +231,8 @@ export class AccountsController { @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) @Get('authors') async getAuthors(@Req() req: Request): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken; const context = makeContext(userId); const authors = await this.accountService.getAuthors(context, userId); @@ -295,23 +264,10 @@ export class AccountsController { @UseGuards(AuthGuard) @Get('typists') async getTypists(@Req() req: Request): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const payload = jwt.decode(accessToken, { json: true }) as AccessToken; - const typists = await this.accountService.getTypists(userId); + const typists = await this.accountService.getTypists(payload.userId); return { typists }; } @@ -340,23 +296,12 @@ export class AccountsController { @UseGuards(AuthGuard) @Get('typist-groups') async getTypistGroups(@Req() req: Request): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const payload = jwt.decode(accessToken, { json: true }) as AccessToken; - const typistGroups = await this.accountService.getTypistGroups(userId); + const typistGroups = await this.accountService.getTypistGroups( + payload.userId, + ); return { typistGroups }; } @@ -397,22 +342,8 @@ export class AccountsController { const { typistGroupId } = param; // アクセストークン取得 - - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken; const context = makeContext(userId); @@ -460,22 +391,8 @@ export class AccountsController { ): Promise { const { typistGroupName, typistIds } = body; // アクセストークン取得 - - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken; const context = makeContext(userId); await this.accountService.createTypistGroup( context, @@ -524,22 +441,8 @@ export class AccountsController { const { typistGroupId } = param; // アクセストークン取得 - - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken; const context = makeContext(userId); @@ -589,24 +492,10 @@ export class AccountsController { @Body() body: CreatePartnerAccountRequest, ): Promise { const { companyName, country, email, adminName } = body; + const accessToken = retrieveAuthorizationToken(req); + const payload = jwt.decode(accessToken, { json: true }) as AccessToken; - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId, tier } = decodedAccessToken as AccessToken; - - const context = makeContext(userId); + const context = makeContext(payload.userId); await this.accountService.createPartnerAccount( context, @@ -614,8 +503,8 @@ export class AccountsController { country, email, adminName, - userId, - tier, + payload.userId, + payload.tier, ); return {}; @@ -731,28 +620,15 @@ export class AccountsController { ): Promise { const { orderedAccountId, poNumber } = body; - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId, tier } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const accessToken = jwt.decode(token, { json: true }) as AccessToken; - const context = makeContext(userId); + const context = makeContext(accessToken.userId); await this.accountService.issueLicense( context, orderedAccountId, - userId, - tier, + accessToken.userId, + accessToken.tier, poNumber, ); return {}; @@ -812,27 +688,14 @@ export class AccountsController { @Req() req: Request, @Body() body: CancelIssueRequest, ): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const payload = jwt.decode(token, { json: true }) as AccessToken; - const context = makeContext(userId); + const context = makeContext(payload.userId); await this.accountService.cancelIssue( context, - userId, + payload.userId, body.poNumber, body.orderedAccountId, ); @@ -860,21 +723,8 @@ export class AccountsController { @UseGuards(AuthGuard) @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) async getWorktypes(@Req() req: Request): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(userId); const worktypes = await this.accountService.getWorktypes(context, userId); @@ -912,22 +762,8 @@ export class AccountsController { @Body() body: CreateWorktypesRequest, ): Promise { const { worktypeId, description } = body; - - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(userId); await this.accountService.createWorktype( @@ -972,22 +808,8 @@ export class AccountsController { ): Promise { const { worktypeId, description } = body; const { id } = param; - - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(userId); @@ -1002,59 +824,6 @@ export class AccountsController { return {}; } - @Post('/worktypes/:id/delete') - @ApiResponse({ - status: HttpStatus.OK, - type: DeleteWorktypeResponse, - description: '成功時のレスポンス', - }) - @ApiResponse({ - status: HttpStatus.BAD_REQUEST, - description: '指定WorktypeIDが削除済み / 指定WorktypeIDがWorkflowで使用中', - type: ErrorResponse, - }) - @ApiResponse({ - status: HttpStatus.UNAUTHORIZED, - description: '認証エラー', - type: ErrorResponse, - }) - @ApiResponse({ - status: HttpStatus.INTERNAL_SERVER_ERROR, - description: '想定外のサーバーエラー', - type: ErrorResponse, - }) - @ApiOperation({ operationId: 'deleteWorktype' }) - @ApiBearerAuth() - @UseGuards(AuthGuard) - @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) - async deleteWorktype( - @Req() req: Request, - @Param() param: DeleteWorktypeRequestParam, - ): Promise { - const { id } = param; - - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; - - const context = makeContext(userId); - - await this.accountService.deleteWorktype(context, userId, id); - return {}; - } - @Get('/worktypes/:id/option-items') @ApiResponse({ status: HttpStatus.OK, @@ -1085,22 +854,8 @@ export class AccountsController { @Param() param: GetOptionItemsRequestParam, ): Promise { const { id } = param; - - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(userId); @@ -1145,22 +900,8 @@ export class AccountsController { ): Promise { const { optionItems } = body; const { id } = param; - - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(userId); @@ -1204,22 +945,8 @@ export class AccountsController { @Body() body: PostActiveWorktypeRequest, ): Promise { const { id } = body; - - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(userId); @@ -1262,22 +989,8 @@ export class AccountsController { @Query() query: GetPartnersRequest, ): Promise { const { limit, offset } = query; - - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(userId); const response = await this.accountService.getPartners( @@ -1329,22 +1042,8 @@ export class AccountsController { primaryAdminUserId, secondryAdminUserId, } = body; - - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId, tier } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const { userId, tier } = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(userId); await this.accountService.updateAccountInfo( @@ -1357,7 +1056,7 @@ export class AccountsController { secondryAdminUserId, ); - return {}; + return; } @Post('/delete') @@ -1389,26 +1088,12 @@ export class AccountsController { @Body() body: DeleteAccountRequest, ): Promise { const { accountId } = body; - - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(userId); await this.accountService.deleteAccountAndData(context, userId, accountId); - return {}; + return; } @Post('/minimal-access') @@ -1431,22 +1116,11 @@ export class AccountsController { async getAccountInfoMinimalAccess( @Body() body: GetAccountInfoMinimalAccessRequest, ): Promise { - // IDトークンの検証 - const idToken = await this.authService.getVerifiedIdToken(body.idToken); - const isVerified = await this.authService.isVerifiedUser(idToken); - if (!isVerified) { - throw new HttpException( - makeErrorResponse('E010201'), - HttpStatus.BAD_REQUEST, - ); - } + const context = makeContext(uuidv4()); - const context = makeContext(idToken.sub); - - const tier = await this.accountService.getAccountInfoMinimalAccess( - context, - idToken.sub, - ); - return { tier }; + // TODO 仮実装。API実装タスクで本実装する。 + // const idToken = await this.authService.getVerifiedIdToken(body.idToken); + // await this.accountService.getAccountInfoMinimalAccess(context, idToken); + return; } } diff --git a/dictation_server/src/features/accounts/accounts.module.ts b/dictation_server/src/features/accounts/accounts.module.ts index 23cf65e..4b43415 100644 --- a/dictation_server/src/features/accounts/accounts.module.ts +++ b/dictation_server/src/features/accounts/accounts.module.ts @@ -9,7 +9,6 @@ import { AdB2cModule } from '../../gateways/adb2c/adb2c.module'; import { UserGroupsRepositoryModule } from '../../repositories/user_groups/user_groups.repository.module'; import { BlobstorageModule } from '../../gateways/blobstorage/blobstorage.module'; import { WorktypesRepositoryModule } from '../../repositories/worktypes/worktypes.repository.module'; -import { AuthService } from '../auth/auth.service'; @Module({ imports: [ @@ -23,6 +22,6 @@ import { AuthService } from '../auth/auth.service'; BlobstorageModule, ], controllers: [AccountsController], - providers: [AccountsService, AuthService], + providers: [AccountsService], }) export class AccountsModule {} diff --git a/dictation_server/src/features/accounts/accounts.service.spec.ts b/dictation_server/src/features/accounts/accounts.service.spec.ts index 3742cad..c7786d5 100644 --- a/dictation_server/src/features/accounts/accounts.service.spec.ts +++ b/dictation_server/src/features/accounts/accounts.service.spec.ts @@ -35,7 +35,6 @@ import { makeTestUser, makeHierarchicalAccounts, getUser, - getLicenses, getUserArchive, } from '../../common/test/utility'; import { AccountsService } from './accounts.service'; @@ -50,11 +49,7 @@ import { USER_ROLES, WORKTYPE_MAX_COUNT, } from '../../constants'; -import { - License, - LicenseAllocationHistory, - LicenseOrder, -} from '../../repositories/licenses/entity/license.entity'; +import { License } from '../../repositories/licenses/entity/license.entity'; import { overrideAccountsRepositoryService, overrideAdB2cService, @@ -65,6 +60,7 @@ import { AdB2cService } from '../../gateways/adb2c/adb2c.service'; import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service'; import { UserGroupsRepositoryService } from '../../repositories/user_groups/user_groups.repository.service'; import { + createLicenseAllocationHistory, createOrder, getLicenseArchive, getLicenseAllocationHistoryArchive, @@ -76,11 +72,9 @@ import { AdB2cUser } from '../../gateways/adb2c/types/types'; import { Worktype } from '../../repositories/worktypes/entity/worktype.entity'; import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service'; import { UsersRepositoryService } from '../../repositories/users/users.repository.service'; -import { createWorkflow, getWorkflows } from '../workflows/test/utility'; -import { UsersService } from '../users/users.service'; describe('createAccount', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -93,14 +87,12 @@ describe('createAccount', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); + it('アカウントを作成できる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const externalId = 'test_external_id'; @@ -154,22 +146,20 @@ describe('createAccount', () => { // DB内が想定通りになっているか確認 const account = await getAccount(source, accountId); const user = await getUserFromExternalId(source, externalUserId); - expect(account?.company_name).toBe(companyName); - expect(account?.country).toBe(country); - expect(account?.parent_account_id).toBe(dealerAccountId); - expect(account?.tier).toBe(TIERS.TIER5); - expect(account?.primary_admin_user_id).toBe(user?.id); - expect(account?.secondary_admin_user_id).toBe(null); - expect(user?.accepted_eula_version).toBe(acceptedEulaVersion); - expect(user?.accepted_dpa_version).toBe(acceptedDpaVersion); - expect(user?.account_id).toBe(accountId); - expect(user?.role).toBe(role); + expect(account.company_name).toBe(companyName); + expect(account.country).toBe(country); + expect(account.parent_account_id).toBe(dealerAccountId); + expect(account.tier).toBe(TIERS.TIER5); + expect(account.primary_admin_user_id).toBe(user.id); + expect(account.secondary_admin_user_id).toBe(null); + expect(user.accepted_eula_version).toBe(acceptedEulaVersion); + expect(user.accepted_dpa_version).toBe(acceptedDpaVersion); + expect(user.account_id).toBe(accountId); + expect(user.role).toBe(role); }); it('アカウントを作成がAzure AD B2Cへの通信失敗によって失敗すると500エラーが発生する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); overrideAdB2cService(service, { @@ -235,9 +225,7 @@ describe('createAccount', () => { }); it('アカウントを作成がメールアドレス重複によって失敗すると400エラーが発生する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); overrideAdB2cService(service, { @@ -304,9 +292,7 @@ describe('createAccount', () => { expect(users.length).toBe(0); }); it('アカウントを作成がDBへの通信失敗によって500エラーが発生した場合、リカバリ処理としてADB2Cユーザーを削除され、500エラーが返却される', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const b2cService = module.get(AdB2cService); const externalId = 'test_external_id'; @@ -370,9 +356,7 @@ describe('createAccount', () => { ); }); it('アカウントを作成がDBへの通信失敗によって500エラーが発生した場合、リカバリ処理が実行されるが、ADB2Cユーザー削除で失敗した場合、500エラーが返却される', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const b2cService = module.get(AdB2cService); const externalId = 'test_external_id'; @@ -437,9 +421,7 @@ describe('createAccount', () => { }); it('アカウントを作成がBlobStorageへの通信失敗によって500エラーが発生した場合、リカバリ処理としてADB2C,DB上のデータが削除され、500エラーが返却される', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const b2cService = module.get(AdB2cService); b2cService.deleteUser = jest.fn(); // リカバリ処理の確認のため、deleteUserをモック化 @@ -506,9 +488,7 @@ describe('createAccount', () => { }); it('アカウントを作成がBlobStorageへの通信失敗によって500エラーが発生した場合、リカバリ処理が実行されるが、そのリカバリ処理に失敗した場合、500エラーが返却される', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const b2cService = module.get(AdB2cService); const externalId = 'test_external_id'; @@ -577,9 +557,7 @@ describe('createAccount', () => { }); it('アカウントを作成がSendGridへの通信失敗によって500エラーが発生した場合、リカバリ処理としてADB2C,DB上のデータとBlobストレージのコンテナが削除され、500エラーが返却される', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const b2cService = module.get(AdB2cService); const blobstorageService = @@ -673,9 +651,7 @@ describe('createAccount', () => { }); it('アカウントを作成がSendGridへの通信失敗によって500エラーが発生した場合、リカバリ処理が実行されるが、そのリカバリ処理に失敗した場合、500エラーが返却される', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const b2cService = module.get(AdB2cService); const blobstorageService = @@ -767,7 +743,7 @@ describe('createAccount', () => { }); describe('createPartnerAccount', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -780,15 +756,12 @@ describe('createPartnerAccount', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('パートナーを追加できる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const adminExternalId = 'ADMIN0001'; @@ -853,19 +826,17 @@ describe('createPartnerAccount', () => { pertnerExternalId, ); const createdAccount = await getAccount(source, accountId); - expect(createdAccount?.company_name).toBe(companyName); - expect(createdAccount?.country).toBe(country); - expect(createdAccount?.parent_account_id).toBe(parent.id); - expect(createdAccount?.tier).toBe(2); - expect(createdAccount?.primary_admin_user_id).toBe(createdUser?.id); - expect(createdAccount?.secondary_admin_user_id).toBe(null); + expect(createdAccount.company_name).toBe(companyName); + expect(createdAccount.country).toBe(country); + expect(createdAccount.parent_account_id).toBe(parent.id); + expect(createdAccount.tier).toBe(2); + expect(createdAccount.primary_admin_user_id).toBe(createdUser.id); + expect(createdAccount.secondary_admin_user_id).toBe(null); } }); it('Azure AD B2Cへの接続に失敗した結果アカウントの追加に失敗した場合、エラーとなる(500エラー)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const adminExternalId = 'ADMIN0001'; @@ -940,9 +911,7 @@ describe('createPartnerAccount', () => { }); it('DBへの通信失敗によって500エラーが発生した場合、リカバリ処理としてADB2Cユーザーを削除され、500エラーが返却される', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const b2cService = module.get(AdB2cService); const adminExternalId = 'ADMIN0001'; @@ -1029,9 +998,7 @@ describe('createPartnerAccount', () => { }); it('DBへの通信失敗によって500エラーが発生した場合、リカバリ処理が実行されるが、ADB2Cユーザー削除で失敗した場合、500エラーが返却される', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const b2cService = module.get(AdB2cService); const adminExternalId = 'ADMIN0001'; @@ -1118,9 +1085,7 @@ describe('createPartnerAccount', () => { }); it('BlobStorageへの通信失敗が原因でアカウントの追加に失敗した場合、リカバリ処理としてADB2C,DB上のデータが削除され、500エラーが返却される', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const b2cService = module.get(AdB2cService); @@ -1200,9 +1165,7 @@ describe('createPartnerAccount', () => { }); it('BlobStorageへの通信失敗が原因でアカウントの追加に失敗した場合、リカバリ処理が実行されるが、そのリカバリ処理に失敗した場合、500エラーが返却される', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const b2cService = module.get(AdB2cService); @@ -1290,9 +1253,7 @@ describe('createPartnerAccount', () => { }); it('SendGridへの通信失敗によって500エラーが発生した場合、リカバリ処理としてADB2C,DB上のデータ,コンテナが削除され、500エラーが返却される', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const b2cService = module.get(AdB2cService); const blobstorageService = @@ -1382,9 +1343,7 @@ describe('createPartnerAccount', () => { }); it('SendGridへの通信失敗によって500エラーが発生した場合、リカバリ処理が実行されるが、そのリカバリ処理に失敗した場合、500エラーが返却される', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const b2cService = module.get(AdB2cService); const blobstorageService = @@ -1481,9 +1440,7 @@ describe('createPartnerAccount', () => { }); it('既に登録済みのメールアドレスが原因でアカウントの追加に失敗した場合、エラーとなる(400エラー)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const adminExternalId = 'ADMIN0001'; @@ -1600,7 +1557,7 @@ describe('AccountsService', () => { const adb2cParam = makeDefaultAdB2cMockValue(); const accountsRepositoryMockValue = makeDefaultAccountsRepositoryMockValue(); - accountsRepositoryMockValue.getLicenseSummaryInfo = new Error(); + accountsRepositoryMockValue.getLicenseSummaryInfo = null; const configMockValue = makeDefaultConfigValue(); const sendGridMockValue = makeDefaultSendGridlValue(); const blobStorageMockValue = makeBlobStorageServiceMockValue(); @@ -1847,7 +1804,7 @@ const expectedAccountLisenceCounts = { }; describe('getPartnerAccount', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -1860,15 +1817,12 @@ describe('getPartnerAccount', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('パラメータのアカウント自身と子アカウントに紐つくライセンス情報を取得する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 親アカウントと子アカウント2つ作成 const { id: parentAccountId } = ( @@ -2029,7 +1983,7 @@ describe('getPartnerAccount', () => { }); describe('getPartnerAccount', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -2042,15 +1996,12 @@ describe('getPartnerAccount', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('パラメータのアカウント自身と子アカウントに紐つくライセンス情報を取得する(第五のshortage確認)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 親アカウントと子アカウント2つ作成 const { id: parentAccountId } = ( @@ -2114,7 +2065,6 @@ describe('getPartnerAccount', () => { // 有効期限が迫っていないライセンスを追加(子1:各ステータスのライセンスを1つずつ、計4つ) const status = ['Unallocated', 'Allocated', 'Reusable', 'Deleted']; status.forEach(async (element) => { - if (!source) fail(); await createLicenseSetExpiryDateAndStatus( source, childAccountId1, @@ -2165,7 +2115,7 @@ describe('getPartnerAccount', () => { }); describe('getOrderHistories', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -2178,15 +2128,12 @@ describe('getOrderHistories', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('指定したアカウントIDの注文履歴情報を取得できる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const targetAccountId = 10; const targetParentAccountId = 14; @@ -2290,7 +2237,7 @@ describe('getOrderHistories', () => { }); describe('issueLicense', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -2303,15 +2250,12 @@ describe('issueLicense', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('指定した注文を発行済みにする', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const now = new Date(); // 親と子アカウントを作成する @@ -2330,7 +2274,7 @@ describe('issueLicense', () => { }) ).account; // 親と子のユーザーを作成する - const user = await makeTestUser(source, { + const { external_id: externalId } = await makeTestUser(source, { account_id: parentAccountId, external_id: 'userId-parent', role: 'admin', @@ -2389,7 +2333,7 @@ describe('issueLicense', () => { await service.issueLicense( context, childAccountId, - user?.external_id ?? '', + externalId, 2, 'TEST001', ); @@ -2404,9 +2348,7 @@ describe('issueLicense', () => { expect(issuedLicenses.length).toEqual(2); }); it('既に注文が発行済みの場合、エラーとなる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const now = new Date(); // 親と子アカウントを作成する @@ -2425,7 +2367,7 @@ describe('issueLicense', () => { }) ).account; // 親と子のユーザーを作成する - const user = await makeTestUser(source, { + const { external_id: externalId } = await makeTestUser(source, { account_id: parentAccountId, external_id: 'userId-parent', role: 'admin', @@ -2483,28 +2425,20 @@ describe('issueLicense', () => { await service.issueLicense( context, childAccountId, - user?.external_id ?? '', + externalId, 2, 'TEST001', ); //再度同じ処理を行う await expect( - service.issueLicense( - context, - childAccountId, - user?.external_id ?? '', - 2, - 'TEST001', - ), + service.issueLicense(context, childAccountId, externalId, 2, 'TEST001'), ).rejects.toEqual( new HttpException(makeErrorResponse('E010803'), HttpStatus.BAD_REQUEST), ); }); it('ライセンスが不足している場合、エラーとなる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const now = new Date(); // 親と子アカウントを作成する @@ -2523,7 +2457,7 @@ describe('issueLicense', () => { }) ).account; // 親と子のユーザーを作成する - const user = await makeTestUser(source, { + const { external_id: externalId } = await makeTestUser(source, { account_id: parentAccountId, external_id: 'userId-parent', role: 'admin', @@ -2580,13 +2514,7 @@ describe('issueLicense', () => { // 注文を発行済みにする await expect( - service.issueLicense( - context, - childAccountId, - user?.external_id ?? '', - 2, - 'TEST001', - ), + service.issueLicense(context, childAccountId, externalId, 2, 'TEST001'), ).rejects.toEqual( new HttpException(makeErrorResponse('E010804'), HttpStatus.BAD_REQUEST), ); @@ -2594,7 +2522,7 @@ describe('issueLicense', () => { }); describe('getDealers', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -2607,14 +2535,11 @@ describe('getDealers', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('Dealerを取得できる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId_1 } = ( await makeTestAccount(source, { parent_account_id: 1, @@ -2662,9 +2587,7 @@ describe('getDealers', () => { }); }); it('0件でもDealerを取得できる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); @@ -2675,7 +2598,7 @@ describe('getDealers', () => { }); describe('createTypistGroup', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -2688,14 +2611,11 @@ describe('createTypistGroup', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('TypistGroupを作成できる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const adminExternalId = 'admin-external-id'; // 第五階層のアカウント作成 const { id: accountId } = ( @@ -2713,12 +2633,12 @@ describe('createTypistGroup', () => { ]; const userIds: number[] = []; for (const typiptUserExternalId of typiptUserExternalIds) { - const user = await makeTestUser(source, { + const { id: userId } = await makeTestUser(source, { account_id: accountId, external_id: typiptUserExternalId, role: 'typist', }); - userIds.push(user?.id ?? 0); + userIds.push(userId); } //作成したデータを確認 { @@ -2754,9 +2674,7 @@ describe('createTypistGroup', () => { }); it('typistIdsにRole:typist以外のユーザーが含まれていた場合、400エラーを返却する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const adminExternalId = 'admin-external-id'; // 第五階層のアカウント作成 const { id: accountId } = ( @@ -2774,7 +2692,7 @@ describe('createTypistGroup', () => { ]; const userIds: number[] = []; for (const typiptUserExternalId of typiptUserExternalIds) { - const user = await makeTestUser(source, { + const { id: userId } = await makeTestUser(source, { account_id: accountId, external_id: typiptUserExternalId, role: @@ -2782,7 +2700,7 @@ describe('createTypistGroup', () => { ? 'none' : 'typist', //typist-user-external-id3のみRole:none, }); - userIds.push(user?.id ?? 0); + userIds.push(userId); } //作成したデータを確認 { @@ -2809,9 +2727,7 @@ describe('createTypistGroup', () => { ); }); it('typistIdsに存在しないユーザーが含まれていた場合、400エラーを返却する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const adminExternalId = 'admin-external-id'; // 第五階層のアカウント作成 const { id: accountId } = ( @@ -2829,12 +2745,12 @@ describe('createTypistGroup', () => { ]; const userIds: number[] = []; for (const typiptUserExternalId of typiptUserExternalIds) { - const user = await makeTestUser(source, { + const { id: userId } = await makeTestUser(source, { account_id: accountId, external_id: typiptUserExternalId, role: 'typist', }); - userIds.push(user?.id ?? 0); + userIds.push(userId); } //作成したデータを確認 { @@ -2860,9 +2776,7 @@ describe('createTypistGroup', () => { ); }); it('DBアクセスに失敗した場合、500エラーを返却する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const adminExternalId = 'admin-external-id'; // 第五階層のアカウント作成 const { id: accountId } = ( @@ -2880,12 +2794,12 @@ describe('createTypistGroup', () => { ]; const userIds: number[] = []; for (const typiptUserExternalId of typiptUserExternalIds) { - const user = await makeTestUser(source, { + const { id: userId } = await makeTestUser(source, { account_id: accountId, external_id: typiptUserExternalId, role: 'typist', }); - userIds.push(user?.id ?? 0); + userIds.push(userId); } //作成したデータを確認 { @@ -2924,7 +2838,7 @@ describe('createTypistGroup', () => { }); describe('getTypistGroup', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -2937,14 +2851,11 @@ describe('getTypistGroup', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('指定したIDのTypistGroupを取得できる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -2956,12 +2867,12 @@ describe('getTypistGroup', () => { ]; const userIds: number[] = []; for (const typiptUserExternalId of typiptUserExternalIds) { - const user = await makeTestUser(source, { + const { id: userId } = await makeTestUser(source, { account_id: account.id, external_id: typiptUserExternalId, role: USER_ROLES.TYPIST, }); - userIds.push(user?.id ?? 0); + userIds.push(userId); } // アカウントにタイピストグループを作成する @@ -3004,9 +2915,7 @@ describe('getTypistGroup', () => { }); it('指定したタイピストグループIDのタイピストグループが存在しない場合、400エラーを返却する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); // 作成したアカウントにユーザーを3名追加する @@ -3017,12 +2926,12 @@ describe('getTypistGroup', () => { ]; const userIds: number[] = []; for (const typiptUserExternalId of typiptUserExternalIds) { - const user = await makeTestUser(source, { + const { id: userId } = await makeTestUser(source, { account_id: account.id, external_id: typiptUserExternalId, role: USER_ROLES.TYPIST, }); - userIds.push(user?.id ?? 0); + userIds.push(userId); } // アカウントにタイピストグループを作成する @@ -3059,9 +2968,7 @@ describe('getTypistGroup', () => { }); it('DBアクセスに失敗した場合、500エラーを返却する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); // 作成したアカウントにユーザーを3名追加する @@ -3072,12 +2979,12 @@ describe('getTypistGroup', () => { ]; const userIds: number[] = []; for (const typiptUserExternalId of typiptUserExternalIds) { - const user = await makeTestUser(source, { + const { id: userId } = await makeTestUser(source, { account_id: account.id, external_id: typiptUserExternalId, role: USER_ROLES.TYPIST, }); - userIds.push(user?.id ?? 0); + userIds.push(userId); } // アカウントにタイピストグループを作成する @@ -3124,7 +3031,7 @@ describe('getTypistGroup', () => { }); describe('updateTypistGroup', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -3137,14 +3044,11 @@ describe('updateTypistGroup', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('TypistGroupを更新できる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); // 作成したアカウントにユーザーを3名追加する @@ -3155,12 +3059,12 @@ describe('updateTypistGroup', () => { ]; const userIds: number[] = []; for (const typiptUserExternalId of typiptUserExternalIds) { - const user = await makeTestUser(source, { + const { id: userId } = await makeTestUser(source, { account_id: account.id, external_id: typiptUserExternalId, role: USER_ROLES.TYPIST, }); - userIds.push(user?.id ?? 0); + userIds.push(userId); } const service = module.get(AccountsService); @@ -3210,9 +3114,7 @@ describe('updateTypistGroup', () => { } }); it('typistIdsにRole:typist以外のユーザーが含まれていた場合、400エラーを返却する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); // 作成したアカウントにユーザーを3名追加する @@ -3223,7 +3125,7 @@ describe('updateTypistGroup', () => { ]; const userIds: number[] = []; for (const typiptUserExternalId of typiptUserExternalIds) { - const user = await makeTestUser(source, { + const { id: userId } = await makeTestUser(source, { account_id: account.id, external_id: typiptUserExternalId, role: @@ -3231,7 +3133,7 @@ describe('updateTypistGroup', () => { ? USER_ROLES.NONE : USER_ROLES.TYPIST, //typist-user-external-id3のみRole:none }); - userIds.push(user?.id ?? 0); + userIds.push(userId); } const typistGroupName = 'typist-group-name'; @@ -3277,9 +3179,7 @@ describe('updateTypistGroup', () => { } }); it('typistIdsに存在しないユーザーが含まれていた場合、400エラーを返却する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); // 作成したアカウントにユーザーを3名追加する @@ -3290,12 +3190,12 @@ describe('updateTypistGroup', () => { ]; const userIds: number[] = []; for (const typiptUserExternalId of typiptUserExternalIds) { - const user = await makeTestUser(source, { + const { id: userId } = await makeTestUser(source, { account_id: account.id, external_id: typiptUserExternalId, role: USER_ROLES.TYPIST, }); - userIds.push(user?.id ?? 0); + userIds.push(userId); } const typistGroupName = 'typist-group-name'; @@ -3339,9 +3239,7 @@ describe('updateTypistGroup', () => { } }); it('タイピストグループが存在しない場合、400エラーを返却する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -3353,12 +3251,12 @@ describe('updateTypistGroup', () => { ]; const userIds: number[] = []; for (const typiptUserExternalId of typiptUserExternalIds) { - const user = await makeTestUser(source, { + const { id: userId } = await makeTestUser(source, { account_id: account.id, external_id: typiptUserExternalId, role: USER_ROLES.TYPIST, }); - userIds.push(user?.id ?? 0); + userIds.push(userId); } const typistGroupName = 'typist-group-name'; @@ -3403,9 +3301,7 @@ describe('updateTypistGroup', () => { } }); it('DBアクセスに失敗した場合、500エラーを返却する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); // 作成したアカウントにユーザーを3名追加する @@ -3416,12 +3312,12 @@ describe('updateTypistGroup', () => { ]; const userIds: number[] = []; for (const typiptUserExternalId of typiptUserExternalIds) { - const user = await makeTestUser(source, { + const { id: userId } = await makeTestUser(source, { account_id: account.id, external_id: typiptUserExternalId, role: USER_ROLES.TYPIST, }); - userIds.push(user?.id ?? 0); + userIds.push(userId); } const typistGroupName = 'typist-group-name'; @@ -3477,7 +3373,7 @@ describe('updateTypistGroup', () => { }); describe('getWorktypes', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -3490,15 +3386,12 @@ describe('getWorktypes', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('アカウント内のWorktypeを取得できる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -3537,9 +3430,7 @@ describe('getWorktypes', () => { }); it('アカウント内のWorktypeを取得できる(0件)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { admin } = await makeTestAccount(source, { tier: 5 }); @@ -3555,9 +3446,7 @@ describe('getWorktypes', () => { }); it('DBアクセスに失敗した場合、500エラーを返却する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -3597,7 +3486,7 @@ describe('getWorktypes', () => { }); describe('createWorktype', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -3610,15 +3499,12 @@ describe('createWorktype', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('Worktypeを作成できる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -3657,9 +3543,7 @@ describe('createWorktype', () => { }); it('WorktypeIDが登録済みのWorktypeIDと重複した場合、400エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -3688,9 +3572,7 @@ describe('createWorktype', () => { }); it('WorktypeIDがすでに最大登録数(20件)まで登録されている場合、400エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -3721,9 +3603,7 @@ describe('createWorktype', () => { }); it('DBアクセスに失敗した場合、500エラーを返却する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { admin } = await makeTestAccount(source, { tier: 5 }); @@ -3750,7 +3630,7 @@ describe('createWorktype', () => { }); describe('updateWorktype', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -3763,15 +3643,12 @@ describe('updateWorktype', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('Worktypeを更新できる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -3818,9 +3695,7 @@ describe('updateWorktype', () => { }); it('指定したIDが登録されていない場合、400エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -3865,9 +3740,7 @@ describe('updateWorktype', () => { }); it('WorktypeIDが登録済みのWorktypeIDと重複した場合、400エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -3927,9 +3800,7 @@ describe('updateWorktype', () => { }); it('WorktypeIDが登録済みの指定IDのWorktypeIDと重複した場合でも更新できること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -3975,9 +3846,7 @@ describe('updateWorktype', () => { }); it('DBアクセスに失敗した場合、500エラーを返却する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -4028,256 +3897,8 @@ describe('updateWorktype', () => { }); }); -describe('deleteWorktype', () => { - let source: DataSource | null = null; - beforeEach(async () => { - source = new DataSource({ - type: 'sqlite', - database: ':memory:', - logging: false, - entities: [__dirname + '/../../**/*.entity{.ts,.js}'], - synchronize: true, // trueにすると自動的にmigrationが行われるため注意 - }); - return source.initialize(); - }); - - afterEach(async () => { - if (!source) return; - await source.destroy(); - source = null; - }); - - it('WorktypeIDを削除できること', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - // 第五階層のアカウント作成 - const { account, admin } = await makeTestAccount(source, { tier: 5 }); - - const service = module.get(AccountsService); - const context = makeContext(admin.external_id); - - const { id: worktypeId1 } = await createWorktype( - source, - account.id, - 'worktype1', - ); - const { id: worktypeId2 } = await createWorktype( - source, - account.id, - 'worktype2', - ); - await createOptionItems(source, worktypeId1); - await createOptionItems(source, worktypeId2); - - // 作成したデータを確認 - { - const worktypes = await getWorktypes(source, account.id); - const optionItems = await getOptionItems(source); - expect(worktypes.length).toBe(2); - expect(worktypes[0].id).toBe(worktypeId1); - expect(worktypes[0].custom_worktype_id).toBe('worktype1'); - expect(worktypes[1].id).toBe(worktypeId2); - expect(worktypes[1].custom_worktype_id).toBe('worktype2'); - expect(optionItems.length).toBe(20); - } - - await service.deleteWorktype(context, admin.external_id, worktypeId1); - - //実行結果を確認 - { - const worktypes = await getWorktypes(source, account.id); - const optionItems = await getOptionItems(source); - expect(worktypes.length).toBe(1); - expect(worktypes[0].id).toBe(worktypeId2); - expect(worktypes[0].custom_worktype_id).toBe('worktype2'); - expect(optionItems.length).toBe(10); - } - }); - - it('指定されたWorktypeIDがアカウントのActiveWorktypeIDの場合、削除できること', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - // 第五階層のアカウント作成 - const { account, admin } = await makeTestAccount(source, { tier: 5 }); - - const service = module.get(AccountsService); - const context = makeContext(admin.external_id); - - const { id: worktypeId1 } = await createWorktype( - source, - account.id, - 'worktype1', - 'description1', - true, - ); - await createOptionItems(source, worktypeId1); - - // 作成したデータを確認 - { - const worktypes = await getWorktypes(source, account.id); - const optionItems = await getOptionItems(source); - const accounts = await getAccounts(source); - - expect(worktypes.length).toBe(1); - expect(worktypes[0].id).toBe(worktypeId1); - expect(worktypes[0].custom_worktype_id).toBe('worktype1'); - expect(optionItems.length).toBe(10); - expect(accounts.length).toBe(1); - expect(accounts[0].active_worktype_id).toBe(worktypeId1); - } - - await service.deleteWorktype(context, admin.external_id, worktypeId1); - - //実行結果を確認 - { - const worktypes = await getWorktypes(source, account.id); - const optionItems = await getOptionItems(source); - const accounts = await getAccounts(source); - expect(worktypes.length).toBe(0); - expect(optionItems.length).toBe(0); - expect(accounts.length).toBe(1); - expect(accounts[0].active_worktype_id).toBe(null); - } - }); - - it('指定したWorktypeIDが登録されていない場合、400エラーとなること', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - // 第五階層のアカウント作成 - const { account, admin } = await makeTestAccount(source, { tier: 5 }); - - const service = module.get(AccountsService); - const context = makeContext(admin.external_id); - - const { id: worktypeId1 } = await createWorktype( - source, - account.id, - 'worktype1', - ); - await createOptionItems(source, worktypeId1); - - // 作成したデータを確認 - { - const worktypes = await getWorktypes(source, account.id); - const optionItems = await getOptionItems(source); - expect(worktypes.length).toBe(1); - expect(worktypes[0].id).toBe(worktypeId1); - expect(worktypes[0].custom_worktype_id).toBe('worktype1'); - expect(optionItems.length).toBe(10); - } - - try { - await service.deleteWorktype(context, admin.external_id, 9999); - fail(); // 例外が発生しない場合はテスト失敗 - } catch (e) { - if (e instanceof HttpException) { - expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST); - expect(e.getResponse()).toEqual(makeErrorResponse('E011003')); - } else { - fail(); - } - } - }); - - it('指定したIDがWorkflowで使用されている場合、400エラーとなること', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - // 第五階層のアカウント作成 - const { account, admin } = await makeTestAccount(source, { tier: 5 }); - const author = await makeTestUser(source, { - account_id: account.id, - role: USER_ROLES.AUTHOR, - }); - - const service = module.get(AccountsService); - const context = makeContext(admin.external_id); - - const { id: worktypeId1 } = await createWorktype( - source, - account.id, - 'worktype1', - ); - await createOptionItems(source, worktypeId1); - await createWorkflow(source, account.id, author?.id ?? 0, worktypeId1); - - // 作成したデータを確認 - { - const worktypes = await getWorktypes(source, account.id); - const optionItems = await getOptionItems(source); - const workflows = await getWorkflows(source, account.id); - expect(worktypes.length).toBe(1); - expect(worktypes[0].id).toBe(worktypeId1); - expect(worktypes[0].custom_worktype_id).toBe('worktype1'); - expect(optionItems.length).toBe(10); - expect(workflows.length).toBe(1); - expect(workflows[0].worktype_id).toBe(worktypeId1); - } - - try { - await service.deleteWorktype(context, admin.external_id, worktypeId1); - fail(); // 例外が発生しない場合はテスト失敗 - } catch (e) { - if (e instanceof HttpException) { - expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST); - expect(e.getResponse()).toEqual(makeErrorResponse('E011004')); - } else { - fail(); - } - } - }); - - it('DBアクセスに失敗した場合、500エラーを返却する', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - // 第五階層のアカウント作成 - const { account, admin } = await makeTestAccount(source, { tier: 5 }); - - const service = module.get(AccountsService); - const context = makeContext(admin.external_id); - - const { id: worktypeId1 } = await createWorktype( - source, - account.id, - 'worktype1', - ); - await createOptionItems(source, worktypeId1); - - // 作成したデータを確認 - { - const worktypes = await getWorktypes(source, account.id); - const optionItems = await getOptionItems(source); - expect(worktypes.length).toBe(1); - expect(worktypes[0].id).toBe(worktypeId1); - expect(worktypes[0].custom_worktype_id).toBe('worktype1'); - expect(optionItems.length).toBe(10); - } - //DBアクセスに失敗するようにする - const worktypeService = module.get( - WorktypesRepositoryService, - ); - worktypeService.deleteWorktype = jest.fn().mockRejectedValue('DB failed'); - - try { - await service.deleteWorktype(context, admin.external_id, worktypeId1); - fail(); // 例外が発生しない場合はテスト失敗 - } catch (e) { - if (e instanceof HttpException) { - expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR); - expect(e.getResponse()).toEqual(makeErrorResponse('E009999')); - } else { - fail(); - } - } - }); -}); - describe('getOptionItems', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -4290,15 +3911,12 @@ describe('getOptionItems', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('指定WorktypeIDに紐づいたOptionItemを取得できる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -4339,9 +3957,7 @@ describe('getOptionItems', () => { }); it('WorktypeIDが存在しない場合、400エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -4374,9 +3990,7 @@ describe('getOptionItems', () => { }); it('DBアクセスに失敗した場合、500エラーを返却する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -4416,7 +4030,7 @@ describe('getOptionItems', () => { }); describe('updateOptionItems', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -4429,15 +4043,12 @@ describe('updateOptionItems', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('指定WorktypeIDに紐づいたOptionItemを更新できる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -4561,9 +4172,7 @@ describe('updateOptionItems', () => { }); it('WorktypeIDが存在しない場合、400エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -4647,9 +4256,7 @@ describe('updateOptionItems', () => { }); it('DBアクセスに失敗した場合、500エラーを返却する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -4742,7 +4349,7 @@ describe('updateOptionItems', () => { }); describe('updateActiveWorktype', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -4755,15 +4362,12 @@ describe('updateActiveWorktype', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('アカウントのActiveWorktypeIDを指定WorktypeIDに更新できる(NULL⇒ID設定)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -4775,22 +4379,20 @@ describe('updateActiveWorktype', () => { //作成したデータを確認 { const beforeAccount = await getAccount(source, account.id); - expect(beforeAccount?.active_worktype_id).toBe(null); + expect(beforeAccount.active_worktype_id).toBe(null); } await service.updateActiveWorktype(context, admin.external_id, worktype.id); //実行結果を確認 { - const resultsAccount = await getAccount(source, account.id); - expect(resultsAccount?.active_worktype_id).toBe(worktype.id); + const { active_worktype_id } = await getAccount(source, account.id); + expect(active_worktype_id).toBe(worktype.id); } }); it('アカウントのActiveWorktypeIDを指定WorktypeIDに更新できる(別のWorkTypeIDを設定)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -4809,7 +4411,7 @@ describe('updateActiveWorktype', () => { //作成したデータを確認 { const beforeAccount = await getAccount(source, account.id); - expect(beforeAccount?.active_worktype_id).toBe(worktype1.id); + expect(beforeAccount.active_worktype_id).toBe(worktype1.id); } await service.updateActiveWorktype( @@ -4820,15 +4422,13 @@ describe('updateActiveWorktype', () => { //実行結果を確認 { - const resultsAccount = await getAccount(source, account.id); - expect(resultsAccount?.active_worktype_id).toBe(worktype2.id); + const { active_worktype_id } = await getAccount(source, account.id); + expect(active_worktype_id).toBe(worktype2.id); } }); it('アカウントのActiveWorktypeIDをNULLに更新できる(WorkTypeID⇒NULL)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -4846,22 +4446,20 @@ describe('updateActiveWorktype', () => { //作成したデータを確認 { const beforeAccount = await getAccount(source, account.id); - expect(beforeAccount?.active_worktype_id).toBe(worktype1.id); + expect(beforeAccount.active_worktype_id).toBe(worktype1.id); } await service.updateActiveWorktype(context, admin.external_id, undefined); //実行結果を確認 { - const resultsAccount = await getAccount(source, account.id); - expect(resultsAccount?.active_worktype_id).toBe(null); + const { active_worktype_id } = await getAccount(source, account.id); + expect(active_worktype_id).toBe(null); } }); it('自アカウント内に指定されたIDのWorktypeIDが存在しない場合、400エラーとなること(WorkTypeIDが存在しない場合)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -4873,7 +4471,7 @@ describe('updateActiveWorktype', () => { //作成したデータを確認 { const beforeAccount = await getAccount(source, account.id); - expect(beforeAccount?.active_worktype_id).toBe(null); + expect(beforeAccount.active_worktype_id).toBe(null); } try { @@ -4889,9 +4487,7 @@ describe('updateActiveWorktype', () => { }); it('自アカウント内に指定されたIDのWorktypeIDが存在しない場合、400エラーとなること(WorkTypeIDが別アカウントの場合)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { account: otherAccount } = await makeTestAccount(source, { @@ -4909,7 +4505,7 @@ describe('updateActiveWorktype', () => { const beforeAccount = await getAccount(source, account.id); const worktype1 = await getWorktypes(source, account.id); const worktype2 = await getWorktypes(source, otherAccount.id); - expect(beforeAccount?.active_worktype_id).toBe(null); + expect(beforeAccount.active_worktype_id).toBe(null); expect(worktype1.length).toBe(1); expect(worktype1[0].custom_worktype_id).toBe('worktype1'); @@ -4931,9 +4527,7 @@ describe('updateActiveWorktype', () => { }); it('DBアクセスに失敗した場合、500エラーを返却する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -4945,7 +4539,7 @@ describe('updateActiveWorktype', () => { //作成したデータを確認 { const beforeAccount = await getAccount(source, account.id); - expect(beforeAccount?.active_worktype_id).toBe(null); + expect(beforeAccount.active_worktype_id).toBe(null); } //DBアクセスに失敗するようにする @@ -4970,7 +4564,7 @@ describe('updateActiveWorktype', () => { }); describe('ライセンス発行キャンセル', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -4983,14 +4577,11 @@ describe('ライセンス発行キャンセル', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('ライセンス発行のキャンセルが完了する(第一階層で実行)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { tier1Accounts: tier1Accounts, tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(source); const tier5Accounts = await makeTestAccount(source, { @@ -5004,7 +4595,7 @@ describe('ライセンス発行キャンセル', () => { source, poNumber, tier5Accounts.account.id, - tier5Accounts.account?.parent_account_id ?? 0, + tier5Accounts.account.parent_account_id, date, 1, LICENSE_ISSUE_STATUS.ISSUED, @@ -5038,22 +4629,20 @@ describe('ライセンス発行キャンセル', () => { tier5Accounts.account.id, poNumber, ); - expect(orderRecord.orderLicense?.issued_at).toBe(null); - expect(orderRecord.orderLicense?.status).toBe( + expect(orderRecord.orderLicense.issued_at).toBe(null); + expect(orderRecord.orderLicense.status).toBe( LICENSE_ISSUE_STATUS.ISSUE_REQUESTING, ); // 未割当に戻したライセンスの状態確認 const licenseRecord = await selectLicense(source, 1); - expect(licenseRecord.license?.status).toBe( + expect(licenseRecord.license.status).toBe( LICENSE_ALLOCATED_STATUS.UNALLOCATED, ); - expect(licenseRecord.license?.delete_order_id).toBe(null); - expect(licenseRecord.license?.deleted_at).toBe(null); + expect(licenseRecord.license.delete_order_id).toBe(null); + expect(licenseRecord.license.deleted_at).toBe(null); }); it('ライセンス発行のキャンセルが完了する(第二階層で実行)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { tier2Accounts: tier2Accounts, tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(source); const tier5Accounts = await makeTestAccount(source, { @@ -5067,7 +4656,7 @@ describe('ライセンス発行キャンセル', () => { source, poNumber, tier5Accounts.account.id, - tier5Accounts.account?.parent_account_id ?? 0, + tier5Accounts.account.parent_account_id, date, 1, LICENSE_ISSUE_STATUS.ISSUED, @@ -5101,22 +4690,20 @@ describe('ライセンス発行キャンセル', () => { tier5Accounts.account.id, poNumber, ); - expect(orderRecord.orderLicense?.issued_at).toBe(null); - expect(orderRecord.orderLicense?.status).toBe( + expect(orderRecord.orderLicense.issued_at).toBe(null); + expect(orderRecord.orderLicense.status).toBe( LICENSE_ISSUE_STATUS.ISSUE_REQUESTING, ); // 未割当に戻したライセンスの状態確認 const licenseRecord = await selectLicense(source, 1); - expect(licenseRecord.license?.status).toBe( + expect(licenseRecord.license.status).toBe( LICENSE_ALLOCATED_STATUS.UNALLOCATED, ); - expect(licenseRecord.license?.delete_order_id).toBe(null); - expect(licenseRecord.license?.deleted_at).toBe(null); + expect(licenseRecord.license.delete_order_id).toBe(null); + expect(licenseRecord.license.deleted_at).toBe(null); }); it('キャンセル対象の発行が存在しない場合エラー', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { tier1Accounts: tier1Accounts, tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(source); const tier5Accounts = await makeTestAccount(source, { @@ -5137,9 +4724,7 @@ describe('ライセンス発行キャンセル', () => { ); }); it('キャンセル対象の発行が14日より経過していた場合エラー', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { tier1Accounts: tier1Accounts, tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(source); const tier5Accounts = await makeTestAccount(source, { @@ -5153,7 +4738,7 @@ describe('ライセンス発行キャンセル', () => { source, poNumber, tier5Accounts.account.id, - tier5Accounts.account?.parent_account_id ?? 0, + tier5Accounts.account.parent_account_id, date, 1, LICENSE_ISSUE_STATUS.ISSUED, @@ -5183,9 +4768,7 @@ describe('ライセンス発行キャンセル', () => { ); }); it('キャンセル対象の発行のライセンスが使われていた場合エラー', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { tier1Accounts: tier1Accounts, tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(source); const tier5Accounts = await makeTestAccount(source, { @@ -5199,7 +4782,7 @@ describe('ライセンス発行キャンセル', () => { source, poNumber, tier5Accounts.account.id, - tier5Accounts.account?.parent_account_id ?? 0, + tier5Accounts.account.parent_account_id, date, 1, LICENSE_ISSUE_STATUS.ISSUED, @@ -5229,9 +4812,7 @@ describe('ライセンス発行キャンセル', () => { ); }); it('自身のパートナー以外の発行をキャンセルしようとした場合、エラー', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { tier1Accounts: tier1Accounts } = await makeHierarchicalAccounts( source, ); @@ -5246,7 +4827,7 @@ describe('ライセンス発行キャンセル', () => { source, poNumber, tier5Accounts.account.id, - tier5Accounts.account?.parent_account_id ?? 0, + tier5Accounts.account.parent_account_id, date, 1, LICENSE_ISSUE_STATUS.ISSUED, @@ -5278,7 +4859,7 @@ describe('ライセンス発行キャンセル', () => { }); describe('パートナー一覧取得', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -5291,16 +4872,13 @@ describe('パートナー一覧取得', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('パートナー一覧を取得する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); - const { tier1Accounts, tier2Accounts, tier3Accounts, tier4Accounts } = + const { tier1Accounts: tier1Accounts, tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts(source); const tier1Difference = await makeTestAccount(source, { tier: 1, @@ -5322,7 +4900,7 @@ describe('パートナー一覧取得', () => { tier: 2, }, {}, - false, + true, true, ); @@ -5337,7 +4915,7 @@ describe('パートナー一覧取得', () => { displayName: 'partner1', identities: [ { - signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + signInType: ADB2C_SIGN_IN_TYPE.EAMILADDRESS, issuer: 'issuer', issuerAssignedId: 'partner1@example.com', }, @@ -5348,34 +4926,12 @@ describe('パートナー一覧取得', () => { displayName: 'partner2', identities: [ { - signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + signInType: ADB2C_SIGN_IN_TYPE.EAMILADDRESS, issuer: 'issuer', issuerAssignedId: 'partner2@example.com', }, ], }, - { - id: tier2_3.admin.external_id, - displayName: 'partner3', - identities: [ - { - signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, - issuer: 'issuer', - issuerAssignedId: 'partner3@example.com', - }, - ], - }, - { - id: tier2_4.admin.external_id, - displayName: 'partner3', - identities: [ - { - signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, - issuer: 'issuer', - issuerAssignedId: 'partner3@example.com', - }, - ], - }, ] as AdB2cUser[]; overrideAdB2cService(service, { @@ -5417,9 +4973,7 @@ describe('パートナー一覧取得', () => { ); }); it('パートナー一覧を取得する(パートナーが0件の場合)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const account = await makeTestAccount(source, { tier: 1, @@ -5446,7 +5000,7 @@ describe('パートナー一覧取得', () => { }); describe('アカウント情報更新', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -5459,14 +5013,11 @@ describe('アカウント情報更新', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('アカウント情報を更新する(第五階層が実行/セカンダリ管理者ユーザがnull)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts( source, @@ -5487,15 +5038,13 @@ describe('アカウント情報更新', () => { // DB内が想定通りになっているか確認 const account = await getAccount(source, tier5Accounts.account.id); - expect(account?.parent_account_id).toBe(tier4Accounts[0].account.id); - expect(account?.delegation_permission).toBe(true); - expect(account?.primary_admin_user_id).toBe(tier5Accounts.admin.id); - expect(account?.secondary_admin_user_id).toBe(null); + expect(account.parent_account_id).toBe(tier4Accounts[0].account.id); + expect(account.delegation_permission).toBe(true); + expect(account.primary_admin_user_id).toBe(tier5Accounts.admin.id); + expect(account.secondary_admin_user_id).toBe(null); }); it('アカウント情報を更新する(第五階層以外が実行)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const { tier3Accounts: tier3Accounts, tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(source); @@ -5511,24 +5060,21 @@ describe('アカウント情報更新', () => { false, tier4Accounts[0].users[0].id, tier3Accounts[0].account.id, - adduser?.id, + adduser.id, ); // DB内が想定通りになっているか確認 const account = await getAccount(source, tier4Accounts[0].account.id); - expect(account?.parent_account_id).toBe(tier3Accounts[0].account.id); - expect(account?.delegation_permission).toBe(false); - expect(account?.primary_admin_user_id).toBe(tier4Accounts[0].users[0].id); - expect(account?.secondary_admin_user_id).toBe(adduser?.id); + expect(account.parent_account_id).toBe(tier3Accounts[0].account.id); + expect(account.delegation_permission).toBe(false); + expect(account.primary_admin_user_id).toBe(tier4Accounts[0].users[0].id); + expect(account.secondary_admin_user_id).toBe(adduser.id); }); it('アカウント情報を更新する(ディーラーアカウントが未入力)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); - const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts( - source, - ); + const { tier3Accounts: tier3Accounts, tier4Accounts: tier4Accounts } = + await makeHierarchicalAccounts(source); const adduser = await makeTestUser(source, { account_id: tier4Accounts[0].account.id, external_id: 'typist-user-external-id', @@ -5541,20 +5087,18 @@ describe('アカウント情報更新', () => { false, tier4Accounts[0].users[0].id, undefined, - adduser?.id, + adduser.id, ); // DB内が想定通りになっているか確認 const account = await getAccount(source, tier4Accounts[0].account.id); - expect(account?.parent_account_id).toBe(null); - expect(account?.delegation_permission).toBe(false); - expect(account?.primary_admin_user_id).toBe(tier4Accounts[0].users[0].id); - expect(account?.secondary_admin_user_id).toBe(adduser?.id); + expect(account.parent_account_id).toBe(null); + expect(account.delegation_permission).toBe(false); + expect(account.primary_admin_user_id).toBe(tier4Accounts[0].users[0].id); + expect(account.secondary_admin_user_id).toBe(adduser.id); }); it('アカウント情報の更新に失敗する(ディーラー未存在)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts( source, @@ -5572,16 +5116,14 @@ describe('アカウント情報更新', () => { false, tier4Accounts[0].users[0].id, 123, - adduser?.id, + adduser.id, ), ).rejects.toEqual( new HttpException(makeErrorResponse('E010502'), HttpStatus.BAD_REQUEST), ); }); it('アカウント情報の更新に失敗する(プライマリ管理者ユーザ未存在)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts( source, @@ -5605,9 +5147,7 @@ describe('アカウント情報更新', () => { ); }); it('アカウント情報の更新に失敗する(セカンダリ管理者ユーザ未存在)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts( source, @@ -5633,7 +5173,7 @@ describe('アカウント情報更新', () => { }); describe('getAccountInfo', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -5646,14 +5186,11 @@ describe('getAccountInfo', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('パラメータのユーザに対応するアカウント情報を取得できる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts( source, ); @@ -5693,7 +5230,7 @@ describe('getAccountInfo', () => { }); }); describe('getAuthors', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -5706,28 +5243,25 @@ describe('getAuthors', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('アカウント内のAuthorユーザーの一覧を取得できる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); - const userId1 = await makeTestUser(source, { + const { id: userId1 } = await makeTestUser(source, { account_id: account.id, role: USER_ROLES.AUTHOR, author_id: 'AUTHOR_ID_1', }); - const userId2 = await makeTestUser(source, { + const { id: userId2 } = await makeTestUser(source, { account_id: account.id, role: USER_ROLES.AUTHOR, author_id: 'AUTHOR_ID_2', }); - const userId3 = await makeTestUser(source, { + const { id: userId3 } = await makeTestUser(source, { account_id: account.id, role: USER_ROLES.TYPIST, }); @@ -5736,9 +5270,9 @@ describe('getAuthors', () => { { const users = await getUsers(source); expect(users.length).toBe(4); - expect(users[1].id).toBe(userId1.id); - expect(users[2].id).toBe(userId2.id); - expect(users[3].id).toBe(userId3.id); + expect(users[1].id).toBe(userId1); + expect(users[2].id).toBe(userId2); + expect(users[3].id).toBe(userId3); } const service = module.get(AccountsService); @@ -5748,16 +5282,14 @@ describe('getAuthors', () => { //実行結果を確認 { expect(authors.length).toBe(2); - expect(authors[0].id).toBe(userId1.id); + expect(authors[0].id).toBe(userId1); expect(authors[0].authorId).toBe('AUTHOR_ID_1'); - expect(authors[1].id).toBe(userId2.id); + expect(authors[1].id).toBe(userId2); expect(authors[1].authorId).toBe('AUTHOR_ID_2'); } }); it('アカウント内のAuthorユーザーの一覧を取得できる(0件)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { admin } = await makeTestAccount(source, { tier: 5 }); @@ -5777,9 +5309,7 @@ describe('getAuthors', () => { } }); it('DBアクセスに失敗した場合、500エラーとなる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { admin } = await makeTestAccount(source, { tier: 5 }); @@ -5806,7 +5336,7 @@ describe('getAuthors', () => { }); }); describe('deleteAccountAndData', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -5819,133 +5349,50 @@ describe('deleteAccountAndData', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('アカウント情報が削除されること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); - // 第一~第四階層のアカウント作成 - const { - tier1Accounts: tier1Accounts, - tier2Accounts: tier2Accounts, - tier3Accounts: tier3Accounts, - tier4Accounts: tier4Accounts, - } = await makeHierarchicalAccounts(source); - - // 第五階層のアカウント作成(A) - const tier5AccountsA = await makeTestAccount(source, { - parent_account_id: tier4Accounts[0].account.id, + // 第五階層のアカウント作成 + const tier4Accounts = await makeHierarchicalAccounts(source); + const { account: account1, admin: admin1 } = await makeTestAccount(source, { + parent_account_id: tier4Accounts.tier4Accounts[0].account.id, + }); + const account = account1; + const admin = admin1; + const context = makeContext(admin.external_id); + // 第五階層のアカウント作成 + const tier5Accounts = await makeTestAccount(source, { + parent_account_id: account.id, tier: 5, }); - // 第五階層のアカウント作成(B) - const tier5AccountsB = await makeTestAccount(source, { - parent_account_id: tier4Accounts[0].account.id, - tier: 5, - }); - // ユーザの作成(A) - const userA = await makeTestUser(source, { - account_id: tier5AccountsA.account.id, - }); - // ユーザの作成(B) - const userB = await makeTestUser(source, { - account_id: tier5AccountsB.account.id, - }); - const context = makeContext(tier5AccountsA.admin.external_id); - // 第一階層~第五階層までのライセンス注文を作成 - await createLicenseOrder( + // ユーザの作成 + const user = await makeTestUser(source, { + account_id: tier5Accounts.account.id, + }); + // ライセンス作成 + await createLicense( source, - tier2Accounts[0].account.id, - tier1Accounts[0].account.id, - 100, - 'PO001', - ); - await createLicenseOrder( - source, - tier3Accounts[0].account.id, - tier2Accounts[0].account.id, - 90, - 'PO002', - ); - await createLicenseOrder( - source, - tier4Accounts[0].account.id, - tier3Accounts[0].account.id, - 80, - 'PO003', - ); - await createLicenseOrder( - source, - tier5AccountsA.account.id, - tier4Accounts[0].account.id, - 40, - 'PO004A', - ); - await createLicenseOrder( - source, - tier5AccountsB.account.id, - tier4Accounts[0].account.id, - 40, - 'PO004B', - ); - - // 第一階層~第五階層までのライセンス注文を発行済みにする - await service.issueLicense( - context, - tier2Accounts[0].account.id, - tier1Accounts[0].users[0].external_id, 1, - 'PO001', + new Date(), + tier5Accounts.account.id, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + user.id, + null, + null, ); - await service.issueLicense( - context, - tier3Accounts[0].account.id, - tier2Accounts[0].users[0].external_id, - 2, - 'PO002', - ); - await service.issueLicense( - context, - tier4Accounts[0].account.id, - tier3Accounts[0].users[0].external_id, - 3, - 'PO003', - ); - await service.issueLicense( - context, - tier5AccountsA.account.id, - tier4Accounts[0].users[0].external_id, - 4, - 'PO004A', - ); - await service.issueLicense( - context, - tier5AccountsB.account.id, - tier4Accounts[0].users[0].external_id, - 4, - 'PO004B', - ); - // アカウントAのライセンスを取得する - const licensesA = await getLicenses(source, tier5AccountsA.account.id); - // アカウントAのライセンスを取得する - const licensesB = await getLicenses(source, tier5AccountsB.account.id); - - const usersService = module.get(UsersService); - // アカウントAのライセンスを割り当てる - await usersService.allocateLicense( - context, - userA?.id ?? 0, - licensesA[0].id, - ); - // アカウントBのライセンスを割り当てる - await usersService.allocateLicense( - context, - userB?.id ?? 0, - licensesB[0].id, + await createLicenseAllocationHistory( + source, + 1, + user.id, + 1, + tier5Accounts.account.id, + 'NONE', ); // ADB2Cユーザーの削除成功 @@ -5956,77 +5403,32 @@ describe('deleteAccountAndData', () => { overrideBlobstorageService(service, { deleteContainer: jest.fn(), }); - // アカウント情報の削除 await service.deleteAccountAndData( context, - tier5AccountsA.admin.external_id, - tier5AccountsA.account.id, + tier5Accounts.admin.external_id, + tier5Accounts.account.id, ); + // DB内が想定通りになっているか確認 - // 第五階層のアカウントAが削除されていること - const accountRecordA = await getAccount(source, tier5AccountsA.account.id); - expect(accountRecordA).toBe(null); - const userRecordA = await getUser(source, userA?.id ?? 0); - expect(userRecordA).toBe(null); + const accountRecord = await getAccount(source, tier5Accounts.account.id); + expect(accountRecord).toBe(null); - // 第五階層のアカウントAのライセンスが削除されていること - const licenseRecordA = await source.manager.find(License, { - where: { account_id: tier5AccountsA.account.id }, - }); - expect(licenseRecordA.length).toBe(0); - // 第五階層のアカウントAのライセンス注文履歴が削除されていること - const licenseOrderRecordA = await source.manager.find(LicenseOrder, { - where: { from_account_id: tier5AccountsA.account.id }, - }); - expect(licenseOrderRecordA.length).toBe(0); - // 第五階層のアカウントAのライセンス割り当て履歴が削除されていること - const LicenseAllocationHistoryRecordA = await source.manager.find( - LicenseAllocationHistory, - { - where: { account_id: tier5AccountsA.account.id }, - }, - ); - expect(LicenseAllocationHistoryRecordA.length).toBe(0); - - // 第五階層のアカウントBは削除されていないこと - const accountRecordB = await getAccount(source, tier5AccountsB.account.id); - expect(accountRecordB?.id).not.toBeNull(); - const userRecordB = await getUser(source, userB?.id ?? 0); - expect(userRecordB).not.toBeNull(); - // 第五階層のアカウントBのライセンスが削除されていないこと - const licenseRecordB = await source.manager.find(License, { - where: { account_id: tier5AccountsB.account.id }, - }); - expect(licenseRecordB.length).not.toBe(0); - // 第五階層のアカウントBのライセンス注文履歴が削除されていないこと - const licenseOrderRecordB = await source.manager.find(LicenseOrder, { - where: { from_account_id: tier5AccountsB.account.id }, - }); - expect(licenseOrderRecordB.length).not.toBe(0); - // 第五階層のアカウントBのライセンス割り当て履歴が削除されていないこと - const LicenseAllocationHistoryRecordB = await source.manager.find( - LicenseAllocationHistory, - { - where: { account_id: tier5AccountsB.account.id }, - }, - ); - expect(LicenseAllocationHistoryRecordB.length).not.toBe(0); + const userRecord = await getUser(source, user.id); + expect(userRecord).toBe(null); const UserArchive = await getUserArchive(source); expect(UserArchive.length).toBe(2); const LicenseArchive = await getLicenseArchive(source); - expect(LicenseArchive.length).toBe(40); + expect(LicenseArchive.length).toBe(1); const LicenseAllocationHistoryArchive = await getLicenseAllocationHistoryArchive(source); expect(LicenseAllocationHistoryArchive.length).toBe(1); }); it('アカウントの削除に失敗した場合はエラーを返す', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const loggerSpy = jest.spyOn(service['logger'], 'log').mockImplementation(); // 第五階層のアカウント作成 @@ -6083,14 +5485,12 @@ describe('deleteAccountAndData', () => { // DB内が削除されていないことを確認 const accountRecord = await getAccount(source, tier5Accounts.account.id); - expect(accountRecord?.id).not.toBeNull(); - const userRecord = await getUser(source, user?.id ?? 0); - expect(userRecord?.id).not.toBeNull(); + expect(accountRecord.id).not.toBeNull(); + const userRecord = await getUser(source, user.id); + expect(userRecord.id).not.toBeNull(); }); it('ADB2Cユーザーの削除失敗時は、MANUAL_RECOVERY_REQUIREDを出して処理続行', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const loggerSpy = jest.spyOn(service['logger'], 'log').mockImplementation(); // 第五階層のアカウント作成 @@ -6138,13 +5538,11 @@ describe('deleteAccountAndData', () => { // DB内が想定通りになっているか確認 const accountRecord = await getAccount(source, tier5Accounts.account.id); expect(accountRecord).toBe(null); - const userRecord = await getUser(source, user?.id ?? 0); + const userRecord = await getUser(source, user.id); expect(userRecord).toBe(null); }); it('blobstorageコンテナを削除で失敗した場合は、MANUAL_RECOVERY_REQUIRED出して正常終了', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(AccountsService); const loggerSpy = jest.spyOn(service['logger'], 'log').mockImplementation(); @@ -6193,140 +5591,7 @@ describe('deleteAccountAndData', () => { // DB内が想定通りになっているか確認 const accountRecord = await getAccount(source, tier5Accounts.account.id); expect(accountRecord).toBe(null); - const userRecord = await getUser(source, user?.id ?? 0); + const userRecord = await getUser(source, user.id); expect(userRecord).toBe(null); }); }); -describe('getAccountInfoMinimalAccess', () => { - let source: DataSource | null = null; - beforeEach(async () => { - source = new DataSource({ - type: 'sqlite', - database: ':memory:', - logging: false, - entities: [__dirname + '/../../**/*.entity{.ts,.js}'], - synchronize: true, // trueにすると自動的にmigrationが行われるため注意 - }); - return source.initialize(); - }); - - afterEach(async () => { - if (!source) return; - await source.destroy(); - source = null; - }); - it('IDトークンのsub情報からアカウントの階層情報を取得できること(第五階層)', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - const service = module.get(AccountsService); - // 第五階層のアカウント作成 - const { account, admin } = await makeTestAccount(source, { - tier: 5, - }); - const context = makeContext(admin.external_id); - - // 作成したデータを確認 - { - const tier5Account = await getAccount(source, account.id); - expect(tier5Account?.tier).toBe(5); - } - - const tier = await service.getAccountInfoMinimalAccess( - context, - admin.external_id, - ); - - //実行結果を確認 - expect(tier).toBe(5); - }); - it('IDトークンのSub情報からアカウントの階層情報を取得できること(第四階層)', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - const service = module.get(AccountsService); - // 第四階層のアカウント作成 - const { account, admin } = await makeTestAccount(source, { - tier: 4, - }); - const context = makeContext(admin.external_id); - - // 作成したデータを確認 - { - const tier5Account = await getAccount(source, account.id); - expect(tier5Account?.tier).toBe(4); - } - - const tier = await service.getAccountInfoMinimalAccess( - context, - admin.external_id, - ); - - //実行結果を確認 - expect(tier).toBe(4); - }); - it('対象のユーザーが存在しない場合、400エラーとなること', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - const service = module.get(AccountsService); - // 第四階層のアカウント作成 - const { account, admin } = await makeTestAccount(source, { - tier: 4, - }); - const context = makeContext(admin.external_id); - - // 作成したデータを確認 - { - const tier5Account = await getAccount(source, account.id); - expect(tier5Account?.tier).toBe(4); - } - - try { - await service.getAccountInfoMinimalAccess(context, 'fail_external_id'); - } catch (e) { - if (e instanceof HttpException) { - expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST); - expect(e.getResponse()).toEqual(makeErrorResponse('E010204')); - } else { - fail(); - } - } - }); - it('DBアクセスに失敗した場合、500エラーとなること', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - const service = module.get(AccountsService); - // 第四階層のアカウント作成 - const { account, admin } = await makeTestAccount(source, { - tier: 4, - }); - const context = makeContext(admin.external_id); - - // 作成したデータを確認 - { - const tier5Account = await getAccount(source, account.id); - expect(tier5Account?.tier).toBe(4); - } - - //DBアクセスに失敗するようにする - const usersRepositoryService = module.get( - UsersRepositoryService, - ); - usersRepositoryService.findUserByExternalId = jest - .fn() - .mockRejectedValue('DB failed'); - - try { - await service.getAccountInfoMinimalAccess(context, admin.external_id); - } catch (e) { - if (e instanceof HttpException) { - expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR); - expect(e.getResponse()).toEqual(makeErrorResponse('E009999')); - } else { - fail(); - } - } - }); -}); diff --git a/dictation_server/src/features/accounts/accounts.service.ts b/dictation_server/src/features/accounts/accounts.service.ts index 985b764..0587575 100644 --- a/dictation_server/src/features/accounts/accounts.service.ts +++ b/dictation_server/src/features/accounts/accounts.service.ts @@ -33,7 +33,6 @@ import { GetPartnersResponse, PostWorktypeOptionItem, Author, - Partner, } from './types/types'; import { DateWithZeroTime, @@ -66,15 +65,12 @@ import { import { WorktypesRepositoryService } from '../../repositories/worktypes/worktypes.repository.service'; import { WorktypeIdAlreadyExistsError, - WorktypeIdInUseError, WorktypeIdMaxCountError, WorktypeIdNotFoundError, } from '../../repositories/worktypes/errors/types'; @Injectable() export class AccountsService { - private readonly mailFrom = - this.configService.getOrThrow('MAIL_FROM'); constructor( private readonly accountRepository: AccountsRepositoryService, private readonly licensesRepository: LicensesRepositoryService, @@ -260,6 +256,9 @@ export class AccountsService { } try { + // メールの送信元を取得 + const from = this.configService.get('MAIL_FROM') ?? ''; + // メールの内容を構成 const { subject, text, html } = await this.sendgridService.createMailContentFromEmailConfirm( @@ -273,7 +272,7 @@ export class AccountsService { await this.sendgridService.sendMail( context, email, - this.mailFrom, + from, subject, text, html, @@ -394,7 +393,7 @@ export class AccountsService { userInfo.account_id, ); - let parentInfo: Account | undefined; + let parentInfo: Account; if (accountInfo.parent_account_id) { parentInfo = await this.accountRepository.findAccountById( accountInfo.parent_account_id, @@ -481,20 +480,14 @@ export class AccountsService { const { account_id } = await this.usersRepository.findUserByExternalId( externalId, ); - const { name, userGroupMembers } = - await this.userGroupsRepository.getTypistGroup( - account_id, - typistGroupId, - ); - if (!userGroupMembers) { - throw new TypistGroupNotExistError( - `Typist Group is not exist. typistGroupId: ${typistGroupId}`, - ); - } + const userGroup = await this.userGroupsRepository.getTypistGroup( + account_id, + typistGroupId, + ); return { - typistGroupName: name, - typistIds: userGroupMembers.map((x) => x.user_id), + typistGroupName: userGroup.name, + typistIds: userGroup.userGroupMembers.map((x) => x.user_id), }; } catch (e) { this.logger.error(`error=${e}`); @@ -547,11 +540,6 @@ export class AccountsService { const typists = typistUsers.map((x) => { const user = adb2cUsers.find((adb2c) => adb2c.id === x.external_id); - if (!user) { - throw new Error( - `user not found. externalId: ${x.external_id}, userId: ${x.id}`, - ); - } return { id: x.id, name: user.displayName, @@ -597,11 +585,6 @@ export class AccountsService { ); const authors = authorUsers.map((x) => { - if (!x.author_id) { - throw new Error( - `author_id is Not Found. externalId: ${x.external_id}, userId: ${x.id}`, - ); - } return { id: x.id, authorId: x.author_id, @@ -717,8 +700,8 @@ export class AccountsService { creatorAccountTier + 1, externalUser.sub, USER_ROLES.NONE, - undefined, - undefined, + null, + null, ); account = newAccount; user = adminUser; @@ -759,6 +742,7 @@ export class AccountsService { } try { + const from = this.configService.get('MAIL_FROM') || ''; const { subject, text, html } = await this.sendgridService.createMailContentFromEmailConfirmForNormalUser( account.id, @@ -768,7 +752,7 @@ export class AccountsService { await this.sendgridService.sendMail( context, email, - this.mailFrom, + from, subject, text, html, @@ -851,19 +835,11 @@ export class AccountsService { // 各子アカウントのShortageを算出してreturn用の変数にマージする const childrenPartnerLicenses: PartnerLicenseInfo[] = []; for (const childPartnerLicenseFromRepository of getPartnerLicenseResult.childPartnerLicensesFromRepository) { - const { allocatableLicenseWithMargin, expiringSoonLicense } = - childPartnerLicenseFromRepository; - let childShortage: number = 0; + let childShortage; if (childPartnerLicenseFromRepository.tier === TIERS.TIER5) { - if ( - allocatableLicenseWithMargin === undefined || - expiringSoonLicense === undefined - ) { - throw new Error( - `Tier5 account has no allocatableLicenseWithMargin or expiringSoonLicense. accountId: ${accountId}`, - ); - } - childShortage = allocatableLicenseWithMargin - expiringSoonLicense; + childShortage = + childPartnerLicenseFromRepository.allocatableLicenseWithMargin - + childPartnerLicenseFromRepository.expiringSoonLicense; } else { childShortage = childPartnerLicenseFromRepository.stockLicense - @@ -930,13 +906,13 @@ export class AccountsService { licenseOrder.issued_at !== null ? new Date(licenseOrder.issued_at) .toISOString() - .substring(0, 10) + .substr(0, 10) .replace(/-/g, '/') - : undefined, + : null, numberOfOrder: licenseOrder.quantity, orderDate: new Date(licenseOrder.ordered_at) .toISOString() - .substring(0, 10) + .substr(0, 10) .replace(/-/g, '/'), poNumber: licenseOrder.po_number, status: licenseOrder.status, @@ -1441,81 +1417,6 @@ export class AccountsService { } } - /** - * ワークタイプを削除します - * @param context - * @param externalId - * @param id - * @returns worktype - */ - async deleteWorktype( - context: Context, - externalId: string, - id: number, - ): Promise { - this.logger.log( - `[IN] [${context.trackingId}] ${this.deleteWorktype.name} | params: { ` + - `externalId: ${externalId}, ` + - `id: ${id} };`, - ); - - try { - // 外部IDをもとにユーザー情報を取得する - const { account, account_id: accountId } = - await this.usersRepository.findUserByExternalId(externalId); - - if (!account) { - throw new AccountNotFoundError( - `account not found. externalId: ${externalId}`, - ); - } - - // ワークタイプを削除する - await this.worktypesRepository.deleteWorktype(accountId, id); - } catch (e) { - this.logger.error(`error=${e}`); - if (e instanceof Error) { - switch (e.constructor) { - case UserNotFoundError: - throw new HttpException( - makeErrorResponse('E010204'), - HttpStatus.BAD_REQUEST, - ); - case AccountNotFoundError: - throw new HttpException( - makeErrorResponse('E010501'), - HttpStatus.BAD_REQUEST, - ); - // 内部IDで指定されたWorktypeが存在しない場合は400エラーを返す - case WorktypeIdNotFoundError: - throw new HttpException( - makeErrorResponse('E011003'), - HttpStatus.BAD_REQUEST, - ); - // 内部IDで指定されたWorktypeがWorkflowで使用中の場合は400エラーを返す - case WorktypeIdInUseError: - throw new HttpException( - makeErrorResponse('E011004'), - HttpStatus.BAD_REQUEST, - ); - default: - throw new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - throw new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } finally { - this.logger.log( - `[OUT] [${context.trackingId}] ${this.deleteWorktype.name}`, - ); - } - } - /** * ワークタイプに紐づいたオプションアイテム一覧を取得します * @param context @@ -1657,7 +1558,7 @@ export class AccountsService { async updateActiveWorktype( context: Context, externalId: string, - id: number | undefined, + id: number, ): Promise { this.logger.log( `[IN] [${context.trackingId}] ${this.updateActiveWorktype.name} | params: { ` + @@ -1724,40 +1625,34 @@ export class AccountsService { const { account_id: accountId } = await this.usersRepository.findUserByExternalId(externalId); - const partnersRecords = await this.accountRepository.getPartners( + const partners = await this.accountRepository.getPartners( accountId, limit, offset, ); // DBから取得したユーザーの外部IDをもとにADB2Cからユーザーを取得する - let externalIds = partnersRecords.partnersInfo.map( + let externalIds = partners.partnersInfo.map( (x) => x.primaryAccountExternalId, ); externalIds = externalIds.filter((item) => item !== undefined); const adb2cUsers = await this.adB2cService.getUsers(context, externalIds); // DBから取得した情報とADB2Cから取得した情報をマージ - const partners = partnersRecords.partnersInfo.map((db): Partner => { + const response = partners.partnersInfo.map((db) => { const adb2cUser = adb2cUsers.find( (adb2c) => db.primaryAccountExternalId === adb2c.id, ); - if (!adb2cUser) { - throw new Error( - `adb2c user not found. externalId: ${db.primaryAccountExternalId}`, - ); - } - const primaryAdmin = adb2cUser.displayName; - const mail = adb2cUser.identities?.find( - (identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS, - )?.issuerAssignedId; - if (!mail) { - throw new Error( - `adb2c user mail not found. externalId: ${db.primaryAccountExternalId}`, - ); + let primaryAdmin = undefined; + let mail = undefined; + if (adb2cUser) { + primaryAdmin = adb2cUser.displayName; + mail = adb2cUser.identities.find( + (identity) => + identity.signInType === ADB2C_SIGN_IN_TYPE.EAMILADDRESS, + ).issuerAssignedId; } - return { name: db.name, tier: db.tier, @@ -1770,15 +1665,17 @@ export class AccountsService { }); return { - total: partnersRecords.total, - partners: partners, + total: partners.total, + partners: response, }; } catch (e) { this.logger.error(`error=${e}`); - throw new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ); + if (e instanceof Error) { + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } } finally { this.logger.log(`[OUT] [${context.trackingId}] ${this.getPartners.name}`); } @@ -1946,61 +1843,4 @@ export class AccountsService { `[OUT] [${context.trackingId}] ${this.deleteAccountAndData.name}`, ); } - - /** - * IDトークンのsubからアカウントの階層情報を取得します - * @param context - * @param externalId - * @returns account info minimal access - */ - async getAccountInfoMinimalAccess( - context: Context, - externalId: string, - ): Promise { - this.logger.log( - `[IN] [${context.trackingId}] ${this.getAccountInfoMinimalAccess.name} | params: { externalId: ${externalId} };`, - ); - - try { - const { account } = await this.usersRepository.findUserByExternalId( - externalId, - ); - if (!account) { - throw new AccountNotFoundError( - `Account not found. externalId: ${externalId}`, - ); - } - - return account.tier; - } catch (e) { - this.logger.error(`[${context.trackingId}] error=${e}`); - if (e instanceof Error) { - switch (e.constructor) { - case UserNotFoundError: - throw new HttpException( - makeErrorResponse('E010204'), - HttpStatus.BAD_REQUEST, - ); - case AccountNotFoundError: - throw new HttpException( - makeErrorResponse('E010501'), - HttpStatus.BAD_REQUEST, - ); - default: - throw new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - throw new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } finally { - this.logger.log( - `[OUT] [${context.trackingId}] ${this.getAccountInfoMinimalAccess.name}`, - ); - } - } } diff --git a/dictation_server/src/features/accounts/test/accounts.service.mock.ts b/dictation_server/src/features/accounts/test/accounts.service.mock.ts index 0330884..920b877 100644 --- a/dictation_server/src/features/accounts/test/accounts.service.mock.ts +++ b/dictation_server/src/features/accounts/test/accounts.service.mock.ts @@ -30,7 +30,7 @@ export type LicensesRepositoryMockValue = { orderHistories: LicenseOrder[]; } | Error; - issueLicense: void | Error; + issueLicense: undefined | Error; }; export type UsersRepositoryMockValue = { findUserById: User | Error; @@ -61,12 +61,10 @@ export type ConfigMockValue = { get: string | Error; }; export type AccountsRepositoryMockValue = { - getLicenseSummaryInfo: - | { - licenseSummary: LicenseSummaryInfo; - isStorageAvailable: boolean; - } - | Error; + getLicenseSummaryInfo: { + licenseSummary: LicenseSummaryInfo; + isStorageAvailable: boolean; + }; createAccount: { newAccount: Account; adminUser: User } | Error; }; @@ -183,7 +181,18 @@ export const makeLicensesRepositoryMock = ( issueLicense: issueLicense instanceof Error ? jest.fn, []>().mockRejectedValue(issueLicense) - : jest.fn, []>().mockResolvedValue(issueLicense), + : jest + .fn< + Promise<{ + context: Context; + orderedAccountId: number; + myAccountId: number; + tier: number; + poNumber: string; + }>, + [] + >() + .mockResolvedValue(issueLicense), }; }; export const makeUsersRepositoryMock = (value: UsersRepositoryMockValue) => { @@ -346,7 +355,7 @@ export const makeDefaultAccountsRepositoryMockValue = user.created_by = 'test'; user.created_at = new Date(); user.updated_by = null; - user.updated_at = new Date(); + user.updated_at = null; return { getLicenseSummaryInfo: { licenseSummary: licenseSummaryInfo, @@ -376,7 +385,7 @@ export const makeDefaultUsersRepositoryMockValue = user.created_by = 'test'; user.created_at = new Date(); user.updated_by = null; - user.updated_at = new Date(); + user.updated_at = null; const typists: User[] = []; typists.push( @@ -425,7 +434,7 @@ export const makeDefaultUserGroupsRepositoryMockValue = user.created_by = 'test'; user.created_at = new Date(); user.updated_by = null; - user.updated_at = new Date(); + user.updated_at = null; return { getUserGroups: [ @@ -435,10 +444,6 @@ export const makeDefaultUserGroupsRepositoryMockValue = name: 'GroupA', created_by: 'test', updated_by: 'test', - created_at: new Date(), - deleted_at: null, - updated_at: null, - userGroupMembers: null, }, { id: 2, @@ -446,10 +451,6 @@ export const makeDefaultUserGroupsRepositoryMockValue = name: 'GroupB', created_by: 'test', updated_by: 'test', - created_at: new Date(), - deleted_at: null, - updated_at: null, - userGroupMembers: null, }, ], }; diff --git a/dictation_server/src/features/accounts/test/utility.ts b/dictation_server/src/features/accounts/test/utility.ts index 70cfc3a..a91a8d4 100644 --- a/dictation_server/src/features/accounts/test/utility.ts +++ b/dictation_server/src/features/accounts/test/utility.ts @@ -23,14 +23,14 @@ export const getSortCriteria = async (dataSource: DataSource) => { export const createLicense = async ( datasource: DataSource, licenseId: number, - expiry_date: Date | null, + expiry_date: Date, accountId: number, type: string, status: string, - allocated_user_id: number | null, - order_id: number | null, - deleted_at: Date | null, - delete_order_id: number | null, + allocated_user_id: number, + order_id: number, + deleted_at: Date, + delete_order_id: number, ): Promise => { const { identifiers } = await datasource.getRepository(License).insert({ id: licenseId, @@ -54,7 +54,7 @@ export const createLicense = async ( export const createLicenseSetExpiryDateAndStatus = async ( datasource: DataSource, accountId: number, - expiryDate: Date | null, + expiryDate: Date, status: string, ): Promise => { const { identifiers } = await datasource.getRepository(License).insert({ @@ -171,21 +171,19 @@ export const createOptionItems = async ( datasource: DataSource, worktypeId: number, ): Promise => { - const optionItems: OptionItem[] = []; + const optionItems = []; for (let i = 0; i < 10; i++) { - const optionItem = new OptionItem(); - { - optionItem.worktype_id = worktypeId; - optionItem.item_label = ''; - optionItem.default_value_type = OPTION_ITEM_VALUE_TYPE.DEFAULT; - optionItem.initial_value = ''; - optionItem.created_by = 'test_runner'; - optionItem.created_at = new Date(); - optionItem.updated_by = 'updater'; - optionItem.updated_at = new Date(); - } - optionItems.push(optionItem); + optionItems.push({ + worktype_id: worktypeId, + item_label: '', + default_value_type: OPTION_ITEM_VALUE_TYPE.DEFAULT, + initial_value: '', + created_by: 'test_runner', + created_at: new Date(), + updated_by: 'updater', + updated_at: new Date(), + }); } await datasource.getRepository(OptionItem).insert(optionItems); diff --git a/dictation_server/src/features/accounts/types/types.ts b/dictation_server/src/features/accounts/types/types.ts index ccb45d9..47f6536 100644 --- a/dictation_server/src/features/accounts/types/types.ts +++ b/dictation_server/src/features/accounts/types/types.ts @@ -327,8 +327,8 @@ export class GetOrderHistoriesRequest { export class LicenseOrder { @ApiProperty({ description: '注文日付' }) orderDate: string; - @ApiProperty({ description: '発行日付', required: false }) - issueDate?: string; + @ApiProperty({ description: '発行日付' }) + issueDate: string; @ApiProperty({ description: '注文数' }) numberOfOrder: number; @ApiProperty({ description: 'POナンバー' }) @@ -497,16 +497,6 @@ export class UpdateWorktypeRequestParam { id: number; } -export class DeleteWorktypeRequestParam { - @ApiProperty({ description: 'Worktypeの内部ID' }) - @Type(() => Number) - @IsInt() - @Min(0) - id: number; -} - -export class DeleteWorktypeResponse {} - export class PostActiveWorktypeRequest { @ApiProperty({ required: false, diff --git a/dictation_server/src/features/auth/auth.controller.spec.ts b/dictation_server/src/features/auth/auth.controller.spec.ts index 93a7999..b6e162a 100644 --- a/dictation_server/src/features/auth/auth.controller.spec.ts +++ b/dictation_server/src/features/auth/auth.controller.spec.ts @@ -5,19 +5,12 @@ import { makeAdB2cServiceMock, makeDefaultAdB2cMockValue, } from './test/auth.service.mock'; -import { ConfigModule } from '@nestjs/config'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - imports: [ - ConfigModule.forRoot({ - envFilePath: ['.env.local', '.env'], - isGlobal: true, - }), - ], controllers: [AuthController], providers: [AuthService], }) diff --git a/dictation_server/src/features/auth/auth.controller.ts b/dictation_server/src/features/auth/auth.controller.ts index 4d8d33b..029e60e 100644 --- a/dictation_server/src/features/auth/auth.controller.ts +++ b/dictation_server/src/features/auth/auth.controller.ts @@ -67,18 +67,6 @@ export class AuthController { const context = makeContext(uuidv4()); - // 同意済み利用規約バージョンが最新かチェック - const isAcceptedLatestVersion = - await this.authService.isAcceptedLatestVersion(context, idToken); - - // 最新でなければエラー - if (!isAcceptedLatestVersion) { - throw new HttpException( - makeErrorResponse('E010209'), - HttpStatus.UNAUTHORIZED, - ); - } - const refreshToken = await this.authService.generateRefreshToken( context, idToken, diff --git a/dictation_server/src/features/auth/auth.module.ts b/dictation_server/src/features/auth/auth.module.ts index 2e3dc4c..6db0c3f 100644 --- a/dictation_server/src/features/auth/auth.module.ts +++ b/dictation_server/src/features/auth/auth.module.ts @@ -4,14 +4,8 @@ import { AdB2cModule } from '../../gateways/adb2c/adb2c.module'; import { UsersRepositoryModule } from '../../repositories/users/users.repository.module'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; -import { TermsRepositoryModule } from '../../repositories/terms/terms.repository.module'; @Module({ - imports: [ - ConfigModule, - AdB2cModule, - UsersRepositoryModule, - TermsRepositoryModule, - ], + imports: [ConfigModule, AdB2cModule, UsersRepositoryModule], controllers: [AuthController], providers: [AuthService], }) diff --git a/dictation_server/src/features/auth/auth.service.spec.ts b/dictation_server/src/features/auth/auth.service.spec.ts index af11c93..a14268a 100644 --- a/dictation_server/src/features/auth/auth.service.spec.ts +++ b/dictation_server/src/features/auth/auth.service.spec.ts @@ -3,22 +3,13 @@ import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { makeAuthServiceMock, makeDefaultAdB2cMockValue, - makeDefaultConfigValue, makeDefaultGetPublicKeyFromJwk, } from './test/auth.service.mock'; -import { DataSource } from 'typeorm'; -import { makeContext } from '../../common/log'; -import { makeTestingModule } from '../../common/test/modules'; -import { makeTestAccount } from '../../common/test/utility'; -import { AuthService } from './auth.service'; -import { createTermInfo } from './test/utility'; -import { v4 as uuidv4 } from 'uuid'; describe('AuthService', () => { it('IDトークンの検証とペイロードの取得に成功する', async () => { const adb2cParam = makeDefaultAdB2cMockValue(); - const configMockValue = makeDefaultConfigValue(); - const service = await makeAuthServiceMock(adb2cParam, configMockValue); + const service = await makeAuthServiceMock(adb2cParam); //JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替 service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk; const token = @@ -29,8 +20,7 @@ describe('AuthService', () => { it('IDトークンの形式が不正な場合、形式不正エラーとなる。', async () => { const adb2cParam = makeDefaultAdB2cMockValue(); - const configMockValue = makeDefaultConfigValue(); - const service = await makeAuthServiceMock(adb2cParam, configMockValue); + const service = await makeAuthServiceMock(adb2cParam); const token = 'invalid.id.token'; await expect(service.getVerifiedIdToken(token)).rejects.toEqual( @@ -40,8 +30,7 @@ describe('AuthService', () => { it('IDトークンの有効期限が切れている場合、有効期限切れエラーとなる。', async () => { const adb2cParam = makeDefaultAdB2cMockValue(); - const configMockValue = makeDefaultConfigValue(); - const service = await makeAuthServiceMock(adb2cParam, configMockValue); + const service = await makeAuthServiceMock(adb2cParam); //JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替 service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk; const token = @@ -54,8 +43,7 @@ describe('AuthService', () => { it('IDトークンが開始日より前の場合、開始前エラーとなる。', async () => { const adb2cParam = makeDefaultAdB2cMockValue(); - const configMockValue = makeDefaultConfigValue(); - const service = await makeAuthServiceMock(adb2cParam, configMockValue); + const service = await makeAuthServiceMock(adb2cParam); //JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替 service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk; const token = @@ -68,8 +56,7 @@ describe('AuthService', () => { it('IDトークンの署名が不正な場合、署名不正エラーとなる。', async () => { const adb2cParam = makeDefaultAdB2cMockValue(); - const configMockValue = makeDefaultConfigValue(); - const service = await makeAuthServiceMock(adb2cParam, configMockValue); + const service = await makeAuthServiceMock(adb2cParam); const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdXNlciIsInN1YiI6InN1YiIsImF1ZCI6ImF1ZCIsIm5vbmNlIjoiZGVmYXVsdE5vbmNlIiwiaWF0IjoxMDAwMDAwMDAwLCJhdXRoX3RpbWUiOjEwMDAwMDAwMDAsImVtYWlscyI6WyJ4eHhAeHguY29tIl0sInRmcCI6InNpZ25pbl91c2VyZmxvdyJ9.sign'; @@ -80,8 +67,7 @@ describe('AuthService', () => { it('IDトークンの発行元が想定と異なる場合、発行元不正エラーとなる。', async () => { const adb2cParam = makeDefaultAdB2cMockValue(); - const configMockValue = makeDefaultConfigValue(); - const service = await makeAuthServiceMock(adb2cParam, configMockValue); + const service = await makeAuthServiceMock(adb2cParam); //JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替 service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk; const token = @@ -94,9 +80,8 @@ describe('AuthService', () => { it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。(メタデータ)', async () => { const adb2cParam = makeDefaultAdB2cMockValue(); - const configMockValue = makeDefaultConfigValue(); adb2cParam.getMetaData = new Error('failed get metadata'); - const service = await makeAuthServiceMock(adb2cParam, configMockValue); + const service = await makeAuthServiceMock(adb2cParam); const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA'; @@ -109,9 +94,8 @@ describe('AuthService', () => { }); it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。(キーセット)', async () => { const adb2cParam = makeDefaultAdB2cMockValue(); - const configMockValue = makeDefaultConfigValue(); adb2cParam.getSignKeySets = new Error('failed get keyset'); - const service = await makeAuthServiceMock(adb2cParam, configMockValue); + const service = await makeAuthServiceMock(adb2cParam); const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA'; @@ -125,11 +109,10 @@ describe('AuthService', () => { it('Azure ADB2Cから取得した鍵が一致しない場合、エラーとなる。', async () => { const adb2cParam = makeDefaultAdB2cMockValue(); - const configMockValue = makeDefaultConfigValue(); adb2cParam.getSignKeySets = [ { kid: 'invalid', kty: 'RSA', nbf: 0, use: 'sig', e: '', n: '' }, ]; - const service = await makeAuthServiceMock(adb2cParam, configMockValue); + const service = await makeAuthServiceMock(adb2cParam); const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA'; @@ -142,140 +125,6 @@ describe('AuthService', () => { }); }); -describe('checkIsAcceptedLatestVersion', () => { - let source: DataSource | null = null; - beforeEach(async () => { - source = new DataSource({ - type: 'sqlite', - database: ':memory:', - logging: false, - entities: [__dirname + '/../../**/*.entity{.ts,.js}'], - synchronize: true, // trueにすると自動的にmigrationが行われるため注意 - }); - return source.initialize(); - }); - - afterEach(async () => { - if (!source) return; - await source.destroy(); - source = null; - }); - it('同意済み利用規約バージョンが最新のときにチェックが通ること(第五)', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - const service = module.get(AuthService); - const { admin } = await makeTestAccount(source, { - tier: 5, - }); - const context = makeContext(uuidv4()); - - const idToken = { - emails: [], - sub: admin.external_id, - exp: 0, - iat: 0, - }; - - await createTermInfo(source, 'EULA', '1.0'); - await createTermInfo(source, 'DPA', '1.0'); - const result = await service.isAcceptedLatestVersion(context, idToken); - expect(result).toBe(true); - }); - - it('同意済み利用規約バージョンが最新のときにチェックが通ること(第一~第四)', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - const service = module.get(AuthService); - const { admin } = await makeTestAccount(source, { - tier: 4, - }); - const context = makeContext(uuidv4()); - - const idToken = { - emails: [], - sub: admin.external_id, - exp: 0, - iat: 0, - }; - - await createTermInfo(source, 'EULA', '1.0'); - await createTermInfo(source, 'DPA', '1.0'); - const result = await service.isAcceptedLatestVersion(context, idToken); - expect(result).toBe(true); - }); - - it('同意済み利用規約バージョンが最新でないときにチェックが通らないこと(第五)', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - const service = module.get(AuthService); - const { admin } = await makeTestAccount(source, { - tier: 5, - }); - const context = makeContext(uuidv4()); - - const idToken = { - emails: [], - sub: admin.external_id, - exp: 0, - iat: 0, - }; - - await createTermInfo(source, 'EULA', '1.1'); - await createTermInfo(source, 'DPA', '1.0'); - const result = await service.isAcceptedLatestVersion(context, idToken); - expect(result).toBe(false); - }); - - it('同意済み利用規約(EULA)バージョンが最新でないときにチェックが通らないこと(第一~第四)', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - const service = module.get(AuthService); - const { admin } = await makeTestAccount(source, { - tier: 4, - }); - const context = makeContext(uuidv4()); - - const idToken = { - emails: [], - sub: admin.external_id, - exp: 0, - iat: 0, - }; - - await createTermInfo(source, 'EULA', '1.1'); - await createTermInfo(source, 'DPA', '1.0'); - const result = await service.isAcceptedLatestVersion(context, idToken); - expect(result).toBe(false); - }); - - it('同意済み利用規約バージョン(DPA)が最新でないときにチェックが通らないこと(第一~第四)', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - const service = module.get(AuthService); - const { admin } = await makeTestAccount(source, { - tier: 4, - }); - const context = makeContext(uuidv4()); - - const idToken = { - emails: [], - sub: admin.external_id, - exp: 0, - iat: 0, - }; - - await createTermInfo(source, 'EULA', '1.0'); - await createTermInfo(source, 'DPA', '1.1'); - const result = await service.isAcceptedLatestVersion(context, idToken); - expect(result).toBe(false); - }); -}); - const idTokenPayload = { exp: 9000000000, nbf: 1000000000, diff --git a/dictation_server/src/features/auth/auth.service.ts b/dictation_server/src/features/auth/auth.service.ts index 1454960..a3198d7 100644 --- a/dictation_server/src/features/auth/auth.service.ts +++ b/dictation_server/src/features/auth/auth.service.ts @@ -17,7 +17,7 @@ import { RefreshToken, isIDToken, } from '../../common/token'; -import { ADMIN_ROLES, TIERS, USER_ROLES } from '../../constants'; +import { ADMIN_ROLES, USER_ROLES } from '../../constants'; import { AdB2cService } from '../../gateways/adb2c/adb2c.service'; import { User } from '../../repositories/users/entity/user.entity'; import { UsersRepositoryService } from '../../repositories/users/users.repository.service'; @@ -25,13 +25,6 @@ import { Context } from '../../common/log'; @Injectable() export class AuthService { - private readonly refreshTokenLifetimeWeb = - this.configService.getOrThrow('REFRESH_TOKEN_LIFETIME_WEB'); - private readonly refreshTokenLifetimeDefault = - this.configService.getOrThrow('REFRESH_TOKEN_LIFETIME_DEFAULT'); - private readonly accessTokenlifetime = this.configService.getOrThrow( - 'ACCESS_TOKEN_LIFETIME_WEB', - ); constructor( private readonly adB2cService: AdB2cService, private readonly configService: ConfigService, @@ -75,7 +68,10 @@ export class AuthService { this.logger.log( `[IN] [${context.trackingId}] ${this.generateRefreshToken.name}`, ); - + const lifetimeWeb = this.configService.get('REFRESH_TOKEN_LIFETIME_WEB'); + const lifetimeDefault = this.configService.get( + 'REFRESH_TOKEN_LIFETIME_DEFAULT', + ); let user: User; // ユーザー情報とユーザーが属しているアカウント情報を取得 try { @@ -110,10 +106,7 @@ export class AuthService { ); } // 要求された環境用トークンの寿命を決定 - const refreshTokenLifetime = - type === 'web' - ? this.refreshTokenLifetimeWeb - : this.refreshTokenLifetimeDefault; + const refreshTokenLifetime = type === 'web' ? lifetimeWeb : lifetimeDefault; const privateKey = getPrivateKey(this.configService); // ユーザーのロールを設定 @@ -172,6 +165,7 @@ export class AuthService { this.logger.log( `[IN] [${context.trackingId}] ${this.generateAccessToken.name}`, ); + const lifetime = this.configService.get('ACCESS_TOKEN_LIFETIME_WEB'); const privateKey = getPrivateKey(this.configService); const pubkey = getPublicKey(this.configService); @@ -194,7 +188,7 @@ export class AuthService { tier: token.tier, userId: token.userId, }, - this.accessTokenlifetime, + lifetime, privateKey, ); @@ -211,14 +205,11 @@ export class AuthService { async getVerifiedIdToken(token: string): Promise { this.logger.log(`[IN] ${this.getVerifiedIdToken.name}`); - let kid: string | undefined = ''; + let kid = ''; try { // JWTトークンのヘッダを見るため一度デコードする const decodedToken = jwt.decode(token, { complete: true }); - kid = decodedToken?.header.kid; - if (!kid) { - throw new Error('kid not found'); - } + kid = decodedToken.header.kid; } catch (e) { this.logger.error(`error=${e}`); throw new HttpException( @@ -340,59 +331,4 @@ export class AuthService { HttpStatus.UNAUTHORIZED, ); }; - - /** - * 同意済み利用規約バージョンが最新かチェック - * @param idToken AzureAD B2Cにより発行されたIDトークン - * @returns boolean - */ - async isAcceptedLatestVersion( - context: Context, - idToken: IDToken, - ): Promise { - this.logger.log( - `[IN] [${context.trackingId}] ${this.isAcceptedLatestVersion.name} | params: { ` + - `idToken.sub: ${idToken.sub}, };`, - ); - - try { - // DBからユーザーの同意済み利用規約バージョンと最新バージョンを取得 - const { - acceptedEulaVersion, - latestEulaVersion, - acceptedDpaVersion, - latestDpaVersion, - tier, - } = await this.usersRepository.getAcceptedAndLatestVersion(idToken.sub); - - // 第五階層はEULAのみ判定 - if (tier === TIERS.TIER5) { - if (!acceptedEulaVersion) { - return false; - } - // 最新バージョンに同意済みか判定 - const eulaAccepted = acceptedEulaVersion === latestEulaVersion; - return eulaAccepted; - } else { - // 第一~第四階層はEULA、DPAを判定 - if (!acceptedEulaVersion || !acceptedDpaVersion) { - return false; - } - // 最新バージョンに同意済みか判定 - const eulaAccepted = acceptedEulaVersion === latestEulaVersion; - const dpaAccepted = acceptedDpaVersion === latestDpaVersion; - return eulaAccepted && dpaAccepted; - } - } catch (e) { - this.logger.error(`[${context.trackingId}] error=${e}`); - throw new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } finally { - this.logger.log( - `[OUT] [${context.trackingId}] ${this.isAcceptedLatestVersion.name}`, - ); - } - } } diff --git a/dictation_server/src/features/auth/test/auth.service.mock.ts b/dictation_server/src/features/auth/test/auth.service.mock.ts index 5f8c9f8..08a7f32 100644 --- a/dictation_server/src/features/auth/test/auth.service.mock.ts +++ b/dictation_server/src/features/auth/test/auth.service.mock.ts @@ -9,13 +9,9 @@ export type AdB2cMockValue = { getMetaData: B2cMetadata | Error; getSignKeySets: JwkSignKey[] | Error; }; -export type ConfigMockValue = { - getOrThrow: number; -}; export const makeAuthServiceMock = async ( adB2cMockValue: AdB2cMockValue, - configMockValue: ConfigMockValue, ): Promise => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], @@ -25,7 +21,7 @@ export const makeAuthServiceMock = async ( case AdB2cService: return makeAdB2cServiceMock(adB2cMockValue); case ConfigService: - return makeConfigMock(configMockValue); + return {}; case UsersRepositoryService: return {}; } @@ -84,16 +80,3 @@ export const makeDefaultGetPublicKeyFromJwk = (jwkKey: JwkSignKey): string => { '-----END PUBLIC KEY-----', ].join('\n'); }; -export const makeConfigMock = (value: ConfigMockValue) => { - const { getOrThrow } = value; - - return { - getOrThrow: jest.fn, []>().mockResolvedValue(getOrThrow), - }; -}; - -export const makeDefaultConfigValue = (): ConfigMockValue => { - return { - getOrThrow: 80000, - }; -}; diff --git a/dictation_server/src/features/auth/test/utility.ts b/dictation_server/src/features/auth/test/utility.ts deleted file mode 100644 index c5c2c51..0000000 --- a/dictation_server/src/features/auth/test/utility.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { DataSource } from 'typeorm'; -import { Term } from '../../../repositories/terms/entity/term.entity'; - -export const createTermInfo = async ( - datasource: DataSource, - documentType: string, - version: string, -): Promise => { - const { identifiers } = await datasource.getRepository(Term).insert({ - document_type: documentType, - version: version, - created_by: 'test_runner', - created_at: new Date(), - updated_by: 'updater', - updated_at: new Date(), - }); - identifiers.pop() as Term; -}; diff --git a/dictation_server/src/features/auth/types/types.ts b/dictation_server/src/features/auth/types/types.ts index a5250bc..95f41a3 100644 --- a/dictation_server/src/features/auth/types/types.ts +++ b/dictation_server/src/features/auth/types/types.ts @@ -17,11 +17,3 @@ export class AccessTokenResponse { accessToken: string; } export class AccessTokenRequest {} - -export type TermsCheckInfo = { - tier: number; - acceptedEulaVersion?: string; - acceptedDpaVersion?: string; - latestEulaVersion: string; - latestDpaVersion: string; -}; diff --git a/dictation_server/src/features/files/files.controller.ts b/dictation_server/src/features/files/files.controller.ts index 4a72268..d188e94 100644 --- a/dictation_server/src/features/files/files.controller.ts +++ b/dictation_server/src/features/files/files.controller.ts @@ -2,7 +2,6 @@ import { Body, Controller, Get, - HttpException, HttpStatus, Post, Query, @@ -38,7 +37,6 @@ import { ADMIN_ROLES, USER_ROLES } from '../../constants'; import { retrieveAuthorizationToken } from '../../common/http/helper'; import { Request } from 'express'; import { makeContext } from '../../common/log'; -import { makeErrorResponse } from '../../common/error/makeErrorResponse'; @ApiTags('files') @Controller('files') @@ -72,28 +70,16 @@ export class FilesController { }) @ApiBearerAuth() @UseGuards(AuthGuard) + @UseGuards(RoleGuard.requireds({ roles: [USER_ROLES.AUTHOR] })) @Post('audio/upload-finished') async uploadFinished( @Req() req: Request, @Body() body: AudioUploadFinishedRequest, ): Promise { - const accessToken = retrieveAuthorizationToken(req); - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const accessToken = jwt.decode(token, { json: true }) as AccessToken; - const context = makeContext(userId); + const context = makeContext(accessToken.userId); const { url, @@ -114,7 +100,7 @@ export class FilesController { const res = await this.filesService.uploadFinished( context, - userId, + accessToken.userId, url, authorId, fileName, @@ -157,6 +143,7 @@ export class FilesController { }) @ApiBearerAuth() @UseGuards(AuthGuard) + @UseGuards(RoleGuard.requireds({ roles: [USER_ROLES.AUTHOR] })) async uploadLocation( @Req() req: Request, // クエリパラメータ AudioUploadLocationRequest は空であるため内部で使用しない。 @@ -164,23 +151,10 @@ export class FilesController { // eslint-disable-next-line @typescript-eslint/no-unused-vars @Query() _query: AudioUploadLocationRequest, ): Promise { - const accessToken = retrieveAuthorizationToken(req); - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const accessToken = jwt.decode(token, { json: true }) as AccessToken; - const context = makeContext(userId); + const context = makeContext(accessToken.userId); const url = await this.filesService.publishUploadSas(context, accessToken); return { url }; @@ -223,27 +197,14 @@ export class FilesController { ): Promise { const { audioFileId } = body; - const accessToken = retrieveAuthorizationToken(req); - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const accessToken = jwt.decode(token, { json: true }) as AccessToken; - const context = makeContext(userId); + const context = makeContext(accessToken.userId); const url = await this.filesService.publishAudioFileDownloadSas( context, - userId, + accessToken.userId, audioFileId, ); @@ -287,27 +248,14 @@ export class FilesController { ): Promise { const { audioFileId } = body; - const accessToken = retrieveAuthorizationToken(req); - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const accessToken = jwt.decode(token, { json: true }) as AccessToken; - const context = makeContext(userId); + const context = makeContext(accessToken.userId); const url = await this.filesService.publishTemplateFileDownloadSas( context, - userId, + accessToken.userId, audioFileId, ); @@ -341,21 +289,8 @@ export class FilesController { async uploadTemplateLocation( @Req() req: Request, ): Promise { - const accessToken = retrieveAuthorizationToken(req); - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(userId); @@ -400,21 +335,8 @@ export class FilesController { @Body() body: TemplateUploadFinishedRequest, ): Promise { const { name, url } = body; - const accessToken = retrieveAuthorizationToken(req); - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(userId); await this.filesService.templateUploadFinished(context, userId, url, name); diff --git a/dictation_server/src/features/files/files.service.spec.ts b/dictation_server/src/features/files/files.service.spec.ts index dccffd8..2ea8e73 100644 --- a/dictation_server/src/features/files/files.service.spec.ts +++ b/dictation_server/src/features/files/files.service.spec.ts @@ -35,10 +35,11 @@ describe('音声ファイルアップロードURL取得', () => { ); expect( - await service.publishUploadSas( - makeContext('trackingId'), - 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', - ), + await service.publishUploadSas(makeContext('trackingId'), { + userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', + role: 'Author', + tier: 5, + }), ).toEqual('https://blob-storage?sas-token'); }); @@ -56,10 +57,11 @@ describe('音声ファイルアップロードURL取得', () => { ); expect( - await service.publishUploadSas( - makeContext('trackingId'), - 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', - ), + await service.publishUploadSas(makeContext('trackingId'), { + userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', + role: 'Author', + tier: 5, + }), ).toEqual('https://blob-storage?sas-token'); }); @@ -76,10 +78,11 @@ describe('音声ファイルアップロードURL取得', () => { ); await expect( - service.publishUploadSas( - makeContext('trackingId'), - 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', - ), + service.publishUploadSas(makeContext('trackingId'), { + userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', + role: 'Author', + tier: 5, + }), ).rejects.toEqual( new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED), ); @@ -99,10 +102,11 @@ describe('音声ファイルアップロードURL取得', () => { blobParam.publishUploadSas = new Error('Azure service down'); await expect( - service.publishUploadSas( - makeContext('trackingId'), - 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', - ), + service.publishUploadSas(makeContext('trackingId'), { + userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', + role: 'Author', + tier: 5, + }), ).rejects.toEqual( new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED), ); @@ -291,7 +295,7 @@ describe('タスク作成', () => { }); describe('音声ファイルダウンロードURL取得', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -304,13 +308,11 @@ describe('音声ファイルダウンロードURL取得', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('ダウンロードSASトークンが乗っているURLを取得できる', async () => { - if (!source) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId, @@ -331,7 +333,7 @@ describe('音声ファイルダウンロードURL取得', () => { 'test.zip', 'InProgress', undefined, - authorId ?? '', + authorId, ); const blobParam = makeBlobstorageServiceMockValue(); @@ -339,7 +341,6 @@ describe('音声ファイルダウンロードURL取得', () => { blobParam.fileExists = true; const module = await makeTestingModuleWithBlob(source, blobParam); - if (!module) fail(); const service = module.get(FilesService); expect( @@ -352,7 +353,6 @@ describe('音声ファイルダウンロードURL取得', () => { }); it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => { - if (!source) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId, id: userId } = await makeTestUser(source, { account_id: accountId, @@ -382,7 +382,6 @@ describe('音声ファイルダウンロードURL取得', () => { blobParam.fileExists = true; const module = await makeTestingModuleWithBlob(source, blobParam); - if (!module) fail(); const service = module.get(FilesService); await expect( @@ -397,7 +396,6 @@ describe('音声ファイルダウンロードURL取得', () => { }); it('Typistの場合、自身が担当するタスクでない場合エラー', async () => { - if (!source) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId } = await makeTestUser(source, { account_id: accountId, @@ -431,7 +429,6 @@ describe('音声ファイルダウンロードURL取得', () => { blobParam.fileExists = true; const module = await makeTestingModuleWithBlob(source, blobParam); - if (!module) fail(); const service = module.get(FilesService); await expect( @@ -446,7 +443,6 @@ describe('音声ファイルダウンロードURL取得', () => { }); it('Authorの場合、自身が登録したタスクでない場合エラー', async () => { - if (!source) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId, id: userId } = await makeTestUser(source, { account_id: accountId, @@ -471,7 +467,6 @@ describe('音声ファイルダウンロードURL取得', () => { blobParam.fileExists = true; const module = await makeTestingModuleWithBlob(source, blobParam); - if (!module) fail(); const service = module.get(FilesService); await expect( @@ -486,7 +481,6 @@ describe('音声ファイルダウンロードURL取得', () => { }); it('Taskが存在しない場合はエラーとなる', async () => { - if (!source) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId } = await makeTestUser(source, { account_id: accountId, @@ -498,7 +492,6 @@ describe('音声ファイルダウンロードURL取得', () => { const blobParam = makeBlobstorageServiceMockValue(); const module = await makeTestingModuleWithBlob(source, blobParam); - if (!module) fail(); const service = module.get(FilesService); await expect( @@ -513,7 +506,6 @@ describe('音声ファイルダウンロードURL取得', () => { }); it('blobストレージにファイルが存在しない場合はエラーとなる', async () => { - if (!source) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId, @@ -534,7 +526,7 @@ describe('音声ファイルダウンロードURL取得', () => { 'test.zip', 'InProgress', undefined, - authorId ?? '', + authorId, ); const blobParam = makeBlobstorageServiceMockValue(); @@ -542,7 +534,6 @@ describe('音声ファイルダウンロードURL取得', () => { blobParam.fileExists = false; const module = await makeTestingModuleWithBlob(source, blobParam); - if (!module) fail(); const service = module.get(FilesService); await expect( @@ -558,7 +549,7 @@ describe('音声ファイルダウンロードURL取得', () => { }); describe('テンプレートファイルダウンロードURL取得', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -571,13 +562,11 @@ describe('テンプレートファイルダウンロードURL取得', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('ダウンロードSASトークンが乗っているURLを取得できる', async () => { - if (!source) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId, author_id: authorId } = await makeTestUser( source, @@ -597,7 +586,7 @@ describe('テンプレートファイルダウンロードURL取得', () => { 'test.zip', 'InProgress', undefined, - authorId ?? '', + authorId, ); const blobParam = makeBlobstorageServiceMockValue(); @@ -605,7 +594,6 @@ describe('テンプレートファイルダウンロードURL取得', () => { blobParam.fileExists = true; const module = await makeTestingModuleWithBlob(source, blobParam); - if (!module) fail(); const service = module.get(FilesService); expect( @@ -618,7 +606,6 @@ describe('テンプレートファイルダウンロードURL取得', () => { }); it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => { - if (!source) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId, id: userId } = await makeTestUser(source, { account_id: accountId, @@ -642,7 +629,6 @@ describe('テンプレートファイルダウンロードURL取得', () => { blobParam.fileExists = true; const module = await makeTestingModuleWithBlob(source, blobParam); - if (!module) fail(); const service = module.get(FilesService); await expect( @@ -657,7 +643,6 @@ describe('テンプレートファイルダウンロードURL取得', () => { }); it('Typistの場合、自身が担当するタスクでない場合エラー', async () => { - if (!source) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId } = await makeTestUser(source, { account_id: accountId, @@ -687,7 +672,6 @@ describe('テンプレートファイルダウンロードURL取得', () => { blobParam.fileExists = true; const module = await makeTestingModuleWithBlob(source, blobParam); - if (!module) fail(); const service = module.get(FilesService); await expect( @@ -702,7 +686,6 @@ describe('テンプレートファイルダウンロードURL取得', () => { }); it('Authorの場合、自身が登録したタスクでない場合エラー', async () => { - if (!source) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId } = await makeTestUser(source, { account_id: accountId, @@ -727,7 +710,6 @@ describe('テンプレートファイルダウンロードURL取得', () => { blobParam.fileExists = true; const module = await makeTestingModuleWithBlob(source, blobParam); - if (!module) fail(); const service = module.get(FilesService); await expect( @@ -742,7 +724,6 @@ describe('テンプレートファイルダウンロードURL取得', () => { }); it('Taskが存在しない場合はエラーとなる', async () => { - if (!source) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId } = await makeTestUser(source, { account_id: accountId, @@ -754,7 +735,6 @@ describe('テンプレートファイルダウンロードURL取得', () => { const blobParam = makeBlobstorageServiceMockValue(); const module = await makeTestingModuleWithBlob(source, blobParam); - if (!module) fail(); const service = module.get(FilesService); await expect( @@ -769,7 +749,6 @@ describe('テンプレートファイルダウンロードURL取得', () => { }); it('blobストレージにファイルが存在しない場合はエラーとなる', async () => { - if (!source) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId, author_id: authorId } = await makeTestUser( source, @@ -789,7 +768,7 @@ describe('テンプレートファイルダウンロードURL取得', () => { 'test.zip', 'InProgress', undefined, - authorId ?? '', + authorId, ); const blobParam = makeBlobstorageServiceMockValue(); @@ -797,7 +776,6 @@ describe('テンプレートファイルダウンロードURL取得', () => { blobParam.fileExists = false; const module = await makeTestingModuleWithBlob(source, blobParam); - if (!module) fail(); const service = module.get(FilesService); await expect( @@ -813,7 +791,7 @@ describe('テンプレートファイルダウンロードURL取得', () => { }); describe('publishTemplateFileUploadSas', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -826,15 +804,12 @@ describe('publishTemplateFileUploadSas', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('テンプレートファイルアップロードSASトークンが乗っているURLを取得できる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(FilesService); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -857,9 +832,7 @@ describe('publishTemplateFileUploadSas', () => { }); it('blobストレージにコンテナが存在しない場合はエラーとなる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(FilesService); // 第五階層のアカウント作成 const { admin } = await makeTestAccount(source, { tier: 5 }); @@ -885,9 +858,7 @@ describe('publishTemplateFileUploadSas', () => { }); it('SASトークンの取得に失敗した場合はエラーとなる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(FilesService); // 第五階層のアカウント作成 const { admin } = await makeTestAccount(source, { tier: 5 }); @@ -916,7 +887,7 @@ describe('publishTemplateFileUploadSas', () => { }); describe('templateUploadFinished', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -929,15 +900,12 @@ describe('templateUploadFinished', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('アップロード完了後のテンプレートファイル情報をDBに保存できる(新規追加)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(FilesService); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -969,9 +937,7 @@ describe('templateUploadFinished', () => { }); it('アップロード完了後のテンプレートファイル情報をDBに保存できる(更新)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(FilesService); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -1009,9 +975,7 @@ describe('templateUploadFinished', () => { }); it('DBへの保存に失敗した場合はエラーとなる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(FilesService); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); diff --git a/dictation_server/src/features/files/files.service.ts b/dictation_server/src/features/files/files.service.ts index db97e77..bcae416 100644 --- a/dictation_server/src/features/files/files.service.ts +++ b/dictation_server/src/features/files/files.service.ts @@ -24,7 +24,6 @@ import { } from '../../repositories/tasks/errors/types'; import { Context } from '../../common/log'; import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service'; -import { AccountNotFoundError } from '../../repositories/accounts/errors/types'; @Injectable() export class FilesService { @@ -207,7 +206,7 @@ export class FilesService { */ async publishUploadSas( context: Context, - externalId: string, + token: AccessToken, ): Promise { this.logger.log( `[IN] [${context.trackingId}] ${this.publishUploadSas.name}`, @@ -217,11 +216,10 @@ export class FilesService { let accountId: number; let country: string; try { - const user = await this.usersRepository.findUserByExternalId(externalId); - if (!user.account) { - throw new AccountNotFoundError('account not found.'); - } - accountId = user.account_id; + const user = await this.usersRepository.findUserByExternalId( + token.userId, + ); + accountId = user.account.id; country = user.account.country; } catch (e) { this.logger.error(`error=${e}`); @@ -293,17 +291,14 @@ export class FilesService { let userId: number; let country: string; let isTypist: boolean; - let authorId: string | undefined; + let authorId: string; try { const user = await this.usersRepository.findUserByExternalId(externalId); - if (!user.account) { - throw new AccountNotFoundError('account not found.'); - } accountId = user.account.id; userId = user.id; country = user.account.country; isTypist = user.role === USER_ROLES.TYPIST; - authorId = user.author_id ?? undefined; + authorId = user.author_id; } catch (e) { this.logger.error(`error=${e}`); @@ -326,7 +321,7 @@ export class FilesService { accountId, status, ); - const { file } = task; + const file = task.file; // タスクに紐づく音声ファイルだけが消される場合がある。 // その場合はダウンロード不可なので不在エラーとして扱う @@ -337,9 +332,9 @@ export class FilesService { } // ユーザーがAuthorの場合、自身が追加したタスクでない場合はエラー - if (!isTypist && file.author_id !== authorId) { + if (!isTypist && task.file.author_id !== authorId) { throw new AuthorUserNotMatchError( - `task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${file.author_id}, authorId:${authorId}`, + `task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${task.file.author_id}, authorId:${authorId}`, ); } @@ -430,17 +425,14 @@ export class FilesService { let userId: number; let country: string; let isTypist: boolean; - let authorId: string | undefined; + let authorId: string; try { const user = await this.usersRepository.findUserByExternalId(externalId); - if (!user.account) { - throw new AccountNotFoundError('account not found.'); - } - accountId = user.account_id; + accountId = user.account.id; userId = user.id; country = user.account.country; isTypist = user.role === USER_ROLES.TYPIST; - authorId = user.author_id ?? undefined; + authorId = user.author_id; } catch (e) { this.logger.error(`error=${e}`); this.logger.log( @@ -462,15 +454,6 @@ export class FilesService { accountId, status, ); - const { file } = task; - - // タスクに紐づく音声ファイルだけが消される場合がある。 - // その場合はダウンロード不可なので不在エラーとして扱う - if (!file) { - throw new AudioFileNotFoundError( - `Audio file is not exists in DB. audio_file_id:${audioFileId}`, - ); - } const template_file = task.template_file; @@ -483,9 +466,9 @@ export class FilesService { } // ユーザーがAuthorの場合、自身が追加したタスクでない場合はエラー - if (!isTypist && file.author_id !== authorId) { + if (!isTypist && task.file.author_id !== authorId) { throw new AuthorUserNotMatchError( - `task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${file.author_id}, authorId:${authorId}`, + `task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${task.file.author_id}, authorId:${authorId}`, ); } @@ -532,7 +515,6 @@ export class FilesService { makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST, ); - case AudioFileNotFoundError: case TemplateFileNotFoundError: throw new HttpException( makeErrorResponse('E010701'), @@ -570,18 +552,15 @@ export class FilesService { `[IN] [${context.trackingId}] ${this.publishTemplateFileUploadSas.name} | params: { externalId: ${externalId} };`, ); try { - const { account } = await this.usersRepository.findUserByExternalId( - externalId, - ); - if (!account) { - throw new AccountNotFoundError('account not found.'); - } + const { + account: { id: accountId, country }, + } = await this.usersRepository.findUserByExternalId(externalId); // 国に応じたリージョンのBlobストレージにコンテナが存在するか確認 const isContainerExists = await this.blobStorageService.containerExists( context, - account.id, - account.country, + accountId, + country, ); if (!isContainerExists) { throw new Error('container not found.'); @@ -590,8 +569,8 @@ export class FilesService { // SASトークン発行 const url = await this.blobStorageService.publishTemplateUploadSas( context, - account.id, - account.country, + accountId, + country, ); return url; diff --git a/dictation_server/src/features/files/test/files.service.mock.ts b/dictation_server/src/features/files/test/files.service.mock.ts index 9facc6f..dbc8a6f 100644 --- a/dictation_server/src/features/files/test/files.service.mock.ts +++ b/dictation_server/src/features/files/test/files.service.mock.ts @@ -134,15 +134,12 @@ export const makeDefaultUsersRepositoryMockValue = created_by: 'test', created_at: new Date(), updated_by: null, - updated_at: new Date(), + updated_at: null, auto_renew: true, license_alert: true, notification: true, encryption: false, prompt: false, - encryption_password: null, - license: null, - userGroupMembers: null, account: { id: 2, parent_account_id: 2, @@ -157,10 +154,7 @@ export const makeDefaultUsersRepositoryMockValue = created_by: '', created_at: new Date(), updated_by: '', - updated_at: new Date(), - active_worktype_id: null, - secondary_admin_user_id: null, - user: null, + updated_at: null, }, }, }; @@ -178,14 +172,6 @@ export const makeDefaultTasksRepositoryMockValue = status: 'Uploaded', priority: '01', created_at: new Date(), - finished_at: null, - started_at: null, - template_file_id: null, - typist_user_id: null, - file: null, - option_items: null, - template_file: null, - typist_user: null, }, getTasksFromAccountId: { tasks: [], diff --git a/dictation_server/src/features/files/test/utility.ts b/dictation_server/src/features/files/test/utility.ts index c1f6e52..61772f6 100644 --- a/dictation_server/src/features/files/test/utility.ts +++ b/dictation_server/src/features/files/test/utility.ts @@ -98,7 +98,7 @@ export const createTask = async ( export const makeTestingModuleWithBlob = async ( datasource: DataSource, blobStorageService: BlobstorageServiceMockValue, -): Promise => { +): Promise => { try { const module: TestingModule = await Test.createTestingModule({ imports: [ diff --git a/dictation_server/src/features/licenses/licenses.controller.ts b/dictation_server/src/features/licenses/licenses.controller.ts index fac9163..d739ccd 100644 --- a/dictation_server/src/features/licenses/licenses.controller.ts +++ b/dictation_server/src/features/licenses/licenses.controller.ts @@ -2,7 +2,6 @@ import { Body, Controller, Get, - HttpException, HttpStatus, Post, Req, @@ -35,7 +34,6 @@ import { RoleGuard } from '../../common/guards/role/roleguards'; import { ADMIN_ROLES, TIERS } from '../../constants'; import jwt from 'jsonwebtoken'; import { makeContext } from '../../common/log'; -import { makeErrorResponse } from '../../common/error/makeErrorResponse'; @ApiTags('licenses') @Controller('licenses') @@ -75,25 +73,12 @@ export class LicensesController { @Req() req: Request, @Body() body: CreateOrdersRequest, ): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const payload = jwt.decode(accessToken, { json: true }) as AccessToken; // ライセンス注文処理 await this.licensesService.licenseOrders( - userId, + payload, body.poNumber, body.orderCount, ); @@ -126,24 +111,11 @@ export class LicensesController { @Req() req: Request, @Body() body: IssueCardLicensesRequest, ): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const payload = jwt.decode(accessToken, { json: true }) as AccessToken; const cardLicenseKeys = await this.licensesService.issueCardLicenseKeys( - userId, + payload.userId, body.createCount, ); @@ -182,24 +154,11 @@ export class LicensesController { @Req() req: Request, @Body() body: ActivateCardLicensesRequest, ): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const payload = jwt.decode(accessToken, { json: true }) as AccessToken; await this.licensesService.activateCardLicenseKey( - userId, + payload.userId, body.cardLicenseKey, ); @@ -235,26 +194,16 @@ export class LicensesController { // eslint-disable-next-line @typescript-eslint/no-unused-vars @Req() req: Request, ): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const payload = jwt.decode(token, { json: true }) as AccessToken; - const context = makeContext(userId); + const context = makeContext(payload.userId); const allocatableLicenses = - await this.licensesService.getAllocatableLicenses(context, userId); + await this.licensesService.getAllocatableLicenses( + context, + payload.userId, + ); return allocatableLicenses; } @@ -296,25 +245,16 @@ export class LicensesController { @Req() req: Request, @Body() body: CancelOrderRequest, ): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const payload = jwt.decode(token, { json: true }) as AccessToken; - const context = makeContext(userId); + const context = makeContext(payload.userId); - await this.licensesService.cancelOrder(context, userId, body.poNumber); + await this.licensesService.cancelOrder( + context, + payload.userId, + body.poNumber, + ); return {}; } } diff --git a/dictation_server/src/features/licenses/licenses.service.spec.ts b/dictation_server/src/features/licenses/licenses.service.spec.ts index 56d1ef2..4b5b87d 100644 --- a/dictation_server/src/features/licenses/licenses.service.spec.ts +++ b/dictation_server/src/features/licenses/licenses.service.spec.ts @@ -56,11 +56,11 @@ describe('LicensesService', () => { accountsRepositoryMockValue, ); const body = new CreateOrdersRequest(); - const userId = '0001'; + const token: AccessToken = { userId: '0001', role: '', tier: 5 }; body.orderCount = 1000; body.poNumber = '1'; expect( - await service.licenseOrders(userId, body.poNumber, body.orderCount), + await service.licenseOrders(token, body.poNumber, body.orderCount), ).toEqual(undefined); }); it('ユーザID取得できなかった場合、エラーとなる', async () => { @@ -78,11 +78,11 @@ describe('LicensesService', () => { accountsRepositoryMockValue, ); const body = new CreateOrdersRequest(); - const userId = ''; + const token: AccessToken = { userId: '', role: '', tier: 5 }; body.orderCount = 1000; body.poNumber = '1'; await expect( - service.licenseOrders(userId, body.poNumber, body.orderCount), + service.licenseOrders(token, body.poNumber, body.orderCount), ).rejects.toEqual( new HttpException( makeErrorResponse('E009999'), @@ -105,11 +105,11 @@ describe('LicensesService', () => { accountsRepositoryMockValue, ); const body = new CreateOrdersRequest(); - const userId = '0001'; + const token: AccessToken = { userId: '0001', role: '', tier: 5 }; body.orderCount = 1000; body.poNumber = '1'; await expect( - service.licenseOrders(userId, body.poNumber, body.orderCount), + service.licenseOrders(token, body.poNumber, body.orderCount), ).rejects.toEqual( new HttpException( makeErrorResponse('E009999'), @@ -130,11 +130,11 @@ describe('LicensesService', () => { accountsRepositoryMockValue, ); const body = new CreateOrdersRequest(); - const userId = '0001'; + const token: AccessToken = { userId: '0001', role: '', tier: 5 }; body.orderCount = 1000; body.poNumber = '1'; await expect( - service.licenseOrders(userId, body.poNumber, body.orderCount), + service.licenseOrders(token, body.poNumber, body.orderCount), ).rejects.toEqual( new HttpException( makeErrorResponse('E010401'), @@ -154,7 +154,7 @@ describe('LicensesService', () => { accountsRepositoryMockValue, ); const body = new IssueCardLicensesRequest(); - const userId = '0001'; + const token: AccessToken = { userId: '0001', role: '', tier: 5 }; body.createCount = 10; const issueCardLicensesResponse: IssueCardLicensesResponse = { cardLicenseKeys: [ @@ -171,7 +171,7 @@ describe('LicensesService', () => { ], }; expect( - await service.issueCardLicenseKeys(userId, body.createCount), + await service.issueCardLicenseKeys(token.userId, body.createCount), ).toEqual(issueCardLicensesResponse); }); it('カードライセンス発行に失敗した場合、エラーになる', async () => { @@ -187,10 +187,10 @@ describe('LicensesService', () => { accountsRepositoryMockValue, ); const body = new IssueCardLicensesRequest(); - const userId = '0001'; + const token: AccessToken = { userId: '0001', role: '', tier: 5 }; body.createCount = 1000; await expect( - service.issueCardLicenseKeys(userId, body.createCount), + service.issueCardLicenseKeys(token.userId, body.createCount), ).rejects.toEqual( new HttpException( makeErrorResponse('E009999'), @@ -210,10 +210,10 @@ describe('LicensesService', () => { accountsRepositoryMockValue, ); const body = new ActivateCardLicensesRequest(); - const userId = '0001'; + const token: AccessToken = { userId: '0001', role: '', tier: 5 }; body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY'; expect( - await service.activateCardLicenseKey(userId, body.cardLicenseKey), + await service.activateCardLicenseKey(token.userId, body.cardLicenseKey), ).toEqual(undefined); }); it('カードライセンス取り込みに失敗した場合、エラーになる(DBエラー)', async () => { @@ -229,10 +229,10 @@ describe('LicensesService', () => { accountsRepositoryMockValue, ); const body = new ActivateCardLicensesRequest(); - const userId = '0001'; + const token: AccessToken = { userId: '0001', role: '', tier: 5 }; body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY'; await expect( - service.activateCardLicenseKey(userId, body.cardLicenseKey), + service.activateCardLicenseKey(token.userId, body.cardLicenseKey), ).rejects.toEqual( new HttpException( makeErrorResponse('E009999'), @@ -254,10 +254,10 @@ describe('LicensesService', () => { accountsRepositoryMockValue, ); const body = new ActivateCardLicensesRequest(); - const userId = '0001'; + const token: AccessToken = { userId: '0001', role: '', tier: 5 }; body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY'; await expect( - service.activateCardLicenseKey(userId, body.cardLicenseKey), + service.activateCardLicenseKey(token.userId, body.cardLicenseKey), ).rejects.toEqual( new HttpException(makeErrorResponse('E010801'), HttpStatus.BAD_REQUEST), ); @@ -276,10 +276,10 @@ describe('LicensesService', () => { accountsRepositoryMockValue, ); const body = new ActivateCardLicensesRequest(); - const userId = '0001'; + const token: AccessToken = { userId: '0001', role: '', tier: 5 }; body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY'; await expect( - service.activateCardLicenseKey(userId, body.cardLicenseKey), + service.activateCardLicenseKey(token.userId, body.cardLicenseKey), ).rejects.toEqual( new HttpException(makeErrorResponse('E010802'), HttpStatus.BAD_REQUEST), ); @@ -287,7 +287,7 @@ describe('LicensesService', () => { }); describe('DBテスト', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -300,15 +300,12 @@ describe('DBテスト', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('カードライセンス発行が完了する(発行数が合っているか確認)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId } = await makeTestUser(source, { @@ -326,9 +323,7 @@ describe('DBテスト', () => { }); it('カードライセンス取り込みが完了する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId } = await makeTestUser(source, { @@ -367,15 +362,13 @@ describe('DBテスト', () => { ); const dbSelectResultFromLicense = await selectLicense(source, license_id); expect( - dbSelectResultFromCardLicense?.cardLicense?.activated_at, + dbSelectResultFromCardLicense.cardLicense.activated_at, ).toBeDefined(); - expect(dbSelectResultFromLicense?.license?.account_id).toEqual(accountId); + expect(dbSelectResultFromLicense.license.account_id).toEqual(accountId); }); it('取込可能なライセンスのみが取得できる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const now = new Date(); const { id: accountId } = await makeTestSimpleAccount(source); @@ -520,7 +513,7 @@ describe('DBテスト', () => { }); describe('ライセンス割り当て', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -533,15 +526,12 @@ describe('ライセンス割り当て', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('未割当のライセンスに対して、ライセンス割り当てが完了する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: userId } = await makeTestUser(source, { @@ -577,11 +567,11 @@ describe('ライセンス割り当て', () => { await service.allocateLicense(makeContext('trackingId'), userId, 1); const resultLicense = await selectLicense(source, 1); - expect(resultLicense.license?.allocated_user_id).toBe(userId); - expect(resultLicense.license?.status).toBe( + expect(resultLicense.license.allocated_user_id).toBe(userId); + expect(resultLicense.license.status).toBe( LICENSE_ALLOCATED_STATUS.ALLOCATED, ); - expect(resultLicense.license?.expiry_date?.setMilliseconds(0)).toEqual( + expect(resultLicense.license.expiry_date.setMilliseconds(0)).toEqual( expiry_date.setMilliseconds(0), ); const licenseAllocationHistory = await selectLicenseAllocationHistory( @@ -589,24 +579,22 @@ describe('ライセンス割り当て', () => { userId, 1, ); - expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe( + expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe( userId, ); - expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe( + expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe( 1, ); - expect( - licenseAllocationHistory.licenseAllocationHistory?.is_allocated, - ).toBe(true); - expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe( + expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe( + true, + ); + expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe( accountId, ); }); it('再割り当て可能なライセンスに対して、ライセンス割り当てが完了する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: userId } = await makeTestUser(source, { @@ -642,32 +630,30 @@ describe('ライセンス割り当て', () => { await service.allocateLicense(makeContext('trackingId'), userId, 1); const result = await selectLicense(source, 1); - expect(result.license?.allocated_user_id).toBe(userId); - expect(result.license?.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED); - expect(result.license?.expiry_date).toEqual(date); + expect(result.license.allocated_user_id).toBe(userId); + expect(result.license.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED); + expect(result.license.expiry_date).toEqual(date); const licenseAllocationHistory = await selectLicenseAllocationHistory( source, userId, 1, ); - expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe( + expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe( userId, ); - expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe( + expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe( 1, ); - expect( - licenseAllocationHistory.licenseAllocationHistory?.is_allocated, - ).toBe(true); - expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe( + expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe( + true, + ); + expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe( accountId, ); }); it('未割当のライセンスに対して、別のライセンスが割り当てられているユーザーの割り当てが完了する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: userId } = await makeTestUser(source, { @@ -719,32 +705,32 @@ describe('ライセンス割り当て', () => { // もともと割り当てられていたライセンスの状態確認 const result1 = await selectLicense(source, 1); - expect(result1.license?.allocated_user_id).toBe(null); - expect(result1.license?.status).toBe(LICENSE_ALLOCATED_STATUS.REUSABLE); - expect(result1.license?.expiry_date).toEqual(date); + expect(result1.license.allocated_user_id).toBe(null); + expect(result1.license.status).toBe(LICENSE_ALLOCATED_STATUS.REUSABLE); + expect(result1.license.expiry_date).toEqual(date); const licenseAllocationHistory = await selectLicenseAllocationHistory( source, userId, 1, ); - expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe( + expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe( userId, ); - expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe( + expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe( 1, ); - expect( - licenseAllocationHistory.licenseAllocationHistory?.is_allocated, - ).toBe(false); - expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe( + expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe( + false, + ); + expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe( accountId, ); // 新たに割り当てたライセンスの状態確認 const result2 = await selectLicense(source, 2); - expect(result2.license?.allocated_user_id).toBe(userId); - expect(result2.license?.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED); - expect(result2.license?.expiry_date?.setMilliseconds(0)).toEqual( + expect(result2.license.allocated_user_id).toBe(userId); + expect(result2.license.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED); + expect(result2.license.expiry_date.setMilliseconds(0)).toEqual( expiry_date.setMilliseconds(0), ); const newlicenseAllocationHistory = await selectLicenseAllocationHistory( @@ -752,24 +738,22 @@ describe('ライセンス割り当て', () => { userId, 2, ); - expect(newlicenseAllocationHistory.licenseAllocationHistory?.user_id).toBe( + expect(newlicenseAllocationHistory.licenseAllocationHistory.user_id).toBe( userId, ); expect( - newlicenseAllocationHistory.licenseAllocationHistory?.license_id, + newlicenseAllocationHistory.licenseAllocationHistory.license_id, ).toBe(2); expect( - newlicenseAllocationHistory.licenseAllocationHistory?.is_allocated, + newlicenseAllocationHistory.licenseAllocationHistory.is_allocated, ).toBe(true); expect( - newlicenseAllocationHistory.licenseAllocationHistory?.account_id, + newlicenseAllocationHistory.licenseAllocationHistory.account_id, ).toBe(accountId); }); it('割り当て時にライセンス履歴テーブルへの登録が完了する(元がNORMALのとき)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: userId } = await makeTestUser(source, { @@ -822,14 +806,12 @@ describe('ライセンス割り当て', () => { 2, ); expect( - licenseAllocationHistory.licenseAllocationHistory?.switch_from_type, + licenseAllocationHistory.licenseAllocationHistory.switch_from_type, ).toBe('NONE'); }); it('割り当て時にライセンス履歴テーブルへの登録が完了する(元がCARDのとき)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: userId } = await makeTestUser(source, { @@ -882,14 +864,12 @@ describe('ライセンス割り当て', () => { 2, ); expect( - licenseAllocationHistory.licenseAllocationHistory?.switch_from_type, + licenseAllocationHistory.licenseAllocationHistory.switch_from_type, ).toBe('CARD'); }); it('割り当て時にライセンス履歴テーブルへの登録が完了する(元がTRIALのとき)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: userId } = await makeTestUser(source, { @@ -942,14 +922,12 @@ describe('ライセンス割り当て', () => { 2, ); expect( - licenseAllocationHistory.licenseAllocationHistory?.switch_from_type, + licenseAllocationHistory.licenseAllocationHistory.switch_from_type, ).toBe('TRIAL'); }); it('有効期限が切れているライセンスを割り当てようとした場合、エラーになる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: userId } = await makeTestUser(source, { @@ -983,9 +961,7 @@ describe('ライセンス割り当て', () => { }); it('割り当て不可なライセンスを割り当てようとした場合、エラーになる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: userId } = await makeTestUser(source, { @@ -1037,7 +1013,7 @@ describe('ライセンス割り当て', () => { }); describe('ライセンス割り当て解除', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -1050,15 +1026,12 @@ describe('ライセンス割り当て解除', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('ライセンスの割り当て解除が完了する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: userId } = await makeTestUser(source, { @@ -1095,11 +1068,11 @@ describe('ライセンス割り当て解除', () => { // 割り当て解除したライセンスの状態確認 const deallocatedLicense = await selectLicense(source, 1); - expect(deallocatedLicense.license?.allocated_user_id).toBe(null); - expect(deallocatedLicense.license?.status).toBe( + expect(deallocatedLicense.license.allocated_user_id).toBe(null); + expect(deallocatedLicense.license.status).toBe( LICENSE_ALLOCATED_STATUS.REUSABLE, ); - expect(deallocatedLicense.license?.expiry_date).toEqual(date); + expect(deallocatedLicense.license.expiry_date).toEqual(date); // ライセンス履歴テーブルの状態確認 const licenseAllocationHistory = await selectLicenseAllocationHistory( @@ -1107,27 +1080,25 @@ describe('ライセンス割り当て解除', () => { userId, 1, ); - expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe( + expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe( userId, ); - expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe( + expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe( 1, ); - expect( - licenseAllocationHistory.licenseAllocationHistory?.is_allocated, - ).toBe(false); - expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe( + expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe( + false, + ); + expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe( accountId, ); expect( - licenseAllocationHistory.licenseAllocationHistory?.switch_from_type, + licenseAllocationHistory.licenseAllocationHistory.switch_from_type, ).toBe('NONE'); }); it('ライセンスが既に割り当て解除されていた場合、エラーとなる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: userId } = await makeTestUser(source, { @@ -1187,7 +1158,7 @@ describe('ライセンス割り当て解除', () => { }); describe('ライセンス注文キャンセル', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -1200,15 +1171,12 @@ describe('ライセンス注文キャンセル', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('ライセンス注文のキャンセルが完了する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts( source, ); @@ -1217,7 +1185,7 @@ describe('ライセンス注文キャンセル', () => { source, poNumber, tier2Accounts[0].account.id, - tier2Accounts[0].account.parent_account_id ?? 0, + tier2Accounts[0].account.parent_account_id, null, 10, 'Issue Requesting', @@ -1227,7 +1195,7 @@ describe('ライセンス注文キャンセル', () => { source, poNumber, tier2Accounts[0].account.id, - tier2Accounts[0].account.parent_account_id ?? 0, + tier2Accounts[0].account.parent_account_id, null, 10, 'Order Canceled', @@ -1246,14 +1214,12 @@ describe('ライセンス注文キャンセル', () => { tier2Accounts[0].account.id, poNumber, ); - expect(orderRecord.orderLicense?.canceled_at).toBeDefined(); - expect(orderRecord.orderLicense?.status).toBe('Order Canceled'); + expect(orderRecord.orderLicense.canceled_at).toBeDefined(); + expect(orderRecord.orderLicense.status).toBe('Order Canceled'); }); it('ライセンスが既に発行済みの場合、エラーとなる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts( source, ); @@ -1262,7 +1228,7 @@ describe('ライセンス注文キャンセル', () => { source, poNumber, tier2Accounts[0].account.id, - tier2Accounts[0].account.parent_account_id ?? 0, + tier2Accounts[0].account.parent_account_id, null, 10, 'Issued', @@ -1281,9 +1247,7 @@ describe('ライセンス注文キャンセル', () => { }); it('ライセンスが既にキャンセル済みの場合、エラーとなる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts( source, @@ -1293,7 +1257,7 @@ describe('ライセンス注文キャンセル', () => { source, poNumber, tier2Accounts[0].account.id, - tier2Accounts[0].account.parent_account_id ?? 0, + tier2Accounts[0].account.parent_account_id, null, 10, 'Order Canceled', diff --git a/dictation_server/src/features/licenses/licenses.service.ts b/dictation_server/src/features/licenses/licenses.service.ts index 3aa04f1..2c23bd8 100644 --- a/dictation_server/src/features/licenses/licenses.service.ts +++ b/dictation_server/src/features/licenses/licenses.service.ts @@ -33,20 +33,20 @@ export class LicensesService { * @param body */ async licenseOrders( - externalId: string, + accessToken: AccessToken, poNumber: string, orderCount: number, ): Promise { //アクセストークンからユーザーIDを取得する this.logger.log(`[IN] ${this.licenseOrders.name}`); + const userId = accessToken.userId; let myAccountId: number; - let parentAccountId: number | undefined; + let parentAccountId: number; // ユーザIDからアカウントIDを取得する try { - myAccountId = ( - await this.usersRepository.findUserByExternalId(externalId) - ).account_id; + myAccountId = (await this.usersRepository.findUserByExternalId(userId)) + .account_id; } catch (e) { this.logger.error(`error=${e}`); switch (e.constructor) { @@ -65,13 +65,9 @@ export class LicensesService { // 親アカウントIDを取得 try { - parentAccountId = - (await this.accountsRepository.findAccountById(myAccountId)) - .parent_account_id ?? undefined; - // 親アカウントIDが取得できない場合はエラー - if (parentAccountId === undefined) { - throw new Error('parent account id is undefined'); - } + parentAccountId = ( + await this.accountsRepository.findAccountById(myAccountId) + ).parent_account_id; } catch (e) { this.logger.error(`error=${e}`); switch (e.constructor) { diff --git a/dictation_server/src/features/licenses/test/liscense.service.mock.ts b/dictation_server/src/features/licenses/test/liscense.service.mock.ts index 5c0ef7b..619aa95 100644 --- a/dictation_server/src/features/licenses/test/liscense.service.mock.ts +++ b/dictation_server/src/features/licenses/test/liscense.service.mock.ts @@ -124,11 +124,11 @@ export const makeDefaultUsersRepositoryMockValue = user1.notification = false; user1.encryption = false; user1.prompt = false; - user1.deleted_at = null; + user1.deleted_at = undefined; user1.created_by = 'test'; user1.created_at = new Date(); - user1.updated_by = null; - user1.updated_at = new Date(); + user1.updated_by = undefined; + user1.updated_at = undefined; return { findUserByExternalId: user1, diff --git a/dictation_server/src/features/licenses/test/utility.ts b/dictation_server/src/features/licenses/test/utility.ts index 321f9c0..d0e9a89 100644 --- a/dictation_server/src/features/licenses/test/utility.ts +++ b/dictation_server/src/features/licenses/test/utility.ts @@ -12,14 +12,14 @@ import { export const createLicense = async ( datasource: DataSource, licenseId: number, - expiry_date: Date | null, + expiry_date: Date, accountId: number, type: string, status: string, - allocated_user_id: number | null, - order_id: number | null, - deleted_at: Date | null, - delete_order_id: number | null, + allocated_user_id: number, + order_id: number, + deleted_at: Date, + delete_order_id: number, ): Promise => { const { identifiers } = await datasource.getRepository(License).insert({ id: licenseId, @@ -107,7 +107,7 @@ export const createOrder = async ( poNumber: string, fromId: number, toId: number, - issuedAt: Date | null, + issuedAt: Date, quantity: number, status: string, ): Promise => { @@ -138,7 +138,7 @@ export const selectCardLicensesCount = async ( export const selectCardLicense = async ( datasource: DataSource, cardLicenseKey: string, -): Promise<{ cardLicense: CardLicense | null }> => { +): Promise<{ cardLicense: CardLicense }> => { const cardLicense = await datasource.getRepository(CardLicense).findOne({ where: { card_license_key: cardLicenseKey, @@ -150,7 +150,7 @@ export const selectCardLicense = async ( export const selectLicense = async ( datasource: DataSource, id: number, -): Promise<{ license: License | null }> => { +): Promise<{ license: License }> => { const license = await datasource.getRepository(License).findOne({ where: { id: id, @@ -163,7 +163,7 @@ export const selectLicenseAllocationHistory = async ( datasource: DataSource, userId: number, licence_id: number, -): Promise<{ licenseAllocationHistory: LicenseAllocationHistory | null }> => { +): Promise<{ licenseAllocationHistory: LicenseAllocationHistory }> => { const licenseAllocationHistory = await datasource .getRepository(LicenseAllocationHistory) .findOne({ @@ -182,7 +182,7 @@ export const selectOrderLicense = async ( datasource: DataSource, accountId: number, poNumber: string, -): Promise<{ orderLicense: LicenseOrder | null }> => { +): Promise<{ orderLicense: LicenseOrder }> => { const orderLicense = await datasource.getRepository(LicenseOrder).findOne({ where: { from_account_id: accountId, diff --git a/dictation_server/src/features/licenses/types/types.ts b/dictation_server/src/features/licenses/types/types.ts index d303b1e..a75885f 100644 --- a/dictation_server/src/features/licenses/types/types.ts +++ b/dictation_server/src/features/licenses/types/types.ts @@ -47,8 +47,8 @@ export class GetAllocatableLicensesRequest {} export class AllocatableLicenseInfo { @ApiProperty() licenseId: number; - @ApiProperty({ required: false }) - expiryDate?: Date; + @ApiProperty() + expiryDate: Date; } export class GetAllocatableLicensesResponse { @ApiProperty({ type: [AllocatableLicenseInfo] }) diff --git a/dictation_server/src/features/notification/notification.controller.ts b/dictation_server/src/features/notification/notification.controller.ts index 0a640be..c234163 100644 --- a/dictation_server/src/features/notification/notification.controller.ts +++ b/dictation_server/src/features/notification/notification.controller.ts @@ -1,7 +1,6 @@ import { Body, Controller, - HttpException, HttpStatus, Post, Req, @@ -22,7 +21,6 @@ import { retrieveAuthorizationToken } from '../../common/http/helper'; import { AccessToken } from '../../common/token'; import jwt from 'jsonwebtoken'; import { makeContext } from '../../common/log'; -import { makeErrorResponse } from '../../common/error/makeErrorResponse'; @ApiTags('notification') @Controller('notification') @@ -59,20 +57,7 @@ export class NotificationController { const { handler, pns } = body; const accessToken = retrieveAuthorizationToken(req); - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken; const context = makeContext(userId); diff --git a/dictation_server/src/features/notification/test/notification.service.mock.ts b/dictation_server/src/features/notification/test/notification.service.mock.ts index 51d7601..c2df2d6 100644 --- a/dictation_server/src/features/notification/test/notification.service.mock.ts +++ b/dictation_server/src/features/notification/test/notification.service.mock.ts @@ -77,14 +77,14 @@ export const makeDefaultUsersRepositoryMockValue = user.external_id = 'external_id'; user.account_id = 123; user.role = 'none'; - user.author_id = null; + user.author_id = undefined; user.accepted_eula_version = '1.0'; user.accepted_dpa_version = '1.0'; user.email_verified = true; user.auto_renew = false; user.license_alert = false; user.notification = false; - user.deleted_at = null; + user.deleted_at = undefined; user.created_by = 'test'; user.created_at = new Date(); user.updated_by = 'test'; diff --git a/dictation_server/src/features/tasks/tasks.controller.ts b/dictation_server/src/features/tasks/tasks.controller.ts index 49fbe9e..b2c860e 100644 --- a/dictation_server/src/features/tasks/tasks.controller.ts +++ b/dictation_server/src/features/tasks/tasks.controller.ts @@ -3,7 +3,6 @@ import { Controller, Get, Headers, - HttpException, HttpStatus, Param, ParseIntPipe, @@ -33,8 +32,6 @@ import { TasksResponse, } from './types/types'; import { - SortDirection, - TaskListSortableAttribute, isSortDirection, isTaskListSortableAttribute, } from '../../common/types/sort'; @@ -46,7 +43,6 @@ import { RoleGuard } from '../../common/guards/role/roleguards'; import { ADMIN_ROLES, USER_ROLES } from '../../constants'; import { Roles } from '../../common/types/role'; import { makeContext } from '../../common/log'; -import { makeErrorResponse } from '../../common/error/makeErrorResponse'; @ApiTags('tasks') @Controller('tasks') @@ -84,38 +80,22 @@ export class TasksController { @Req() req, @Query() body: TasksRequest, ): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId, role } = decodedAccessToken as AccessToken; - // RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う - const roles = role.split(' ') as Roles[]; + const accessToken = retrieveAuthorizationToken(req); + const decodedToken = jwt.decode(accessToken, { json: true }) as AccessToken; - const context = makeContext(userId); + const context = makeContext(decodedToken.userId); const { limit, offset, status } = body; - const paramName = isTaskListSortableAttribute(body.paramName ?? '') - ? (body.paramName as TaskListSortableAttribute) + const paramName = isTaskListSortableAttribute(body.paramName) + ? body.paramName : undefined; - const direction = isSortDirection(body.direction ?? '') - ? (body.direction as SortDirection) + const direction = isSortDirection(body.direction) + ? body.direction : undefined; const { tasks, total } = await this.taskService.getTasks( context, - userId, - roles, + decodedToken, offset, limit, // statusが指定されていない場合は全てのステータスを取得する @@ -203,22 +183,10 @@ export class TasksController { @Param() param: ChangeStatusRequest, ): Promise { // AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない - - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId, role } = decodedAccessToken as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const { role, userId } = jwt.decode(accessToken, { + json: true, + }) as AccessToken; // RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う const roles = role.split(' ') as Roles[]; @@ -273,22 +241,10 @@ export class TasksController { ): Promise { const { audioFileId } = params; // AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない - - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId, role } = decodedAccessToken as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(accessToken, { + json: true, + }) as AccessToken; const context = makeContext(userId); @@ -340,22 +296,10 @@ export class TasksController { ): Promise { const { audioFileId } = params; // AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない - - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId, role } = decodedAccessToken as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const { userId, role } = jwt.decode(accessToken, { + json: true, + }) as AccessToken; // RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う const roles = role.split(' ') as Roles[]; @@ -409,22 +353,10 @@ export class TasksController { ): Promise { const { audioFileId } = params; // AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない - - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(accessToken, { + json: true, + }) as AccessToken; const context = makeContext(userId); @@ -559,22 +491,11 @@ export class TasksController { @Body() body: PostCheckoutPermissionRequest, ): Promise { const { assignees } = body; + const accessToken = retrieveAuthorizationToken(req); - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId, role } = decodedAccessToken as AccessToken; + const { role, userId } = jwt.decode(accessToken, { + json: true, + }) as AccessToken; // RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う const roles = role.split(' ') as Roles[]; diff --git a/dictation_server/src/features/tasks/tasks.service.spec.ts b/dictation_server/src/features/tasks/tasks.service.spec.ts index bc61100..6b11396 100644 --- a/dictation_server/src/features/tasks/tasks.service.spec.ts +++ b/dictation_server/src/features/tasks/tasks.service.spec.ts @@ -16,12 +16,11 @@ import { createUserGroup, getCheckoutPermissions, getTask, - makeTaskTestingModuleWithNotificaiton, + makeTaskTestingModule, } from './test/utility'; import { Adb2cTooManyRequestsError } from '../../gateways/adb2c/adb2c.service'; import { makeContext } from '../../common/log'; import { makeTestSimpleAccount, makeTestUser } from '../../common/test/utility'; -import { ADMIN_ROLES, USER_ROLES } from '../../constants'; describe('TasksService', () => { it('タスク一覧を取得できる(admin)', async () => { @@ -40,7 +39,7 @@ describe('TasksService', () => { notificationhubServiceMockValue, ); - const userId = 'userId'; + const accessToken = { userId: 'userId', role: 'admin', tier: 5 }; const offset = 0; const limit = 20; const status = ['Uploaded', 'Backup']; @@ -49,8 +48,7 @@ describe('TasksService', () => { expect( await service.tasksService.getTasks( makeContext('trackingId'), - userId, - [ADMIN_ROLES.ADMIN, USER_ROLES.NONE], + accessToken, offset, limit, status, @@ -104,6 +102,7 @@ describe('TasksService', () => { const userGroupsRepositoryMockValue = makeDefaultUserGroupsRepositoryMockValue(); const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue(); + const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); usersRepositoryMockValue.findUserByExternalId = new Error('DB failed'); @@ -115,7 +114,7 @@ describe('TasksService', () => { notificationhubServiceMockValue, ); - const userId = 'userId'; + const accessToken = { userId: 'userId', role: 'admin', tier: 5 }; const offset = 0; const limit = 20; const status = ['Uploaded', 'Backup']; @@ -124,8 +123,7 @@ describe('TasksService', () => { await expect( service.tasksService.getTasks( makeContext('trackingId'), - userId, - [ADMIN_ROLES.ADMIN, USER_ROLES.NONE], + accessToken, offset, limit, status, @@ -157,7 +155,7 @@ describe('TasksService', () => { notificationhubServiceMockValue, ); - const userId = 'userId'; + const accessToken = { userId: 'userId', role: 'admin', tier: 5 }; const offset = 0; const limit = 20; const status = ['Uploaded', 'Backup']; @@ -166,8 +164,7 @@ describe('TasksService', () => { await expect( service.tasksService.getTasks( makeContext('trackingId'), - userId, - [ADMIN_ROLES.ADMIN, USER_ROLES.NONE], + accessToken, offset, limit, status, @@ -195,13 +192,7 @@ describe('TasksService', () => { status: 'Uploaded', priority: '00', created_at: new Date('2023-01-01T01:01:01.000'), - option_items: null, // 存在しない場合でも空配列であるはずのものがnullになっているため形式不正 - finished_at: null, - started_at: null, - typist_user_id: null, - template_file_id: null, - typist_user: null, - template_file: null, + option_items: undefined, // 存在しない場合でも空配列であるはずのものがundefined file: { id: 1, account_id: 1, @@ -219,8 +210,6 @@ describe('TasksService', () => { audio_format: 'DS', comment: 'comment', is_encrypted: true, - deleted_at: null, - task: null, }, }, ], @@ -240,7 +229,7 @@ describe('TasksService', () => { adb2cServiceMockValue, notificationhubServiceMockValue, ); - const userId = 'userId'; + const accessToken = { userId: 'userId', role: 'admin', tier: 5 }; const offset = 0; const limit = 20; const status = ['Uploaded', 'Backup']; @@ -249,8 +238,7 @@ describe('TasksService', () => { await expect( service.tasksService.getTasks( makeContext('trackingId'), - userId, - [ADMIN_ROLES.ADMIN, USER_ROLES.NONE], + accessToken, offset, limit, status, @@ -285,7 +273,7 @@ describe('TasksService', () => { notificationhubServiceMockValue, ); - const userId = 'userId'; + const accessToken = { userId: 'userId', role: 'author', tier: 5 }; const offset = 0; const limit = 20; const status = ['Uploaded', 'Backup']; @@ -293,8 +281,7 @@ describe('TasksService', () => { const direction = 'ASC'; const result = await service.tasksService.getTasks( makeContext('trackingId'), - userId, - [USER_ROLES.AUTHOR], + accessToken, offset, limit, status, @@ -367,7 +354,7 @@ describe('TasksService', () => { notificationhubServiceMockValue, ); - const userId = 'userId'; + const accessToken = { userId: 'userId', role: 'author', tier: 5 }; const offset = 0; const limit = 20; const status = ['Uploaded', 'Backup']; @@ -376,8 +363,7 @@ describe('TasksService', () => { await expect( service.tasksService.getTasks( makeContext('trackingId'), - userId, - [USER_ROLES.AUTHOR], + accessToken, offset, limit, status, @@ -413,7 +399,7 @@ describe('TasksService', () => { notificationhubServiceMockValue, ); - const userId = 'userId'; + const accessToken = { userId: 'userId', role: 'typist', tier: 5 }; const offset = 0; const limit = 20; const status = ['Uploaded', 'Backup']; @@ -421,8 +407,7 @@ describe('TasksService', () => { const direction = 'ASC'; const result = await service.tasksService.getTasks( makeContext('trackingId'), - userId, - [USER_ROLES.TYPIST], + accessToken, offset, limit, status, @@ -495,7 +480,7 @@ describe('TasksService', () => { notificationhubServiceMockValue, ); - const userId = 'userId'; + const accessToken = { userId: 'userId', role: 'typist', tier: 5 }; const offset = 0; const limit = 20; const status = ['Uploaded', 'Backup']; @@ -504,8 +489,47 @@ describe('TasksService', () => { await expect( service.tasksService.getTasks( makeContext('trackingId'), - userId, - [USER_ROLES.TYPIST], + accessToken, + offset, + limit, + status, + paramName, + direction, + ), + ).rejects.toEqual( + new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ), + ); + }); + + it('想定外のRoleの場合、エラーを返却する', async () => { + const tasksRepositoryMockValue = makeDefaultTasksRepositoryMockValue(); + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + const userGroupsRepositoryMockValue = + makeDefaultUserGroupsRepositoryMockValue(); + const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue(); + const notificationhubServiceMockValue = + makeDefaultNotificationhubServiceMockValue(); + const service = await makeTasksServiceMock( + tasksRepositoryMockValue, + usersRepositoryMockValue, + userGroupsRepositoryMockValue, + adb2cServiceMockValue, + notificationhubServiceMockValue, + ); + + const accessToken = { userId: 'userId', role: 'XXX', tier: 5 }; + const offset = 0; + const limit = 20; + const status = ['Uploaded', 'Backup']; + const paramName = 'JOB_NUMBER'; + const direction = 'ASC'; + await expect( + service.tasksService.getTasks( + makeContext('trackingId'), + accessToken, offset, limit, status, @@ -537,7 +561,7 @@ describe('TasksService', () => { notificationhubServiceMockValue, ); - const userId = 'userId'; + const accessToken = { userId: 'userId', role: 'admin', tier: 5 }; const offset = 0; const limit = 20; const status = ['Uploaded,Backup']; @@ -546,8 +570,7 @@ describe('TasksService', () => { await expect( service.tasksService.getTasks( makeContext('trackingId'), - userId, - [ADMIN_ROLES.ADMIN, USER_ROLES.NONE], + accessToken, offset, limit, status, @@ -563,7 +586,7 @@ describe('TasksService', () => { }); describe('DBテスト', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -576,7 +599,6 @@ describe('TasksService', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); @@ -584,12 +606,10 @@ describe('TasksService', () => { it('[Admin] Taskが0件であっても実行できる', async () => { const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - if (!source) fail(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId } = await makeTestUser(source, { account_id: accountId, @@ -598,6 +618,7 @@ describe('TasksService', () => { }); const service = module.get(TasksService); + const accessToken = { userId: externalId, role: 'admin', tier: 5 }; const offset = 0; const limit = 20; const status = ['Uploaded,Backup']; @@ -606,8 +627,7 @@ describe('TasksService', () => { const { tasks, total } = await service.getTasks( makeContext('trackingId'), - externalId, - [ADMIN_ROLES.ADMIN, USER_ROLES.NONE], + accessToken, offset, limit, status, @@ -617,18 +637,15 @@ describe('TasksService', () => { expect(tasks).toEqual([]); expect(total).toEqual(0); }); - it('[Author] Authorは自分が作成者のTask一覧を取得できる', async () => { const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - if (!source) fail(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); - const { id: userId, external_id } = await makeTestUser(source, { + const { id: userId } = await makeTestUser(source, { account_id: accountId, external_id: 'userId', role: 'author', @@ -656,6 +673,7 @@ describe('TasksService', () => { ); const service = module.get(TasksService); + const accessToken = { userId: 'userId', role: 'author', tier: 5 }; const offset = 0; const limit = 20; const status = ['Uploaded', 'Backup']; @@ -664,8 +682,7 @@ describe('TasksService', () => { const { tasks, total } = await service.getTasks( makeContext('trackingId'), - external_id, - [USER_ROLES.AUTHOR], + accessToken, offset, limit, status, @@ -686,14 +703,12 @@ describe('TasksService', () => { it('[Author] Authorは同一アカウントであっても自分以外のAuhtorのTaskは取得できない', async () => { const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - if (!source) fail(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); - const { id: userId_1, external_id } = await makeTestUser(source, { + const { id: userId_1 } = await makeTestUser(source, { account_id: accountId, external_id: 'userId_1', role: 'author', @@ -728,6 +743,7 @@ describe('TasksService', () => { ); const service = module.get(TasksService); + const accessToken = { userId: 'userId_1', role: 'author', tier: 5 }; const offset = 0; const limit = 20; const status = ['Uploaded', 'Backup']; @@ -736,8 +752,7 @@ describe('TasksService', () => { const { tasks, total } = await service.getTasks( makeContext('trackingId'), - external_id, - [USER_ROLES.AUTHOR], + accessToken, offset, limit, status, @@ -754,7 +769,7 @@ describe('TasksService', () => { }); describe('changeCheckoutPermission', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -767,20 +782,17 @@ describe('changeCheckoutPermission', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('タスクのチェックアウト権限を変更できる。(個人指定)', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId_1 } = await makeTestUser(source, { account_id: accountId, @@ -835,14 +847,12 @@ describe('changeCheckoutPermission', () => { }); it('タスクのチェックアウト権限を変更できる。(グループ指定)', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId_1 } = await makeTestUser(source, { account_id: accountId, @@ -903,14 +913,12 @@ describe('changeCheckoutPermission', () => { }); it('タスクのチェックアウト権限を変更できる。(チェックアウト権限を外す)', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId_1 } = await makeTestUser(source, { account_id: accountId, @@ -954,14 +962,12 @@ describe('changeCheckoutPermission', () => { }); it('ユーザーが存在しない場合、タスクのチェックアウト権限を変更できない', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId_1 } = await makeTestUser(source, { account_id: accountId, @@ -1008,14 +1014,12 @@ describe('changeCheckoutPermission', () => { }); it('ユーザーグループが存在しない場合、タスクのチェックアウト権限を変更できない', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId_1 } = await makeTestUser(source, { account_id: accountId, @@ -1062,14 +1066,12 @@ describe('changeCheckoutPermission', () => { }); it('タスクが存在しない場合、タスクのチェックアウト権限を変更できない', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId } = await makeTestUser(source, { account_id: accountId, @@ -1098,14 +1100,12 @@ describe('changeCheckoutPermission', () => { }); it('タスクのステータスがUploadedでない場合、タスクのチェックアウト権限を変更できない', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId } = await makeTestUser(source, { account_id: accountId, @@ -1144,14 +1144,12 @@ describe('changeCheckoutPermission', () => { }); it('ユーザーのRoleがAuthorでタスクのAuthorIDと自身のAuthorIDが一致しない場合、タスクのチェックアウト権限を変更できない', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId } = await makeTestUser(source, { account_id: accountId, @@ -1190,15 +1188,13 @@ describe('changeCheckoutPermission', () => { }); it('通知に失敗した場合、エラーとなる', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); notificationhubServiceMockValue.notify = new Error('Notify Error.'); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId_1 } = await makeTestUser(source, { account_id: accountId, @@ -1254,7 +1250,7 @@ describe('changeCheckoutPermission', () => { }); describe('checkout', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -1267,20 +1263,17 @@ describe('checkout', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('ユーザーのRoleがTypistで、タスクのチェックアウト権限が個人指定である時、タスクをチェックアウトできる', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId } = await makeTestUser(source, { account_id: accountId, @@ -1322,12 +1315,15 @@ describe('checkout', () => { ['typist'], 'typist-user-external-id', ); - const resultTask = await getTask(source, taskId); + const { status, typist_user_id, started_at } = await getTask( + source, + taskId, + ); const permisions = await getCheckoutPermissions(source, taskId); - expect(resultTask?.status).toEqual('InProgress'); - expect(resultTask?.typist_user_id).toEqual(typistUserId); - expect(resultTask?.started_at).not.toEqual(initTask?.started_at); + expect(status).toEqual('InProgress'); + expect(typist_user_id).toEqual(typistUserId); + expect(started_at).not.toEqual(initTask.started_at); expect(permisions.length).toEqual(1); expect(permisions[0]).toEqual({ id: 3, @@ -1338,14 +1334,12 @@ describe('checkout', () => { }); it('ユーザーのRoleがTypistで、タスクのチェックアウト権限がグループ指定である時、タスクをチェックアウトできる', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId } = await makeTestUser(source, { account_id: accountId, @@ -1387,12 +1381,15 @@ describe('checkout', () => { ['typist'], 'typist-user-external-id', ); - const resultTask = await getTask(source, taskId); + const { status, typist_user_id, started_at } = await getTask( + source, + taskId, + ); const permisions = await getCheckoutPermissions(source, taskId); - expect(resultTask?.status).toEqual('InProgress'); - expect(resultTask?.typist_user_id).toEqual(typistUserId); - expect(resultTask?.started_at).not.toEqual(initTask?.started_at); + expect(status).toEqual('InProgress'); + expect(typist_user_id).toEqual(typistUserId); + expect(started_at).not.toEqual(initTask.started_at); expect(permisions.length).toEqual(1); expect(permisions[0]).toEqual({ id: 3, @@ -1403,14 +1400,12 @@ describe('checkout', () => { }); it('ユーザーのRoleがTypistで、タスクのステータスがPendingである時、タスクをチェックアウトできる', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId } = await makeTestUser(source, { account_id: accountId, @@ -1445,13 +1440,16 @@ describe('checkout', () => { ['typist'], 'typist-user-external-id', ); - const resultTask = await getTask(source, taskId); + const { status, typist_user_id, started_at } = await getTask( + source, + taskId, + ); const permisions = await getCheckoutPermissions(source, taskId); - expect(resultTask?.status).toEqual('InProgress'); - expect(resultTask?.typist_user_id).toEqual(typistUserId); + expect(status).toEqual('InProgress'); + expect(typist_user_id).toEqual(typistUserId); //タスクの元々のステータスがPending,Inprogressの場合、文字起こし開始時刻は更新されない - expect(resultTask?.started_at).toEqual(initTask?.started_at); + expect(started_at).toEqual(initTask.started_at); expect(permisions.length).toEqual(1); expect(permisions[0]).toEqual({ id: 2, @@ -1462,14 +1460,12 @@ describe('checkout', () => { }); it('ユーザーのRoleがTypistで、対象のタスクのStatus[Uploaded,Inprogress,Pending]以外の時、タスクをチェックアウトできない', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); await makeTestUser(source, { account_id: accountId, @@ -1508,14 +1504,12 @@ describe('checkout', () => { }); it('ユーザーのRoleがTypistで、チェックアウト権限が存在しない時、タスクをチェックアウトできない', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); await makeTestUser(source, { account_id: accountId, @@ -1554,14 +1548,12 @@ describe('checkout', () => { }); it('ユーザーのRoleがAuthorで、アップロードした音声ファイルに紐づいたタスクをチェックアウトできる(Uploaded)', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: authorUserId } = await makeTestUser(source, { account_id: accountId, @@ -1592,14 +1584,12 @@ describe('checkout', () => { }); it('ユーザーのRoleがAuthorで、アップロードした音声ファイルに紐づいたタスクをチェックアウトできる(Finished)', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: authorUserId } = await makeTestUser(source, { account_id: accountId, @@ -1630,14 +1620,12 @@ describe('checkout', () => { }); it('ユーザーのRoleがAuthorで、アップロードした音声ファイルに紐づいたタスクが存在しない場合、タスクをチェックアウトできない', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); await makeTestUser(source, { account_id: accountId, @@ -1660,14 +1648,12 @@ describe('checkout', () => { }); it('ユーザーのRoleがAuthorで、音声ファイルに紐づいたタスクでユーザーと一致するAuthorIDでない場合、タスクをチェックアウトできない', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: authorUserId } = await makeTestUser(source, { account_id: accountId, @@ -1700,14 +1686,12 @@ describe('checkout', () => { }); it('ユーザーのRoleに[Typist,author]が設定されていない時、タスクをチェックアウトできない', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); await makeTestUser(source, { account_id: accountId, @@ -1731,7 +1715,7 @@ describe('checkout', () => { }); describe('checkin', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -1744,20 +1728,17 @@ describe('checkin', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('API実行者が文字起こし実行中のタスクである場合、タスクをチェックインできる', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId } = await makeTestUser(source, { account_id: accountId, @@ -1792,21 +1773,19 @@ describe('checkin', () => { 1, 'typist-user-external-id', ); - const resultTask = await getTask(source, taskId); + const { status, finished_at } = await getTask(source, taskId); - expect(resultTask?.status).toEqual('Finished'); - expect(resultTask?.finished_at).not.toEqual(initTask?.finished_at); + expect(status).toEqual('Finished'); + expect(finished_at).not.toEqual(initTask.finished_at); }); it('タスクのステータスがInprogressでない時、タスクをチェックインできない', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId } = await makeTestUser(source, { account_id: accountId, @@ -1841,14 +1820,12 @@ describe('checkin', () => { }); it('API実行者が文字起こし実行中のタスクでない場合、タスクをチェックインできない', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); await makeTestUser(source, { account_id: accountId, @@ -1889,14 +1866,12 @@ describe('checkin', () => { }); it('タスクがない時、タスクをチェックインできない', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); await makeTestUser(source, { account_id: accountId, @@ -1922,7 +1897,7 @@ describe('checkin', () => { }); describe('suspend', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -1935,20 +1910,17 @@ describe('suspend', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('API実行者が文字起こし実行中のタスクである場合、タスクを中断できる', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId } = await makeTestUser(source, { account_id: accountId, @@ -1980,20 +1952,18 @@ describe('suspend', () => { 1, 'typist-user-external-id', ); - const resultTask = await getTask(source, taskId); + const { status } = await getTask(source, taskId); - expect(resultTask?.status).toEqual('Pending'); + expect(status).toEqual('Pending'); }); it('タスクのステータスがInprogressでない時、タスクを中断できない', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId } = await makeTestUser(source, { account_id: accountId, @@ -2028,14 +1998,12 @@ describe('suspend', () => { }); it('API実行者が文字起こし実行中のタスクでない場合、タスクを中断できない', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); await makeTestUser(source, { account_id: accountId, @@ -2076,14 +2044,12 @@ describe('suspend', () => { }); it('タスクがない時、タスクを中断できない', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); await makeTestUser(source, { account_id: accountId, @@ -2109,7 +2075,7 @@ describe('suspend', () => { }); describe('cancel', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -2122,20 +2088,17 @@ describe('cancel', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('API実行者のRoleがTypistの場合、自身が文字起こし実行中のタスクをキャンセルできる', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId } = await makeTestUser(source, { account_id: accountId, @@ -2169,23 +2132,21 @@ describe('cancel', () => { 'typist-user-external-id', ['typist', 'standard'], ); - const resultTask = await getTask(source, taskId); + const { status, typist_user_id } = await getTask(source, taskId); const permisions = await getCheckoutPermissions(source, taskId); - expect(resultTask?.status).toEqual('Uploaded'); - expect(resultTask?.typist_user_id).toEqual(null); + expect(status).toEqual('Uploaded'); + expect(typist_user_id).toEqual(null); expect(permisions.length).toEqual(0); }); it('API実行者のRoleがTypistの場合、自身が文字起こし中断しているタスクをキャンセルできる', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId } = await makeTestUser(source, { account_id: accountId, @@ -2218,24 +2179,22 @@ describe('cancel', () => { 'typist-user-external-id', ['typist', 'standard'], ); + const { status, typist_user_id } = await getTask(source, taskId); - const resultTask = await getTask(source, taskId); const permisions = await getCheckoutPermissions(source, taskId); - expect(resultTask?.status).toEqual('Uploaded'); - expect(resultTask?.typist_user_id).toEqual(null); + expect(status).toEqual('Uploaded'); + expect(typist_user_id).toEqual(null); expect(permisions.length).toEqual(0); }); it('API実行者のRoleがAdminの場合、文字起こし実行中のタスクをキャンセルできる', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId } = await makeTestUser(source, { account_id: accountId, @@ -2270,23 +2229,21 @@ describe('cancel', () => { 'typist-user-external-id', ['admin', 'author'], ); - const resultTask = await getTask(source, taskId); + const { status, typist_user_id } = await getTask(source, taskId); const permisions = await getCheckoutPermissions(source, taskId); - expect(resultTask?.status).toEqual('Uploaded'); - expect(resultTask?.typist_user_id).toEqual(null); + expect(status).toEqual('Uploaded'); + expect(typist_user_id).toEqual(null); expect(permisions.length).toEqual(0); }); it('API実行者のRoleがAdminの場合、文字起こし中断しているタスクをキャンセルできる', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId } = await makeTestUser(source, { account_id: accountId, @@ -2321,23 +2278,21 @@ describe('cancel', () => { 'typist-user-external-id', ['admin', 'author'], ); - const resultTask = await getTask(source, taskId); + const { status, typist_user_id } = await getTask(source, taskId); const permisions = await getCheckoutPermissions(source, taskId); - expect(resultTask?.status).toEqual('Uploaded'); - expect(resultTask?.typist_user_id).toEqual(null); + expect(status).toEqual('Uploaded'); + expect(typist_user_id).toEqual(null); expect(permisions.length).toEqual(0); }); it('タスクのステータスが[Inprogress,Pending]でない時、タスクをキャンセルできない', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: typistUserId } = await makeTestUser(source, { account_id: accountId, @@ -2375,14 +2330,12 @@ describe('cancel', () => { }); it('API実行者のRoleがTypistの場合、他人が文字起こし実行中のタスクをキャンセルできない', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); await makeTestUser(source, { account_id: accountId, @@ -2426,14 +2379,12 @@ describe('cancel', () => { }); it('タスクがない時、タスクをキャンセルできない', async () => { - if (!source) fail(); const notificationhubServiceMockValue = makeDefaultNotificationhubServiceMockValue(); - const module = await makeTaskTestingModuleWithNotificaiton( + const module = await makeTaskTestingModule( source, notificationhubServiceMockValue, ); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); await makeTestUser(source, { account_id: accountId, diff --git a/dictation_server/src/features/tasks/tasks.service.ts b/dictation_server/src/features/tasks/tasks.service.ts index 902dfc1..23659b7 100644 --- a/dictation_server/src/features/tasks/tasks.service.ts +++ b/dictation_server/src/features/tasks/tasks.service.ts @@ -45,10 +45,10 @@ export class TasksService { private readonly notificationhubService: NotificationhubService, ) {} + // TODO [Task2244] 引数にAccessTokenがあるのは不適切なのでController側で分解したい async getTasks( context: Context, - userId: string, - roles: Roles[], + accessToken: AccessToken, offset: number, limit: number, status?: string[], @@ -59,6 +59,10 @@ export class TasksService { `[IN] [${context.trackingId}] ${this.getTasks.name} | params: { offset: ${offset}, limit: ${limit}, status: ${status}, paramName: ${paramName}, direction: ${direction} };`, ); + const { role, userId } = accessToken; + // TODO [Task2244] Roleに型で定義されている値が入っているかをチェックして異常値を弾く実装に修正する + const roles = role.split(' '); + // パラメータが省略された場合のデフォルト値: 保存するソート条件の値の初期値と揃える const defaultParamName: TaskListSortableAttribute = 'JOB_NUMBER'; const defaultDirection: SortDirection = 'ASC'; @@ -91,10 +95,6 @@ export class TasksService { return { tasks: tasks, total: result.count }; } if (roles.includes(USER_ROLES.AUTHOR)) { - // API実行者がAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする - if (!author_id) { - throw new Error('AuthorID not found'); - } const result = await this.taskRepository.getTasksFromAuthorIdAndAccountId( author_id, @@ -179,10 +179,6 @@ export class TasksService { await this.usersRepository.findUserByExternalId(externalId); if (roles.includes(USER_ROLES.AUTHOR)) { - // API実行者がAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする - if (!author_id) { - throw new Error('AuthorID not found'); - } await this.taskRepository.getTaskFromAudioFileId( audioFileId, account_id, @@ -411,21 +407,30 @@ export class TasksService { permissions: CheckoutPermission[], ): Promise { // 割り当て候補の外部IDを列挙 - const assigneesExternalIds = permissions.flatMap((permission) => - permission.user ? [permission.user.external_id] : [], - ); + const assigneesExternalIds = permissions.map((x) => { + if (x.user) { + return x.user.external_id; + } + }); // 割り当てられているタイピストの外部IDを列挙 - const typistExternalIds = tasks.flatMap((task) => - task.typist_user ? [task.typist_user.external_id] : [], - ); + const typistExternalIds = tasks.flatMap((x) => { + if (x.typist_user) { + return x.typist_user.external_id; + } + }); //重複をなくす const distinctedExternalIds = [ ...new Set(assigneesExternalIds.concat(typistExternalIds)), ]; + // undefinedがあった場合、取り除く + const filteredExternalIds: string[] = distinctedExternalIds.filter( + (x): x is string => x !== undefined, + ); + // B2Cからユーザー名を取得する - return await this.adB2cService.getUsers(context, distinctedExternalIds); + return await this.adB2cService.getUsers(context, filteredExternalIds); } /** * 文字起こし候補を変更する @@ -446,14 +451,10 @@ export class TasksService { ); const { author_id, account_id } = await this.usersRepository.findUserByExternalId(externalId); - // RoleがAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする - if (role.includes(USER_ROLES.AUTHOR) && !author_id) { - throw new Error('AuthorID not found'); - } await this.taskRepository.changeCheckoutPermission( audioFileId, - author_id ?? undefined, + author_id, account_id, role, assignees, @@ -461,16 +462,11 @@ export class TasksService { // すべての割り当て候補ユーザーを取得する const assigneesGroupIds = assignees - .filter((assignee) => assignee.typistGroupId) - .flatMap((assignee) => - assignee.typistGroupId ? [assignee.typistGroupId] : [], - ); - + .filter((x) => x.typistGroupId) + .map((x) => x.typistGroupId); const assigneesUserIds = assignees - .filter((assignee) => assignee.typistUserId) - .flatMap((assignee) => - assignee.typistUserId ? [assignee.typistUserId] : [], - ); + .filter((x) => x.typistUserId) + .map((x) => x.typistUserId); const groupMembers = await this.userGroupsRepositoryService.getGroupMembersFromGroupIds( diff --git a/dictation_server/src/features/tasks/test/tasks.service.mock.ts b/dictation_server/src/features/tasks/test/tasks.service.mock.ts index 1e09b84..c09742f 100644 --- a/dictation_server/src/features/tasks/test/tasks.service.mock.ts +++ b/dictation_server/src/features/tasks/test/tasks.service.mock.ts @@ -263,11 +263,6 @@ export const makeDefaultUserGroupsRepositoryMockValue = user_id: 1, created_by: 'test', updated_by: 'test', - created_at: new Date(), - deleted_at: null, - updated_at: null, - user: null, - userGroup: null, }, { id: 2, @@ -275,11 +270,6 @@ export const makeDefaultUserGroupsRepositoryMockValue = user_id: 2, created_by: 'test', updated_by: 'test', - created_at: new Date(), - deleted_at: null, - updated_at: null, - user: null, - userGroup: null, }, { id: 3, @@ -287,11 +277,6 @@ export const makeDefaultUserGroupsRepositoryMockValue = user_id: 1, created_by: 'test', updated_by: 'test', - created_at: new Date(), - deleted_at: null, - updated_at: null, - user: null, - userGroup: null, }, { id: 4, @@ -299,11 +284,6 @@ export const makeDefaultUserGroupsRepositoryMockValue = user_id: 1, created_by: 'test', updated_by: 'test', - created_at: new Date(), - deleted_at: null, - updated_at: null, - user: null, - userGroup: null, }, { id: 5, @@ -311,11 +291,6 @@ export const makeDefaultUserGroupsRepositoryMockValue = user_id: 3, created_by: 'test', updated_by: 'test', - created_at: new Date(), - deleted_at: null, - updated_at: null, - user: null, - userGroup: null, }, ], }; @@ -332,7 +307,7 @@ export const makeDefaultUsersRepositoryMockValue = user1.auto_renew = false; user1.license_alert = false; user1.notification = false; - user1.deleted_at = null; + user1.deleted_at = undefined; user1.created_by = 'test'; user1.created_at = new Date(); user1.author_id = 'abcdef'; @@ -356,82 +331,66 @@ const defaultTasksRepositoryMockValue: { status: 'Uploaded', priority: '00', created_at: new Date('2023-01-01T01:01:01.000Z'), - finished_at: null, - started_at: null, - typist_user_id: null, - template_file_id: null, - typist_user: null, - template_file: null, option_items: [ { id: 1, audio_file_id: 1, label: 'label01', value: 'value01', - task: null, }, { id: 2, audio_file_id: 1, label: 'label02', value: 'value02', - task: null, }, { id: 3, audio_file_id: 1, label: 'label03', value: 'value03', - task: null, }, { id: 4, audio_file_id: 1, label: 'label04', value: 'value04', - task: null, }, { id: 5, audio_file_id: 1, label: 'label05', value: 'value05', - task: null, }, { id: 6, audio_file_id: 1, label: 'label06', value: 'value06', - task: null, }, { id: 7, audio_file_id: 1, label: 'label07', value: 'value07', - task: null, }, { id: 8, audio_file_id: 1, label: 'label08', value: 'value08', - task: null, }, { id: 9, audio_file_id: 1, label: 'label09', value: 'value09', - task: null, }, { id: 10, audio_file_id: 1, label: 'label10', value: 'value10', - task: null, }, ], file: { @@ -451,8 +410,6 @@ const defaultTasksRepositoryMockValue: { audio_format: 'DS', comment: 'comment', is_encrypted: true, - deleted_at: null, - task: null, }, }, ], @@ -478,16 +435,7 @@ const defaultTasksRepositoryMockValue: { created_at: new Date(), updated_by: 'test', updated_at: new Date(), - account: null, - author_id: null, - deleted_at: null, - encryption_password: null, - license: null, - userGroupMembers: null, }, - task: null, - user_group_id: null, - user_group: null, }, ], count: 1, diff --git a/dictation_server/src/features/tasks/test/utility.ts b/dictation_server/src/features/tasks/test/utility.ts index 8b8d1c4..8c49279 100644 --- a/dictation_server/src/features/tasks/test/utility.ts +++ b/dictation_server/src/features/tasks/test/utility.ts @@ -39,10 +39,10 @@ import { makeNotificationhubServiceMock, } from './tasks.service.mock'; -export const makeTaskTestingModuleWithNotificaiton = async ( +export const makeTaskTestingModule = async ( datasource: DataSource, notificationhubServiceMockValue: NotificationhubServiceMockValue, -): Promise => { +): Promise => { try { const module: TestingModule = await Test.createTestingModule({ imports: [ @@ -205,7 +205,7 @@ export const createUserGroup = async ( export const getTask = async ( datasource: DataSource, task_id: number, -): Promise => { +): Promise => { const task = await datasource.getRepository(Task).findOne({ where: { id: task_id, diff --git a/dictation_server/src/features/tasks/types/convert.ts b/dictation_server/src/features/tasks/types/convert.ts index 5353542..bdb96f6 100644 --- a/dictation_server/src/features/tasks/types/convert.ts +++ b/dictation_server/src/features/tasks/types/convert.ts @@ -45,7 +45,7 @@ const createTask = ( const assignees = createAssignees(permissions, b2cUserInfo); // RepositoryDTO => ControllerDTOに変換 - const typist: Typist | undefined = + const typist: Typist = typist_user != null ? convertUserToTypist(typist_user, b2cUserInfo) : undefined; @@ -113,10 +113,7 @@ const convertUserToAssignee = ( ): Assignee => { const typistName = b2cUserInfo.find( (x) => x.id === user.external_id, - )?.displayName; - if (!typistName) { - throw new Error('typistName not found.'); - } + ).displayName; return { typistUserId: user.id, typistName, @@ -138,10 +135,7 @@ const convertUserToTypist = ( ): Typist => { const typistName = b2cUserInfo.find( (x) => x.id === user.external_id, - )?.displayName; - if (!typistName) { - throw new Error('typistName not found.'); - } + ).displayName; return { id: user.id, name: typistName, diff --git a/dictation_server/src/features/templates/templates.controller.ts b/dictation_server/src/features/templates/templates.controller.ts index ef972cf..6d85968 100644 --- a/dictation_server/src/features/templates/templates.controller.ts +++ b/dictation_server/src/features/templates/templates.controller.ts @@ -1,11 +1,4 @@ -import { - Controller, - Get, - HttpException, - HttpStatus, - Req, - UseGuards, -} from '@nestjs/common'; +import { Controller, Get, HttpStatus, Req, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, @@ -23,7 +16,6 @@ import { retrieveAuthorizationToken } from '../../common/http/helper'; import { Request } from 'express'; import { makeContext } from '../../common/log'; import { TemplatesService } from './templates.service'; -import { makeErrorResponse } from '../../common/error/makeErrorResponse'; @ApiTags('templates') @Controller('templates') @@ -54,21 +46,8 @@ export class TemplatesController { @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) @Get() async getTemplates(@Req() req: Request): Promise { - const accessToken = retrieveAuthorizationToken(req); - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(userId); const templates = await this.templatesService.getTemplates(context, userId); diff --git a/dictation_server/src/features/templates/templates.service.spec.ts b/dictation_server/src/features/templates/templates.service.spec.ts index d3b0160..c29b168 100644 --- a/dictation_server/src/features/templates/templates.service.spec.ts +++ b/dictation_server/src/features/templates/templates.service.spec.ts @@ -9,7 +9,7 @@ import { HttpException, HttpStatus } from '@nestjs/common'; import { makeErrorResponse } from '../../common/error/makeErrorResponse'; describe('getTemplates', () => { - let source: DataSource | undefined = undefined; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -22,16 +22,12 @@ describe('getTemplates', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); - source = undefined; + source = null; }); it('テンプレートファイル一覧を取得できる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); - const service = module.get(TemplatesService); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -69,10 +65,7 @@ describe('getTemplates', () => { }); it('テンプレートファイル一覧を取得できる(0件)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); - const service = module.get(TemplatesService); // 第五階層のアカウント作成 const { admin } = await makeTestAccount(source, { tier: 5 }); @@ -87,10 +80,7 @@ describe('getTemplates', () => { }); it('テンプレートファイル一覧の取得に失敗した場合、エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); - const service = module.get(TemplatesService); // 第五階層のアカウント作成 const { admin } = await makeTestAccount(source, { tier: 5 }); diff --git a/dictation_server/src/features/templates/test/utility.ts b/dictation_server/src/features/templates/test/utility.ts index 224b536..ac78857 100644 --- a/dictation_server/src/features/templates/test/utility.ts +++ b/dictation_server/src/features/templates/test/utility.ts @@ -23,9 +23,6 @@ export const createTemplateFile = async ( id: template.id, }, }); - if (!templateFile) { - fail(); - } return templateFile; }; diff --git a/dictation_server/src/features/terms/terms.controller.spec.ts b/dictation_server/src/features/terms/terms.controller.spec.ts index b57a498..7473f05 100644 --- a/dictation_server/src/features/terms/terms.controller.spec.ts +++ b/dictation_server/src/features/terms/terms.controller.spec.ts @@ -6,14 +6,11 @@ describe('TermsController', () => { let controller: TermsController; beforeEach(async () => { - const mockTermsService = {}; const module: TestingModule = await Test.createTestingModule({ controllers: [TermsController], providers: [TermsService], - }) - .overrideProvider(TermsService) - .useValue(mockTermsService) - .compile(); + }).compile(); + controller = module.get(TermsController); }); diff --git a/dictation_server/src/features/terms/terms.controller.ts b/dictation_server/src/features/terms/terms.controller.ts index 1d855ba..f28d5f8 100644 --- a/dictation_server/src/features/terms/terms.controller.ts +++ b/dictation_server/src/features/terms/terms.controller.ts @@ -1,4 +1,4 @@ -import { Controller, HttpStatus, Get } from '@nestjs/common'; +import { Controller, HttpStatus, Post } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { TermsService } from '../terms/terms.service'; import { ErrorResponse } from '../../common/error/types/types'; @@ -13,7 +13,7 @@ export class TermsController { private readonly termsService: TermsService, //private readonly cryptoService: CryptoService, ) {} - @Get() + @Post() @ApiResponse({ status: HttpStatus.OK, type: GetTermsInfoResponse, @@ -28,7 +28,12 @@ export class TermsController { async getTermsInfo(): Promise { const context = makeContext(uuidv4()); - const termsInfo = await this.termsService.getTermsInfo(context); + // TODO 仮実装。API実装タスクで本実装する。 + // const termInfo = await this.termsService.getTermsInfo(context); + const termsInfo = [ + { documentType: 'EULA', version: '1.0' }, + { documentType: 'DPA', version: '1.1' }, + ] as TermInfo[]; return { termsInfo }; } diff --git a/dictation_server/src/features/terms/terms.module.ts b/dictation_server/src/features/terms/terms.module.ts index 704e003..e314518 100644 --- a/dictation_server/src/features/terms/terms.module.ts +++ b/dictation_server/src/features/terms/terms.module.ts @@ -1,11 +1,9 @@ import { Module } from '@nestjs/common'; import { TermsController } from './terms.controller'; import { TermsService } from './terms.service'; -import { TermsRepositoryModule } from '../../repositories/terms/terms.repository.module'; @Module({ - imports: [TermsRepositoryModule], controllers: [TermsController], - providers: [TermsService], + providers: [TermsService] }) export class TermsModule {} diff --git a/dictation_server/src/features/terms/terms.service.spec.ts b/dictation_server/src/features/terms/terms.service.spec.ts index 772e9f5..6e8839b 100644 --- a/dictation_server/src/features/terms/terms.service.spec.ts +++ b/dictation_server/src/features/terms/terms.service.spec.ts @@ -1,92 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; import { TermsService } from './terms.service'; -import { DataSource } from 'typeorm'; -import { makeTestingModule } from '../../common/test/modules'; -import { createTermInfo } from '../auth/test/utility'; -import { makeContext } from '../../common/log'; -import { v4 as uuidv4 } from 'uuid'; -import { HttpException, HttpStatus } from '@nestjs/common'; -import { makeErrorResponse } from '../../common/error/makeErrorResponse'; -describe('利用規約取得', () => { - let source: DataSource | null = null; +describe('TermsService', () => { + let service: TermsService; + beforeEach(async () => { - source = new DataSource({ - type: 'sqlite', - database: ':memory:', - logging: false, - entities: [__dirname + '/../../**/*.entity{.ts,.js}'], - synchronize: true, // trueにすると自動的にmigrationが行われるため注意 - }); - return source.initialize(); + const module: TestingModule = await Test.createTestingModule({ + providers: [TermsService], + }).compile(); + + service = module.get(TermsService); }); - afterEach(async () => { - if (!source) return; - await source.destroy(); - source = null; - }); - - it('最新の利用規約情報が取得できる', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - const service = module.get(TermsService); - - await createTermInfo(source, 'EULA', 'v1.0'); - await createTermInfo(source, 'EULA', 'v1.1'); - await createTermInfo(source, 'DPA', 'v1.0'); - await createTermInfo(source, 'DPA', 'v1.2'); - - const context = makeContext(uuidv4()); - const result = await service.getTermsInfo(context); - - expect(result[0].documentType).toBe('EULA'); - expect(result[0].version).toBe('v1.1'); - expect(result[1].documentType).toBe('DPA'); - expect(result[1].version).toBe('v1.2'); - }); - - it('利用規約情報(EULA、DPA両方)が存在しない場合エラーとなる', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - const service = module.get(TermsService); - const context = makeContext(uuidv4()); - await expect(service.getTermsInfo(context)).rejects.toEqual( - new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ), - ); - }); - - it('利用規約情報(EULAのみ)が存在しない場合エラーとなる', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - const service = module.get(TermsService); - await createTermInfo(source, 'DPA', 'v1.0'); - const context = makeContext(uuidv4()); - await expect(service.getTermsInfo(context)).rejects.toEqual( - new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ), - ); - }); - - it('利用規約情報(DPAのみ)が存在しない場合エラーとなる', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - const service = module.get(TermsService); - await createTermInfo(source, 'EULA', 'v1.0'); - const context = makeContext(uuidv4()); - await expect(service.getTermsInfo(context)).rejects.toEqual( - new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ), - ); + it('should be defined', () => { + expect(service).toBeDefined(); }); }); diff --git a/dictation_server/src/features/terms/terms.service.ts b/dictation_server/src/features/terms/terms.service.ts index 526bde6..51ba395 100644 --- a/dictation_server/src/features/terms/terms.service.ts +++ b/dictation_server/src/features/terms/terms.service.ts @@ -1,44 +1,4 @@ -import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'; -import { Context } from '../../common/log'; -import { makeErrorResponse } from '../../common/error/makeErrorResponse'; -import { TermInfo } from './types/types'; -import { TermsRepositoryService } from '../../repositories/terms/terms.repository.service'; -import { TERM_TYPE } from '../../constants'; +import { Injectable } from '@nestjs/common'; @Injectable() -export class TermsService { - constructor(private readonly termsRepository: TermsRepositoryService) {} - private readonly logger = new Logger(TermsService.name); - - /** - * 利用規約情報を取得する - * return termsInfo - */ - async getTermsInfo(context: Context): Promise { - this.logger.log(`[IN] [${context.trackingId}] ${this.getTermsInfo.name}`); - try { - const { eulaVersion, dpaVersion } = - await this.termsRepository.getLatestTermsInfo(); - return [ - { - documentType: TERM_TYPE.EULA, - version: eulaVersion, - }, - { - documentType: TERM_TYPE.DPA, - version: dpaVersion, - }, - ]; - } catch (e) { - this.logger.error(`error=${e}`); - throw new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } finally { - this.logger.log( - `[OUT] [${context.trackingId}] ${this.getTermsInfo.name}`, - ); - } - } -} +export class TermsService {} diff --git a/dictation_server/src/features/terms/types/types.ts b/dictation_server/src/features/terms/types/types.ts index 6a45eae..e832cc6 100644 --- a/dictation_server/src/features/terms/types/types.ts +++ b/dictation_server/src/features/terms/types/types.ts @@ -1,17 +1,12 @@ import { ApiProperty } from '@nestjs/swagger'; +export class GetTermsInfoResponse { + termsInfo: TermInfo[]; +} + export class TermInfo { @ApiProperty({ description: '利用規約種別' }) documentType: string; @ApiProperty({ description: 'バージョン' }) version: string; } -export class GetTermsInfoResponse { - @ApiProperty({ type: [TermInfo] }) - termsInfo: TermInfo[]; -} - -export type TermsVersion = { - eulaVersion: string; - dpaVersion: string; -}; diff --git a/dictation_server/src/features/users/test/users.service.mock.ts b/dictation_server/src/features/users/test/users.service.mock.ts index b862547..036f759 100644 --- a/dictation_server/src/features/users/test/users.service.mock.ts +++ b/dictation_server/src/features/users/test/users.service.mock.ts @@ -64,7 +64,7 @@ export type ConfigMockValue = { export const makeUsersServiceMock = async ( usersRepositoryMockValue: UsersRepositoryMockValue, - licensesRepositoryMockValue: LicensesRepositoryMockValue | null, + licensesRepositoryMockValue: LicensesRepositoryMockValue, adB2cMockValue: AdB2cMockValue, sendGridMockValue: SendGridMockValue, configMockValue: ConfigMockValue, @@ -368,7 +368,7 @@ export const makeDefaultUsersRepositoryMockValue = user1.created_by = 'test'; user1.created_at = new Date(); user1.updated_by = null; - user1.updated_at = new Date(); + user1.updated_at = null; const user2 = new User(); user2.id = 3; @@ -388,7 +388,7 @@ export const makeDefaultUsersRepositoryMockValue = user2.created_by = 'test'; user2.created_at = new Date(); user2.updated_by = null; - user2.updated_at = new Date(); + user2.updated_at = null; return { updateUserVerified: undefined, @@ -406,7 +406,7 @@ const AdB2cMockUsers: AdB2cUser[] = [ displayName: 'test1', identities: [ { - signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + signInType: ADB2C_SIGN_IN_TYPE.EAMILADDRESS, issuer: 'issuer', issuerAssignedId: 'test1@mail.com', }, @@ -417,7 +417,7 @@ const AdB2cMockUsers: AdB2cUser[] = [ displayName: 'test2', identities: [ { - signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + signInType: ADB2C_SIGN_IN_TYPE.EAMILADDRESS, issuer: 'issuer', issuerAssignedId: 'test2@mail.com', }, @@ -428,7 +428,7 @@ const AdB2cMockUsers: AdB2cUser[] = [ displayName: 'test3', identities: [ { - signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + signInType: ADB2C_SIGN_IN_TYPE.EAMILADDRESS, issuer: 'issuer', issuerAssignedId: 'test3@mail.com', }, diff --git a/dictation_server/src/features/users/test/utility.ts b/dictation_server/src/features/users/test/utility.ts index ab4dfe2..7f66b68 100644 --- a/dictation_server/src/features/users/test/utility.ts +++ b/dictation_server/src/features/users/test/utility.ts @@ -107,7 +107,7 @@ export const createLicense = async ( export const makeTestingModuleWithAdb2c = async ( datasource: DataSource, adB2cMockValue: AdB2cMockValue, -): Promise => { +): Promise => { try { const module: TestingModule = await Test.createTestingModule({ imports: [ diff --git a/dictation_server/src/features/users/users.controller.spec.ts b/dictation_server/src/features/users/users.controller.spec.ts index b64a9fc..6193160 100644 --- a/dictation_server/src/features/users/users.controller.spec.ts +++ b/dictation_server/src/features/users/users.controller.spec.ts @@ -2,12 +2,10 @@ import { Test, TestingModule } from '@nestjs/testing'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; import { ConfigModule } from '@nestjs/config'; -import { AuthService } from '../auth/auth.service'; describe('UsersController', () => { let controller: UsersController; const mockUserService = {}; - const mockAuthService = {}; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -18,12 +16,10 @@ describe('UsersController', () => { }), ], controllers: [UsersController], - providers: [UsersService, AuthService], + providers: [UsersService], }) .overrideProvider(UsersService) .useValue(mockUserService) - .overrideProvider(AuthService) - .useValue(mockAuthService) .compile(); controller = module.get(UsersController); diff --git a/dictation_server/src/features/users/users.controller.ts b/dictation_server/src/features/users/users.controller.ts index ee6175a..81306ec 100644 --- a/dictation_server/src/features/users/users.controller.ts +++ b/dictation_server/src/features/users/users.controller.ts @@ -41,7 +41,6 @@ import { UpdateAcceptedVersionResponse, } from './types/types'; import { UsersService } from './users.service'; -import { AuthService } from '../auth/auth.service'; import jwt from 'jsonwebtoken'; import { AuthGuard } from '../../common/guards/auth/authguards'; import { @@ -57,10 +56,7 @@ import { v4 as uuidv4 } from 'uuid'; @ApiTags('users') @Controller('users') export class UsersController { - constructor( - private readonly usersService: UsersService, - private readonly authService: AuthService, - ) {} + constructor(private readonly usersService: UsersService) {} @ApiResponse({ status: HttpStatus.OK, @@ -130,23 +126,10 @@ export class UsersController { @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) @Get() async getUsers(@Req() req: Request): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const decodedToken = jwt.decode(accessToken, { json: true }) as AccessToken; - const users = await this.usersService.getUsers(userId); + const users = await this.usersService.getUsers(decodedToken.userId); return { users }; } @@ -192,28 +175,15 @@ export class UsersController { prompt, } = body; - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const payload = jwt.decode(accessToken, { json: true }) as AccessToken; - const context = makeContext(userId); + const context = makeContext(payload.userId); //ユーザ作成処理 await this.usersService.createUser( context, - userId, + payload, name, role as UserRoles, email, @@ -251,21 +221,8 @@ export class UsersController { @UseGuards(AuthGuard) @Get('relations') async getRelations(@Req() req: Request): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(userId); @@ -304,22 +261,8 @@ export class UsersController { @Req() req: Request, ): Promise { const { direction, paramName } = body; - - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const decodedToken = jwt.decode(accessToken, { json: true }) as AccessToken; //型チェック if ( @@ -331,7 +274,11 @@ export class UsersController { HttpStatus.BAD_REQUEST, ); } - await this.usersService.updateSortCriteria(paramName, direction, userId); + await this.usersService.updateSortCriteria( + paramName, + direction, + decodedToken, + ); return {}; } @@ -362,25 +309,11 @@ export class UsersController { @Req() req: Request, ): Promise { const {} = query; - - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const decodedToken = jwt.decode(accessToken, { json: true }) as AccessToken; const { direction, paramName } = await this.usersService.getSortCriteria( - userId, + decodedToken, ); return { direction, paramName }; } @@ -430,20 +363,7 @@ export class UsersController { } = body; const accessToken = retrieveAuthorizationToken(req); - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken; const context = makeContext(userId); @@ -497,21 +417,8 @@ export class UsersController { @Body() body: AllocateLicenseRequest, @Req() req: Request, ): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken; const context = makeContext(userId); await this.usersService.allocateLicense( @@ -556,21 +463,8 @@ export class UsersController { @Body() body: DeallocateLicenseRequest, @Req() req: Request, ): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const accessToken = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken; const context = makeContext(userId); @@ -601,24 +495,11 @@ export class UsersController { async updateAcceptedVersion( @Body() body: UpdateAcceptedVersionRequest, ): Promise { - const { idToken, acceptedEULAVersion, acceptedDPAVersion } = body; + const context = makeContext(uuidv4()); - const verifiedIdToken = await this.authService.getVerifiedIdToken(idToken); - const context = makeContext(verifiedIdToken.sub); - - const isVerified = await this.authService.isVerifiedUser(verifiedIdToken); - if (!isVerified) { - throw new HttpException( - makeErrorResponse('E010201'), - HttpStatus.BAD_REQUEST, - ); - } - await this.usersService.updateAcceptedVersion( - context, - verifiedIdToken.sub, - acceptedEULAVersion, - acceptedDPAVersion, - ); + // TODO 仮実装。API実装タスクで本実装する。 + // const idToken = await this.authService.getVerifiedIdToken(body.idToken); + // await this.usersService.updateAcceptedVersion(context, idToken); return {}; } } diff --git a/dictation_server/src/features/users/users.module.ts b/dictation_server/src/features/users/users.module.ts index f349a95..e4ae006 100644 --- a/dictation_server/src/features/users/users.module.ts +++ b/dictation_server/src/features/users/users.module.ts @@ -7,7 +7,6 @@ import { UsersRepositoryModule } from '../../repositories/users/users.repository import { LicensesRepositoryModule } from '../../repositories/licenses/licenses.repository.module'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; -import { AuthService } from '../auth/auth.service'; @Module({ imports: [ @@ -19,6 +18,6 @@ import { AuthService } from '../auth/auth.service'; ConfigModule, ], controllers: [UsersController], - providers: [UsersService, AuthService], + providers: [UsersService], }) export class UsersModule {} diff --git a/dictation_server/src/features/users/users.service.spec.ts b/dictation_server/src/features/users/users.service.spec.ts index 6c5c2b4..7bc2eae 100644 --- a/dictation_server/src/features/users/users.service.spec.ts +++ b/dictation_server/src/features/users/users.service.spec.ts @@ -43,10 +43,9 @@ import { makeTestSimpleAccount, makeTestUser, } from '../../common/test/utility'; -import { v4 as uuidv4 } from 'uuid'; describe('UsersService.confirmUser', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -57,15 +56,12 @@ describe('UsersService.confirmUser', () => { return source.initialize(); }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('ユーザの仮登録時に払い出されるトークンにより、未認証のユーザが認証済みになり、トライアルライセンスが100件作成される', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = (await makeTestAccount(source)).account; const { id: userId } = await makeTestUser(source, { account_id: accountId, @@ -113,10 +109,9 @@ describe('UsersService.confirmUser', () => { allocated_user_id: resultLicenses[0].allocated_user_id, order_id: resultLicenses[0].order_id, delete_order_id: resultLicenses[0].delete_order_id, - user: resultLicenses[0].user ?? null, }; - expect(resultUser?.email_verified).toBe(true); + expect(resultUser.email_verified).toBe(true); expect(resultLicenses.length).toBe(100); expect(resultLicensesCheckParam).toEqual({ id: 0, @@ -127,14 +122,11 @@ describe('UsersService.confirmUser', () => { allocated_user_id: null, order_id: null, delete_order_id: null, - user: null, }); - }, 600000); + }); it('トークンの形式が不正な場合、形式不正エラーとなる。', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const token = 'invalid.id.token'; const service = module.get(UsersService); await expect(service.confirmUser(token)).rejects.toEqual( @@ -143,9 +135,7 @@ describe('UsersService.confirmUser', () => { }); it('ユーザが既に認証済みだった場合、認証済みユーザエラーとなる。', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = (await makeTestAccount(source)).account; await makeTestUser(source, { account_id: accountId, @@ -177,9 +167,7 @@ describe('UsersService.confirmUser', () => { ); }); it('ユーザーが存在しない場合は、想定外のエラーとなる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(UsersService); const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw'; @@ -212,12 +200,6 @@ describe('UsersService.confirmUserAndInitPassword', () => { notification: true, encryption: false, prompt: false, - account: null, - author_id: null, - deleted_at: null, - encryption_password: null, - license: null, - userGroupMembers: null, }; const licensesRepositoryMockValue = null; const adb2cParam = makeDefaultAdB2cMockValue(); @@ -263,12 +245,6 @@ describe('UsersService.confirmUserAndInitPassword', () => { notification: true, encryption: false, prompt: false, - account: null, - author_id: null, - deleted_at: null, - encryption_password: null, - license: null, - userGroupMembers: null, }; const licensesRepositoryMockValue = null; const adb2cParam = makeDefaultAdB2cMockValue(); @@ -310,12 +286,6 @@ describe('UsersService.confirmUserAndInitPassword', () => { notification: true, encryption: false, prompt: false, - account: null, - author_id: null, - deleted_at: null, - encryption_password: null, - license: null, - userGroupMembers: null, }; const licensesRepositoryMockValue = null; const adb2cParam = makeDefaultAdB2cMockValue(); @@ -361,12 +331,6 @@ describe('UsersService.confirmUserAndInitPassword', () => { notification: true, encryption: false, prompt: false, - account: null, - author_id: null, - deleted_at: null, - encryption_password: null, - license: null, - userGroupMembers: null, }; const licensesRepositoryMockValue = null; const adb2cParam = makeDefaultAdB2cMockValue(); @@ -397,7 +361,7 @@ describe('UsersService.confirmUserAndInitPassword', () => { }); describe('UsersService.createUser', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -410,15 +374,12 @@ describe('UsersService.createUser', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:None)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(UsersService); const adminExternalId = 'ADMIN0001'; @@ -471,7 +432,7 @@ describe('UsersService.createUser', () => { expect( await service.createUser( makeContext('trackingId'), - adminExternalId, + token, name, role, email, @@ -484,16 +445,16 @@ describe('UsersService.createUser', () => { // 追加されたユーザーが正しくDBに登録されていることを確認 const user = await getUserFromExternalId(source, externalId); expect(user).not.toBeNull(); - expect(user?.account_id).toEqual(accountId); - expect(user?.role).toEqual(role); - expect(user?.author_id).toEqual(null); - expect(user?.email_verified).toEqual(false); - expect(user?.auto_renew).toEqual(autoRenew); - expect(user?.license_alert).toEqual(licenseAlert); - expect(user?.notification).toEqual(notification); - expect(user?.encryption).toEqual(false); - expect(user?.encryption_password).toEqual(null); - expect(user?.prompt).toEqual(false); + expect(user.account_id).toEqual(accountId); + expect(user.role).toEqual(role); + expect(user.author_id).toEqual(null); + expect(user.email_verified).toEqual(false); + expect(user.auto_renew).toEqual(autoRenew); + expect(user.license_alert).toEqual(licenseAlert); + expect(user.notification).toEqual(notification); + expect(user.encryption).toEqual(false); + expect(user.encryption_password).toEqual(null); + expect(user.prompt).toEqual(false); // 他にユーザーが登録されていないことを確認 const users = await getUsers(source); @@ -501,9 +462,7 @@ describe('UsersService.createUser', () => { }); it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Author; 暗号化あり)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(UsersService); const adminExternalId = 'ADMIN0001'; const { account, admin } = await makeTestAccount( @@ -559,7 +518,7 @@ describe('UsersService.createUser', () => { expect( await service.createUser( makeContext('trackingId'), - adminExternalId, + token, name, role, email, @@ -576,16 +535,16 @@ describe('UsersService.createUser', () => { // 追加されたユーザーが正しくDBに登録されていることを確認 const user = await getUserFromExternalId(source, externalId); expect(user).not.toBeNull(); - expect(user?.account_id).toEqual(accountId); - expect(user?.role).toEqual(role); - expect(user?.author_id).toEqual(authorId); - expect(user?.email_verified).toEqual(false); - expect(user?.auto_renew).toEqual(autoRenew); - expect(user?.license_alert).toEqual(licenseAlert); - expect(user?.notification).toEqual(notification); - expect(user?.encryption).toEqual(encryption); - expect(user?.encryption_password).toEqual(encryptionPassword); - expect(user?.prompt).toEqual(prompt); + expect(user.account_id).toEqual(accountId); + expect(user.role).toEqual(role); + expect(user.author_id).toEqual(authorId); + expect(user.email_verified).toEqual(false); + expect(user.auto_renew).toEqual(autoRenew); + expect(user.license_alert).toEqual(licenseAlert); + expect(user.notification).toEqual(notification); + expect(user.encryption).toEqual(encryption); + expect(user.encryption_password).toEqual(encryptionPassword); + expect(user.prompt).toEqual(prompt); // 他にユーザーが登録されていないことを確認 const users = await getUsers(source); @@ -593,9 +552,7 @@ describe('UsersService.createUser', () => { }); it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Author; 暗号化無し)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(UsersService); const adminExternalId = 'ADMIN0001'; const { account, admin } = await makeTestAccount( @@ -650,7 +607,7 @@ describe('UsersService.createUser', () => { expect( await service.createUser( makeContext('trackingId'), - adminExternalId, + token, name, role, email, @@ -667,16 +624,16 @@ describe('UsersService.createUser', () => { // 追加されたユーザーが正しくDBに登録されていることを確認 const user = await getUserFromExternalId(source, externalId); expect(user).not.toBeNull(); - expect(user?.account_id).toEqual(accountId); - expect(user?.role).toEqual(role); - expect(user?.author_id).toEqual(authorId); - expect(user?.email_verified).toEqual(false); - expect(user?.auto_renew).toEqual(autoRenew); - expect(user?.license_alert).toEqual(licenseAlert); - expect(user?.notification).toEqual(notification); - expect(user?.encryption).toEqual(encryption); - expect(user?.encryption_password).toBeNull(); - expect(user?.prompt).toEqual(prompt); + expect(user.account_id).toEqual(accountId); + expect(user.role).toEqual(role); + expect(user.author_id).toEqual(authorId); + expect(user.email_verified).toEqual(false); + expect(user.auto_renew).toEqual(autoRenew); + expect(user.license_alert).toEqual(licenseAlert); + expect(user.notification).toEqual(notification); + expect(user.encryption).toEqual(encryption); + expect(user.encryption_password).toBeNull(); + expect(user.prompt).toEqual(prompt); // 他にユーザーが登録されていないことを確認 const users = await getUsers(source); @@ -684,9 +641,7 @@ describe('UsersService.createUser', () => { }); it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Transcriptioninst)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(UsersService); const adminExternalId = 'ADMIN0001'; const { account, admin } = await makeTestAccount( @@ -738,7 +693,7 @@ describe('UsersService.createUser', () => { expect( await service.createUser( makeContext('trackingId'), - adminExternalId, + token, name, role, email, @@ -751,16 +706,16 @@ describe('UsersService.createUser', () => { // 追加されたユーザーが正しくDBに登録されていることを確認 const user = await getUserFromExternalId(source, externalId); expect(user).not.toBeNull(); - expect(user?.account_id).toEqual(accountId); - expect(user?.role).toEqual(role); - expect(user?.author_id).toBeNull(); - expect(user?.email_verified).toEqual(false); - expect(user?.auto_renew).toEqual(autoRenew); - expect(user?.license_alert).toEqual(licenseAlert); - expect(user?.notification).toEqual(notification); - expect(user?.encryption).toEqual(false); - expect(user?.encryption_password).toBeNull(); - expect(user?.prompt).toEqual(false); + expect(user.account_id).toEqual(accountId); + expect(user.role).toEqual(role); + expect(user.author_id).toBeNull(); + expect(user.email_verified).toEqual(false); + expect(user.auto_renew).toEqual(autoRenew); + expect(user.license_alert).toEqual(licenseAlert); + expect(user.notification).toEqual(notification); + expect(user.encryption).toEqual(false); + expect(user.encryption_password).toBeNull(); + expect(user.prompt).toEqual(false); // 他にユーザーが登録されていないことを確認 const users = await getUsers(source); @@ -768,9 +723,7 @@ describe('UsersService.createUser', () => { }); it('DBネットワークエラーとなる場合、リカバリ処理を実施し、ADB2Cに作成したユーザーを削除する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(UsersService); const b2cService = module.get(AdB2cService); const adminExternalId = 'ADMIN0001'; @@ -831,7 +784,7 @@ describe('UsersService.createUser', () => { try { await service.createUser( makeContext('trackingId'), - adminExternalId, + token, name, role, email, @@ -855,9 +808,7 @@ describe('UsersService.createUser', () => { }); it('DBネットワークエラーとなる場合、リカバリ処理を実施されるが、そのリカバリ処理に失敗した場合、ADB2Cのユーザーは削除されない', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(UsersService); const b2cService = module.get(AdB2cService); const adminExternalId = 'ADMIN0001'; @@ -918,7 +869,7 @@ describe('UsersService.createUser', () => { try { await service.createUser( makeContext('trackingId'), - adminExternalId, + token, name, role, email, @@ -947,9 +898,7 @@ describe('UsersService.createUser', () => { }); it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(UsersService); const adminExternalId = 'ADMIN0001'; const { account, admin } = await makeTestAccount( @@ -999,7 +948,7 @@ describe('UsersService.createUser', () => { try { await service.createUser( makeContext('trackingId'), - adminExternalId, + token, name, role, email, @@ -1022,9 +971,7 @@ describe('UsersService.createUser', () => { }); it('Azure AD B2C内でメールアドレスが重複している場合、エラーとなる。', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(UsersService); const adminExternalId = 'ADMIN0001'; const { account, admin } = await makeTestAccount( @@ -1078,7 +1025,7 @@ describe('UsersService.createUser', () => { try { await service.createUser( makeContext('trackingId'), - adminExternalId, + token, name, role, email, @@ -1101,9 +1048,7 @@ describe('UsersService.createUser', () => { }); it('AuthorIDが重複している場合、エラーとなる。(AuthorID重複チェックでエラー)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(UsersService); const adminExternalId = 'ADMIN0001'; const { account, admin } = await makeTestAccount( @@ -1159,7 +1104,7 @@ describe('UsersService.createUser', () => { expect( await service.createUser( makeContext('trackingId'), - adminExternalId, + token, name, role, email_1, @@ -1200,7 +1145,7 @@ describe('UsersService.createUser', () => { try { await service.createUser( makeContext('trackingId'), - adminExternalId, + token, name, role, email_2, @@ -1229,9 +1174,7 @@ describe('UsersService.createUser', () => { }); it('AuthorIDが重複している場合、エラー(insert失敗)となり、リカバリ処理が実行され、ADB2Cに追加したユーザーが削除される', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(UsersService); const b2cService = module.get(AdB2cService); const adminExternalId = 'ADMIN0001'; @@ -1296,7 +1239,7 @@ describe('UsersService.createUser', () => { try { await service.createUser( makeContext('trackingId'), - adminExternalId, + token, name, role, email, @@ -1328,9 +1271,7 @@ describe('UsersService.createUser', () => { }); it('メール送信に失敗した場合、リカバリ処理が実行され、ADB2C,DBのユーザーが削除される', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(UsersService); const b2cService = module.get(AdB2cService); @@ -1385,7 +1326,7 @@ describe('UsersService.createUser', () => { try { await service.createUser( makeContext('trackingId'), - adminExternalId, + token, name, role, email, @@ -1415,9 +1356,7 @@ describe('UsersService.createUser', () => { }); it('メール送信に失敗した場合、リカバリ処理が実行されるが、そのリカバリ処理に失敗した場合、ADB2C,DBのユーザーが削除されない', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const service = module.get(UsersService); const b2cService = module.get(AdB2cService); @@ -1477,7 +1416,7 @@ describe('UsersService.createUser', () => { try { await service.createUser( makeContext('trackingId'), - adminExternalId, + token, name, role, email, @@ -1506,7 +1445,7 @@ describe('UsersService.createUser', () => { }); describe('UsersService.getUsers', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -1519,16 +1458,13 @@ describe('UsersService.getUsers', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('ユーザーの一覧を取得できる(ライセンス未割当)', async () => { const adb2cParam = makeDefaultAdB2cMockValue(); - if (!source) fail(); const module = await makeTestingModuleWithAdb2c(source, adb2cParam); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId_author, id: authorUserId } = @@ -1628,9 +1564,7 @@ describe('UsersService.getUsers', () => { it('ユーザーの一覧を取得できること(ライセンス割当済み)', async () => { const adb2cParam = makeDefaultAdB2cMockValue(); - if (!source) fail(); const module = await makeTestingModuleWithAdb2c(source, adb2cParam); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { id: user1, external_id: external_id1 } = await makeTestUser( @@ -1744,9 +1678,7 @@ describe('UsersService.getUsers', () => { it('DBからのユーザーの取得に失敗した場合、エラーとなる', async () => { const adb2cParam = makeDefaultAdB2cMockValue(); - if (!source) fail(); const module = await makeTestingModuleWithAdb2c(source, adb2cParam); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); await makeTestUser(source, { @@ -1769,9 +1701,7 @@ describe('UsersService.getUsers', () => { it('ADB2Cからのユーザーの取得に失敗した場合、エラーとなる', async () => { const adb2cParam = makeDefaultAdB2cMockValue(); adb2cParam.getUsers = new Error('ADB2C error'); - if (!source) fail(); const module = await makeTestingModuleWithAdb2c(source, adb2cParam); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId_author } = await makeTestUser(source, { @@ -1811,7 +1741,11 @@ describe('UsersService.updateSortCriteria', () => { ); expect( - await service.updateSortCriteria('AUTHOR_ID', 'ASC', 'external_id'), + await service.updateSortCriteria('AUTHOR_ID', 'ASC', { + role: 'none admin', + userId: 'xxxxxxxxxxxx', + tier: 5, + }), ).toEqual(undefined); }); @@ -1836,7 +1770,11 @@ describe('UsersService.updateSortCriteria', () => { ); await expect( - service.updateSortCriteria('AUTHOR_ID', 'ASC', 'external_id'), + service.updateSortCriteria('AUTHOR_ID', 'ASC', { + role: 'none admin', + userId: 'xxxxxxxxxxxx', + tier: 5, + }), ).rejects.toEqual( new HttpException( makeErrorResponse('E009999'), @@ -1867,7 +1805,11 @@ describe('UsersService.updateSortCriteria', () => { ); await expect( - service.updateSortCriteria('AUTHOR_ID', 'ASC', 'external_id'), + service.updateSortCriteria('AUTHOR_ID', 'ASC', { + role: 'none admin', + userId: 'xxxxxxxxxxxx', + tier: 5, + }), ).rejects.toEqual( new HttpException( makeErrorResponse('E009999'), @@ -1895,10 +1837,13 @@ describe('UsersService.getSortCriteria', () => { sortCriteriaRepositoryMockValue, ); - expect(await service.getSortCriteria('external_id')).toEqual({ - direction: 'ASC', - paramName: 'JOB_NUMBER', - }); + expect( + await service.getSortCriteria({ + role: 'none admin', + userId: 'xxxxxxxxxxxx', + tier: 5, + }), + ).toEqual({ direction: 'ASC', paramName: 'JOB_NUMBER' }); }); it('ソート条件が存在せず、ソート条件を取得できない', async () => { @@ -1923,7 +1868,13 @@ describe('UsersService.getSortCriteria', () => { sortCriteriaRepositoryMockValue, ); - await expect(service.getSortCriteria('external_id')).rejects.toEqual( + await expect( + service.getSortCriteria({ + role: 'none admin', + userId: 'xxxxxxxxxxxx', + tier: 5, + }), + ).rejects.toEqual( new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, @@ -1955,7 +1906,13 @@ describe('UsersService.getSortCriteria', () => { sortCriteriaRepositoryMockValue, ); - await expect(service.getSortCriteria('external_id')).rejects.toEqual( + await expect( + service.getSortCriteria({ + role: 'none admin', + userId: 'xxxxxxxxxxxx', + tier: 5, + }), + ).rejects.toEqual( new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, @@ -1965,7 +1922,7 @@ describe('UsersService.getSortCriteria', () => { }); describe('UsersService.updateUser', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -1978,15 +1935,12 @@ describe('UsersService.updateUser', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('ユーザー情報を更新できる(None)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: external_id } = await makeTestUser(source, { @@ -2030,21 +1984,19 @@ describe('UsersService.updateUser', () => { const createdUser = await getUser(source, user1); - expect(createdUser?.id).toBe(user1); - expect(createdUser?.role).toBe(USER_ROLES.NONE); - expect(createdUser?.author_id).toBeNull(); - expect(createdUser?.auto_renew).toBe(false); - expect(createdUser?.license_alert).toBe(false); - expect(createdUser?.notification).toBe(false); - expect(createdUser?.encryption).toBe(false); - expect(createdUser?.encryption_password).toBeNull(); - expect(createdUser?.prompt).toBe(false); + expect(createdUser.id).toBe(user1); + expect(createdUser.role).toBe(USER_ROLES.NONE); + expect(createdUser.author_id).toBeNull(); + expect(createdUser.auto_renew).toBe(false); + expect(createdUser.license_alert).toBe(false); + expect(createdUser.notification).toBe(false); + expect(createdUser.encryption).toBe(false); + expect(createdUser.encryption_password).toBeNull(); + expect(createdUser.prompt).toBe(false); }); it('ユーザー情報を更新できる(Typist)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: external_id } = await makeTestUser(source, { @@ -2088,21 +2040,19 @@ describe('UsersService.updateUser', () => { const createdUser = await getUser(source, user1); - expect(createdUser?.id).toBe(user1); - expect(createdUser?.role).toBe(USER_ROLES.TYPIST); - expect(createdUser?.author_id).toBeNull(); - expect(createdUser?.auto_renew).toBe(false); - expect(createdUser?.license_alert).toBe(false); - expect(createdUser?.notification).toBe(false); - expect(createdUser?.encryption).toBe(false); - expect(createdUser?.encryption_password).toBeNull(); - expect(createdUser?.prompt).toBe(false); + expect(createdUser.id).toBe(user1); + expect(createdUser.role).toBe(USER_ROLES.TYPIST); + expect(createdUser.author_id).toBeNull(); + expect(createdUser.auto_renew).toBe(false); + expect(createdUser.license_alert).toBe(false); + expect(createdUser.notification).toBe(false); + expect(createdUser.encryption).toBe(false); + expect(createdUser.encryption_password).toBeNull(); + expect(createdUser.prompt).toBe(false); }); it('ユーザー情報を更新できる(Author)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: external_id } = await makeTestUser(source, { @@ -2146,21 +2096,19 @@ describe('UsersService.updateUser', () => { const createdUser = await getUser(source, user1); - expect(createdUser?.id).toBe(user1); - expect(createdUser?.role).toBe(USER_ROLES.AUTHOR); - expect(createdUser?.author_id).toBe('AUTHOR_ID'); - expect(createdUser?.auto_renew).toBe(false); - expect(createdUser?.license_alert).toBe(false); - expect(createdUser?.notification).toBe(false); - expect(createdUser?.encryption).toBe(true); - expect(createdUser?.encryption_password).toBe('new_password'); - expect(createdUser?.prompt).toBe(true); + expect(createdUser.id).toBe(user1); + expect(createdUser.role).toBe(USER_ROLES.AUTHOR); + expect(createdUser.author_id).toBe('AUTHOR_ID'); + expect(createdUser.auto_renew).toBe(false); + expect(createdUser.license_alert).toBe(false); + expect(createdUser.notification).toBe(false); + expect(createdUser.encryption).toBe(true); + expect(createdUser.encryption_password).toBe('new_password'); + expect(createdUser.prompt).toBe(true); }); it('ユーザーのRoleを更新できる(None⇒Typist)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: external_id } = await makeTestUser(source, { @@ -2204,21 +2152,19 @@ describe('UsersService.updateUser', () => { const createdUser = await getUser(source, user1); - expect(createdUser?.id).toBe(user1); - expect(createdUser?.role).toBe(USER_ROLES.TYPIST); - expect(createdUser?.author_id).toBeNull(); - expect(createdUser?.auto_renew).toBe(false); - expect(createdUser?.license_alert).toBe(false); - expect(createdUser?.notification).toBe(false); - expect(createdUser?.encryption).toBe(false); - expect(createdUser?.encryption_password).toBeNull(); - expect(createdUser?.prompt).toBe(false); + expect(createdUser.id).toBe(user1); + expect(createdUser.role).toBe(USER_ROLES.TYPIST); + expect(createdUser.author_id).toBeNull(); + expect(createdUser.auto_renew).toBe(false); + expect(createdUser.license_alert).toBe(false); + expect(createdUser.notification).toBe(false); + expect(createdUser.encryption).toBe(false); + expect(createdUser.encryption_password).toBeNull(); + expect(createdUser.prompt).toBe(false); }); it('ユーザーのRoleを更新できる(None⇒Author)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: external_id } = await makeTestUser(source, { @@ -2262,21 +2208,19 @@ describe('UsersService.updateUser', () => { const createdUser = await getUser(source, user1); - expect(createdUser?.id).toBe(user1); - expect(createdUser?.role).toBe(USER_ROLES.AUTHOR); - expect(createdUser?.author_id).toBe('AUTHOR_ID'); - expect(createdUser?.auto_renew).toBe(false); - expect(createdUser?.license_alert).toBe(false); - expect(createdUser?.notification).toBe(false); - expect(createdUser?.encryption).toBe(false); - expect(createdUser?.encryption_password).toBeNull(); - expect(createdUser?.prompt).toBe(false); + expect(createdUser.id).toBe(user1); + expect(createdUser.role).toBe(USER_ROLES.AUTHOR); + expect(createdUser.author_id).toBe('AUTHOR_ID'); + expect(createdUser.auto_renew).toBe(false); + expect(createdUser.license_alert).toBe(false); + expect(createdUser.notification).toBe(false); + expect(createdUser.encryption).toBe(false); + expect(createdUser.encryption_password).toBeNull(); + expect(createdUser.prompt).toBe(false); }); it('None以外からRoleを変更した場合、エラーとなる(Typist⇒None)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: external_id } = await makeTestUser(source, { @@ -2322,9 +2266,7 @@ describe('UsersService.updateUser', () => { }); it('Authorがパスワードundefinedで渡されたとき、元のパスワードを維持する(Encryptionがtrue)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: external_id } = await makeTestUser(source, { @@ -2368,21 +2310,19 @@ describe('UsersService.updateUser', () => { const createdUser = await getUser(source, user1); - expect(createdUser?.id).toBe(user1); - expect(createdUser?.role).toBe(USER_ROLES.AUTHOR); - expect(createdUser?.author_id).toBe('AUTHOR_ID'); - expect(createdUser?.auto_renew).toBe(false); - expect(createdUser?.license_alert).toBe(false); - expect(createdUser?.notification).toBe(false); - expect(createdUser?.encryption).toBe(true); - expect(createdUser?.encryption_password).toBe('password'); - expect(createdUser?.prompt).toBe(true); + expect(createdUser.id).toBe(user1); + expect(createdUser.role).toBe(USER_ROLES.AUTHOR); + expect(createdUser.author_id).toBe('AUTHOR_ID'); + expect(createdUser.auto_renew).toBe(false); + expect(createdUser.license_alert).toBe(false); + expect(createdUser.notification).toBe(false); + expect(createdUser.encryption).toBe(true); + expect(createdUser.encryption_password).toBe('password'); + expect(createdUser.prompt).toBe(true); }); it('Authorが暗号化なしで更新した場合、パスワードをNULLにする(Encryptionがfalse)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: external_id } = await makeTestUser(source, { @@ -2426,21 +2366,19 @@ describe('UsersService.updateUser', () => { const createdUser = await getUser(source, user1); - expect(createdUser?.id).toBe(user1); - expect(createdUser?.role).toBe(USER_ROLES.AUTHOR); - expect(createdUser?.author_id).toBe('AUTHOR_ID'); - expect(createdUser?.auto_renew).toBe(false); - expect(createdUser?.license_alert).toBe(false); - expect(createdUser?.notification).toBe(false); - expect(createdUser?.encryption).toBe(false); - expect(createdUser?.encryption_password).toBeNull(); - expect(createdUser?.prompt).toBe(true); + expect(createdUser.id).toBe(user1); + expect(createdUser.role).toBe(USER_ROLES.AUTHOR); + expect(createdUser.author_id).toBe('AUTHOR_ID'); + expect(createdUser.auto_renew).toBe(false); + expect(createdUser.license_alert).toBe(false); + expect(createdUser.notification).toBe(false); + expect(createdUser.encryption).toBe(false); + expect(createdUser.encryption_password).toBeNull(); + expect(createdUser.prompt).toBe(true); }); it('AuthorのDBにパスワードが設定されていない場合、パスワードundefinedでわたすとエラーとなる(Encryptionがtrue)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: external_id } = await makeTestUser(source, { @@ -2486,9 +2424,7 @@ describe('UsersService.updateUser', () => { }); it('AuthorIdが既存のユーザーと重複した場合、エラーとなる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: external_id } = await makeTestUser(source, { @@ -2544,83 +2480,3 @@ describe('UsersService.updateUser', () => { ); }); }); - -describe('UsersService.updateAcceptedVersion', () => { - let source: DataSource | null = null; - beforeEach(async () => { - source = new DataSource({ - type: 'sqlite', - database: ':memory:', - logging: false, - entities: [__dirname + '/../../**/*.entity{.ts,.js}'], - synchronize: true, // trueにすると自動的にmigrationが行われるため注意 - }); - return source.initialize(); - }); - - afterEach(async () => { - if (!source) return; - await source.destroy(); - source = null; - }); - - it('同意済み利用規約バージョンを更新できる(第五)', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - const { admin } = await makeTestAccount(source, { - tier: 5, - }); - const context = makeContext(uuidv4()); - - const service = module.get(UsersService); - await service.updateAcceptedVersion(context, admin.external_id, 'v2.0'); - const user = await getUser(source, admin.id); - - expect(user?.accepted_eula_version).toBe('v2.0'); - }); - - it('同意済み利用規約バージョンを更新できる(第一~第四)', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - const { admin } = await makeTestAccount(source, { - tier: 4, - }); - const context = makeContext(uuidv4()); - - const service = module.get(UsersService); - await service.updateAcceptedVersion( - context, - admin.external_id, - 'v2.0', - 'v3.0', - ); - const user = await getUser(source, admin.id); - - expect(user?.accepted_eula_version).toBe('v2.0'); - expect(user?.accepted_dpa_version).toBe('v3.0'); - }); - - it('パラメータが不在のときエラーとなる(第一~第四)', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - const { admin } = await makeTestAccount(source, { - tier: 4, - }); - const context = makeContext(uuidv4()); - - const service = module.get(UsersService); - await expect( - service.updateAcceptedVersion( - context, - admin.external_id, - 'v2.0', - undefined, - ), - ).rejects.toEqual( - new HttpException(makeErrorResponse('E010001'), HttpStatus.BAD_REQUEST), - ); - }); -}); diff --git a/dictation_server/src/features/users/users.service.ts b/dictation_server/src/features/users/users.service.ts index 5ba3c84..a3e9521 100644 --- a/dictation_server/src/features/users/users.service.ts +++ b/dictation_server/src/features/users/users.service.ts @@ -4,7 +4,7 @@ import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { isVerifyError, verify } from '../../common/jwt'; import { getPublicKey } from '../../common/jwt/jwt'; import { makePassword } from '../../common/password/password'; -import { AccessToken, IDToken } from '../../common/token'; +import { AccessToken } from '../../common/token'; import { SortDirection, TaskListSortableAttribute, @@ -30,7 +30,6 @@ import { EmailAlreadyVerifiedError, EncryptionPasswordNeedError, InvalidRoleChangeError, - UpdateTermsVersionNotSetError, UserNotFoundError, } from '../../repositories/users/errors/types'; import { @@ -48,13 +47,9 @@ import { LicenseExpiredError, LicenseUnavailableError, } from '../../repositories/licenses/errors/types'; -import { AccountNotFoundError } from '../../repositories/accounts/errors/types'; @Injectable() export class UsersService { - private readonly logger = new Logger(UsersService.name); - private readonly mailFrom: string; - private readonly appDomain: string; constructor( private readonly usersRepository: UsersRepositoryService, private readonly licensesRepository: LicensesRepositoryService, @@ -62,10 +57,8 @@ export class UsersService { private readonly adB2cService: AdB2cService, private readonly configService: ConfigService, private readonly sendgridService: SendGridService, - ) { - this.mailFrom = this.configService.getOrThrow('MAIL_FROM'); - this.appDomain = this.configService.getOrThrow('APP_DOMAIN'); - } + ) {} + private readonly logger = new Logger(UsersService.name); /** * Confirms user @@ -134,7 +127,7 @@ export class UsersService { */ async createUser( context: Context, - externalId: string, + accessToken: AccessToken, name: string, role: UserRoles, email: string, @@ -151,7 +144,9 @@ export class UsersService { //DBよりアクセス者の所属するアカウントIDを取得する let adminUser: EntityUser; try { - adminUser = await this.usersRepository.findUserByExternalId(externalId); + adminUser = await this.usersRepository.findUserByExternalId( + accessToken.userId, + ); } catch (e) { this.logger.error(`error=${e}`); throw new HttpException( @@ -258,6 +253,9 @@ export class UsersService { //Email送信用のコンテンツを作成する try { + // メールの送信元を取得 + const from = this.configService.get('MAIL_FROM') ?? ''; + // メールの内容を構成 const { subject, text, html } = await this.sendgridService.createMailContentFromEmailConfirmForNormalUser( @@ -270,7 +268,7 @@ export class UsersService { await this.sendgridService.sendMail( context, email, - this.mailFrom, + from, subject, text, html, @@ -344,12 +342,6 @@ export class UsersService { license_alert: licenseAlert, notification, role, - accepted_dpa_version: null, - accepted_eula_version: null, - encryption: false, - encryption_password: null, - prompt: false, - author_id: null, }; case USER_ROLES.AUTHOR: return { @@ -359,12 +351,10 @@ export class UsersService { license_alert: licenseAlert, notification, role, - author_id: authorId ?? null, - encryption: encryption ?? false, - encryption_password: encryptionPassword ?? null, - prompt: prompt ?? false, - accepted_dpa_version: null, - accepted_eula_version: null, + author_id: authorId, + encryption, + encryption_password: encryptionPassword, + prompt, }; default: //不正なroleが指定された場合はログを出力してエラーを返す @@ -414,16 +404,19 @@ export class UsersService { await this.adB2cService.changePassword(extarnalId, ramdomPassword); // ユーザを認証済みにする await this.usersRepository.updateUserVerified(userId); + // メールの送信元を取得 + const from = this.configService.get('MAIL_FROM') ?? ''; // TODO [Task2163] ODMS側が正式にメッセージを決めるまで仮のメール内容とする const subject = 'A temporary password has been issued.'; const text = 'temporary password: ' + ramdomPassword; - const html = `

      OMDS TOP PAGE URL.

      ${this.appDomain}"
      temporary password: ${ramdomPassword}`; + const domains = this.configService.get('APP_DOMAIN'); + const html = `

      OMDS TOP PAGE URL.

      ${domains}"
      temporary password: ${ramdomPassword}`; // メールを送信 await this.sendgridService.sendMail( context, email, - this.mailFrom, + from, subject, text, html, @@ -470,29 +463,17 @@ export class UsersService { ); // DBから取得した各ユーザーをもとにADB2C情報をマージしライセンス情報を算出 - const users = dbUsers.map((dbUser): User => { + const users = dbUsers.map((x) => { // ユーザーの所属グループ名を取得する - const userGroupMembers = - dbUser.userGroupMembers !== null ? dbUser.userGroupMembers : []; + const groupNames = + x.userGroupMembers?.map((group) => group.userGroup?.name) ?? []; - //所属グループ名の配列にする - const groupNames = userGroupMembers.flatMap((userGroupMember) => - userGroupMember.userGroup ? [userGroupMember.userGroup.name] : [], - ); - - const adb2cUser = adb2cUsers.find( - (user) => user.id === dbUser.external_id, - ); + const adb2cUser = adb2cUsers.find((user) => user.id === x.external_id); // メールアドレスを取得する - const mail = adb2cUser?.identities?.find( - (identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS, - )?.issuerAssignedId; - - //メールアドレスが取得できない場合はエラー - if (!mail) { - throw new Error('mail not found.'); - } + const mail = adb2cUser.identities.find( + (identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EAMILADDRESS, + ).issuerAssignedId; let status = USER_LICENSE_STATUS.NORMAL; @@ -501,30 +482,21 @@ export class UsersService { let expiration: string | undefined = undefined; let remaining: number | undefined = undefined; - if (dbUser.license) { + if (x.license) { // 有効期限日付 YYYY/MM/DD - const expiry_date = dbUser.license.expiry_date ?? undefined; - expiration = - expiry_date !== undefined - ? `${expiry_date.getFullYear()}/${ - expiry_date.getMonth() + 1 - }/${expiry_date.getDate()}` - : undefined; + const expiry_date = x.license.expiry_date; + expiration = `${expiry_date.getFullYear()}/${ + expiry_date.getMonth() + 1 + }/${expiry_date.getDate()}`; const currentDate = new DateWithZeroTime(); // 有効期限までの日数 - remaining = - expiry_date !== undefined - ? Math.floor( - (expiry_date.getTime() - currentDate.getTime()) / - (1000 * 60 * 60 * 24), - ) - : undefined; - if ( - remaining !== undefined && - remaining <= LICENSE_EXPIRATION_THRESHOLD_DAYS - ) { - status = dbUser.auto_renew + remaining = Math.floor( + (expiry_date.getTime() - currentDate.getTime()) / + (1000 * 60 * 60 * 24), + ); + if (remaining <= LICENSE_EXPIRATION_THRESHOLD_DAYS) { + status = x.auto_renew ? USER_LICENSE_STATUS.RENEW : USER_LICENSE_STATUS.ALERT; } @@ -533,18 +505,18 @@ export class UsersService { } return { - id: dbUser.id, + id: x.id, name: adb2cUser.displayName, - role: dbUser.role, - authorId: dbUser.author_id ?? undefined, + role: x.role, + authorId: x.author_id ?? undefined, typistGroupName: groupNames, email: mail, - emailVerified: dbUser.email_verified, - autoRenew: dbUser.auto_renew, - licenseAlert: dbUser.license_alert, - notification: dbUser.notification, - encryption: dbUser.encryption, - prompt: dbUser.prompt, + emailVerified: x.email_verified, + autoRenew: x.auto_renew, + licenseAlert: x.license_alert, + notification: x.notification, + encryption: x.encryption, + prompt: x.prompt, expiration: expiration, remaining: remaining, licenseStatus: status, @@ -572,13 +544,14 @@ export class UsersService { async updateSortCriteria( paramName: TaskListSortableAttribute, direction: SortDirection, - externalId: string, + token: AccessToken, ): Promise { this.logger.log(`[IN] ${this.updateSortCriteria.name}`); let user: EntityUser; try { // ユーザー情報を取得 - user = await this.usersRepository.findUserByExternalId(externalId); + const sub = token.userId; + user = await this.usersRepository.findUserByExternalId(sub); } catch (e) { this.logger.error(`error=${e}`); @@ -610,7 +583,7 @@ export class UsersService { * @param token * @returns sort criteria */ - async getSortCriteria(externalId: string): Promise<{ + async getSortCriteria(token: AccessToken): Promise<{ paramName: TaskListSortableAttribute; direction: SortDirection; }> { @@ -618,7 +591,8 @@ export class UsersService { let user: EntityUser; try { // ユーザー情報を取得 - user = await this.usersRepository.findUserByExternalId(externalId); + const sub = token.userId; + user = await this.usersRepository.findUserByExternalId(sub); } catch (e) { this.logger.error(`error=${e}`); @@ -668,8 +642,8 @@ export class UsersService { // TODO: PBI2105 本実装時に修正すること return { - authorId: user.author_id ?? '', - authorIdList: [user.author_id ?? '', 'XXX'], + authorId: user.author_id, + authorIdList: [user.author_id, 'XXX'], isEncrypted: true, encryptionPassword: 'abcd@123?dcba', audioFormat: 'DS2(QP)', @@ -993,63 +967,4 @@ export class UsersService { ); } } - - /** - * 同意済み利用規約バージョンを更新する - * @param context - * @param idToken - * @param eulaVersion - * @param dpaVersion - */ - async updateAcceptedVersion( - context: Context, - externalId: string, - eulaVersion: string, - dpaVersion?: string, - ): Promise { - this.logger.log( - `[IN] [${context.trackingId}] ${this.updateAcceptedVersion.name} | params: { ` + - `externalId: ${externalId}, ` + - `eulaVersion: ${eulaVersion}, ` + - `dpaVersion: ${dpaVersion}, };`, - ); - - try { - await this.usersRepository.updateAcceptedTermsVersion( - externalId, - eulaVersion, - dpaVersion, - ); - } catch (e) { - this.logger.error(`[${context.trackingId}] error=${e}`); - if (e instanceof Error) { - switch (e.constructor) { - case UserNotFoundError: - throw new HttpException( - makeErrorResponse('E010204'), - HttpStatus.BAD_REQUEST, - ); - case AccountNotFoundError: - throw new HttpException( - makeErrorResponse('E010501'), - HttpStatus.BAD_REQUEST, - ); - case UpdateTermsVersionNotSetError: - throw new HttpException( - makeErrorResponse('E010001'), - HttpStatus.BAD_REQUEST, - ); - default: - throw new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - } finally { - this.logger.log( - `[OUT] [${context.trackingId}] ${this.updateAcceptedVersion.name}`, - ); - } - } } diff --git a/dictation_server/src/features/workflows/test/utility.ts b/dictation_server/src/features/workflows/test/utility.ts index d29d8fb..473f9df 100644 --- a/dictation_server/src/features/workflows/test/utility.ts +++ b/dictation_server/src/features/workflows/test/utility.ts @@ -37,19 +37,12 @@ export const getWorkflows = async ( }); }; -// Workflow一覧全体を取得する -export const getAllWorkflows = async ( - datasource: DataSource, -): Promise => { - return await datasource.getRepository(Workflow).find(); -}; - // Workflowを取得する export const getWorkflow = async ( datasource: DataSource, accountId: number, id: number, -): Promise => { +): Promise => { return await datasource.getRepository(Workflow).findOne({ where: { account_id: accountId, diff --git a/dictation_server/src/features/workflows/workflows.controller.ts b/dictation_server/src/features/workflows/workflows.controller.ts index 4c6ac32..c571842 100644 --- a/dictation_server/src/features/workflows/workflows.controller.ts +++ b/dictation_server/src/features/workflows/workflows.controller.ts @@ -2,7 +2,6 @@ import { Body, Controller, Get, - HttpException, HttpStatus, Param, Post, @@ -35,7 +34,6 @@ import { retrieveAuthorizationToken } from '../../common/http/helper'; import { Request } from 'express'; import { makeContext } from '../../common/log'; import { WorkflowsService } from './workflows.service'; -import { makeErrorResponse } from '../../common/error/makeErrorResponse'; @ApiTags('workflows') @Controller('workflows') @@ -66,21 +64,8 @@ export class WorkflowsController { @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) @Get() async getWorkflows(@Req() req: Request): Promise { - const accessToken = retrieveAuthorizationToken(req); - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(userId); @@ -122,31 +107,17 @@ export class WorkflowsController { @Body() body: CreateWorkflowsRequest, ): Promise { const { authorId, worktypeId, templateId, typists } = body; - - const accessToken = retrieveAuthorizationToken(req); - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(userId); await this.workflowsService.createWorkflow( context, userId, authorId, - typists, worktypeId, templateId, + typists, ); return {}; @@ -187,22 +158,8 @@ export class WorkflowsController { ): Promise { const { authorId, worktypeId, templateId, typists } = body; const { workflowId } = param; - - const accessToken = retrieveAuthorizationToken(req); - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(userId); await this.workflowsService.updateWorkflow( @@ -210,9 +167,9 @@ export class WorkflowsController { userId, workflowId, authorId, - typists, worktypeId, templateId, + typists, ); return {}; @@ -223,11 +180,6 @@ export class WorkflowsController { type: DeleteWorkflowResponse, description: '成功時のレスポンス', }) - @ApiResponse({ - status: HttpStatus.BAD_REQUEST, - description: 'パラメータ不正エラー', - type: ErrorResponse, - }) @ApiResponse({ status: HttpStatus.UNAUTHORIZED, description: '認証エラー', @@ -251,25 +203,12 @@ export class WorkflowsController { @Param() param: DeleteWorkflowRequestParam, ): Promise { const { workflowId } = param; - - const accessToken = retrieveAuthorizationToken(req); - if (!accessToken) { - throw new HttpException( - makeErrorResponse('E000107'), - HttpStatus.UNAUTHORIZED, - ); - } - const decodedAccessToken = jwt.decode(accessToken, { json: true }); - if (!decodedAccessToken) { - throw new HttpException( - makeErrorResponse('E000101'), - HttpStatus.UNAUTHORIZED, - ); - } - const { userId } = decodedAccessToken as AccessToken; + const token = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(userId); - await this.workflowsService.deleteWorkflow(context, userId, workflowId); + console.log(workflowId); + console.log(context.trackingId); return {}; } } diff --git a/dictation_server/src/features/workflows/workflows.service.spec.ts b/dictation_server/src/features/workflows/workflows.service.spec.ts index 3d4a65d..10e5714 100644 --- a/dictation_server/src/features/workflows/workflows.service.spec.ts +++ b/dictation_server/src/features/workflows/workflows.service.spec.ts @@ -10,7 +10,6 @@ import { createWorkflow, createWorkflowTypist, getAllWorkflowTypists, - getAllWorkflows, getWorkflowTypists, getWorkflows, } from './test/utility'; @@ -21,7 +20,7 @@ import { HttpException, HttpStatus } from '@nestjs/common'; import { makeErrorResponse } from '../../common/error/makeErrorResponse'; describe('getWorkflows', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -34,15 +33,12 @@ describe('getWorkflows', () => { }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('アカウント内のWorkflow一覧を取得できる', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId1 } = await makeTestUser(source, { @@ -152,10 +148,10 @@ describe('getWorkflows', () => { expect(resWorkflows[0].id).toBe(workflow1.id); expect(resWorkflows[0].author.id).toBe(authorId1); expect(resWorkflows[0].author.authorId).toBe('AUTHOR1'); - expect(resWorkflows[0].worktype?.id).toBe(worktypeId1); - expect(resWorkflows[0].worktype?.worktypeId).toBe('worktype1'); - expect(resWorkflows[0].template?.id).toBe(templateId1); - expect(resWorkflows[0].template?.fileName).toBe('fileName1'); + expect(resWorkflows[0].worktype.id).toBe(worktypeId1); + expect(resWorkflows[0].worktype.worktypeId).toBe('worktype1'); + expect(resWorkflows[0].template.id).toBe(templateId1); + expect(resWorkflows[0].template.fileName).toBe('fileName1'); expect(resWorkflows[0].typists.length).toBe(1); expect(resWorkflows[0].typists[0].typistUserId).toBe(typistId); expect(resWorkflows[0].typists[0].typistName).toBe('typist1'); @@ -164,8 +160,8 @@ describe('getWorkflows', () => { expect(resWorkflows[1].author.id).toBe(authorId2); expect(resWorkflows[1].author.authorId).toBe('AUTHOR2'); expect(resWorkflows[1].worktype).toBe(undefined); - expect(resWorkflows[1].template?.id).toBe(templateId1); - expect(resWorkflows[1].template?.fileName).toBe('fileName1'); + expect(resWorkflows[1].template.id).toBe(templateId1); + expect(resWorkflows[1].template.fileName).toBe('fileName1'); expect(resWorkflows[1].typists.length).toBe(1); expect(resWorkflows[1].typists[0].typistGroupId).toBe(userGroupId); expect(resWorkflows[1].typists[0].typistName).toBe('group1'); @@ -173,8 +169,8 @@ describe('getWorkflows', () => { expect(resWorkflows[2].id).toBe(workflow3.id); expect(resWorkflows[2].author.id).toBe(authorId3); expect(resWorkflows[2].author.authorId).toBe('AUTHOR3'); - expect(resWorkflows[2].worktype?.id).toBe(worktypeId1); - expect(resWorkflows[2].worktype?.worktypeId).toBe('worktype1'); + expect(resWorkflows[2].worktype.id).toBe(worktypeId1); + expect(resWorkflows[2].worktype.worktypeId).toBe('worktype1'); expect(resWorkflows[2].template).toBe(undefined); expect(resWorkflows[2].typists.length).toBe(1); expect(resWorkflows[2].typists[0].typistGroupId).toBe(userGroupId); @@ -183,9 +179,7 @@ describe('getWorkflows', () => { }); it('アカウント内のWorkflow一覧を取得できる(0件)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { admin } = await makeTestAccount(source, { tier: 5 }); @@ -205,9 +199,7 @@ describe('getWorkflows', () => { }); it('DBアクセスに失敗した場合、500エラーを返却する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -235,7 +227,7 @@ describe('getWorkflows', () => { }); describe('createWorkflows', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -247,15 +239,12 @@ describe('createWorkflows', () => { return source.initialize(); }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('アカウント内にWorkflowを作成できる(WorktypeIDあり、テンプレートファイルあり)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId } = await makeTestUser(source, { @@ -298,13 +287,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, + worktypeId, + templateId, [ { typistId: typistId, }, ], - worktypeId, - templateId, ); //実行結果を確認 @@ -324,9 +313,7 @@ describe('createWorkflows', () => { }); it('アカウント内にWorkflowを作成できる(WorktypeIDなし、テンプレートファイルあり)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId } = await makeTestUser(source, { @@ -363,13 +350,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, + undefined, + templateId, [ { typistId: typistId, }, ], - undefined, - templateId, ); //実行結果を確認 @@ -389,9 +376,7 @@ describe('createWorkflows', () => { }); it('アカウント内にWorkflowを作成できる(WorktypeIDあり、テンプレートファイルなし)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId } = await makeTestUser(source, { @@ -427,13 +412,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, + worktypeId, + undefined, [ { typistId: typistId, }, ], - worktypeId, - undefined, ); //実行結果を確認 @@ -453,9 +438,7 @@ describe('createWorkflows', () => { }); it('アカウント内にWorkflowを作成できる(WorktypeIDなし、テンプレートファイルなし)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId } = await makeTestUser(source, { @@ -485,13 +468,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, + undefined, + undefined, [ { typistId: typistId, }, ], - undefined, - undefined, ); //実行結果を確認 @@ -511,9 +494,7 @@ describe('createWorkflows', () => { }); it('アカウント内にWorkflowを作成できる(WorktypeIDなし、テンプレートファイルなし、同一AuthorIDのワークフローあり)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId } = await makeTestUser(source, { @@ -550,26 +531,26 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, + worktypeId, + undefined, [ { typistId: typistId, }, ], - worktypeId, - undefined, ); await service.createWorkflow( context, admin.external_id, authorId, + undefined, + undefined, [ { typistId: typistId, }, ], - undefined, - undefined, ); //実行結果を確認 @@ -589,9 +570,7 @@ describe('createWorkflows', () => { }); it('DBにAuthorが存在しない場合、400エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); @@ -631,13 +610,13 @@ describe('createWorkflows', () => { context, admin.external_id, 0, + worktypeId, + templateId, [ { typistId: typistId, }, ], - worktypeId, - templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -650,9 +629,7 @@ describe('createWorkflows', () => { }); it('DBにWorktypeIDが存在しない場合、400エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId } = await makeTestUser(source, { @@ -691,13 +668,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, + 9999, + templateId, [ { typistId: typistId, }, ], - 9999, - templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -710,9 +687,7 @@ describe('createWorkflows', () => { }); it('DBにテンプレートファイルが存在しない場合、400エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId } = await makeTestUser(source, { @@ -750,13 +725,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, + worktypeId, + 9999, [ { typistId: typistId, }, ], - worktypeId, - 9999, ); } catch (e) { if (e instanceof HttpException) { @@ -769,9 +744,7 @@ describe('createWorkflows', () => { }); it('DBにルーティング候補ユーザーが存在しない場合、400エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId } = await makeTestUser(source, { @@ -811,13 +784,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, + worktypeId, + templateId, [ { typistId: 9999, }, ], - worktypeId, - templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -830,9 +803,7 @@ describe('createWorkflows', () => { }); it('DBにルーティング候補グループが存在しない場合、400エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId } = await makeTestUser(source, { @@ -872,13 +843,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, + worktypeId, + templateId, [ { typistGroupId: 9999, }, ], - worktypeId, - templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -891,9 +862,7 @@ describe('createWorkflows', () => { }); it('DBにAuthorIDとWorktypeIDのペアがすでに存在する場合、400エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId } = await makeTestUser(source, { @@ -942,13 +911,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, + worktypeId, + templateId, [ { typistId: typistId, }, ], - worktypeId, - templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -961,9 +930,7 @@ describe('createWorkflows', () => { }); it('DBアクセスに失敗した場合、500エラーを返却する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId } = await makeTestUser(source, { @@ -1016,13 +983,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, + worktypeId, + templateId, [ { typistId: typistId, }, ], - worktypeId, - templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -1036,7 +1003,7 @@ describe('createWorkflows', () => { }); describe('updateWorkflow', () => { - let source: DataSource | null = null; + let source: DataSource = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -1048,15 +1015,12 @@ describe('updateWorkflow', () => { return source.initialize(); }); afterEach(async () => { - if (!source) return; await source.destroy(); source = null; }); it('アカウント内のWorkflowを更新できる(WorktypeIDあり、テンプレートファイルあり)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId1 } = await makeTestUser(source, { @@ -1125,13 +1089,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId2, + worktypeId, + templateId, [ { typistId: typistId2, }, ], - worktypeId, - templateId, ); //実行結果を確認 @@ -1150,9 +1114,7 @@ describe('updateWorkflow', () => { }); it('アカウント内にWorkflowを作成できる(WorktypeIDなし、テンプレートファイルあり)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId1 } = await makeTestUser(source, { @@ -1215,13 +1177,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId2, + undefined, + templateId, [ { typistId: typistId2, }, ], - undefined, - templateId, ); //実行結果を確認 @@ -1240,9 +1202,7 @@ describe('updateWorkflow', () => { }); it('アカウント内にWorkflowを作成できる(WorktypeIDあり、テンプレートファイルなし)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId1 } = await makeTestUser(source, { @@ -1304,13 +1264,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId2, + worktypeId, + undefined, [ { typistId: typistId2, }, ], - worktypeId, - undefined, ); //実行結果を確認 @@ -1329,9 +1289,7 @@ describe('updateWorkflow', () => { }); it('アカウント内にWorkflowを作成できる(WorktypeIDなし、テンプレートファイルなし)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId1 } = await makeTestUser(source, { @@ -1387,13 +1345,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId2, + undefined, + undefined, [ { typistId: typistId2, }, ], - undefined, - undefined, ); //実行結果を確認 @@ -1412,9 +1370,7 @@ describe('updateWorkflow', () => { }); it('アカウント内にWorkflowを作成できる(WorktypeIDなし、テンプレートファイルなし、同一AuthorIDのワークフローあり)', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId1 } = await makeTestUser(source, { @@ -1490,13 +1446,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow1.id, authorId2, + undefined, + undefined, [ { typistId: typistId2, }, ], - undefined, - undefined, ); //実行結果を確認 @@ -1515,9 +1471,7 @@ describe('updateWorkflow', () => { }); it('DBにWorkflowが存在しない場合、400エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId1 } = await makeTestUser(source, { @@ -1542,13 +1496,13 @@ describe('updateWorkflow', () => { admin.external_id, 9999, authorId1, + undefined, + undefined, [ { typistId: typistId1, }, ], - undefined, - undefined, ); } catch (e) { if (e instanceof HttpException) { @@ -1560,9 +1514,7 @@ describe('updateWorkflow', () => { } }); it('DBにAuthorが存在しない場合、400エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId1 } = await makeTestUser(source, { @@ -1615,13 +1567,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, 9999, + worktypeId, + templateId, [ { typistId: typistId1, }, ], - worktypeId, - templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -1634,9 +1586,7 @@ describe('updateWorkflow', () => { }); it('DBにWorktypeIDが存在しない場合、400エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId1 } = await makeTestUser(source, { @@ -1683,13 +1633,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId1, + 9999, + templateId, [ { typistId: typistId1, }, ], - 9999, - templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -1702,9 +1652,7 @@ describe('updateWorkflow', () => { }); it('DBにテンプレートファイルが存在しない場合、400エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId1 } = await makeTestUser(source, { @@ -1750,13 +1698,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId1, + worktypeId, + 9999, [ { typistId: typistId1, }, ], - worktypeId, - 9999, ); } catch (e) { if (e instanceof HttpException) { @@ -1769,9 +1717,7 @@ describe('updateWorkflow', () => { }); it('DBにルーティング候補ユーザーが存在しない場合、400エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId1 } = await makeTestUser(source, { @@ -1824,13 +1770,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId1, + worktypeId, + templateId, [ { typistId: 9999, }, ], - worktypeId, - templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -1843,9 +1789,7 @@ describe('updateWorkflow', () => { }); it('DBにルーティング候補グループが存在しない場合、400エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId1 } = await makeTestUser(source, { @@ -1898,13 +1842,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId1, + worktypeId, + templateId, [ { typistGroupId: 9999, }, ], - worktypeId, - templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -1917,9 +1861,7 @@ describe('updateWorkflow', () => { }); it('DBにAuthorIDとWorktypeIDのペアがすでに存在する場合、400エラーとなること', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId1 } = await makeTestUser(source, { @@ -1966,13 +1908,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId1, + worktypeId1, + undefined, [ { typistId: typistId1, }, ], - worktypeId1, - undefined, ); } catch (e) { if (e instanceof HttpException) { @@ -1985,9 +1927,7 @@ describe('updateWorkflow', () => { }); it('DBアクセスに失敗した場合、500エラーを返却する', async () => { - if (!source) fail(); const module = await makeTestingModule(source); - if (!module) fail(); // 第五階層のアカウント作成 const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { id: authorId1 } = await makeTestUser(source, { @@ -2042,13 +1982,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId1, + undefined, + undefined, [ { typistId: typistId1, }, ], - undefined, - undefined, ); } catch (e) { if (e instanceof HttpException) { @@ -2060,313 +2000,3 @@ describe('updateWorkflow', () => { } }); }); - -describe('deleteWorkflows', () => { - let source: DataSource | null = null; - beforeEach(async () => { - source = new DataSource({ - type: 'sqlite', - database: ':memory:', - logging: false, - entities: [__dirname + '/../../**/*.entity{.ts,.js}'], - synchronize: true, // trueにすると自動的にmigrationが行われるため注意 - }); - return source.initialize(); - }); - afterEach(async () => { - if (!source) return; - await source.destroy(); - source = null; - }); - - it('アカウント内のWorkflowを削除できる', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - // 第五階層のアカウント作成 - const { account, admin } = await makeTestAccount(source, { tier: 5 }); - const { id: authorId } = await makeTestUser(source, { - external_id: 'author1', - author_id: 'AUTHOR1', - account_id: account.id, - role: USER_ROLES.AUTHOR, - }); - const { id: typistId } = await makeTestUser(source, { - external_id: 'typist1', - account_id: account.id, - role: USER_ROLES.TYPIST, - }); - - const workflow = await createWorkflow( - source, - account.id, - authorId, - undefined, - undefined, - ); - await createWorkflowTypist(source, workflow.id, typistId); - - //作成したデータを確認 - { - const workflows = await getWorkflows(source, account.id); - const workflowTypists = await getAllWorkflowTypists(source); - expect(workflows.length).toBe(1); - expect(workflowTypists.length).toBe(1); - } - - const service = module.get(WorkflowsService); - const context = makeContext(admin.external_id); - - await service.deleteWorkflow(context, admin.external_id, workflow.id); - - //実行結果を確認 - { - const workflows = await getWorkflows(source, account.id); - const workflowTypists = await getAllWorkflowTypists(source); - expect(workflows.length).toBe(0); - expect(workflowTypists.length).toBe(0); - } - }); - - it('アカウント内のWorkflowを削除できる(複数ワークフローがある場合)', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - // 第五階層のアカウント作成 - const { account, admin } = await makeTestAccount(source, { tier: 5 }); - const { id: authorId1 } = await makeTestUser(source, { - external_id: 'author1', - author_id: 'AUTHOR1', - account_id: account.id, - role: USER_ROLES.AUTHOR, - }); - const { id: authorId2 } = await makeTestUser(source, { - external_id: 'author2', - author_id: 'AUTHOR2', - account_id: account.id, - role: USER_ROLES.AUTHOR, - }); - const { id: typistId } = await makeTestUser(source, { - external_id: 'typist1', - account_id: account.id, - role: USER_ROLES.TYPIST, - }); - - const workflow1 = await createWorkflow(source, account.id, authorId1); - await createWorkflowTypist(source, workflow1.id, typistId); - const workflow2 = await createWorkflow(source, account.id, authorId2); - await createWorkflowTypist(source, workflow2.id, typistId); - - //作成したデータを確認 - { - const workflows = await getAllWorkflows(source); - const workflowTypists = await getAllWorkflowTypists(source); - expect(workflows.length).toBe(2); - expect(workflowTypists.length).toBe(2); - } - - const service = module.get(WorkflowsService); - const context = makeContext(admin.external_id); - - await service.deleteWorkflow(context, admin.external_id, workflow1.id); - - //実行結果を確認 - { - const workflows = await getAllWorkflows(source); - const workflowTypists = await getAllWorkflowTypists(source); - expect(workflows.length).toBe(1); - expect(workflows[0].id).toBe(workflow2.id); - expect(workflowTypists.length).toBe(1); - expect(workflowTypists[0].workflow_id).toBe(workflow2.id); - } - }); - - it('指定されたワークフローが存在しない場合、400エラーを返却する', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - // 第五階層のアカウント作成 - const { account, admin } = await makeTestAccount(source, { tier: 5 }); - const { id: authorId } = await makeTestUser(source, { - external_id: 'author1', - author_id: 'AUTHOR1', - account_id: account.id, - role: USER_ROLES.AUTHOR, - }); - const { id: typistId } = await makeTestUser(source, { - external_id: 'typist1', - account_id: account.id, - role: USER_ROLES.TYPIST, - }); - - const workflow = await createWorkflow( - source, - account.id, - authorId, - undefined, - undefined, - ); - await createWorkflowTypist(source, workflow.id, typistId); - - //作成したデータを確認 - { - const workflows = await getWorkflows(source, account.id); - const workflowTypists = await getAllWorkflowTypists(source); - expect(workflows.length).toBe(1); - expect(workflowTypists.length).toBe(1); - } - - const service = module.get(WorkflowsService); - const context = makeContext(admin.external_id); - - //実行結果を確認 - try { - await service.deleteWorkflow(context, admin.external_id, 9999); - } catch (e) { - if (e instanceof HttpException) { - expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST); - expect(e.getResponse()).toEqual(makeErrorResponse('E013002')); - } else { - fail(); - } - } - }); - - it('指定されたワークフローが存在しない場合、400エラーを返却する(ログインユーザーのアカウント外)', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - // 第五階層のアカウント作成 - const { account, admin } = await makeTestAccount(source, { tier: 5 }); - const { id: authorId } = await makeTestUser(source, { - external_id: 'author1', - author_id: 'AUTHOR1', - account_id: account.id, - role: USER_ROLES.AUTHOR, - }); - const { id: typistId } = await makeTestUser(source, { - external_id: 'typist1', - account_id: account.id, - role: USER_ROLES.TYPIST, - }); - - const workflow = await createWorkflow( - source, - account.id, - authorId, - undefined, - undefined, - ); - await createWorkflowTypist(source, workflow.id, typistId); - - const { account: otherAccount } = await makeTestAccount(source, { - tier: 5, - }); - const { id: otherAauthorId } = await makeTestUser(source, { - external_id: 'author1', - author_id: 'AUTHOR1', - account_id: otherAccount.id, - role: USER_ROLES.AUTHOR, - }); - const { id: otherTypistId } = await makeTestUser(source, { - external_id: 'typist1', - account_id: otherAccount.id, - role: USER_ROLES.TYPIST, - }); - - const otherWorkflow = await createWorkflow( - source, - otherAccount.id, - otherAauthorId, - undefined, - undefined, - ); - await createWorkflowTypist(source, otherWorkflow.id, otherTypistId); - - //作成したデータを確認 - { - const workflows = await getAllWorkflows(source); - const workflowTypists = await getAllWorkflowTypists(source); - expect(workflows.length).toBe(2); - expect(workflowTypists.length).toBe(2); - } - - const service = module.get(WorkflowsService); - const context = makeContext(admin.external_id); - - //実行結果を確認 - try { - await service.deleteWorkflow( - context, - admin.external_id, - otherWorkflow.id, - ); - } catch (e) { - if (e instanceof HttpException) { - expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST); - expect(e.getResponse()).toEqual(makeErrorResponse('E013002')); - } else { - fail(); - } - } - }); - - it('DBアクセスに失敗した場合、500エラーを返却する', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - // 第五階層のアカウント作成 - const { account, admin } = await makeTestAccount(source, { tier: 5 }); - const { id: authorId } = await makeTestUser(source, { - external_id: 'author1', - author_id: 'AUTHOR1', - account_id: account.id, - role: USER_ROLES.AUTHOR, - }); - const { id: typistId } = await makeTestUser(source, { - external_id: 'typist1', - account_id: account.id, - role: USER_ROLES.TYPIST, - }); - - const workflow = await createWorkflow( - source, - account.id, - authorId, - undefined, - undefined, - ); - await createWorkflowTypist(source, workflow.id, typistId); - - //作成したデータを確認 - { - const workflows = await getWorkflows(source, account.id); - const workflowTypists = await getAllWorkflowTypists(source); - expect(workflows.length).toBe(1); - expect(workflowTypists.length).toBe(1); - } - - const service = module.get(WorkflowsService); - const context = makeContext(admin.external_id); - - //DBアクセスに失敗するようにする - const workflowsRepositoryService = module.get( - WorkflowsRepositoryService, - ); - workflowsRepositoryService.deleteWorkflow = jest - .fn() - .mockRejectedValue('DB failed'); - - //実行結果を確認 - try { - await service.deleteWorkflow(context, admin.external_id, workflow.id); - } catch (e) { - if (e instanceof HttpException) { - expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR); - expect(e.getResponse()).toEqual(makeErrorResponse('E009999')); - } else { - fail(); - } - } - }); -}); diff --git a/dictation_server/src/features/workflows/workflows.service.ts b/dictation_server/src/features/workflows/workflows.service.ts index 6f98c95..288629a 100644 --- a/dictation_server/src/features/workflows/workflows.service.ts +++ b/dictation_server/src/features/workflows/workflows.service.ts @@ -11,10 +11,8 @@ import { WorktypeIdNotFoundError } from '../../repositories/worktypes/errors/typ import { TemplateFileNotExistError } from '../../repositories/template_files/errors/types'; import { AuthorIdAndWorktypeIdPairAlreadyExistsError, - WorkflowNotFoundError, + WorkflowIdNotFoundError, } from '../../repositories/workflows/errors/types'; -import { AccountNotFoundError } from '../../repositories/accounts/errors/types'; -import { Assignee } from '../tasks/types/types'; @Injectable() export class WorkflowsService { @@ -48,20 +46,15 @@ export class WorkflowsService { // ワークフロー一覧からtypistのexternalIdを取得 const externalIds = workflowRecords.flatMap((workflow) => { - const workflowTypists = workflow.workflowTypists?.flatMap( + const workflowTypists = workflow.workflowTypists.flatMap( (workflowTypist) => { const { typist } = workflowTypist; - return typist ? [typist.external_id] : []; + return typist ? [typist?.external_id] : []; }, ); return workflowTypists; }); - // externalIdsからundefinedを除外 - const filteredExternalIds = externalIds.flatMap((externalId) => - externalId ? [externalId] : [], - ); - // externalIdsから重複を除外 - const distinctedExternalIds = [...new Set(filteredExternalIds)]; + const distinctedExternalIds = [...new Set(externalIds)]; // ADB2Cからユーザー一覧を取得 const adb2cUsers = await this.adB2cService.getUsers( @@ -70,11 +63,8 @@ export class WorkflowsService { ); // DBから取得したワークフロー一覧を整形 - const workflows = workflowRecords.map((workflow): Workflow => { + const workflows = workflowRecords.map((workflow) => { const { id, author, worktype, template, workflowTypists } = workflow; - if (!author || !author.id || !author.author_id) { - throw new Error('author is undefined'); - } const authorId = { id: author.id, authorId: author.author_id }; const worktypeId = worktype @@ -84,24 +74,16 @@ export class WorkflowsService { ? { id: template.id, fileName: template.file_name } : undefined; - if (!workflowTypists) { - throw new Error('workflowTypists is undefined'); - } - // ルーティング候補を整形 - const typists = workflowTypists.map((workflowTypist): Assignee => { + const typists = workflowTypists.map((workflowTypist) => { const { typist, typistGroup } = workflowTypist; // typistがユーザーの場合はADB2Cからユーザー名を取得 const typistName = typist ? adb2cUsers.find( (adb2cUser) => adb2cUser.id === typist.external_id, - )?.displayName - : typistGroup?.name; - - if (!typistName) { - throw new Error('typistName is undefined'); - } + ).displayName + : typistGroup.name; return { typistUserId: typist?.id, @@ -147,9 +129,9 @@ export class WorkflowsService { context: Context, externalId: string, authorId: number, - typists: WorkflowTypist[], worktypeId?: number | undefined, templateId?: number | undefined, + typists?: WorkflowTypist[], ): Promise { this.logger.log( `[IN] [${context.trackingId}] ${this.createWorkflow.name} | | params: { ` + @@ -166,9 +148,9 @@ export class WorkflowsService { await this.workflowsRepository.createtWorkflows( accountId, authorId, - typists, worktypeId, templateId, + typists, ); } catch (e) { this.logger.error(`[${context.trackingId}] error=${e}`); @@ -232,9 +214,9 @@ export class WorkflowsService { externalId: string, workflowId: number, authorId: number, - typists: WorkflowTypist[], worktypeId?: number | undefined, templateId?: number | undefined, + typists?: WorkflowTypist[], ): Promise { this.logger.log( `[IN] [${context.trackingId}] ${this.updateWorkflow.name} | params: { ` + @@ -253,15 +235,15 @@ export class WorkflowsService { accountId, workflowId, authorId, - typists, worktypeId, templateId, + typists, ); } catch (e) { this.logger.error(`[${context.trackingId}] error=${e}`); if (e instanceof Error) { switch (e.constructor) { - case WorkflowNotFoundError: + case WorkflowIdNotFoundError: throw new HttpException( makeErrorResponse('E013002'), HttpStatus.BAD_REQUEST, @@ -308,70 +290,4 @@ export class WorkflowsService { ); } } - - /** - * ワークフローを削除する - * @param context - * @param externalId - * @param workflowId - * @returns workflow - */ - async deleteWorkflow( - context: Context, - externalId: string, - workflowId: number, - ): Promise { - this.logger.log( - `[IN] [${context.trackingId}] ${this.deleteWorkflow.name} | | params: { ` + - `externalId: ${externalId}, ` + - `workflowId: ${workflowId} };`, - ); - try { - const { account } = await this.usersRepository.findUserByExternalId( - externalId, - ); - - if (!account) { - throw new AccountNotFoundError( - `account not found. externalId: ${externalId}`, - ); - } - - await this.workflowsRepository.deleteWorkflow(account.id, workflowId); - } catch (e) { - this.logger.error(`[${context.trackingId}] error=${e}`); - if (e instanceof Error) { - switch (e.constructor) { - case UserNotFoundError: - throw new HttpException( - makeErrorResponse('E010204'), - HttpStatus.BAD_REQUEST, - ); - case AccountNotFoundError: - throw new HttpException( - makeErrorResponse('E010501'), - HttpStatus.BAD_REQUEST, - ); - case WorkflowNotFoundError: - throw new HttpException( - makeErrorResponse('E013002'), - HttpStatus.BAD_REQUEST, - ); - default: - throw new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - throw new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } finally { - this.logger.log( - `[OUT] [${context.trackingId}] ${this.deleteWorkflow.name}`, - ); - } - } } diff --git a/dictation_server/src/gateways/adb2c/adb2c.service.ts b/dictation_server/src/gateways/adb2c/adb2c.service.ts index e7dcce6..254acea 100644 --- a/dictation_server/src/gateways/adb2c/adb2c.service.ts +++ b/dictation_server/src/gateways/adb2c/adb2c.service.ts @@ -30,18 +30,17 @@ export const isConflictError = (arg: unknown): arg is ConflictError => { @Injectable() export class AdB2cService { private readonly logger = new Logger(AdB2cService.name); - private readonly tenantName = - this.configService.getOrThrow('TENANT_NAME'); + private readonly tenantName = this.configService.get('TENANT_NAME'); private readonly flowName = - this.configService.getOrThrow('SIGNIN_FLOW_NAME'); + this.configService.get('SIGNIN_FLOW_NAME'); private graphClient: Client; constructor(private readonly configService: ConfigService) { // ADB2Cへの認証情報 const credential = new ClientSecretCredential( - this.configService.getOrThrow('ADB2C_TENANT_ID'), - this.configService.getOrThrow('ADB2C_CLIENT_ID'), - this.configService.getOrThrow('ADB2C_CLIENT_SECRET'), + this.configService.get('ADB2C_TENANT_ID'), + this.configService.get('ADB2C_CLIENT_ID'), + this.configService.get('ADB2C_CLIENT_SECRET'), ); const authProvider = new TokenCredentialAuthenticationProvider(credential, { scopes: ['https://graph.microsoft.com/.default'], @@ -76,7 +75,7 @@ export class AdB2cService { }, identities: [ { - signinType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + signinType: ADB2C_SIGN_IN_TYPE.EAMILADDRESS, issuer: `${this.tenantName}.onmicrosoft.com`, issuerAssignedId: email, }, diff --git a/dictation_server/src/gateways/blobstorage/blobstorage.service.ts b/dictation_server/src/gateways/blobstorage/blobstorage.service.ts index 8d8b91e..57dd908 100644 --- a/dictation_server/src/gateways/blobstorage/blobstorage.service.ts +++ b/dictation_server/src/gateways/blobstorage/blobstorage.service.ts @@ -28,31 +28,31 @@ export class BlobstorageService { private readonly sasTokenExpireHour: number; constructor(private readonly configService: ConfigService) { this.sharedKeyCredentialUS = new StorageSharedKeyCredential( - this.configService.getOrThrow('STORAGE_ACCOUNT_NAME_US'), - this.configService.getOrThrow('STORAGE_ACCOUNT_KEY_US'), + this.configService.get('STORAGE_ACCOUNT_NAME_US'), + this.configService.get('STORAGE_ACCOUNT_KEY_US'), ); this.sharedKeyCredentialAU = new StorageSharedKeyCredential( - this.configService.getOrThrow('STORAGE_ACCOUNT_NAME_AU'), - this.configService.getOrThrow('STORAGE_ACCOUNT_KEY_AU'), + this.configService.get('STORAGE_ACCOUNT_NAME_AU'), + this.configService.get('STORAGE_ACCOUNT_KEY_AU'), ); this.sharedKeyCredentialEU = new StorageSharedKeyCredential( - this.configService.getOrThrow('STORAGE_ACCOUNT_NAME_EU'), - this.configService.getOrThrow('STORAGE_ACCOUNT_KEY_EU'), + this.configService.get('STORAGE_ACCOUNT_NAME_EU'), + this.configService.get('STORAGE_ACCOUNT_KEY_EU'), ); this.blobServiceClientUS = new BlobServiceClient( - this.configService.getOrThrow('STORAGE_ACCOUNT_ENDPOINT_US'), + this.configService.get('STORAGE_ACCOUNT_ENDPOINT_US'), this.sharedKeyCredentialUS, ); this.blobServiceClientAU = new BlobServiceClient( - this.configService.getOrThrow('STORAGE_ACCOUNT_ENDPOINT_AU'), + this.configService.get('STORAGE_ACCOUNT_ENDPOINT_AU'), this.sharedKeyCredentialAU, ); this.blobServiceClientEU = new BlobServiceClient( - this.configService.getOrThrow('STORAGE_ACCOUNT_ENDPOINT_EU'), + this.configService.get('STORAGE_ACCOUNT_ENDPOINT_EU'), this.sharedKeyCredentialEU, ); - this.sasTokenExpireHour = this.configService.getOrThrow( - 'STORAGE_TOKEN_EXPIRE_TIME', + this.sasTokenExpireHour = Number( + this.configService.get('STORAGE_TOKEN_EXPIRE_TIME'), ); } diff --git a/dictation_server/src/gateways/notificationhub/notificationhub.service.ts b/dictation_server/src/gateways/notificationhub/notificationhub.service.ts index 22038cc..2f8fe90 100644 --- a/dictation_server/src/gateways/notificationhub/notificationhub.service.ts +++ b/dictation_server/src/gateways/notificationhub/notificationhub.service.ts @@ -18,8 +18,8 @@ export class NotificationhubService { private readonly client: NotificationHubsClient; constructor(private readonly configService: ConfigService) { this.client = new NotificationHubsClient( - this.configService.getOrThrow('NOTIFICATION_HUB_CONNECT_STRING'), - this.configService.getOrThrow('NOTIFICATION_HUB_NAME'), + this.configService.get('NOTIFICATION_HUB_CONNECT_STRING'), + this.configService.get('NOTIFICATION_HUB_NAME'), ); } diff --git a/dictation_server/src/gateways/sendgrid/sendgrid.service.ts b/dictation_server/src/gateways/sendgrid/sendgrid.service.ts index 20f68d8..3fb3e66 100644 --- a/dictation_server/src/gateways/sendgrid/sendgrid.service.ts +++ b/dictation_server/src/gateways/sendgrid/sendgrid.service.ts @@ -8,14 +8,8 @@ import { Context } from '../../common/log'; @Injectable() export class SendGridService { private readonly logger = new Logger(SendGridService.name); - private readonly emailConfirmLifetime: number; - private readonly appDomain: string; constructor(private readonly configService: ConfigService) { - this.appDomain = this.configService.getOrThrow('APP_DOMAIN'); - this.emailConfirmLifetime = this.configService.getOrThrow( - 'EMAIL_CONFIRM_LIFETIME', - ); - const key = this.configService.getOrThrow('SENDGRID_API_KEY'); + const key = this.configService.get('SENDGRID_API_KEY'); sendgrid.setApiKey(key); } @@ -36,6 +30,8 @@ export class SendGridService { `[IN] [${context.trackingId}] ${this.createMailContentFromEmailConfirm.name}`, ); + const lifetime = + this.configService.get('EMAIL_CONFIRM_LIFETIME') ?? 0; const privateKey = getPrivateKey(this.configService); const token = sign<{ accountId: number; userId: number; email: string }>( { @@ -43,9 +39,10 @@ export class SendGridService { userId, email, }, - this.emailConfirmLifetime, + lifetime, privateKey, ); + const domains = this.configService.get('APP_DOMAIN'); const path = 'mail-confirm/'; this.logger.log( @@ -53,8 +50,8 @@ export class SendGridService { ); return { subject: 'Verify your new account', - text: `The verification URL. ${this.appDomain}${path}?verify=${token}`, - html: `

      The verification URL.

      ${this.appDomain}${path}?verify=${token}"`, + text: `The verification URL. ${domains}${path}?verify=${token}`, + html: `

      The verification URL.

      ${domains}${path}?verify=${token}"`, }; } @@ -71,6 +68,9 @@ export class SendGridService { userId: number, email: string, ): Promise<{ subject: string; text: string; html: string }> { + const lifetime = + this.configService.get('EMAIL_CONFIRM_LIFETIME') ?? 0; + const privateKey = getPrivateKey(this.configService); const token = sign<{ accountId: number; userId: number; email: string }>( @@ -79,15 +79,16 @@ export class SendGridService { userId, email, }, - this.emailConfirmLifetime, + lifetime, privateKey, ); + const domains = this.configService.get('APP_DOMAIN'); const path = 'mail-confirm/user/'; return { subject: 'Verify your new account', - text: `The verification URL. ${this.appDomain}${path}?verify=${token}`, - html: `

      The verification URL.

      ${this.appDomain}${path}?verify=${token}"`, + text: `The verification URL. ${domains}${path}?verify=${token}`, + html: `

      The verification URL.

      ${domains}${path}?verify=${token}"`, }; } diff --git a/dictation_server/src/main.ts b/dictation_server/src/main.ts index eec7370..dc7cb5c 100644 --- a/dictation_server/src/main.ts +++ b/dictation_server/src/main.ts @@ -5,15 +5,14 @@ import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; import helmet from 'helmet'; const helmetDirectives = helmet.contentSecurityPolicy.getDefaultDirectives(); - helmetDirectives['connect-src'] = process.env.STAGE === 'local' ? [ "'self'", - process.env.ADB2C_ORIGIN ?? '', - process.env.STORAGE_ACCOUNT_ENDPOINT_US ?? '', - process.env.STORAGE_ACCOUNT_ENDPOINT_AU ?? '', - process.env.STORAGE_ACCOUNT_ENDPOINT_EU ?? '', + process.env.ADB2C_ORIGIN, + process.env.STORAGE_ACCOUNT_ENDPOINT_US, + process.env.STORAGE_ACCOUNT_ENDPOINT_AU, + process.env.STORAGE_ACCOUNT_ENDPOINT_EU, ] : ["'self'"]; @@ -21,7 +20,6 @@ helmetDirectives['navigate-to'] = ["'self'"]; helmetDirectives['style-src'] = ["'self'", 'https:']; helmetDirectives['report-uri'] = ["'self'"]; async function bootstrap() { - console.log(`BUILD_VERSION: ${process.env.BUILD_VERSION}`); const app = await NestFactory.create(AppModule); app.use( diff --git a/dictation_server/src/repositories/accounts/accounts.repository.service.ts b/dictation_server/src/repositories/accounts/accounts.repository.service.ts index 8c582f0..39fe067 100644 --- a/dictation_server/src/repositories/accounts/accounts.repository.service.ts +++ b/dictation_server/src/repositories/accounts/accounts.repository.service.ts @@ -13,7 +13,6 @@ import { import { User, UserArchive } from '../users/entity/user.entity'; import { Account } from './entity/account.entity'; import { - CardLicense, License, LicenseAllocationHistory, LicenseAllocationHistoryArchive, @@ -49,14 +48,6 @@ import { import { DateWithZeroTime } from '../../features/licenses/types/types'; import { Worktype } from '../worktypes/entity/worktype.entity'; import { WorktypeIdNotFoundError } from '../worktypes/errors/types'; -import { OptionItem } from '../worktypes/entity/option_item.entity'; -import { Task } from '../tasks/entity/task.entity'; -import { CheckoutPermission } from '../checkout_permissions/entity/checkout_permission.entity'; -import { AudioFile } from '../audio_files/entity/audio_file.entity'; -import { AudioOptionItem } from '../audio_option_items/entity/audio_option_item.entity'; -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'; @Injectable() export class AccountsRepositoryService { @@ -126,13 +117,13 @@ export class AccountsRepositoryService { tier: number, adminExternalUserId: string, adminUserRole: string, - adminUserAcceptedEulaVersion?: string, - adminUserAcceptedDpaVersion?: string, + adminUserAcceptedEulaVersion: string, + adminUserAcceptedDpaVersion: string, ): Promise<{ newAccount: Account; adminUser: User }> { return await this.dataSource.transaction(async (entityManager) => { const account = new Account(); { - account.parent_account_id = dealerAccountId ?? null; + account.parent_account_id = dealerAccountId; account.company_name = companyName; account.country = country; account.tier = tier; @@ -147,8 +138,8 @@ export class AccountsRepositoryService { user.account_id = persistedAccount.id; user.external_id = adminExternalUserId; user.role = adminUserRole; - user.accepted_eula_version = adminUserAcceptedEulaVersion ?? null; - user.accepted_dpa_version = adminUserAcceptedDpaVersion ?? null; + user.accepted_eula_version = adminUserAcceptedEulaVersion; + user.accepted_dpa_version = adminUserAcceptedDpaVersion; } const usersRepo = entityManager.getRepository(User); const newUser = usersRepo.create(user); @@ -499,9 +490,6 @@ export class AccountsRepositoryService { id: id, }, }); - if (!ownAccount) { - throw new AccountNotFoundError(); - } // 自アカウントのライセンス注文状況を取得する const ownLicenseOrderStatus = await this.getAccountLicenseOrderStatus( @@ -544,8 +532,8 @@ export class AccountsRepositoryService { ); // 第五の不足数を算出するためのライセンス数情報を取得する - let expiringSoonLicense: number = 0; - let allocatableLicenseWithMargin: number = 0; + let expiringSoonLicense: number; + let allocatableLicenseWithMargin: number; if (childAccount.tier === TIERS.TIER5) { expiringSoonLicense = await this.getExpiringSoonLicense( entityManager, @@ -618,7 +606,7 @@ export class AccountsRepositoryService { return await this.dataSource.transaction(async (entityManager) => { const accountRepository = entityManager.getRepository(Account); const maxTierDifference = TIERS.TIER5 - TIERS.TIER1; - const parentAccountIds: number[] = []; + const parentAccountIds = []; let currentAccountId = targetAccountId; // システム的な最大の階層差異分、親を参照する @@ -631,9 +619,6 @@ export class AccountsRepositoryService { if (!account) { break; } - if (!account.parent_account_id) { - throw new Error("Parent account doesn't exist."); - } parentAccountIds.push(account.parent_account_id); currentAccountId = account.parent_account_id; @@ -755,13 +740,11 @@ export class AccountsRepositoryService { }); // ADB2Cから情報を取得するための外部ユーザIDを取得する(念のためプライマリ管理者IDが存在しない場合を考慮) - const primaryUserIds = partnerAccounts.flatMap((x) => { + const primaryUserIds = partnerAccounts.map((x) => { if (x.primary_admin_user_id) { - return [x.primary_admin_user_id]; + return x.primary_admin_user_id; } else if (x.secondary_admin_user_id) { - return [x.secondary_admin_user_id]; - } else { - return []; + return x.secondary_admin_user_id; } }); const userRepo = entityManager.getRepository(User); @@ -778,18 +761,15 @@ export class AccountsRepositoryService { user.id === account.primary_admin_user_id || user.id === account.secondary_admin_user_id, ); - if (!primaryUser) { - throw new AdminUserNotFoundError( - `Primary admin user is not found. id: ${account.primary_admin_user_id}, account_id: ${account.id}`, - ); - } - + const primaryAccountExternalId = primaryUser + ? primaryUser.external_id + : undefined; return { name: account.company_name, tier: account.tier, accountId: account.id, country: account.country, - primaryAccountExternalId: primaryUser.external_id, + primaryAccountExternalId: primaryAccountExternalId, dealerManagement: account.delegation_permission, }; }); @@ -810,7 +790,7 @@ export class AccountsRepositoryService { async getOneUpperTierAccount( accountId: number, tier: number, - ): Promise { + ): Promise { return await this.dataSource.transaction(async (entityManager) => { const accountRepo = entityManager.getRepository(Account); return await accountRepo.findOne({ @@ -889,10 +869,10 @@ export class AccountsRepositoryService { await accountRepo.update( { id: myAccountId }, { - parent_account_id: parentAccountId ?? null, + parent_account_id: parentAccountId || null, delegation_permission: delegationPermission, primary_admin_user_id: primaryAdminUserId, - secondary_admin_user_id: secondryAdminUserId ?? null, + secondary_admin_user_id: secondryAdminUserId || null, }, ); }); @@ -986,107 +966,9 @@ export class AccountsRepositoryService { .execute(); // アカウントを削除 + // アカウントを削除することで、外部キー制約がで紐づいている関連テーブルのデータも削除される const accountRepo = entityManager.getRepository(Account); await accountRepo.delete({ id: accountId }); - - // ライセンス系(card_license_issue以外)のテーブルのレコードを削除する - const orderRepo = entityManager.getRepository(LicenseOrder); - await orderRepo.delete({ - from_account_id: accountId, - }); - const licenseRepo = entityManager.getRepository(License); - const targetLicenses = await licenseRepo.find({ - where: { - account_id: accountId, - }, - }); - const cardLicenseRepo = entityManager.getRepository(CardLicense); - await cardLicenseRepo.delete({ - license_id: In(targetLicenses.map((license) => license.id)), - }); - await licenseRepo.delete({ - account_id: accountId, - }); - const LicenseAllocationHistoryRepo = entityManager.getRepository( - LicenseAllocationHistory, - ); - await LicenseAllocationHistoryRepo.delete({ - account_id: accountId, - }); - - // ワークタイプ系のテーブルのレコードを削除する - const worktypeRepo = entityManager.getRepository(Worktype); - const taggerWorktypes = await worktypeRepo.find({ - where: { account_id: accountId }, - }); - - const optionItemRepo = entityManager.getRepository(OptionItem); - await optionItemRepo.delete({ - worktype_id: In(taggerWorktypes.map((worktype) => worktype.id)), - }); - await worktypeRepo.delete({ account_id: accountId }); - - // タスク系のテーブルのレコードを削除する - const taskRepo = entityManager.getRepository(Task); - const targetTasks = await taskRepo.find({ - where: { - account_id: accountId, - }, - }); - const checkoutPermissionRepo = - entityManager.getRepository(CheckoutPermission); - await checkoutPermissionRepo.delete({ - task_id: In(targetTasks.map((task) => task.id)), - }); - await taskRepo.delete({ - account_id: accountId, - }); - - // オーディオファイル系のテーブルのレコードを削除する - const audioFileRepo = entityManager.getRepository(AudioFile); - const targetaudioFiles = await audioFileRepo.find({ - where: { - account_id: accountId, - }, - }); - const audioOptionItemsRepo = entityManager.getRepository(AudioOptionItem); - await audioOptionItemsRepo.delete({ - audio_file_id: In(targetaudioFiles.map((audioFile) => audioFile.id)), - }); - await audioFileRepo.delete({ - account_id: accountId, - }); - - // ユーザーグループ系のテーブルのレコードを削除する - const userGroupRepo = entityManager.getRepository(UserGroup); - const targetUserGroup = await userGroupRepo.find({ - where: { - account_id: accountId, - }, - }); - const userGroupMemberRepo = entityManager.getRepository(UserGroupMember); - await userGroupMemberRepo.delete({ - user_group_id: In(targetUserGroup.map((userGroup) => userGroup.id)), - }); - await userGroupRepo.delete({ - account_id: accountId, - }); - - // テンプレートファイルテーブルのレコードを削除する - const templateFileRepo = entityManager.getRepository(TemplateFile); - await templateFileRepo.delete({ account_id: accountId }); - - // ユーザテーブルのレコードを削除する - const userRepo = entityManager.getRepository(User); - await userRepo.delete({ - account_id: accountId, - }); - - // ソート条件のテーブルのレコードを削除する - const sortCriteriaRepo = entityManager.getRepository(SortCriteria); - await sortCriteriaRepo.delete({ - user_id: In(users.map((user) => user.id)), - }); return users; }); } diff --git a/dictation_server/src/repositories/accounts/entity/account.entity.ts b/dictation_server/src/repositories/accounts/entity/account.entity.ts index 74a0a0a..f6787d9 100644 --- a/dictation_server/src/repositories/accounts/entity/account.entity.ts +++ b/dictation_server/src/repositories/accounts/entity/account.entity.ts @@ -13,8 +13,8 @@ export class Account { @PrimaryGeneratedColumn() id: number; - @Column({ nullable: true, type: 'unsigned big int' }) - parent_account_id: number | null; + @Column({ nullable: true }) + parent_account_id?: number; @Column() tier: number; @@ -34,36 +34,30 @@ export class Account { @Column({ default: false }) verified: boolean; - @Column({ nullable: true, type: 'unsigned big int' }) - primary_admin_user_id: number | null; + @Column({ nullable: true }) + primary_admin_user_id?: number; - @Column({ nullable: true, type: 'unsigned big int' }) - secondary_admin_user_id: number | null; + @Column({ nullable: true }) + secondary_admin_user_id?: number; - @Column({ nullable: true, type: 'unsigned big int' }) - active_worktype_id: number | null; + @Column({ nullable: true }) + active_worktype_id?: number; - @Column({ nullable: true, type: 'datetime' }) - deleted_at: Date | null; + @Column({ nullable: true }) + deleted_at?: Date; - @Column({ nullable: true, type: 'datetime' }) - created_by: string | null; + @Column({ nullable: true }) + created_by?: string; - @CreateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 created_at: Date; - @Column({ nullable: true, type: 'datetime' }) - updated_by: string | null; + @Column({ nullable: true }) + updated_by?: string; - @UpdateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + @UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 updated_at: Date; @OneToMany(() => User, (user) => user.id) - user: User[] | null; + user?: User[]; } diff --git a/dictation_server/src/repositories/audio_files/entity/audio_file.entity.ts b/dictation_server/src/repositories/audio_files/entity/audio_file.entity.ts index 5a0d9f5..244f955 100644 --- a/dictation_server/src/repositories/audio_files/entity/audio_file.entity.ts +++ b/dictation_server/src/repositories/audio_files/entity/audio_file.entity.ts @@ -32,12 +32,12 @@ export class AudioFile { priority: string; @Column() audio_format: string; - @Column({ nullable: true, type: 'varchar' }) - comment: string | null; - @Column({ nullable: true, type: 'datetime' }) - deleted_at: Date | null; + @Column({ nullable: true }) + comment?: string; + @Column({ nullable: true }) + deleted_at?: Date; @Column() is_encrypted: boolean; @OneToOne(() => Task, (task) => task.file) - task: Task | null; + task?: Task; } diff --git a/dictation_server/src/repositories/audio_option_items/entity/audio_option_item.entity.ts b/dictation_server/src/repositories/audio_option_items/entity/audio_option_item.entity.ts index 665ac69..ca39f80 100644 --- a/dictation_server/src/repositories/audio_option_items/entity/audio_option_item.entity.ts +++ b/dictation_server/src/repositories/audio_option_items/entity/audio_option_item.entity.ts @@ -19,5 +19,5 @@ export class AudioOptionItem { value: string; @ManyToOne(() => Task, (task) => task.audio_file_id) @JoinColumn({ name: 'audio_file_id' }) - task: Task | null; + task?: Task; } diff --git a/dictation_server/src/repositories/checkout_permissions/entity/checkout_permission.entity.ts b/dictation_server/src/repositories/checkout_permissions/entity/checkout_permission.entity.ts index 1269c1f..cd6b116 100644 --- a/dictation_server/src/repositories/checkout_permissions/entity/checkout_permission.entity.ts +++ b/dictation_server/src/repositories/checkout_permissions/entity/checkout_permission.entity.ts @@ -18,21 +18,21 @@ export class CheckoutPermission { @Column({}) task_id: number; - @Column({ nullable: true, type: 'unsigned big int' }) - user_id: number | null; + @Column({ nullable: true }) + user_id?: number; - @Column({ nullable: true, type: 'unsigned big int' }) - user_group_id: number | null; + @Column({ nullable: true }) + user_group_id?: number; @OneToOne(() => User, (user) => user.id) @JoinColumn({ name: 'user_id' }) - user: User | null; + user?: User; @OneToOne(() => UserGroup, (group) => group.id) @JoinColumn({ name: 'user_group_id' }) - user_group: UserGroup | null; + user_group?: UserGroup; @ManyToOne(() => Task, (task) => task.id) @JoinColumn({ name: 'task_id' }) - task: Task | null; + task?: Task; } diff --git a/dictation_server/src/repositories/licenses/entity/license.entity.ts b/dictation_server/src/repositories/licenses/entity/license.entity.ts index b8e2cd2..dc39e6e 100644 --- a/dictation_server/src/repositories/licenses/entity/license.entity.ts +++ b/dictation_server/src/repositories/licenses/entity/license.entity.ts @@ -25,14 +25,11 @@ export class LicenseOrder { @Column() to_account_id: number; - @CreateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) + @CreateDateColumn() ordered_at: Date; - @Column({ nullable: true, type: 'datetime' }) - issued_at: Date | null; + @Column({ nullable: true }) + issued_at?: Date; @Column() quantity: number; @@ -40,25 +37,19 @@ export class LicenseOrder { @Column() status: string; - @Column({ nullable: true, type: 'datetime' }) - canceled_at: Date | null; + @Column({ nullable: true }) + canceled_at?: Date; - @Column({ nullable: true, type: 'datetime' }) - created_by: string | null; + @Column({ nullable: true }) + created_by: string; - @CreateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) + @CreateDateColumn() created_at: Date; - @Column({ nullable: true, type: 'datetime' }) - updated_by: string | null; + @Column({ nullable: true }) + updated_by: string; - @UpdateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) + @UpdateDateColumn() updated_at: Date; } @@ -67,8 +58,8 @@ export class License { @PrimaryGeneratedColumn() id: number; - @Column({ nullable: true, type: 'datetime' }) - expiry_date: Date | null; + @Column({ nullable: true }) + expiry_date: Date; @Column() account_id: number; @@ -79,41 +70,33 @@ export class License { @Column() status: string; - @Column({ nullable: true, type: 'unsigned big int' }) - allocated_user_id: number | null; + @Column({ nullable: true }) + allocated_user_id: number; - @Column({ nullable: true, type: 'unsigned big int' }) - order_id: number | null; + @Column({ nullable: true }) + order_id: number; - @Column({ nullable: true, type: 'datetime' }) - deleted_at: Date | null; + @Column({ nullable: true }) + deleted_at: Date; - @Column({ nullable: true, type: 'unsigned big int' }) - delete_order_id: number | null; + @Column({ nullable: true }) + delete_order_id: number; - @Column({ nullable: true, type: 'datetime' }) - created_by: string | null; + @Column({ nullable: true }) + created_by: string; - @CreateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) + @CreateDateColumn() created_at: Date; - @Column({ nullable: true, type: 'datetime' }) - updated_by: string | null; + @Column({ nullable: true }) + updated_by: string; - @UpdateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) + @UpdateDateColumn() updated_at: Date; - @OneToOne(() => User, (user) => user.license, { - createForeignKeyConstraints: false, - }) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定 + @OneToOne(() => User, (user) => user.license) @JoinColumn({ name: 'allocated_user_id' }) - user: User | null; + user?: User; } @Entity({ name: 'card_license_issue' }) @@ -124,22 +107,16 @@ export class CardLicenseIssue { @Column() issued_at: Date; - @Column({ nullable: true, type: 'datetime' }) - created_by: string | null; + @Column({ nullable: true }) + created_by: string; - @CreateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) + @CreateDateColumn() created_at: Date; - @Column({ nullable: true, type: 'datetime' }) - updated_by: string | null; + @Column({ nullable: true }) + updated_by: string; - @UpdateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) + @UpdateDateColumn() updated_at: Date; } @@ -154,25 +131,19 @@ export class CardLicense { @Column() card_license_key: string; - @Column({ nullable: true, type: 'datetime' }) - activated_at: Date | null; + @Column({ nullable: true }) + activated_at: Date; - @Column({ nullable: true, type: 'datetime' }) - created_by: string | null; + @Column({ nullable: true }) + created_by: string; - @CreateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) + @CreateDateColumn() created_at: Date; - @Column({ nullable: true, type: 'datetime' }) - updated_by: string | null; + @Column({ nullable: true }) + updated_by: string; - @UpdateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) + @UpdateDateColumn({}) updated_at: Date; } @@ -199,32 +170,24 @@ export class LicenseAllocationHistory { @Column() switch_from_type: string; - @Column({ nullable: true, type: 'datetime' }) - deleted_at: Date | null; + @Column({ nullable: true }) + deleted_at: Date; - @Column({ nullable: true, type: 'datetime' }) - created_by: string | null; + @Column({ nullable: true }) + created_by: string; - @CreateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) + @CreateDateColumn() created_at: Date; - @Column({ nullable: true, type: 'datetime' }) - updated_by: string | null; + @Column({ nullable: true }) + updated_by: string; - @UpdateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) + @UpdateDateColumn() updated_at: Date; - @ManyToOne(() => License, (licenses) => licenses.id, { - createForeignKeyConstraints: false, - }) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定 + @ManyToOne(() => License, (licenses) => licenses.id) @JoinColumn({ name: 'license_id' }) - license: License | null; + license?: License; } @Entity({ name: 'licenses_archive' }) @@ -232,8 +195,8 @@ export class LicenseArchive { @PrimaryColumn() id: number; - @Column({ nullable: true, type: 'datetime' }) - expiry_date: Date | null; + @Column({ nullable: true }) + expiry_date: Date; @Column() account_id: number; @@ -244,34 +207,31 @@ export class LicenseArchive { @Column() status: string; - @Column({ nullable: true, type: 'unsigned big int' }) - allocated_user_id: number | null; + @Column({ nullable: true }) + allocated_user_id: number; - @Column({ nullable: true, type: 'unsigned big int' }) - order_id: number | null; + @Column({ nullable: true }) + order_id: number; - @Column({ nullable: true, type: 'datetime' }) - deleted_at: Date | null; + @Column({ nullable: true }) + deleted_at: Date; - @Column({ nullable: true, type: 'unsigned big int' }) - delete_order_id: number | null; + @Column({ nullable: true }) + delete_order_id: number; - @Column({ nullable: true, type: 'datetime' }) - created_by: string | null; + @Column({ nullable: true }) + created_by: string; @Column() created_at: Date; - @Column({ nullable: true, type: 'datetime' }) - updated_by: string | null; + @Column({ nullable: true }) + updated_by: string; @Column() updated_at: Date; - @CreateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) + @CreateDateColumn() archived_at: Date; } @@ -298,24 +258,21 @@ export class LicenseAllocationHistoryArchive { @Column() switch_from_type: string; - @Column({ nullable: true, type: 'datetime' }) - deleted_at: Date | null; + @Column({ nullable: true }) + deleted_at: Date; - @Column({ nullable: true, type: 'datetime' }) - created_by: string | null; + @Column({ nullable: true }) + created_by: string; @Column() created_at: Date; - @Column({ nullable: true, type: 'datetime' }) - updated_by: string | null; + @Column({ nullable: true }) + updated_by: string; @Column() updated_at: Date; - @CreateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) + @CreateDateColumn() archived_at: Date; } diff --git a/dictation_server/src/repositories/licenses/licenses.repository.service.ts b/dictation_server/src/repositories/licenses/licenses.repository.service.ts index c43fa47..67496bf 100644 --- a/dictation_server/src/repositories/licenses/licenses.repository.service.ts +++ b/dictation_server/src/repositories/licenses/licenses.repository.service.ts @@ -444,7 +444,7 @@ export class LicensesRepositoryService { const allocatableLicenses = await queryBuilder.getMany(); return allocatableLicenses.map((license) => ({ licenseId: license.id, - expiryDate: license.expiry_date ?? undefined, + expiryDate: license.expiry_date, })); } /** @@ -469,13 +469,6 @@ export class LicensesRepositoryService { }, }); - // ライセンスが存在しない場合はエラー - if (!targetLicense) { - throw new LicenseNotExistError( - `License not exist. licenseId: ${newLicenseId}`, - ); - } - // 期限切れの場合はエラー if (targetLicense.expiry_date) { const currentDay = new Date(); @@ -540,7 +533,7 @@ export class LicensesRepositoryService { }); let switchFromType = ''; - if (oldLicenseType && oldLicenseType.license) { + if (oldLicenseType) { switch (oldLicenseType.license.type) { case LICENSE_TYPE.CARD: switchFromType = SWITCH_FROM_TYPE.CARD; diff --git a/dictation_server/src/repositories/tasks/entity/task.entity.ts b/dictation_server/src/repositories/tasks/entity/task.entity.ts index c734e1f..1252620 100644 --- a/dictation_server/src/repositories/tasks/entity/task.entity.ts +++ b/dictation_server/src/repositories/tasks/entity/task.entity.ts @@ -20,33 +20,33 @@ export class Task { job_number: string; @Column() account_id: number; - @Column({ nullable: true, type: 'tinyint' }) - is_job_number_enabled: boolean | null; + @Column({ nullable: true }) + is_job_number_enabled?: boolean; @Column() audio_file_id: number; @Column() status: string; - @Column({ nullable: true, type: 'unsigned big int' }) - typist_user_id: number | null; + @Column({ nullable: true }) + typist_user_id?: number; @Column() priority: string; - @Column({ nullable: true, type: 'unsigned big int' }) - template_file_id: number | null; - @Column({ nullable: true, type: 'datetime' }) - started_at: Date | null; - @Column({ nullable: true, type: 'datetime' }) - finished_at: Date | null; + @Column({ nullable: true }) + template_file_id?: number; + @Column({ nullable: true }) + started_at?: Date; + @Column({ nullable: true }) + finished_at?: Date; @Column({}) created_at: Date; @OneToOne(() => AudioFile, (audiofile) => audiofile.task) @JoinColumn({ name: 'audio_file_id' }) - file: AudioFile | null; + file?: AudioFile; @OneToMany(() => AudioOptionItem, (option) => option.task) - option_items: AudioOptionItem[] | null; + option_items?: AudioOptionItem[]; @OneToOne(() => User, (user) => user.id) @JoinColumn({ name: 'typist_user_id' }) - typist_user: User | null; + typist_user?: User; @ManyToOne(() => TemplateFile, (templateFile) => templateFile.id) @JoinColumn({ name: 'template_file_id' }) - template_file: TemplateFile | null; + template_file?: TemplateFile; } diff --git a/dictation_server/src/repositories/tasks/tasks.repository.service.ts b/dictation_server/src/repositories/tasks/tasks.repository.service.ts index b6edf11..1e207ca 100644 --- a/dictation_server/src/repositories/tasks/tasks.repository.service.ts +++ b/dictation_server/src/repositories/tasks/tasks.repository.service.ts @@ -336,7 +336,7 @@ export class TasksRepositoryService { await taskRepo.update( { audio_file_id: audio_file_id }, { - typist_user_id: null, + typist_user: null, status: TASK_STATUS.UPLOADED, }, ); @@ -757,7 +757,7 @@ export class TasksRepositoryService { */ async changeCheckoutPermission( audio_file_id: number, - author_id: string | undefined, + author_id: string, account_id: number, roles: Roles[], assignees: Assignee[], @@ -844,8 +844,8 @@ export class TasksRepositoryService { (assignee) => { const checkoutPermission = new CheckoutPermission(); checkoutPermission.task_id = taskRecord.id; - checkoutPermission.user_id = assignee.typistUserId ?? null; - checkoutPermission.user_group_id = assignee.typistGroupId ?? null; + checkoutPermission.user_id = assignee.typistUserId; + checkoutPermission.user_group_id = assignee.typistGroupId; return checkoutPermission; }, ); diff --git a/dictation_server/src/repositories/template_files/entity/template_file.entity.ts b/dictation_server/src/repositories/template_files/entity/template_file.entity.ts index b49b998..f5f9064 100644 --- a/dictation_server/src/repositories/template_files/entity/template_file.entity.ts +++ b/dictation_server/src/repositories/template_files/entity/template_file.entity.ts @@ -18,14 +18,14 @@ export class TemplateFile { url: string; @Column() file_name: string; - @Column({ nullable: true, type: 'datetime' }) - created_by: string | null; + @Column({ nullable: true }) + created_by?: string; @CreateDateColumn() created_at: Date; - @Column({ nullable: true, type: 'datetime' }) - updated_by: string | null; + @Column({ nullable: true }) + updated_by?: string; @UpdateDateColumn() updated_at: Date; @OneToMany(() => Task, (task) => task.template_file) - tasks: Task[] | null; + tasks?: Task[]; } diff --git a/dictation_server/src/repositories/terms/entity/term.entity.ts b/dictation_server/src/repositories/terms/entity/term.entity.ts deleted file mode 100644 index 7a2097b..0000000 --- a/dictation_server/src/repositories/terms/entity/term.entity.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - Entity, - Column, - PrimaryGeneratedColumn, - CreateDateColumn, - UpdateDateColumn, -} from 'typeorm'; - -@Entity({ name: 'terms' }) -export class Term { - @PrimaryGeneratedColumn() - id: number; - - @Column() - document_type: string; - - @Column() - version: string; - - @Column({ nullable: true, type: 'datetime' }) - created_by: string | null; - - @CreateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) // defaultはSQLite用設定値.本番用は別途migrationで設定 - created_at: Date; - - @Column({ nullable: true, type: 'varchar' }) - updated_by: string | null; - - @UpdateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) // defaultはSQLite用設定値.本番用は別途migrationで設定 - updated_at: Date; -} diff --git a/dictation_server/src/repositories/terms/terms.repository.module.ts b/dictation_server/src/repositories/terms/terms.repository.module.ts deleted file mode 100644 index f88c52c..0000000 --- a/dictation_server/src/repositories/terms/terms.repository.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Term } from './entity/term.entity'; -import { TermsRepositoryService } from './terms.repository.service'; - -@Module({ - imports: [TypeOrmModule.forFeature([Term])], - providers: [TermsRepositoryService], - exports: [TermsRepositoryService], -}) -export class TermsRepositoryModule {} diff --git a/dictation_server/src/repositories/terms/terms.repository.service.ts b/dictation_server/src/repositories/terms/terms.repository.service.ts deleted file mode 100644 index 7c79f24..0000000 --- a/dictation_server/src/repositories/terms/terms.repository.service.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { DataSource } from 'typeorm'; -import { TermsVersion } from '../../features/terms/types/types'; -import { Term } from './entity/term.entity'; -import { TERM_TYPE } from '../../constants'; -import { TermInfoNotFoundError } from '../users/errors/types'; - -@Injectable() -export class TermsRepositoryService { - constructor(private dataSource: DataSource) {} - - /* - * 利用規約の最新バージョンを取得する - * @returns Term[] - */ - async getLatestTermsInfo(): Promise { - return await this.dataSource.transaction(async (entityManager) => { - const termRepo = entityManager.getRepository(Term); - const latestEulaInfo = await termRepo.findOne({ - where: { - document_type: TERM_TYPE.EULA, - }, - order: { - id: 'DESC', - }, - }); - const latestDpaInfo = await termRepo.findOne({ - where: { - document_type: TERM_TYPE.DPA, - }, - order: { - id: 'DESC', - }, - }); - - if (!latestEulaInfo || !latestDpaInfo) { - throw new TermInfoNotFoundError( - `Terms info is not found. latestEulaInfo: ${latestEulaInfo}, latestDpaInfo: ${latestDpaInfo}`, - ); - } - return { - eulaVersion: latestEulaInfo.version, - dpaVersion: latestDpaInfo.version, - }; - }); - } -} diff --git a/dictation_server/src/repositories/user_groups/entity/user_group.entity.ts b/dictation_server/src/repositories/user_groups/entity/user_group.entity.ts index 2a1fbce..42e14fa 100644 --- a/dictation_server/src/repositories/user_groups/entity/user_group.entity.ts +++ b/dictation_server/src/repositories/user_groups/entity/user_group.entity.ts @@ -19,30 +19,24 @@ export class UserGroup { @Column() name: string; - @Column({ nullable: true, type: 'datetime' }) - deleted_at: Date | null; + @Column({ nullable: true }) + deleted_at?: Date; - @Column({ nullable: true, type: 'datetime' }) - created_by: string | null; + @Column({ nullable: true }) + created_by?: string; - @CreateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) // defaultはSQLite用設定値.本番用は別途migrationで設定 - created_at: Date | null; + @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + created_at?: Date; - @Column({ nullable: true, type: 'datetime' }) - updated_by: string | null; + @Column({ nullable: true }) + updated_by?: string; - @UpdateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) // defaultはSQLite用設定値.本番用は別途migrationで設定 - updated_at: Date | null; + @UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + updated_at?: Date; @OneToMany( () => UserGroupMember, (userGroupMember) => userGroupMember.userGroup, ) - userGroupMembers: UserGroupMember[] | null; + userGroupMembers?: UserGroupMember[]; } diff --git a/dictation_server/src/repositories/user_groups/entity/user_group_member.entity.ts b/dictation_server/src/repositories/user_groups/entity/user_group_member.entity.ts index 1530102..93f2484 100644 --- a/dictation_server/src/repositories/user_groups/entity/user_group_member.entity.ts +++ b/dictation_server/src/repositories/user_groups/entity/user_group_member.entity.ts @@ -21,32 +21,26 @@ export class UserGroupMember { @Column() user_id: number; - @Column({ nullable: true, type: 'datetime' }) - deleted_at: Date | null; + @Column({ nullable: true }) + deleted_at?: Date; - @Column({ nullable: true, type: 'datetime' }) - created_by: string | null; + @Column({ nullable: true }) + created_by?: string; - @CreateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) // defaultはSQLite用設定値.本番用は別途migrationで設定 - created_at: Date | null; + @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + created_at?: Date; - @Column({ nullable: true, type: 'datetime' }) - updated_by: string | null; + @Column({ nullable: true }) + updated_by?: string; - @UpdateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) // defaultはSQLite用設定値.本番用は別途migrationで設定 - updated_at: Date | null; + @UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + updated_at?: Date; @ManyToOne(() => User, (user) => user.id) @JoinColumn({ name: 'user_id' }) - user: User | null; + user?: User; @ManyToOne(() => UserGroup, (userGroup) => userGroup.id) @JoinColumn({ name: 'user_group_id' }) - userGroup: UserGroup | null; + userGroup?: UserGroup; } diff --git a/dictation_server/src/repositories/users/entity/user.entity.ts b/dictation_server/src/repositories/users/entity/user.entity.ts index 34d0bca..e7399aa 100644 --- a/dictation_server/src/repositories/users/entity/user.entity.ts +++ b/dictation_server/src/repositories/users/entity/user.entity.ts @@ -28,14 +28,14 @@ export class User { @Column() role: string; - @Column({ nullable: true, type: 'varchar' }) - author_id: string | null; + @Column({ nullable: true }) + author_id?: string; - @Column({ nullable: true, type: 'varchar' }) - accepted_eula_version: string | null; + @Column({ nullable: true }) + accepted_eula_version?: string; - @Column({ nullable: true, type: 'varchar' }) - accepted_dpa_version: string | null; + @Column({ nullable: true }) + accepted_dpa_version?: string; @Column({ default: false }) email_verified: boolean; @@ -50,46 +50,38 @@ export class User { notification: boolean; @Column({ default: false }) - encryption: boolean; + encryption?: boolean; - @Column({ nullable: true, type: 'varchar' }) - encryption_password: string | null; + @Column({ nullable: true }) + encryption_password?: string; @Column({ default: false }) - prompt: boolean; + prompt?: boolean; - @Column({ nullable: true, type: 'datetime' }) - deleted_at: Date | null; + @Column({ nullable: true }) + deleted_at?: Date; - @Column({ nullable: true, type: 'datetime' }) - created_by: string | null; + @Column({ nullable: true }) + created_by: string; - @CreateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 created_at: Date; - @Column({ nullable: true, type: 'datetime' }) - updated_by: string | null; + @Column({ nullable: true }) + updated_by?: string; - @UpdateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + @UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 updated_at: Date; - @ManyToOne(() => Account, (account) => account.user, { - createForeignKeyConstraints: false, - }) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定 + @ManyToOne(() => Account, (account) => account.user, { onDelete: 'CASCADE' }) // onDeleteはSQLite用設定値.本番用は別途migrationで設定 @JoinColumn({ name: 'account_id' }) - account: Account | null; + account?: Account; @OneToOne(() => License, (license) => license.user) - license: License | null; + license?: License; @OneToMany(() => UserGroupMember, (userGroupMember) => userGroupMember.user) - userGroupMembers: UserGroupMember[] | null; + userGroupMembers?: UserGroupMember[]; } @Entity({ name: 'users_archive' }) @@ -106,14 +98,14 @@ export class UserArchive { @Column() role: string; - @Column({ nullable: true, type: 'varchar' }) - author_id: string | null; + @Column({ nullable: true }) + author_id?: string; - @Column({ nullable: true, type: 'varchar' }) - accepted_eula_version: string | null; + @Column({ nullable: true }) + accepted_eula_version?: string; - @Column({ nullable: true, type: 'varchar' }) - accepted_dpa_version: string | null; + @Column({ nullable: true }) + accepted_dpa_version?: string; @Column() email_verified: boolean; @@ -133,25 +125,22 @@ export class UserArchive { @Column() prompt: boolean; - @Column({ nullable: true, type: 'datetime' }) - deleted_at: Date | null; + @Column({ nullable: true }) + deleted_at?: Date; - @Column({ nullable: true, type: 'datetime' }) - created_by: string | null; + @Column({ nullable: true }) + created_by: string; @Column() created_at: Date; - @Column({ nullable: true, type: 'datetime' }) - updated_by: string | null; + @Column({ nullable: true }) + updated_by?: string; @Column() updated_at: Date; - @CreateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 archived_at: Date; } diff --git a/dictation_server/src/repositories/users/errors/types.ts b/dictation_server/src/repositories/users/errors/types.ts index faee0b1..6f78b9a 100644 --- a/dictation_server/src/repositories/users/errors/types.ts +++ b/dictation_server/src/repositories/users/errors/types.ts @@ -8,7 +8,3 @@ export class AuthorIdAlreadyExistsError extends Error {} export class InvalidRoleChangeError extends Error {} // 暗号化パスワード不足エラー export class EncryptionPasswordNeedError extends Error {} -// 利用規約バージョン情報不在エラー -export class TermInfoNotFoundError extends Error {} -// 利用規約バージョンパラメータ不在エラー -export class UpdateTermsVersionNotSetError extends Error {} diff --git a/dictation_server/src/repositories/users/users.repository.service.ts b/dictation_server/src/repositories/users/users.repository.service.ts index 0a9dcab..cab67bd 100644 --- a/dictation_server/src/repositories/users/users.repository.service.ts +++ b/dictation_server/src/repositories/users/users.repository.service.ts @@ -12,22 +12,15 @@ import { AuthorIdAlreadyExistsError, InvalidRoleChangeError, EncryptionPasswordNeedError, - TermInfoNotFoundError, - UpdateTermsVersionNotSetError, } from './errors/types'; import { LICENSE_ALLOCATED_STATUS, LICENSE_TYPE, - TERM_TYPE, - TIERS, TRIAL_LICENSE_ISSUE_NUM, USER_ROLES, } from '../../constants'; import { License } from '../licenses/entity/license.entity'; import { NewTrialLicenseExpirationDate } from '../../features/licenses/types/types'; -import { Term } from '../terms/entity/term.entity'; -import { TermsCheckInfo } from '../../features/auth/types/types'; -import { AccountNotFoundError } from '../accounts/errors/types'; @Injectable() export class UsersRepositoryService { @@ -121,7 +114,7 @@ export class UsersRepositoryService { return user; } - async findUserById(id: number): Promise { + async findUserById(id: number): Promise { const user = await this.dataSource.getRepository(User).findOne({ where: { id: id, @@ -129,7 +122,7 @@ export class UsersRepositoryService { }); if (!user) { - throw new UserNotFoundError(); + return undefined; } return user; } @@ -139,7 +132,10 @@ export class UsersRepositoryService { * @param user * @returns 存在する:true 存在しない:false */ - async existsAuthorId(accountId: number, authorId: string): Promise { + async existsAuthorId( + accountId: number, + authorId: string, + ): Promise { const user = await this.dataSource.getRepository(User).findOne({ where: [ { @@ -217,9 +213,9 @@ export class UsersRepositoryService { } // Author用項目を更新 - targetUser.author_id = authorId ?? null; - targetUser.encryption = encryption ?? false; - targetUser.prompt = prompt ?? false; + targetUser.author_id = authorId; + targetUser.encryption = encryption; + targetUser.prompt = prompt; } else { // ユーザーのロールがAuthor以外の場合はAuthor用項目はundefinedにする targetUser.author_id = null; @@ -313,7 +309,7 @@ export class UsersRepositoryService { for (let i = 0; i < TRIAL_LICENSE_ISSUE_NUM; i++) { const license = new License(); license.expiry_date = expiryDate; - license.account_id = targetUser.account_id; + license.account_id = targetUser.account.id; license.type = LICENSE_TYPE.TRIAL; license.status = LICENSE_ALLOCATED_STATUS.UNALLOCATED; licenses.push(license); @@ -338,11 +334,7 @@ export class UsersRepositoryService { const repo = entityManager.getRepository(User); const accountId = (await repo.findOne({ where: { external_id } })) - ?.account_id; - - if (!accountId) { - throw new AccountNotFoundError('Account is Not Found.'); - } + .account_id; const dbUsers = await this.dataSource.getRepository(User).find({ relations: { @@ -373,11 +365,6 @@ export class UsersRepositoryService { }, }); - // 運用上ユーザがいないことはあり得ないが、プログラム上発生しうるのでエラーとして処理 - if (!user) { - throw new UserNotFoundError(); - } - const typists = await repo.find({ where: { account_id: user.account_id, @@ -426,111 +413,4 @@ export class UsersRepositoryService { await usersRepo.delete({ id: userId }); }); } - - /** - * 同意済み利用規約バージョンの情報を取得する - * @param externalId - * @returns TermsCheckInfo - */ - async getAcceptedAndLatestVersion( - externalId: string, - ): Promise { - return await this.dataSource.transaction(async (entityManager) => { - const userRepo = entityManager.getRepository(User); - const user = await userRepo.findOne({ - where: { - external_id: externalId, - }, - relations: { - account: true, - }, - }); - - if (!user) { - throw new UserNotFoundError(); - } - if (!user.account) { - throw new AccountNotFoundError('Account is Not Found.'); - } - - const termRepo = entityManager.getRepository(Term); - const latestEulaInfo = await termRepo.findOne({ - where: { - document_type: TERM_TYPE.EULA, - }, - order: { - id: 'DESC', - }, - }); - const latestDpaInfo = await termRepo.findOne({ - where: { - document_type: TERM_TYPE.DPA, - }, - order: { - id: 'DESC', - }, - }); - - if (!latestEulaInfo || !latestDpaInfo) { - throw new TermInfoNotFoundError(`Terms info is not found.`); - } - - return { - tier: user.account.tier, - acceptedEulaVersion: user.accepted_eula_version ?? undefined, - acceptedDpaVersion: user.accepted_dpa_version ?? undefined, - latestEulaVersion: latestEulaInfo.version, - latestDpaVersion: latestDpaInfo.version, - }; - }); - } - - /** - * 同意済み利用規約のバージョンを更新する - * @param externalId - * @param eulaVersion - * @param dpaVersion - * @returns update - */ - async updateAcceptedTermsVersion( - externalId: string, - eulaVersion: string, - dpaVersion: string | undefined, - ): Promise { - await this.dataSource.transaction(async (entityManager) => { - const userRepo = entityManager.getRepository(User); - const user = await userRepo.findOne({ - where: { - external_id: externalId, - }, - relations: { - account: true, - }, - }); - - if (!user) { - throw new UserNotFoundError( - `User not found. externalId: ${externalId}`, - ); - } - - if (!user.account) { - throw new AccountNotFoundError('Account is Not Found.'); - } - - // パラメータが不在の場合はエラーを返却 - if (!eulaVersion) { - throw new UpdateTermsVersionNotSetError(`EULA version param not set.`); - } - if (user.account.tier !== TIERS.TIER5 && !dpaVersion) { - throw new UpdateTermsVersionNotSetError( - `DPA version param not set. User's tier: ${user.account.tier}`, - ); - } - - user.accepted_eula_version = eulaVersion; - user.accepted_dpa_version = dpaVersion ?? user.accepted_dpa_version; - await userRepo.update({ id: user.id }, user); - }); - } } diff --git a/dictation_server/src/repositories/workflows/entity/workflow.entity.ts b/dictation_server/src/repositories/workflows/entity/workflow.entity.ts index 6c51d96..e3bac8e 100644 --- a/dictation_server/src/repositories/workflows/entity/workflow.entity.ts +++ b/dictation_server/src/repositories/workflows/entity/workflow.entity.ts @@ -24,42 +24,36 @@ export class Workflow { @Column() author_id: number; - @Column({ nullable: true, type: 'unsigned big int' }) - worktype_id: number | null; + @Column({ nullable: true }) + worktype_id?: number; - @Column({ nullable: true, type: 'unsigned big int' }) - template_id: number | null; + @Column({ nullable: true }) + template_id?: number; - @Column({ nullable: true, type: 'datetime' }) - created_by: string | null; + @Column({ nullable: true }) + created_by: string; - @CreateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 created_at: Date; - @Column({ nullable: true, type: 'datetime' }) - updated_by: string | null; + @Column({ nullable: true }) + updated_by?: string; - @UpdateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + @UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 updated_at: Date; @ManyToOne(() => User, (user) => user.id) @JoinColumn({ name: 'author_id' }) - author: User | null; + author?: User; @ManyToOne(() => Worktype, (worktype) => worktype.id) @JoinColumn({ name: 'worktype_id' }) - worktype: Worktype | null; + worktype?: Worktype; @ManyToOne(() => TemplateFile, (templateFile) => templateFile.id) @JoinColumn({ name: 'template_id' }) - template: TemplateFile | null; + template?: TemplateFile; @OneToMany(() => WorkflowTypist, (workflowTypist) => workflowTypist.workflow) - workflowTypists: WorkflowTypist[] | null; + workflowTypists?: WorkflowTypist[]; } diff --git a/dictation_server/src/repositories/workflows/entity/workflow_typists.entity.ts b/dictation_server/src/repositories/workflows/entity/workflow_typists.entity.ts index f92d02e..b3d7139 100644 --- a/dictation_server/src/repositories/workflows/entity/workflow_typists.entity.ts +++ b/dictation_server/src/repositories/workflows/entity/workflow_typists.entity.ts @@ -19,39 +19,33 @@ export class WorkflowTypist { @Column() workflow_id: number; - @Column({ nullable: true, type: 'unsigned big int' }) - typist_id: number | null; + @Column({ nullable: true }) + typist_id?: number; - @Column({ nullable: true, type: 'unsigned big int' }) - typist_group_id: number | null; + @Column({ nullable: true }) + typist_group_id?: number; - @Column({ nullable: true, type: 'datetime' }) - created_by: string | null; + @Column({ nullable: true }) + created_by: string; - @CreateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 created_at: Date; - @Column({ nullable: true, type: 'datetime' }) - updated_by: string | null; + @Column({ nullable: true }) + updated_by?: string; - @UpdateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + @UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 updated_at: Date; @ManyToOne(() => Workflow, (workflow) => workflow.id) @JoinColumn({ name: 'workflow_id' }) - workflow: Workflow | null; + workflow?: Workflow; @ManyToOne(() => User, (user) => user.id) @JoinColumn({ name: 'typist_id' }) - typist: User | null; + typist?: User; @ManyToOne(() => UserGroup, (userGroup) => userGroup.id) @JoinColumn({ name: 'typist_group_id' }) - typistGroup: UserGroup | null; + typistGroup?: UserGroup; } diff --git a/dictation_server/src/repositories/workflows/errors/types.ts b/dictation_server/src/repositories/workflows/errors/types.ts index 633bc00..8680e30 100644 --- a/dictation_server/src/repositories/workflows/errors/types.ts +++ b/dictation_server/src/repositories/workflows/errors/types.ts @@ -1,4 +1,4 @@ // AuthorIDとWorktypeIDのペア重複エラー export class AuthorIdAndWorktypeIdPairAlreadyExistsError extends Error {} -// Workflow存在エラー -export class WorkflowNotFoundError extends Error {} +// WorkflowID存在エラー +export class WorkflowIdNotFoundError extends Error {} diff --git a/dictation_server/src/repositories/workflows/workflows.repository.service.ts b/dictation_server/src/repositories/workflows/workflows.repository.service.ts index 8a4ccf3..0437d79 100644 --- a/dictation_server/src/repositories/workflows/workflows.repository.service.ts +++ b/dictation_server/src/repositories/workflows/workflows.repository.service.ts @@ -13,7 +13,7 @@ import { WorktypeIdNotFoundError } from '../worktypes/errors/types'; import { TemplateFileNotExistError } from '../template_files/errors/types'; import { AuthorIdAndWorktypeIdPairAlreadyExistsError, - WorkflowNotFoundError, + WorkflowIdNotFoundError, } from './errors/types'; @Injectable() @@ -61,9 +61,9 @@ export class WorkflowsRepositoryService { async createtWorkflows( accountId: number, authorId: number, - typists: WorkflowTypist[], worktypeId?: number | undefined, templateId?: number | undefined, + typists?: WorkflowTypist[], ): Promise { return await this.dataSource.transaction(async (entityManager) => { // authorの存在確認 @@ -178,9 +178,9 @@ export class WorkflowsRepositoryService { accountId: number, workflowId: number, authorId: number, - typists: WorkflowTypist[], worktypeId?: number | undefined, templateId?: number | undefined, + typists?: WorkflowTypist[], ): Promise { return await this.dataSource.transaction(async (entityManager) => { const workflowRepo = entityManager.getRepository(Workflow); @@ -190,7 +190,7 @@ export class WorkflowsRepositoryService { where: { account_id: accountId, id: workflowId }, }); if (!targetWorkflow) { - throw new WorkflowNotFoundError( + throw new WorkflowIdNotFoundError( `workflow not found. id: ${workflowId}`, ); } @@ -300,32 +300,6 @@ export class WorkflowsRepositoryService { }); } - /** - * ワークフローを削除する - * @param accountId - * @param workflowId - * @returns workflow - */ - async deleteWorkflow(accountId: number, workflowId: number): Promise { - return await this.dataSource.transaction(async (entityManager) => { - const workflowRepo = entityManager.getRepository(Workflow); - const workflowTypistsRepo = entityManager.getRepository(DbWorkflowTypist); - - // ワークフローの存在確認 - const workflow = await workflowRepo.findOne({ - where: { account_id: accountId, id: workflowId }, - }); - if (!workflow) { - throw new WorkflowNotFoundError( - `workflow not found. id: ${workflowId}`, - ); - } - - await workflowTypistsRepo.delete({ workflow_id: workflowId }); - await workflowRepo.delete(workflowId); - }); - } - /** * DBに保存するワークフローデータを作成する * @param accountId @@ -337,14 +311,14 @@ export class WorkflowsRepositoryService { private makeWorkflow( accountId: number, authorId: number, - worktypeId?: number, - templateId?: number, + worktypeId?: number | undefined, + templateId?: number | undefined, ): Workflow { const workflow = new Workflow(); workflow.account_id = accountId; workflow.author_id = authorId; - workflow.worktype_id = worktypeId ?? null; - workflow.template_id = templateId ?? null; + workflow.worktype_id = worktypeId; + workflow.template_id = templateId; return workflow; } @@ -358,13 +332,13 @@ export class WorkflowsRepositoryService { */ private makeWorkflowTypist( workflowId: number, - typistId?: number, - typistGroupId?: number, + typistId: number, + typistGroupId: number, ): DbWorkflowTypist { const workflowTypist = new DbWorkflowTypist(); workflowTypist.workflow_id = workflowId; - workflowTypist.typist_id = typistId ?? null; - workflowTypist.typist_group_id = typistGroupId ?? null; + workflowTypist.typist_id = typistId; + workflowTypist.typist_group_id = typistGroupId; return workflowTypist; } diff --git a/dictation_server/src/repositories/worktypes/entity/option_item.entity.ts b/dictation_server/src/repositories/worktypes/entity/option_item.entity.ts index 1aa911a..fd0a14e 100644 --- a/dictation_server/src/repositories/worktypes/entity/option_item.entity.ts +++ b/dictation_server/src/repositories/worktypes/entity/option_item.entity.ts @@ -18,18 +18,12 @@ export class OptionItem { default_value_type: string; @Column() initial_value: string; - @Column({ nullable: true, type: 'datetime' }) - created_by: string | null; - @CreateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) // defaultはSQLite用設定値.本番用は別途migrationで設定 - created_at: Date | null; - @Column({ nullable: true, type: 'datetime' }) - updated_by: string | null; - @UpdateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) // defaultはSQLite用設定値.本番用は別途migrationで設定 - updated_at: Date | null; + @Column({ nullable: true }) + created_by?: string; + @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + created_at?: Date; + @Column({ nullable: true }) + updated_by?: string; + @UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + updated_at?: Date; } diff --git a/dictation_server/src/repositories/worktypes/entity/worktype.entity.ts b/dictation_server/src/repositories/worktypes/entity/worktype.entity.ts index 854e634..67d055a 100644 --- a/dictation_server/src/repositories/worktypes/entity/worktype.entity.ts +++ b/dictation_server/src/repositories/worktypes/entity/worktype.entity.ts @@ -18,27 +18,21 @@ export class Worktype { @Column() custom_worktype_id: string; - @Column({ nullable: true, type: 'varchar' }) - description: string | null; + @Column({ nullable: true }) + description?: string; - @Column({ nullable: true, type: 'datetime' }) - deleted_at: Date | null; + @Column({ nullable: true }) + deleted_at?: Date; - @Column({ nullable: true, type: 'datetime' }) - created_by: string | null; + @Column({ nullable: true }) + created_by: string; - @CreateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 created_at: Date; - @Column({ nullable: true, type: 'datetime' }) - updated_by: string | null; + @Column({ nullable: true }) + updated_by?: string; - @UpdateDateColumn({ - default: () => "datetime('now', 'localtime')", - type: 'datetime', - }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + @UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 updated_at: Date; } diff --git a/dictation_server/src/repositories/worktypes/errors/types.ts b/dictation_server/src/repositories/worktypes/errors/types.ts index 6fe60c1..af772a1 100644 --- a/dictation_server/src/repositories/worktypes/errors/types.ts +++ b/dictation_server/src/repositories/worktypes/errors/types.ts @@ -4,5 +4,3 @@ export class WorktypeIdAlreadyExistsError extends Error {} export class WorktypeIdMaxCountError extends Error {} // WorktypeID不在エラー export class WorktypeIdNotFoundError extends Error {} -// WorktypeID使用中エラー -export class WorktypeIdInUseError extends Error {} diff --git a/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts b/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts index 616f9e0..7ce50da 100644 --- a/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts +++ b/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts @@ -8,7 +8,6 @@ import { } from '../../constants'; import { WorktypeIdAlreadyExistsError, - WorktypeIdInUseError, WorktypeIdMaxCountError, WorktypeIdNotFoundError, } from './errors/types'; @@ -16,7 +15,6 @@ import { OptionItem } from './entity/option_item.entity'; 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'; @Injectable() export class WorktypesRepositoryService { @@ -153,63 +151,11 @@ export class WorktypesRepositoryService { // ワークタイプを更新 worktype.custom_worktype_id = worktypeId; - worktype.description = description ?? null; + worktype.description = description; await worktypeRepo.save(worktype); }); } - /** - * ワークタイプを削除する - * @param accountId - * @param id - * @returns worktype - */ - async deleteWorktype(accountId: number, id: number): Promise { - await this.dataSource.transaction(async (entityManager) => { - const worktypeRepo = entityManager.getRepository(Worktype); - - const worktype = await worktypeRepo.findOne({ - where: { account_id: accountId, id: id }, - }); - // ワークタイプが存在しない場合はエラー - if (!worktype) { - throw new WorktypeIdNotFoundError(`Worktype is not found. id: ${id}`); - } - - // アカウントのActiveWorktypeIDが削除対象のワークタイプIDの場合はActiveWorktypeIDをnullに更新 - const accountRepo = entityManager.getRepository(Account); - const account = await accountRepo.findOne({ - where: { id: accountId }, - }); - - if (account?.active_worktype_id === id) { - await accountRepo.update( - { id: accountId }, - { active_worktype_id: null }, - ); - } - - // ワークタイプがワークフローに紐づいている場合はエラー - const workflowRepo = entityManager.getRepository(Workflow); - const workflows = await workflowRepo.find({ - where: { account_id: accountId, worktype_id: id }, - }); - if (workflows.length > 0) { - const workflowIds = workflows.map((workflow) => workflow.id); - throw new WorktypeIdInUseError( - `Worktype is in use by workflow. worktype id: ${id}, workflow ids: [${workflowIds}]`, - ); - } - - // ワークタイプに紐づくオプションアイテムを削除 - const optionItemRepo = entityManager.getRepository(OptionItem); - await optionItemRepo.delete({ worktype_id: id }); - - // ワークタイプを削除 - await worktypeRepo.delete({ id: id }); - }); - } - /** * オプションアイテム一覧を取得する * @param accountId diff --git a/dictation_server/tsconfig.json b/dictation_server/tsconfig.json index 8b9d137..0c28902 100644 --- a/dictation_server/tsconfig.json +++ b/dictation_server/tsconfig.json @@ -12,7 +12,7 @@ "baseUrl": "./", "incremental": true, "skipLibCheck": true, - "strictNullChecks": true, + "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, From b88c0d9b96bed4eb1bf515c67cfc51127ba24847 Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Fri, 19 Apr 2024 04:47:32 +0000 Subject: [PATCH 08/13] =?UTF-8?q?Merged=20PR=20877:=20=E3=83=87=E3=82=A3?= =?UTF-8?q?=E3=83=BC=E3=83=A9=E3=83=BC=E5=8F=96=E5=BE=97API=E3=81=AE?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task4104: ディーラー取得APIの修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4104) - メールの文面を各言語版に置き換えました。 - 環境変数に設定されたアカウントIDのDealerはResponseに含めないように修正 ## レビューポイント - 環境変数からインスタンス変数に代入するときの処理に問題はあるか - 環境変数のフォーマットはこれで良いか - もっとよいやり方があれば指摘いただきたいです - テストケースに不足はないか ## UIの変更 - なし ## クエリの変更 - なし ## 動作確認状況 - ローカルで確認 - 行った修正がデグレを発生させていないことを確認できるか - ほかのテストに影響が出ていない --- dictation_server/.env.local.example | 3 +- dictation_server/.env.test | 3 +- .../src/common/validators/env.validator.ts | 4 ++ .../accounts/accounts.service.spec.ts | 29 ++++++++++++ .../src/features/accounts/accounts.service.ts | 46 +++++++++++++++++-- 5 files changed, 80 insertions(+), 5 deletions(-) diff --git a/dictation_server/.env.local.example b/dictation_server/.env.local.example index 8c03436..5aaecca 100644 --- a/dictation_server/.env.local.example +++ b/dictation_server/.env.local.example @@ -33,4 +33,5 @@ EMAIL_CONFIRM_LIFETIME=86400 REDIS_HOST=redis-cache REDIS_PORT=6379 REDIS_PASSWORD=omdsredispass -ADB2C_CACHE_TTL=86400 \ No newline at end of file +ADB2C_CACHE_TTL=86400 +DEALER_ACCOUNT_ID_HIDDEN_LIST=1,2,3,4 \ No newline at end of file diff --git a/dictation_server/.env.test b/dictation_server/.env.test index a2748df..ca88821 100644 --- a/dictation_server/.env.test +++ b/dictation_server/.env.test @@ -34,4 +34,5 @@ REDIS_HOST=redis-cache REDIS_PORT=6379 REDIS_PASSWORD=omdsredispass ADB2C_CACHE_TTL=86400 -TEMPLATE_ROOT=dist \ No newline at end of file +TEMPLATE_ROOT=dist +DEALER_ACCOUNT_ID_HIDDEN_LIST=50,99 \ No newline at end of file diff --git a/dictation_server/src/common/validators/env.validator.ts b/dictation_server/src/common/validators/env.validator.ts index 8ce706f..8ce1547 100644 --- a/dictation_server/src/common/validators/env.validator.ts +++ b/dictation_server/src/common/validators/env.validator.ts @@ -32,6 +32,10 @@ export class EnvValidator { @IsString() DB_PASSWORD: string; + @IsOptional() + @IsString() + DEALER_ACCOUNT_ID_HIDDEN_LIST: string; + // .env.local @IsOptional() @IsString() diff --git a/dictation_server/src/features/accounts/accounts.service.spec.ts b/dictation_server/src/features/accounts/accounts.service.spec.ts index 29c5899..d5e0575 100644 --- a/dictation_server/src/features/accounts/accounts.service.spec.ts +++ b/dictation_server/src/features/accounts/accounts.service.spec.ts @@ -2645,6 +2645,35 @@ describe('getDealers', () => { ], }); }); + + it('非表示指定されたDealer以外のDealerを取得できる', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + // 100件のDealerを作成し、country,id,company_nameを取得する + const dealers: { country: string; id: number; name: string }[] = []; + for (let i = 0; i < 100; i++) { + const { id, company_name, country } = ( + await makeTestAccount(source, { + parent_account_id: i, + tier: TIERS.TIER4, + country: 'JP', + company_name: `DEALER_${i}`, + }) + ).account; + dealers.push({ id, name: company_name, country }); + } + const service = module.get(AccountsService); + const context = makeContext(`uuidv4`, 'requestId'); + const result = await service.getDealers(context); + // idが50と99のDealerを非表示にする + + expect(result.dealers.length).toBe(98); + expect(result).toEqual({ + dealers: dealers.filter((dealer) => dealer.id !== 50 && dealer.id !== 99), + }); + }); + it('0件でもDealerを取得できる', async () => { if (!source) fail(); const module = await makeTestingModule(source); diff --git a/dictation_server/src/features/accounts/accounts.service.ts b/dictation_server/src/features/accounts/accounts.service.ts index 8b6e271..688981b 100644 --- a/dictation_server/src/features/accounts/accounts.service.ts +++ b/dictation_server/src/features/accounts/accounts.service.ts @@ -72,9 +72,13 @@ import { WorktypeIdNotFoundError, } from '../../repositories/worktypes/errors/types'; import { getUserNameAndMailAddress } from '../../gateways/adb2c/utils/utils'; +import { ConfigService } from '@nestjs/config'; @Injectable() export class AccountsService { + // プロダクト バックログ項目 4077: [保守]本番環境動作確認用のDealerアカウントを表示しないようにする の対応 + private readonly dealerAccountIdHiddenList: number[] = []; + private readonly logger = new Logger(AccountsService.name); constructor( private readonly accountRepository: AccountsRepositoryService, private readonly licensesRepository: LicensesRepositoryService, @@ -84,8 +88,27 @@ export class AccountsService { private readonly adB2cService: AdB2cService, private readonly sendgridService: SendGridService, private readonly blobStorageService: BlobstorageService, - ) {} - private readonly logger = new Logger(AccountsService.name); + private readonly configService: ConfigService, + ) { + const dealerAccountIdList = this.configService.get( + 'DEALER_ACCOUNT_ID_HIDDEN_LIST', + ); + // ディーラーアカウントIDリストを数値配列に変換する + // 変換できない場合はエラーをスローする + // 存在しない場合や空文字列の場合は空の配列を返す + if (dealerAccountIdList) { + this.dealerAccountIdHiddenList = dealerAccountIdList + .split(',') + .map((x) => { + const id = parseInt(x, 10); + if (isNaN(id)) { + throw new Error('DEALER_ACCOUNT_ID_HIDDEN_LIST is invalid'); + } + return id; + }); + } + } + /** * 第五階層用のライセンス情報を取得する * @param accountId @@ -1174,9 +1197,26 @@ export class AccountsService { const dealerAccounts = await this.accountRepository.findDealerAccounts( context, ); + // プロダクト バックログ項目 4077: [保守]本番環境動作確認用のDealerアカウントを表示しないようにする の対応 + // this.dealerAccountIdHiddenListに含まれるアカウント(動作確認用のアカウント)を除外する。 + // 除外したアカウントをlogに出力する + const filteredDealerAccounts = dealerAccounts.filter((dealerAccount) => { + const isHidden = this.dealerAccountIdHiddenList.includes( + dealerAccount.id, + ); + if (isHidden) { + this.logger.log( + `[${context.getTrackingId()}] hidden dealer account: ${ + dealerAccount.id + }`, + ); + } + return !isHidden; + }); + // レスポンス用の型に変換 const dealers: GetDealersResponse = { - dealers: dealerAccounts.map((dealerAccount): Dealer => { + dealers: filteredDealerAccounts.map((dealerAccount): Dealer => { return { id: dealerAccount.id, name: dealerAccount.company_name, From 0b01da936dc97593637efb04d8dd85d79b0a72b8 Mon Sep 17 00:00:00 2001 From: "SAITO-PC-3\\saito.k" Date: Fri, 19 Apr 2024 19:15:23 +0900 Subject: [PATCH 09/13] =?UTF-8?q?CacheModule=E4=BD=9C=E6=88=90=E6=99=82?= =?UTF-8?q?=E3=81=ABTTL=E3=82=92=E8=A8=AD=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dictation_server/src/common/test/modules.ts | 2 +- dictation_server/src/features/accounts/accounts.service.spec.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dictation_server/src/common/test/modules.ts b/dictation_server/src/common/test/modules.ts index 7c51a50..8d50139 100644 --- a/dictation_server/src/common/test/modules.ts +++ b/dictation_server/src/common/test/modules.ts @@ -80,7 +80,7 @@ export const makeTestingModule = async ( WorktypesRepositoryModule, TermsRepositoryModule, RedisModule, - CacheModule.register({ isGlobal: true }), + CacheModule.register({ isGlobal: true, ttl: 86400 }), ], providers: [ AuthService, diff --git a/dictation_server/src/features/accounts/accounts.service.spec.ts b/dictation_server/src/features/accounts/accounts.service.spec.ts index d5e0575..e042a85 100644 --- a/dictation_server/src/features/accounts/accounts.service.spec.ts +++ b/dictation_server/src/features/accounts/accounts.service.spec.ts @@ -6873,6 +6873,7 @@ describe('deleteAccountAndData', () => { expect(userRecord).toBe(null); }); }); + describe('getAccountInfoMinimalAccess', () => { let source: DataSource | null = null; beforeAll(async () => { From 7eecb001c69f8f2a5c836ae9816990bb8f434508 Mon Sep 17 00:00:00 2001 From: "SAITO-PC-3\\saito.k" Date: Sun, 21 Apr 2024 22:41:44 +0900 Subject: [PATCH 10/13] =?UTF-8?q?4/22=E3=81=AE=E6=9C=AC=E7=95=AA=E7=92=B0?= =?UTF-8?q?=E5=A2=83=E3=83=87=E3=83=97=E3=83=AD=E3=82=A4=E3=81=AE=E3=81=9F?= =?UTF-8?q?=E3=82=81=E3=81=AB=E7=A2=BA=E8=AA=8D=E6=9C=AA=E5=AE=9F=E6=96=BD?= =?UTF-8?q?=E3=81=AE=E4=BF=AE=E6=AD=A3=E3=82=92=E3=82=B3=E3=83=A1=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=82=A2=E3=82=A6=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../accounts/accounts.service.spec.ts | 54 +++++++++--------- .../src/features/accounts/accounts.service.ts | 57 ++++++++++--------- 2 files changed, 57 insertions(+), 54 deletions(-) diff --git a/dictation_server/src/features/accounts/accounts.service.spec.ts b/dictation_server/src/features/accounts/accounts.service.spec.ts index e042a85..c76e4fe 100644 --- a/dictation_server/src/features/accounts/accounts.service.spec.ts +++ b/dictation_server/src/features/accounts/accounts.service.spec.ts @@ -2645,34 +2645,34 @@ describe('getDealers', () => { ], }); }); + // TODO 本番環境デプロイのためにこのテストはスキップする + // it('非表示指定されたDealer以外のDealerを取得できる', async () => { + // if (!source) fail(); + // const module = await makeTestingModule(source); + // if (!module) fail(); + // // 100件のDealerを作成し、country,id,company_nameを取得する + // const dealers: { country: string; id: number; name: string }[] = []; + // for (let i = 0; i < 100; i++) { + // const { id, company_name, country } = ( + // await makeTestAccount(source, { + // parent_account_id: i, + // tier: TIERS.TIER4, + // country: 'JP', + // company_name: `DEALER_${i}`, + // }) + // ).account; + // dealers.push({ id, name: company_name, country }); + // } + // const service = module.get(AccountsService); + // const context = makeContext(`uuidv4`, 'requestId'); + // const result = await service.getDealers(context); + // // idが50と99のDealerを非表示にする - it('非表示指定されたDealer以外のDealerを取得できる', async () => { - if (!source) fail(); - const module = await makeTestingModule(source); - if (!module) fail(); - // 100件のDealerを作成し、country,id,company_nameを取得する - const dealers: { country: string; id: number; name: string }[] = []; - for (let i = 0; i < 100; i++) { - const { id, company_name, country } = ( - await makeTestAccount(source, { - parent_account_id: i, - tier: TIERS.TIER4, - country: 'JP', - company_name: `DEALER_${i}`, - }) - ).account; - dealers.push({ id, name: company_name, country }); - } - const service = module.get(AccountsService); - const context = makeContext(`uuidv4`, 'requestId'); - const result = await service.getDealers(context); - // idが50と99のDealerを非表示にする - - expect(result.dealers.length).toBe(98); - expect(result).toEqual({ - dealers: dealers.filter((dealer) => dealer.id !== 50 && dealer.id !== 99), - }); - }); + // expect(result.dealers.length).toBe(98); + // expect(result).toEqual({ + // dealers: dealers.filter((dealer) => dealer.id !== 50 && dealer.id !== 99), + // }); + // }); it('0件でもDealerを取得できる', async () => { if (!source) fail(); diff --git a/dictation_server/src/features/accounts/accounts.service.ts b/dictation_server/src/features/accounts/accounts.service.ts index 688981b..aaefaf6 100644 --- a/dictation_server/src/features/accounts/accounts.service.ts +++ b/dictation_server/src/features/accounts/accounts.service.ts @@ -76,8 +76,9 @@ import { ConfigService } from '@nestjs/config'; @Injectable() export class AccountsService { - // プロダクト バックログ項目 4077: [保守]本番環境動作確認用のDealerアカウントを表示しないようにする の対応 - private readonly dealerAccountIdHiddenList: number[] = []; + // TODO 本番環境デプロイのためにこの変数はコメントアウトする 2024年4月22日 + //プロダクト バックログ項目 4077: [保守]本番環境動作確認用のDealerアカウントを表示しないようにする の対応 + // private readonly dealerAccountIdHiddenList: number[] = []; private readonly logger = new Logger(AccountsService.name); constructor( private readonly accountRepository: AccountsRepositoryService, @@ -93,20 +94,21 @@ export class AccountsService { const dealerAccountIdList = this.configService.get( 'DEALER_ACCOUNT_ID_HIDDEN_LIST', ); + // TODO 本番環境デプロイのためにこの変数はコメントアウトする 2024年4月22日 // ディーラーアカウントIDリストを数値配列に変換する // 変換できない場合はエラーをスローする // 存在しない場合や空文字列の場合は空の配列を返す - if (dealerAccountIdList) { - this.dealerAccountIdHiddenList = dealerAccountIdList - .split(',') - .map((x) => { - const id = parseInt(x, 10); - if (isNaN(id)) { - throw new Error('DEALER_ACCOUNT_ID_HIDDEN_LIST is invalid'); - } - return id; - }); - } + // if (dealerAccountIdList) { + // this.dealerAccountIdHiddenList = dealerAccountIdList + // .split(',') + // .map((x) => { + // const id = parseInt(x, 10); + // if (isNaN(id)) { + // throw new Error('DEALER_ACCOUNT_ID_HIDDEN_LIST is invalid'); + // } + // return id; + // }); + // } } /** @@ -1197,26 +1199,27 @@ export class AccountsService { const dealerAccounts = await this.accountRepository.findDealerAccounts( context, ); + // TODO 本番環境デプロイのためにこの変数はコメントアウトする 2024年4月22日 // プロダクト バックログ項目 4077: [保守]本番環境動作確認用のDealerアカウントを表示しないようにする の対応 // this.dealerAccountIdHiddenListに含まれるアカウント(動作確認用のアカウント)を除外する。 // 除外したアカウントをlogに出力する - const filteredDealerAccounts = dealerAccounts.filter((dealerAccount) => { - const isHidden = this.dealerAccountIdHiddenList.includes( - dealerAccount.id, - ); - if (isHidden) { - this.logger.log( - `[${context.getTrackingId()}] hidden dealer account: ${ - dealerAccount.id - }`, - ); - } - return !isHidden; - }); + // const filteredDealerAccounts = dealerAccounts.filter((dealerAccount) => { + // const isHidden = this.dealerAccountIdHiddenList.includes( + // dealerAccount.id, + // ); + // if (isHidden) { + // this.logger.log( + // `[${context.getTrackingId()}] hidden dealer account: ${ + // dealerAccount.id + // }`, + // ); + // } + // return !isHidden; + // }); // レスポンス用の型に変換 const dealers: GetDealersResponse = { - dealers: filteredDealerAccounts.map((dealerAccount): Dealer => { + dealers: dealerAccounts.map((dealerAccount): Dealer => { return { id: dealerAccount.id, name: dealerAccount.company_name, From ba7196cac16098324ac2e2a60396f5e58d078270 Mon Sep 17 00:00:00 2001 From: "SAITO-PC-3\\saito.k" Date: Mon, 22 Apr 2024 10:44:56 +0900 Subject: [PATCH 11/13] =?UTF-8?q?=E6=9C=AC=E7=95=AA=E3=83=87=E3=83=97?= =?UTF-8?q?=E3=83=AD=E3=82=A4=E3=82=88=E3=81=86=E3=81=AB=E3=82=B3=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E3=82=A2=E3=82=A6=E3=83=88=E3=81=97=E3=81=9F?= =?UTF-8?q?=E5=AE=9F=E8=A3=85=E3=82=92=E3=82=82=E3=81=A8=E3=81=AB=E6=88=BB?= =?UTF-8?q?=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../accounts/accounts.service.spec.ts | 54 +++++++++--------- .../src/features/accounts/accounts.service.ts | 55 +++++++++---------- 2 files changed, 53 insertions(+), 56 deletions(-) diff --git a/dictation_server/src/features/accounts/accounts.service.spec.ts b/dictation_server/src/features/accounts/accounts.service.spec.ts index c76e4fe..6debabc 100644 --- a/dictation_server/src/features/accounts/accounts.service.spec.ts +++ b/dictation_server/src/features/accounts/accounts.service.spec.ts @@ -2645,34 +2645,34 @@ describe('getDealers', () => { ], }); }); - // TODO 本番環境デプロイのためにこのテストはスキップする - // it('非表示指定されたDealer以外のDealerを取得できる', async () => { - // if (!source) fail(); - // const module = await makeTestingModule(source); - // if (!module) fail(); - // // 100件のDealerを作成し、country,id,company_nameを取得する - // const dealers: { country: string; id: number; name: string }[] = []; - // for (let i = 0; i < 100; i++) { - // const { id, company_name, country } = ( - // await makeTestAccount(source, { - // parent_account_id: i, - // tier: TIERS.TIER4, - // country: 'JP', - // company_name: `DEALER_${i}`, - // }) - // ).account; - // dealers.push({ id, name: company_name, country }); - // } - // const service = module.get(AccountsService); - // const context = makeContext(`uuidv4`, 'requestId'); - // const result = await service.getDealers(context); - // // idが50と99のDealerを非表示にする + + it('非表示指定されたDealer以外のDealerを取得できる', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + // 100件のDealerを作成し、country,id,company_nameを取得する + const dealers: { country: string; id: number; name: string }[] = []; + for (let i = 0; i < 100; i++) { + const { id, company_name, country } = ( + await makeTestAccount(source, { + parent_account_id: i, + tier: TIERS.TIER4, + country: 'JP', + company_name: `DEALER_${i}`, + }) + ).account; + dealers.push({ id, name: company_name, country }); + } + const service = module.get(AccountsService); + const context = makeContext(`uuidv4`, 'requestId'); + const result = await service.getDealers(context); + // idが50と99のDealerを非表示にする - // expect(result.dealers.length).toBe(98); - // expect(result).toEqual({ - // dealers: dealers.filter((dealer) => dealer.id !== 50 && dealer.id !== 99), - // }); - // }); + expect(result.dealers.length).toBe(98); + expect(result).toEqual({ + dealers: dealers.filter((dealer) => dealer.id !== 50 && dealer.id !== 99), + }); + }); it('0件でもDealerを取得できる', async () => { if (!source) fail(); diff --git a/dictation_server/src/features/accounts/accounts.service.ts b/dictation_server/src/features/accounts/accounts.service.ts index aaefaf6..94e7e3c 100644 --- a/dictation_server/src/features/accounts/accounts.service.ts +++ b/dictation_server/src/features/accounts/accounts.service.ts @@ -76,9 +76,8 @@ import { ConfigService } from '@nestjs/config'; @Injectable() export class AccountsService { - // TODO 本番環境デプロイのためにこの変数はコメントアウトする 2024年4月22日 //プロダクト バックログ項目 4077: [保守]本番環境動作確認用のDealerアカウントを表示しないようにする の対応 - // private readonly dealerAccountIdHiddenList: number[] = []; + private readonly dealerAccountIdHiddenList: number[] = []; private readonly logger = new Logger(AccountsService.name); constructor( private readonly accountRepository: AccountsRepositoryService, @@ -94,21 +93,20 @@ export class AccountsService { const dealerAccountIdList = this.configService.get( 'DEALER_ACCOUNT_ID_HIDDEN_LIST', ); - // TODO 本番環境デプロイのためにこの変数はコメントアウトする 2024年4月22日 // ディーラーアカウントIDリストを数値配列に変換する // 変換できない場合はエラーをスローする // 存在しない場合や空文字列の場合は空の配列を返す - // if (dealerAccountIdList) { - // this.dealerAccountIdHiddenList = dealerAccountIdList - // .split(',') - // .map((x) => { - // const id = parseInt(x, 10); - // if (isNaN(id)) { - // throw new Error('DEALER_ACCOUNT_ID_HIDDEN_LIST is invalid'); - // } - // return id; - // }); - // } + if (dealerAccountIdList) { + this.dealerAccountIdHiddenList = dealerAccountIdList + .split(',') + .map((x) => { + const id = parseInt(x, 10); + if (isNaN(id)) { + throw new Error('DEALER_ACCOUNT_ID_HIDDEN_LIST is invalid'); + } + return id; + }); + } } /** @@ -1199,27 +1197,26 @@ export class AccountsService { const dealerAccounts = await this.accountRepository.findDealerAccounts( context, ); - // TODO 本番環境デプロイのためにこの変数はコメントアウトする 2024年4月22日 // プロダクト バックログ項目 4077: [保守]本番環境動作確認用のDealerアカウントを表示しないようにする の対応 // this.dealerAccountIdHiddenListに含まれるアカウント(動作確認用のアカウント)を除外する。 // 除外したアカウントをlogに出力する - // const filteredDealerAccounts = dealerAccounts.filter((dealerAccount) => { - // const isHidden = this.dealerAccountIdHiddenList.includes( - // dealerAccount.id, - // ); - // if (isHidden) { - // this.logger.log( - // `[${context.getTrackingId()}] hidden dealer account: ${ - // dealerAccount.id - // }`, - // ); - // } - // return !isHidden; - // }); + const filteredDealerAccounts = dealerAccounts.filter((dealerAccount) => { + const isHidden = this.dealerAccountIdHiddenList.includes( + dealerAccount.id, + ); + if (isHidden) { + this.logger.log( + `[${context.getTrackingId()}] hidden dealer account: ${ + dealerAccount.id + }`, + ); + } + return !isHidden; + }); // レスポンス用の型に変換 const dealers: GetDealersResponse = { - dealers: dealerAccounts.map((dealerAccount): Dealer => { + dealers: filteredDealerAccounts.map((dealerAccount): Dealer => { return { id: dealerAccount.id, name: dealerAccount.company_name, From af0ba78ae9bda018f93c125ef39c1397f1175e7c Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Thu, 25 Apr 2024 08:56:03 +0000 Subject: [PATCH 12/13] =?UTF-8?q?Merged=20PR=20883:=20Functions=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task4132: Functions修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4132) - 自動割り当てを行うライセンスの取得条件を変更 - 有効期限が近いライセンスまたは有効期限が設定されていないライセンス(新規ライセンス)を取得する - 有効期限が近いものから割り当てを行うので、ソートはサーバー側で行うようにした - メール送信処理を追加 ## レビューポイント - テンプレート取得からメール送信までの実装で漏れはないか - テストケースは足りているか ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## クエリの変更 - 2行目で割り当てるライセンスを取得しているが、その条件を修正した - https://ndstokyo.sharepoint.com/:u:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%AF%E3%82%A8%E3%83%AA/task4132/after.log?csf=1&web=1&e=jh49c3 ## 動作確認状況 - ローカルで確認、develop環境で確認など - 行った修正がデグレを発生させていないことを確認できるか - 追加したメール送信処理を確認するように各テストを修正し、テストが通っている - テストの観点を拡充したうえでテストが通っていることを確認 - ライセンス割り当て履歴の内容をより詳細に確認するようにした ## 補足 - 相談、参考資料などがあれば --- dictation_function/src/constants/index.ts | 10 + .../src/functions/licenseAlert.ts | 16 +- .../src/functions/licenseAutoAllocation.ts | 363 ++++++++++- .../licenseAutoAllocationManualRetry.ts | 25 +- dictation_function/src/sendgrid/sendgrid.ts | 27 +- .../src/templates/template_U_108.html | 81 +++ .../src/templates/template_U_108.txt | 47 ++ .../templates/template_U_108_no_parent.html | 70 ++ .../templates/template_U_108_no_parent.txt | 41 ++ .../src/test/common/adb2c.mock.ts | 88 +++ .../src/test/common/sendGrid.mock.ts | 24 + dictation_function/src/test/common/utility.ts | 25 +- .../src/test/licenseAlert.spec.ts | 115 +--- .../src/test/licenseAutoAllocation.spec.ts | 611 ++++++++++++++++-- dictation_function/tsconfig.json | 2 +- 15 files changed, 1369 insertions(+), 176 deletions(-) create mode 100644 dictation_function/src/templates/template_U_108.html create mode 100644 dictation_function/src/templates/template_U_108.txt create mode 100644 dictation_function/src/templates/template_U_108_no_parent.html create mode 100644 dictation_function/src/templates/template_U_108_no_parent.txt create mode 100644 dictation_function/src/test/common/adb2c.mock.ts create mode 100644 dictation_function/src/test/common/sendGrid.mock.ts diff --git a/dictation_function/src/constants/index.ts b/dictation_function/src/constants/index.ts index f1e0b64..3975fda 100644 --- a/dictation_function/src/constants/index.ts +++ b/dictation_function/src/constants/index.ts @@ -293,3 +293,13 @@ export const HTTP_STATUS_CODES = { BAD_REQUEST: 400, INTERNAL_SERVER_ERROR: 500, }; + +/** + * メールの置換文字列 + * @const {string} + */ +export const CUSTOMER_NAME = "$CUSTOMER_NAME$"; +export const DEALER_NAME = "$DEALER_NAME$"; +export const TOP_URL = "$TOP_URL$"; +export const USER_NAME = "$USER_NAME$"; +export const USER_EMAIL = "$USER_EMAIL$"; \ No newline at end of file diff --git a/dictation_function/src/functions/licenseAlert.ts b/dictation_function/src/functions/licenseAlert.ts index 5e7fd6d..f6b5f10 100644 --- a/dictation_function/src/functions/licenseAlert.ts +++ b/dictation_function/src/functions/licenseAlert.ts @@ -404,7 +404,9 @@ async function sendAlertMail( // メールを送信 try { await sendgrid.sendMail( - targetAccount.primaryAdminEmail, + context, + [targetAccount.primaryAdminEmail], + [], mailFrom, subject, text, @@ -464,7 +466,9 @@ async function sendAlertMail( // メールを送信 try { await sendgrid.sendMail( - targetAccount.secondaryAdminEmail, + context, + [targetAccount.secondaryAdminEmail], + [], mailFrom, subject, text, @@ -523,7 +527,9 @@ async function sendAlertMail( // メールを送信 try { await sendgrid.sendMail( - targetAccount.primaryAdminEmail, + context, + [targetAccount.primaryAdminEmail], + [], mailFrom, subject, text, @@ -581,7 +587,9 @@ async function sendAlertMail( // メールを送信 try { await sendgrid.sendMail( - targetAccount.secondaryAdminEmail, + context, + [targetAccount.secondaryAdminEmail], + [], mailFrom, subject, text, diff --git a/dictation_function/src/functions/licenseAutoAllocation.ts b/dictation_function/src/functions/licenseAutoAllocation.ts index b4b623f..0e08f36 100644 --- a/dictation_function/src/functions/licenseAutoAllocation.ts +++ b/dictation_function/src/functions/licenseAutoAllocation.ts @@ -1,14 +1,20 @@ import { app, InvocationContext, Timer } from "@azure/functions"; -import { Between, DataSource, In, MoreThan, Repository } from "typeorm"; +import { Between, DataSource, In, IsNull, MoreThan, Repository } from "typeorm"; import { User } from "../entity/user.entity"; import { Account } from "../entity/account.entity"; import { License, LicenseAllocationHistory } from "../entity/license.entity"; import * as dotenv from "dotenv"; import { + ADB2C_SIGN_IN_TYPE, + CUSTOMER_NAME, + DEALER_NAME, LICENSE_ALLOCATED_STATUS, LICENSE_TYPE, SWITCH_FROM_TYPE, TIERS, + TOP_URL, + USER_EMAIL, + USER_NAME, USER_ROLES, } from "../constants"; import { @@ -16,10 +22,19 @@ import { DateWithZeroTime, NewAllocatedLicenseExpirationDate, } from "../common/types/types"; +import { readFileSync } from "fs"; +import path from "path"; +import { SendGridService } from "../sendgrid/sendgrid"; +import { AdB2cService } from "../adb2c/adb2c"; +import { RedisClient } from "redis"; +import { createRedisClient } from "../redis/redis"; export async function licenseAutoAllocationProcessing( context: InvocationContext, datasource: DataSource, + redisClient: RedisClient, + sendGrid: SendGridService, + adb2c: AdB2cService, dateToTrigger?: Date ): Promise { try { @@ -32,6 +47,7 @@ export async function licenseAutoAllocationProcessing( currentDateZeroTime = new DateWithZeroTime(dateToTrigger); currentDateEndTime = new DateWithDayEndTime(dateToTrigger); } + // 自動更新対象の候補となるアカウントを取得 const accountRepository = datasource.getRepository(Account); const targetAccounts = await accountRepository.find({ @@ -55,6 +71,9 @@ export async function licenseAutoAllocationProcessing( await allocateLicense( context, datasource, + redisClient, + sendGrid, + adb2c, autoAllocationList, currentDateZeroTime, currentDateEndTime @@ -94,8 +113,25 @@ export async function licenseAutoAllocation( context.error(e); throw e; } + let redisClient: RedisClient; + try { + // redis接続 + redisClient = createRedisClient(); + } catch (e) { + context.log("redis client create failed."); + context.error(e); + throw e; + } + const sendGrid = new SendGridService(); + const adb2c = new AdB2cService(); - await licenseAutoAllocationProcessing(context, datasource); + await licenseAutoAllocationProcessing( + context, + datasource, + redisClient, + sendGrid, + adb2c + ); } catch (e) { context.log("licenseAutoAllocation failed."); context.error(e); @@ -215,24 +251,45 @@ export async function getAutoAllocatableLicense( try { context.log("[IN]getAutoAllocatableLicense"); // 割り当て可能なライセンスを取得 - const license = await licenseRepository.findOne({ - where: { - account_id: accountId, - status: In([ - LICENSE_ALLOCATED_STATUS.REUSABLE, - LICENSE_ALLOCATED_STATUS.UNALLOCATED, - ]), - expiry_date: MoreThan(currentDateEndTime) || null, - }, - order: { - expiry_date: "ASC", - }, + const license = await licenseRepository.find({ + where: [ + { + account_id: accountId, + status: In([ + LICENSE_ALLOCATED_STATUS.REUSABLE, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ]), + expiry_date: MoreThan(currentDateEndTime), + }, + { + account_id: accountId, + status: In([ + LICENSE_ALLOCATED_STATUS.REUSABLE, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ]), + expiry_date: IsNull(), + }, + ], }); - if (!license) { + if (license.length === 0) { // 割り当て可能なライセンスが存在しない場合でもエラーとはしたくないので、undifinedを返却する return undefined; } - return license; + // ライセンスをソートする + // 有効期限が近いものから割り当てるため、expiry_dateがnullのものは最後にする + const sortedLicense = license.sort((a, b) => { + if (a.expiry_date && b.expiry_date) { + return a.expiry_date.getTime() - b.expiry_date.getTime(); + } else if (a.expiry_date && !b.expiry_date) { + return -1; + } else if (!a.expiry_date && b.expiry_date) { + return 1; + } else { + return 0; + } + }); + // 有効期限が近いライセンスを返却する + return sortedLicense[0]; } catch (e) { context.error(e); context.log("getAutoAllocatableLicense failed."); @@ -253,17 +310,23 @@ export async function getAutoAllocatableLicense( export async function allocateLicense( context: InvocationContext, datasource: DataSource, + redisClient: RedisClient, + sendGrid: SendGridService, + adb2c: AdB2cService, autoAllocationList: autoAllocationList, currentDateZeroTime: DateWithZeroTime, currentDateEndTime: DateWithDayEndTime ): Promise { + context.log("[IN]allocateLicense"); try { - context.log("[IN]allocateLicense"); - - // 自動更新対象ユーザーにライセンスを割り当てる + // 割り当て可能なライセンスが存在するかどうかのフラグ let hasAllocatebleLicense = true; + // ユーザーに割り当てられているライセンスが自動更新対象であるかどうかのフラグ + let hasAutoRenewLicense = true; for (const userId of autoAllocationList.userIds) { await datasource.transaction(async (entityManager) => { + // フラグの初期化 + hasAutoRenewLicense = true; const licenseRepository = entityManager.getRepository(License); const licenseAllocationHistoryRepo = entityManager.getRepository( LicenseAllocationHistory @@ -292,6 +355,7 @@ export async function allocateLicense( }); if (!allocatedLicense) { context.log(`skip auto allocation. userID:${userId}`); + hasAutoRenewLicense = false; return; } @@ -365,17 +429,276 @@ export async function allocateLicense( if (!hasAllocatebleLicense) { break; } + + // ユーザーに割り当てられているライセンスが自動更新対象であるかどうかのフラグがfalseの場合、次のユーザーへ + if (!hasAutoRenewLicense) { + continue; + } + + try { + //メール送信に必要な情報をDBから取得 + const userRepository = datasource.getRepository(User); + const accountRepository = datasource.getRepository(Account); + + // ライセンスを割り当てたユーザーとアカウントの情報を取得 + const user = await userRepository.findOne({ + where: { id: userId }, + }); + if (!user) { + throw new Error(`Target user not found. ${userId}`); + } + const account = await accountRepository.findOne({ + where: { id: autoAllocationList.accountId }, + relations: { + primaryAdminUser: true, + secondaryAdminUser: true, + }, + }); + if (!account) { + throw new Error( + `Target account not found. ${autoAllocationList.accountId}` + ); + } + // アカウントのプライマリー管理者が存在しない場合はエラー + if (!account.primaryAdminUser) { + throw new Error( + `Primary admin user not found. accountID: ${account.id}` + ); + } + // 親アカウントが存在する場合は取得 + let parentAccount: Account | null = null; + if (account.parent_account_id) { + parentAccount = await accountRepository.findOne({ + where: { id: account.parent_account_id }, + }); + if (!parentAccount) { + throw new Error( + `Parent account not found. accountID: ${account.parent_account_id}` + ); + } + } + + // アカウントの管理者とライセンスを割り当てたユーザーのメールアドレス取得に必要な外部IDを抽出 + const externalIds: string[] = []; + externalIds.push(user.external_id); + externalIds.push(account.primaryAdminUser.external_id); + // セカンダリ管理者が存在する場合はセカンダリ管理者の外部IDも抽出 + if (account.secondaryAdminUser) { + externalIds.push(account.secondaryAdminUser.external_id); + } + const adb2cUsers = await getMailAddressAndDisplayNameList( + context, + redisClient, + adb2c, + externalIds + ); + // ライセンス割り当てされたユーザーの名前を取得 + const userName = adb2cUsers.find( + (adb2cUser) => adb2cUser.externalId === user.external_id + )?.displayName; + if (!userName) { + throw new Error( + `Target ADb2Cuser name not found. externalId=${user.external_id}` + ); + } + // ライセンス割り当てされたユーザーのメールアドレスを取得 + const userMail = adb2cUsers.find( + (adb2cUser) => adb2cUser.externalId === user.external_id + )?.mailAddress; + if (!userMail) { + throw new Error( + `Target ADb2Cuser mail not found. externalId=${user.external_id}` + ); + } + // アカウントのプライマリー管理者のメールアドレスを取得 + const adminMails: string[] = []; + const primaryAdminMail = adb2cUsers.find( + (adb2cUser) => + adb2cUser.externalId === account.primaryAdminUser?.external_id + )?.mailAddress; + if (!primaryAdminMail) { + throw new Error( + `Primary admin user mail not found. externalId=${account.primaryAdminUser.external_id}` + ); + } + adminMails.push(primaryAdminMail); + + // アカウントのセカンダリ管理者のメールアドレスを取得 + const secondaryAdminMail = adb2cUsers.find( + (adb2cUser) => + adb2cUser.externalId === account.secondaryAdminUser?.external_id + )?.mailAddress; + if (secondaryAdminMail) { + adminMails.push(secondaryAdminMail); + } + + // メール送信 + await sendMailWithU108( + context, + userName, + userMail, + adminMails, + account.company_name, + parentAccount ? parentAccount.company_name : null, + sendGrid + ); + } catch (e) { + context.error(`error=${e}`); + // メール送信に関する例外はログだけ出して握りつぶす + } } } catch (e) { // エラーが発生しても次のアカウントへの処理は継続させるため、例外をthrowせずにreturnだけする - context.error(e); context.log("allocateLicense failed."); + context.error(e); return; } finally { context.log("[OUT]allocateLicense"); } } +// adb2cから指定した外部IDのユーザー情報を取得する +export async function getMailAddressAndDisplayNameList( + context: InvocationContext, + redisClient: RedisClient, + adb2c: AdB2cService, + externalIds: string[] +): Promise< + { + externalId: string; + displayName: string; + mailAddress: string; + }[] +> { + context.log("[IN]getUsers"); + try { + const users = [] as { + externalId: string; + displayName: string; + mailAddress: string; + }[]; + // 外部IDからADB2Cユーザー情報を取得 + const adb2cUsers = await adb2c.getUsers(context, redisClient, externalIds); + for (const externalId of externalIds) { + const adb2cUser = adb2cUsers.find((user) => user.id === externalId); + if (!adb2cUser) { + throw new Error(`ADB2C user not found. externalId=${externalId}`); + } + const mailAddress = adb2cUser.identities?.find( + (identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS + )?.issuerAssignedId; + if (!mailAddress) { + throw new Error(`ADB2C user mail not found. externalId=${externalId}`); + } + users.push({ + externalId: externalId, + displayName: adb2cUser.displayName, + mailAddress: mailAddress, + }); + } + return users; + } catch (e) { + context.error(e); + context.log("getUsers failed."); + throw e; + } finally { + context.log("[OUT]getUsers"); + } +} + +/** + * U-108のテンプレートを使用したメールを送信する + * @param context + * @param userName ライセンス割り当てされたユーザーの名前 + * @param userMail ライセンス割り当てされたユーザーのメールアドレス + * @param customerAdminMails ライセンス割り当てされたユーザーの所属するアカウントの管理者(primary/secondary)のメールアドレス + * @param customerAccountName ライセンス割り当てされたユーザーの所属するアカウントの名前 + * @param dealerAccountName 問題発生時に問い合わせする先の上位のディーラー名(会社名) + * @returns mail with u108 + */ +export async function sendMailWithU108( + context: InvocationContext, + userName: string, + userMail: string, + customerAdminMails: string[], + customerAccountName: string, + dealerAccountName: string | null, + sendGrid: SendGridService +): Promise { + context.log("[IN] sendMailWithU108"); + try { + const subject = "License Assigned Notification [U-108]"; + const domain = process.env.APP_DOMAIN; + if (!domain) { + throw new Error("APP_DOMAIN is not defined."); + } + const mailFrom = process.env.MAIL_FROM; + if (!mailFrom) { + throw new Error("MAIL_FROM is not defined."); + } + const url = new URL(domain).href; + + let html: string; + let text: string; + + if (dealerAccountName === null) { + const templateU108NoParentHtml = readFileSync( + path.resolve(__dirname, `../templates/template_U_108_no_parent.html`), + "utf-8" + ); + const templateU108NoParentText = readFileSync( + path.resolve(__dirname, `../templates/template_U_108_no_parent.txt`), + "utf-8" + ); + html = templateU108NoParentHtml + .replaceAll(CUSTOMER_NAME, customerAccountName) + .replaceAll(USER_NAME, userName) + .replaceAll(USER_EMAIL, userMail) + .replaceAll(TOP_URL, url); + text = templateU108NoParentText + .replaceAll(CUSTOMER_NAME, customerAccountName) + .replaceAll(USER_NAME, userName) + .replaceAll(USER_EMAIL, userMail) + .replaceAll(TOP_URL, url); + } else { + const templateU108Html = readFileSync( + path.resolve(__dirname, `../templates/template_U_108.html`), + "utf-8" + ); + const templateU108Text = readFileSync( + path.resolve(__dirname, `../templates/template_U_108.txt`), + "utf-8" + ); + html = templateU108Html + .replaceAll(CUSTOMER_NAME, customerAccountName) + .replaceAll(DEALER_NAME, dealerAccountName) + .replaceAll(USER_NAME, userName) + .replaceAll(USER_EMAIL, userMail) + .replaceAll(TOP_URL, url); + text = templateU108Text + .replaceAll(CUSTOMER_NAME, customerAccountName) + .replaceAll(DEALER_NAME, dealerAccountName) + .replaceAll(USER_NAME, userName) + .replaceAll(USER_EMAIL, userMail) + .replaceAll(TOP_URL, url); + } + const ccAddress = customerAdminMails.includes(userMail) ? [] : [userMail]; + + // メールを送信する + await sendGrid.sendMail( + context, + customerAdminMails, + ccAddress, + mailFrom, + subject, + text, + html + ); + } finally { + context.log(`[OUT] sendMailWithU108`); + } +} + app.timer("licenseAutoAllocation", { schedule: "0 0 16 * * *", handler: licenseAutoAllocation, diff --git a/dictation_function/src/functions/licenseAutoAllocationManualRetry.ts b/dictation_function/src/functions/licenseAutoAllocationManualRetry.ts index 18fa0f2..5864510 100644 --- a/dictation_function/src/functions/licenseAutoAllocationManualRetry.ts +++ b/dictation_function/src/functions/licenseAutoAllocationManualRetry.ts @@ -12,6 +12,10 @@ import { User } from "../entity/user.entity"; import { Account } from "../entity/account.entity"; import { License, LicenseAllocationHistory } from "../entity/license.entity"; import { HTTP_METHODS, HTTP_STATUS_CODES } from "../constants"; +import { RedisClient } from "redis"; +import { createRedisClient } from "../redis/redis"; +import { AdB2cService } from "../adb2c/adb2c"; +import { SendGridService } from "../sendgrid/sendgrid"; export async function licenseAutoAllocationManualRetry( req: HttpRequest, @@ -57,8 +61,27 @@ export async function licenseAutoAllocationManualRetry( context.error(e); throw e; } + let redisClient: RedisClient; + try { + // redis接続 + redisClient = createRedisClient(); + } catch (e) { + context.log("redis client create failed."); + context.error(e); + throw e; + } + const sendGrid = new SendGridService(); + const adb2c = new AdB2cService(); + - await licenseAutoAllocationProcessing(context, datasource, dateToTrigger); + await licenseAutoAllocationProcessing( + context, + datasource, + redisClient, + sendGrid, + adb2c, + dateToTrigger + ); context.log("Automatic license allocation has been triggered."); return { status: HTTP_STATUS_CODES.OK, diff --git a/dictation_function/src/sendgrid/sendgrid.ts b/dictation_function/src/sendgrid/sendgrid.ts index cb90de1..ef8e049 100644 --- a/dictation_function/src/sendgrid/sendgrid.ts +++ b/dictation_function/src/sendgrid/sendgrid.ts @@ -1,3 +1,4 @@ +import { InvocationContext } from "@azure/functions"; import sendgrid from "@sendgrid/mail"; import { error } from "console"; @@ -9,9 +10,11 @@ export class SendGridService { sendgrid.setApiKey(process.env.SENDGRID_API_KEY); } - /** + /** * メールを送信する + * @param context * @param to + * @param cc * @param from * @param subject * @param text @@ -19,28 +22,40 @@ export class SendGridService { * @returns mail */ async sendMail( - to: string, + context: InvocationContext, + to: string[], + cc: string[], from: string, subject: string, text: string, - html: string + html: string, ): Promise { + context.log(`[IN] ${this.sendMail.name}`); try { const res = await sendgrid .send({ from: { email: from, }, - to: { - email: to, - }, + to: to.map((v) => ({ email: v })), + cc: cc.map((v) => ({ email: v })), subject: subject, text: text, html: html, }) .then((v) => v[0]); + context.log( + ` status code: ${ + res.statusCode + } body: ${JSON.stringify(res.body)}`, + ); } catch (e) { + context.warn(`send mail faild.`); + context.warn(`${this.sendMail.name} error=${e}`); throw e; + } finally { + context.log(`[OUT] ${this.sendMail.name}`); } } } + diff --git a/dictation_function/src/templates/template_U_108.html b/dictation_function/src/templates/template_U_108.html new file mode 100644 index 0000000..0ba001c --- /dev/null +++ b/dictation_function/src/templates/template_U_108.html @@ -0,0 +1,81 @@ + + + License Assigned Notification [U-108] + + +

      +

      <English>

      +

      Dear $CUSTOMER_NAME$,

      +

      + Please be informed that a license has been assigned to the following + user.
      + - User Name: $USER_NAME$
      + - Email: $USER_EMAIL$ +

      +

      + Please log in to ODMS Cloud to verify the license expiration date.
      + URL: $TOP_URL$ +

      +

      + If you need support regarding ODMS Cloud, please contact $DEALER_NAME$. +

      +

      + If you have received this e-mail in error, please delete this e-mail + from your system.
      + This is an automatically generated e-mail and this mailbox is not + monitored. Please do not reply. +

      +
      +
      +

      <Deutsch>

      +

      Sehr geehrte(r) $CUSTOMER_NAME$,

      +

      + Bitte beachten Sie, dass dem folgenden Benutzer eine Lizenz zugewiesen + wurde.
      + - Nutzername: $USER_NAME$
      + - Email: $USER_EMAIL$ +

      +

      + Bitte melden Sie sich bei ODMS Cloud an, um das Ablaufdatum der Lizenz + zu überprüfen.
      + URL: $TOP_URL$ +

      +

      + Wenn Sie Unterstützung bezüglich ODMS Cloud benötigen, wenden Sie sich + bitte an $DEALER_NAME$. +

      +

      + Wenn Sie diese E-Mail irrtümlich erhalten haben, löschen Sie diese + E-Mail bitte aus Ihrem System.
      + Dies ist eine automatisch generierte + E-Mail und diese Mailbox wird nicht überwacht. Bitte antworten Sie + nicht. +

      +
      +
      +

      <Français>

      +

      Chère/Cher $CUSTOMER_NAME$,

      +

      + Veuillez être informé qu'une licence a été attribuée à l'utilisateur + suivant.
      + - Nom d'utilisateur: $USER_NAME$
      + - Email: $USER_EMAIL$ +

      +

      + Veuillez vous connecter à ODMS Cloud pour vérifier la date d'expiration + de la licence.
      + URL: $TOP_URL$ +

      +

      + Si vous avez besoin d'assistance concernant ODMS Cloud, veuillez + contacter $DEALER_NAME$. +

      +

      + Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail + de votre système.
      + Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres + n'est pas surveillée. Merci de ne pas répondre. +

      +
      + + diff --git a/dictation_function/src/templates/template_U_108.txt b/dictation_function/src/templates/template_U_108.txt new file mode 100644 index 0000000..3615550 --- /dev/null +++ b/dictation_function/src/templates/template_U_108.txt @@ -0,0 +1,47 @@ + + +Dear $CUSTOMER_NAME$, + +Please be informed that a license has been assigned to the following user. + - User Name: $USER_NAME$ + - Email: $USER_EMAIL$ + +Please log in to ODMS Cloud to verify the license expiration date. +URL: $TOP_URL$ + +If you need support regarding ODMS Cloud, please contact $DEALER_NAME$. + +If you have received this e-mail in error, please delete this e-mail from your system. +This is an automatically generated e-mail and this mailbox is not monitored. Please do not reply. + + + +Sehr geehrte(r) $CUSTOMER_NAME$, + +Bitte beachten Sie, dass dem folgenden Benutzer eine Lizenz zugewiesen wurde. + - Nutzername: $USER_NAME$ + - Email: $USER_EMAIL$ + +Bitte melden Sie sich bei ODMS Cloud an, um das Ablaufdatum der Lizenz zu überprüfen. +URL: $TOP_URL$ + +Wenn Sie Unterstützung bezüglich ODMS Cloud benötigen, wenden Sie sich bitte an $DEALER_NAME$. + +Wenn Sie diese E-Mail irrtümlich erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System. +Dies ist eine automatisch generierte E-Mail und diese Mailbox wird nicht überwacht. Bitte antworten Sie nicht. + + + +Chère/Cher $CUSTOMER_NAME$, + +Veuillez être informé qu'une licence a été attribuée à l'utilisateur suivant. + - Nom d'utilisateur: $USER_NAME$ + - Email: $USER_EMAIL$ + +Veuillez vous connecter à ODMS Cloud pour vérifier la date d'expiration de la licence. +URL: $TOP_URL$ + +Si vous avez besoin d'assistance concernant ODMS Cloud, veuillez contacter $DEALER_NAME$. + +Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail de votre système. +Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres n'est pas surveillée. Merci de ne pas répondre. \ No newline at end of file diff --git a/dictation_function/src/templates/template_U_108_no_parent.html b/dictation_function/src/templates/template_U_108_no_parent.html new file mode 100644 index 0000000..6f93225 --- /dev/null +++ b/dictation_function/src/templates/template_U_108_no_parent.html @@ -0,0 +1,70 @@ + + + License Assigned Notification [U-108] + + +
      +

      <English>

      +

      Dear $CUSTOMER_NAME$,

      +

      + Please be informed that a license has been assigned to the following + user.
      + - User Name: $USER_NAME$
      + - Email: $USER_EMAIL$ +

      +

      + Please log in to ODMS Cloud to verify the license expiration date.
      + URL: $TOP_URL$ +

      +

      + If you have received this e-mail in error, please delete this e-mail + from your system.
      + This is an automatically generated e-mail and this mailbox is not + monitored. Please do not reply. +

      +
      +
      +

      <Deutsch>

      +

      Sehr geehrte(r) $CUSTOMER_NAME$,

      +

      + Bitte beachten Sie, dass dem folgenden Benutzer eine Lizenz zugewiesen + wurde.
      + - Nutzername: $USER_NAME$
      + - Email: $USER_EMAIL$ +

      +

      + Bitte melden Sie sich bei ODMS Cloud an, um das Ablaufdatum der Lizenz + zu überprüfen.
      + URL: $TOP_URL$ +

      +

      + Wenn Sie diese E-Mail irrtümlich erhalten haben, löschen Sie diese + E-Mail bitte aus Ihrem System.
      + Dies ist eine automatisch generierte + E-Mail und diese Mailbox wird nicht überwacht. Bitte antworten Sie + nicht. +

      +
      +
      +

      <Français>

      +

      Chère/Cher $CUSTOMER_NAME$,

      +

      + Veuillez être informé qu'une licence a été attribuée à l'utilisateur + suivant.
      + - Nom d'utilisateur: $USER_NAME$
      + - Email: $USER_EMAIL$ +

      +

      + Veuillez vous connecter à ODMS Cloud pour vérifier la date d'expiration + de la licence.
      + URL: $TOP_URL$ +

      +

      + Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail + de votre système.
      + Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres + n'est pas surveillée. Merci de ne pas répondre. +

      +
      + + diff --git a/dictation_function/src/templates/template_U_108_no_parent.txt b/dictation_function/src/templates/template_U_108_no_parent.txt new file mode 100644 index 0000000..8c07856 --- /dev/null +++ b/dictation_function/src/templates/template_U_108_no_parent.txt @@ -0,0 +1,41 @@ + + +Dear $CUSTOMER_NAME$, + +Please be informed that a license has been assigned to the following user. + - User Name: $USER_NAME$ + - Email: $USER_EMAIL$ + +Please log in to ODMS Cloud to verify the license expiration date. +URL: $TOP_URL$ + +If you have received this e-mail in error, please delete this e-mail from your system. +This is an automatically generated e-mail and this mailbox is not monitored. Please do not reply. + + + +Sehr geehrte(r) $CUSTOMER_NAME$, + +Bitte beachten Sie, dass dem folgenden Benutzer eine Lizenz zugewiesen wurde. + - Nutzername: $USER_NAME$ + - Email: $USER_EMAIL$ + +Bitte melden Sie sich bei ODMS Cloud an, um das Ablaufdatum der Lizenz zu überprüfen. +URL: $TOP_URL$ + +Wenn Sie diese E-Mail irrtümlich erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System. +Dies ist eine automatisch generierte E-Mail und diese Mailbox wird nicht überwacht. Bitte antworten Sie nicht. + + + +Chère/Cher $CUSTOMER_NAME$, + +Veuillez être informé qu'une licence a été attribuée à l'utilisateur suivant. + - Nom d'utilisateur: $USER_NAME$ + - Email: $USER_EMAIL$ + +Veuillez vous connecter à ODMS Cloud pour vérifier la date d'expiration de la licence. +URL: $TOP_URL$ + +Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail de votre système. +Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres n'est pas surveillée. Merci de ne pas répondre. \ No newline at end of file diff --git a/dictation_function/src/test/common/adb2c.mock.ts b/dictation_function/src/test/common/adb2c.mock.ts new file mode 100644 index 0000000..aa0a59b --- /dev/null +++ b/dictation_function/src/test/common/adb2c.mock.ts @@ -0,0 +1,88 @@ +import { InvocationContext } from "@azure/functions"; +import { AdB2cUser } from "../../adb2c/types/types"; +import { ADB2C_SIGN_IN_TYPE } from "../../constants"; +import { RedisClient } from "redis-mock"; + +// テスト用adb2c +export class AdB2cServiceMock { + /** + * Azure AD B2Cからユーザ情報を取得する + * @param externalIds 外部ユーザーID + * @returns ユーザ情報 + */ + async getUsers( + context: InvocationContext, + redisClient: RedisClient, + externalIds: string[] + ): Promise { + const AdB2cMockUsers: AdB2cUser[] = [ + { + id: "external_id1", + displayName: "test1", + identities: [ + { + signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + issuer: "issuer", + issuerAssignedId: "test1@mail.com", + }, + ], + }, + { + id: "external_id2", + displayName: "test2", + identities: [ + { + signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + issuer: "issuer", + issuerAssignedId: "test2@mail.com", + }, + ], + }, + { + id: "external_id3", + displayName: "test3", + identities: [ + { + signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + issuer: "issuer", + issuerAssignedId: "test3@mail.com", + }, + ], + }, + { + id: "external_id4", + displayName: "test4", + identities: [ + { + signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + issuer: "issuer", + issuerAssignedId: "test4@mail.com", + }, + ], + }, + { + id: "external_id5", + displayName: "test5", + identities: [ + { + signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + issuer: "issuer", + issuerAssignedId: "test5@mail.com", + }, + ], + }, + { + id: "external_id6", + displayName: "test6", + identities: [ + { + signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + issuer: "issuer", + issuerAssignedId: "test6@mail.com", + }, + ], + }, + ]; + return AdB2cMockUsers; + } +} diff --git a/dictation_function/src/test/common/sendGrid.mock.ts b/dictation_function/src/test/common/sendGrid.mock.ts new file mode 100644 index 0000000..7abfc30 --- /dev/null +++ b/dictation_function/src/test/common/sendGrid.mock.ts @@ -0,0 +1,24 @@ +import { InvocationContext } from "@azure/functions"; + +export class SendGridServiceMock { + /** + * メールを送信する + * @param to + * @param from + * @param subject + * @param text + * @param html + * @returns mail + */ + async sendMail( + context: InvocationContext, + to: string[], + cc: string[], + from: string, + subject: string, + text: string, + html: string + ): Promise { + return; + } +} diff --git a/dictation_function/src/test/common/utility.ts b/dictation_function/src/test/common/utility.ts index 3db9a67..7cec394 100644 --- a/dictation_function/src/test/common/utility.ts +++ b/dictation_function/src/test/common/utility.ts @@ -173,7 +173,7 @@ export const makeTestAccount = async ( }; }; -export const createLicense = async ( +export const createAndAllocateLicense = async ( datasource: DataSource, licenseId: number, expiry_date: Date | null, @@ -201,6 +201,29 @@ export const createLicense = async ( updated_at: new Date(), }); identifiers.pop() as License; + // 割り当てるユーザーがいない場合は履歴を作成しない + if (!allocated_user_id) { + return; + } + // switch_from_typeを作成 + // typeが"CARD"の場合は"CARD","TRIAL"の場合は"TRIAL","NORMAL"の場合は"NONE"を設定 + + const switch_from_type = + type === "CARD" ? "CARD" : type === "TRIAL" ? "TRIAL" : "NONE"; + + // ライセンスの割り当て履歴を作成 + await datasource.getRepository(LicenseAllocationHistory).insert({ + license_id: licenseId, + account_id: accountId, + user_id: allocated_user_id ?? -1, + is_allocated: true, + switch_from_type: switch_from_type, + executed_at: new Date(), + created_by: "test_runner", + created_at: new Date(), + updated_by: "updater", + updated_at: new Date(), + }); }; export const selectLicenseByAllocatedUser = async ( diff --git a/dictation_function/src/test/licenseAlert.spec.ts b/dictation_function/src/test/licenseAlert.spec.ts index 610fe7e..42cfc51 100644 --- a/dictation_function/src/test/licenseAlert.spec.ts +++ b/dictation_function/src/test/licenseAlert.spec.ts @@ -1,6 +1,6 @@ import { DataSource } from "typeorm"; import { licenseAlertProcessing } from "../functions/licenseAlert"; -import { makeTestAccount, createLicense } from "./common/utility"; +import { makeTestAccount, createAndAllocateLicense } from "./common/utility"; import * as dotenv from "dotenv"; import { DateWithDayEndTime, @@ -8,13 +8,13 @@ import { ExpirationThresholdDate, NewTrialLicenseExpirationDate, } from "../common/types/types"; -import { AdB2cUser } from "../adb2c/types/types"; -import { ADB2C_SIGN_IN_TYPE } from "../constants"; import { SendGridService } from "../sendgrid/sendgrid"; import { AdB2cService } from "../adb2c/adb2c"; import { InvocationContext } from "@azure/functions"; -import { RedisClient, createClient } from "redis-mock"; +import { createClient } from "redis-mock"; import { promisify } from "util"; +import { SendGridServiceMock } from "./common/sendGrid.mock"; +import { AdB2cServiceMock } from "./common/adb2c.mock"; describe("licenseAlert", () => { dotenv.config({ path: ".env" }); @@ -56,7 +56,7 @@ describe("licenseAlert", () => { { tier: 5 }, { external_id: "external_id1", accepted_dpa_version: null } ); - await createLicense( + await createAndAllocateLicense( source, 1, expiringSoonDate, @@ -103,7 +103,7 @@ describe("licenseAlert", () => { accepted_dpa_version: null, } ); - await createLicense( + await createAndAllocateLicense( source, 1, expiringSoonDate, @@ -147,7 +147,7 @@ describe("licenseAlert", () => { { tier: 5 }, { external_id: "external_id3", accepted_dpa_version: null } ); - await createLicense( + await createAndAllocateLicense( source, 1, expiringSoonDate, @@ -159,7 +159,7 @@ describe("licenseAlert", () => { null, null ); - await createLicense( + await createAndAllocateLicense( source, 2, expiryDate, @@ -206,7 +206,7 @@ describe("licenseAlert", () => { accepted_dpa_version: null, } ); - await createLicense( + await createAndAllocateLicense( source, 1, expiringSoonDate, @@ -254,7 +254,7 @@ describe("licenseAlert", () => { accepted_privacy_notice_version: null, } ); - await createLicense( + await createAndAllocateLicense( source, 1, expiringSoonDate, @@ -278,97 +278,6 @@ describe("licenseAlert", () => { }); }); -// テスト用sendgrid -export class SendGridServiceMock { - /** - * メールを送信する - * @param to - * @param from - * @param subject - * @param text - * @param html - * @returns mail - */ - async sendMail( - to: string, - from: string, - subject: string, - text: string, - html: string - ): Promise { - return; - } -} -// テスト用adb2c -export class AdB2cServiceMock { - /** - * Azure AD B2Cからユーザ情報を取得する - * @param externalIds 外部ユーザーID - * @returns ユーザ情報 - */ - async getUsers( - context: InvocationContext, - redisClient: RedisClient, - externalIds: string[] - ): Promise { - const AdB2cMockUsers: AdB2cUser[] = [ - { - id: "external_id1", - displayName: "test1", - identities: [ - { - signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, - issuer: "issuer", - issuerAssignedId: "test1@mail.com", - }, - ], - }, - { - id: "external_id2", - displayName: "test2", - identities: [ - { - signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, - issuer: "issuer", - issuerAssignedId: "test2@mail.com", - }, - ], - }, - { - id: "external_id3", - displayName: "test3", - identities: [ - { - signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, - issuer: "issuer", - issuerAssignedId: "test3@mail.com", - }, - ], - }, - { - id: "external_id4", - displayName: "test4", - identities: [ - { - signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, - issuer: "issuer", - issuerAssignedId: "test4@mail.com", - }, - ], - }, - { - id: "external_id5", - displayName: "test5", - identities: [ - { - signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, - issuer: "issuer", - issuerAssignedId: "test5@mail.com", - }, - ], - }, - ]; - return AdB2cMockUsers; - } -} + + diff --git a/dictation_function/src/test/licenseAutoAllocation.spec.ts b/dictation_function/src/test/licenseAutoAllocation.spec.ts index d0bf9ef..24919e4 100644 --- a/dictation_function/src/test/licenseAutoAllocation.spec.ts +++ b/dictation_function/src/test/licenseAutoAllocation.spec.ts @@ -8,18 +8,24 @@ import { import { DateWithDayEndTime } from "../common/types/types"; import { makeTestAccount, - createLicense, + createAndAllocateLicense, makeTestUser, selectLicenseByAllocatedUser, selectLicenseAllocationHistory, } from "./common/utility"; import * as dotenv from "dotenv"; import { InvocationContext } from "@azure/functions"; +import { AdB2cService } from "../adb2c/adb2c"; +import { SendGridService } from "../sendgrid/sendgrid"; +import { SendGridServiceMock } from "./common/sendGrid.mock"; +import { AdB2cServiceMock } from "./common/adb2c.mock"; +import { createClient } from "redis-mock"; describe("licenseAutoAllocation", () => { dotenv.config({ path: ".env" }); dotenv.config({ path: ".env.local", override: true }); let source: DataSource | null = null; + const redisClient = createClient(); beforeEach(async () => { source = new DataSource({ type: "sqlite", @@ -40,33 +46,39 @@ describe("licenseAutoAllocation", () => { it("有効期限が本日のライセンスが自動更新されること", async () => { if (!source) fail(); const context = new InvocationContext(); - + const sendgridMock = new SendGridServiceMock() as SendGridService; + const adb2cMock = new AdB2cServiceMock() as AdB2cService; + // 呼び出し回数でテスト成否を判定 + const spySend = jest.spyOn(sendgridMock, "sendMail"); const currentDateEndTime = new DateWithDayEndTime(); // アカウント const account1 = await makeTestAccount( source, { tier: 5 }, - { role: `${USER_ROLES.NONE}` } + { role: `${USER_ROLES.NONE}`, external_id: "external_id1" } ); const account2 = await makeTestAccount( source, { tier: 5 }, - { role: `${USER_ROLES.NONE}` } + { role: `${USER_ROLES.NONE}`, external_id: "external_id6" } ); // 更新対象のユーザー(3role分) const user1 = await makeTestUser(source, { account_id: account1.account.id, role: `${USER_ROLES.NONE}`, + external_id: "external_id2", }); const user2 = await makeTestUser(source, { account_id: account1.account.id, role: `${USER_ROLES.AUTHOR}`, + external_id: "external_id3", }); const user3 = await makeTestUser(source, { account_id: account1.account.id, role: `${USER_ROLES.TYPIST}`, + external_id: "external_id4", }); // 更新対象ではないユーザー(まだ有効期限が残っている) @@ -90,10 +102,11 @@ describe("licenseAutoAllocation", () => { const user7 = await makeTestUser(source, { account_id: account1.account.id, role: `${USER_ROLES.NONE}`, + external_id: "external_id5", }); // 割り当て済みで有効期限が本日のライセンス - await createLicense( + await createAndAllocateLicense( source, 1, currentDateEndTime, @@ -105,7 +118,7 @@ describe("licenseAutoAllocation", () => { null, null ); - await createLicense( + await createAndAllocateLicense( source, 2, currentDateEndTime, @@ -117,7 +130,7 @@ describe("licenseAutoAllocation", () => { null, null ); - await createLicense( + await createAndAllocateLicense( source, 3, currentDateEndTime, @@ -129,7 +142,7 @@ describe("licenseAutoAllocation", () => { null, null ); - await createLicense( + await createAndAllocateLicense( source, 20, currentDateEndTime, @@ -141,7 +154,7 @@ describe("licenseAutoAllocation", () => { null, null ); - await createLicense( + await createAndAllocateLicense( source, 5, currentDateEndTime, @@ -153,7 +166,7 @@ describe("licenseAutoAllocation", () => { null, null ); - await createLicense( + await createAndAllocateLicense( source, 6, currentDateEndTime, @@ -165,7 +178,7 @@ describe("licenseAutoAllocation", () => { null, null ); - await createLicense( + await createAndAllocateLicense( source, 7, currentDateEndTime, @@ -183,7 +196,7 @@ describe("licenseAutoAllocation", () => { nextDate.setDate(nextDate.getDate() + 1); nextDate.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定 nextDate.setMilliseconds(0); - await createLicense( + await createAndAllocateLicense( source, 4, nextDate, @@ -204,7 +217,7 @@ describe("licenseAutoAllocation", () => { date.setDate(date.getDate() + i); date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定 date.setMilliseconds(0); - await createLicense( + await createAndAllocateLicense( source, i + 100, date, @@ -222,7 +235,7 @@ describe("licenseAutoAllocation", () => { date.setDate(date.getDate() + 30); date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定 date.setMilliseconds(0); - await createLicense( + await createAndAllocateLicense( source, 200, date, @@ -235,7 +248,13 @@ describe("licenseAutoAllocation", () => { null ); - await licenseAutoAllocationProcessing(context, source); + await licenseAutoAllocationProcessing( + context, + source, + redisClient, + sendgridMock, + adb2cMock + ); const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id); const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id); const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id); @@ -277,11 +296,349 @@ describe("licenseAutoAllocation", () => { expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe( account1.account.id ); + expect( + licenseAllocationHistory.licenseAllocationHistory?.switch_from_type + ).toBe("CARD"); + // メール送信が行われていることを確認 + expect(spySend).toHaveBeenCalledTimes(4); + }); + + it("新規ライセンスがある場合でも、有効期限が本日のライセンスが自動更新されること", async () => { + if (!source) fail(); + const context = new InvocationContext(); + const sendgridMock = new SendGridServiceMock() as SendGridService; + const adb2cMock = new AdB2cServiceMock() as AdB2cService; + // 呼び出し回数でテスト成否を判定 + const spySend = jest.spyOn(sendgridMock, "sendMail"); + + const currentDateEndTime = new DateWithDayEndTime(); + + // アカウント + const account1 = await makeTestAccount( + source, + { tier: 5 }, + { role: `${USER_ROLES.NONE}`, external_id: "external_id1" } + ); + const account2 = await makeTestAccount( + source, + { tier: 5 }, + { role: `${USER_ROLES.NONE}`, external_id: "external_id6" } + ); + + // 更新対象のユーザー(3role分) + const testNoneUser1 = await makeTestUser(source, { + account_id: account1.account.id, + role: `${USER_ROLES.NONE}`, + external_id: "external_id2", + }); + const testAuthorUser2 = await makeTestUser(source, { + account_id: account1.account.id, + role: `${USER_ROLES.AUTHOR}`, + external_id: "external_id3", + }); + const testTypistUser3 = await makeTestUser(source, { + account_id: account1.account.id, + role: `${USER_ROLES.TYPIST}`, + external_id: "external_id4", + }); + + // 更新対象ではないユーザー(まだ有効期限が残っている) + const testNoneUser4ExpirationRemain = await makeTestUser(source, { + account_id: account1.account.id, + role: `${USER_ROLES.NONE}`, + }); + + // 更新対象ではないユーザー(auto_renewがfalse) + const testNoneUser5AutoRenewFalse = await makeTestUser(source, { + account_id: account1.account.id, + role: `${USER_ROLES.NONE}`, + auto_renew: false, + }); + // 更新対象のユーザー(Author二人目) + const testAuthorUser6 = await makeTestUser(source, { + account_id: account1.account.id, + role: `${USER_ROLES.AUTHOR}`, + external_id: "external_id5", + }); + // 更新対象のユーザー(ただしライセンスが足りない) + const testNoneUser7lackofLicenses = await makeTestUser(source, { + account_id: account1.account.id, + role: `${USER_ROLES.NONE}`, + }); + + // 割り当て済みで有効期限が本日のライセンス + await createAndAllocateLicense( + source, + 1, + currentDateEndTime, + account1.account.id, + LICENSE_TYPE.CARD, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + testNoneUser1.id, + null, + null, + null + ); + await createAndAllocateLicense( + source, + 2, + currentDateEndTime, + account1.account.id, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + testAuthorUser2.id, + null, + null, + null + ); + await createAndAllocateLicense( + source, + 3, + currentDateEndTime, + account1.account.id, + LICENSE_TYPE.TRIAL, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + testTypistUser3.id, + null, + null, + null + ); + await createAndAllocateLicense( + source, + 20, + currentDateEndTime, + account2.account.id, + LICENSE_TYPE.CARD, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + account2.admin.id, + null, + null, + null + ); + await createAndAllocateLicense( + source, + 5, + currentDateEndTime, + account1.account.id, + LICENSE_TYPE.CARD, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + testNoneUser5AutoRenewFalse.id, + null, + null, + null + ); + await createAndAllocateLicense( + source, + 6, + currentDateEndTime, + account1.account.id, + LICENSE_TYPE.CARD, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + testAuthorUser6.id, + null, + null, + null + ); + await createAndAllocateLicense( + source, + 7, + currentDateEndTime, + account1.account.id, + LICENSE_TYPE.CARD, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + testNoneUser7lackofLicenses.id, + null, + null, + null + ); + + // 割り当て済みの更新対象ではないライセンス + const nextDate = new Date(); + nextDate.setDate(nextDate.getDate() + 1); + nextDate.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定 + nextDate.setMilliseconds(0); + await createAndAllocateLicense( + source, + 4, + nextDate, + account1.account.id, + LICENSE_TYPE.CARD, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + testNoneUser4ExpirationRemain.id, + null, + null, + null + ); + + // 有効期限が先のライセンスを作成 + // idが100のものは有効期限が当日なので自動割り当て対象外 + // idが101のものから割り当てられる + for (let i = 0; i < 4; i++) { + const date = new Date(); + date.setDate(date.getDate() + i); + date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定 + date.setMilliseconds(0); + await createAndAllocateLicense( + source, + i + 100, + date, + account1.account.id, + LICENSE_TYPE.TRIAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + null, + null, + null + ); + } + // account1用の有効期限が設定されていないライセンスを作成 + await createAndAllocateLicense( + source, + 99, + null, + account1.account.id, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.REUSABLE, + null, + null, + null, + null + ); + // account2用の有効期限が設定されていないライセンスを作成 + await createAndAllocateLicense( + source, + 200, + null, + account2.account.id, + LICENSE_TYPE.CARD, + LICENSE_ALLOCATED_STATUS.REUSABLE, + null, + null, + null, + null + ); + + await licenseAutoAllocationProcessing( + context, + source, + redisClient, + sendgridMock, + adb2cMock + ); + const testNoneUser1Allocated = await selectLicenseByAllocatedUser( + source, + testNoneUser1.id + ); + const testAuthorUser2Allocated = await selectLicenseByAllocatedUser( + source, + testAuthorUser2.id + ); + const testTypistUser3Allocated = await selectLicenseByAllocatedUser( + source, + testTypistUser3.id + ); + const testNoneUser4ExpirationRemainAllocated = + await selectLicenseByAllocatedUser( + source, + testNoneUser4ExpirationRemain.id + ); + const testNoneUser5AutoRenewFalseAllocated = + await selectLicenseByAllocatedUser( + source, + testNoneUser5AutoRenewFalse.id + ); + const testAuthorUser6Allocated = await selectLicenseByAllocatedUser( + source, + testAuthorUser6.id + ); + const testNoneUser7lackofLicensesAllocated = + await selectLicenseByAllocatedUser( + source, + testNoneUser7lackofLicenses.id + ); + const admin2Allocated = await selectLicenseByAllocatedUser( + source, + account2.admin.id + ); + const testNoneUser1LicenseAllocationHistory = + await selectLicenseAllocationHistory(source, testNoneUser1.id, 99); + const testAuthorUser2LicenseAllocationHistory = + await selectLicenseAllocationHistory(source, testAuthorUser2.id, 101); + const testTypistUser3LicenseAllocationHistory = + await selectLicenseAllocationHistory(source, testTypistUser3.id, 103); + + // Author、Typist、Noneの優先順位で割り当てられていることを確認 + // 複数Authorがいる場合、それぞれに割り当てられていることを確認 + expect(testAuthorUser2Allocated.license?.id).toBe(101); + expect(testAuthorUser6Allocated.license?.id).toBe(102); + expect(testTypistUser3Allocated.license?.id).toBe(103); + expect(testNoneUser1Allocated.license?.id).toBe(99); + // 有効期限がまだあるので、ライセンスが更新されていないことを確認 + expect(testNoneUser4ExpirationRemainAllocated.license?.id).toBe(4); + // auto_renewがfalseなので、ライセンスが更新されていないことを確認 + expect(testNoneUser5AutoRenewFalseAllocated.license?.id).toBe(5); + // ライセンスが足りない場合、ライセンスが更新されていないことを確認 + expect(testNoneUser7lackofLicensesAllocated.license?.id).toBe(7); + // 複数アカウント分の処理が正常に行われていることの確認 + expect(admin2Allocated.license?.id).toBe(200); + + // ライセンス割り当て履歴テーブルが更新されていることを確認 + expect( + testNoneUser1LicenseAllocationHistory.licenseAllocationHistory?.user_id + ).toBe(testNoneUser1.id); + expect( + testNoneUser1LicenseAllocationHistory.licenseAllocationHistory + ?.is_allocated + ).toBe(true); + expect( + testNoneUser1LicenseAllocationHistory.licenseAllocationHistory?.account_id + ).toBe(account1.account.id); + expect( + testNoneUser1LicenseAllocationHistory.licenseAllocationHistory + ?.switch_from_type + ).toBe("CARD"); + expect( + testAuthorUser2LicenseAllocationHistory.licenseAllocationHistory?.user_id + ).toBe(testAuthorUser2.id); + expect( + testAuthorUser2LicenseAllocationHistory.licenseAllocationHistory + ?.is_allocated + ).toBe(true); + expect( + testAuthorUser2LicenseAllocationHistory.licenseAllocationHistory + ?.account_id + ).toBe(account1.account.id); + expect( + testAuthorUser2LicenseAllocationHistory.licenseAllocationHistory + ?.switch_from_type + ).toBe("NONE"); + expect( + testTypistUser3LicenseAllocationHistory.licenseAllocationHistory?.user_id + ).toBe(testTypistUser3.id); + expect( + testTypistUser3LicenseAllocationHistory.licenseAllocationHistory + ?.is_allocated + ).toBe(true); + expect( + testTypistUser3LicenseAllocationHistory.licenseAllocationHistory + ?.account_id + ).toBe(account1.account.id); + expect( + testTypistUser3LicenseAllocationHistory.licenseAllocationHistory + ?.switch_from_type + ).toBe("TRIAL"); + // メール送信が行われていることを確認 + expect(spySend).toHaveBeenCalledTimes(5); }); it("有効期限が指定日のライセンスが自動更新されること(リトライ用)", async () => { if (!source) fail(); const context = new InvocationContext(); + const sendgridMock = new SendGridServiceMock() as SendGridService; + const adb2cMock = new AdB2cServiceMock() as AdB2cService; + // 呼び出し回数でテスト成否を判定 + const spySend = jest.spyOn(sendgridMock, "sendMail"); + // 2023/11/22の日付を作成 const date1122 = new Date(2023, 10, 22, 23, 59, 59); const currentDateEndTime = new DateWithDayEndTime(date1122); @@ -289,26 +646,29 @@ describe("licenseAutoAllocation", () => { const account1 = await makeTestAccount( source, { tier: 5 }, - { role: `${USER_ROLES.NONE}` } + { role: `${USER_ROLES.NONE}`, external_id: "external_id1" } ); const account2 = await makeTestAccount( source, { tier: 5 }, - { role: `${USER_ROLES.NONE}` } + { role: `${USER_ROLES.NONE}`, external_id: "external_id6" } ); // 更新対象のユーザー(3role分) const user1 = await makeTestUser(source, { account_id: account1.account.id, role: `${USER_ROLES.NONE}`, + external_id: "external_id2", }); const user2 = await makeTestUser(source, { account_id: account1.account.id, role: `${USER_ROLES.AUTHOR}`, + external_id: "external_id3", }); const user3 = await makeTestUser(source, { account_id: account1.account.id, role: `${USER_ROLES.TYPIST}`, + external_id: "external_id4", }); // 更新対象ではないユーザー(まだ有効期限が残っている) @@ -325,7 +685,7 @@ describe("licenseAutoAllocation", () => { }); // 割り当て済みで有効期限が11/22のライセンス - await createLicense( + await createAndAllocateLicense( source, 1, currentDateEndTime, @@ -337,7 +697,7 @@ describe("licenseAutoAllocation", () => { null, null ); - await createLicense( + await createAndAllocateLicense( source, 2, currentDateEndTime, @@ -349,7 +709,7 @@ describe("licenseAutoAllocation", () => { null, null ); - await createLicense( + await createAndAllocateLicense( source, 3, currentDateEndTime, @@ -361,7 +721,7 @@ describe("licenseAutoAllocation", () => { null, null ); - await createLicense( + await createAndAllocateLicense( source, 20, currentDateEndTime, @@ -373,7 +733,7 @@ describe("licenseAutoAllocation", () => { null, null ); - await createLicense( + await createAndAllocateLicense( source, 5, currentDateEndTime, @@ -390,7 +750,7 @@ describe("licenseAutoAllocation", () => { nextDate.setDate(date1122.getDate() + 1); nextDate.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定 nextDate.setMilliseconds(0); - await createLicense( + await createAndAllocateLicense( source, 4, nextDate, @@ -409,7 +769,7 @@ describe("licenseAutoAllocation", () => { // 2023/11/22の日付を作成 const date = new Date(2023, 10, 22, 23, 59, 59); date.setDate(date.getDate() + i); - await createLicense( + await createAndAllocateLicense( source, i + 100, date, @@ -427,7 +787,7 @@ describe("licenseAutoAllocation", () => { dateMarch31.setMonth(12); dateMarch31.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定 dateMarch31.setMilliseconds(0); - await createLicense( + await createAndAllocateLicense( source, 200, dateMarch31, @@ -439,7 +799,14 @@ describe("licenseAutoAllocation", () => { null, null ); - await licenseAutoAllocationProcessing(context, source, date1122); + await licenseAutoAllocationProcessing( + context, + source, + redisClient, + sendgridMock, + adb2cMock, + date1122 + ); const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id); const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id); const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id); @@ -475,36 +842,50 @@ describe("licenseAutoAllocation", () => { expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe( account1.account.id ); + // メール送信が行われていることを確認 + expect(spySend).toHaveBeenCalledTimes(4); }); - it("新たに割り当てられるライセンスが存在しないため、ライセンスが自動更新されない(エラーではない)", async () => { + it("新たに割り当てられるライセンスが存在しないアカウントは、ライセンスが自動更新されない(エラーではない)", async () => { if (!source) fail(); const context = new InvocationContext(); + const sendgridMock = new SendGridServiceMock() as SendGridService; + const adb2cMock = new AdB2cServiceMock() as AdB2cService; + // 呼び出し回数でテスト成否を判定 + const spySend = jest.spyOn(sendgridMock, "sendMail"); const currentDateEndTime = new DateWithDayEndTime(); - // アカウント + // アカウントと管理者 const account1 = await makeTestAccount( source, { tier: 5 }, - { role: `${USER_ROLES.NONE}` } + { role: `${USER_ROLES.NONE}`, external_id: "external_id1" } + ); + const account2 = await makeTestAccount( + source, + { tier: 5 }, + { role: `${USER_ROLES.NONE}`, external_id: "external_id5" } ); - // 更新対象のユーザー(3role分) + // account1の更新対象のユーザー(3role分) const user1 = await makeTestUser(source, { account_id: account1.account.id, role: `${USER_ROLES.NONE}`, + external_id: "external_id2", }); const user2 = await makeTestUser(source, { account_id: account1.account.id, role: `${USER_ROLES.AUTHOR}`, + external_id: "external_id3", }); const user3 = await makeTestUser(source, { account_id: account1.account.id, role: `${USER_ROLES.TYPIST}`, + external_id: "external_id4", }); // 割り当て済みで有効期限が本日のライセンス - await createLicense( + await createAndAllocateLicense( source, 1, currentDateEndTime, @@ -516,7 +897,7 @@ describe("licenseAutoAllocation", () => { null, null ); - await createLicense( + await createAndAllocateLicense( source, 2, currentDateEndTime, @@ -528,7 +909,7 @@ describe("licenseAutoAllocation", () => { null, null ); - await createLicense( + await createAndAllocateLicense( source, 3, currentDateEndTime, @@ -540,20 +921,67 @@ describe("licenseAutoAllocation", () => { null, null ); + await createAndAllocateLicense( + source, + 4, + currentDateEndTime, + account2.account.id, + LICENSE_TYPE.TRIAL, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + account2.admin.id, + null, + null, + null + ); - await licenseAutoAllocationProcessing(context, source); + // account2の有効期限が先の未割当ライセンスを作成 + const nextDate = new Date(); + nextDate.setDate(nextDate.getDate() + 1); + nextDate.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定 + nextDate.setMilliseconds(0); + await createAndAllocateLicense( + source, + 100, + nextDate, + account2.account.id, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + null, + null, + null + ); + + await licenseAutoAllocationProcessing( + context, + source, + redisClient, + sendgridMock, + adb2cMock + ); const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id); const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id); const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id); + const account2AdminAllocated = await selectLicenseByAllocatedUser( + source, + account2.admin.id + ); // ライセンスが更新されていないことを確認 expect(user1Allocated.license?.id).toBe(1); expect(user2Allocated.license?.id).toBe(2); expect(user3Allocated.license?.id).toBe(3); + expect(account2AdminAllocated.license?.id).toBe(100); + // メール送信が行われていないことを確認 + expect(spySend).toHaveBeenCalledTimes(1); }); it("tier4のアカウントのため、ライセンスが自動更新されない", async () => { if (!source) fail(); const context = new InvocationContext(); + const sendgridMock = new SendGridServiceMock() as SendGridService; + const adb2cMock = new AdB2cServiceMock() as AdB2cService; + // 呼び出し回数でテスト成否を判定 + const spySend = jest.spyOn(sendgridMock, "sendMail"); const currentDateEndTime = new DateWithDayEndTime(); @@ -561,25 +989,28 @@ describe("licenseAutoAllocation", () => { const account1 = await makeTestAccount( source, { tier: 4 }, - { role: `${USER_ROLES.NONE}` } + { role: `${USER_ROLES.NONE}`, external_id: "external_id1" } ); // 更新対象のユーザー(3role分) const user1 = await makeTestUser(source, { account_id: account1.account.id, role: `${USER_ROLES.NONE}`, + external_id: "external_id2", }); const user2 = await makeTestUser(source, { account_id: account1.account.id, role: `${USER_ROLES.AUTHOR}`, + external_id: "external_id3", }); const user3 = await makeTestUser(source, { account_id: account1.account.id, role: `${USER_ROLES.TYPIST}`, + external_id: "external_id4", }); // 割り当て済みで有効期限が本日のライセンス - await createLicense( + await createAndAllocateLicense( source, 1, currentDateEndTime, @@ -591,7 +1022,7 @@ describe("licenseAutoAllocation", () => { null, null ); - await createLicense( + await createAndAllocateLicense( source, 2, currentDateEndTime, @@ -603,7 +1034,7 @@ describe("licenseAutoAllocation", () => { null, null ); - await createLicense( + await createAndAllocateLicense( source, 3, currentDateEndTime, @@ -624,7 +1055,7 @@ describe("licenseAutoAllocation", () => { date.setDate(date.getDate() + i); date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定 date.setMilliseconds(0); - await createLicense( + await createAndAllocateLicense( source, i + 100, date, @@ -638,7 +1069,13 @@ describe("licenseAutoAllocation", () => { ); } - await licenseAutoAllocationProcessing(context, source); + await licenseAutoAllocationProcessing( + context, + source, + redisClient, + sendgridMock, + adb2cMock + ); const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id); const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id); const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id); @@ -646,5 +1083,99 @@ describe("licenseAutoAllocation", () => { expect(user1Allocated.license?.id).toBe(1); expect(user2Allocated.license?.id).toBe(2); expect(user3Allocated.license?.id).toBe(3); + // メール送信が行われていないことを確認 + expect(spySend).toHaveBeenCalledTimes(0); + }); + + it("メール送信に失敗しても、有効期限が本日のライセンスが自動更新されること", async () => { + if (!source) fail(); + const context = new InvocationContext(); + const sendgridMock = new SendGridServiceMock() as SendGridService; + const adb2cMock = new AdB2cServiceMock() as AdB2cService; + // 呼び出し回数でテスト成否を判定 + const spySend = jest.spyOn(sendgridMock, "sendMail"); + const currentDateEndTime = new DateWithDayEndTime(); + + // アカウント + const account1 = await makeTestAccount( + source, + { tier: 5 }, + { role: `${USER_ROLES.NONE}`, external_id: "external_id1" } + ); + + // 更新対象のユーザー(3role分) + const user1 = await makeTestUser(source, { + account_id: account1.account.id, + role: `${USER_ROLES.NONE}`, + external_id: "external_id7", // メール送信失敗用 + }); + // 割り当て済みで有効期限が本日のライセンス + await createAndAllocateLicense( + source, + 1, + currentDateEndTime, + account1.account.id, + LICENSE_TYPE.CARD, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + user1.id, + null, + null, + null + ); + + // 有効期限が先の未割当ライセンスを作成 + // idが100のものは有効期限が当日なので自動割り当て対象外 + // idが101のものから割り当てられる + for (let i = 0; i < 5; i++) { + const date = new Date(); + date.setDate(date.getDate() + i); + date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定 + date.setMilliseconds(0); + await createAndAllocateLicense( + source, + i + 100, + date, + account1.account.id, + LICENSE_TYPE.CARD, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + null, + null, + null + ); + } + + await licenseAutoAllocationProcessing( + context, + source, + redisClient, + sendgridMock, + adb2cMock + ); + const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id); + + const licenseAllocationHistory = await selectLicenseAllocationHistory( + source, + user1.id, + 101 + ); + // 割り当てられていることを確認 + expect(user1Allocated.license?.id).toBe(101); + + // ライセンス割り当て履歴テーブルが更新されていることを確認 + expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe( + user1.id + ); + expect( + licenseAllocationHistory.licenseAllocationHistory?.is_allocated + ).toBe(true); + expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe( + account1.account.id + ); + expect( + licenseAllocationHistory.licenseAllocationHistory?.switch_from_type + ).toBe("CARD"); + // メール送信が行われていないことを確認 + expect(spySend).toHaveBeenCalledTimes(0); }); }); diff --git a/dictation_function/tsconfig.json b/dictation_function/tsconfig.json index dd9c7a4..a541177 100644 --- a/dictation_function/tsconfig.json +++ b/dictation_function/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es6", + "target": "ES2021", "outDir": "dist", "rootDir": ".", "sourceMap": true, From e76242bddd9f3f119c4e62392df68010a49f0d25 Mon Sep 17 00:00:00 2001 From: "SAITO-PC-3\\saito.k" Date: Thu, 25 Apr 2024 18:19:48 +0900 Subject: [PATCH 13/13] =?UTF-8?q?Functions=E3=81=AE=E3=83=86=E3=82=B9?= =?UTF-8?q?=E3=83=88=E3=81=A7=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B=E7=92=B0?= =?UTF-8?q?=E5=A2=83=E5=A4=89=E6=95=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- azure-pipelines-staging.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines-staging.yml b/azure-pipelines-staging.yml index 65c19c4..918dcf9 100644 --- a/azure-pipelines-staging.yml +++ b/azure-pipelines-staging.yml @@ -208,7 +208,7 @@ jobs: ADB2C_ORIGIN: xxxxxx SENDGRID_API_KEY: $(sendgrid-api-key) MAIL_FROM: xxxxxx - APP_DOMAIN: xxxxxxxxx + APP_DOMAIN: http://localhost:8081/ REDIS_HOST: xxxxxxxxxxxx REDIS_PORT: 0 REDIS_PASSWORD: xxxxxxxxxxxx