Skip to content

Commit

Permalink
Merge pull request #110 from soma-baekgu/feature/BG-392-get-reaction-…
Browse files Browse the repository at this point in the history
…chat

[BG-392]: reaction 채팅 상세 조회 기능 구현 (1.5h / 2h)
  • Loading branch information
GGHDMS authored Sep 3, 2024
2 parents e5945b9 + d2c1aa4 commit b3e0f66
Show file tree
Hide file tree
Showing 14 changed files with 325 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.backgu.amaker.api.event.controller

import com.backgu.amaker.api.event.dto.request.ReactionEventCreateRequest
import com.backgu.amaker.api.event.dto.request.ReplyEventCreateRequest
import com.backgu.amaker.api.event.dto.response.ReactionEventDetailResponse
import com.backgu.amaker.api.event.dto.response.ReplyEventDetailResponse
import com.backgu.amaker.api.event.service.EventFacadeService
import com.backgu.amaker.common.http.ApiHandler
Expand Down Expand Up @@ -44,6 +45,26 @@ class EventController(
),
)

@GetMapping("/events/{event-id}/reaction")
override fun getReactionEvent(
@AuthenticationPrincipal token: JwtAuthentication,
@PathVariable("chat-room-id") chatRoomId: Long,
@PathVariable("event-id") eventId: Long,
): ResponseEntity<ApiResult<ReactionEventDetailResponse>> =
ResponseEntity
.ok()
.body(
apiHandler.onSuccess(
ReactionEventDetailResponse.of(
eventFacadeService.getReactionEvent(
token.id,
chatRoomId,
eventId,
),
),
),
)

