-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[김하린] 휴대폰 인증 API #6
base: main
Are you sure you want to change the base?
Changes from all commits
dcb4641
4cc76bb
887d1e4
de16a32
68c67c9
b33ae7a
3014ad1
152521a
084e77c
7db6ba8
4f11d8f
d87f373
a9bb973
c87ae8e
730b6f8
f0485f4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { Body, Controller, Patch, Post } from '@nestjs/common'; | ||
import { PhoneVerifyService } from '../../domain/phone-verify/phone-verify.service'; | ||
import { PhoneVerifyCodeRequestDto } from '../../domain/phone-verify/dto/request/phone-verify-code-request.dto'; | ||
import { PhoneVerifyCodeResponseDto } from '../../domain/phone-verify/dto/response/phone-verify-code-response.dto'; | ||
import { PhoneVerifyRequestDto } from '../../domain/phone-verify/dto/request/phone-verify-request.dto'; | ||
import { ApiOperation, ApiTags } from '@nestjs/swagger'; | ||
@Controller('phone-verifications') | ||
@ApiTags('phone-verifications') | ||
export class PhoneVerifyController { | ||
constructor(private readonly phoneVerifyService: PhoneVerifyService) {} | ||
|
||
@ApiOperation({ | ||
summary: '휴대폰 인증번호 발송', | ||
}) | ||
@Post('/phone-verifications') | ||
async sendVerifyCode( | ||
@Body() dto: PhoneVerifyCodeRequestDto, | ||
): Promise<PhoneVerifyCodeResponseDto> { | ||
return this.phoneVerifyService.sendVerifyCode(dto); | ||
} | ||
|
||
@ApiOperation({ | ||
summary: '휴대폰 인증', | ||
}) | ||
@Patch('/phone-verifications') | ||
verify(@Body() dto: PhoneVerifyRequestDto) { | ||
return this.phoneVerifyService.verify(dto); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export const Messages = { | ||
// auth | ||
ERROR_AUTH_FAIL: '인증에 실패했습니다.', | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { HttpException } from '@nestjs/common'; | ||
|
||
export class ErrorResponse { | ||
public status: number; | ||
public code: string; | ||
public message: string; | ||
|
||
constructor(exception: HttpException) { | ||
this.status = exception.getStatus(); | ||
this.code = exception.getResponse()['error']; | ||
this.message = exception.getResponse()['message']; | ||
} | ||
|
||
toJson() { | ||
return { | ||
status: this.status, | ||
code: this.code, | ||
message: this.message, | ||
}; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { | ||
ArgumentsHost, | ||
Catch, | ||
ExceptionFilter, | ||
HttpException, | ||
} from '@nestjs/common'; | ||
import { ErrorResponse } from './error-response'; | ||
|
||
@Catch(HttpException) | ||
export class HttpExceptionFilter implements ExceptionFilter { | ||
catch(exception: HttpException, host: ArgumentsHost) { | ||
const ctx = host.switchToHttp(); | ||
const response = ctx.getResponse(); | ||
const errorResponse = new ErrorResponse(exception); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 위에 ErrorResponse Class를 만들어 놓으니 코드가 한결 깔끔하네요!👍 |
||
|
||
response.status(errorResponse.status).json(errorResponse.toJson()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { UnauthorizedException } from '@nestjs/common'; | ||
|
||
/* 401 Unauthorized */ | ||
export class AuthFailedException extends UnauthorizedException { | ||
constructor(message?: string, code?: string) { | ||
super(message ?? '인증에 실패하였습니다.', code ?? 'AUTH_FAILED'); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export function generateNumericToken( | ||
length: number = 6, | ||
alphabet: string = '1234567890', | ||
): string { | ||
let id = ''; | ||
let i = length; | ||
while (i--) { | ||
id += alphabet[(Math.random() * alphabet.length) | 0]; | ||
} | ||
return id; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export const validator = { | ||
PHONE_NUMBER_REGEX: '^[0-9]{11}$', | ||
VERIFY_CODE_REGEX: '^[0-9]{6}$', | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { IsString, Matches } from 'class-validator'; | ||
import { validator } from '../../../../common/validator'; | ||
|
||
export class PhoneVerifyCodeRequestDto { | ||
@IsString() | ||
@Matches(validator.PHONE_NUMBER_REGEX) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 상수화 처리 좋네요👍 |
||
phoneNumber: string; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { IsString, Matches } from 'class-validator'; | ||
import { validator } from '../../../../common/validator'; | ||
|
||
export class PhoneVerifyRequestDto { | ||
@IsString() | ||
@Matches(validator.PHONE_NUMBER_REGEX) | ||
phoneNumber: string; | ||
|
||
@IsString() | ||
@Matches(validator.VERIFY_CODE_REGEX) | ||
verifyCode: string; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export class PhoneVerifyCodeResponseDto { | ||
code: string; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export class PhoneVerifyResponseDto { | ||
result: boolean; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { | ||
Column, | ||
CreateDateColumn, | ||
Entity, | ||
PrimaryGeneratedColumn, | ||
UpdateDateColumn, | ||
} from 'typeorm'; | ||
|
||
@Entity() | ||
export class PhoneVerify { | ||
@PrimaryGeneratedColumn() | ||
id: number; | ||
|
||
@CreateDateColumn() | ||
createdAt: Date; | ||
|
||
@UpdateDateColumn() | ||
updatedAt: Date; | ||
|
||
@Column() | ||
phoneNumber: string; | ||
|
||
@Column() | ||
verifyCode: string; | ||
|
||
@Column({ default: false }) | ||
isVerified: boolean; | ||
|
||
@Column() | ||
expiredAt: Date; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { PhoneVerifyService } from './phone-verify.service'; | ||
import { TypeOrmModule } from '@nestjs/typeorm'; | ||
import { PhoneVerify } from './phone-verify.entity'; | ||
|
||
@Module({ | ||
imports: [TypeOrmModule.forFeature([PhoneVerify])], | ||
providers: [PhoneVerifyService], | ||
exports: [PhoneVerifyService], | ||
}) | ||
export class PhoneVerifyModule {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { InjectRepository } from '@nestjs/typeorm'; | ||
import { PhoneVerify } from './phone-verify.entity'; | ||
import { Repository } from 'typeorm'; | ||
import { PhoneVerifyCodeRequestDto } from './dto/request/phone-verify-code-request.dto'; | ||
import { PhoneVerifyCodeResponseDto } from './dto/response/phone-verify-code-response.dto'; | ||
import { generateNumericToken } from '../../common/util'; | ||
import { PhoneVerifyRequestDto } from './dto/request/phone-verify-request.dto'; | ||
import { PhoneVerifyResponseDto } from './dto/response/phone-verify-response.dto'; | ||
|
||
const VERIFY_CODE_VALID_TIME = 5; | ||
|
||
@Injectable() | ||
export class PhoneVerifyService { | ||
constructor( | ||
@InjectRepository(PhoneVerify) | ||
private readonly phoneVerifyRepository: Repository<PhoneVerify>, | ||
) {} | ||
|
||
async sendVerifyCode( | ||
dto: PhoneVerifyCodeRequestDto, | ||
): Promise<PhoneVerifyCodeResponseDto> { | ||
const verifyCode = generateNumericToken(); | ||
|
||
const expiredDate = new Date(); | ||
expiredDate.setMinutes(expiredDate.getMinutes() + VERIFY_CODE_VALID_TIME); | ||
|
||
const phoneVerify = new PhoneVerify(); | ||
phoneVerify.verifyCode = verifyCode; | ||
phoneVerify.phoneNumber = dto.phoneNumber; | ||
phoneVerify.expiredAt = expiredDate; | ||
|
||
await this.phoneVerifyRepository.save(phoneVerify); | ||
|
||
const response = new PhoneVerifyCodeResponseDto(); | ||
response.code = verifyCode; | ||
|
||
return response; | ||
} | ||
|
||
async verify(dto: PhoneVerifyRequestDto) { | ||
const phoneVerification = await this.phoneVerifyRepository | ||
.createQueryBuilder('pv') | ||
.where('pv.phoneNumber = :phoneNumber', { phoneNumber: dto.phoneNumber }) | ||
.andWhere('pv.verifyCode = :verifyCode', { verifyCode: dto.verifyCode }) | ||
.andWhere('pv.isVerified = false') | ||
.andWhere('pv.expiredAt > NOW()') | ||
.orderBy({ createdAt: 'DESC' }) | ||
.getOne(); | ||
Comment on lines
+42
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 findOne() 함수를 통해서 가져왔는데, createQueryBuilder를 쓰면 어떤 이점이 있을까요?! |
||
|
||
const response = new PhoneVerifyResponseDto(); | ||
if (!phoneVerification) { | ||
response.result = false; | ||
return response; | ||
} | ||
|
||
phoneVerification.isVerified = true; | ||
await this.phoneVerifyRepository.save(phoneVerification); | ||
response.result = true; | ||
return response; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저는 함수를 만들어서 json 형태로 반환하는 형태를 작성했었는데, 하린님처럼 ErrorResponse Class를 만들어서 하는 방법 좋은 것 같습니다👍 저도 다음에 적용해봐야겠어요~ 덕분에 알아갑니다😄