Merged PR 86: IF実装(ステータス変更要求API)

## 概要
[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で確認

## 補足
- 相談、参考資料などがあれば
This commit is contained in:
saito.k 2023-04-26 06:32:50 +00:00
parent 476c810cc3
commit 0bcb0b071b
4 changed files with 552 additions and 227 deletions

View File

@ -158,9 +158,9 @@
"tags": ["accounts"] "tags": ["accounts"]
} }
}, },
"/files/audio/upload-finished": { "/task/checkout": {
"post": { "post": {
"operationId": "createTask", "operationId": "checkout",
"summary": "", "summary": "",
"parameters": [], "parameters": [],
"requestBody": { "requestBody": {
@ -168,7 +168,7 @@
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/AudioUploadFinishedRequest" "$ref": "#/components/schemas/ChangeStatusRequest"
} }
} }
} }
@ -179,13 +179,23 @@
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/AudioUploadFinishedResponse" "$ref": "#/components/schemas/ChangeStatusResponse"
} }
} }
} }
}, },
"400": { "401": {
"description": "不正なパラメータ", "description": "認証エラー",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "指定したIDの音声ファイルが存在しない場合",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "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": { "/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": { "/notification/register": {
"post": { "post": {
"operationId": "register", "operationId": "register",
@ -852,95 +1137,19 @@
"type": "object", "type": "object",
"properties": {} "properties": {}
}, },
"AudioUploadFinishedRequest": { "ChangeStatusRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
"url": { "audioFileId": {
"type": "string", "type": "string",
"description": "アップロード先Blob Storage(ファイル名含む)" "description": "OMDS Cloud上の音声ファイルID"
},
"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"
} }
}, },
"required": [ "required": ["audioFileId"]
"url",
"authorId",
"fileName",
"duration",
"createdDate",
"completedDate",
"uploadedDate",
"fileSize",
"priority",
"audioFormat",
"comment",
"workType",
"optionItemLabel",
"optionItemValue",
"isEncrypted"
]
}, },
"AudioUploadFinishedResponse": { "ChangeStatusResponse": {
"type": "object", "type": "object",
"properties": { "properties": {}
"jobNumber": {
"type": "string",
"description": "8桁固定の数字"
}
},
"required": ["jobNumber"]
}, },
"ConfirmRequest": { "ConfirmRequest": {
"type": "object", "type": "object",
@ -1149,16 +1358,6 @@
"prompt" "prompt"
] ]
}, },
"AudioNextResponse": {
"type": "object",
"properties": {
"nextFileId": {
"type": "string",
"description": "OMDS Cloud上の次の音声ファイルID"
}
},
"required": ["nextFileId"]
},
"RegisterRequest": { "RegisterRequest": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -23,12 +23,9 @@ import { NotificationhubModule } from './gateways/notificationhub/notificationhu
import { NotificationhubService } from './gateways/notificationhub/notificationhub.service'; import { NotificationhubService } from './gateways/notificationhub/notificationhub.service';
import { NotificationModule } from './features/notification/notification.module'; import { NotificationModule } from './features/notification/notification.module';
import { BlobModule } from './features/blob/blob.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 { TaskService } from './features/task/task.service';
import { TaskModule } from './features/task/task.module'; import { TaskModule } from './features/task/task.module';
import { TaskController } from './features/task/task.controller';
@Module({ @Module({
imports: [ imports: [
@ -45,7 +42,6 @@ import { TaskModule } from './features/task/task.module';
AccountsModule, AccountsModule,
TaskModule, TaskModule,
UsersModule, UsersModule,
FilesModule,
SendGridModule, SendGridModule,
AccountsRepositoryModule, AccountsRepositoryModule,
UsersRepositoryModule, UsersRepositoryModule,
@ -66,6 +62,7 @@ import { TaskModule } from './features/task/task.module';
NotificationModule, NotificationModule,
NotificationhubModule, NotificationhubModule,
BlobModule, BlobModule,
TaskModule,
], ],
controllers: [ controllers: [
HealthController, HealthController,
@ -73,7 +70,6 @@ import { TaskModule } from './features/task/task.module';
AccountsController, AccountsController,
TaskController, TaskController,
UsersController, UsersController,
FilesController,
], ],
providers: [ providers: [
AuthService, AuthService,
@ -81,7 +77,7 @@ import { TaskModule } from './features/task/task.module';
TaskService, TaskService,
UsersService, UsersService,
NotificationhubService, NotificationhubService,
FilesService, TaskService,
], ],
}) })
export class AppModule { export class AppModule {

View File

@ -1,25 +1,32 @@
import { Body, Controller, HttpStatus, Post } from '@nestjs/common'; import { Body, Controller, Headers, 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 { import {
AudioUploadFinishedRequest, ApiBearerAuth,
AudioUploadFinishedResponse, ApiOperation,
} from './types/types'; 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') @ApiTags('task')
@Controller('files') @Controller('task')
export class TaskController { export class TaskController {
constructor(private readonly taskService: TaskService) {} constructor(private readonly taskService: TaskService) {}
@Post('checkout')
@ApiResponse({ @ApiResponse({
status: HttpStatus.OK, status: HttpStatus.OK,
type: AudioUploadFinishedResponse, type: ChangeStatusResponse,
description: '成功時のレスポンス', description: '成功時のレスポンス',
}) })
@ApiResponse({ @ApiResponse({
status: HttpStatus.BAD_REQUEST, status: HttpStatus.NOT_FOUND,
description: '不正なパラメータ', description: '指定したIDの音声ファイルが存在しない場合',
type: ErrorResponse,
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
description: '認証エラー',
type: ErrorResponse, type: ErrorResponse,
}) })
@ApiResponse({ @ApiResponse({
@ -27,12 +34,174 @@ export class TaskController {
description: '想定外のサーバーエラー', description: '想定外のサーバーエラー',
type: ErrorResponse, type: ErrorResponse,
}) })
@ApiOperation({ operationId: 'createTask' }) @ApiOperation({ operationId: 'checkout' })
@Post('audio/upload-finished') @ApiBearerAuth()
async uploadFinished( async checkout(
@Body() body: AudioUploadFinishedRequest, @Headers() headers,
): Promise<AudioUploadFinishedResponse> { @Body() body: ChangeStatusRequest,
console.log(body); ): Promise<ChangeStatusResponse> {
return { jobNumber: '00000001' }; 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<ChangeStatusResponse> {
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<ChangeStatusResponse> {
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<ChangeStatusResponse> {
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<ChangeStatusResponse> {
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<ChangeStatusResponse> {
const {} = body;
return {};
} }
} }

View File

@ -1,48 +1,9 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
export class AudioUploadFinishedRequest {
@ApiProperty({ description: 'アップロード先Blob Storage(ファイル名含む)' }) export class ChangeStatusRequest {
url: string; @ApiProperty({ description: 'OMDS Cloud上の音声ファイルID' })
@ApiProperty({ description: '自分自身(ログイン認証)したAuthorID' }) audioFileId: string;
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 AudioUploadFinishedResponse { export class ChangeStatusResponse {}
@ApiProperty({ description: '8桁固定の数字' })
jobNumber: string;
}