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 구현 #174

Merged
merged 13 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from 11 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
25 changes: 19 additions & 6 deletions user/src/main/kotlin/kpring/user/controller/FriendController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package kpring.user.controller

import kpring.core.auth.client.AuthClient
import kpring.core.global.dto.response.ApiResponse
import kpring.user.dto.response.AddFriendResponse
import kpring.user.dto.response.DeleteFriendResponse
import kpring.user.dto.response.GetFriendRequestsResponse
import kpring.user.dto.response.GetFriendsResponse
import kpring.user.dto.result.AddFriendResponse
import kpring.user.global.AuthValidator
import kpring.user.service.FriendService
import org.springframework.http.ResponseEntity
Expand All @@ -17,16 +18,15 @@ class FriendController(
private val authValidator: AuthValidator,
private val authClient: AuthClient,
) {
@PostMapping("/user/{userId}/friend/{friendId}")
fun addFriend(
@GetMapping("/user/{userId}/requests")
fun getFriendRequests(
@RequestHeader("Authorization") token: String,
@PathVariable userId: Long,
@PathVariable friendId: Long,
): ResponseEntity<ApiResponse<AddFriendResponse>> {
): ResponseEntity<ApiResponse<GetFriendRequestsResponse>> {
val validationResult = authClient.getTokenInfo(token)
val validatedUserId = authValidator.checkIfAccessTokenAndGetUserId(validationResult)
authValidator.checkIfUserIsSelf(userId.toString(), validatedUserId)
val response = friendService.addFriend(userId, friendId)
val response = friendService.getFriendRequests(userId)
return ResponseEntity.ok(ApiResponse(data = response))
}

Expand All @@ -41,6 +41,19 @@ class FriendController(
return ResponseEntity.ok(ApiResponse(data = response))
}

@PostMapping("/user/{userId}/friend/{friendId}")
fun addFriend(
@RequestHeader("Authorization") token: String,
@PathVariable userId: Long,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 토큰에서 뽑을 수 있는 userId랑은 다른 정보일까요?! 제가 생각하기에는 요청하는 user의 id를 토큰에서 뽑아와서 사용하면 될 것 같은데..! 입력을 따로 받는 이유가 있나요?! 궁금합니다 😽

Copy link
Contributor Author

@minahYu minahYu Jun 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

토큰에서 뽑을 수 있는 userId랑 같은 정보입니다! 기존에 작성되어 있던 코드들이 pathVariable로 userId를 받아와서 동일하게 코드 작성했었는데, user쪽 기능 구현 다 하면 수정해봐도 좋을 것 같아요! 추후에 수정하겠습니다~!

@PathVariable friendId: Long,
): ResponseEntity<ApiResponse<AddFriendResponse>> {
val validationResult = authClient.getTokenInfo(token)
val validatedUserId = authValidator.checkIfAccessTokenAndGetUserId(validationResult)
authValidator.checkIfUserIsSelf(userId.toString(), validatedUserId)
val response = friendService.addFriend(userId, friendId)
return ResponseEntity.ok(ApiResponse(data = response))
}

@DeleteMapping("/user/{userId}/friend/{friendId}")
fun deleteFriend(
@RequestHeader("Authorization") token: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package kpring.user.dto.result
package kpring.user.dto.response

data class AddFriendResponse(
val friendId: Long,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package kpring.user.dto.response

data class GetFriendRequestResponse(
val friendId: Long,
val username: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package kpring.user.dto.response

data class GetFriendRequestsResponse(
val userId: Long,
var friendRequests: MutableList<GetFriendRequestResponse>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

immutable한 타입을 사용하는것은 어떻게 생각하시낭요??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

가져온 친구 신청 목록 담는 용도라 immutable한 타입이 좋겠네요!! 해당 코드 수정하겠습니다~!

)
2 changes: 1 addition & 1 deletion user/src/main/kotlin/kpring/user/entity/Friend.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class Friend(
private var user: User,
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "friend_id")
private var friend: User,
var friend: User,
@Enumerated(EnumType.STRING)
@Column(nullable = false)
var requestStatus: FriendRequestStatus,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package kpring.user.repository

import kpring.user.entity.Friend
import kpring.user.entity.FriendRequestStatus
import org.springframework.data.jpa.repository.JpaRepository

interface FriendRepository : JpaRepository<Friend, Long> {
fun existsByUserIdAndFriendId(
userId: Long,
friendId: Long,
): Boolean

fun findAllByUserIdAndRequestStatus(
userId: Long,
requestStatus: FriendRequestStatus,
): List<Friend>
}
5 changes: 4 additions & 1 deletion user/src/main/kotlin/kpring/user/service/FriendService.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package kpring.user.service

import kpring.user.dto.response.AddFriendResponse
import kpring.user.dto.response.DeleteFriendResponse
import kpring.user.dto.response.GetFriendRequestsResponse
import kpring.user.dto.response.GetFriendsResponse
import kpring.user.dto.result.AddFriendResponse

interface FriendService {
fun getFriendRequests(userId: Long): GetFriendRequestsResponse

fun getFriends(userId: Long): GetFriendsResponse

fun addFriend(
Expand Down
19 changes: 16 additions & 3 deletions user/src/main/kotlin/kpring/user/service/FriendServiceImpl.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package kpring.user.service

import kpring.core.global.exception.ServiceException
import kpring.user.dto.response.DeleteFriendResponse
import kpring.user.dto.response.GetFriendsResponse
import kpring.user.dto.result.AddFriendResponse
import kpring.user.dto.response.*
import kpring.user.entity.Friend
import kpring.user.entity.FriendRequestStatus
import kpring.user.entity.User
import kpring.user.exception.UserErrorCode
import kpring.user.repository.FriendRepository
Expand All @@ -16,6 +16,19 @@ class FriendServiceImpl(
private val userServiceImpl: UserServiceImpl,
private val friendRepository: FriendRepository,
) : FriendService {
override fun getFriendRequests(userId: Long): GetFriendRequestsResponse {
val friendRelations: List<Friend> =
friendRepository.findAllByUserIdAndRequestStatus(userId, FriendRequestStatus.RECEIVED)
val friendRequests: MutableList<GetFriendRequestResponse> = mutableListOf()

for (friendRelation in friendRelations) {
val friend = friendRelation.friend
friendRequests.add(GetFriendRequestResponse(friend.id!!, friend.username))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stream api 사용하면 좀 더 가독성 좋은 코드가 될 것 같아요!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 stream api 사용해 코드 수정해보겠습니다~!


return GetFriendRequestsResponse(userId, friendRequests)
}

override fun getFriends(userId: Long): GetFriendsResponse {
TODO("Not yet implemented")
}
Expand Down
152 changes: 151 additions & 1 deletion user/src/test/kotlin/kpring/user/controller/FriendControllerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ import kpring.core.auth.enums.TokenType
import kpring.core.global.dto.response.ApiResponse
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.result.AddFriendResponse
import kpring.user.dto.response.GetFriendRequestResponse
import kpring.user.dto.response.GetFriendRequestsResponse
import kpring.user.exception.UserErrorCode
import kpring.user.global.AuthValidator
import kpring.user.global.CommonTest
Expand Down Expand Up @@ -194,4 +197,151 @@ internal class FriendControllerTest(
}
}
}

describe("친구신청 조회 API") {
it("친구신청 조회 성공") {
// given
val friendRequest =
GetFriendRequestResponse(
friendId = CommonTest.TEST_FRIEND_ID,
username = CommonTest.TEST_FRIEND_USERNAME,
)
val data =
GetFriendRequestsResponse(
userId = CommonTest.TEST_USER_ID,
friendRequests = mutableListOf(friendRequest),
)
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.getFriendRequests(CommonTest.TEST_USER_ID)
} returns data

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

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

// docs
docsRoot
.restDoc(
identifier = "getFriendRequests200",
description = "친구신청 조회 API",
) {
request {
path {
"userId" mean "사용자 아이디"
}
header { "Authorization" mean "Bearer token" }
}
response {
body {
"data.userId" type Strings mean "사용자 아이디"
"data.friendRequests" type JsonDataType.Arrays mean "친구신청한 사용자 리스트"
"data.friendRequests[].friendId" type Strings mean "친구신청한 사용자 아이디"
"data.friendRequests[].username" type Strings mean "친구신청한 사용자 닉네임"
}
Comment on lines +258 to +261
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

frinedRequest는 무슨의미로 사용한 건가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

friendReuqests는 로그인한 사용자가 받은 친구신청 목록을 의미합니다!

}
}
}
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.get()
.uri(
"/api/v1/user/{userId}/requests",
CommonTest.TEST_USER_ID,
)
.header("Authorization", CommonTest.TEST_TOKEN)
.exchange()

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

// docs
docsRoot
.restDoc(
identifier = "getFriendRequests403",
description = "친구신청 조회 API",
) {
request {
path {
"userId" 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.get()
.uri(
"/api/v1/user/{userId}/requests",
CommonTest.TEST_USER_ID,
)
.header("Authorization", CommonTest.TEST_TOKEN)
.exchange()

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

// docs
docsRoot
.restDoc(
identifier = "getFriendRequests500",
description = "친구신청 조회 API",
) {
request {
path {
"userId" mean "사용자 아이디"
}
header { "Authorization" mean "Bearer token" }
}
response {
body {
"message" type Strings mean "에러 메시지"
}
}
}
}
}
})
1 change: 1 addition & 0 deletions user/src/test/kotlin/kpring/user/global/CommonTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ interface CommonTest {
const val TEST_TOKEN = "Bearer test"

const val TEST_FRIEND_ID = 2L
const val TEST_FRIEND_USERNAME = "friend"
}
}
17 changes: 17 additions & 0 deletions user/src/test/kotlin/kpring/user/service/FriendServiceImplTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
import kpring.core.global.exception.ServiceException
import kpring.user.entity.Friend
import kpring.user.entity.FriendRequestStatus
import kpring.user.entity.User
import kpring.user.exception.UserErrorCode
import kpring.user.global.CommonTest
Expand Down Expand Up @@ -99,4 +101,19 @@ internal class FriendServiceImplTest : FunSpec({
friendService.checkFriendRelationExists(CommonTest.TEST_USER_ID, CommonTest.TEST_FRIEND_ID)
}
}

test("친구신청조회_성공") {
val friend = mockk<User>(relaxed = true)
val friendList = listOf(mockk<Friend>(relaxed = true))

every {
friendRepository.findAllByUserIdAndRequestStatus(CommonTest.TEST_USER_ID, FriendRequestStatus.RECEIVED)
} returns friendList

val response = friendService.getFriendRequests(CommonTest.TEST_USER_ID)
for (request in response.friendRequests) {
request.friendId shouldBe friend.id
request.username shouldBe friend.username
}
}
})