From 0bcb0b071bacd00c7b9b00a6ff8e1c17db6d9500 Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Wed, 26 Apr 2023 06:32:50 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=2086:=20IF=E5=AE=9F=E8=A3=85(?= =?UTF-8?q?=E3=82=B9=E3=83=86=E3=83=BC=E3=82=BF=E3=82=B9=E5=A4=89=E6=9B=B4?= =?UTF-8?q?=E8=A6=81=E6=B1=82API)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task1645: IF実装(ステータス変更要求API/初回音声ファイル情報)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/1645) - ステータス変更要求APIのIF実装 - 使用目的ごとにAPIを作成 - OpenAPI.json生成 ## レビューポイント - API仕様詳細では引数にファイルIDを渡すように記載されていたが、タスクIDを受け取るようにした。 - タスクIDで問題ないと思っていますが、認識あってますでしょうか? - 返却するエラーの種類は足りているか ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## 動作確認状況 - Swagger UIと生成したOpenAPI.jsonで確認 ## 補足 - 相談、参考資料などがあれば --- dictation_server/src/api/odms/openapi.json | 507 ++++++++++++------ dictation_server/src/app.module.ts | 14 +- .../src/features/task/task.controller.ts | 209 +++++++- .../src/features/task/types/types.ts | 49 +- 4 files changed, 552 insertions(+), 227 deletions(-) diff --git a/dictation_server/src/api/odms/openapi.json b/dictation_server/src/api/odms/openapi.json index 08bd350..16900de 100644 --- a/dictation_server/src/api/odms/openapi.json +++ b/dictation_server/src/api/odms/openapi.json @@ -158,9 +158,9 @@ "tags": ["accounts"] } }, - "/files/audio/upload-finished": { + "/task/checkout": { "post": { - "operationId": "createTask", + "operationId": "checkout", "summary": "", "parameters": [], "requestBody": { @@ -168,7 +168,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AudioUploadFinishedRequest" + "$ref": "#/components/schemas/ChangeStatusRequest" } } } @@ -179,13 +179,23 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AudioUploadFinishedResponse" + "$ref": "#/components/schemas/ChangeStatusResponse" } } } }, - "400": { - "description": "不正なパラメータ", + "401": { + "description": "認証エラー", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "指定したIDの音声ファイルが存在しない場合", "content": { "application/json": { "schema": { @@ -205,7 +215,337 @@ } } }, - "tags": ["files"] + "tags": ["task"], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/task/checkin": { + "post": { + "operationId": "checkin", + "summary": "", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangeStatusRequest" + } + } + } + }, + "responses": { + "200": { + "description": "成功時のレスポンス", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangeStatusResponse" + } + } + } + }, + "401": { + "description": "認証エラー", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "指定したIDの音声ファイルが存在しない場合", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "想定外のサーバーエラー", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": ["task"], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/task/cancel": { + "post": { + "operationId": "cancel", + "summary": "", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangeStatusRequest" + } + } + } + }, + "responses": { + "200": { + "description": "成功時のレスポンス", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangeStatusResponse" + } + } + } + }, + "401": { + "description": "認証エラー", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "指定したIDの音声ファイルが存在しない場合", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "想定外のサーバーエラー", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": ["task"], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/task/suspend": { + "post": { + "operationId": "suspend", + "summary": "", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangeStatusRequest" + } + } + } + }, + "responses": { + "200": { + "description": "成功時のレスポンス", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangeStatusResponse" + } + } + } + }, + "401": { + "description": "認証エラー", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "指定したIDの音声ファイルが存在しない場合", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "想定外のサーバーエラー", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": ["task"], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/task/send-back": { + "post": { + "operationId": "sendBack", + "summary": "", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangeStatusRequest" + } + } + } + }, + "responses": { + "200": { + "description": "成功時のレスポンス", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangeStatusResponse" + } + } + } + }, + "401": { + "description": "認証エラー", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "指定したIDの音声ファイルが存在しない場合", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "想定外のサーバーエラー", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": ["task"], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/task/backup": { + "post": { + "operationId": "backup", + "summary": "", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangeStatusRequest" + } + } + } + }, + "responses": { + "200": { + "description": "成功時のレスポンス", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangeStatusResponse" + } + } + } + }, + "401": { + "description": "認証エラー", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "指定したIDの音声ファイルが存在しない場合", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "想定外のサーバーエラー", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": ["task"], + "security": [ + { + "bearer": [] + } + ] } }, "/users/confirm": { @@ -463,61 +803,6 @@ ] } }, - "/files/audio/next": { - "get": { - "operationId": "getNextAudioFile", - "summary": "", - "parameters": [ - { - "name": "endedFileId", - "required": true, - "in": "query", - "description": "文字起こし完了したタスクの音声ファイルID", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "成功時のレスポンス", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AudioNextResponse" - } - } - } - }, - "401": { - "description": "認証エラー", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "想定外のサーバーエラー", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "tags": ["files"], - "security": [ - { - "bearer": [] - } - ] - } - }, "/notification/register": { "post": { "operationId": "register", @@ -852,95 +1137,19 @@ "type": "object", "properties": {} }, - "AudioUploadFinishedRequest": { + "ChangeStatusRequest": { "type": "object", "properties": { - "url": { + "audioFileId": { "type": "string", - "description": "アップロード先Blob Storage(ファイル名含む)" - }, - "authorId": { - "type": "string", - "description": "自分自身(ログイン認証)したAuthorID" - }, - "fileName": { - "type": "string", - "description": "音声ファイル名" - }, - "duration": { - "type": "string", - "description": "音声ファイルの録音時間(yyyy-mm-ddThh:mm:ss.sss)" - }, - "createdDate": { - "type": "string", - "description": "音声ファイルの録音作成日時(開始日時)(yyyy-mm-ddThh:mm:ss.sss)" - }, - "completedDate": { - "type": "string", - "description": "音声ファイルの録音作成終了日時(yyyy-mm-ddThh:mm:ss.sss)" - }, - "uploadedDate": { - "type": "string", - "description": "音声ファイルのアップロード日時(yyyy-mm-ddThh:mm:ss.sss)" - }, - "fileSize": { - "type": "string" - }, - "priority": { - "type": "string", - "description": "優先度 \"00\":Normal / \"01\":High" - }, - "audioFormat": { - "type": "string", - "description": "録音形式: DSS/DS2(SP)/DS2(QP)" - }, - "comment": { - "type": "string" - }, - "workType": { - "type": "string" - }, - "optionItemLabel": { - "type": "string", - "minLength": 1, - "maxLength": 16 - }, - "optionItemValue": { - "type": "string", - "minLength": 1, - "maxLength": 20 - }, - "isEncrypted": { - "type": "boolean" + "description": "OMDS Cloud上の音声ファイルID" } }, - "required": [ - "url", - "authorId", - "fileName", - "duration", - "createdDate", - "completedDate", - "uploadedDate", - "fileSize", - "priority", - "audioFormat", - "comment", - "workType", - "optionItemLabel", - "optionItemValue", - "isEncrypted" - ] + "required": ["audioFileId"] }, - "AudioUploadFinishedResponse": { + "ChangeStatusResponse": { "type": "object", - "properties": { - "jobNumber": { - "type": "string", - "description": "8桁固定の数字" - } - }, - "required": ["jobNumber"] + "properties": {} }, "ConfirmRequest": { "type": "object", @@ -1149,16 +1358,6 @@ "prompt" ] }, - "AudioNextResponse": { - "type": "object", - "properties": { - "nextFileId": { - "type": "string", - "description": "OMDS Cloud上の次の音声ファイルID" - } - }, - "required": ["nextFileId"] - }, "RegisterRequest": { "type": "object", "properties": { diff --git a/dictation_server/src/app.module.ts b/dictation_server/src/app.module.ts index 5e4be5c..1355476 100644 --- a/dictation_server/src/app.module.ts +++ b/dictation_server/src/app.module.ts @@ -23,12 +23,9 @@ import { NotificationhubModule } from './gateways/notificationhub/notificationhu import { NotificationhubService } from './gateways/notificationhub/notificationhub.service'; import { NotificationModule } from './features/notification/notification.module'; import { BlobModule } from './features/blob/blob.module'; -import { FilesModule } from './features/files/files.module'; -import { FilesController } from './features/files/files.controller'; -import { FilesService } from './features/files/files.service'; -import { TaskController } from './features/task/task.controller'; import { TaskService } from './features/task/task.service'; import { TaskModule } from './features/task/task.module'; +import { TaskController } from './features/task/task.controller'; @Module({ imports: [ @@ -44,8 +41,7 @@ import { TaskModule } from './features/task/task.module'; AdB2cModule, AccountsModule, TaskModule, - UsersModule, - FilesModule, + UsersModule, SendGridModule, AccountsRepositoryModule, UsersRepositoryModule, @@ -66,14 +62,14 @@ import { TaskModule } from './features/task/task.module'; NotificationModule, NotificationhubModule, BlobModule, + TaskModule, ], controllers: [ HealthController, AuthController, AccountsController, TaskController, - UsersController, - FilesController, + UsersController, ], providers: [ AuthService, @@ -81,7 +77,7 @@ import { TaskModule } from './features/task/task.module'; TaskService, UsersService, NotificationhubService, - FilesService, + TaskService, ], }) export class AppModule { diff --git a/dictation_server/src/features/task/task.controller.ts b/dictation_server/src/features/task/task.controller.ts index 479ee40..19e2aa4 100644 --- a/dictation_server/src/features/task/task.controller.ts +++ b/dictation_server/src/features/task/task.controller.ts @@ -1,25 +1,32 @@ -import { Body, Controller, HttpStatus, Post } from '@nestjs/common'; -import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { ErrorResponse } from '../../common/error/types/types'; -import { TaskService } from './task.service'; +import { Body, Controller, Headers, HttpStatus, Post } from '@nestjs/common'; import { - AudioUploadFinishedRequest, - AudioUploadFinishedResponse, -} from './types/types'; + ApiBearerAuth, + ApiOperation, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { TaskService } from './task.service'; +import { ChangeStatusRequest, ChangeStatusResponse } from './types/types'; +import { ErrorResponse } from '../../common/error/types/types'; -@ApiTags('files') -@Controller('files') +@ApiTags('task') +@Controller('task') export class TaskController { constructor(private readonly taskService: TaskService) {} - + @Post('checkout') @ApiResponse({ status: HttpStatus.OK, - type: AudioUploadFinishedResponse, + type: ChangeStatusResponse, description: '成功時のレスポンス', }) @ApiResponse({ - status: HttpStatus.BAD_REQUEST, - description: '不正なパラメータ', + status: HttpStatus.NOT_FOUND, + description: '指定したIDの音声ファイルが存在しない場合', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.UNAUTHORIZED, + description: '認証エラー', type: ErrorResponse, }) @ApiResponse({ @@ -27,12 +34,174 @@ export class TaskController { description: '想定外のサーバーエラー', type: ErrorResponse, }) - @ApiOperation({ operationId: 'createTask' }) - @Post('audio/upload-finished') - async uploadFinished( - @Body() body: AudioUploadFinishedRequest, - ): Promise { - console.log(body); - return { jobNumber: '00000001' }; + @ApiOperation({ operationId: 'checkout' }) + @ApiBearerAuth() + async checkout( + @Headers() headers, + @Body() body: ChangeStatusRequest, + ): Promise { + const {} = body; + + return {}; + } + + @Post('checkin') + @ApiResponse({ + status: HttpStatus.OK, + type: ChangeStatusResponse, + description: '成功時のレスポンス', + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: '指定したIDの音声ファイルが存在しない場合', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.UNAUTHORIZED, + description: '認証エラー', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: '想定外のサーバーエラー', + type: ErrorResponse, + }) + @ApiOperation({ operationId: 'checkin' }) + @ApiBearerAuth() + async checkin( + @Headers() headers, + @Body() body: ChangeStatusRequest, + ): Promise { + const {} = body; + + return {}; + } + + @Post('cancel') + @ApiResponse({ + status: HttpStatus.OK, + type: ChangeStatusResponse, + description: '成功時のレスポンス', + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: '指定したIDの音声ファイルが存在しない場合', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.UNAUTHORIZED, + description: '認証エラー', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: '想定外のサーバーエラー', + type: ErrorResponse, + }) + @ApiOperation({ operationId: 'cancel' }) + @ApiBearerAuth() + async cancel( + @Headers() headers, + @Body() body: ChangeStatusRequest, + ): Promise { + const {} = body; + + return {}; + } + + @Post('suspend') + @ApiResponse({ + status: HttpStatus.OK, + type: ChangeStatusResponse, + description: '成功時のレスポンス', + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: '指定したIDの音声ファイルが存在しない場合', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.UNAUTHORIZED, + description: '認証エラー', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: '想定外のサーバーエラー', + type: ErrorResponse, + }) + @ApiOperation({ operationId: 'suspend' }) + @ApiBearerAuth() + async suspend( + @Headers() headers, + @Body() body: ChangeStatusRequest, + ): Promise { + const {} = body; + + return {}; + } + + @Post('send-back') + @ApiResponse({ + status: HttpStatus.OK, + type: ChangeStatusResponse, + description: '成功時のレスポンス', + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: '指定したIDの音声ファイルが存在しない場合', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.UNAUTHORIZED, + description: '認証エラー', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: '想定外のサーバーエラー', + type: ErrorResponse, + }) + @ApiOperation({ operationId: 'sendBack' }) + @ApiBearerAuth() + async sendBack( + @Headers() headers, + @Body() body: ChangeStatusRequest, + ): Promise { + const {} = body; + + return {}; + } + // TODO 操作としてarchiveの方が適切かもしれない + @Post('backup') + @ApiResponse({ + status: HttpStatus.OK, + type: ChangeStatusResponse, + description: '成功時のレスポンス', + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: '指定したIDの音声ファイルが存在しない場合', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.UNAUTHORIZED, + description: '認証エラー', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: '想定外のサーバーエラー', + type: ErrorResponse, + }) + @ApiOperation({ operationId: 'backup' }) + @ApiBearerAuth() + async backup( + @Headers() headers, + @Body() body: ChangeStatusRequest, + ): Promise { + const {} = body; + + return {}; } } diff --git a/dictation_server/src/features/task/types/types.ts b/dictation_server/src/features/task/types/types.ts index 79ec3af..ab60ad5 100644 --- a/dictation_server/src/features/task/types/types.ts +++ b/dictation_server/src/features/task/types/types.ts @@ -1,48 +1,9 @@ import { ApiProperty } from '@nestjs/swagger'; -export class AudioUploadFinishedRequest { - @ApiProperty({ description: 'アップロード先Blob Storage(ファイル名含む)' }) - url: string; - @ApiProperty({ description: '自分自身(ログイン認証)したAuthorID' }) - authorId: string; - @ApiProperty({ description: '音声ファイル名' }) - fileName: string; - @ApiProperty({ - description: '音声ファイルの録音時間(yyyy-mm-ddThh:mm:ss.sss)', - }) - duration: string; - @ApiProperty({ - description: - '音声ファイルの録音作成日時(開始日時)(yyyy-mm-ddThh:mm:ss.sss)', - }) - createdDate: string; - @ApiProperty({ - description: '音声ファイルの録音作成終了日時(yyyy-mm-ddThh:mm:ss.sss)', - }) - completedDate: string; - @ApiProperty({ - description: '音声ファイルのアップロード日時(yyyy-mm-ddThh:mm:ss.sss)', - }) - uploadedDate: string; - @ApiProperty() - fileSize: string; - @ApiProperty({ description: '優先度 "00":Normal / "01":High' }) - priority: string; - @ApiProperty({ description: '録音形式: DSS/DS2(SP)/DS2(QP)' }) - audioFormat: string; - @ApiProperty() - comment: string; - @ApiProperty() - workType: string; - @ApiProperty({ minLength: 1, maxLength: 16 }) - optionItemLabel: string; - @ApiProperty({ minLength: 1, maxLength: 20 }) - optionItemValue: string; - @ApiProperty() - isEncrypted: boolean; + +export class ChangeStatusRequest { + @ApiProperty({ description: 'OMDS Cloud上の音声ファイルID' }) + audioFileId: string; } -export class AudioUploadFinishedResponse { - @ApiProperty({ description: '8桁固定の数字' }) - jobNumber: string; -} +export class ChangeStatusResponse {}