Skip to content

[이상민] 휴대폰 인증 API #10

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

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
9 changes: 9 additions & 0 deletions nest-cli.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"plugins": [
{
"name": "@nestjs/swagger",
"options": {
"classValidatorShim": false,
"introspectComments": true
}
}
],
"deleteOutDir": true
}
}
File renamed without changes.
Empty file modified setup.sh
100644 → 100755
Empty file.
17 changes: 15 additions & 2 deletions template/source/typeorm/app.module.ts → src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
import { addTransactionalDataSource } from 'typeorm-transactional';
import { DataSource } from 'typeorm';
import { PhoneVerifyModule } from './phone-verify/phone-verify.module';
import { PhoneVerify } from './phone-verify/entities/phone-verify.entity';
import { PhoneVerifyController } from './phone-verify/phone-verify.controller';
import { APP_FILTER } from '@nestjs/core';
import { HttpExceptionFilter } from './common/error/http-exception.filter';

@Module({
imports: [
Expand All @@ -17,6 +22,7 @@ import { DataSource } from 'typeorm';
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
synchronize: process.env.DB_SYNC === 'true',
entities: [PhoneVerify],
timezone: 'Z',
};
},
Expand All @@ -28,8 +34,15 @@ import { DataSource } from 'typeorm';
return addTransactionalDataSource(new DataSource(options));
},
}),
TypeOrmModule.forFeature([PhoneVerify]),
PhoneVerifyModule,
],
controllers: [PhoneVerifyController],
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
],
controllers: [],
providers: [],
})
export class AppModule {}
21 changes: 21 additions & 0 deletions src/common/error/error-response.ts
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,
};
}
}
18 changes: 18 additions & 0 deletions src/common/error/http-exception.filter.ts
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);

response.status(errorResponse.status).json(errorResponse.toJson());
}
}
8 changes: 8 additions & 0 deletions src/common/error/index.ts
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');
}
}
37 changes: 37 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { initializeTransactionalContext } from 'typeorm-transactional';
import { ValidationPipe } from '@nestjs/common';
import { HttpExceptionFilter } from './common/error/http-exception.filter';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

async function bootstrap() {
initializeTransactionalContext();

const app = await NestFactory.create(AppModule);

// ---------------------------- Swagger 설정 ----------------------------
const config = new DocumentBuilder()
.setTitle('NestJS API')
.setDescription('어쩌다 Nest 과제 API')
.setVersion('1.0')
.build();
const swaggerOptions = {
operationIdFactory: (controllerKey: string, methodKey: string) => methodKey,
};

const document = SwaggerModule.createDocument(app, config, swaggerOptions);
SwaggerModule.setup('api', app, document);

// ---------------------------- Global Pipe 설정 ----------------------------
app.useGlobalPipes(new ValidationPipe({ transform: true }));

// ---------------------------- Global Filter 설정 ----------------------------
app.useGlobalFilters(new HttpExceptionFilter());

await app.listen(process.env.PORT || 8000);

console.log(`Application is running on: ${await app.getUrl()}`);
}

bootstrap();
7 changes: 7 additions & 0 deletions src/phone-verify/dto/request/send-code-request.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Matches, IsString } from 'class-validator';

export class SendCodeRequestDto {
@IsString()
@Matches(/^010-\d{4}-\d{4}$/)
phoneNumber: string;
}
11 changes: 11 additions & 0 deletions src/phone-verify/dto/request/verify-code-request.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { IsString, Matches } from 'class-validator';

export class VerifyCodeRequestDto {
@IsString()
@Matches(/^010-\d{4}-\d{4}$/)
phoneNumber: string;

@IsString()
@Matches(/^[0-9]{6}$/)
code: string;
}
3 changes: 3 additions & 0 deletions src/phone-verify/dto/response/send-code-response.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class SendCodeResponseDto {
code: string;
}
3 changes: 3 additions & 0 deletions src/phone-verify/dto/response/verify-code-response.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class VerifyCodeResponseDto {
result: boolean;
}
27 changes: 27 additions & 0 deletions src/phone-verify/entities/phone-verify.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
} from 'typeorm';

@Entity()
export class PhoneVerify {
@PrimaryGeneratedColumn()
id: number;

@Column()
phoneNumber: string;

@Column()
code: string;

@CreateDateColumn()
createdAt: Date;

@Column()
expiredAt: Date;

@Column({ default: false })
isVerified: boolean;
}
20 changes: 20 additions & 0 deletions src/phone-verify/phone-verify.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PhoneVerifyController } from './phone-verify.controller';
import { PhoneVerifyService } from './phone-verify.service';

