From b38d2c44f79a4cc4e98dfe32cbaaf797613e3781 Mon Sep 17 00:00:00 2001 From: "makabe.t" Date: Thu, 21 Sep 2023 04:51:00 +0000 Subject: [PATCH 1/4] =?UTF-8?q?Merged=20PR=20428:=20API=20IF=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2714: API IF修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2714) - 以下のAPIのIFを修正しました。 - テンプレートファイル一覧取得API - テンプレートファイルアップロード完了API ## レビューポイント - 想定通りの修正となっているか。 ## UIの変更 - なし ## 動作確認状況 - ローカルで確認 --- dictation_server/src/api/odms/openapi.json | 25 ++++++++++--------- .../src/features/files/files.controller.ts | 5 ++-- .../src/features/files/types/types.ts | 7 +++--- .../src/features/templates/types/types.ts | 4 +-- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/dictation_server/src/api/odms/openapi.json b/dictation_server/src/api/odms/openapi.json index d91cfc5..caf1c85 100644 --- a/dictation_server/src/api/odms/openapi.json +++ b/dictation_server/src/api/odms/openapi.json @@ -3792,7 +3792,7 @@ "properties": { "url": { "type": "string" } }, "required": ["url"] }, - "TemplateFile": { + "TemplateUploadFinishedRequest": { "type": "object", "properties": { "name": { @@ -3801,21 +3801,11 @@ }, "url": { "type": "string", - "description": "テンプレートファイルのURL" + "description": "テンプレートファイルのアップロード先URL" } }, "required": ["name", "url"] }, - "TemplateUploadFinishedRequest": { - "type": "object", - "properties": { - "templateFile": { - "description": "テンプレートファイルのファイル情報", - "allOf": [{ "$ref": "#/components/schemas/TemplateFile" }] - } - }, - "required": ["templateFile"] - }, "TemplateUploadFinishedReqponse": { "type": "object", "properties": {} }, "Assignee": { "type": "object", @@ -4023,6 +4013,17 @@ "required": ["poNumber"] }, "CancelOrderResponse": { "type": "object", "properties": {} }, + "TemplateFile": { + "type": "object", + "properties": { + "id": { "type": "number", "description": "テンプレートファイルのID" }, + "name": { + "type": "string", + "description": "テンプレートファイルのファイル名" + } + }, + "required": ["id", "name"] + }, "GetTemplatesResponse": { "type": "object", "properties": { diff --git a/dictation_server/src/features/files/files.controller.ts b/dictation_server/src/features/files/files.controller.ts index eb0d311..8a89dc4 100644 --- a/dictation_server/src/features/files/files.controller.ts +++ b/dictation_server/src/features/files/files.controller.ts @@ -331,13 +331,14 @@ export class FilesController { @Req() req: Request, @Body() body: TemplateUploadFinishedRequest, ): Promise { - const { templateFile } = body; + const { name, url } = body; const token = retrieveAuthorizationToken(req); const accessToken = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(accessToken.userId); console.log(context.trackingId); - console.log(templateFile); + console.log(name); + console.log(url); return {}; } diff --git a/dictation_server/src/features/files/types/types.ts b/dictation_server/src/features/files/types/types.ts index ed38f54..2de8a4a 100644 --- a/dictation_server/src/features/files/types/types.ts +++ b/dictation_server/src/features/files/types/types.ts @@ -1,5 +1,4 @@ import { ApiProperty } from '@nestjs/swagger'; -import { TemplateFile } from '../../templates/types/types'; export class AudioUploadLocationRequest {} @@ -95,8 +94,10 @@ export class AudioUploadFinishedResponse { } export class TemplateUploadFinishedRequest { - @ApiProperty({ description: 'テンプレートファイルのファイル情報' }) - templateFile: TemplateFile; + @ApiProperty({ description: 'テンプレートファイルのファイル名' }) + name: string; + @ApiProperty({ description: 'テンプレートファイルのアップロード先URL' }) + url: string; } export class TemplateUploadFinishedReqponse {} diff --git a/dictation_server/src/features/templates/types/types.ts b/dictation_server/src/features/templates/types/types.ts index a331ad8..243298d 100644 --- a/dictation_server/src/features/templates/types/types.ts +++ b/dictation_server/src/features/templates/types/types.ts @@ -1,10 +1,10 @@ import { ApiProperty } from '@nestjs/swagger'; export class TemplateFile { + @ApiProperty({ description: 'テンプレートファイルのID' }) + id: number; @ApiProperty({ description: 'テンプレートファイルのファイル名' }) name: string; - @ApiProperty({ description: 'テンプレートファイルのURL' }) - url: string; } export class GetTemplatesResponse { From cda36528171120e74d183720b9dacb99cdbe687d Mon Sep 17 00:00:00 2001 From: masaaki Date: Thu, 21 Sep 2023 06:52:15 +0000 Subject: [PATCH 2/4] =?UTF-8?q?Merged=20PR=20430:=20=E8=84=86=E5=BC=B1?= =?UTF-8?q?=E6=80=A7=E8=A9=A6=E9=A8=93=E3=83=84=E3=83=BC=E3=83=AB=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C=E3=82=92=E5=85=83=E3=81=AB=E6=88=BB=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2694: 脆弱性試験ツール対応を元に戻す](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2694) - 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず) - 何をどう変更したか、追加したライブラリなど - ドメイン確認用のファイルについて削除しました。 - このPull Requestでの対象/対象外 - すべて対象 - 影響範囲(他の機能にも影響があるか) - 無し ## レビューポイント - 特にレビューしてほしい箇所 - 軽微なものや自明なものは記載不要 - 修正範囲が大きい場合などに記載 - 全体的にや仕様を満たしているか等は本当に必要な時のみ記載 - 特筆する部分はありません ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 - 無し ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば - ローカル環境でnpm run build:localを実施していた場合、 /app/dictation_server/build配下のファイル「YOweYATRY5PBN1G9d….html」を手動で削除する必要があります。 動作上の悪影響はありませんが、ゴミなので削除願います。 --- ...G9dHDpfWLbSeVvGNpe61DtYdLSC82pqmPNyIw8EHXwTa6o4iNQB5rNSa.html | 1 - 1 file changed, 1 deletion(-) delete mode 100644 dictation_client/static_contents/YOweYATRY5PBN1G9dHDpfWLbSeVvGNpe61DtYdLSC82pqmPNyIw8EHXwTa6o4iNQB5rNSa.html diff --git a/dictation_client/static_contents/YOweYATRY5PBN1G9dHDpfWLbSeVvGNpe61DtYdLSC82pqmPNyIw8EHXwTa6o4iNQB5rNSa.html b/dictation_client/static_contents/YOweYATRY5PBN1G9dHDpfWLbSeVvGNpe61DtYdLSC82pqmPNyIw8EHXwTa6o4iNQB5rNSa.html deleted file mode 100644 index 6ed15bd..0000000 --- a/dictation_client/static_contents/YOweYATRY5PBN1G9dHDpfWLbSeVvGNpe61DtYdLSC82pqmPNyIw8EHXwTa6o4iNQB5rNSa.html +++ /dev/null @@ -1 +0,0 @@ -YOweYATRY5PBN1G9dHDpfWLbSeVvGNpe61DtYdLSC82pqmPNyIw8EHXwTa6o4iNQB5rNSa \ No newline at end of file From 9ac40c00ac6ebe8c42476a24126702751c6fe6a6 Mon Sep 17 00:00:00 2001 From: masaaki Date: Thu, 21 Sep 2023 07:59:09 +0000 Subject: [PATCH 3/4] =?UTF-8?q?Merged=20PR=20431:=20static=5Fcontents?= =?UTF-8?q?=E3=83=95=E3=82=A9=E3=83=AB=E3=83=80=E3=81=8C=E7=A9=BA=E3=81=AE?= =?UTF-8?q?=E5=A0=B4=E5=90=88=E3=81=AE=E5=AF=BE=E5=BF=9C=E3=82=92=E8=A1=8C?= =?UTF-8?q?=E3=81=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2719: static_contentsフォルダが空の場合の対応を行う](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2719) - 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず) - 何をどう変更したか、追加したライブラリなど - このPull Requestでの対象/対象外 - 影響範囲(他の機能にも影響があるか) - statis_contents配下が空の場合、gitに認識されなくなってしまうので、コピー処理自体を削除しました - 今後復活させたくなった時のため、wikiに手順を追加しておきました。 https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/OMDSDictation/_wiki/wikis/OMDSDictation_wiki/268/%E9%9D%99%E7%9A%84%E3%82%B3%E3%83%B3%E3%83%86%E3%83%B3%E3%83%84%E3%81%AB%E5%AF%BE%E3%81%97%E3%81%A6%E9%9D%99%E7%9A%84%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E9%85%8D%E7%BD%AE%E3%81%97%E3%81%9F%E3%81%84 ## レビューポイント - 特にレビューしてほしい箇所 - 軽微なものや自明なものは記載不要 - 修正範囲が大きい場合などに記載 - 全体的にや仕様を満たしているか等は本当に必要な時のみ記載 - 特にありません ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 - 特にありません ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば --- dictation_client/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dictation_client/package.json b/dictation_client/package.json index 7e9c601..25c1b3b 100644 --- a/dictation_client/package.json +++ b/dictation_client/package.json @@ -7,9 +7,9 @@ }, "scripts": { "start": "vite", - "build": "tsc && vite build && cp -r static_contents/. build/", - "build:prod": "tsc && vite build && cp -r static_contents/. build/", - "build:local": "tsc && vite build && cp -r static_contents/. build/ && sh localdeploy.sh", + "build": "tsc && vite build", + "build:prod": "tsc && vite build", + "build:local": "tsc && vite build && sh localdeploy.sh", "preview": "vite preview", "typecheck": "tsc --noEmit", "codegen": "sh codegen.sh", @@ -96,4 +96,4 @@ } ] } -} \ No newline at end of file +} From f928aa4fd2c1e5433c2fbc7bf48fcd0a4b7c9f06 Mon Sep 17 00:00:00 2001 From: "makabe.t" Date: Thu, 21 Sep 2023 08:30:21 +0000 Subject: [PATCH 4/4] =?UTF-8?q?Merged=20PR=20424:=20API=E5=AE=9F=E8=A3=85?= =?UTF-8?q?=EF=BC=88=E3=83=86=E3=83=B3=E3=83=97=E3=83=AC=E3=83=BC=E3=83=88?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E4=B8=80=E8=A6=A7=E5=8F=96?= =?UTF-8?q?=E5=BE=97API=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2650: API実装(テンプレートファイル一覧取得API)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2650) - テンプレートファイル一覧取得APIとテストを実装しました。 ## レビューポイント - サービスの配置、リポジトリの呼び出しは適切か - テストケースは適切か - テスト用にtemplates配下にテンプレートファイル追加関数を追加したが適切か ## UIの変更 - なし ## 動作確認状況 - ローカルで確認 --- dictation_server/src/common/test/modules.ts | 4 + .../templates/templates.controller.ts | 8 +- .../features/templates/templates.module.ts | 4 +- .../templates/templates.service.spec.ts | 108 ++++++++++++++++++ .../features/templates/templates.service.ts | 53 ++++++++- .../src/features/templates/test/utility.ts | 28 +++++ .../entity/template_file.entity.ts | 2 - .../template_files.repository.service.ts | 18 +++ 8 files changed, 216 insertions(+), 9 deletions(-) create mode 100644 dictation_server/src/features/templates/templates.service.spec.ts create mode 100644 dictation_server/src/features/templates/test/utility.ts diff --git a/dictation_server/src/common/test/modules.ts b/dictation_server/src/common/test/modules.ts index ed77ffc..b3a2eef 100644 --- a/dictation_server/src/common/test/modules.ts +++ b/dictation_server/src/common/test/modules.ts @@ -30,6 +30,8 @@ import { NotificationhubService } from '../../gateways/notificationhub/notificat import { FilesService } from '../../features/files/files.service'; import { LicensesService } from '../../features/licenses/licenses.service'; import { TasksService } from '../../features/tasks/tasks.service'; +import { TemplatesService } from '../../features/templates/templates.service'; +import { TemplatesModule } from '../../features/templates/templates.module'; export const makeTestingModule = async ( datasource: DataSource, @@ -50,6 +52,7 @@ export const makeTestingModule = async ( UsersModule, SendGridModule, LicensesModule, + TemplatesModule, AccountsRepositoryModule, UsersRepositoryModule, LicensesRepositoryModule, @@ -74,6 +77,7 @@ export const makeTestingModule = async ( FilesService, TasksService, LicensesService, + TemplatesService, ], }) .useMocker(async (token) => { diff --git a/dictation_server/src/features/templates/templates.controller.ts b/dictation_server/src/features/templates/templates.controller.ts index f850067..6d85968 100644 --- a/dictation_server/src/features/templates/templates.controller.ts +++ b/dictation_server/src/features/templates/templates.controller.ts @@ -47,11 +47,11 @@ export class TemplatesController { @Get() async getTemplates(@Req() req: Request): Promise { const token = retrieveAuthorizationToken(req); - const accessToken = jwt.decode(token, { json: true }) as AccessToken; + const { userId } = jwt.decode(token, { json: true }) as AccessToken; - const context = makeContext(accessToken.userId); - console.log(context.trackingId); + const context = makeContext(userId); + const templates = await this.templatesService.getTemplates(context, userId); - return { templates: [] }; + return { templates }; } } diff --git a/dictation_server/src/features/templates/templates.module.ts b/dictation_server/src/features/templates/templates.module.ts index 76aa447..ac14580 100644 --- a/dictation_server/src/features/templates/templates.module.ts +++ b/dictation_server/src/features/templates/templates.module.ts @@ -1,9 +1,11 @@ import { Module } from '@nestjs/common'; import { TemplatesController } from './templates.controller'; import { TemplatesService } from './templates.service'; +import { UsersRepositoryModule } from '../../repositories/users/users.repository.module'; +import { TemplateFilesRepositoryModule } from '../../repositories/template_files/template_files.repository.module'; @Module({ - imports: [], + imports: [UsersRepositoryModule, TemplateFilesRepositoryModule], providers: [TemplatesService], controllers: [TemplatesController], }) diff --git a/dictation_server/src/features/templates/templates.service.spec.ts b/dictation_server/src/features/templates/templates.service.spec.ts new file mode 100644 index 0000000..c29b168 --- /dev/null +++ b/dictation_server/src/features/templates/templates.service.spec.ts @@ -0,0 +1,108 @@ +import { DataSource } from 'typeorm'; +import { makeTestingModule } from '../../common/test/modules'; +import { TemplatesService } from './templates.service'; +import { createTemplateFile } from './test/utility'; +import { makeTestAccount } from '../../common/test/utility'; +import { makeContext } from '../../common/log'; +import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service'; +import { HttpException, HttpStatus } from '@nestjs/common'; +import { makeErrorResponse } from '../../common/error/makeErrorResponse'; + +describe('getTemplates', () => { + let source: DataSource = null; + beforeEach(async () => { + source = new DataSource({ + type: 'sqlite', + database: ':memory:', + logging: false, + entities: [__dirname + '/../../**/*.entity{.ts,.js}'], + synchronize: true, // trueにすると自動的にmigrationが行われるため注意 + }); + return source.initialize(); + }); + + afterEach(async () => { + await source.destroy(); + source = null; + }); + + it('テンプレートファイル一覧を取得できる', async () => { + const module = await makeTestingModule(source); + const service = module.get(TemplatesService); + // 第五階層のアカウント作成 + const { account, admin } = await makeTestAccount(source, { tier: 5 }); + const context = makeContext(admin.external_id); + + const template1 = await createTemplateFile( + source, + account.id, + 'test1', + 'https://url1/test1', + ); + const template2 = await createTemplateFile( + source, + account.id, + 'test2', + 'https://url2/test2', + ); + + // 作成したデータを確認 + { + expect(template1.file_name).toBe('test1'); + expect(template2.file_name).toBe('test2'); + } + + const templates = await service.getTemplates(context, admin.external_id); + + //実行結果を確認 + { + expect(templates.length).toBe(2); + expect(templates[0].id).toBe(template1.id); + expect(templates[0].name).toBe(template1.file_name); + expect(templates[1].id).toBe(template2.id); + expect(templates[1].name).toBe(template2.file_name); + } + }); + + it('テンプレートファイル一覧を取得できる(0件)', async () => { + const module = await makeTestingModule(source); + const service = module.get(TemplatesService); + // 第五階層のアカウント作成 + const { admin } = await makeTestAccount(source, { tier: 5 }); + const context = makeContext(admin.external_id); + + const templates = await service.getTemplates(context, admin.external_id); + + //実行結果を確認 + { + expect(templates.length).toBe(0); + } + }); + + it('テンプレートファイル一覧の取得に失敗した場合、エラーとなること', async () => { + const module = await makeTestingModule(source); + const service = module.get(TemplatesService); + // 第五階層のアカウント作成 + const { admin } = await makeTestAccount(source, { tier: 5 }); + const context = makeContext(admin.external_id); + + //DBアクセスに失敗するようにする + const typistGroupService = module.get( + TemplateFilesRepositoryService, + ); + typistGroupService.getTemplateFiles = jest + .fn() + .mockRejectedValue('DB failed'); + + try { + await service.getTemplates(context, admin.external_id); + } catch (e) { + if (e instanceof HttpException) { + expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR); + expect(e.getResponse()).toEqual(makeErrorResponse('E009999')); + } else { + fail(); + } + } + }); +}); diff --git a/dictation_server/src/features/templates/templates.service.ts b/dictation_server/src/features/templates/templates.service.ts index cf1d193..d2a88c4 100644 --- a/dictation_server/src/features/templates/templates.service.ts +++ b/dictation_server/src/features/templates/templates.service.ts @@ -1,7 +1,56 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'; +import { UsersRepositoryService } from '../../repositories/users/users.repository.service'; +import { TemplateFile } from './types/types'; +import { Context } from '../../common/log'; +import { makeErrorResponse } from '../../common/error/makeErrorResponse'; +import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service'; @Injectable() export class TemplatesService { private readonly logger = new Logger(TemplatesService.name); - constructor() {} + constructor( + private readonly usersRepository: UsersRepositoryService, + private readonly templateFilesRepository: TemplateFilesRepositoryService, + ) {} + + /** + * アカウント内のテンプレートファイルの一覧を取得する + * @param context + * @param externalId + * @returns templates + */ + async getTemplates( + context: Context, + externalId: string, + ): Promise { + this.logger.log( + `[IN] [${context.trackingId}] ${this.getTemplates.name} | params: { externalId: ${externalId} };`, + ); + + try { + const { account_id: accountId } = + await this.usersRepository.findUserByExternalId(externalId); + + const templateFileRecords = + await this.templateFilesRepository.getTemplateFiles(accountId); + + // DBから取得したテンプレートファイルのレコードをレスポンス用に整形する + const resTemplates = templateFileRecords.map((templateFile) => ({ + id: templateFile.id, + name: templateFile.file_name, + })); + + return resTemplates; + } catch (e) { + this.logger.error(`error=${e}`); + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } finally { + this.logger.log( + `[OUT] [${context.trackingId}] ${this.getTemplates.name}`, + ); + } + } } diff --git a/dictation_server/src/features/templates/test/utility.ts b/dictation_server/src/features/templates/test/utility.ts new file mode 100644 index 0000000..2643e88 --- /dev/null +++ b/dictation_server/src/features/templates/test/utility.ts @@ -0,0 +1,28 @@ +import { DataSource } from 'typeorm'; +import { TemplateFile } from '../../../repositories/template_files/entity/template_file.entity'; + +export const createTemplateFile = async ( + datasource: DataSource, + accountId: number, + name: string, + url: string, +): Promise => { + const { identifiers } = await datasource.getRepository(TemplateFile).insert({ + account_id: accountId, + file_name: name, + url: url, + created_by: 'test_runner', + created_at: new Date(), + updated_by: 'updater', + updated_at: new Date(), + }); + + const template = identifiers.pop() as TemplateFile; + const templateFile = await datasource.getRepository(TemplateFile).findOne({ + where: { + id: template.id, + }, + }); + + return templateFile; +}; 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 5c45d2f..41d6738 100644 --- a/dictation_server/src/repositories/template_files/entity/template_file.entity.ts +++ b/dictation_server/src/repositories/template_files/entity/template_file.entity.ts @@ -18,8 +18,6 @@ export class TemplateFile { url: string; @Column() file_name: string; - @Column({ nullable: true }) - deleted_at?: Date; @Column() created_by: string; @CreateDateColumn() diff --git a/dictation_server/src/repositories/template_files/template_files.repository.service.ts b/dictation_server/src/repositories/template_files/template_files.repository.service.ts index b35c191..ffe3b81 100644 --- a/dictation_server/src/repositories/template_files/template_files.repository.service.ts +++ b/dictation_server/src/repositories/template_files/template_files.repository.service.ts @@ -1,7 +1,25 @@ import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; +import { TemplateFile } from './entity/template_file.entity'; @Injectable() export class TemplateFilesRepositoryService { constructor(private dataSource: DataSource) {} + + /** + * アカウント内のテンプレートファイルの一覧を取得する + * @param accountId + * @returns template files + */ + async getTemplateFiles(accountId: number): Promise { + return await this.dataSource.transaction(async (entityManager) => { + const templateFilesRepo = entityManager.getRepository(TemplateFile); + + const templates = await templateFilesRepo.find({ + where: { account_id: accountId }, + }); + + return templates; + }); + } }