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

## 概要
[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の引数の型などは問題ないか

## 動作確認状況
- ビルドが通ることを確認
This commit is contained in:
湯本 開 2023-04-11 04:03:33 +00:00
parent 33509fb228
commit bfa7fc4f76
3 changed files with 276 additions and 4 deletions

View File

@ -172,6 +172,92 @@
"tags": ["users"] "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": { "/notification/register": {
"post": { "post": {
"operationId": "register", "operationId": "register",
@ -312,6 +398,63 @@
"required": ["token"] "required": ["token"]
}, },
"ConfirmResponse": { "type": "object", "properties": {} }, "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": { "RegisterRequest": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -6,3 +6,65 @@ export class ConfirmRequest {
} }
export class ConfirmResponse {} 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 {}

View File

@ -1,15 +1,26 @@
import { Body, Controller, HttpStatus, Post } from '@nestjs/common'; import { Body, Controller, Get, HttpStatus, Post, Req } from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import {
ApiBearerAuth,
ApiOperation,
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
import { ErrorResponse } from '../../common/error/types/types'; 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 { UsersService } from './users.service';
import { Request } from 'express';
@ApiTags('users') @ApiTags('users')
@Controller('users') @Controller('users')
export class UsersController { export class UsersController {
constructor(private readonly usersService: UsersService) {} constructor(private readonly usersService: UsersService) {}
@Post('confirm')
@ApiResponse({ @ApiResponse({
status: HttpStatus.OK, status: HttpStatus.OK,
type: ConfirmResponse, type: ConfirmResponse,
@ -26,8 +37,64 @@ export class UsersController {
type: ErrorResponse, type: ErrorResponse,
}) })
@ApiOperation({ operationId: 'confirmUser' }) @ApiOperation({ operationId: 'confirmUser' })
@Post('confirm')
async confirmUser(@Body() body: ConfirmRequest): Promise<ConfirmResponse> { async confirmUser(@Body() body: ConfirmRequest): Promise<ConfirmResponse> {
await this.usersService.confirmUser(body.token); await this.usersService.confirmUser(body.token);
return {}; 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<GetUsersResponse> {
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<SignupResponse> {
console.log(req.header('Authorization'));
console.log(body);
return {};
}
} }