Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[User] 친구삭제 API 구현 #194

Merged
merged 11 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package kpring.user.dto.response

data class DeleteFriendResponse(
val friendId: Long,
)
12 changes: 11 additions & 1 deletion user/src/main/kotlin/kpring/user/entity/User.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ class User(
@Column(nullable = false)
var password: String,
var file: String?,
@OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = [CascadeType.ALL])
@OneToMany(
fetch = FetchType.LAZY,
mappedBy = "user",
cascade = [CascadeType.ALL],
orphanRemoval = true,
)
val friends: MutableSet<Friend> = mutableSetOf(),
// Other fields and methods...
) {
Expand All @@ -40,6 +45,11 @@ class User(
friends.add(friendRelation)
}

fun removeFriendRelation(friendRelation: Friend) {
friends.remove(friendRelation)
friendRelation.friend.friends.removeIf { it.friend == this }
}

fun updateInfo(
request: UpdateUserProfileRequest,
newPassword: String?,
Expand Down
10 changes: 5 additions & 5 deletions user/src/main/kotlin/kpring/user/exception/UserErrorCode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ enum class UserErrorCode(

ALREADY_FRIEND(HttpStatus.BAD_REQUEST, "4030", "이미 친구입니다."),
NOT_SELF_FOLLOW(HttpStatus.BAD_REQUEST, "4031", "자기자신에게 친구요청을 보낼 수 없습니다"),
FRIENDSHIP_ALREADY_EXISTS_OR_NOT_FOUND(
HttpStatus.BAD_REQUEST,
"4032",
"해당하는 친구신청이 없거나 이미 친구입니다.",
),

FRIENDSHIP_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "4032", "이미 친구입니다."),
FRIENDSHIP_NOT_FOUND(HttpStatus.NOT_FOUND, "4033", "해당하는 친구신청이 없습니다."),

FRIEND_NOT_FOUND(HttpStatus.NOT_FOUND, "4034", "해당하는 친구가 없습니다."),
;

override fun message(): String = this.message
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,9 @@ interface FriendRepository : JpaRepository<Friend, Long> {
friendId: Long,
requestStatus: FriendRequestStatus,
): Friend?

fun findByUserIdAndFriendId(
userId: Long,
friendId: Long,
): Friend?
}
8 changes: 8 additions & 0 deletions user/src/main/kotlin/kpring/user/service/FriendService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ interface FriendService {
friendId: Long,
): AddFriendResponse

/***
* 사용자가 친구와의 관계를 끊을 때 친구 상태를 삭제하는 메서드
*
* @param userId : 로그인한 사용자 ID.
* @param friendId : 기존에 친구였지만 친구관계를 삭제하고자 하는 사용자 ID.
* @return 전에 친구였던 사용자의 ID를 담고 있는 DeleteFriendResponse 리턴
* @throws FRIEND_NOT_FOUND : 친구 중에 friendId 를 가진 사용자가 없을 경우 발생하는 예외
*/
fun deleteFriend(
userId: Long,
friendId: Long,
Expand Down
44 changes: 37 additions & 7 deletions user/src/main/kotlin/kpring/user/service/FriendServiceImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ class FriendServiceImpl(
userId: Long,
friendId: Long,
): AddFriendResponse {
val receivedFriend = getFriendshipWithStatus(userId, friendId, FriendRequestStatus.RECEIVED)
val requestedFriend = getFriendshipWithStatus(friendId, userId, FriendRequestStatus.REQUESTED)
val receivedFriend = getPendingFriendship(userId, friendId)
val requestedFriend = getPendingFriendship(friendId, userId)

receivedFriend.updateRequestStatus(FriendRequestStatus.ACCEPTED)
requestedFriend.updateRequestStatus(FriendRequestStatus.ACCEPTED)
Expand All @@ -66,7 +66,13 @@ class FriendServiceImpl(
userId: Long,
friendId: Long,
): DeleteFriendResponse {
TODO("Not yet implemented")
val user = userServiceImpl.getUser(userId)
userServiceImpl.getUser(friendId)

val userFriendRelation = findAcceptedFriendship(userId, friendId)
user.removeFriendRelation(userFriendRelation)

return DeleteFriendResponse(friendId)
}

fun checkSelfFriend(
Expand All @@ -87,13 +93,37 @@ class FriendServiceImpl(
}
}

private fun getFriendshipWithStatus(
private fun findFriendship(
userId: Long,
friendId: Long,
): Friend {
val friendship =
friendRepository.findByUserIdAndFriendId(userId, friendId)
?: throw ServiceException(UserErrorCode.FRIENDSHIP_NOT_FOUND)
return friendship
}

private fun checkNotAcceptedFriendship(friendship: Friend) {
if (friendship.requestStatus == FriendRequestStatus.ACCEPTED) {
throw ServiceException(UserErrorCode.FRIENDSHIP_ALREADY_EXISTS)
}
}

private fun getPendingFriendship(
userId: Long,
friendId: Long,
): Friend {
val friendship = findFriendship(userId, friendId)
checkNotAcceptedFriendship(friendship)
return friendship
}

private fun findAcceptedFriendship(
userId: Long,
friendId: Long,
requestStatus: FriendRequestStatus,
): Friend {
return friendRepository
.findByUserIdAndFriendIdAndRequestStatus(userId, friendId, requestStatus)
?: throw ServiceException(UserErrorCode.FRIENDSHIP_ALREADY_EXISTS_OR_NOT_FOUND)
.findByUserIdAndFriendIdAndRequestStatus(userId, friendId, FriendRequestStatus.ACCEPTED)
?: throw ServiceException(UserErrorCode.FRIEND_NOT_FOUND)
}
}
154 changes: 150 additions & 4 deletions user/src/test/kotlin/kpring/user/controller/FriendControllerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ import kpring.core.global.exception.ServiceException
import kpring.test.restdoc.dsl.restDoc
import kpring.test.restdoc.json.JsonDataType
import kpring.test.restdoc.json.JsonDataType.Strings
import kpring.user.dto.response.AddFriendResponse
import kpring.user.dto.response.FailMessageResponse
import kpring.user.dto.response.GetFriendRequestResponse
import kpring.user.dto.response.GetFriendRequestsResponse
import kpring.user.dto.response.*
import kpring.user.exception.UserErrorCode
import kpring.user.global.AuthValidator
import kpring.user.global.CommonTest
Expand Down Expand Up @@ -492,5 +489,154 @@ internal class FriendControllerTest(
}
}
}

describe("친구삭제 API") {
it("친구삭제 성공") {
// given
val data = DeleteFriendResponse(friendId = CommonTest.TEST_FRIEND_ID)

val response = ApiResponse(data = data)
every { authClient.getTokenInfo(any()) }.returns(
ApiResponse(data = TokenInfo(TokenType.ACCESS, CommonTest.TEST_USER_ID.toString())),
)
every { authValidator.checkIfAccessTokenAndGetUserId(any()) } returns CommonTest.TEST_USER_ID.toString()
every { authValidator.checkIfUserIsSelf(any(), any()) } returns Unit
every {
friendService.deleteFriend(
CommonTest.TEST_USER_ID,
CommonTest.TEST_FRIEND_ID,
)
} returns data

// when
val result =
webTestClient.delete()
.uri(
"/api/v1/user/{userId}/friend/{friendId}",
CommonTest.TEST_USER_ID,
CommonTest.TEST_FRIEND_ID,
)
.header("Authorization", CommonTest.TEST_TOKEN)
.exchange()

// then
val docsRoot =
result
.expectStatus().isOk
.expectBody().json(objectMapper.writeValueAsString(response))

// docs
docsRoot
.restDoc(
identifier = "deleteFriend200",
description = "친구삭제 API",
) {
request {
path {
"userId" mean "사용자 아이디"
"friendId" mean "친구신청을 받은 사용자의 아이디"
}
header {
"Authorization" mean "Bearer token"
}
}
response {
body {
"data.friendId" type Strings mean "친구신청을 받은 사용자의 아이디"
}
}
}
}
it("친구삭제 실패 : 권한이 없는 토큰") {
// given
val response =
FailMessageResponse.builder().message(UserErrorCode.NOT_ALLOWED.message()).build()
every { authClient.getTokenInfo(any()) } throws ServiceException(UserErrorCode.NOT_ALLOWED)

// when
val result =
webTestClient.delete()
.uri(
"/api/v1/user/{userId}/friend/{friendId}",
CommonTest.TEST_USER_ID,
CommonTest.TEST_FRIEND_ID,
)
.header("Authorization", CommonTest.TEST_TOKEN)
.exchange()

// then
val docsRoot =
result
.expectStatus().isForbidden
.expectBody().json(objectMapper.writeValueAsString(response))

// docs
docsRoot
.restDoc(
identifier = "deleteFriend403",
description = "친구삭제 API",
) {
request {
path {
"userId" mean "사용자 아이디"
"friendId" mean "친구신청을 받은 사용자의 아이디"
}
header {
"Authorization" mean "Bearer token"
}
}
response {
body {
"message" type Strings mean "에러 메시지"
}
}
}
}
it("친구삭제 실패 : 서버 내부 오류") {
// given
val response =
FailMessageResponse.serverError
every { authClient.getTokenInfo(any()) } throws RuntimeException("서버 내부 오류")

// when
val result =
webTestClient.delete()
.uri(
"/api/v1/user/{userId}/friend/{friendId}",
CommonTest.TEST_USER_ID,
CommonTest.TEST_FRIEND_ID,
)
.header("Authorization", CommonTest.TEST_TOKEN)
.exchange()

// then
val docsRoot =
result
.expectStatus().isEqualTo(500)
.expectBody().json(objectMapper.writeValueAsString(response))

// docs
docsRoot
.restDoc(
identifier = "deleteFriend500",
description = "친구삭제 API",
) {
request {
path {
"userId" mean "사용자 아이디"
"friendId" mean "친구신청을 받은 사용자의 아이디"
}
header {
"Authorization" mean "Bearer token"
}
}
response {
body {
"message" type Strings mean "에러 메시지"
}
}
}
}
}
}
})
Loading