Skip to content

#135 - 익명 프로필 조회 기능을 구현합니다. #152

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

Merged
merged 2 commits into from
May 4, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.wespot.message.v2

import com.wespot.message.dto.response.AnonymousProfileResponse
import com.wespot.message.port.`in`.GetAnonymousProfileUseCase
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/api/v1/messages")
class GetAnonymousProfileController(
private val getAnonymousProfileUseCase: GetAnonymousProfileUseCase,
) {

@GetMapping("/receiver/{receiverId}/profiles")
fun getAnonymousProfileByReceiverId(
@PathVariable receiverId: Long
): ResponseEntity<List<AnonymousProfileResponse>> {
val response = getAnonymousProfileUseCase.getAnonymousProfileByReceiverId(receiverId)

return ResponseEntity.ok()
.body(response)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,20 @@ import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/api/v1/messages/profiles")
class AnonymousProfileController(
@RequestMapping("/api/v1/messages")
class CreatedAnonymousProfileController(
private val anonymousProfileUseCase: AnonymousProfileUseCase
) {

@PostMapping
@PostMapping("/profiles")
fun createAnonymousProfile(@RequestBody createdAnonymousProfileRequest: CreatedAnonymousProfileRequest): ResponseEntity<Unit> {
anonymousProfileUseCase.createAnonymousProfile(createdAnonymousProfileRequest)

return ResponseEntity.status(HttpStatus.CREATED)
.build()
}

@PutMapping("/{id}")
@PutMapping("/profiles/{id}")
fun unblockUser(
@PathVariable id: Long,
@RequestBody updatedAnonymousProfileRequest: UpdatedAnonymousProfileRequest
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.wespot.message.dto.response

import com.fasterxml.jackson.annotation.JsonFormat
import com.wespot.message.v2.UserProfile
import java.time.LocalDateTime

data class AnonymousProfileResponse(
val id: Long,
val image: String,
val name: String,
val isAnonymous: Boolean,
@JsonFormat(pattern = "yyyy.MM.dd HH:mm:ss")
val recentlyTalk: LocalDateTime?,
val myTurnToAnswer: Boolean
) {

companion object {
fun from(
userProfile: UserProfile
): AnonymousProfileResponse {
return AnonymousProfileResponse(
id = userProfile.profileId,
image = userProfile.image,
name = userProfile.name,
isAnonymous = userProfile.isAnonymous,
recentlyTalk = userProfile.recentlyTalk(),
myTurnToAnswer = userProfile.isAbleToAnswer()
)
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.wespot.message.port.`in`

import com.wespot.message.dto.response.AnonymousProfileResponse

interface GetAnonymousProfileUseCase {

fun getAnonymousProfileByReceiverId(receiverId: Long): List<AnonymousProfileResponse>

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ interface MessageV2Port {

fun findAllByMessageRoomId(messageRoomId: Long): List<MessageV2>

fun findAllMessageRoomBySenderIdAndReceiverId(senderId: Long, receiverId: Long): List<MessageV2>

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.wespot.message.service.v2

import com.wespot.auth.service.SecurityUtils
import com.wespot.message.dto.response.AnonymousProfileResponse
import com.wespot.message.port.`in`.GetAnonymousProfileUseCase
import com.wespot.message.port.out.MessageV2Port
import com.wespot.message.v2.MessageRooms
import com.wespot.message.v2.UserProfiles
import com.wespot.user.port.out.UserPort
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
class GetAnonymousProfileService(
private val userPort: UserPort,
private val messagePort: MessageV2Port,
) : GetAnonymousProfileUseCase {

@Transactional(readOnly = true)
override fun getAnonymousProfileByReceiverId(receiverId: Long): List<AnonymousProfileResponse> {
val sender = SecurityUtils.getLoginUser(userPort = userPort)
val messageRooms =
messagePort.findAllMessageRoomBySenderIdAndReceiverId(senderId = sender.id, receiverId = receiverId)
val messageRoomIds = messageRooms.map { it.id }
val messageDetails = messagePort.findAllLastMessageOfRoomByRoomIdIn(messageRoomIds)

val rooms =
MessageRooms.createOverview(user = sender, rooms = messageRooms, messageDetails = messageDetails)

return UserProfiles.of(messageRooms = rooms)
.asList()
.map { AnonymousProfileResponse.from(it) }
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.wespot.image.event.DeletedImageEvent
import com.wespot.image.event.SavedImageEvent
import com.wespot.user.dto.request.CreatedAnonymousProfileRequest
import com.wespot.user.dto.request.UpdatedAnonymousProfileRequest
import com.wespot.message.dto.response.AnonymousProfileResponse
import com.wespot.user.message.AnonymousProfile
import com.wespot.user.message.ProfileName
import com.wespot.user.port.`in`.AnonymousProfileUseCase
Expand Down Expand Up @@ -90,4 +91,5 @@ class AnonymousProfileService(
EventUtils.publish(DeletedImageEvent(savedAnonymousProfile.imageUrl))
return savedAnonymousProfileAfterUpdate
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,8 @@ data class MessageDetails(
return messages
}

fun isAbleToAnswer(): Boolean {
return messages.any { it.isAbleToAnswer }
Copy link
Preview

Copilot AI May 4, 2025

Choose a reason for hiding this comment

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

The method isAbleToAnswer appears to be a function and should be invoked (e.g., it.isAbleToAnswer()) rather than referenced as a property.

Suggested change
return messages.any { it.isAbleToAnswer }
return messages.any { it.isAbleToAnswer() }

Copilot uses AI. Check for mistakes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

nope i don't want

}

}
20 changes: 20 additions & 0 deletions domain/src/main/kotlin/com/wespot/message/v2/MessageRoom.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.wespot.message.v2
import com.wespot.exception.CustomException
import com.wespot.exception.ExceptionView
import com.wespot.user.User
import com.wespot.user.message.AnonymousProfile
import org.springframework.http.HttpStatus
import java.time.LocalDateTime

Expand Down Expand Up @@ -94,4 +95,23 @@ data class MessageRoom(
return roomMessage.isReportedByReceiver(viewer = viewer)
}

fun isAbleToAnswer(): Boolean {
return messages.isAbleToAnswer()
}

fun isSameUserProfileAndNotAnonymous(viewer: User): Boolean {
if (roomMessage.isSameUserProfileAndNotAnonymous(viewer = viewer)) {
return true
}
return false
}

fun isSameAnonymousProfile(anonymousProfileId: Long): Boolean {
return roomMessage.isSameAnonymousProfile(anonymousProfileId = anonymousProfileId)
}

fun anonymousProfile(): AnonymousProfile? {
return roomMessage.anonymousProfile
}

}
18 changes: 18 additions & 0 deletions domain/src/main/kotlin/com/wespot/message/v2/MessageRooms.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.wespot.message.v2

import com.wespot.exception.CustomException
import com.wespot.exception.ExceptionView
import com.wespot.user.User
import org.springframework.http.HttpStatus

data class MessageRooms(
val rooms: List<MessageRoom>
Expand Down Expand Up @@ -30,4 +33,19 @@ data class MessageRooms(
return rooms
}

fun viewer(): User {
val viewerSet = rooms.map { it.viewer }
.toSet()

if (viewerSet.size != 1) {
throw CustomException(
message = "조회자는 무조건 1명을 초과할 수 없습니다.",
status = HttpStatus.BAD_REQUEST,
view = ExceptionView.TOAST,
)
}

return viewerSet.first()
}

}
12 changes: 12 additions & 0 deletions domain/src/main/kotlin/com/wespot/message/v2/MessageV2.kt
Original file line number Diff line number Diff line change
Expand Up @@ -270,5 +270,17 @@ data class MessageV2(
return false
}

fun isSameUserProfileAndNotAnonymous(viewer: User): Boolean {
return isRoom() && viewer.isMeSender(senderId = sender.id) && anonymousProfile == null
}

fun isSameAnonymousProfile(anonymousProfileId: Long): Boolean {
if (anonymousProfile == null) {
return false
}

return anonymousProfile.id == anonymousProfileId
}


}
63 changes: 63 additions & 0 deletions domain/src/main/kotlin/com/wespot/message/v2/UserProfile.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.wespot.message.v2

import com.wespot.user.User
import com.wespot.user.message.AnonymousProfile
import java.time.LocalDateTime

data class UserProfile(
val profileId: Long,
val image: String,
val name: String,
val isAnonymous: Boolean,
val messageRoom: MessageRoom?
) {

companion object {

private val EMPTY_MESSAGE_ROOM = null

fun createByAnonymousProfile(
anonymousProfile: AnonymousProfile,
messageRoom: List<MessageRoom>
): UserProfile {
return UserProfile(
profileId = anonymousProfile.id,
image = anonymousProfile.imageUrl,
name = anonymousProfile.name,
isAnonymous = true,
messageRoom = messageRoom.find { it.isSameAnonymousProfile(anonymousProfile.id) }
)
}

fun createByUserProfile(
user: User,
messageRoom: List<MessageRoom>
): UserProfile {
return UserProfile(
profileId = user.profile.id,
image = user.profile.iconUrl,
name = user.name,
isAnonymous = false,
messageRoom = messageRoom.find { it.isSameUserProfileAndNotAnonymous(viewer = user) }
)
}

}

fun recentlyTalk(): LocalDateTime? {
if (messageRoom == EMPTY_MESSAGE_ROOM) {
Copy link
Preview

Copilot AI May 4, 2025

Choose a reason for hiding this comment

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

[nitpick] Using an EMPTY_MESSAGE_ROOM constant set to null adds an extra layer of indirection; consider checking messageRoom directly for null to improve clarity.

Suggested change
if (messageRoom == EMPTY_MESSAGE_ROOM) {
if (messageRoom == null) {

Copilot uses AI. Check for mistakes.

return null
}

return messageRoom.latestChatTime()
}

fun isAbleToAnswer(): Boolean {
if (messageRoom == EMPTY_MESSAGE_ROOM) {
return false
}

return messageRoom.isAbleToAnswer()
}

}
50 changes: 50 additions & 0 deletions domain/src/main/kotlin/com/wespot/message/v2/UserProfiles.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.wespot.message.v2

import com.wespot.exception.CustomException
import com.wespot.exception.ExceptionView
import org.springframework.http.HttpStatus

data class UserProfiles(
val userProfiles: List<UserProfile>
) {

companion object {

fun of(messageRooms: MessageRooms): UserProfiles {
val isContainsOwnerIsNotViewer = messageRooms.asList()
.any { !it.isViewerOwnerOfMessageRoom() }
if (isContainsOwnerIsNotViewer) {
throw CustomException(
message = "익명 프로필을 조회한자가 소유한 쪽지방이 아닌 쪽지를 가져왔습니다.",
status = HttpStatus.BAD_REQUEST,
view = ExceptionView.TOAST,
)
}

val anonymousProfiles = messageRooms.asList()
.filter { it.isAnonymous() }
.map { it.anonymousProfile()!! }

val resultOfUserProfiles = anonymousProfiles.map { anonymousProfile ->
UserProfile.createByAnonymousProfile(
anonymousProfile = anonymousProfile,
messageRoom = messageRooms.asList()
)
} + UserProfile.createByUserProfile(
user = messageRooms.viewer(),
messageRoom = messageRooms.asList()
)

return UserProfiles(
userProfiles = resultOfUserProfiles.sortedBy { it.recentlyTalk() }
.reversed()
)
}

}

fun asList(): List<UserProfile> {
return userProfiles
}

}
10 changes: 10 additions & 0 deletions domain/src/main/kotlin/com/wespot/user/User.kt
Original file line number Diff line number Diff line change
Expand Up @@ -301,4 +301,14 @@ data class User(
return name == EVER_NAME
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is User) return false
return id == other.id
}

override fun hashCode(): Int {
return id.hashCode()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,9 @@ interface MessageV2JpaRepository : JpaRepository<MessageJpaEntityV2, Long> {

fun findAllByMessageRoomId(messageRoomId: Long): List<MessageJpaEntityV2>

fun findAllByMessageRoomIdIsNullAndSenderIdAndReceiverId(
senderId: Long,
receiverId: Long,
): List<MessageJpaEntityV2>

}
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,13 @@ class MessageV2PersistenceAdapter(
return getCompleteMessageV2(messages)
}

override fun findAllMessageRoomBySenderIdAndReceiverId(senderId: Long, receiverId: Long): List<MessageV2> {
val messageRooms = messageV2JpaRepository.findAllByMessageRoomIdIsNullAndSenderIdAndReceiverId(
senderId = senderId,
receiverId = receiverId
)

return getCompleteMessageV2(messageRooms)
}

}