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

[BG-391]: reaction 채팅 생성 구현 (1.5h / 2.0h) #109

Merged
merged 4 commits into from
Sep 2, 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
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ package com.backgu.amaker.api.event.config

import com.backgu.amaker.application.event.service.EventAssignedUserService
import com.backgu.amaker.application.event.service.EventService
import com.backgu.amaker.application.event.service.ReactionEventService
import com.backgu.amaker.application.event.service.ReactionOptionService
import com.backgu.amaker.application.event.service.ReplyCommentService
import com.backgu.amaker.application.event.service.ReplyEventService
import com.backgu.amaker.infra.jpa.event.repository.EventAssignedUserRepository
import com.backgu.amaker.infra.jpa.event.repository.EventRepository
import com.backgu.amaker.infra.jpa.event.repository.ReactionEventRepository
import com.backgu.amaker.infra.jpa.event.repository.ReactionOptionRepository
import com.backgu.amaker.infra.jpa.event.repository.ReplyCommentRepository
import com.backgu.amaker.infra.jpa.event.repository.ReplyEventRepository
import org.springframework.context.annotation.Bean
Expand All @@ -26,4 +30,12 @@ class EventServiceConfig {

@Bean
fun eventService(eventRepository: EventRepository): EventService = EventService(eventRepository)

@Bean
fun reactionEventService(reactionEventRepository: ReactionEventRepository): ReactionEventService =
ReactionEventService(reactionEventRepository)

@Bean
fun reactionOptionService(reactionOptionRepository: ReactionOptionRepository): ReactionOptionService =
ReactionOptionService(reactionOptionRepository)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
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.ReplyEventDetailResponse
import com.backgu.amaker.api.event.service.EventFacadeService
Expand All @@ -23,6 +24,26 @@ class EventController(
private val eventFacadeService: EventFacadeService,
private val apiHandler: ApiHandler,
) : EventSwagger {
@GetMapping("/events/{event-id}/reply")
override fun getReplyEvent(
@AuthenticationPrincipal token: JwtAuthentication,
@PathVariable("chat-room-id") chatRoomId: Long,
@PathVariable("event-id") eventId: Long,
): ResponseEntity<ApiResult<ReplyEventDetailResponse>> =
ResponseEntity
.ok()
.body(
apiHandler.onSuccess(
ReplyEventDetailResponse.of(
eventFacadeService.getReplyEvent(
token.id,
chatRoomId,
eventId,
),
),
),
)

@PostMapping("/events/reply")
override fun createReplyEvent(
@AuthenticationPrincipal token: JwtAuthentication,
Expand All @@ -44,23 +65,24 @@ class EventController(
).toUri(),
).build()

@GetMapping("/events/{event-id}/reply")
override fun getReplyEvent(
@PostMapping("/events/reaction")
override fun createReactionEvent(
@AuthenticationPrincipal token: JwtAuthentication,
@PathVariable("chat-room-id") chatRoomId: Long,
@PathVariable("event-id") eventId: Long,
): ResponseEntity<ApiResult<ReplyEventDetailResponse>> =
@RequestBody @Valid request: ReactionEventCreateRequest,
): ResponseEntity<Unit> =
ResponseEntity
.ok()
.body(
apiHandler.onSuccess(
ReplyEventDetailResponse.of(
eventFacadeService.getReplyEvent(
token.id,
chatRoomId,
eventId,
),
),
),
)
.created(
ServletUriComponentsBuilder
.fromCurrentContextPath()
.path("/api/v1/events/{id}/reply")
.buildAndExpand(
eventFacadeService
.createReactionEvent(
token.id,
chatRoomId,
request.toDto(),
).id,
).toUri(),
).build()
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
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.ReplyEventDetailResponse
import com.backgu.amaker.common.http.response.ApiResult
Expand All @@ -12,10 +13,26 @@ import jakarta.validation.Valid
import org.springframework.http.ResponseEntity
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody

@Tag(name = "event", description = "이벤트 API")
interface EventSwagger {
@Operation(summary = "reply 이벤트 상세조회", description = "reply 이벤트 상세조회합니다.")
@ApiResponses(
value = [
ApiResponse(
responseCode = "200",
description = "reply 이벤트 상세조회 성공",
),
],
)
fun getReplyEvent(
@AuthenticationPrincipal token: JwtAuthentication,
@PathVariable("chat-room-id") chatRoomId: Long,
@PathVariable("event-id") eventId: Long,
): ResponseEntity<ApiResult<ReplyEventDetailResponse>>

@Operation(summary = "reply 이벤트 생성", description = "reply 이벤트 생성합니다.")
@ApiResponses(
value = [
Expand All @@ -31,18 +48,19 @@ interface EventSwagger {
@RequestBody @Valid request: ReplyEventCreateRequest,
): ResponseEntity<Unit>

@Operation(summary = "reply 이벤트 상세조회", description = "reply 이벤트 상세조회합니다.")
@Operation(summary = "reaction 이벤트 생성", description = "reaction 이벤트 생성합니다.")
@ApiResponses(
value = [
ApiResponse(
responseCode = "200",
description = "reply 이벤트 상세조회 성공",
responseCode = "201",
description = "reaction 이벤트 생성 성공",
),
],
)
fun getReplyEvent(
@AuthenticationPrincipal token: JwtAuthentication,
@PathVariable("chat-room-id") chatRoomId: Long,
@PathVariable("event-id") eventId: Long,
): ResponseEntity<ApiResult<ReplyEventDetailResponse>>
@PostMapping("/events/reaction")
fun createReactionEvent(
token: JwtAuthentication,
chatRoomId: Long,
request: ReactionEventCreateRequest,
): ResponseEntity<Unit>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.backgu.amaker.api.event.dto

import java.time.LocalDateTime

data class ReactionEventCreateDto(
val eventTitle: String,
val options: List<String>,
val assignees: List<String>,
val deadLine: LocalDateTime,
val notificationStartHour: Int,
val notificationStartMinute: Int,
val interval: Int,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.backgu.amaker.api.event.dto

import com.backgu.amaker.domain.event.ReactionEvent
import com.backgu.amaker.domain.event.ReactionOption
import java.time.LocalDateTime

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

import com.backgu.amaker.api.event.dto.ReactionEventCreateDto
import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Size
import org.springframework.format.annotation.DateTimeFormat
import java.time.LocalDateTime

data class ReactionEventCreateRequest(
@Schema(description = "제목", example = "제목 어때요?")
@field:NotBlank(message = "이벤트 제목을 입력해주세요.")
val eventTitle: String,
@Schema(description = "선택지", example = "[\"예\", \"아니오\"]")
val options: List<String>,
@Schema(description = "답변을 요청할 인원", example = "[\"user1\", \"user2\"]")
@field:Size(min = 1, message = "최소 한 명 이상의 인원을 지정해야 합니다.")
val assignees: List<String>,
@Schema(description = "마감 기한", example = "2021-08-01T00:00:00")
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
val deadLine: LocalDateTime,
@Schema(description = "알림 시작 시간", example = "1")
val notificationStartHour: Int,
@Schema(description = "알림 시작 분", example = "30")
val notificationStartMinute: Int,
@Schema(description = "알림 주기", example = "15")
val interval: Int,
) {
fun toDto() =
ReactionEventCreateDto(
eventTitle = eventTitle,
options = options,
assignees = assignees,
deadLine = deadLine,
notificationStartMinute = notificationStartMinute,
notificationStartHour = notificationStartHour,
interval = interval,
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.backgu.amaker.api.event.service

import com.backgu.amaker.api.event.dto.ReactionEventCreateDto
import com.backgu.amaker.api.event.dto.ReactionEventDto
import com.backgu.amaker.api.event.dto.ReplyEventCreateDto
import com.backgu.amaker.api.event.dto.ReplyEventDetailDto
import com.backgu.amaker.api.event.dto.ReplyEventDto
Expand All @@ -9,6 +11,8 @@ import com.backgu.amaker.application.chat.service.ChatRoomService
import com.backgu.amaker.application.chat.service.ChatRoomUserService
import com.backgu.amaker.application.chat.service.ChatService
import com.backgu.amaker.application.event.service.EventAssignedUserService
import com.backgu.amaker.application.event.service.ReactionEventService
import com.backgu.amaker.application.event.service.ReactionOptionService
import com.backgu.amaker.application.event.service.ReplyEventService
import com.backgu.amaker.application.user.service.UserService
import com.backgu.amaker.common.exception.BusinessException
Expand All @@ -27,9 +31,49 @@ class EventFacadeService(
private val chatRoomUserService: ChatRoomUserService,
private val chatService: ChatService,
private val replyEventService: ReplyEventService,
private val reactionEventService: ReactionEventService,
private val reactionOptionService: ReactionOptionService,
private val eventAssignedUserService: EventAssignedUserService,
private val eventPublisher: ApplicationEventPublisher,
) {
@Transactional
fun getReplyEvent(
userId: String,
chatRoomId: Long,
eventId: Long,
): ReplyEventDetailDto {
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 replyEvent = replyEventService.getById(eventId)

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

return ReplyEventDetailDto.of(
replyEvent = replyEvent,
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 @@ -70,40 +114,43 @@ class EventFacadeService(
}

@Transactional
fun getReplyEvent(
fun createReactionEvent(
userId: String,
chatRoomId: Long,
eventId: Long,
): ReplyEventDetailDto {
reactionEventCreateDto: ReactionEventCreateDto,
): ReactionEventDto {
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 chat: Chat =
chatService.save(chatRoom.createChat(user, reactionEventCreateDto.eventTitle, ChatType.REACTION))
chatRoomService.save(chatRoom.updateLastChatId(chat))

val users = userService.findAllByUserIdsToMap(eventAssignedUserIds.union(listOf(chat.userId)).toList())
val reactionEvent =
reactionEventService.save(
chat.createReactionEvent(
reactionEventCreateDto.deadLine,
reactionEventCreateDto.notificationStartHour,
reactionEventCreateDto.notificationStartMinute,
reactionEventCreateDto.interval,
),
)

val replyEvent = replyEventService.getById(eventId)
val reactionOptions = reactionOptionService.saveAll(reactionEvent.createReactionOption(reactionEventCreateDto.options))

val (finishedUsers, waitingUsers) = eventAssignedUsers.partition { it.isFinished }
val users = userService.getAllByUserEmails(reactionEventCreateDto.assignees)
chatRoomUserService.validateUsersInChatRoom(users, chatRoom)

return ReplyEventDetailDto.of(
replyEvent = replyEvent,
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),
)
},
eventAssignedUserService.saveAll(reactionEvent.createAssignedUsers(users))

eventPublisher.publishEvent(
EventChatSaveEvent.of(
chatRoomId,
chat.createEventChatWithUser(reactionEvent, user, users),
),
)

return ReactionEventDto.of(reactionEvent, reactionOptions)
}
}
Loading
Loading