Merged PR 123: API実装(I/F実装)

## 概要
[Task1836: API実装(I/F実装)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/1836)

- タスクのソート条件更新APIのI/Fを実装
- タスク一覧APIのI/Fを修正
- TODO/XXXですぐ対処可能、または単純に消し忘れているものを対処

## レビューポイント
- API I/Fの修正は妥当な内容であるか
  - 特にfilterはカンマ区切りでいいか、指定したものを除外という形式でいいか等
- TODO/XXXの対処は妥当な対処であるか

## 動作確認状況
- ローカルでswagger表示されることを確認
This commit is contained in:
湯本 開 2023-06-02 06:12:35 +00:00
parent e4f84f78ba
commit 1ced5ef66d
13 changed files with 496 additions and 627 deletions

View File

@ -490,6 +490,25 @@ export interface SignupRequest {
*/
'notification': boolean;
}
/**
*
* @export
* @interface SortCriteriaRequest
*/
export interface SortCriteriaRequest {
/**
* ASC/DESC
* @type {string}
* @memberof SortCriteriaRequest
*/
'direction': string;
/**
* JOB_NUMBER/STATUS/ENCRYPTION/AUTHOR_ID/FILE_NAME/FILE_LENGTH/FILE_SIZE/RECORDING_STARTED_DATE/RECORDING_FINISHED_DATE/UPLOAD_DATE/TRANSCRIPTION_STARTED_DATE/TRANSCRIPTION_FINISHED_DATE
* @type {string}
* @memberof SortCriteriaRequest
*/
'paramName': string;
}
/**
*
* @export
@ -1284,11 +1303,14 @@ export const FilesApiAxiosParamCreator = function (configuration?: Configuration
/**
*
* @summary
* @param {string} authorization
* @param {AudioUploadFinishedRequest} audioUploadFinishedRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
uploadFinished: async (audioUploadFinishedRequest: AudioUploadFinishedRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
uploadFinished: async (authorization: string, audioUploadFinishedRequest: AudioUploadFinishedRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'authorization' is not null or undefined
assertParamExists('uploadFinished', 'authorization', authorization)
// verify required parameter 'audioUploadFinishedRequest' is not null or undefined
assertParamExists('uploadFinished', 'audioUploadFinishedRequest', audioUploadFinishedRequest)
const localVarPath = `/files/audio/upload-finished`;
@ -1307,6 +1329,10 @@ export const FilesApiAxiosParamCreator = function (configuration?: Configuration
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
if (authorization != null) {
localVarHeaderParameter['authorization'] = String(authorization);
}
localVarHeaderParameter['Content-Type'] = 'application/json';
@ -1324,10 +1350,13 @@ export const FilesApiAxiosParamCreator = function (configuration?: Configuration
/**
* Blob Storage上の音声ファイルのアップロード先アクセスURLを取得します
* @summary
* @param {string} authorization
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
uploadLocation: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
uploadLocation: async (authorization: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'authorization' is not null or undefined
assertParamExists('uploadLocation', 'authorization', authorization)
const localVarPath = `/files/audio/upload-location`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
@ -1344,6 +1373,10 @@ export const FilesApiAxiosParamCreator = function (configuration?: Configuration
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
if (authorization != null) {
localVarHeaderParameter['authorization'] = String(authorization);
}
setSearchParams(localVarUrlObj, localVarQueryParameter);
@ -1390,22 +1423,24 @@ export const FilesApiFp = function(configuration?: Configuration) {
/**
*
* @summary
* @param {string} authorization
* @param {AudioUploadFinishedRequest} audioUploadFinishedRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async uploadFinished(audioUploadFinishedRequest: AudioUploadFinishedRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AudioUploadFinishedResponse>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.uploadFinished(audioUploadFinishedRequest, options);
async uploadFinished(authorization: string, audioUploadFinishedRequest: AudioUploadFinishedRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AudioUploadFinishedResponse>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.uploadFinished(authorization, audioUploadFinishedRequest, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
* Blob Storage上の音声ファイルのアップロード先アクセスURLを取得します
* @summary
* @param {string} authorization
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async uploadLocation(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AudioUploadLocationResponse>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.uploadLocation(options);
async uploadLocation(authorization: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AudioUploadLocationResponse>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.uploadLocation(authorization, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
}
@ -1441,21 +1476,23 @@ export const FilesApiFactory = function (configuration?: Configuration, basePath
/**
*
* @summary
* @param {string} authorization
* @param {AudioUploadFinishedRequest} audioUploadFinishedRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
uploadFinished(audioUploadFinishedRequest: AudioUploadFinishedRequest, options?: any): AxiosPromise<AudioUploadFinishedResponse> {
return localVarFp.uploadFinished(audioUploadFinishedRequest, options).then((request) => request(axios, basePath));
uploadFinished(authorization: string, audioUploadFinishedRequest: AudioUploadFinishedRequest, options?: any): AxiosPromise<AudioUploadFinishedResponse> {
return localVarFp.uploadFinished(authorization, audioUploadFinishedRequest, options).then((request) => request(axios, basePath));
},
/**
* Blob Storage上の音声ファイルのアップロード先アクセスURLを取得します
* @summary
* @param {string} authorization
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
uploadLocation(options?: any): AxiosPromise<AudioUploadLocationResponse> {
return localVarFp.uploadLocation(options).then((request) => request(axios, basePath));
uploadLocation(authorization: string, options?: any): AxiosPromise<AudioUploadLocationResponse> {
return localVarFp.uploadLocation(authorization, options).then((request) => request(axios, basePath));
},
};
};
@ -1494,24 +1531,26 @@ export class FilesApi extends BaseAPI {
/**
*
* @summary
* @param {string} authorization
* @param {AudioUploadFinishedRequest} audioUploadFinishedRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof FilesApi
*/
public uploadFinished(audioUploadFinishedRequest: AudioUploadFinishedRequest, options?: AxiosRequestConfig) {
return FilesApiFp(this.configuration).uploadFinished(audioUploadFinishedRequest, options).then((request) => request(this.axios, this.basePath));
public uploadFinished(authorization: string, audioUploadFinishedRequest: AudioUploadFinishedRequest, options?: AxiosRequestConfig) {
return FilesApiFp(this.configuration).uploadFinished(authorization, audioUploadFinishedRequest, options).then((request) => request(this.axios, this.basePath));
}
/**
* Blob Storage上の音声ファイルのアップロード先アクセスURLを取得します
* @summary
* @param {string} authorization
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof FilesApi
*/
public uploadLocation(options?: AxiosRequestConfig) {
return FilesApiFp(this.configuration).uploadLocation(options).then((request) => request(this.axios, this.basePath));
public uploadLocation(authorization: string, options?: AxiosRequestConfig) {
return FilesApiFp(this.configuration).uploadLocation(authorization, options).then((request) => request(this.axios, this.basePath));
}
}
@ -1942,10 +1981,11 @@ export const TasksApiAxiosParamCreator = function (configuration?: Configuration
* @summary
* @param {number} [limit]
* @param {number} [offset]  
* @param {string} [status] (,)許容するステータスの値は次の通り: Uploaded / Pending / InProgress / Finished / Backup
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getTasks: async (limit?: number, offset?: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
getTasks: async (limit?: number, offset?: number, status?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/tasks`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
@ -1970,6 +2010,10 @@ export const TasksApiAxiosParamCreator = function (configuration?: Configuration
localVarQueryParameter['offset'] = offset;
}
if (status !== undefined) {
localVarQueryParameter['status'] = status;
}
setSearchParams(localVarUrlObj, localVarQueryParameter);
@ -2127,11 +2171,12 @@ export const TasksApiFp = function(configuration?: Configuration) {
* @summary
* @param {number} [limit]
* @param {number} [offset]  
* @param {string} [status] (,)許容するステータスの値は次の通り: Uploaded / Pending / InProgress / Finished / Backup
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getTasks(limit?: number, offset?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<TasksResponse>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getTasks(limit, offset, options);
async getTasks(limit?: number, offset?: number, status?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<TasksResponse>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getTasks(limit, offset, status, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
@ -2221,11 +2266,12 @@ export const TasksApiFactory = function (configuration?: Configuration, basePath
* @summary
* @param {number} [limit]
* @param {number} [offset]  
* @param {string} [status] (,)許容するステータスの値は次の通り: Uploaded / Pending / InProgress / Finished / Backup
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getTasks(limit?: number, offset?: number, options?: any): AxiosPromise<TasksResponse> {
return localVarFp.getTasks(limit, offset, options).then((request) => request(axios, basePath));
getTasks(limit?: number, offset?: number, status?: string, options?: any): AxiosPromise<TasksResponse> {
return localVarFp.getTasks(limit, offset, status, options).then((request) => request(axios, basePath));
},
/**
* Pendingにします
@ -2322,12 +2368,13 @@ export class TasksApi extends BaseAPI {
* @summary
* @param {number} [limit]
* @param {number} [offset]  
* @param {string} [status] (,)許容するステータスの値は次の通り: Uploaded / Pending / InProgress / Finished / Backup
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof TasksApi
*/
public getTasks(limit?: number, offset?: number, options?: AxiosRequestConfig) {
return TasksApiFp(this.configuration).getTasks(limit, offset, options).then((request) => request(this.axios, this.basePath));
public getTasks(limit?: number, offset?: number, status?: string, options?: AxiosRequestConfig) {
return TasksApiFp(this.configuration).getTasks(limit, offset, status, options).then((request) => request(this.axios, this.basePath));
}
/**
@ -2537,6 +2584,46 @@ export const UsersApiAxiosParamCreator = function (configuration?: Configuration
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(signupRequest, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @summary
* @param {SortCriteriaRequest} sortCriteriaRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
updateSortCcriteria: async (sortCriteriaRequest: SortCriteriaRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'sortCriteriaRequest' is not null or undefined
assertParamExists('updateSortCcriteria', 'sortCriteriaRequest', sortCriteriaRequest)
const localVarPath = `/users/sort-criteria`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
localVarHeaderParameter['Content-Type'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(sortCriteriaRequest, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
@ -2605,6 +2692,17 @@ export const UsersApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.signup(signupRequest, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @summary
* @param {SortCriteriaRequest} sortCriteriaRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async updateSortCcriteria(sortCriteriaRequest: SortCriteriaRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.updateSortCcriteria(sortCriteriaRequest, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
}
};
@ -2663,6 +2761,16 @@ export const UsersApiFactory = function (configuration?: Configuration, basePath
signup(signupRequest: SignupRequest, options?: any): AxiosPromise<object> {
return localVarFp.signup(signupRequest, options).then((request) => request(axios, basePath));
},
/**
*
* @summary
* @param {SortCriteriaRequest} sortCriteriaRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
updateSortCcriteria(sortCriteriaRequest: SortCriteriaRequest, options?: any): AxiosPromise<object> {
return localVarFp.updateSortCcriteria(sortCriteriaRequest, options).then((request) => request(axios, basePath));
},
};
};
@ -2730,6 +2838,18 @@ export class UsersApi extends BaseAPI {
public signup(signupRequest: SignupRequest, options?: AxiosRequestConfig) {
return UsersApiFp(this.configuration).signup(signupRequest, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary
* @param {SortCriteriaRequest} sortCriteriaRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof UsersApi
*/
public updateSortCcriteria(sortCriteriaRequest: SortCriteriaRequest, options?: AxiosRequestConfig) {
return UsersApiFp(this.configuration).updateSortCcriteria(sortCriteriaRequest, options).then((request) => request(this.axios, this.basePath));
}
}

View File

@ -23,7 +23,7 @@
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"og": "openapi-generator-cli",
"swgbundle:sentinel": "swagger-cli bundle -o openapi/build/bundle.yml -t yaml openapi/sentinel_ems/sentinel_ems.yml"
"openapi-format": "cat \"src/api/odms/openapi.json\" | jq -c . > \"src/api/odms/openapi.json\" && prettier --write \"src/api/odms/*.json\""
},
"dependencies": {
"@azure/identity": "^3.1.3",

File diff suppressed because it is too large Load Diff

View File

@ -3,14 +3,9 @@
* @param {string[]}
* @return {boolean}
*/
// XXX: deprecated 削除予定
export const confirmPermission = (authority: string): boolean => {
console.log(authority);
return true;
// TODO 将来的にscopeの内容に応じた処理を入れることになる
//  if (authority.startsWith('hogehoge')) {
//    return true;
//  } else {
//    return false;
//  }
};

View File

@ -1,10 +1,22 @@
export type RefreshToken = {
/**
*
*/
userId: string;
/**
* Roleを表現する文字列(ex. "author admin")
*/
role: string;
};
export type AccessToken = {
/**
*
*/
userId: string;
/**
* Roleを表現する文字列(ex. "author admin")
*/
role: string;
};

View File

@ -130,3 +130,24 @@ export const TASK_STATUS = {
FINISHED: 'Finished',
BACKUP: 'Backup',
} as const;
/**
*
*/
export const TASK_LIST_SORTABLE_ATTRIBUTES = [
'JOB_NUMBER',
'STATUS',
'ENCRYPTION',
'AUTHOR_ID',
'FILE_NAME',
'FILE_LENGTH',
'FILE_SIZE',
'RECORDING_STARTED_DATE',
'RECORDING_FINISHED_DATE',
'UPLOAD_DATE',
'TRANSCRIPTION_STARTED_DATE',
'TRANSCRIPTION_FINISHED_DATE',
] as const;
export type TaskListSortableAttribute =
(typeof TASK_LIST_SORTABLE_ATTRIBUTES)[number];

View File

@ -1,4 +1,4 @@
import { Body, Controller, HttpStatus, Post } from '@nestjs/common';
import { Body, Controller, HttpStatus, Post, UseGuards } from '@nestjs/common';
import {
ApiResponse,
ApiOperation,
@ -8,6 +8,7 @@ import {
import { ErrorResponse } from '../../common/error/types/types';
import { RegisterRequest, RegisterResponse } from './types/types';
import { NotificationService } from './notification.service';
import { AuthGuard } from 'src/common/guards/auth/authguards';
@ApiTags('notification')
@Controller('notification')
@ -36,9 +37,9 @@ export class NotificationController {
type: ErrorResponse,
})
@ApiOperation({ operationId: 'register' })
@UseGuards(AuthGuard)
async register(@Body() body: RegisterRequest): Promise<RegisterResponse> {
const { handler, pns } = body;
// XXX 登録処理の前にアクセストークンの認証を行う
await this.notificationService.register(pns, handler);
return {};
}

View File

@ -6,7 +6,6 @@ import {
Param,
Post,
Query,
UseGuards,
} from '@nestjs/common';
import {
ApiResponse,
@ -24,7 +23,6 @@ import {
TasksRequest,
TasksResponse,
} from './types/types';
import { AuthGuard } from '../../common/guards/auth/authguards';
@ApiTags('tasks')
@Controller('tasks')
@ -317,7 +315,7 @@ export class TasksController {
return {};
}
// TODO 操作としてarchiveの方が適切かもしれない
@Post(':audioFileId/backup')
@ApiResponse({
status: HttpStatus.OK,

View File

@ -1,5 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
import { AudioOptionItem } from '../../../features/files/types/types';
import { IsInt, IsOptional, Min } from 'class-validator';
export class TasksRequest {
@ApiProperty({
@ -7,14 +8,31 @@ export class TasksRequest {
default: 200,
description: 'タスクの取得件数(指定しない場合はデフォルト値)',
})
@IsInt()
@Min(0)
@IsOptional()
limit: number;
@ApiProperty({
required: false,
default: 0,
description:
'オフセット(何件目から取得するか 設定しない場合はデフォルト値)',
})
@IsInt()
@Min(0)
@IsOptional()
offset: number;
@ApiProperty({
required: false,
description:
'取得対象とするタスクのステータス。カンマ(,)区切りで複数指定可能。設定されない場合はすべてのステータスを取得対象とする。許容するステータスの値は次の通り: Uploaded / Pending / InProgress / Finished / Backup',
example: 'Uploaded,Pending,InProgress',
})
@IsOptional()
// TODO: 入力チェックを行うデコレータを追加する。@Matches or カスタムデコレータで実装想定。statusの値は先頭大文字であることまで一致しなくていいはず実装時レビューにて要確認
status?: string;
}
export class Typist {

View File

@ -1,5 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsIn } from 'class-validator';
import { TASK_LIST_SORTABLE_ATTRIBUTES } from '../../../constants';
import { USER_ROLES } from '../../../constants';
export class ConfirmRequest {
@ -132,3 +133,15 @@ export class GetRelationsResponse {
})
prompt: boolean;
}
export class SortCriteriaRequest {
@ApiProperty({ description: 'ASC/DESC' })
@IsIn(['ASC', 'DESC'], { message: 'invalid direction' })
direction: string;
@ApiProperty({ description: `${TASK_LIST_SORTABLE_ATTRIBUTES.join('/')}` })
@IsIn(TASK_LIST_SORTABLE_ATTRIBUTES, { message: 'invalid attributes' })
paramName: string;
}
export class SortCriteriaResponse {}

View File

@ -29,6 +29,8 @@ import {
GetUsersResponse,
SignupRequest,
SignupResponse,
SortCriteriaRequest,
SortCriteriaResponse,
} from './types/types';
import { UsersService } from './users.service';
import { AuthGuard } from '../../common/guards/auth/authguards';
@ -271,4 +273,33 @@ export class UsersController {
prompt: true,
};
}
@ApiResponse({
status: HttpStatus.OK,
type: SortCriteriaResponse,
description: '成功時のレスポンス',
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
description: '認証エラー',
type: ErrorResponse,
})
@ApiResponse({
status: HttpStatus.INTERNAL_SERVER_ERROR,
description: '想定外のサーバーエラー',
type: ErrorResponse,
})
@ApiOperation({
operationId: 'updateSortCcriteria',
description: 'ログインしているユーザーのタスクソート条件を更新します',
})
@ApiBearerAuth()
@UseGuards(AuthGuard)
@Post('sort-criteria')
async updateSortCriteria(
@Body() body: SortCriteriaRequest,
): Promise<SortCriteriaResponse> {
console.log(body);
return {};
}
}

View File

@ -95,8 +95,6 @@ export class UsersService {
authorId?: string | undefined,
groupID?: number | undefined,
): Promise<void> {
//アクセストークンからユーザーIDを取得する
// TODO アクセストークンの中身が具体的に確定したら、型変換を取り払う必要があるかも
this.logger.log(`[IN] ${this.createUser.name}`);
//DBよりアクセス者の所属するアカウントIDを取得する

View File

@ -49,8 +49,6 @@ async function bootstrap() {
SwaggerModule.setup('api', app, document);
}
// TODO:検証のためポートを固定 後で直す
// await app.listen(process.env.PORT || 80);
await app.listen(process.env.PORT || 80);
}
bootstrap();