Skip to content

Commit

Permalink
[feat #183] 프로필 수정 API (#184)
Browse files Browse the repository at this point in the history
* feat : UserImage 엔티티, 도메인

* feat : 프로필 수정 API

* feat : 프로필 수정 usecase 구현

* feat : 닉네임 중복 체크 쿼리(본인 제외한 중복)

* feat : test 코드 의존 클래스 추가
  • Loading branch information
dlswns2480 authored Nov 28, 2024
1 parent 22772a0 commit 223496e
Show file tree
Hide file tree
Showing 17 changed files with 160 additions and 12 deletions.
16 changes: 12 additions & 4 deletions adapters/in-web/src/main/kotlin/com/pokit/user/UserController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import com.pokit.auth.config.ErrorOperation
import com.pokit.auth.model.PrincipalUser
import com.pokit.auth.model.toDomain
import com.pokit.common.wrapper.ResponseWrapper.wrapOk
import com.pokit.user.dto.request.ApiCreateFcmTokenRequest
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.request.*
import com.pokit.user.dto.response.CheckDuplicateNicknameResponse
import com.pokit.user.dto.response.InterestTypeResponse
import com.pokit.user.dto.response.UserResponse
Expand Down Expand Up @@ -90,4 +87,15 @@ class UserController(
.toResponse()
.wrapOk()
}

@PutMapping
@Operation(summary = "유저 프로필 수정 API")
fun updateProfile(
@AuthenticationPrincipal user: PrincipalUser,
@RequestBody request: UpdateProfileRequest
): ResponseEntity<UserResponse> {
return userUseCase.updateProfile(user.id, request.toDto())
.toResponse()
.wrapOk()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.pokit.user.dto.request

import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Size

data class UpdateProfileRequest(
val profileImageId: Int,
@field:NotBlank(message = "닉네임은 필수값입니다.")
@field:Size(max = 10, message = "닉네임은 10자 이하만 가능합니다.")
val nickname: String
)

internal fun UpdateProfileRequest.toDto() = UserCommand(
profileImageId = this.profileImageId,
nickname = this.nickname
)
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ class UserAdapter(
?.run { toDomain() }

override fun checkByNickname(nickname: String) = userRepository.existsByNickname(nickname)

override fun checkByNickname(nickname: String, userId: Long) =
userRepository.existsByNicknameAndIdNot(nickname, userId)

override fun delete(user: User) {
userRepository.findByIdOrNull(user.id)
?.delete()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.pokit.out.persistence.user.impl

import com.pokit.out.persistence.user.persist.UserImageRepository
import com.pokit.out.persistence.user.persist.toDomain
import com.pokit.user.model.UserImage
import com.pokit.user.port.out.UserImagePort
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Repository

@Repository
class UserImageAdapter(
private val userImageRepository: UserImageRepository
) : UserImagePort {
override fun loadById(id: Int): UserImage? {
return userImageRepository.findByIdOrNull(id)
?.toDomain()
}

override fun loadAll(): List<UserImage> {
return userImageRepository.findAll()
.map { it.toDomain() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ class UserEntity(
var registered: Boolean,

@Column(name = "sub")
var sub: String?
var sub: String?,

@OneToOne
@JoinColumn(name = "image_id", foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT))
val image: UserImageEntity?
) : BaseEntity() {
fun delete() {
this.deleted = true
Expand All @@ -50,7 +54,8 @@ class UserEntity(
nickname = user.nickName,
authPlatform = user.authPlatform,
registered = user.registered,
sub = user.sub
sub = user.sub,
image = user.image?.let { UserImageEntity.of(it) }
)
}
}
Expand All @@ -62,5 +67,6 @@ fun UserEntity.toDomain() = User(
nickName = this.nickname,
authPlatform = this.authPlatform,
registered = this.registered,
sub = this.sub
sub = this.sub,
image = this.image?.toDomain()
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.pokit.out.persistence.user.persist

import com.pokit.user.model.UserImage
import jakarta.persistence.*

@Table(name = "USER_IMAGE")
@Entity
class UserImageEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
val id: Int = 0,

@Column(name = "url")
val url: String
) {
companion object {
fun of(userImage: UserImage) =
UserImageEntity(
id = userImage.id,
url = userImage.url
)
}
}

fun UserImageEntity.toDomain() = UserImage(this.id, this.url)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.pokit.out.persistence.user.persist

import org.springframework.data.jpa.repository.JpaRepository

interface UserImageRepository : JpaRepository<UserImageEntity, Int> {
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ interface UserRepository : JpaRepository<UserEntity, Long> {

fun existsByNickname(nickname: String): Boolean

fun existsByNicknameAndIdNot(nickname: String, userId: Long): Boolean

fun findByIdAndDeleted(id: Long, deleted: Boolean): UserEntity?

fun findByDeleted(deleted: Boolean): List<UserEntity>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.pokit.user.port.`in`
import com.pokit.user.dto.request.CreateFcmTokenRequest
import com.pokit.user.dto.request.SignUpRequest
import com.pokit.user.dto.request.UpdateNicknameRequest
import com.pokit.user.dto.request.UserCommand
import com.pokit.user.model.FcmToken
import com.pokit.user.model.User

Expand All @@ -18,4 +19,6 @@ interface UserUseCase {
fun createFcmToken(userId: Long, request: CreateFcmTokenRequest): FcmToken

fun getUserInfo(userId: Long): User

fun updateProfile(userId: Long, command: UserCommand): User
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.pokit.user.port.out

import com.pokit.user.model.UserImage

interface UserImagePort {
fun loadById(id: Int): UserImage?

fun loadAll(): List<UserImage>
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ interface UserPort {

fun checkByNickname(nickname: String): Boolean

fun checkByNickname(nickname: String, userId: Long): Boolean

fun delete(user: User)

fun loadAllIds(): List<Long>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import com.pokit.common.exception.NotFoundCustomException
import com.pokit.user.dto.request.CreateFcmTokenRequest
import com.pokit.user.dto.request.SignUpRequest
import com.pokit.user.dto.request.UpdateNicknameRequest
import com.pokit.user.dto.request.UserCommand
import com.pokit.user.exception.UserErrorCode
import com.pokit.user.model.FcmToken
import com.pokit.user.model.User
import com.pokit.user.port.`in`.UserUseCase
import com.pokit.user.port.out.FcmTokenPort
import com.pokit.user.port.out.UserImagePort
import com.pokit.user.port.out.UserPort
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
Expand All @@ -26,7 +28,8 @@ class UserService(
private val userPort: UserPort,
private val categoryPort: CategoryPort,
private val categoryImagePort: CategoryImagePort,
private val fcmTokenPort: FcmTokenPort
private val fcmTokenPort: FcmTokenPort,
private val userImagePort: UserImagePort
) : UserUseCase {
companion object {
private const val UNCATEGORIZED_IMAGE_ID = 1
Expand Down Expand Up @@ -92,4 +95,21 @@ class UserService(
return userPort.loadById(userId)
?: throw NotFoundCustomException(UserErrorCode.NOT_FOUND_USER)
}

@Transactional
override fun updateProfile(userId: Long, command: UserCommand): User {
val user = userPort.loadById(userId)
?: throw NotFoundCustomException(UserErrorCode.NOT_FOUND_USER)

val image = userImagePort.loadById(command.profileImageId)
?: throw NotFoundCustomException(UserErrorCode.NOT_FOUND_PROFILE_IMAGE)

val isDuplicate = userPort.checkByNickname(command.nickname, userId)
if (isDuplicate) {
throw ClientValidationException(UserErrorCode.ALREADY_EXISTS_NICKNAME)
}

user.modifyProfile(image, command.nickname)
return userPort.persist(user)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import com.pokit.token.model.AuthPlatform
import com.pokit.user.UserFixture
import com.pokit.user.dto.request.UpdateNicknameRequest
import com.pokit.user.model.User
import com.pokit.user.model.UserImage
import com.pokit.user.port.out.FcmTokenPort
import com.pokit.user.port.out.UserImagePort
import com.pokit.user.port.out.UserPort
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.BehaviorSpec
Expand All @@ -23,12 +25,14 @@ class UserServiceTest : BehaviorSpec({
val categoryPort = mockk<CategoryPort>()
val categoryImagePort = mockk<CategoryImagePort>()
val fcmTokenPort = mockk<FcmTokenPort>()
val userService = UserService(userPort, categoryPort, categoryImagePort, fcmTokenPort)
val userImagePort = mockk<UserImagePort>()
val userService = UserService(userPort, categoryPort, categoryImagePort, fcmTokenPort, userImagePort)
Given("회원을 등록할 때") {
val user = UserFixture.getUser()
val invalidUser = UserFixture.getInvalidUser()
val request = UserFixture.getSignUpRequest()
val modifieUser = User(user.id, user.email, user.role, request.nickName, AuthPlatform.GOOGLE, sub = "sub")
val userImage = UserImage(1, "url")
val modifieUser = User(user.id, user.email, user.role, request.nickName, AuthPlatform.GOOGLE, sub = "sub", image = userImage)
val image = CategoryImage(1, "https://www.image.com")
val unCategorized = CategoryFixture.getUnCategorized(user.id, image)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.pokit.user.dto.request

data class UserCommand(
val profileImageId: Int,
val nickname: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ enum class UserErrorCode(
INVALID_INTEREST_TYPE("관심사가 잘못되었습니다.", "U_002"),
NOT_FOUND_USER("존재하지 않는 회원입니다.", "U_003"),
ALREADY_EXISTS_NICKNAME("이미 존재하는 닉네임입니다.", "U_004"),
ALREADY_REGISTERED("이미 회원가입 한 유저입니다.", "U_005")
ALREADY_REGISTERED("이미 회원가입 한 유저입니다.", "U_005"),
NOT_FOUND_PROFILE_IMAGE("존재하지 않는 프로필 이미지입니다.", "U_006"),
}
8 changes: 7 additions & 1 deletion domain/src/main/kotlin/com/pokit/user/model/User.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ data class User(
var nickName: String = "NOT_REGISTERED",
val authPlatform: AuthPlatform,
var registered: Boolean = false,
var sub: String?
var sub: String?,
var image: UserImage? = null
) {
fun register(nickName: String) {
this.nickName = nickName
Expand All @@ -27,6 +28,11 @@ data class User(
this.nickName = nickName
}

fun modifyProfile(image: UserImage, nickname: String) {
this.image = image
this.nickName = nickname
}

init {
val pattern =
Pattern.compile(
Expand Down
6 changes: 6 additions & 0 deletions domain/src/main/kotlin/com/pokit/user/model/UserImage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.pokit.user.model

data class UserImage(
val id: Int,
val url: String
)

0 comments on commit 223496e

Please sign in to comment.