describe('PhoneVerifyController', () => {
let controller: PhoneVerifyController;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [PhoneVerifyController],
providers: [PhoneVerifyService],
}).compile();

controller = module.get<PhoneVerifyController>(PhoneVerifyController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});
});
31 changes: 31 additions & 0 deletions src/phone-verify/phone-verify.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Body, Controller, Patch, Post } from '@nestjs/common';
import { PhoneVerifyService } from './phone-verify.service';
import { SendCodeRequestDto } from './dto/request/send-code-request.dto';
import { SendCodeResponseDto } from './dto/response/send-code-response.dto';
import { VerifyCodeRequestDto } from './dto/request/verify-code-request.dto';
import { VerifyCodeResponseDto } from './dto/response/verify-code-response.dto';
import { ApiOperation, ApiTags } from '@nestjs/swagger';

@Controller('phone-verify')
@ApiTags('phone-verify')
export class PhoneVerifyController {
constructor(private readonly phoneVerifyService: PhoneVerifyService) {}

@ApiOperation({
summary: '휴대폰 인증번호 발송',
})
@Post('send-code')
async sendVerifyCode(
@Body() dto: SendCodeRequestDto,
): Promise<SendCodeResponseDto> {
return this.phoneVerifyService.sendCode(dto);
}

@ApiOperation({
summary: '휴대폰 인증',
})
@Patch('/verify-code')
verify(@Body() dto: VerifyCodeRequestDto): Promise<VerifyCodeResponseDto> {
return this.phoneVerifyService.verifyCode(dto);
}
}
11 changes: 11 additions & 0 deletions src/phone-verify/phone-verify.module.ts
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 './entities/phone-verify.entity';

@Module({
imports: [TypeOrmModule.forFeature([PhoneVerify])],
providers: [PhoneVerifyService],
exports: [PhoneVerifyService],
})
export class PhoneVerifyModule {}
18 changes: 18 additions & 0 deletions src/phone-verify/phone-verify.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PhoneVerifyService } from './phone-verify.service';

describe('PhoneVerifyService', () => {
let service: PhoneVerifyService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [PhoneVerifyService],
}).compile();

service = module.get<PhoneVerifyService>(PhoneVerifyService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
53 changes: 53 additions & 0 deletions src/phone-verify/phone-verify.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { PhoneVerify } from './entities/phone-verify.entity';
import { Repository } from 'typeorm';
import { SendCodeResponseDto } from './dto/response/send-code-response.dto';
import { SendCodeRequestDto } from './dto/request/send-code-request.dto';
import { VerifyCodeRequestDto } from './dto/request/verify-code-request.dto';
import { VerifyCodeResponseDto } from './dto/response/verify-code-response.dto';

@Injectable()
export class PhoneVerifyService {
constructor(
@InjectRepository(PhoneVerify)
private phoneRepository: Repository<PhoneVerify>,
) {}

async sendCode(dto: SendCodeRequestDto): Promise<SendCodeResponseDto> {
const code = Math.floor(100000 + Math.random() * 900000).toString();
const createdAt = new Date();
const expiredAt = new Date();
expiredAt.setMinutes(expiredAt.getMinutes() + 5);

const phoneVerify = new PhoneVerify();
phoneVerify.phoneNumber = dto.phoneNumber;
phoneVerify.code = code;
phoneVerify.createdAt = createdAt;
phoneVerify.expiredAt = expiredAt;

await this.phoneRepository.save(phoneVerify);

const response = new SendCodeResponseDto();
response.code = code;

return response;
}

async verifyCode(dto: VerifyCodeRequestDto): Promise<VerifyCodeResponseDto> {
const phoneRecord = await this.phoneRepository.findOne({
where: { phoneNumber: dto.phoneNumber, code: dto.code },
});

const response = new VerifyCodeResponseDto();
if (!phoneRecord) {
response.result = false;
return response;
}

phoneRecord.isVerified = true;
await this.phoneRepository.save(phoneRecord);
response.result = true;
return response;
}
}
6 changes: 0 additions & 6 deletions template/environment/prisma/.env.example

This file was deleted.

39 changes: 0 additions & 39 deletions template/environment/prisma/docker-compose.yml

This file was deleted.

11 changes: 0 additions & 11 deletions template/environment/typeorm/.env.example

This file was deleted.

Loading