From 370d143c2cbb28676213af93c03d980d1122d7d7 Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Fri, 13 Oct 2023 04:07:18 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20473:=20strictNullCheck=E3=81=AE?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C=E3=82=92=E9=83=A8=E5=88=86=E7=9A=84=E3=81=AB?= =?UTF-8?q?=E8=A1=8C=E3=81=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2795: 部分的に修正を行う](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2795) - strictNullChecks対応 - features - template - workflow - gateways - adb2c - repositories - template - workflow ## レビューポイント - entityの修正内容 - nullを追加する項目はあってるか - adb2cの環境変数を取得している箇所 - getOrThrowで値が取得できなければエラーになる関数があったので使用しています。 ![image.png](https://dev.azure.com/ODMSCloud/6023ff7b-d41c-4fa7-9c6f-f576ba48c07c/_apis/git/repositories/302da463-a2d7-40f9-b2bb-6e8edf324fa9/pullRequests/473/attachments/image.png) ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## 動作確認状況 - ローカルでテストが通ることを確認 ## 補足 - 相談、参考資料などがあれば --- dictation_server/src/common/test/modules.ts | 2 +- .../templates/templates.controller.ts | 20 +- .../templates/templates.service.spec.ts | 16 +- .../workflows/workflows.controller.ts | 78 +++++++- .../workflows/workflows.service.spec.ts | 185 ++++++++++++------ .../features/workflows/workflows.service.ts | 39 ++-- .../src/gateways/adb2c/adb2c.service.ts | 11 +- .../notificationhub.service.ts | 4 +- dictation_server/src/main.ts | 10 +- .../tasks/tasks.repository.service.ts | 2 +- .../entity/template_file.entity.ts | 6 +- .../workflows/entity/workflow.entity.ts | 16 +- .../workflows/workflows.repository.service.ts | 12 +- 13 files changed, 280 insertions(+), 121 deletions(-) diff --git a/dictation_server/src/common/test/modules.ts b/dictation_server/src/common/test/modules.ts index 05f8e77..876f907 100644 --- a/dictation_server/src/common/test/modules.ts +++ b/dictation_server/src/common/test/modules.ts @@ -37,7 +37,7 @@ import { WorkflowsModule } from '../../features/workflows/workflows.module'; export const makeTestingModule = async ( datasource: DataSource, -): Promise => { +): Promise => { try { const module: TestingModule = await Test.createTestingModule({ imports: [ diff --git a/dictation_server/src/features/templates/templates.controller.ts b/dictation_server/src/features/templates/templates.controller.ts index 6d85968..da4f061 100644 --- a/dictation_server/src/features/templates/templates.controller.ts +++ b/dictation_server/src/features/templates/templates.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, HttpStatus, Req, UseGuards } from '@nestjs/common'; +import { Controller, Get, HttpException, HttpStatus, Req, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, @@ -16,6 +16,7 @@ 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') @@ -46,8 +47,21 @@ export class TemplatesController { @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) @Get() async getTemplates(@Req() req: Request): Promise { - const token = retrieveAuthorizationToken(req); - const { userId } = jwt.decode(token, { 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 } = decodedAccessToken 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 c29b168..8401db7 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 = null; + let source: DataSource | undefined = undefined; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -22,12 +22,16 @@ describe('getTemplates', () => { }); afterEach(async () => { + if (!source) return; await source.destroy(); - source = null; + source = undefined; }); 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 }); @@ -62,10 +66,13 @@ describe('getTemplates', () => { expect(templates[1].id).toBe(template2.id); expect(templates[1].name).toBe(template2.file_name); } - }); + }, 6000000); 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 }); @@ -80,7 +87,10 @@ 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/workflows/workflows.controller.ts b/dictation_server/src/features/workflows/workflows.controller.ts index 46b0fa0..63deb5e 100644 --- a/dictation_server/src/features/workflows/workflows.controller.ts +++ b/dictation_server/src/features/workflows/workflows.controller.ts @@ -2,6 +2,7 @@ import { Body, Controller, Get, + HttpException, HttpStatus, Param, Post, @@ -34,6 +35,7 @@ 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') @@ -64,8 +66,22 @@ export class WorkflowsController { @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) @Get() async getWorkflows(@Req() req: Request): Promise { - const token = retrieveAuthorizationToken(req); - const { userId } = jwt.decode(token, { json: true }) as AccessToken; + // TODO strictNullChecks対応 + 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); @@ -107,17 +123,31 @@ export class WorkflowsController { @Body() body: CreateWorkflowsRequest, ): Promise { const { authorId, worktypeId, templateId, typists } = body; - const token = retrieveAuthorizationToken(req); - const { userId } = jwt.decode(token, { json: true }) as AccessToken; + // TODO strictNullChecks対応 + 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.workflowsService.createWorkflow( context, userId, authorId, + typists, worktypeId, templateId, - typists, ); return {}; @@ -158,8 +188,22 @@ export class WorkflowsController { ): Promise { const { authorId, worktypeId, templateId, typists } = body; const { workflowId } = param; - const token = retrieveAuthorizationToken(req); - const { userId } = jwt.decode(token, { json: true }) as AccessToken; + // TODO strictNullChecks対応 + 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.workflowsService.updateWorkflow( @@ -167,9 +211,9 @@ export class WorkflowsController { userId, workflowId, authorId, + typists, worktypeId, templateId, - typists, ); return {}; @@ -208,8 +252,22 @@ export class WorkflowsController { @Param() param: DeleteWorkflowRequestParam, ): Promise { const { workflowId } = param; - const token = retrieveAuthorizationToken(req); - const { userId } = jwt.decode(token, { json: true }) as AccessToken; + // TODO strictNullChecks対応 + 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.workflowsService.deleteWorkflow(context, userId, workflowId); diff --git a/dictation_server/src/features/workflows/workflows.service.spec.ts b/dictation_server/src/features/workflows/workflows.service.spec.ts index 51a3cb4..047e870 100644 --- a/dictation_server/src/features/workflows/workflows.service.spec.ts +++ b/dictation_server/src/features/workflows/workflows.service.spec.ts @@ -21,7 +21,7 @@ import { HttpException, HttpStatus } from '@nestjs/common'; import { makeErrorResponse } from '../../common/error/makeErrorResponse'; describe('getWorkflows', () => { - let source: DataSource = null; + let source: DataSource | null = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -34,12 +34,15 @@ 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, { @@ -149,10 +152,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'); @@ -161,8 +164,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'); @@ -170,8 +173,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); @@ -180,7 +183,9 @@ describe('getWorkflows', () => { }); it('アカウント内のWorkflow一覧を取得できる(0件)', async () => { + if (!source) fail(); const module = await makeTestingModule(source); + if (!module) fail(); // 第五階層のアカウント作成 const { admin } = await makeTestAccount(source, { tier: 5 }); @@ -200,7 +205,9 @@ 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 }); @@ -228,7 +235,7 @@ describe('getWorkflows', () => { }); describe('createWorkflows', () => { - let source: DataSource = null; + let source: DataSource | null = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -240,12 +247,15 @@ 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, { @@ -288,13 +298,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, - worktypeId, - templateId, [ { typistId: typistId, }, ], + worktypeId, + templateId, ); //実行結果を確認 @@ -314,7 +324,9 @@ 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, { @@ -351,13 +363,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, - undefined, - templateId, [ { typistId: typistId, }, ], + undefined, + templateId, ); //実行結果を確認 @@ -377,7 +389,9 @@ 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, { @@ -413,13 +427,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, - worktypeId, - undefined, [ { typistId: typistId, }, ], + worktypeId, + undefined, ); //実行結果を確認 @@ -439,7 +453,9 @@ 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, { @@ -469,13 +485,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, - undefined, - undefined, [ { typistId: typistId, }, ], + undefined, + undefined, ); //実行結果を確認 @@ -495,7 +511,9 @@ 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, { @@ -532,26 +550,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, ); //実行結果を確認 @@ -571,7 +589,9 @@ 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 }); @@ -611,13 +631,13 @@ describe('createWorkflows', () => { context, admin.external_id, 0, - worktypeId, - templateId, [ { typistId: typistId, }, ], + worktypeId, + templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -630,7 +650,9 @@ 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, { @@ -669,13 +691,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, - 9999, - templateId, [ { typistId: typistId, }, ], + 9999, + templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -688,7 +710,9 @@ 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, { @@ -726,13 +750,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, - worktypeId, - 9999, [ { typistId: typistId, }, ], + worktypeId, + 9999, ); } catch (e) { if (e instanceof HttpException) { @@ -745,7 +769,9 @@ 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, { @@ -785,13 +811,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, - worktypeId, - templateId, [ { typistId: 9999, }, ], + worktypeId, + templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -804,7 +830,9 @@ 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, { @@ -844,13 +872,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, - worktypeId, - templateId, [ { typistGroupId: 9999, }, ], + worktypeId, + templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -863,7 +891,9 @@ 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, { @@ -912,13 +942,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, - worktypeId, - templateId, [ { typistId: typistId, }, ], + worktypeId, + templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -931,7 +961,9 @@ 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, { @@ -984,13 +1016,13 @@ describe('createWorkflows', () => { context, admin.external_id, authorId, - worktypeId, - templateId, [ { typistId: typistId, }, ], + worktypeId, + templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -1004,7 +1036,7 @@ describe('createWorkflows', () => { }); describe('updateWorkflow', () => { - let source: DataSource = null; + let source: DataSource | null = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -1016,12 +1048,15 @@ 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, { @@ -1090,13 +1125,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId2, - worktypeId, - templateId, [ { typistId: typistId2, }, ], + worktypeId, + templateId, ); //実行結果を確認 @@ -1115,7 +1150,9 @@ 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, { @@ -1178,13 +1215,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId2, - undefined, - templateId, [ { typistId: typistId2, }, ], + undefined, + templateId, ); //実行結果を確認 @@ -1203,7 +1240,9 @@ 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, { @@ -1265,13 +1304,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId2, - worktypeId, - undefined, [ { typistId: typistId2, }, ], + worktypeId, + undefined, ); //実行結果を確認 @@ -1290,7 +1329,9 @@ 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, { @@ -1346,13 +1387,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId2, - undefined, - undefined, [ { typistId: typistId2, }, ], + undefined, + undefined, ); //実行結果を確認 @@ -1371,7 +1412,9 @@ 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, { @@ -1447,13 +1490,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow1.id, authorId2, - undefined, - undefined, [ { typistId: typistId2, }, ], + undefined, + undefined, ); //実行結果を確認 @@ -1472,7 +1515,9 @@ 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, { @@ -1497,13 +1542,13 @@ describe('updateWorkflow', () => { admin.external_id, 9999, authorId1, - undefined, - undefined, [ { typistId: typistId1, }, ], + undefined, + undefined, ); } catch (e) { if (e instanceof HttpException) { @@ -1515,7 +1560,9 @@ 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, { @@ -1568,13 +1615,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, 9999, - worktypeId, - templateId, [ { typistId: typistId1, }, ], + worktypeId, + templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -1587,7 +1634,9 @@ 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, { @@ -1634,13 +1683,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId1, - 9999, - templateId, [ { typistId: typistId1, }, ], + 9999, + templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -1653,7 +1702,9 @@ 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, { @@ -1699,13 +1750,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId1, - worktypeId, - 9999, [ { typistId: typistId1, }, ], + worktypeId, + 9999, ); } catch (e) { if (e instanceof HttpException) { @@ -1718,7 +1769,9 @@ 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, { @@ -1771,13 +1824,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId1, - worktypeId, - templateId, [ { typistId: 9999, }, ], + worktypeId, + templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -1790,7 +1843,9 @@ 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, { @@ -1843,13 +1898,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId1, - worktypeId, - templateId, [ { typistGroupId: 9999, }, ], + worktypeId, + templateId, ); } catch (e) { if (e instanceof HttpException) { @@ -1862,7 +1917,9 @@ 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, { @@ -1909,13 +1966,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId1, - worktypeId1, - undefined, [ { typistId: typistId1, }, ], + worktypeId1, + undefined, ); } catch (e) { if (e instanceof HttpException) { @@ -1928,7 +1985,9 @@ 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, { @@ -1983,13 +2042,13 @@ describe('updateWorkflow', () => { admin.external_id, preWorkflow.id, authorId1, - undefined, - undefined, [ { typistId: typistId1, }, ], + undefined, + undefined, ); } catch (e) { if (e instanceof HttpException) { diff --git a/dictation_server/src/features/workflows/workflows.service.ts b/dictation_server/src/features/workflows/workflows.service.ts index de23f5f..4dbc95c 100644 --- a/dictation_server/src/features/workflows/workflows.service.ts +++ b/dictation_server/src/features/workflows/workflows.service.ts @@ -14,6 +14,7 @@ import { WorkflowNotFoundError, } from '../../repositories/workflows/errors/types'; import { AccountNotFoundError } from '../../repositories/accounts/errors/types'; +import { Assignee } from '../tasks/types/types'; @Injectable() export class WorkflowsService { @@ -47,15 +48,20 @@ 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; }); - const distinctedExternalIds = [...new Set(externalIds)]; + // externalIdsからundefinedを除外 + const filteredExternalIds = externalIds.filter( + (externalId):externalId is string => externalId !== undefined, + ); + // externalIdsから重複を除外 + const distinctedExternalIds = [...new Set(filteredExternalIds)]; // ADB2Cからユーザー一覧を取得 const adb2cUsers = await this.adB2cService.getUsers( @@ -64,8 +70,11 @@ export class WorkflowsService { ); // DBから取得したワークフロー一覧を整形 - const workflows = workflowRecords.map((workflow) => { + const workflows = workflowRecords.map((workflow): 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 @@ -75,16 +84,24 @@ export class WorkflowsService { ? { id: template.id, fileName: template.file_name } : undefined; + if (!workflowTypists) { + throw new Error('workflowTypists is undefined'); + } + // ルーティング候補を整形 - const typists = workflowTypists.map((workflowTypist) => { + const typists = workflowTypists.map((workflowTypist): Assignee => { const { typist, typistGroup } = workflowTypist; // typistがユーザーの場合はADB2Cからユーザー名を取得 const typistName = typist ? adb2cUsers.find( (adb2cUser) => adb2cUser.id === typist.external_id, - ).displayName - : typistGroup.name; + )?.displayName + : typistGroup?.name; + + if (!typistName) { + throw new Error('typistName is undefined'); + } return { typistUserId: typist?.id, @@ -130,9 +147,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: { ` + @@ -149,9 +166,9 @@ export class WorkflowsService { await this.workflowsRepository.createtWorkflows( accountId, authorId, + typists, worktypeId, templateId, - typists, ); } catch (e) { this.logger.error(`[${context.trackingId}] error=${e}`); @@ -215,9 +232,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: { ` + @@ -236,9 +253,9 @@ export class WorkflowsService { accountId, workflowId, authorId, + typists, worktypeId, templateId, - typists, ); } catch (e) { this.logger.error(`[${context.trackingId}] error=${e}`); diff --git a/dictation_server/src/gateways/adb2c/adb2c.service.ts b/dictation_server/src/gateways/adb2c/adb2c.service.ts index 254acea..6a41b2b 100644 --- a/dictation_server/src/gateways/adb2c/adb2c.service.ts +++ b/dictation_server/src/gateways/adb2c/adb2c.service.ts @@ -30,17 +30,16 @@ export const isConflictError = (arg: unknown): arg is ConflictError => { @Injectable() export class AdB2cService { private readonly logger = new Logger(AdB2cService.name); - private readonly tenantName = this.configService.get('TENANT_NAME'); - private readonly flowName = - this.configService.get('SIGNIN_FLOW_NAME'); + private readonly tenantName = this.configService.getOrThrow('TENANT_NAME'); + private readonly flowName = this.configService.getOrThrow('SIGNIN_FLOW_NAME'); private graphClient: Client; constructor(private readonly configService: ConfigService) { // ADB2Cへの認証情報 const credential = new ClientSecretCredential( - this.configService.get('ADB2C_TENANT_ID'), - this.configService.get('ADB2C_CLIENT_ID'), - this.configService.get('ADB2C_CLIENT_SECRET'), + this.configService.getOrThrow('ADB2C_TENANT_ID'), + this.configService.getOrThrow('ADB2C_CLIENT_ID'), + this.configService.getOrThrow('ADB2C_CLIENT_SECRET'), ); const authProvider = new TokenCredentialAuthenticationProvider(credential, { scopes: ['https://graph.microsoft.com/.default'], diff --git a/dictation_server/src/gateways/notificationhub/notificationhub.service.ts b/dictation_server/src/gateways/notificationhub/notificationhub.service.ts index 2f8fe90..679eef2 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.get('NOTIFICATION_HUB_CONNECT_STRING'), - this.configService.get('NOTIFICATION_HUB_NAME'), + this.configService.get('NOTIFICATION_HUB_CONNECT_STRING')??"", + this.configService.get('NOTIFICATION_HUB_NAME')??"", ); } diff --git a/dictation_server/src/main.ts b/dictation_server/src/main.ts index b4ad979..7943c9a 100644 --- a/dictation_server/src/main.ts +++ b/dictation_server/src/main.ts @@ -5,16 +5,18 @@ 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'"]; + helmetDirectives['navigate-to'] = ["'self'"]; helmetDirectives['style-src'] = ["'self'", 'https:']; diff --git a/dictation_server/src/repositories/tasks/tasks.repository.service.ts b/dictation_server/src/repositories/tasks/tasks.repository.service.ts index 1e207ca..3436d1c 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: null, + typist_user_id: null, status: TASK_STATUS.UPLOADED, }, ); 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 f5f9064..d772869 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 @@ -19,13 +19,13 @@ export class TemplateFile { @Column() file_name: string; @Column({ nullable: true }) - created_by?: string; + created_by: string | null; @CreateDateColumn() created_at: Date; @Column({ nullable: true }) - updated_by?: string; + updated_by: string | null; @UpdateDateColumn() updated_at: Date; @OneToMany(() => Task, (task) => task.template_file) - tasks?: Task[]; + tasks: Task[] | null; } diff --git a/dictation_server/src/repositories/workflows/entity/workflow.entity.ts b/dictation_server/src/repositories/workflows/entity/workflow.entity.ts index e3bac8e..806311a 100644 --- a/dictation_server/src/repositories/workflows/entity/workflow.entity.ts +++ b/dictation_server/src/repositories/workflows/entity/workflow.entity.ts @@ -25,35 +25,35 @@ export class Workflow { author_id: number; @Column({ nullable: true }) - worktype_id?: number; + worktype_id: number | null; @Column({ nullable: true }) - template_id?: number; + template_id: number | null; @Column({ nullable: true }) - created_by: string; + created_by: string | null; @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 created_at: Date; @Column({ nullable: true }) - updated_by?: string; + updated_by: string | null; @UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 updated_at: Date; @ManyToOne(() => User, (user) => user.id) @JoinColumn({ name: 'author_id' }) - author?: User; + author: User | null; @ManyToOne(() => Worktype, (worktype) => worktype.id) @JoinColumn({ name: 'worktype_id' }) - worktype?: Worktype; + worktype: Worktype | null; @ManyToOne(() => TemplateFile, (templateFile) => templateFile.id) @JoinColumn({ name: 'template_id' }) - template?: TemplateFile; + template: TemplateFile | null; @OneToMany(() => WorkflowTypist, (workflowTypist) => workflowTypist.workflow) - workflowTypists?: WorkflowTypist[]; + workflowTypists: WorkflowTypist[] | null; } diff --git a/dictation_server/src/repositories/workflows/workflows.repository.service.ts b/dictation_server/src/repositories/workflows/workflows.repository.service.ts index f658dcd..6ec186d 100644 --- a/dictation_server/src/repositories/workflows/workflows.repository.service.ts +++ b/dictation_server/src/repositories/workflows/workflows.repository.service.ts @@ -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); @@ -343,8 +343,8 @@ export class WorkflowsRepositoryService { const workflow = new Workflow(); workflow.account_id = accountId; workflow.author_id = authorId; - workflow.worktype_id = worktypeId; - workflow.template_id = templateId; + workflow.worktype_id = worktypeId ?? null; + workflow.template_id = templateId ?? null; return workflow; } @@ -358,8 +358,8 @@ export class WorkflowsRepositoryService { */ private makeWorkflowTypist( workflowId: number, - typistId: number, - typistGroupId: number, + typistId?: number, + typistGroupId?: number, ): DbWorkflowTypist { const workflowTypist = new DbWorkflowTypist(); workflowTypist.workflow_id = workflowId;