## 概要 [Task1593: API実装(ユーザー登録)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/1593) https://dev.azure.com/ODMSCloud/ODMS%20Cloud/_git/ODMS%20Cloud users.controllerにアクセストークン取得処理を追加 users.serviceにユーザ追加処理を追加 user.entityにauto_renew、license_alert、notificationを追加 users.repository.serviceにユーザ追加・AuthorId検索処理を追加 ## レビューポイント 処理の記載場所が適切かどうか 期待通りの処理になっているかどうか テストコードの記載方法が正しいかどうか ## UIの変更 なし ## 動作確認状況 ローカルでのビルド・実行を確認 ## 補足 テスト実装について不安要素があります。 ・テストの粒度はこれでよいのでしょうか? ・テスト実行に40分かかってしまうのですが実装方法を間違えている箇所がありそうでしょうか?
239 lines
6.1 KiB
TypeScript
239 lines
6.1 KiB
TypeScript
import {
|
|
Body,
|
|
Controller,
|
|
Get,
|
|
HttpStatus,
|
|
Post,
|
|
Req,
|
|
HttpException,
|
|
} from '@nestjs/common';
|
|
import {
|
|
ApiBearerAuth,
|
|
ApiOperation,
|
|
ApiResponse,
|
|
ApiTags,
|
|
} from '@nestjs/swagger';
|
|
import { ErrorResponse } from '../../common/error/types/types';
|
|
import {
|
|
ConfirmRequest,
|
|
ConfirmResponse,
|
|
GetRelationsResponse,
|
|
GetUsersResponse,
|
|
SignupRequest,
|
|
SignupResponse,
|
|
} from './types/types';
|
|
import { UsersService } from './users.service';
|
|
import { Request } from 'express';
|
|
import { verify, isVerifyError } from '../../common/jwt/jwt';
|
|
import { CryptoService } from '../../gateways/crypto/crypto.service';
|
|
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
|
import { AccessToken } from '../../common/token';
|
|
import { retrieveAccessToken } from '../../common/http/helper';
|
|
import { confirmPermission } from '../../common/auth/auth';
|
|
|
|
@ApiTags('users')
|
|
@Controller('users')
|
|
export class UsersController {
|
|
constructor(
|
|
private readonly usersService: UsersService,
|
|
private readonly cryptoService: CryptoService,
|
|
) {}
|
|
|
|
@ApiResponse({
|
|
status: HttpStatus.OK,
|
|
type: ConfirmResponse,
|
|
description: '成功時のレスポンス',
|
|
})
|
|
@ApiResponse({
|
|
status: HttpStatus.BAD_REQUEST,
|
|
description: '不正なトークン',
|
|
type: ErrorResponse,
|
|
})
|
|
@ApiResponse({
|
|
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
|
description: '想定外のサーバーエラー',
|
|
type: ErrorResponse,
|
|
})
|
|
@ApiOperation({ operationId: 'confirmUser' })
|
|
@Post('confirm')
|
|
async confirmUser(@Body() body: ConfirmRequest): Promise<ConfirmResponse> {
|
|
await this.usersService.confirmUser(body.token);
|
|
return {};
|
|
}
|
|
|
|
@ApiResponse({
|
|
status: HttpStatus.OK,
|
|
type: ConfirmResponse,
|
|
description: '成功時のレスポンス',
|
|
})
|
|
@ApiResponse({
|
|
status: HttpStatus.BAD_REQUEST,
|
|
description: '不正なトークン',
|
|
type: ErrorResponse,
|
|
})
|
|
@ApiResponse({
|
|
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
|
description: '想定外のサーバーエラー',
|
|
type: ErrorResponse,
|
|
})
|
|
@ApiOperation({ operationId: 'confirmUserAndInitPassword' })
|
|
@Post('confirm/initpassword')
|
|
async confirmUserAndInitPassword(
|
|
@Body() body: ConfirmRequest,
|
|
): Promise<ConfirmResponse> {
|
|
console.log(body);
|
|
await this.usersService.confirmUserAndInitPassword(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<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> {
|
|
const {
|
|
name,
|
|
role,
|
|
email,
|
|
autoRenew,
|
|
licenseAlert,
|
|
notification,
|
|
authorId,
|
|
typistGroupId,
|
|
} = body;
|
|
|
|
// アクセストークンにより権限を確認する
|
|
const pubKey = await this.cryptoService.getPublicKey();
|
|
const accessToken = retrieveAccessToken(req);
|
|
|
|
//アクセストークンが存在しない場合のエラー
|
|
if (accessToken == undefined) {
|
|
throw new HttpException(
|
|
makeErrorResponse('E000107'),
|
|
HttpStatus.UNAUTHORIZED,
|
|
);
|
|
}
|
|
const payload = verify<AccessToken>(accessToken, pubKey);
|
|
|
|
//アクセストークン形式エラー
|
|
if (isVerifyError(payload)) {
|
|
throw new HttpException(
|
|
makeErrorResponse('E000101'),
|
|
HttpStatus.UNAUTHORIZED,
|
|
);
|
|
}
|
|
//アクセストークンの権限不足エラー
|
|
if (!confirmPermission(payload.scope)) {
|
|
throw new HttpException(
|
|
makeErrorResponse('E000108'),
|
|
HttpStatus.UNAUTHORIZED,
|
|
);
|
|
}
|
|
|
|
//ユーザ作成処理
|
|
await this.usersService.createUser(
|
|
payload,
|
|
name,
|
|
role,
|
|
email,
|
|
autoRenew,
|
|
licenseAlert,
|
|
notification,
|
|
authorId,
|
|
typistGroupId,
|
|
);
|
|
return {};
|
|
}
|
|
|
|
@ApiResponse({
|
|
status: HttpStatus.OK,
|
|
type: GetRelationsResponse,
|
|
description: '成功時のレスポンス',
|
|
})
|
|
@ApiResponse({
|
|
status: HttpStatus.UNAUTHORIZED,
|
|
description: '認証エラー',
|
|
type: ErrorResponse,
|
|
})
|
|
@ApiResponse({
|
|
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
|
description: '想定外のサーバーエラー',
|
|
type: ErrorResponse,
|
|
})
|
|
@ApiOperation({
|
|
operationId: 'getRelations',
|
|
description: 'ログインしているユーザーに関連する各種情報を取得します',
|
|
})
|
|
@ApiBearerAuth()
|
|
@Get('relations')
|
|
async getRelations(@Req() req: Request): Promise<GetRelationsResponse> {
|
|
console.log(req.header('Authorization'));
|
|
return {
|
|
authorId: 'AUTHOR',
|
|
authorIdList: ['AUTHOR', 'AUTHOR1'],
|
|
workTypeList: [
|
|
{
|
|
workTypeId: '1',
|
|
optionItemList: [
|
|
{
|
|
label: '1',
|
|
initialValueType: 0,
|
|
defaultValue: 'default',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
isEncrypted: true,
|
|
encryptionPassword: '',
|
|
activeWorktype: '',
|
|
audioFormat: 'DS2',
|
|
prompt: true,
|
|
};
|
|
}
|
|
}
|