Skip to content

Commit d3fcb52

Browse files
authored
Merge pull request #114 from soma-baekgu/feature/BG-257-ongoing-event-retrieve
[BG-257]: 워크스페이스 이벤트 조회(3H/3H)
2 parents c1dfa6a + eb1131f commit d3fcb52

File tree

12 files changed

+271
-5
lines changed

12 files changed

+271
-5
lines changed

api/src/main/kotlin/com/backgu/amaker/api/chat/service/ChatFacadeService.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,9 @@ class ChatFacadeService(
185185
userMap: Map<String, User>,
186186
): ChatWithUserDto<*> =
187187
if (ChatType.isEventChat(chat.chatType)) {
188-
val event = eventMap[chat.id] ?: throw BusinessException(StatusCode.EVENT_NOT_FOUND)
189-
val eventUsers = eventUserMap[event.id]?.mapNotNull { userMap[it.userId] } ?: emptyList()
190-
val finishedNumber = eventUserMap[event.id]?.count { it.isFinished } ?: 0
188+
val event: Event = eventMap[chat.id] ?: throw BusinessException(StatusCode.EVENT_NOT_FOUND)
189+
val eventUsers: List<User> = eventUserMap[event.id]?.mapNotNull { userMap[it.userId] } ?: emptyList()
190+
val finishedNumber: Int = eventUserMap[event.id]?.count { it.isFinished } ?: 0
191191
EventChatWithUserDto.of(chat, EventWithUserDto.of(event, eventUsers, finishedNumber))
192192
} else {
193193
DefaultChatWithUserDto.of(chat)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.backgu.amaker.api.event.controller
2+
3+
import com.backgu.amaker.api.event.dto.response.EventBriefResponse
4+
import com.backgu.amaker.api.event.service.EventFacadeService
5+
import com.backgu.amaker.common.http.ApiHandler
6+
import com.backgu.amaker.common.http.response.ApiResult
7+
import com.backgu.amaker.common.security.jwt.authentication.JwtAuthentication
8+
import com.backgu.amaker.domain.event.EventStatus
9+
import org.springframework.http.ResponseEntity
10+
import org.springframework.security.core.annotation.AuthenticationPrincipal
11+
import org.springframework.web.bind.annotation.GetMapping
12+
import org.springframework.web.bind.annotation.PathVariable
13+
import org.springframework.web.bind.annotation.RequestMapping
14+
import org.springframework.web.bind.annotation.RequestParam
15+
import org.springframework.web.bind.annotation.RestController
16+
import java.util.Locale
17+
18+
@RestController
19+
@RequestMapping("/api/v1/workspaces/{workspace-id}/events")
20+
class EventQueryController(
21+
private val eventFacadeService: EventFacadeService,
22+
private val apiHandler: ApiHandler,
23+
) : EventQuerySwagger {
24+
@GetMapping
25+
override fun getOngoingEvent(
26+
@AuthenticationPrincipal token: JwtAuthentication,
27+
@PathVariable("workspace-id") workspaceId: Long,
28+
@RequestParam status: String,
29+
): ResponseEntity<ApiResult<List<EventBriefResponse>>> {
30+
val eventStatus = EventStatus.valueOf(status.uppercase(Locale.getDefault()))
31+
return ResponseEntity.ok().body(
32+
apiHandler.onSuccess(
33+
eventFacadeService
34+
.getEvents(
35+
token.id,
36+
workspaceId,
37+
eventStatus,
38+
).map { EventBriefResponse.of(it, token.id) },
39+
),
40+
)
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.backgu.amaker.api.event.controller
2+
3+
import com.backgu.amaker.api.event.dto.response.EventBriefResponse
4+
import com.backgu.amaker.common.http.response.ApiResult
5+
import com.backgu.amaker.common.security.jwt.authentication.JwtAuthentication
6+
import io.swagger.v3.oas.annotations.Operation
7+
import io.swagger.v3.oas.annotations.responses.ApiResponse
8+
import io.swagger.v3.oas.annotations.responses.ApiResponses
9+
import io.swagger.v3.oas.annotations.tags.Tag
10+
import org.springframework.http.ResponseEntity
11+
import org.springframework.security.core.annotation.AuthenticationPrincipal
12+
import org.springframework.web.bind.annotation.PathVariable
13+
14+
@Tag(name = "event-query", description = "이벤트 조회 API")
15+
interface EventQuerySwagger {
16+
@Operation(summary = "진행중인 이벤트 조회", description = "진행중인 이벤트 리스트 조회입니다.")
17+
@ApiResponses(
18+
value = [
19+
ApiResponse(
20+
responseCode = "200",
21+
description = "진행중인 이벤트 리스트 조회 성공",
22+
),
23+
],
24+
)
25+
fun getOngoingEvent(
26+
@AuthenticationPrincipal token: JwtAuthentication,
27+
@PathVariable("workspace-id") workspaceId: Long,
28+
status: String,
29+
): ResponseEntity<ApiResult<List<EventBriefResponse>>>
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.backgu.amaker.api.event.dto
2+
3+
import com.backgu.amaker.api.user.dto.UserDto
4+
import com.backgu.amaker.domain.chat.Chat
5+
import com.backgu.amaker.domain.event.Event
6+
import com.backgu.amaker.domain.user.User
7+
import java.time.LocalDateTime
8+
9+
data class EventWithUserAndChatRoomDto(
10+
val id: Long,
11+
val chatRoomId: Long,
12+
val eventTitle: String,
13+
val deadLine: LocalDateTime,
14+
val notificationStartTime: LocalDateTime,
15+
val notificationInterval: Int,
16+
val users: List<UserDto>,
17+
val finishedCount: Int,
18+
val totalAssignedCount: Int,
19+
) {
20+
companion object {
21+
fun of(
22+
event: Event,
23+
chat: Chat,
24+
users: List<User>,
25+
finishedCount: Int = 0,
26+
): EventWithUserAndChatRoomDto =
27+
EventWithUserAndChatRoomDto(
28+
id = event.id,
29+
chatRoomId = chat.chatRoomId,
30+
eventTitle = event.eventTitle,
31+
deadLine = event.deadLine,
32+
notificationStartTime = event.notificationStartTime,
33+
notificationInterval = event.notificationInterval,
34+
users = users.map { UserDto.of(it) },
35+
finishedCount = finishedCount,
36+
totalAssignedCount = users.size,
37+
)
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.backgu.amaker.api.event.dto.response
2+
3+
import com.backgu.amaker.api.event.dto.EventWithUserAndChatRoomDto
4+
import io.swagger.v3.oas.annotations.media.Schema
5+
import java.time.LocalDateTime
6+
7+
data class EventBriefResponse(
8+
@Schema(
9+
description = "이벤트 아이디",
10+
example = "2",
11+
)
12+
val eventId: Long,
13+
@Schema(
14+
description = "채팅방 아이디",
15+
example = "21",
16+
)
17+
val chatRoomId: Long,
18+
@Schema(
19+
description = "이벤트 제목",
20+
example = "우리 어디서 만날지",
21+
)
22+
val eventTitle: String,
23+
@Schema(
24+
description = "데드라인",
25+
example = "2024-07-24T07:39:37.598",
26+
)
27+
val deadLine: LocalDateTime,
28+
@Schema(
29+
description = "알림 보낼 시작 시간",
30+
example = "2024-07-24T06:09:37.598",
31+
)
32+
val notificationStartTime: LocalDateTime,
33+
@Schema(
34+
description = "알림 주기",
35+
example = "15",
36+
)
37+
val notificationInterval: Int,
38+
@Schema(
39+
description = "유저 사진 리스트",
40+
example = "[\"http://a-maker.com/hi1.png\", \"http://a-maker.com/hi2.png\"]",
41+
)
42+
val users: List<String>,
43+
@Schema(
44+
description = "완료된 이벤트 수",
45+
example = "2",
46+
)
47+
val finishedCount: Int,
48+
@Schema(
49+
description = "총 배정된 이벤트 수",
50+
example = "5",
51+
)
52+
val totalAssignedCount: Int,
53+
@Schema(
54+
description = "이벤트가 자신의 것인지",
55+
example = "true",
56+
)
57+
val isMine: Boolean,
58+
) {
59+
companion object {
60+
fun of(
61+
event: EventWithUserAndChatRoomDto,
62+
userId: String,
63+
) = EventBriefResponse(
64+
eventId = event.id,
65+
chatRoomId = event.chatRoomId,
66+
eventTitle = event.eventTitle,
67+
deadLine = event.deadLine,
68+
notificationStartTime = event.notificationStartTime,
69+
notificationInterval = event.notificationInterval,
70+
users = event.users.map { it.picture },
71+
finishedCount = event.finishedCount,
72+
totalAssignedCount = event.totalAssignedCount,
73+
isMine = event.users.any { it.id == userId },
74+
)
75+
}
76+
}

api/src/main/kotlin/com/backgu/amaker/api/event/service/EventFacadeService.kt

+34
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.backgu.amaker.api.event.service
22

3+
import com.backgu.amaker.api.event.dto.EventWithUserAndChatRoomDto
34
import com.backgu.amaker.api.event.dto.ReactionEventCreateDto
45
import com.backgu.amaker.api.event.dto.ReactionEventDetailDto
56
import com.backgu.amaker.api.event.dto.ReactionEventDto
@@ -12,14 +13,20 @@ import com.backgu.amaker.application.chat.service.ChatRoomService
1213
import com.backgu.amaker.application.chat.service.ChatRoomUserService
1314
import com.backgu.amaker.application.chat.service.ChatService
1415
import com.backgu.amaker.application.event.service.EventAssignedUserService
16+
import com.backgu.amaker.application.event.service.EventService
1517
import com.backgu.amaker.application.event.service.ReactionEventService
1618
import com.backgu.amaker.application.event.service.ReactionOptionService
1719
import com.backgu.amaker.application.event.service.ReplyEventService
1820
import com.backgu.amaker.application.user.service.UserService
21+
import com.backgu.amaker.application.workspace.WorkspaceUserService
1922
import com.backgu.amaker.common.exception.BusinessException
2023
import com.backgu.amaker.common.status.StatusCode
2124
import com.backgu.amaker.domain.chat.Chat
2225
import com.backgu.amaker.domain.chat.ChatType
26+
import com.backgu.amaker.domain.event.Event
27+
import com.backgu.amaker.domain.event.EventAssignedUser
28+
import com.backgu.amaker.domain.event.EventStatus
29+
import com.backgu.amaker.domain.user.User
2330
import org.springframework.context.ApplicationEventPublisher
2431
import org.springframework.stereotype.Service
2532
import org.springframework.transaction.annotation.Transactional
@@ -36,6 +43,8 @@ class EventFacadeService(
3643
private val reactionOptionService: ReactionOptionService,
3744
private val eventAssignedUserService: EventAssignedUserService,
3845
private val eventPublisher: ApplicationEventPublisher,
46+
private val eventService: EventService,
47+
private val workspaceUserService: WorkspaceUserService,
3948
) {
4049
@Transactional
4150
fun getReplyEvent(
@@ -196,4 +205,29 @@ class EventFacadeService(
196205

197206
return ReactionEventDto.of(reactionEvent, reactionOptions)
198207
}
208+
209+
fun getEvents(
210+
userId: String,
211+
workspaceId: Long,
212+
eventStatus: EventStatus,
213+
): List<EventWithUserAndChatRoomDto> {
214+
workspaceUserService.validateUserInWorkspace(userId, workspaceId)
215+
val events: List<Event> = eventService.findEventByWorkspaceId(workspaceId)
216+
val eventUserMap: Map<Long, List<EventAssignedUser>> =
217+
eventAssignedUserService.findByEventIdsToEventIdMapped(events.map { it.id })
218+
val filteredEvents =
219+
events.filter { eventStatus.filter(it, eventUserMap[it.id] ?: emptyList()) }
220+
val chatToMap = chatService.findAllByIdsToMap(filteredEvents.map { it.id })
221+
222+
val userMap = userService.findAllByUserIdsToMap(eventUserMap.values.flatten().map { it.userId })
223+
224+
return filteredEvents.map { event: Event ->
225+
val eventAssignedUsers: List<User> =
226+
eventUserMap[event.id]?.mapNotNull { userMap[it.userId] } ?: emptyList()
227+
val finishedNumber = eventUserMap[event.id]?.count { it.isFinished } ?: 0
228+
val chat = chatToMap[event.id] ?: throw BusinessException(StatusCode.CHAT_NOT_FOUND)
229+
230+
EventWithUserAndChatRoomDto.of(event, chat, eventAssignedUsers, finishedNumber)
231+
}
232+
}
199233
}

domain/src/main/kotlin/com/backgu/amaker/domain/event/Event.kt

+4
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,8 @@ abstract class Event(
2929
finishedCount,
3030
totalAssignedCount,
3131
)
32+
33+
fun isAchieved(user: List<EventAssignedUser>): Boolean = user.size == user.count { it.isFinished }
34+
35+
fun isClosed(): Boolean = LocalDateTime.now().isAfter(deadLine)
3236
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.backgu.amaker.domain.event
2+
3+
enum class EventStatus {
4+
ONGOING {
5+
override fun filter(
6+
event: Event,
7+
assignedUsers: List<EventAssignedUser>,
8+
): Boolean = !event.isAchieved(assignedUsers) && !event.isClosed()
9+
},
10+
EXPIRED {
11+
override fun filter(
12+
event: Event,
13+
assignedUsers: List<EventAssignedUser>,
14+
): Boolean = event.isClosed() && !event.isAchieved(assignedUsers)
15+
},
16+
COMPLETED {
17+
override fun filter(
18+
event: Event,
19+
assignedUsers: List<EventAssignedUser>,
20+
): Boolean = event.isAchieved(assignedUsers)
21+
}, ;
22+
23+
abstract fun filter(
24+
event: Event,
25+
assignedUsers: List<EventAssignedUser>,
26+
): Boolean
27+
}

domain/src/test/kotlin/com/backgu/amaker/domain/event/EventAssignedUserTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import kotlin.test.Test
88
class EventAssignedUserTest {
99
@Test
1010
@DisplayName("isFinished 업데이트 테스트")
11-
fun updateIsFinished() {
11+
fun updateIsAchieved() {
1212
// given
1313
val eventAssignedUser =
1414
EventAssignedUser(

infra/src/main/kotlin/com/backgu/amaker/application/chat/service/ChatService.kt

+3
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,8 @@ class ChatService(
2929

3030
fun findAllByIds(chatIds: List<Long>): List<Chat> = chatRepository.findAllById(chatIds).map { it.toDomain() }
3131

32+
fun findAllByIdsToMap(chatIds: List<Long>): Map<Long, Chat> =
33+
chatRepository.findAllById(chatIds).map { it.toDomain() }.associateBy { it.id }
34+
3235
fun getById(chatId: Long) = chatRepository.findByIdOrNull(chatId)?.toDomain() ?: throw BusinessException(StatusCode.CHAT_NOT_FOUND)
3336
}

infra/src/main/kotlin/com/backgu/amaker/application/event/service/EventService.kt

+2
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@ class EventService(
1818

1919
fun findEventByIdsToMap(eventIds: List<Long>): Map<Long, Event> =
2020
eventRepository.findAllById(eventIds).map { it.toDomain() }.associateBy { it.id }
21+
22+
fun findEventByWorkspaceId(workspaceId: Long): List<Event> = eventRepository.findAllByWorkspaceId(workspaceId).map { it.toDomain() }
2123
}

infra/src/main/kotlin/com/backgu/amaker/infra/jpa/event/repository/EventRepository.kt

+10-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,14 @@ package com.backgu.amaker.infra.jpa.event.repository
22

33
import com.backgu.amaker.infra.jpa.event.entity.EventEntity
44
import org.springframework.data.jpa.repository.JpaRepository
5+
import org.springframework.data.jpa.repository.Query
56

6-
interface EventRepository : JpaRepository<EventEntity, Long>
7+
interface EventRepository : JpaRepository<EventEntity, Long> {
8+
@Query(
9+
"SELECT e FROM Event e " +
10+
"inner join Chat c on e.id = c.id " +
11+
"inner join ChatRoom ch on ch.id = c.chatRoomId " +
12+
"WHERE ch.workspaceId = :workspaceId",
13+
)
14+
fun findAllByWorkspaceId(workspaceId: Long): List<EventEntity>
15+
}

0 commit comments

Comments
 (0)