Merged PR 418: API IF実装

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

- テンプレートファイル周りで以下のAPIIFを実装し、OpenAPIを更新しました。
  - テンプレートファイル一覧取得API
  - テンプレートファイルアップロード先取得API
  - テンプレートファイルアップロード完了API

## レビューポイント
- 各APIのパスは適切か
- パラメータは適切か

## UIの変更
- なし

## 動作確認状況
- ローカルで確認
This commit is contained in:
makabe.t 2023-09-20 01:41:14 +00:00
parent 41e4fbb8de
commit cec740f65e
9 changed files with 396 additions and 1 deletions

View File

@ -1849,6 +1849,100 @@
"security": [{ "bearer": [] }]
}
},
"/files/template/upload-location": {
"get": {
"operationId": "uploadTemplateLocation",
"summary": "",
"description": "ログイン中ユーザー用のBlob Storage上のテンプレートファイルのアップロード先アクセスURLを取得します",
"parameters": [],
"responses": {
"200": {
"description": "成功時のレスポンス",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TemplateUploadLocationResponse"
}
}
}
},
"401": {
"description": "認証エラー",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
}
}
},
"500": {
"description": "想定外のサーバーエラー",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
}
}
}
},
"tags": ["files"],
"security": [{ "bearer": [] }]
}
},
"/files/template/upload-finished": {
"post": {
"operationId": "uploadTemplateFinished",
"summary": "",
"description": "アップロードが完了したテンプレートファイルの情報を登録します",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TemplateUploadFinishedRequest"
}
}
}
},
"responses": {
"200": {
"description": "成功時のレスポンス",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TemplateUploadFinishedReqponse"
}
}
}
},
"400": {
"description": "不正なパラメータ",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
}
}
},
"401": {
"description": "認証エラー",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
}
}
},
"500": {
"description": "想定外のサーバーエラー",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
}
}
}
},
"tags": ["files"],
"security": [{ "bearer": [] }]
}
},
"/tasks": {
"get": {
"operationId": "getTasks",
@ -2671,6 +2765,44 @@
"security": [{ "bearer": [] }]
}
},
"/templates": {
"get": {
"operationId": "getTemplates",
"summary": "",
"description": "アカウント内のテンプレートファイルの一覧を取得します",
"parameters": [],
"responses": {
"200": {
"description": "成功時のレスポンス",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetTemplatesResponse"
}
}
}
},
"401": {
"description": "認証エラー",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
}
}
},
"500": {
"description": "想定外のサーバーエラー",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
}
}
}
},
"tags": ["templates"],
"security": [{ "bearer": [] }]
}
},
"/notification/register": {
"post": {
"operationId": "register",
@ -3602,6 +3734,36 @@
"properties": { "url": { "type": "string" } },
"required": ["url"]
},
"TemplateUploadLocationResponse": {
"type": "object",
"properties": { "url": { "type": "string" } },
"required": ["url"]
},
"TemplateFile": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "テンプレートファイルのファイル名"
},
"url": {
"type": "string",
"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",
"properties": {
@ -3808,6 +3970,17 @@
"required": ["poNumber"]
},
"CancelOrderResponse": { "type": "object", "properties": {} },
"GetTemplatesResponse": {
"type": "object",
"properties": {
"templates": {
"description": "テンプレートファイルの一覧",
"type": "array",
"items": { "$ref": "#/components/schemas/TemplateFile" }
}
},
"required": ["templates"]
},
"RegisterRequest": {
"type": "object",
"properties": {

View File

@ -28,6 +28,8 @@ import { NotificationModule } from './features/notification/notification.module'
import { FilesModule } from './features/files/files.module';
import { FilesController } from './features/files/files.controller';
import { FilesService } from './features/files/files.service';
import { TemplatesModule } from './features/templates/templates.module';
import { TemplatesController } from './features/templates/templates.controller';
import { TasksService } from './features/tasks/tasks.service';
import { TasksController } from './features/tasks/tasks.controller';
import { TasksModule } from './features/tasks/tasks.module';
@ -41,6 +43,7 @@ import { UserGroupsRepositoryModule } from './repositories/user_groups/user_grou
import { SortCriteriaRepositoryModule } from './repositories/sort_criteria/sort_criteria.repository.module';
import { TemplateFilesRepositoryModule } from './repositories/template_files/template_files.repository.module';
import { WorktypesRepositoryModule } from './repositories/worktypes/worktypes.repository.module';
import { TemplatesService } from './features/templates/templates.service';
@Module({
imports: [
@ -65,6 +68,7 @@ import { WorktypesRepositoryModule } from './repositories/worktypes/worktypes.re
FilesModule,
TasksModule,
UsersModule,
TemplatesModule,
SendGridModule,
LicensesModule,
AccountsRepositoryModule,
@ -106,6 +110,7 @@ import { WorktypesRepositoryModule } from './repositories/worktypes/worktypes.re
TasksController,
UsersController,
LicensesController,
TemplatesController,
],
providers: [
AuthService,
@ -115,6 +120,7 @@ import { WorktypesRepositoryModule } from './repositories/worktypes/worktypes.re
FilesService,
TasksService,
LicensesService,
TemplatesService,
],
})
export class AppModule {

View File

@ -27,10 +27,13 @@ import {
AudioUploadLocationResponse,
TemplateDownloadLocationRequest,
TemplateDownloadLocationResponse,
TemplateUploadFinishedReqponse,
TemplateUploadFinishedRequest,
TemplateUploadLocationResponse,
} from './types/types';
import { AuthGuard } from '../../common/guards/auth/authguards';
import { RoleGuard } from '../../common/guards/role/roleguards';
import { USER_ROLES } from '../../constants';
import { ADMIN_ROLES, USER_ROLES } from '../../constants';
import { retrieveAuthorizationToken } from '../../common/http/helper';
import { Request } from 'express';
import { makeContext } from '../../common/log';
@ -258,4 +261,84 @@ export class FilesController {
return { url };
}
@Get('template/upload-location')
@ApiResponse({
status: HttpStatus.OK,
type: TemplateUploadLocationResponse,
description: '成功時のレスポンス',
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
description: '認証エラー',
type: ErrorResponse,
})
@ApiResponse({
status: HttpStatus.INTERNAL_SERVER_ERROR,
description: '想定外のサーバーエラー',
type: ErrorResponse,
})
@ApiOperation({
operationId: 'uploadTemplateLocation',
description:
'ログイン中ユーザー用のBlob Storage上のテンプレートファイルのアップロード先アクセスURLを取得します',
})
@ApiBearerAuth()
@UseGuards(AuthGuard)
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
async uploadTemplateLocation(
@Req() req: Request,
): Promise<TemplateUploadLocationResponse> {
const token = retrieveAuthorizationToken(req);
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
const context = makeContext(accessToken.userId);
console.log(context.trackingId);
return { url: '' };
}
@ApiResponse({
status: HttpStatus.OK,
type: TemplateUploadFinishedReqponse,
description: '成功時のレスポンス',
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: '不正なパラメータ',
type: ErrorResponse,
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
description: '認証エラー',
type: ErrorResponse,
})
@ApiResponse({
status: HttpStatus.INTERNAL_SERVER_ERROR,
description: '想定外のサーバーエラー',
type: ErrorResponse,
})
@ApiOperation({
operationId: 'uploadTemplateFinished',
description: 'アップロードが完了したテンプレートファイルの情報を登録します',
})
@ApiBearerAuth()
@UseGuards(AuthGuard)
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
@Post('template/upload-finished')
async templateUploadFinished(
@Req() req: Request,
@Body() body: TemplateUploadFinishedRequest,
): Promise<TemplateUploadFinishedReqponse> {
const { templateFile } = 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);
return {};
}
}

View File

@ -1,4 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { TemplateFile } from '../../templates/types/types';
export class AudioUploadLocationRequest {}
@ -31,6 +32,11 @@ export class TemplateDownloadLocationResponse {
url: string;
}
export class TemplateUploadLocationResponse {
@ApiProperty()
url: string;
}
export class AudioOptionItem {
@ApiProperty({ minLength: 1, maxLength: 16 })
optionItemLabel: string;
@ -87,3 +93,10 @@ export class AudioUploadFinishedResponse {
@ApiProperty({ description: '8桁固定の数字' })
jobNumber: string;
}
export class TemplateUploadFinishedRequest {
@ApiProperty({ description: 'テンプレートファイルのファイル情報' })
templateFile: TemplateFile;
}
export class TemplateUploadFinishedReqponse {}

View File

@ -0,0 +1,30 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigModule } from '@nestjs/config';
import { TemplatesController } from './templates.controller';
import { TemplatesService } from './templates.service';
describe('TemplatesController', () => {
let controller: TemplatesController;
const mockTemplatesService = {};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
envFilePath: ['.env.local', '.env'],
isGlobal: true,
}),
],
controllers: [TemplatesController],
providers: [TemplatesService],
})
.overrideProvider(TemplatesService)
.useValue(mockTemplatesService)
.compile();
controller = module.get<TemplatesController>(TemplatesController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,57 @@
import { Controller, Get, HttpStatus, Req, UseGuards } from '@nestjs/common';
import {
ApiBearerAuth,
ApiOperation,
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
import jwt from 'jsonwebtoken';
import { AccessToken } from '../../common/token';
import { ErrorResponse } from '../../common/error/types/types';
import { GetTemplatesResponse } from './types/types';
import { AuthGuard } from '../../common/guards/auth/authguards';
import { RoleGuard } from '../../common/guards/role/roleguards';
import { ADMIN_ROLES } from '../../constants';
import { retrieveAuthorizationToken } from '../../common/http/helper';
import { Request } from 'express';
import { makeContext } from '../../common/log';
import { TemplatesService } from './templates.service';
@ApiTags('templates')
@Controller('templates')
export class TemplatesController {
constructor(private readonly templatesService: TemplatesService) {}
@ApiResponse({
status: HttpStatus.OK,
type: GetTemplatesResponse,
description: '成功時のレスポンス',
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
description: '認証エラー',
type: ErrorResponse,
})
@ApiResponse({
status: HttpStatus.INTERNAL_SERVER_ERROR,
description: '想定外のサーバーエラー',
type: ErrorResponse,
})
@ApiOperation({
operationId: 'getTemplates',
description: 'アカウント内のテンプレートファイルの一覧を取得します',
})
@ApiBearerAuth()
@UseGuards(AuthGuard)
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
@Get()
async getTemplates(@Req() req: Request): Promise<GetTemplatesResponse> {
const token = retrieveAuthorizationToken(req);
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
const context = makeContext(accessToken.userId);
console.log(context.trackingId);
return { templates: [] };
}
}

View File

@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { TemplatesController } from './templates.controller';
import { TemplatesService } from './templates.service';
@Module({
imports: [],
providers: [TemplatesService],
controllers: [TemplatesController],
})
export class TemplatesModule {}

View File

@ -0,0 +1,7 @@
import { Injectable, Logger } from '@nestjs/common';
@Injectable()
export class TemplatesService {
private readonly logger = new Logger(TemplatesService.name);
constructor() {}
}

View File

@ -0,0 +1,16 @@
import { ApiProperty } from '@nestjs/swagger';
export class TemplateFile {
@ApiProperty({ description: 'テンプレートファイルのファイル名' })
name: string;
@ApiProperty({ description: 'テンプレートファイルのURL' })
url: string;
}
export class GetTemplatesResponse {
@ApiProperty({
description: 'テンプレートファイルの一覧',
type: [TemplateFile],
})
templates: TemplateFile[];
}