@PostMapping("/events/reply")
override fun createReplyEvent(
@AuthenticationPrincipal token: JwtAuthentication,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.backgu.amaker.api.event.controller

import com.backgu.amaker.api.event.dto.request.ReactionEventCreateRequest
import com.backgu.amaker.api.event.dto.request.ReplyEventCreateRequest
import com.backgu.amaker.api.event.dto.response.ReactionEventDetailResponse
import com.backgu.amaker.api.event.dto.response.ReplyEventDetailResponse
import com.backgu.amaker.common.http.response.ApiResult
import com.backgu.amaker.common.security.jwt.authentication.JwtAuthentication
Expand Down Expand Up @@ -33,6 +34,21 @@ interface EventSwagger {
@PathVariable("event-id") eventId: Long,
): ResponseEntity<ApiResult<ReplyEventDetailResponse>>

@Operation(summary = "reaction 이벤트 상세조회", description = "reaction 이벤트 상세조회합니다.")
@ApiResponses(
value = [
ApiResponse(
responseCode = "200",
description = "reaction 이벤트 상세조회 성공",
),
],
)
fun getReactionEvent(
token: JwtAuthentication,
chatRoomId: Long,
eventId: Long,
): ResponseEntity<ApiResult<ReactionEventDetailResponse>>

@Operation(summary = "reply 이벤트 생성", description = "reply 이벤트 생성합니다.")
@ApiResponses(
value = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.backgu.amaker.api.event.dto

import com.backgu.amaker.api.user.dto.UserDto
import com.backgu.amaker.domain.event.ReactionEvent
import com.backgu.amaker.domain.event.ReactionOption
import java.time.LocalDateTime

data class ReactionEventDetailDto(
val id: Long,
val eventTitle: String,
val options: List<ReactionOptionDto>,
val deadLine: LocalDateTime,
val notificationStartTime: LocalDateTime,
val notificationInterval: Int,
val eventCreator: UserDto,
val finishUser: List<UserDto>,
val waitingUser: List<UserDto>,
) {
companion object {
fun of(
reactionEvent: ReactionEvent,
reactionOptions: List<ReactionOption>,
eventCreator: UserDto,
finishUser: List<UserDto>,
waitingUser: List<UserDto>,
) = ReactionEventDetailDto(
id = reactionEvent.id,
eventTitle = reactionEvent.eventTitle,
options = reactionOptions.map { ReactionOptionDto.of(it) },
deadLine = reactionEvent.deadLine,
notificationStartTime = reactionEvent.notificationStartTime,
notificationInterval = reactionEvent.notificationInterval,
eventCreator = eventCreator,
finishUser = finishUser,
waitingUser = waitingUser,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.backgu.amaker.api.event.dto

import com.backgu.amaker.domain.event.ReactionOption

data class ReactionOptionDto(
val id: Long,
val eventId: Long,
val content: String,
) {
companion object {
fun of(reactionOption: ReactionOption) =
ReactionOptionDto(
id = reactionOption.id,
eventId = reactionOption.eventId,
content = reactionOption.content,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.backgu.amaker.api.event.dto.response

import com.backgu.amaker.api.event.dto.ReactionEventDetailDto
import com.backgu.amaker.api.user.dto.response.UserResponse
import io.swagger.v3.oas.annotations.media.Schema
import java.time.LocalDateTime

data class ReactionEventDetailResponse(
@Schema(description = "이벤트 id", example = "1")
val id: Long,
@Schema(description = "이벤트 제목", example = "우리 어디서 만날지")
val eventTitle: String,
@Schema(description = "선택지")
val options: List<ReactionOptionResponse>,
@Schema(description = "데드라인", example = "2024-07-24T07:39:37.598")
val deadLine: LocalDateTime,
@Schema(description = "알림 보낼 시작 시간", example = "2024-07-24T06:09:37.598")
val notificationStartTime: LocalDateTime,
@Schema(description = "알림 주기", example = "15")
val notificationInterval: Int,
@Schema(description = "이벤트 생성자")
val eventCreator: UserResponse,
@Schema(description = "이벤트를 수행한 유저")
val finishUser: List<UserResponse>,
@Schema(description = "이벤트 수행 대기중인 유저")
val waitingUser: List<UserResponse>,
) {
companion object {
fun of(reactionEventDetailDto: ReactionEventDetailDto) =
ReactionEventDetailResponse(
id = reactionEventDetailDto.id,
eventTitle = reactionEventDetailDto.eventTitle,
options = reactionEventDetailDto.options.map { ReactionOptionResponse.of(it) },
deadLine = reactionEventDetailDto.deadLine,
notificationStartTime = reactionEventDetailDto.notificationStartTime,
notificationInterval = reactionEventDetailDto.notificationInterval,
eventCreator = UserResponse.of(reactionEventDetailDto.eventCreator),
finishUser = reactionEventDetailDto.finishUser.map { UserResponse.of(it) },
waitingUser = reactionEventDetailDto.waitingUser.map { UserResponse.of(it) },
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.backgu.amaker.api.event.dto.response

import com.backgu.amaker.api.event.dto.ReactionOptionDto
import io.swagger.v3.oas.annotations.media.Schema

data class ReactionOptionResponse(
@Schema(description = "선택지 id", example = "1")
val id: Long,
@Schema(description = "이벤트 id", example = "1")
val eventId: Long,
@Schema(description = "내용", example = "옵션 1")
val content: String,
) {
companion object {
fun of(reactionOptionDto: ReactionOptionDto) =
ReactionOptionResponse(
id = reactionOptionDto.id,
eventId = reactionOptionDto.eventId,
content = reactionOptionDto.content,
)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.backgu.amaker.api.event.service

import com.backgu.amaker.api.event.dto.ReactionEventCreateDto
import com.backgu.amaker.api.event.dto.ReactionEventDetailDto
import com.backgu.amaker.api.event.dto.ReactionEventDto
import com.backgu.amaker.api.event.dto.ReplyEventCreateDto
import com.backgu.amaker.api.event.dto.ReplyEventDetailDto
Expand Down Expand Up @@ -74,6 +75,47 @@ class EventFacadeService(
)
}

@Transactional
fun getReactionEvent(
userId: String,
chatRoomId: Long,
eventId: Long,
): ReactionEventDetailDto {
val user = userService.getById(userId)
val chatRoom = chatRoomService.getById(chatRoomId)
chatRoomUserService.validateUserInChatRoom(user, chatRoom)

val chat = chatService.getById(eventId)
val eventAssignedUsers = eventAssignedUserService.findAllByEventId(eventId)
val eventAssignedUserIds = eventAssignedUsers.map { it.userId }

val users = userService.findAllByUserIdsToMap(eventAssignedUserIds.union(listOf(chat.userId)).toList())

val reactionEvent = reactionEventService.getById(eventId)

val reactionOptions = reactionOptionService.getAllByEventId(eventId)

val (finishedUsers, waitingUsers) = eventAssignedUsers.partition { it.isFinished }

return ReactionEventDetailDto.of(
reactionEvent = reactionEvent,
reactionOptions = reactionOptions,
eventCreator = UserDto.of(users[chat.userId] ?: throw BusinessException(StatusCode.USER_NOT_FOUND)),
finishUser =
finishedUsers.map {
UserDto.of(
users[it.userId] ?: throw BusinessException(StatusCode.USER_NOT_FOUND),
)
},
waitingUser =
waitingUsers.map {
UserDto.of(
users[it.userId] ?: throw BusinessException(StatusCode.USER_NOT_FOUND),
)
},
)
}

@Transactional
fun createReplyEvent(
userId: String,
Expand Down Expand Up @@ -137,7 +179,8 @@ class EventFacadeService(
),
)

val reactionOptions = reactionOptionService.saveAll(reactionEvent.createReactionOption(reactionEventCreateDto.options))
val reactionOptions =
reactionOptionService.saveAll(reactionEvent.createReactionOption(reactionEventCreateDto.options))

val users = userService.getAllByUserEmails(reactionEventCreateDto.assignees)
chatRoomUserService.validateUsersInChatRoom(users, chatRoom)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,59 @@ class EventFacadeServiceTest : IntegrationTest() {
assertThat(result.waitingUser.size).isEqualTo(2)
assertThat(result.eventCreator.id).isEqualTo(anotherUser)
}

@Test
@DisplayName("reaction 이벤트 조회 테스트")
fun getReactionEvent() {
// given
val anotherUser = "another-user"
val chat =
fixtures.chatFixtureFacade.chatFixture.createPersistedChat(
chatRoomId = chatRoom.id,
userId = DEFAULT_USER_ID,
chatType = ChatType.REACTION,
)
val reactionEvent = fixtures.reactionEventFixture.createPersistedReactionEvent(chat.id)
fixtures.reactionOptionFixture.createPersistedReactionOptions(reactionEvent.id)
fixtures.eventAssignedUserFixture.createPersistedEventAssignedUser(DEFAULT_USER_ID, reactionEvent.id)
fixtures.chatFixtureFacade.userFixture.createPersistedUser(anotherUser)
fixtures.eventAssignedUserFixture.createPersistedEventAssignedUser(anotherUser, reactionEvent.id)

// when
val result = eventFacadeService.getReactionEvent(DEFAULT_USER_ID, chatRoom.id, reactionEvent.id)

// then
assertThat(result).isNotNull()
assertThat(result.id).isEqualTo(chat.id)
assertThat(result.waitingUser.size).isEqualTo(2)
assertThat(result.eventCreator.id).isEqualTo(DEFAULT_USER_ID)
}

@Test
@DisplayName("reaction 이벤트 조회 테스트 - 다른 유저가 생성")
fun getReactionEventCreateAnotherUser() {
// given
val anotherUser = "another-user"
fixtures.chatFixtureFacade.userFixture.createPersistedUser(anotherUser)
val chat =
fixtures.chatFixtureFacade.chatFixture.createPersistedChat(
chatRoomId = chatRoom.id,
userId = anotherUser,
chatType = ChatType.REACTION,
)
val reactionEvent = fixtures.reactionEventFixture.createPersistedReactionEvent(chat.id)
fixtures.reactionOptionFixture.createPersistedReactionOptions(reactionEvent.id)
fixtures.eventAssignedUserFixture.createPersistedEventAssignedUser(DEFAULT_USER_ID, reactionEvent.id)
fixtures.eventAssignedUserFixture.createPersistedEventAssignedUser(anotherUser, reactionEvent.id)

// when
val result = eventFacadeService.getReactionEvent(DEFAULT_USER_ID, chatRoom.id, reactionEvent.id)

// then
assertThat(result).isNotNull()
assertThat(result.id).isEqualTo(chat.id)
assertThat(result.waitingUser.size).isEqualTo(2)
assertThat(result.eventCreator.id).isEqualTo(anotherUser)
assertThat(result.options.size).isEqualTo(3)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import org.springframework.stereotype.Component
class EventFixtureFacade(
val chatFixtureFacade: ChatFixtureFacade,
val replyEventFixture: ReplyEventFixture,
val reactionEventFixture: ReactionEventFixture,
val reactionOptionFixture: ReactionOptionFixture,
val eventAssignedUserFixture: EventAssignedUserFixture,
) {
fun setUp(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.backgu.amaker.api.fixture

import com.backgu.amaker.domain.event.ReactionEvent
import com.backgu.amaker.infra.jpa.event.entity.ReactionEventEntity
import com.backgu.amaker.infra.jpa.event.repository.ReactionEventRepository
import org.springframework.stereotype.Component
import java.time.LocalDateTime

@Component
class ReactionEventFixture(
val reactionEventRepository: ReactionEventRepository,
) {
fun createPersistedReactionEvent(
id: Long,
eventTitle: String = "$id 번째 이벤트",
deadLine: LocalDateTime = LocalDateTime.now(),
notificationStartTime: LocalDateTime = LocalDateTime.now(),
notificationInterval: Int = id.toInt(),
): ReactionEvent =
reactionEventRepository
.save(
ReactionEventEntity(
id = id,
eventTitle = eventTitle,
deadLine = deadLine,
notificationStartTime = notificationStartTime,
notificationInterval = notificationInterval,
),
).toDomain()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.backgu.amaker.api.fixture

import com.backgu.amaker.domain.event.ReactionOption
import com.backgu.amaker.infra.jpa.event.entity.ReactionOptionEntity
import com.backgu.amaker.infra.jpa.event.repository.ReactionOptionRepository
import org.springframework.stereotype.Component

@Component
class ReactionOptionFixture(
val reactionOptionRepository: ReactionOptionRepository,
) {
fun createPersistedReactionOptions(
eventId: Long,
options: List<String> = listOf("옵션1", "옵션2", "옵션3"),
): List<ReactionOption> =
reactionOptionRepository
.saveAll(
options.map {
ReactionOptionEntity(
eventId = eventId,
content = it,
)
},
).map { it.toDomain() }
}
Loading

0 comments on commit b3e0f66

Please sign in to comment.