Skip to content

Commit 7ce9e94

Browse files
authored
Merge pull request #68 from CodeVac513/feature/register-admin
✨ feat: 관리자 회원 가입 API 구현
2 parents 082b963 + 781ba2a commit 7ce9e94

11 files changed

+383
-53
lines changed

server/package-lock.json

+207-53
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
"@nestjs/platform-express": "^10.0.0",
2828
"@nestjs/swagger": "^8.0.1",
2929
"@nestjs/typeorm": "^10.0.2",
30+
"bcrypt": "^5.1.1",
31+
"class-transformer": "^0.5.1",
32+
"class-validator": "^0.14.1",
3033
"cross-env": "^7.0.3",
3134
"ioredis": "^5.4.1",
3235
"mysql2": "^3.11.3",
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {
2+
ApiBadRequestResponse,
3+
ApiConflictResponse,
4+
ApiCreatedResponse,
5+
ApiOperation,
6+
} from '@nestjs/swagger';
7+
import { applyDecorators } from '@nestjs/common';
8+
9+
export function ApiPostRegisterAdmin() {
10+
return applyDecorators(
11+
ApiOperation({
12+
summary: `관리자 회원 가입 API`,
13+
}),
14+
ApiCreatedResponse({
15+
description: 'Created',
16+
schema: {
17+
example: {
18+
message: '성공적으로 관리자 계정이 생성되었습니다.',
19+
},
20+
},
21+
}),
22+
ApiBadRequestResponse({
23+
description: 'Bad Request',
24+
schema: {
25+
example: {
26+
message: '오류 메세지 출력',
27+
},
28+
},
29+
}),
30+
ApiConflictResponse({
31+
description: 'Conflict',
32+
schema: {
33+
example: {
34+
message: '이미 존재하는 계정입니다.',
35+
},
36+
},
37+
}),
38+
);
39+
}
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import {
2+
Body,
3+
Controller,
4+
Inject,
5+
Post,
6+
UsePipes,
7+
ValidationPipe,
8+
} from '@nestjs/common';
9+
import { AccountService } from './account.service';
10+
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
11+
import { Logger } from 'winston';
12+
import { RegisterAdminDto } from './dto/register-admin.dto';
13+
import { ApiTags } from '@nestjs/swagger';
14+
import { ApiPostRegisterAdmin } from './account.api-docs';
15+
import { ApiResponse } from '../common/response/common.response';
16+
17+
@ApiTags('Account')
18+
@Controller()
19+
export class AccountController {
20+
constructor(
21+
private readonly loginService: AccountService,
22+
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
23+
) {}
24+
25+
@ApiPostRegisterAdmin()
26+
@Post('/register/admin')
27+
@UsePipes(ValidationPipe)
28+
async registerAdmin(@Body() registerAdminDto: RegisterAdminDto) {
29+
const result = await this.loginService.registerAdmin(registerAdminDto);
30+
this.logger.info(`admin 등록: ${result.loginId}`);
31+
return ApiResponse.responseWithNoContent(
32+
'성공적으로 관리자 계정이 생성되었습니다.',
33+
);
34+
}
35+
}
File renamed without changes.

server/src/account/account.module.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Module } from '@nestjs/common';
2+
import { AccountController } from './account.controller';
3+
import { AccountService } from './account.service';
4+
import { TypeOrmModule } from '@nestjs/typeorm';
5+
import { Admin } from './account.entity';
6+
import { AccountRepository } from './account.repository';
7+
import { winstonModule } from '../common/logger/logger.module';
8+
9+
@Module({
10+
imports: [winstonModule, TypeOrmModule.forFeature([Admin])],
11+
controllers: [AccountController],
12+
providers: [AccountService, AccountRepository],
13+
})
14+
export class AccountModule {}
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { DataSource, Repository } from 'typeorm';
3+
import { Admin } from './account.entity';
4+
import { RegisterAdminDto } from './dto/register-admin.dto';
5+
6+
@Injectable()
7+
export class AccountRepository extends Repository<Admin> {
8+
constructor(private dataSource: DataSource) {
9+
super(Admin, dataSource.createEntityManager());
10+
}
11+
12+
async registerAdmin(registerAdminDto: RegisterAdminDto) {
13+
const { loginId, password } = registerAdminDto;
14+
const admin = this.create({
15+
loginId,
16+
password,
17+
});
18+
await this.save(admin);
19+
return admin;
20+
}
21+
}

