Skip to content

Commit

Permalink
feat(BE): 사용자 생성 API 구현 (#16)
Browse files Browse the repository at this point in the history
* feat: string, length validation message 추가

* feat: Users entity class-validator 데코레이터 제거

* feat: Users password 컬럼 Exclude 추가

* comment: Users entity 컬럼 주석 추가

* feat: ValidationPipe 전역 설정

* feat: AppModule ClassSerializerInterceptor 추가

* feat: 사용자 생성 DTO 추가

* feat: 사용자 생성 API 구현
  • Loading branch information
eeseung authored May 31, 2024
1 parent 3cac86b commit cf18d52
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 23 deletions.
11 changes: 9 additions & 2 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Module } from '@nestjs/common';
import { ClassSerializerInterceptor, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
Expand Down Expand Up @@ -39,6 +39,7 @@ import {
ENV_DB_PORT_KEY,
ENV_DB_USERNAME_KEY,
} from './common/const/env-keys.const';
import { APP_INTERCEPTOR } from '@nestjs/core';

@Module({
imports: [
Expand Down Expand Up @@ -94,6 +95,12 @@ import {
KmMedicinesModule,
],
controllers: [AppController],
providers: [AppService],
providers: [
AppService,
{
provide: APP_INTERCEPTOR,
useClass: ClassSerializerInterceptor,
},
],
})
export class AppModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ValidationArguments } from 'class-validator';

export const lengthValidationMessage = (args: ValidationArguments) => {
if (args.constraints.length === 2) {
return `${args.property}${args.constraints[0]}~${args.constraints[1]}글자를 입력해 주세요.`;
} else {
return `${args.property}은 최소 ${args.constraints[0]}글자를 입력해 주세요.`;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ValidationArguments } from 'class-validator';

export const stringValidationMessage = (args: ValidationArguments) => {
return `${args.property}에 String을 입력해 주세요.`;
};
12 changes: 12 additions & 0 deletions backend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,22 @@ import {
ENV_SWAGGER_PASSWORD_KEY,
ENV_SWAGGER_USER_KEY,
} from './common/const/env-keys.const';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
const app = await NestFactory.create(AppModule);

app.useGlobalPipes(
new ValidationPipe({
transform: true,
transformOptions: {
enableImplicitConversion: true,
},
whitelist: true,
forbidNonWhitelisted: true,
}),
);

app.use(
['/api/docs'],
expressBasicAuth({
Expand Down
46 changes: 46 additions & 0 deletions backend/src/users/dto/create-user.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { ApiProperty, PickType } from '@nestjs/swagger';
import { Users } from '../entity/users.entity';
import { IsString, Length, Matches } from 'class-validator';
import { stringValidationMessage } from 'src/common/validation-message/string-validation.message';
import { lengthValidationMessage } from 'src/common/validation-message/length-validation.message';

export class CreateUserDto extends PickType(Users, [
'account',
'password',
'permission',
'name',
]) {
@ApiProperty({
description: '사용자 계정',
example: 'test',
})
@IsString({ message: stringValidationMessage })
@Length(4, 20, { message: lengthValidationMessage })
account: string;

@ApiProperty({
description: '사용자 비밀번호',
example: 'test123',
})
@IsString({ message: stringValidationMessage })
password: string;

@ApiProperty({
description: '사용자 권한',
example: '9000',
pattern: '^[4-9]000/',
})
@IsString({ message: stringValidationMessage })
@Matches(/^[4-9]000$/, {
message: '권한은 4000~9000 형식이어야 합니다.',
})
permission: string;

@ApiProperty({
description: '사용자 이름',
example: '테스트',
})
@IsString({ message: stringValidationMessage })
@Length(2, 20, { message: lengthValidationMessage })
name: string;
}
29 changes: 11 additions & 18 deletions backend/src/users/entity/users.entity.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,25 @@
import { IsString } from 'class-validator';
import { Exclude } from 'class-transformer';
import { BaseModel } from 'src/common/entity/base.entity';
import { Column, Entity } from 'typeorm';

@Entity()
export class Users extends BaseModel {
@Column({
length: 20,
unique: true,
})
@IsString()
/** 사용자 게정 */
@Column({ length: 20, unique: true })
account: string;

@Column({
length: 100,
/** 사용자 비밀번호 */
@Column({ length: 100 })
@Exclude({
toPlainOnly: true,
})
@IsString()
password: string;

@Column('char', {
length: 4,
})
@IsString()
/** 사용자 권한 */
@Column('char', { length: 4 })
permission: string;

@Column({
length: 20,
unique: true,
})
@IsString()
/** 사용자 이름 */
@Column({ length: 20, unique: true })
name: string;
}
17 changes: 16 additions & 1 deletion backend/src/users/users.controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
import { Controller } from '@nestjs/common';
import { Body, Controller, HttpStatus, Post } from '@nestjs/common';
import { UsersService } from './users.service';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { CreateUserDto } from './dto/create-user.dto';

@ApiTags('User')
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}

@Post()
@ApiOperation({
summary: '사용자 생성',
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: '이미 존재하는 account/name 입니다.',
})
async postUser(@Body() body: CreateUserDto): Promise<CreateUserDto> {
return this.usersService.createUser(body);
}
}
47 changes: 45 additions & 2 deletions backend/src/users/users.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,47 @@
import { Injectable } from '@nestjs/common';
import { BadRequestException, Injectable } from '@nestjs/common';
import { Users } from './entity/users.entity';
import { Repository } from 'typeorm';
import * as bcrypt from 'bcrypt';
import { InjectRepository } from '@nestjs/typeorm';
import { CreateUserDto } from './dto/create-user.dto';

@Injectable()
export class UsersService {}
export class UsersService {
constructor(
@InjectRepository(Users)
private readonly usersRepository: Repository<Users>,
) {}

async createUser(user: CreateUserDto): Promise<CreateUserDto> {
const accountExists = await this.usersRepository.exists({
where: {
account: user.account,
},
});

if (accountExists) {
throw new BadRequestException('이미 존재하는 account 입니다.');
}

const nameExists = await this.usersRepository.exists({
where: {
name: user.name,
},
});

if (nameExists) {
throw new BadRequestException('이미 존재하는 name 입니다.');
}

const userObject = this.usersRepository.create({
account: user.account,
password: await bcrypt.hash(user.password, 10),
permission: user.permission,
name: user.name,
});

const newUser = await this.usersRepository.save(userObject);

return newUser;
}
}

0 comments on commit cf18d52

Please sign in to comment.