From 96848f5e54a5ae6c0f0742ebdd1c1a24581863a1 Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Thu, 19 Oct 2023 01:04:14 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20499:=20=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E2=91=A1=EF=BC=88files,licenses=20,=20Repositoies=E3=81=AElice?= =?UTF-8?q?nses=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2836: 修正②(files,licenses , Repositoiesのlicenses)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2836) - feature - files - licenses - repositories - licenses - users - worktypes - entity - licenses - audio_files - audio_option_item - checkout_permission - アクセストークンをそのままService層に渡している箇所を修正し、必要なパラメータのみ渡すように修正 - クライアントの型生成 - 割り当て可能ライセンス取得APIのIFが変わったため ## レビューポイント - ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## 動作確認状況 - ローカルでテストが通ることを確認 ## 補足 - 相談、参考資料などがあれば --- dictation_client/src/api/api.ts | 2 +- dictation_server/src/api/odms/openapi.json | 2 +- .../accounts/test/accounts.service.mock.ts | 8 + .../src/features/files/files.controller.ts | 118 +++++++++-- .../src/features/files/files.service.spec.ts | 92 ++++++--- .../src/features/files/files.service.ts | 65 ++++-- .../features/files/test/files.service.mock.ts | 4 +- .../src/features/files/test/utility.ts | 2 +- .../features/licenses/licenses.controller.ts | 113 +++++++--- .../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 +- .../src/features/tasks/tasks.service.spec.ts | 2 + .../features/tasks/test/tasks.service.mock.ts | 40 ++++ .../src/features/users/users.service.spec.ts | 6 +- .../src/features/users/users.service.ts | 27 ++- .../accounts/entity/account.entity.ts | 4 +- .../audio_files/entity/audio_file.entity.ts | 6 +- .../entity/audio_option_item.entity.ts | 2 +- .../entity/checkout_permission.entity.ts | 10 +- .../licenses/entity/license.entity.ts | 88 ++++---- .../licenses/licenses.repository.service.ts | 11 +- .../tasks/tasks.repository.service.ts | 4 +- .../user_groups/entity/user_group.entity.ts | 12 +- .../entity/user_group_member.entity.ts | 14 +- .../worktypes/entity/option_item.entity.ts | 8 +- .../worktypes/entity/worktype.entity.ts | 6 +- .../worktypes/worktypes.repository.service.ts | 2 +- 30 files changed, 601 insertions(+), 291 deletions(-) diff --git a/dictation_client/src/api/api.ts b/dictation_client/src/api/api.ts index 429e763..680a7e6 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; } /** * diff --git a/dictation_server/src/api/odms/openapi.json b/dictation_server/src/api/odms/openapi.json index 3887c30..993efb4 100644 --- a/dictation_server/src/api/odms/openapi.json +++ b/dictation_server/src/api/odms/openapi.json @@ -4465,7 +4465,7 @@ "licenseId": { "type": "number" }, "expiryDate": { "format": "date-time", "type": "string" } }, - "required": ["licenseId", "expiryDate"] + "required": ["licenseId"] }, "GetAllocatableLicensesResponse": { "type": "object", 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 920b877..b06fe63 100644 --- a/dictation_server/src/features/accounts/test/accounts.service.mock.ts +++ b/dictation_server/src/features/accounts/test/accounts.service.mock.ts @@ -444,6 +444,10 @@ 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, @@ -451,6 +455,10 @@ 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/files/files.controller.ts b/dictation_server/src/features/files/files.controller.ts index 7388d37..4a72268 100644 --- a/dictation_server/src/features/files/files.controller.ts +++ b/dictation_server/src/features/files/files.controller.ts @@ -2,6 +2,7 @@ import { Body, Controller, Get, + HttpException, HttpStatus, Post, Query, @@ -37,6 +38,7 @@ 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') @@ -75,10 +77,23 @@ export class FilesController { @Req() req: Request, @Body() body: AudioUploadFinishedRequest, ): Promise { - const token = retrieveAuthorizationToken(req); - const accessToken = jwt.decode(token, { json: true }) as AccessToken; + 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 context = makeContext(accessToken.userId); + const context = makeContext(userId); const { url, @@ -99,7 +114,7 @@ export class FilesController { const res = await this.filesService.uploadFinished( context, - accessToken.userId, + userId, url, authorId, fileName, @@ -149,10 +164,23 @@ export class FilesController { // eslint-disable-next-line @typescript-eslint/no-unused-vars @Query() _query: AudioUploadLocationRequest, ): Promise { - const token = retrieveAuthorizationToken(req); - const accessToken = jwt.decode(token, { json: true }) as AccessToken; + 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 context = makeContext(accessToken.userId); + const context = makeContext(userId); const url = await this.filesService.publishUploadSas(context, accessToken); return { url }; @@ -195,14 +223,27 @@ export class FilesController { ): Promise { const { audioFileId } = body; - const token = retrieveAuthorizationToken(req); - const accessToken = jwt.decode(token, { json: true }) as AccessToken; + 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 context = makeContext(accessToken.userId); + const context = makeContext(userId); const url = await this.filesService.publishAudioFileDownloadSas( context, - accessToken.userId, + userId, audioFileId, ); @@ -246,14 +287,27 @@ export class FilesController { ): Promise { const { audioFileId } = body; - const token = retrieveAuthorizationToken(req); - const accessToken = jwt.decode(token, { json: true }) as AccessToken; + 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 context = makeContext(accessToken.userId); + const context = makeContext(userId); const url = await this.filesService.publishTemplateFileDownloadSas( context, - accessToken.userId, + userId, audioFileId, ); @@ -287,8 +341,21 @@ export class FilesController { async uploadTemplateLocation( @Req() req: Request, ): Promise { - const token = retrieveAuthorizationToken(req); - const { userId } = jwt.decode(token, { json: true }) as AccessToken; + 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 context = makeContext(userId); @@ -333,8 +400,21 @@ export class FilesController { @Body() body: TemplateUploadFinishedRequest, ): Promise { const { name, url } = body; - const token = retrieveAuthorizationToken(req); - const { userId } = jwt.decode(token, { json: true }) as AccessToken; + 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 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 2ea8e73..dccffd8 100644 --- a/dictation_server/src/features/files/files.service.spec.ts +++ b/dictation_server/src/features/files/files.service.spec.ts @@ -35,11 +35,10 @@ describe('音声ファイルアップロードURL取得', () => { ); expect( - await service.publishUploadSas(makeContext('trackingId'), { - userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', - role: 'Author', - tier: 5, - }), + await service.publishUploadSas( + makeContext('trackingId'), + 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', + ), ).toEqual('https://blob-storage?sas-token'); }); @@ -57,11 +56,10 @@ describe('音声ファイルアップロードURL取得', () => { ); expect( - await service.publishUploadSas(makeContext('trackingId'), { - userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', - role: 'Author', - tier: 5, - }), + await service.publishUploadSas( + makeContext('trackingId'), + 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', + ), ).toEqual('https://blob-storage?sas-token'); }); @@ -78,11 +76,10 @@ describe('音声ファイルアップロードURL取得', () => { ); await expect( - service.publishUploadSas(makeContext('trackingId'), { - userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', - role: 'Author', - tier: 5, - }), + service.publishUploadSas( + makeContext('trackingId'), + 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', + ), ).rejects.toEqual( new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED), ); @@ -102,11 +99,10 @@ describe('音声ファイルアップロードURL取得', () => { blobParam.publishUploadSas = new Error('Azure service down'); await expect( - service.publishUploadSas(makeContext('trackingId'), { - userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', - role: 'Author', - tier: 5, - }), + service.publishUploadSas( + makeContext('trackingId'), + 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', + ), ).rejects.toEqual( new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED), ); @@ -295,7 +291,7 @@ describe('タスク作成', () => { }); describe('音声ファイルダウンロードURL取得', () => { - let source: DataSource = null; + let source: DataSource | null = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -308,11 +304,13 @@ 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, @@ -333,7 +331,7 @@ describe('音声ファイルダウンロードURL取得', () => { 'test.zip', 'InProgress', undefined, - authorId, + authorId ?? '', ); const blobParam = makeBlobstorageServiceMockValue(); @@ -341,6 +339,7 @@ describe('音声ファイルダウンロードURL取得', () => { blobParam.fileExists = true; const module = await makeTestingModuleWithBlob(source, blobParam); + if (!module) fail(); const service = module.get(FilesService); expect( @@ -353,6 +352,7 @@ 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,6 +382,7 @@ describe('音声ファイルダウンロードURL取得', () => { blobParam.fileExists = true; const module = await makeTestingModuleWithBlob(source, blobParam); + if (!module) fail(); const service = module.get(FilesService); await expect( @@ -396,6 +397,7 @@ describe('音声ファイルダウンロードURL取得', () => { }); it('Typistの場合、自身が担当するタスクでない場合エラー', async () => { + if (!source) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId } = await makeTestUser(source, { account_id: accountId, @@ -429,6 +431,7 @@ describe('音声ファイルダウンロードURL取得', () => { blobParam.fileExists = true; const module = await makeTestingModuleWithBlob(source, blobParam); + if (!module) fail(); const service = module.get(FilesService); await expect( @@ -443,6 +446,7 @@ 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, @@ -467,6 +471,7 @@ describe('音声ファイルダウンロードURL取得', () => { blobParam.fileExists = true; const module = await makeTestingModuleWithBlob(source, blobParam); + if (!module) fail(); const service = module.get(FilesService); await expect( @@ -481,6 +486,7 @@ describe('音声ファイルダウンロードURL取得', () => { }); it('Taskが存在しない場合はエラーとなる', async () => { + if (!source) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId } = await makeTestUser(source, { account_id: accountId, @@ -492,6 +498,7 @@ describe('音声ファイルダウンロードURL取得', () => { const blobParam = makeBlobstorageServiceMockValue(); const module = await makeTestingModuleWithBlob(source, blobParam); + if (!module) fail(); const service = module.get(FilesService); await expect( @@ -506,6 +513,7 @@ describe('音声ファイルダウンロードURL取得', () => { }); it('blobストレージにファイルが存在しない場合はエラーとなる', async () => { + if (!source) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId, @@ -526,7 +534,7 @@ describe('音声ファイルダウンロードURL取得', () => { 'test.zip', 'InProgress', undefined, - authorId, + authorId ?? '', ); const blobParam = makeBlobstorageServiceMockValue(); @@ -534,6 +542,7 @@ describe('音声ファイルダウンロードURL取得', () => { blobParam.fileExists = false; const module = await makeTestingModuleWithBlob(source, blobParam); + if (!module) fail(); const service = module.get(FilesService); await expect( @@ -549,7 +558,7 @@ describe('音声ファイルダウンロードURL取得', () => { }); describe('テンプレートファイルダウンロードURL取得', () => { - let source: DataSource = null; + let source: DataSource | null = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -562,11 +571,13 @@ 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, @@ -586,7 +597,7 @@ describe('テンプレートファイルダウンロードURL取得', () => { 'test.zip', 'InProgress', undefined, - authorId, + authorId ?? '', ); const blobParam = makeBlobstorageServiceMockValue(); @@ -594,6 +605,7 @@ describe('テンプレートファイルダウンロードURL取得', () => { blobParam.fileExists = true; const module = await makeTestingModuleWithBlob(source, blobParam); + if (!module) fail(); const service = module.get(FilesService); expect( @@ -606,6 +618,7 @@ 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, @@ -629,6 +642,7 @@ describe('テンプレートファイルダウンロードURL取得', () => { blobParam.fileExists = true; const module = await makeTestingModuleWithBlob(source, blobParam); + if (!module) fail(); const service = module.get(FilesService); await expect( @@ -643,6 +657,7 @@ describe('テンプレートファイルダウンロードURL取得', () => { }); it('Typistの場合、自身が担当するタスクでない場合エラー', async () => { + if (!source) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId } = await makeTestUser(source, { account_id: accountId, @@ -672,6 +687,7 @@ describe('テンプレートファイルダウンロードURL取得', () => { blobParam.fileExists = true; const module = await makeTestingModuleWithBlob(source, blobParam); + if (!module) fail(); const service = module.get(FilesService); await expect( @@ -686,6 +702,7 @@ describe('テンプレートファイルダウンロードURL取得', () => { }); it('Authorの場合、自身が登録したタスクでない場合エラー', async () => { + if (!source) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId } = await makeTestUser(source, { account_id: accountId, @@ -710,6 +727,7 @@ describe('テンプレートファイルダウンロードURL取得', () => { blobParam.fileExists = true; const module = await makeTestingModuleWithBlob(source, blobParam); + if (!module) fail(); const service = module.get(FilesService); await expect( @@ -724,6 +742,7 @@ describe('テンプレートファイルダウンロードURL取得', () => { }); it('Taskが存在しない場合はエラーとなる', async () => { + if (!source) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId } = await makeTestUser(source, { account_id: accountId, @@ -735,6 +754,7 @@ describe('テンプレートファイルダウンロードURL取得', () => { const blobParam = makeBlobstorageServiceMockValue(); const module = await makeTestingModuleWithBlob(source, blobParam); + if (!module) fail(); const service = module.get(FilesService); await expect( @@ -749,6 +769,7 @@ describe('テンプレートファイルダウンロードURL取得', () => { }); it('blobストレージにファイルが存在しない場合はエラーとなる', async () => { + if (!source) fail(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId, author_id: authorId } = await makeTestUser( source, @@ -768,7 +789,7 @@ describe('テンプレートファイルダウンロードURL取得', () => { 'test.zip', 'InProgress', undefined, - authorId, + authorId ?? '', ); const blobParam = makeBlobstorageServiceMockValue(); @@ -776,6 +797,7 @@ describe('テンプレートファイルダウンロードURL取得', () => { blobParam.fileExists = false; const module = await makeTestingModuleWithBlob(source, blobParam); + if (!module) fail(); const service = module.get(FilesService); await expect( @@ -791,7 +813,7 @@ describe('テンプレートファイルダウンロードURL取得', () => { }); describe('publishTemplateFileUploadSas', () => { - let source: DataSource = null; + let source: DataSource | null = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -804,12 +826,15 @@ 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 }); @@ -832,7 +857,9 @@ 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 }); @@ -858,7 +885,9 @@ 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 }); @@ -887,7 +916,7 @@ describe('publishTemplateFileUploadSas', () => { }); describe('templateUploadFinished', () => { - let source: DataSource = null; + let source: DataSource | null = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -900,12 +929,15 @@ 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 }); @@ -937,7 +969,9 @@ 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 }); @@ -975,7 +1009,9 @@ 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 bcae416..db97e77 100644 --- a/dictation_server/src/features/files/files.service.ts +++ b/dictation_server/src/features/files/files.service.ts @@ -24,6 +24,7 @@ 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 { @@ -206,7 +207,7 @@ export class FilesService { */ async publishUploadSas( context: Context, - token: AccessToken, + externalId: string, ): Promise { this.logger.log( `[IN] [${context.trackingId}] ${this.publishUploadSas.name}`, @@ -216,10 +217,11 @@ export class FilesService { let accountId: number; let country: string; try { - const user = await this.usersRepository.findUserByExternalId( - token.userId, - ); - accountId = user.account.id; + const user = await this.usersRepository.findUserByExternalId(externalId); + if (!user.account) { + throw new AccountNotFoundError('account not found.'); + } + accountId = user.account_id; country = user.account.country; } catch (e) { this.logger.error(`error=${e}`); @@ -291,14 +293,17 @@ export class FilesService { let userId: number; let country: string; let isTypist: boolean; - let authorId: string; + let authorId: string | undefined; 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; + authorId = user.author_id ?? undefined; } catch (e) { this.logger.error(`error=${e}`); @@ -321,7 +326,7 @@ export class FilesService { accountId, status, ); - const file = task.file; + const { file } = task; // タスクに紐づく音声ファイルだけが消される場合がある。 // その場合はダウンロード不可なので不在エラーとして扱う @@ -332,9 +337,9 @@ export class FilesService { } // ユーザーがAuthorの場合、自身が追加したタスクでない場合はエラー - if (!isTypist && task.file.author_id !== authorId) { + if (!isTypist && file.author_id !== authorId) { throw new AuthorUserNotMatchError( - `task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${task.file.author_id}, authorId:${authorId}`, + `task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${file.author_id}, authorId:${authorId}`, ); } @@ -425,14 +430,17 @@ export class FilesService { let userId: number; let country: string; let isTypist: boolean; - let authorId: string; + let authorId: string | undefined; try { const user = await this.usersRepository.findUserByExternalId(externalId); - accountId = user.account.id; + 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; + authorId = user.author_id ?? undefined; } catch (e) { this.logger.error(`error=${e}`); this.logger.log( @@ -454,6 +462,15 @@ 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; @@ -466,9 +483,9 @@ export class FilesService { } // ユーザーがAuthorの場合、自身が追加したタスクでない場合はエラー - if (!isTypist && task.file.author_id !== authorId) { + if (!isTypist && file.author_id !== authorId) { throw new AuthorUserNotMatchError( - `task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${task.file.author_id}, authorId:${authorId}`, + `task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${file.author_id}, authorId:${authorId}`, ); } @@ -515,6 +532,7 @@ export class FilesService { makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST, ); + case AudioFileNotFoundError: case TemplateFileNotFoundError: throw new HttpException( makeErrorResponse('E010701'), @@ -552,15 +570,18 @@ export class FilesService { `[IN] [${context.trackingId}] ${this.publishTemplateFileUploadSas.name} | params: { externalId: ${externalId} };`, ); try { - const { - account: { id: accountId, country }, - } = await this.usersRepository.findUserByExternalId(externalId); + const { account } = await this.usersRepository.findUserByExternalId( + externalId, + ); + if (!account) { + throw new AccountNotFoundError('account not found.'); + } // 国に応じたリージョンのBlobストレージにコンテナが存在するか確認 const isContainerExists = await this.blobStorageService.containerExists( context, - accountId, - country, + account.id, + account.country, ); if (!isContainerExists) { throw new Error('container not found.'); @@ -569,8 +590,8 @@ export class FilesService { // SASトークン発行 const url = await this.blobStorageService.publishTemplateUploadSas( context, - accountId, - country, + account.id, + account.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 2efce63..3221ab1 100644 --- a/dictation_server/src/features/files/test/files.service.mock.ts +++ b/dictation_server/src/features/files/test/files.service.mock.ts @@ -134,7 +134,7 @@ export const makeDefaultUsersRepositoryMockValue = created_by: 'test', created_at: new Date(), updated_by: null, - updated_at: null, + updated_at: new Date(), auto_renew: true, license_alert: true, notification: true, @@ -157,7 +157,7 @@ export const makeDefaultUsersRepositoryMockValue = created_by: '', created_at: new Date(), updated_by: '', - updated_at: null, + updated_at: new Date(), }, }, }; diff --git a/dictation_server/src/features/files/test/utility.ts b/dictation_server/src/features/files/test/utility.ts index 61772f6..c1f6e52 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 d739ccd..29dc015 100644 --- a/dictation_server/src/features/licenses/licenses.controller.ts +++ b/dictation_server/src/features/licenses/licenses.controller.ts @@ -2,6 +2,7 @@ import { Body, Controller, Get, + HttpException, HttpStatus, Post, Req, @@ -34,6 +35,7 @@ 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') @@ -73,12 +75,26 @@ export class LicensesController { @Req() req: Request, @Body() body: CreateOrdersRequest, ): Promise { - const accessToken = retrieveAuthorizationToken(req); - const payload = jwt.decode(accessToken, { 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; // ライセンス注文処理 await this.licensesService.licenseOrders( - payload, + userId, body.poNumber, body.orderCount, ); @@ -111,11 +127,25 @@ export class LicensesController { @Req() req: Request, @Body() body: IssueCardLicensesRequest, ): Promise { - const accessToken = retrieveAuthorizationToken(req); - const payload = jwt.decode(accessToken, { 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 cardLicenseKeys = await this.licensesService.issueCardLicenseKeys( - payload.userId, + userId, body.createCount, ); @@ -154,11 +184,25 @@ export class LicensesController { @Req() req: Request, @Body() body: ActivateCardLicensesRequest, ): Promise { - const accessToken = retrieveAuthorizationToken(req); - const payload = jwt.decode(accessToken, { 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; await this.licensesService.activateCardLicenseKey( - payload.userId, + userId, body.cardLicenseKey, ); @@ -194,16 +238,27 @@ export class LicensesController { // eslint-disable-next-line @typescript-eslint/no-unused-vars @Req() req: Request, ): Promise { - const token = retrieveAuthorizationToken(req); - const payload = 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(payload.userId); + const context = makeContext(userId); const allocatableLicenses = - await this.licensesService.getAllocatableLicenses( - context, - payload.userId, - ); + await this.licensesService.getAllocatableLicenses(context, userId); return allocatableLicenses; } @@ -245,16 +300,26 @@ export class LicensesController { @Req() req: Request, @Body() body: CancelOrderRequest, ): Promise { - const token = retrieveAuthorizationToken(req); - const payload = 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(payload.userId); + const context = makeContext(userId); - await this.licensesService.cancelOrder( - context, - payload.userId, - body.poNumber, - ); + await this.licensesService.cancelOrder(context, 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 4b5b87d..56d1ef2 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 token: AccessToken = { userId: '0001', role: '', tier: 5 }; + const userId = '0001'; body.orderCount = 1000; body.poNumber = '1'; expect( - await service.licenseOrders(token, body.poNumber, body.orderCount), + await service.licenseOrders(userId, body.poNumber, body.orderCount), ).toEqual(undefined); }); it('ユーザID取得できなかった場合、エラーとなる', async () => { @@ -78,11 +78,11 @@ describe('LicensesService', () => { accountsRepositoryMockValue, ); const body = new CreateOrdersRequest(); - const token: AccessToken = { userId: '', role: '', tier: 5 }; + const userId = ''; body.orderCount = 1000; body.poNumber = '1'; await expect( - service.licenseOrders(token, body.poNumber, body.orderCount), + service.licenseOrders(userId, body.poNumber, body.orderCount), ).rejects.toEqual( new HttpException( makeErrorResponse('E009999'), @@ -105,11 +105,11 @@ describe('LicensesService', () => { accountsRepositoryMockValue, ); const body = new CreateOrdersRequest(); - const token: AccessToken = { userId: '0001', role: '', tier: 5 }; + const userId = '0001'; body.orderCount = 1000; body.poNumber = '1'; await expect( - service.licenseOrders(token, body.poNumber, body.orderCount), + service.licenseOrders(userId, body.poNumber, body.orderCount), ).rejects.toEqual( new HttpException( makeErrorResponse('E009999'), @@ -130,11 +130,11 @@ describe('LicensesService', () => { accountsRepositoryMockValue, ); const body = new CreateOrdersRequest(); - const token: AccessToken = { userId: '0001', role: '', tier: 5 }; + const userId = '0001'; body.orderCount = 1000; body.poNumber = '1'; await expect( - service.licenseOrders(token, body.poNumber, body.orderCount), + service.licenseOrders(userId, body.poNumber, body.orderCount), ).rejects.toEqual( new HttpException( makeErrorResponse('E010401'), @@ -154,7 +154,7 @@ describe('LicensesService', () => { accountsRepositoryMockValue, ); const body = new IssueCardLicensesRequest(); - const token: AccessToken = { userId: '0001', role: '', tier: 5 }; + const userId = '0001'; body.createCount = 10; const issueCardLicensesResponse: IssueCardLicensesResponse = { cardLicenseKeys: [ @@ -171,7 +171,7 @@ describe('LicensesService', () => { ], }; expect( - await service.issueCardLicenseKeys(token.userId, body.createCount), + await service.issueCardLicenseKeys(userId, body.createCount), ).toEqual(issueCardLicensesResponse); }); it('カードライセンス発行に失敗した場合、エラーになる', async () => { @@ -187,10 +187,10 @@ describe('LicensesService', () => { accountsRepositoryMockValue, ); const body = new IssueCardLicensesRequest(); - const token: AccessToken = { userId: '0001', role: '', tier: 5 }; + const userId = '0001'; body.createCount = 1000; await expect( - service.issueCardLicenseKeys(token.userId, body.createCount), + service.issueCardLicenseKeys(userId, body.createCount), ).rejects.toEqual( new HttpException( makeErrorResponse('E009999'), @@ -210,10 +210,10 @@ describe('LicensesService', () => { accountsRepositoryMockValue, ); const body = new ActivateCardLicensesRequest(); - const token: AccessToken = { userId: '0001', role: '', tier: 5 }; + const userId = '0001'; body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY'; expect( - await service.activateCardLicenseKey(token.userId, body.cardLicenseKey), + await service.activateCardLicenseKey(userId, body.cardLicenseKey), ).toEqual(undefined); }); it('カードライセンス取り込みに失敗した場合、エラーになる(DBエラー)', async () => { @@ -229,10 +229,10 @@ describe('LicensesService', () => { accountsRepositoryMockValue, ); const body = new ActivateCardLicensesRequest(); - const token: AccessToken = { userId: '0001', role: '', tier: 5 }; + const userId = '0001'; body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY'; await expect( - service.activateCardLicenseKey(token.userId, body.cardLicenseKey), + service.activateCardLicenseKey(userId, body.cardLicenseKey), ).rejects.toEqual( new HttpException( makeErrorResponse('E009999'), @@ -254,10 +254,10 @@ describe('LicensesService', () => { accountsRepositoryMockValue, ); const body = new ActivateCardLicensesRequest(); - const token: AccessToken = { userId: '0001', role: '', tier: 5 }; + const userId = '0001'; body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY'; await expect( - service.activateCardLicenseKey(token.userId, body.cardLicenseKey), + service.activateCardLicenseKey(userId, body.cardLicenseKey), ).rejects.toEqual( new HttpException(makeErrorResponse('E010801'), HttpStatus.BAD_REQUEST), ); @@ -276,10 +276,10 @@ describe('LicensesService', () => { accountsRepositoryMockValue, ); const body = new ActivateCardLicensesRequest(); - const token: AccessToken = { userId: '0001', role: '', tier: 5 }; + const userId = '0001'; body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY'; await expect( - service.activateCardLicenseKey(token.userId, body.cardLicenseKey), + service.activateCardLicenseKey(userId, body.cardLicenseKey), ).rejects.toEqual( new HttpException(makeErrorResponse('E010802'), HttpStatus.BAD_REQUEST), ); @@ -287,7 +287,7 @@ describe('LicensesService', () => { }); describe('DBテスト', () => { - let source: DataSource = null; + let source: DataSource | null = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -300,12 +300,15 @@ 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, { @@ -323,7 +326,9 @@ 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, { @@ -362,13 +367,15 @@ 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); @@ -513,7 +520,7 @@ describe('DBテスト', () => { }); describe('ライセンス割り当て', () => { - let source: DataSource = null; + let source: DataSource | null = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -526,12 +533,15 @@ 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, { @@ -567,11 +577,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( @@ -579,22 +589,24 @@ 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, { @@ -630,30 +642,32 @@ 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, { @@ -705,32 +719,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( @@ -738,22 +752,24 @@ 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, { @@ -806,12 +822,14 @@ 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, { @@ -864,12 +882,14 @@ 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, { @@ -922,12 +942,14 @@ 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, { @@ -961,7 +983,9 @@ 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, { @@ -1013,7 +1037,7 @@ describe('ライセンス割り当て', () => { }); describe('ライセンス割り当て解除', () => { - let source: DataSource = null; + let source: DataSource | null = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -1026,12 +1050,15 @@ 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, { @@ -1068,11 +1095,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( @@ -1080,25 +1107,27 @@ 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, { @@ -1158,7 +1187,7 @@ describe('ライセンス割り当て解除', () => { }); describe('ライセンス注文キャンセル', () => { - let source: DataSource = null; + let source: DataSource | null = null; beforeEach(async () => { source = new DataSource({ type: 'sqlite', @@ -1171,12 +1200,15 @@ 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, ); @@ -1185,7 +1217,7 @@ describe('ライセンス注文キャンセル', () => { source, poNumber, tier2Accounts[0].account.id, - tier2Accounts[0].account.parent_account_id, + tier2Accounts[0].account.parent_account_id ?? 0, null, 10, 'Issue Requesting', @@ -1195,7 +1227,7 @@ describe('ライセンス注文キャンセル', () => { source, poNumber, tier2Accounts[0].account.id, - tier2Accounts[0].account.parent_account_id, + tier2Accounts[0].account.parent_account_id ?? 0, null, 10, 'Order Canceled', @@ -1214,12 +1246,14 @@ 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, ); @@ -1228,7 +1262,7 @@ describe('ライセンス注文キャンセル', () => { source, poNumber, tier2Accounts[0].account.id, - tier2Accounts[0].account.parent_account_id, + tier2Accounts[0].account.parent_account_id ?? 0, null, 10, 'Issued', @@ -1247,7 +1281,9 @@ describe('ライセンス注文キャンセル', () => { }); it('ライセンスが既にキャンセル済みの場合、エラーとなる', async () => { + if (!source) fail(); const module = await makeTestingModule(source); + if (!module) fail(); const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts( source, @@ -1257,7 +1293,7 @@ describe('ライセンス注文キャンセル', () => { source, poNumber, tier2Accounts[0].account.id, - tier2Accounts[0].account.parent_account_id, + tier2Accounts[0].account.parent_account_id ?? 0, 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 2c23bd8..3aa04f1 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( - accessToken: AccessToken, + externalId: string, poNumber: string, orderCount: number, ): Promise { //アクセストークンからユーザーIDを取得する this.logger.log(`[IN] ${this.licenseOrders.name}`); - const userId = accessToken.userId; let myAccountId: number; - let parentAccountId: number; + let parentAccountId: number | undefined; // ユーザIDからアカウントIDを取得する try { - myAccountId = (await this.usersRepository.findUserByExternalId(userId)) - .account_id; + myAccountId = ( + await this.usersRepository.findUserByExternalId(externalId) + ).account_id; } catch (e) { this.logger.error(`error=${e}`); switch (e.constructor) { @@ -65,9 +65,13 @@ export class LicensesService { // 親アカウントIDを取得 try { - parentAccountId = ( - await this.accountsRepository.findAccountById(myAccountId) - ).parent_account_id; + parentAccountId = + (await this.accountsRepository.findAccountById(myAccountId)) + .parent_account_id ?? undefined; + // 親アカウントIDが取得できない場合はエラー + if (parentAccountId === undefined) { + throw new Error('parent account id is undefined'); + } } 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 619aa95..5c0ef7b 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 = undefined; + user1.deleted_at = null; user1.created_by = 'test'; user1.created_at = new Date(); - user1.updated_by = undefined; - user1.updated_at = undefined; + user1.updated_by = null; + user1.updated_at = new Date(); return { findUserByExternalId: user1, diff --git a/dictation_server/src/features/licenses/test/utility.ts b/dictation_server/src/features/licenses/test/utility.ts index d0e9a89..321f9c0 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, + expiry_date: Date | null, accountId: number, type: string, status: string, - allocated_user_id: number, - order_id: number, - deleted_at: Date, - delete_order_id: number, + allocated_user_id: number | null, + order_id: number | null, + deleted_at: Date | null, + delete_order_id: number | null, ): 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, + issuedAt: Date | null, quantity: number, status: string, ): Promise => { @@ -138,7 +138,7 @@ export const selectCardLicensesCount = async ( export const selectCardLicense = async ( datasource: DataSource, cardLicenseKey: string, -): Promise<{ cardLicense: CardLicense }> => { +): Promise<{ cardLicense: CardLicense | null }> => { 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 }> => { +): Promise<{ license: License | null }> => { 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 }> => { +): Promise<{ licenseAllocationHistory: LicenseAllocationHistory | null }> => { const licenseAllocationHistory = await datasource .getRepository(LicenseAllocationHistory) .findOne({ @@ -182,7 +182,7 @@ export const selectOrderLicense = async ( datasource: DataSource, accountId: number, poNumber: string, -): Promise<{ orderLicense: LicenseOrder }> => { +): Promise<{ orderLicense: LicenseOrder | null }> => { 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 a75885f..d303b1e 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() - expiryDate: Date; + @ApiProperty({ required: false }) + expiryDate?: Date; } export class GetAllocatableLicensesResponse { @ApiProperty({ type: [AllocatableLicenseInfo] }) diff --git a/dictation_server/src/features/tasks/tasks.service.spec.ts b/dictation_server/src/features/tasks/tasks.service.spec.ts index f203563..bc61100 100644 --- a/dictation_server/src/features/tasks/tasks.service.spec.ts +++ b/dictation_server/src/features/tasks/tasks.service.spec.ts @@ -219,6 +219,8 @@ describe('TasksService', () => { audio_format: 'DS', comment: 'comment', is_encrypted: true, + deleted_at: null, + task: null, }, }, ], 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 e94a728..1e09b84 100644 --- a/dictation_server/src/features/tasks/test/tasks.service.mock.ts +++ b/dictation_server/src/features/tasks/test/tasks.service.mock.ts @@ -263,6 +263,11 @@ 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, @@ -270,6 +275,11 @@ 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, @@ -277,6 +287,11 @@ 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, @@ -284,6 +299,11 @@ 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, @@ -291,6 +311,11 @@ 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, }, ], }; @@ -343,60 +368,70 @@ const defaultTasksRepositoryMockValue: { 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: { @@ -416,6 +451,8 @@ const defaultTasksRepositoryMockValue: { audio_format: 'DS', comment: 'comment', is_encrypted: true, + deleted_at: null, + task: null, }, }, ], @@ -448,6 +485,9 @@ const defaultTasksRepositoryMockValue: { license: null, userGroupMembers: null, }, + task: null, + user_group_id: null, + user_group: null, }, ], count: 1, diff --git a/dictation_server/src/features/users/users.service.spec.ts b/dictation_server/src/features/users/users.service.spec.ts index f4c42aa..2b257c9 100644 --- a/dictation_server/src/features/users/users.service.spec.ts +++ b/dictation_server/src/features/users/users.service.spec.ts @@ -113,6 +113,7 @@ 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); @@ -126,8 +127,9 @@ describe('UsersService.confirmUser', () => { allocated_user_id: null, order_id: null, delete_order_id: null, + user: null, }); - }); + }, 600000); it('トークンの形式が不正な場合、形式不正エラーとなる。', async () => { if (!source) fail(); @@ -1762,7 +1764,7 @@ describe('UsersService.getUsers', () => { await expect(service.getUsers('externalId_failed')).rejects.toEqual( new HttpException(makeErrorResponse('E009999'), HttpStatus.NOT_FOUND), ); - },60000000); + }, 60000000); it('ADB2Cからのユーザーの取得に失敗した場合、エラーとなる', async () => { const adb2cParam = makeDefaultAdB2cMockValue(); diff --git a/dictation_server/src/features/users/users.service.ts b/dictation_server/src/features/users/users.service.ts index 8df69b4..fe92b63 100644 --- a/dictation_server/src/features/users/users.service.ts +++ b/dictation_server/src/features/users/users.service.ts @@ -503,18 +503,27 @@ export class UsersService { if (dbUser.license) { // 有効期限日付 YYYY/MM/DD - const expiry_date = dbUser.license.expiry_date; - expiration = `${expiry_date.getFullYear()}/${ - expiry_date.getMonth() + 1 - }/${expiry_date.getDate()}`; + const expiry_date = dbUser.license.expiry_date ?? undefined; + expiration = + expiry_date !== undefined + ? `${expiry_date.getFullYear()}/${ + expiry_date.getMonth() + 1 + }/${expiry_date.getDate()}` + : undefined; const currentDate = new DateWithZeroTime(); // 有効期限までの日数 - remaining = Math.floor( - (expiry_date.getTime() - currentDate.getTime()) / - (1000 * 60 * 60 * 24), - ); - if (remaining <= LICENSE_EXPIRATION_THRESHOLD_DAYS) { + 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 ? USER_LICENSE_STATUS.RENEW : USER_LICENSE_STATUS.ALERT; diff --git a/dictation_server/src/repositories/accounts/entity/account.entity.ts b/dictation_server/src/repositories/accounts/entity/account.entity.ts index f6787d9..fb10ee6 100644 --- a/dictation_server/src/repositories/accounts/entity/account.entity.ts +++ b/dictation_server/src/repositories/accounts/entity/account.entity.ts @@ -14,7 +14,7 @@ export class Account { id: number; @Column({ nullable: true }) - parent_account_id?: number; + parent_account_id: number | null; @Column() tier: number; @@ -44,7 +44,7 @@ export class Account { active_worktype_id?: number; @Column({ nullable: true }) - deleted_at?: Date; + deleted_at: Date | null; @Column({ nullable: true }) created_by?: string; 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 244f955..b845d01 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 @@ -33,11 +33,11 @@ export class AudioFile { @Column() audio_format: string; @Column({ nullable: true }) - comment?: string; + comment: string | null; @Column({ nullable: true }) - deleted_at?: Date; + deleted_at: Date | null; @Column() is_encrypted: boolean; @OneToOne(() => Task, (task) => task.file) - task?: Task; + task: Task | null; } 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 ca39f80..665ac69 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; + task: Task | null; } 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 cd6b116..4339e84 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 @@ -19,20 +19,20 @@ export class CheckoutPermission { task_id: number; @Column({ nullable: true }) - user_id?: number; + user_id: number | null; @Column({ nullable: true }) - user_group_id?: number; + user_group_id: number | null; @OneToOne(() => User, (user) => user.id) @JoinColumn({ name: 'user_id' }) - user?: User; + user: User | null; @OneToOne(() => UserGroup, (group) => group.id) @JoinColumn({ name: 'user_group_id' }) - user_group?: UserGroup; + user_group: UserGroup | null; @ManyToOne(() => Task, (task) => task.id) @JoinColumn({ name: 'task_id' }) - task?: Task; + task: Task | null; } diff --git a/dictation_server/src/repositories/licenses/entity/license.entity.ts b/dictation_server/src/repositories/licenses/entity/license.entity.ts index b30018e..d462e6e 100644 --- a/dictation_server/src/repositories/licenses/entity/license.entity.ts +++ b/dictation_server/src/repositories/licenses/entity/license.entity.ts @@ -25,11 +25,11 @@ export class LicenseOrder { @Column() to_account_id: number; - @CreateDateColumn() + @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) ordered_at: Date; @Column({ nullable: true }) - issued_at?: Date; + issued_at: Date | null; @Column() quantity: number; @@ -38,18 +38,18 @@ export class LicenseOrder { status: string; @Column({ nullable: true }) - canceled_at?: Date; + canceled_at: Date | null; @Column({ nullable: true }) - created_by: string; + created_by: string | null; - @CreateDateColumn() + @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) created_at: Date; @Column({ nullable: true }) - updated_by: string; + updated_by: string | null; - @UpdateDateColumn() + @UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) updated_at: Date; } @@ -59,7 +59,7 @@ export class License { id: number; @Column({ nullable: true }) - expiry_date: Date; + expiry_date: Date | null; @Column() account_id: number; @@ -71,34 +71,34 @@ export class License { status: string; @Column({ nullable: true }) - allocated_user_id: number; + allocated_user_id: number | null; @Column({ nullable: true }) - order_id: number; + order_id: number | null; @Column({ nullable: true }) - deleted_at: Date; + deleted_at: Date | null; @Column({ nullable: true }) - delete_order_id: number; + delete_order_id: number | null; @Column({ nullable: true }) - created_by: string; + created_by: string | null; - @CreateDateColumn() + @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) created_at: Date; @Column({ nullable: true }) - updated_by: string; + updated_by: string | null; - @UpdateDateColumn() + @UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) updated_at: Date; @OneToOne(() => User, (user) => user.license, { createForeignKeyConstraints: false, }) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定 @JoinColumn({ name: 'allocated_user_id' }) - user?: User; + user: User | null; } @Entity({ name: 'card_license_issue' }) @@ -110,15 +110,15 @@ export class CardLicenseIssue { issued_at: Date; @Column({ nullable: true }) - created_by: string; + created_by: string | null; - @CreateDateColumn() + @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) created_at: Date; @Column({ nullable: true }) - updated_by: string; + updated_by: string | null; - @UpdateDateColumn() + @UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) updated_at: Date; } @@ -134,18 +134,18 @@ export class CardLicense { card_license_key: string; @Column({ nullable: true }) - activated_at: Date; + activated_at: Date | null; @Column({ nullable: true }) - created_by: string; + created_by: string | null; - @CreateDateColumn() + @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) created_at: Date; @Column({ nullable: true }) - updated_by: string; + updated_by: string | null; - @UpdateDateColumn({}) + @UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) updated_at: Date; } @@ -173,25 +173,25 @@ export class LicenseAllocationHistory { switch_from_type: string; @Column({ nullable: true }) - deleted_at: Date; + deleted_at: Date | null; @Column({ nullable: true }) - created_by: string; + created_by: string | null; - @CreateDateColumn() + @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) created_at: Date; @Column({ nullable: true }) - updated_by: string; + updated_by: string | null; - @UpdateDateColumn() + @UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) updated_at: Date; @ManyToOne(() => License, (licenses) => licenses.id, { createForeignKeyConstraints: false, }) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定 @JoinColumn({ name: 'license_id' }) - license?: License; + license: License | null; } @Entity({ name: 'licenses_archive' }) @@ -200,7 +200,7 @@ export class LicenseArchive { id: number; @Column({ nullable: true }) - expiry_date: Date; + expiry_date: Date | null; @Column() account_id: number; @@ -212,30 +212,30 @@ export class LicenseArchive { status: string; @Column({ nullable: true }) - allocated_user_id: number; + allocated_user_id: number | null; @Column({ nullable: true }) - order_id: number; + order_id: number | null; @Column({ nullable: true }) - deleted_at: Date; + deleted_at: Date | null; @Column({ nullable: true }) - delete_order_id: number; + delete_order_id: number | null; @Column({ nullable: true }) - created_by: string; + created_by: string | null; @Column() created_at: Date; @Column({ nullable: true }) - updated_by: string; + updated_by: string | null; @Column() updated_at: Date; - @CreateDateColumn() + @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) archived_at: Date; } @@ -263,20 +263,20 @@ export class LicenseAllocationHistoryArchive { switch_from_type: string; @Column({ nullable: true }) - deleted_at: Date; + deleted_at: Date | null; @Column({ nullable: true }) - created_by: string; + created_by: string | null; @Column() created_at: Date; @Column({ nullable: true }) - updated_by: string; + updated_by: string | null; @Column() updated_at: Date; - @CreateDateColumn() + @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) 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 67496bf..c43fa47 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, + expiryDate: license.expiry_date ?? undefined, })); } /** @@ -469,6 +469,13 @@ export class LicensesRepositoryService { }, }); + // ライセンスが存在しない場合はエラー + if (!targetLicense) { + throw new LicenseNotExistError( + `License not exist. licenseId: ${newLicenseId}`, + ); + } + // 期限切れの場合はエラー if (targetLicense.expiry_date) { const currentDay = new Date(); @@ -533,7 +540,7 @@ export class LicensesRepositoryService { }); let switchFromType = ''; - if (oldLicenseType) { + if (oldLicenseType && oldLicenseType.license) { switch (oldLicenseType.license.type) { case LICENSE_TYPE.CARD: switchFromType = SWITCH_FROM_TYPE.CARD; diff --git a/dictation_server/src/repositories/tasks/tasks.repository.service.ts b/dictation_server/src/repositories/tasks/tasks.repository.service.ts index 0f88525..b6edf11 100644 --- a/dictation_server/src/repositories/tasks/tasks.repository.service.ts +++ b/dictation_server/src/repositories/tasks/tasks.repository.service.ts @@ -844,8 +844,8 @@ export class TasksRepositoryService { (assignee) => { const checkoutPermission = new CheckoutPermission(); checkoutPermission.task_id = taskRecord.id; - checkoutPermission.user_id = assignee.typistUserId; - checkoutPermission.user_group_id = assignee.typistGroupId; + checkoutPermission.user_id = assignee.typistUserId ?? null; + checkoutPermission.user_group_id = assignee.typistGroupId ?? null; return checkoutPermission; }, ); 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 42e14fa..7299107 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 @@ -20,23 +20,23 @@ export class UserGroup { name: string; @Column({ nullable: true }) - deleted_at?: Date; + deleted_at: Date | null; @Column({ nullable: true }) - created_by?: string; + created_by: string | null; @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 - created_at?: Date; + created_at: Date | null; @Column({ nullable: true }) - updated_by?: string; + updated_by: string | null; @UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 - updated_at?: Date; + updated_at: Date | null; @OneToMany( () => UserGroupMember, (userGroupMember) => userGroupMember.userGroup, ) - userGroupMembers?: UserGroupMember[]; + userGroupMembers: UserGroupMember[] | null; } 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 93f2484..59940a8 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 @@ -22,25 +22,25 @@ export class UserGroupMember { user_id: number; @Column({ nullable: true }) - deleted_at?: Date; + deleted_at: Date | null; @Column({ nullable: true }) - created_by?: string; + created_by: string | null; @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 - created_at?: Date; + created_at: Date | null; @Column({ nullable: true }) - updated_by?: string; + updated_by: string | null; @UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 - updated_at?: Date; + updated_at: Date | null; @ManyToOne(() => User, (user) => user.id) @JoinColumn({ name: 'user_id' }) - user?: User; + user: User | null; @ManyToOne(() => UserGroup, (userGroup) => userGroup.id) @JoinColumn({ name: 'user_group_id' }) - userGroup?: UserGroup; + userGroup: UserGroup | null; } 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 fd0a14e..e35331c 100644 --- a/dictation_server/src/repositories/worktypes/entity/option_item.entity.ts +++ b/dictation_server/src/repositories/worktypes/entity/option_item.entity.ts @@ -19,11 +19,11 @@ export class OptionItem { @Column() initial_value: string; @Column({ nullable: true }) - created_by?: string; + created_by: string | null; @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 - created_at?: Date; + created_at: Date | null; @Column({ nullable: true }) - updated_by?: string; + updated_by: string | null; @UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 - updated_at?: Date; + updated_at: Date | null; } diff --git a/dictation_server/src/repositories/worktypes/entity/worktype.entity.ts b/dictation_server/src/repositories/worktypes/entity/worktype.entity.ts index 67d055a..74cdb75 100644 --- a/dictation_server/src/repositories/worktypes/entity/worktype.entity.ts +++ b/dictation_server/src/repositories/worktypes/entity/worktype.entity.ts @@ -19,10 +19,10 @@ export class Worktype { custom_worktype_id: string; @Column({ nullable: true }) - description?: string; + description: string | null; @Column({ nullable: true }) - deleted_at?: Date; + deleted_at: Date | null; @Column({ nullable: true }) created_by: string; @@ -31,7 +31,7 @@ export class Worktype { created_at: Date; @Column({ nullable: true }) - updated_by?: string; + updated_by: string | null; @UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 updated_at: Date; diff --git a/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts b/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts index 88f3bd3..616f9e0 100644 --- a/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts +++ b/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts @@ -153,7 +153,7 @@ export class WorktypesRepositoryService { // ワークタイプを更新 worktype.custom_worktype_id = worktypeId; - worktype.description = description; + worktype.description = description ?? null; await worktypeRepo.save(worktype); }); }