server/src/account/account.service.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { ConflictException, Injectable } from '@nestjs/common';
2+
import type { RegisterAdminDto } from './dto/register-admin.dto';
3+
import { AccountRepository } from './account.repository';
4+
import * as bcrypt from 'bcrypt';
5+
6+
@Injectable()
7+
export class AccountService {
8+
constructor(private loginRepository: AccountRepository) {}
9+
10+
async registerAdmin(registerAdminDto: RegisterAdminDto) {
11+
let { loginId, password } = registerAdminDto;
12+
13+
const existingAdmin = await this.loginRepository.findOne({
14+
where: { loginId },
15+
});
16+
17+
if (existingAdmin) {
18+
throw new ConflictException('이미 존재하는 아이디입니다.');
19+
}
20+
21+
const saltRounds = 10;
22+
password = await bcrypt.hash(password, saltRounds);
23+
24+
return this.loginRepository.registerAdmin({ loginId, password });
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { IsString, Length, Matches } from 'class-validator';
2+
import { ApiProperty } from '@nestjs/swagger';
3+
4+
const PASSWORD_REG = /^(?=.*[!@#$%^&*()_+])[A-Za-z0-9!@#$%^&*()_+]+$/;
5+
6+
export class RegisterAdminDto {
7+
@ApiProperty({
8+
example: 'minseokjo',
9+
description: '관리자 로그인 아이디를 입력해주세요.',
10+
})
11+
@IsString({
12+
message: '문자열을 입력해주세요',
13+
})
14+
@Length(6, 255, {
15+
message: '아이디의 길이는 6자 이상, 255자 이하로 작성해주세요.',
16+
})
17+
loginId: string;
18+
19+
@ApiProperty({
20+
example: 'heisgoat123!',
21+
description:
22+
'패스워드를 입력해주세요. (최소 6자, 영문/숫자/특수문자로 이루어질 수 있으며 특수문자 1개 이상 포함)',
23+
})
24+
@IsString({
25+
message: '문자열을 입력해주세요',
26+
})
27+
@Matches(PASSWORD_REG, {
28+
message:
29+
'영문, 숫자, 특수문자로 이루어질 수 있으며 특수문자는 1개 이상 포함해주세요.',
30+
})
31+
@Length(6, 60, {
32+
message: '패스워드의 길이는 6자 이상, 60자 이하로 작성해주세요.',
33+
})
34+
password: string;
35+
}

server/src/app.module.ts

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { winstonModule } from './common/logger/logger.module';
33
import { TypeOrmModule } from '@nestjs/typeorm';
44
import { ConfigModule, ConfigService } from '@nestjs/config';
55
import { loadDBSetting } from './common/database/load.config';
6+
import { AccountModule } from './account/account.module';
67
import { RedisModule } from './redis/redis.module';
78

89
@Module({
@@ -20,6 +21,7 @@ import { RedisModule } from './redis/redis.module';
2021
useFactory: (configService: ConfigService) =>
2122
loadDBSetting(configService),
2223
}),
24+
AccountModule,
2325
RedisModule,
2426
],
2527
controllers: [],

server/src/main.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { GlobalExceptionsFilter } from './common/filters/global-exceptions.filte
66
async function bootstrap() {
77
const app = await NestFactory.create(AppModule);
88
setupSwagger(app);
9+
app.setGlobalPrefix('api');
910
app.useGlobalFilters(new GlobalExceptionsFilter());
1011
await app.listen(process.env.PORT ?? 3000);
1112
}

0 commit comments

Comments
 (0)