diff --git a/src/controllers/members.controller.ts b/src/controllers/members.controller.ts new file mode 100644 index 0000000..67bd1d6 --- /dev/null +++ b/src/controllers/members.controller.ts @@ -0,0 +1,70 @@ +import { + Body, + Controller, + Delete, + ForbiddenException, + Get, + NotFoundException, + Param, + ParseIntPipe, + Patch, +} from "@nestjs/common"; +import * as bcrypt from "bcrypt"; + +import { CurrentMemberDecorator } from "@APP/common/decorators/current-member.decorator"; +import { UpdateMemberDto } from "@APP/dtos/update-member.dto"; +import { MembersService } from "@APP/services/members.service"; + +@Controller("members") +export class MembersController { + constructor(private readonly membersService: MembersService) {} + + @Get(":memberId") + async getMember(@Param("memberId", new ParseIntPipe()) memberId: number) { + return await this.membersService.findById(memberId); + } + + @Delete(":memberId") + async deleteMember( + @CurrentMemberDecorator("id") currentMemberId: number, + @Param("memberId", new ParseIntPipe()) memberId: number, + ) { + const member = await this.membersService.findById(memberId); + + if (!member) { + throw new NotFoundException("존재하지 않는 회원입니다."); + } + + if (currentMemberId !== memberId) { + throw new ForbiddenException("권한이 없습니다."); + } + + await this.membersService.deleteById(memberId); + } + + @Patch(":memberId") + async patchMember( + @CurrentMemberDecorator("id") currentMemberId: number, + @Param("memberId", new ParseIntPipe()) memberId: number, + @Body() dto: UpdateMemberDto, + ) { + const member = await this.membersService.findById(memberId); + + if (!member) { + throw new NotFoundException("존재하지 않는 회원입니다."); + } + + if (currentMemberId !== memberId) { + throw new ForbiddenException("권한이 없습니다."); + } + + if (dto.password) { + const hashedPassword = await bcrypt.hash(dto.password, 10); + dto.password = hashedPassword; + } + + await this.membersService.updateById(memberId, dto); + + return await this.membersService.findById(memberId); + } +} diff --git a/src/dtos/update-member.dto.ts b/src/dtos/update-member.dto.ts new file mode 100644 index 0000000..d6207aa --- /dev/null +++ b/src/dtos/update-member.dto.ts @@ -0,0 +1,7 @@ +import { OmitType, PartialType } from "@nestjs/swagger"; + +import { RegisterMemberDto } from "./register-member.dto"; + +export class UpdateMemberDto extends PartialType( + OmitType(RegisterMemberDto, ["email"]), +) {} diff --git a/src/entities/member.entity.ts b/src/entities/member.entity.ts index e2b22ef..aaaa6a0 100644 --- a/src/entities/member.entity.ts +++ b/src/entities/member.entity.ts @@ -1,4 +1,5 @@ -import { IsEmail, IsNotEmpty, IsString } from "class-validator"; +import { Exclude } from "class-transformer"; +import { IsEmail, IsNotEmpty, IsString, Length } from "class-validator"; import { Column, CreateDateColumn, @@ -31,6 +32,10 @@ export class MemberEntity { @IsNotEmpty() @IsString() + @Exclude({ + toPlainOnly: true, + }) + @Length(4, 20) @Column({ type: "varchar", length: PASSWORD_HASH_LENGTH, nullable: false }) password!: string; // 비밀번호 diff --git a/src/modules/auth.module.ts b/src/modules/auth.module.ts index ccbc801..2a87f6e 100644 --- a/src/modules/auth.module.ts +++ b/src/modules/auth.module.ts @@ -11,6 +11,6 @@ import { MembersModule } from "./members.module"; imports: [JwtModule.register({}), MembersModule], controllers: [AuthController], providers: [AuthService, MailsService], - exports: [], + exports: [AuthService], }) export class AuthModule {} diff --git a/src/services/members.service.ts b/src/services/members.service.ts index f3a97d4..6e5174f 100644 --- a/src/services/members.service.ts +++ b/src/services/members.service.ts @@ -3,6 +3,7 @@ import { Injectable } from "@nestjs/common"; import { BusinessErrorException } from "@APP/common/exception/business-error.exception"; import { MemberErrorCode } from "@APP/common/exception/error-code"; import { RegisterMemberDto } from "@APP/dtos/register-member.dto"; +import { UpdateMemberDto } from "@APP/dtos/update-member.dto"; import { VerifyEmailDto } from "@APP/dtos/verify-email.dto"; import { MembersRepository } from "@APP/repositories/members.repository"; import { RefreshTokenRepository } from "@APP/repositories/refresh-token.repository"; @@ -24,6 +25,29 @@ export class MembersService { }); } + async findById(memberId: number) { + return this.membersRepository.findOne({ + where: { + id: memberId, + }, + }); + } + + async updateById(memberId: number, dto: UpdateMemberDto) { + return this.membersRepository.update( + { + id: memberId, + }, + dto, + ); + } + + async deleteById(memberId: number) { + return this.membersRepository.delete({ + id: memberId, + }); + } + async verifyEmail(dto: VerifyEmailDto) { const member = await this.findByEmail(dto.email);