From bfa7fc4f7675e5ad400a85618bab791582e9ae7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B9=AF=E6=9C=AC=20=E9=96=8B?= Date: Tue, 11 Apr 2023 04:03:33 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=2068:=20API=E5=AE=9F=E8=A3=85?= =?UTF-8?q?=EF=BC=88I/F=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task1590: API実装(I/F)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/1590) - ラフスケッチを元にAPI I/Fを実装し、openapi.jsonを生成 - ラフスケッチ時はユーザー登録のリクエストに `authorMetadata` のような拡張可能な形式で考えていた部分を、単に煩雑になるだけと判断して素のAuthorID/TypistGroupIDに変更 - `@POST()/@GET()` の定義箇所をメソッド定義が書かれている行に近づけるよう修正 # レビュー対象外 - メール認証APIは[別Task](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/OMDSDictation/_workitems/edit/1599)に分割してそちらで対応予定のため今回は未実装 ## レビューポイント - 認証情報の取り方は `@Req()` と `@Body()` を両方引数に定義する方法で問題なさそうか - ラフスケッチ時からリクエストボディの構造が変わったが問題ないか - APIの引数の型などは問題ないか ## 動作確認状況 - ビルドが通ることを確認 --- dictation_server/src/api/odms/openapi.json | 143 ++++++++++++++++++ .../src/features/users/types/types.ts | 62 ++++++++ .../src/features/users/users.controller.ts | 75 ++++++++- 3 files changed, 276 insertions(+), 4 deletions(-) diff --git a/dictation_server/src/api/odms/openapi.json b/dictation_server/src/api/odms/openapi.json index e80b494..c9c6b22 100644 --- a/dictation_server/src/api/odms/openapi.json +++ b/dictation_server/src/api/odms/openapi.json @@ -172,6 +172,92 @@ "tags": ["users"] } }, + "/users": { + "get": { + "operationId": "getUsers", + "summary": "", + "parameters": [], + "responses": { + "200": { + "description": "成功時のレスポンス", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/GetUsersResponse" } + } + } + }, + "401": { + "description": "認証エラー", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + }, + "500": { + "description": "想定外のサーバーエラー", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + } + }, + "tags": ["users"], + "security": [{ "bearer": [] }] + } + }, + "/users/signup": { + "post": { + "operationId": "signup", + "summary": "", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/SignupRequest" } + } + } + }, + "responses": { + "200": { + "description": "成功時のレスポンス", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/SignupResponse" } + } + } + }, + "400": { + "description": "登録済みメールによる再登録、AuthorIDの重複など", + "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": ["users"], + "security": [{ "bearer": [] }] + } + }, "/notification/register": { "post": { "operationId": "register", @@ -312,6 +398,63 @@ "required": ["token"] }, "ConfirmResponse": { "type": "object", "properties": {} }, + "User": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "role": { "type": "string", "description": "none/author/typist" }, + "authorId": { "type": "string", "nullable": true }, + "typistGroupName": { "type": "string", "nullable": true }, + "email": { "type": "string" }, + "emailVerified": { "type": "boolean" }, + "autoRenew": { "type": "boolean" }, + "licenseAlert": { "type": "boolean" }, + "notification": { "type": "boolean" } + }, + "required": [ + "name", + "role", + "authorId", + "typistGroupName", + "email", + "emailVerified", + "autoRenew", + "licenseAlert", + "notification" + ] + }, + "GetUsersResponse": { + "type": "object", + "properties": { + "users": { + "type": "array", + "items": { "$ref": "#/components/schemas/User" } + } + }, + "required": ["users"] + }, + "SignupRequest": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "role": { "type": "string", "description": "none/author/typist" }, + "authorId": { "type": "string" }, + "typistGroupId": { "type": "number" }, + "email": { "type": "string" }, + "autoRenew": { "type": "boolean" }, + "licenseAlert": { "type": "boolean" }, + "notification": { "type": "boolean" } + }, + "required": [ + "name", + "role", + "email", + "autoRenew", + "licenseAlert", + "notification" + ] + }, + "SignupResponse": { "type": "object", "properties": {} }, "RegisterRequest": { "type": "object", "properties": { diff --git a/dictation_server/src/features/users/types/types.ts b/dictation_server/src/features/users/types/types.ts index e638ba8..dbc0227 100644 --- a/dictation_server/src/features/users/types/types.ts +++ b/dictation_server/src/features/users/types/types.ts @@ -6,3 +6,65 @@ export class ConfirmRequest { } export class ConfirmResponse {} + +export class User { + @ApiProperty() + name: string; + + @ApiProperty({ description: 'none/author/typist' }) + role: string; + + @ApiProperty({ nullable: true }) + authorId?: string | undefined; + + @ApiProperty({ nullable: true }) + typistGroupName?: string | undefined; + + @ApiProperty() + email: string; + + @ApiProperty() + emailVerified: boolean; + + @ApiProperty() + autoRenew: boolean; + + @ApiProperty() + licenseAlert: boolean; + + @ApiProperty() + notification: boolean; +} + +export class GetUsersResponse { + @ApiProperty({ type: [User] }) + users: User[]; +} + +export class SignupRequest { + @ApiProperty() + name: string; + + @ApiProperty({ description: 'none/author/typist' }) + role: string; + + @ApiProperty({ required: false }) + authorId?: string | undefined; + + @ApiProperty({ required: false }) + typistGroupId?: number | undefined; + + @ApiProperty() + email: string; + + @ApiProperty() + autoRenew: boolean; + + @ApiProperty() + licenseAlert: boolean; + + @ApiProperty() + notification: boolean; +} + +export class SignupResponse {} diff --git a/dictation_server/src/features/users/users.controller.ts b/dictation_server/src/features/users/users.controller.ts index c3be8af..0bbc043 100644 --- a/dictation_server/src/features/users/users.controller.ts +++ b/dictation_server/src/features/users/users.controller.ts @@ -1,15 +1,26 @@ -import { Body, Controller, HttpStatus, Post } from '@nestjs/common'; -import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { Body, Controller, Get, HttpStatus, Post, Req } from '@nestjs/common'; +import { + ApiBearerAuth, + ApiOperation, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; import { ErrorResponse } from '../../common/error/types/types'; -import { ConfirmRequest, ConfirmResponse } from './types/types'; +import { + ConfirmRequest, + ConfirmResponse, + GetUsersResponse, + SignupRequest, + SignupResponse, +} from './types/types'; import { UsersService } from './users.service'; +import { Request } from 'express'; @ApiTags('users') @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} - @Post('confirm') @ApiResponse({ status: HttpStatus.OK, type: ConfirmResponse, @@ -26,8 +37,64 @@ export class UsersController { type: ErrorResponse, }) @ApiOperation({ operationId: 'confirmUser' }) + @Post('confirm') async confirmUser(@Body() body: ConfirmRequest): Promise { await this.usersService.confirmUser(body.token); return {}; } + + @ApiResponse({ + status: HttpStatus.OK, + type: GetUsersResponse, + description: '成功時のレスポンス', + }) + @ApiResponse({ + status: HttpStatus.UNAUTHORIZED, + description: '認証エラー', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: '想定外のサーバーエラー', + type: ErrorResponse, + }) + @ApiOperation({ operationId: 'getUsers' }) + @ApiBearerAuth() + @Get() + async getUsers(@Req() req: Request): Promise { + console.log(req.header('Authorization')); + return { users: [] }; + } + + @ApiResponse({ + status: HttpStatus.OK, + type: SignupResponse, + description: '成功時のレスポンス', + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: '登録済みメールによる再登録、AuthorIDの重複など', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.UNAUTHORIZED, + description: '認証エラー', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: '想定外のサーバーエラー', + type: ErrorResponse, + }) + @ApiOperation({ operationId: 'signup' }) + @ApiBearerAuth() + @Post('/signup') + async signup( + @Req() req: Request, + @Body() body: SignupRequest, + ): Promise { + console.log(req.header('Authorization')); + console.log(body); + return {}; + } }