diff --git a/adapters/in-web/src/main/kotlin/com/pokit/user/UserController.kt b/adapters/in-web/src/main/kotlin/com/pokit/user/UserController.kt index 4181d9ff..974aea16 100644 --- a/adapters/in-web/src/main/kotlin/com/pokit/user/UserController.kt +++ b/adapters/in-web/src/main/kotlin/com/pokit/user/UserController.kt @@ -3,9 +3,13 @@ package com.pokit.user import com.pokit.auth.config.ErrorOperation import com.pokit.auth.model.PrincipalUser import com.pokit.auth.model.toDomain -import com.pokit.user.dto.ApiSignUpRequest +import com.pokit.common.wrapper.ResponseWrapper.wrapOk +import com.pokit.user.dto.request.ApiSignUpRequest +import com.pokit.user.dto.request.ApiUpdateNicknameRequest +import com.pokit.user.dto.request.toDto import com.pokit.user.dto.response.CheckDuplicateNicknameResponse -import com.pokit.user.dto.response.SignUpResponse +import com.pokit.user.dto.response.UserResponse +import com.pokit.user.dto.response.toResponse import com.pokit.user.exception.UserErrorCode import com.pokit.user.port.`in`.UserUseCase import io.swagger.v3.oas.annotations.Operation @@ -25,20 +29,34 @@ class UserController( fun signUp( @AuthenticationPrincipal principalUser: PrincipalUser, @Valid @RequestBody request: ApiSignUpRequest - ): ResponseEntity { + ): ResponseEntity { val user = principalUser.toDomain() - val signUpRequest = request.toSignUpRequest() - val response = userUseCase.signUp(user, signUpRequest) - return ResponseEntity.ok(response) + return userUseCase.signUp(user, request.toDto()) + .toResponse() + .wrapOk() } @GetMapping("/duplicate/{nickname}") @Operation(summary = "닉네임 중복 체크 API") @ErrorOperation(UserErrorCode::class) fun checkNickname( - @AuthenticationPrincipal principalUser: PrincipalUser, @PathVariable("nickname") nickname: String ): ResponseEntity { - return ResponseEntity.ok(userUseCase.checkDuplicateNickname(nickname)) + return userUseCase.checkDuplicateNickname(nickname) + .toResponse() + .wrapOk() + } + + @PutMapping("/nickname") + @Operation(summary = "닉네임 수정 API") + @ErrorOperation(UserErrorCode::class) + fun updateNickname( + @AuthenticationPrincipal principalUser: PrincipalUser, + @Valid @RequestBody request: ApiUpdateNicknameRequest + ): ResponseEntity { + val user = principalUser.toDomain() + return userUseCase.updateNickname(user, request.toDto()) + .toResponse() + .wrapOk() } } diff --git a/adapters/in-web/src/main/kotlin/com/pokit/user/dto/ApiSignUpRequest.kt b/adapters/in-web/src/main/kotlin/com/pokit/user/dto/request/ApiSignUpRequest.kt similarity index 58% rename from adapters/in-web/src/main/kotlin/com/pokit/user/dto/ApiSignUpRequest.kt rename to adapters/in-web/src/main/kotlin/com/pokit/user/dto/request/ApiSignUpRequest.kt index e90f7e2a..3ab709f8 100644 --- a/adapters/in-web/src/main/kotlin/com/pokit/user/dto/ApiSignUpRequest.kt +++ b/adapters/in-web/src/main/kotlin/com/pokit/user/dto/request/ApiSignUpRequest.kt @@ -1,6 +1,5 @@ -package com.pokit.user.dto +package com.pokit.user.dto.request -import com.pokit.user.dto.request.SignUpRequest import com.pokit.user.model.InterestType import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.Size @@ -11,15 +10,12 @@ data class ApiSignUpRequest( val nickName: String, @Size(min = 1, max = 3, message = "최소 하나 이상, 세개 이하만 가능합니다.") val interests: List -) { - fun toSignUpRequest(): SignUpRequest { - val interestTypes = interests.map { - InterestType.of(it) - } +) - return SignUpRequest( - nickName = nickName, - interests = interestTypes - ) - } +internal fun ApiSignUpRequest.toDto(): SignUpRequest { + val interestTypes = interests.map { InterestType.of(it) } + return SignUpRequest( + nickName = this.nickName, + interests = interestTypes + ) } diff --git a/adapters/in-web/src/main/kotlin/com/pokit/user/dto/request/ApiUpdateNicknameRequest.kt b/adapters/in-web/src/main/kotlin/com/pokit/user/dto/request/ApiUpdateNicknameRequest.kt new file mode 100644 index 00000000..1a90ffb3 --- /dev/null +++ b/adapters/in-web/src/main/kotlin/com/pokit/user/dto/request/ApiUpdateNicknameRequest.kt @@ -0,0 +1,14 @@ +package com.pokit.user.dto.request + +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size + +data class ApiUpdateNicknameRequest( + @field:NotBlank(message = "닉네임은 필수값입니다.") + @field:Size(max = 10, message = "닉네임은 10자 이하만 가능합니다.") + val nickname: String +) + +internal fun ApiUpdateNicknameRequest.toDto() = UpdateNicknameRequest( + nickname = this.nickname +) diff --git a/application/src/main/kotlin/com/pokit/user/port/in/UserUseCase.kt b/application/src/main/kotlin/com/pokit/user/port/in/UserUseCase.kt index 300a4aac..62b88b69 100644 --- a/application/src/main/kotlin/com/pokit/user/port/in/UserUseCase.kt +++ b/application/src/main/kotlin/com/pokit/user/port/in/UserUseCase.kt @@ -1,13 +1,13 @@ package com.pokit.user.port.`in` import com.pokit.user.dto.request.SignUpRequest -import com.pokit.user.dto.response.CheckDuplicateNicknameResponse -import com.pokit.user.dto.response.SignUpResponse +import com.pokit.user.dto.request.UpdateNicknameRequest import com.pokit.user.model.User interface UserUseCase { - fun signUp(user: User, request: SignUpRequest): SignUpResponse + fun signUp(user: User, request: SignUpRequest): User - fun checkDuplicateNickname(nickname: String): CheckDuplicateNicknameResponse + fun checkDuplicateNickname(nickname: String): Boolean + fun updateNickname(user: User, request: UpdateNicknameRequest): User } diff --git a/application/src/main/kotlin/com/pokit/user/port/service/UserService.kt b/application/src/main/kotlin/com/pokit/user/port/service/UserService.kt index b159e5f7..8edcbf32 100644 --- a/application/src/main/kotlin/com/pokit/user/port/service/UserService.kt +++ b/application/src/main/kotlin/com/pokit/user/port/service/UserService.kt @@ -1,9 +1,9 @@ package com.pokit.user.port.service +import com.pokit.common.exception.ClientValidationException import com.pokit.common.exception.NotFoundCustomException import com.pokit.user.dto.request.SignUpRequest -import com.pokit.user.dto.response.CheckDuplicateNicknameResponse -import com.pokit.user.dto.response.SignUpResponse +import com.pokit.user.dto.request.UpdateNicknameRequest import com.pokit.user.exception.UserErrorCode import com.pokit.user.model.User import com.pokit.user.port.`in`.UserUseCase @@ -17,7 +17,7 @@ class UserService( private val userPort: UserPort ) : UserUseCase { @Transactional - override fun signUp(user: User, request: SignUpRequest): SignUpResponse { + override fun signUp(user: User, request: SignUpRequest): User { user.modifyUser( request.nickName, ) @@ -25,12 +25,24 @@ class UserService( val savedUser = userPort.register(user) ?: throw NotFoundCustomException(UserErrorCode.NOT_FOUND_USER) - return SignUpResponse(savedUser.id) + return savedUser } - override fun checkDuplicateNickname(nickname: String) - : CheckDuplicateNicknameResponse { - val isDuplicate = userPort.checkByNickname(nickname) - return CheckDuplicateNicknameResponse(isDuplicate) + override fun checkDuplicateNickname(nickname: String): Boolean { + return userPort.checkByNickname(nickname) + } + + @Transactional + override fun updateNickname(user: User, request: UpdateNicknameRequest): User { + val findUser = (userPort.loadById(user.id) + ?: throw NotFoundCustomException(UserErrorCode.NOT_FOUND_USER)) + + val isDuplicate = userPort.checkByNickname(request.nickname) + if (isDuplicate) { + throw ClientValidationException(UserErrorCode.ALREADY_EXISTS_NICKNAME) + } + + findUser.modifyUser(request.nickname) + return userPort.persist(findUser) } } diff --git a/application/src/test/kotlin/com/pokit/user/port/service/UserServiceTest.kt b/application/src/test/kotlin/com/pokit/user/port/service/UserServiceTest.kt index bc3d96f0..d21537d5 100644 --- a/application/src/test/kotlin/com/pokit/user/port/service/UserServiceTest.kt +++ b/application/src/test/kotlin/com/pokit/user/port/service/UserServiceTest.kt @@ -1,7 +1,9 @@ package com.pokit.user.port.service +import com.pokit.common.exception.ClientValidationException import com.pokit.common.exception.NotFoundCustomException import com.pokit.user.UserFixture +import com.pokit.user.dto.request.UpdateNicknameRequest import com.pokit.user.model.User import com.pokit.user.port.out.UserPort import io.kotest.assertions.throwables.shouldThrow @@ -25,8 +27,8 @@ class UserServiceTest : BehaviorSpec({ When("수정하려는 정보를 받으면") { val response = userService.signUp(user, request) Then("회원 정보가 수정되고 수정된 회원의 id가 반환된다.") { - response.userId shouldBe modifieUser.id - user.nickName shouldBe modifieUser.nickName + response.id shouldBe modifieUser.id + response.nickName shouldBe modifieUser.nickName } } @@ -49,14 +51,43 @@ class UserServiceTest : BehaviorSpec({ When("해당 닉네임의 유저가 존재하지 않으면") { val response = userService.checkDuplicateNickname(notDuplicateName) Then("false가 반환된다") { - response.isDuplicate shouldBe false + response shouldBe false } } When("해당 닉네임의 유저가 존재하면 ") { val response = userService.checkDuplicateNickname(duplicateName) Then("true가 반환된다.") { - response.isDuplicate shouldBe true + response shouldBe true + } + } + } + + Given("닉네임을 수정할 때") { + val user = UserFixture.getUser() + val request = UpdateNicknameRequest( + nickname = "수정하려는 닉네임" + ) + val invalidRequest = UpdateNicknameRequest( + nickname = "이미 사용중인 닉네임" + ) + + every { userPort.loadById(user.id) } returns user + every { userPort.checkByNickname(request.nickname) } returns false + every { userPort.checkByNickname(invalidRequest.nickname) } returns true + every { userPort.persist(any(User::class)) } returns user + + When("사용 가능한 닉네임으로 수정하려 하면 ") { + val userResponse = userService.updateNickname(user, request) + Then("수정된 유저 도메인을 반환한다.") { + userResponse.nickName shouldBe request.nickname + } + } + When("이미 사용중인 닉네임으로 수정하려 하면") { + Then("예외가 발생한다.") { + shouldThrow { + userService.updateNickname(user, invalidRequest) + } } } } diff --git a/domain/src/main/kotlin/com/pokit/user/dto/request/UpdateNicknameRequest.kt b/domain/src/main/kotlin/com/pokit/user/dto/request/UpdateNicknameRequest.kt new file mode 100644 index 00000000..fd806b32 --- /dev/null +++ b/domain/src/main/kotlin/com/pokit/user/dto/request/UpdateNicknameRequest.kt @@ -0,0 +1,5 @@ +package com.pokit.user.dto.request + +data class UpdateNicknameRequest( + val nickname: String +) diff --git a/domain/src/main/kotlin/com/pokit/user/dto/response/CheckDuplicateNicknameResponse.kt b/domain/src/main/kotlin/com/pokit/user/dto/response/CheckDuplicateNicknameResponse.kt index 0b8397ef..d06493b1 100644 --- a/domain/src/main/kotlin/com/pokit/user/dto/response/CheckDuplicateNicknameResponse.kt +++ b/domain/src/main/kotlin/com/pokit/user/dto/response/CheckDuplicateNicknameResponse.kt @@ -3,3 +3,7 @@ package com.pokit.user.dto.response data class CheckDuplicateNicknameResponse( val isDuplicate: Boolean ) + +fun Boolean.toResponse() = CheckDuplicateNicknameResponse( + isDuplicate = this +) diff --git a/domain/src/main/kotlin/com/pokit/user/dto/response/UserResponse.kt b/domain/src/main/kotlin/com/pokit/user/dto/response/UserResponse.kt new file mode 100644 index 00000000..b52646a3 --- /dev/null +++ b/domain/src/main/kotlin/com/pokit/user/dto/response/UserResponse.kt @@ -0,0 +1,15 @@ +package com.pokit.user.dto.response + +import com.pokit.user.model.User + +data class UserResponse ( + val id: Long, + val email: String, + val nickname: String +) + +fun User.toResponse() = UserResponse( + id = this.id, + email = this.email, + nickname = this.nickName +) diff --git a/domain/src/main/kotlin/com/pokit/user/exception/UserErrorCode.kt b/domain/src/main/kotlin/com/pokit/user/exception/UserErrorCode.kt index 60813b66..1293f25f 100644 --- a/domain/src/main/kotlin/com/pokit/user/exception/UserErrorCode.kt +++ b/domain/src/main/kotlin/com/pokit/user/exception/UserErrorCode.kt @@ -8,5 +8,6 @@ enum class UserErrorCode( ) : ErrorCode { INVALID_EMAIL("올바르지 않은 이메일 형식의 유저입니다.", "U_001"), INVALID_INTEREST_TYPE("관심사가 잘못되었습니다.", "U_002"), - NOT_FOUND_USER("존재하지 않는 회원입니다.", "U_003") + NOT_FOUND_USER("존재하지 않는 회원입니다.", "U_003"), + ALREADY_EXISTS_NICKNAME("이미 존재하는 닉네임입니다.", "U_004") }