From da9887c56a75b7fc0b44c982796e7c170cc20a21 Mon Sep 17 00:00:00 2001 From: Arachneee Date: Mon, 22 Jul 2024 17:22:04 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=98=84=EC=9E=AC=20=EC=B0=B8=EC=97=AC?= =?UTF-8?q?=20=EC=9D=B8=EC=9B=90=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/MemberActionService.java | 27 +++++++++++--- .../response/CurrentMemberAppResponse.java | 10 +++++ .../domain/action/CurrentMembers.java | 37 +++++++++++++++++++ .../presentation/MemberActionController.java | 12 ++++++ .../response/CurrentMemberResponse.java | 10 +++++ .../response/CurrentMembersResponse.java | 15 ++++++++ .../application/MemberActionServiceTest.java | 29 ++++++++++----- .../domain/action/CurrentMembersTest.java | 30 +++++++++++++++ .../MemberActionControllerTest.java | 21 +++++++++++ 9 files changed, 176 insertions(+), 15 deletions(-) create mode 100644 server/src/main/java/server/haengdong/application/response/CurrentMemberAppResponse.java create mode 100644 server/src/main/java/server/haengdong/domain/action/CurrentMembers.java create mode 100644 server/src/main/java/server/haengdong/presentation/response/CurrentMemberResponse.java create mode 100644 server/src/main/java/server/haengdong/presentation/response/CurrentMembersResponse.java create mode 100644 server/src/test/java/server/haengdong/domain/action/CurrentMembersTest.java diff --git a/server/src/main/java/server/haengdong/application/MemberActionService.java b/server/src/main/java/server/haengdong/application/MemberActionService.java index 1acc88b03..ba17b28c4 100644 --- a/server/src/main/java/server/haengdong/application/MemberActionService.java +++ b/server/src/main/java/server/haengdong/application/MemberActionService.java @@ -5,12 +5,14 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import server.haengdong.application.request.MemberActionsSaveAppRequest; +import server.haengdong.application.response.CurrentMemberAppResponse; import server.haengdong.domain.action.Action; -import server.haengdong.domain.event.Event; -import server.haengdong.domain.action.MemberAction; import server.haengdong.domain.action.ActionRepository; -import server.haengdong.domain.event.EventRepository; +import server.haengdong.domain.action.CurrentMembers; +import server.haengdong.domain.action.MemberAction; import server.haengdong.domain.action.MemberActionRepository; +import server.haengdong.domain.event.Event; +import server.haengdong.domain.event.EventRepository; @RequiredArgsConstructor @Transactional(readOnly = true) @@ -24,8 +26,7 @@ public class MemberActionService { @Transactional public void saveMemberAction(String token, MemberActionsSaveAppRequest request) { - Event event = eventRepository.findByToken(token) - .orElseThrow(() -> new IllegalArgumentException("event not found")); + Event event = findEvent(token); List findMemberActions = memberActionRepository.findAllByEvent(event); Action action = createStartAction(event); @@ -38,4 +39,20 @@ private Action createStartAction(Event event) { .map(Action::next) .orElse(Action.createFirst(event)); } + + public List getCurrentMembers(String token) { + Event event = findEvent(token); + List findMemberActions = memberActionRepository.findAllByEvent(event); + CurrentMembers currentMembers = CurrentMembers.of(findMemberActions); + + return currentMembers.getMembers() + .stream() + .map(CurrentMemberAppResponse::new) + .toList(); + } + + private Event findEvent(String token) { + return eventRepository.findByToken(token) + .orElseThrow(() -> new IllegalArgumentException("event not found")); + } } diff --git a/server/src/main/java/server/haengdong/application/response/CurrentMemberAppResponse.java b/server/src/main/java/server/haengdong/application/response/CurrentMemberAppResponse.java new file mode 100644 index 000000000..6c682d3e9 --- /dev/null +++ b/server/src/main/java/server/haengdong/application/response/CurrentMemberAppResponse.java @@ -0,0 +1,10 @@ +package server.haengdong.application.response; + +import server.haengdong.domain.action.MemberAction; + +public record CurrentMemberAppResponse(String name) { + + public static CurrentMemberAppResponse of(MemberAction memberAction) { + return new CurrentMemberAppResponse(memberAction.getMemberName()); + } +} diff --git a/server/src/main/java/server/haengdong/domain/action/CurrentMembers.java b/server/src/main/java/server/haengdong/domain/action/CurrentMembers.java new file mode 100644 index 000000000..e6843a404 --- /dev/null +++ b/server/src/main/java/server/haengdong/domain/action/CurrentMembers.java @@ -0,0 +1,37 @@ +package server.haengdong.domain.action; + +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class CurrentMembers { + + private final Set members; + + public static CurrentMembers of(List memberActions) { + List sortedMemberActions = getSortedMemberActions(memberActions); + Set members = new HashSet<>(); + for (MemberAction memberAction : sortedMemberActions) { + String member = memberAction.getMemberName(); + if (memberAction.isSameStatus(MemberActionStatus.IN)) { + members.add(member); + continue; + } + members.remove(member); + } + + return new CurrentMembers(members); + } + + private static List getSortedMemberActions(List memberActions) { + return memberActions.stream() + .sorted(Comparator.comparing(MemberAction::getSequence)) + .toList(); + } +} diff --git a/server/src/main/java/server/haengdong/presentation/MemberActionController.java b/server/src/main/java/server/haengdong/presentation/MemberActionController.java index e35bcfd7b..cd45e3237 100644 --- a/server/src/main/java/server/haengdong/presentation/MemberActionController.java +++ b/server/src/main/java/server/haengdong/presentation/MemberActionController.java @@ -1,13 +1,17 @@ package server.haengdong.presentation; +import java.util.List; import lombok.RequiredArgsConstructor; 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.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import server.haengdong.application.MemberActionService; +import server.haengdong.application.response.CurrentMemberAppResponse; import server.haengdong.presentation.request.MemberActionsSaveRequest; +import server.haengdong.presentation.response.CurrentMembersResponse; @RequiredArgsConstructor @RestController @@ -24,4 +28,12 @@ public ResponseEntity saveMemberAction( return ResponseEntity.ok().build(); } + + @GetMapping("/api/events/{token}/members/current") + public ResponseEntity getCurrentMembers(@PathVariable("token") String token) { + List currentMembers = memberActionService.getCurrentMembers(token); + + return ResponseEntity.ok() + .body(CurrentMembersResponse.of(currentMembers)); + } } diff --git a/server/src/main/java/server/haengdong/presentation/response/CurrentMemberResponse.java b/server/src/main/java/server/haengdong/presentation/response/CurrentMemberResponse.java new file mode 100644 index 000000000..4308c7d17 --- /dev/null +++ b/server/src/main/java/server/haengdong/presentation/response/CurrentMemberResponse.java @@ -0,0 +1,10 @@ +package server.haengdong.presentation.response; + +import server.haengdong.application.response.CurrentMemberAppResponse; + +public record CurrentMemberResponse(String name) { + + public static CurrentMemberResponse of(CurrentMemberAppResponse response) { + return new CurrentMemberResponse(response.name()); + } +} diff --git a/server/src/main/java/server/haengdong/presentation/response/CurrentMembersResponse.java b/server/src/main/java/server/haengdong/presentation/response/CurrentMembersResponse.java new file mode 100644 index 000000000..4eb915dd4 --- /dev/null +++ b/server/src/main/java/server/haengdong/presentation/response/CurrentMembersResponse.java @@ -0,0 +1,15 @@ +package server.haengdong.presentation.response; + +import java.util.List; +import server.haengdong.application.response.CurrentMemberAppResponse; + +public record CurrentMembersResponse(List members) { + + public static CurrentMembersResponse of(List currentMembers) { + List responses = currentMembers.stream() + .map(CurrentMemberResponse::of) + .toList(); + + return new CurrentMembersResponse(responses); + } +} diff --git a/server/src/test/java/server/haengdong/application/MemberActionServiceTest.java b/server/src/test/java/server/haengdong/application/MemberActionServiceTest.java index 73a1f5376..378aa6006 100644 --- a/server/src/test/java/server/haengdong/application/MemberActionServiceTest.java +++ b/server/src/test/java/server/haengdong/application/MemberActionServiceTest.java @@ -1,6 +1,9 @@ package server.haengdong.application; import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static server.haengdong.domain.action.MemberActionStatus.IN; +import static server.haengdong.domain.action.MemberActionStatus.OUT; import java.util.List; import org.junit.jupiter.api.AfterEach; @@ -11,12 +14,11 @@ import server.haengdong.application.request.MemberActionSaveAppRequest; import server.haengdong.application.request.MemberActionsSaveAppRequest; import server.haengdong.domain.action.Action; -import server.haengdong.domain.event.Event; -import server.haengdong.domain.action.MemberAction; -import server.haengdong.domain.action.MemberActionStatus; import server.haengdong.domain.action.ActionRepository; -import server.haengdong.domain.event.EventRepository; +import server.haengdong.domain.action.MemberAction; import server.haengdong.domain.action.MemberActionRepository; +import server.haengdong.domain.event.Event; +import server.haengdong.domain.event.EventRepository; @SpringBootTest class MemberActionServiceTest { @@ -40,12 +42,12 @@ void tearDown() { eventRepository.deleteAllInBatch(); } - @DisplayName("현재 행사에 참여하고 있는 경우에 나갈 수 있다") + @DisplayName("현재 행사에 참여하고 있는 경우에 나갈 수 있다.") @Test void saveMemberActionTest() { Event event = eventRepository.save(new Event("test", "TOKEN")); Action action = new Action(event, 1L); - MemberAction memberAction = new MemberAction(action, "망쵸", MemberActionStatus.IN, 1L); + MemberAction memberAction = new MemberAction(action, "망쵸", IN, 1L); memberActionRepository.save(memberAction); assertThatCode(() -> memberActionService.saveMemberAction("TOKEN", new MemberActionsSaveAppRequest( @@ -53,16 +55,16 @@ void saveMemberActionTest() { .doesNotThrowAnyException(); } - @DisplayName("행사에서 퇴장한 경우에 입장할 수 있다") + @DisplayName("행사에서 퇴장한 경우에 입장할 수 있다.") @Test void saveMemberActionTest1() { Event event = eventRepository.save(new Event("test", "TOKEN")); Action actionOne = new Action(event, 1L); - MemberAction memberActionOne = new MemberAction(actionOne, "망쵸", MemberActionStatus.IN, 1L); + MemberAction memberActionOne = new MemberAction(actionOne, "망쵸", IN, 1L); memberActionRepository.save(memberActionOne); Action actionTwo = new Action(event, 2L); - MemberAction memberActionTwo = new MemberAction(actionTwo, "망쵸", MemberActionStatus.OUT, 1L); + MemberAction memberActionTwo = new MemberAction(actionTwo, "망쵸", OUT, 1L); memberActionRepository.save(memberActionTwo); assertThatCode(() -> memberActionService.saveMemberAction("TOKEN", new MemberActionsSaveAppRequest( @@ -70,7 +72,7 @@ void saveMemberActionTest1() { .doesNotThrowAnyException(); } - @DisplayName("행사에 입장하지 않았을 경우 퇴장할 수 없다") + @DisplayName("행사에 입장하지 않았을 경우 퇴장할 수 없다.") @Test void saveMemberActionTest2() { MemberActionsSaveAppRequest appRequest = new MemberActionsSaveAppRequest( @@ -79,4 +81,11 @@ void saveMemberActionTest2() { assertThatCode(() -> memberActionService.saveMemberAction("TOKEN", appRequest)) .isInstanceOf(IllegalArgumentException.class); } + + @DisplayName("이벤트가 없으면 현재 참여 인원을 조회할 수 없다.") + @Test + void getCurrentMembers() { + assertThatThrownBy(() -> memberActionService.getCurrentMembers("token")) + .isInstanceOf(IllegalArgumentException.class); + } } diff --git a/server/src/test/java/server/haengdong/domain/action/CurrentMembersTest.java b/server/src/test/java/server/haengdong/domain/action/CurrentMembersTest.java new file mode 100644 index 000000000..37ddca432 --- /dev/null +++ b/server/src/test/java/server/haengdong/domain/action/CurrentMembersTest.java @@ -0,0 +1,30 @@ +package server.haengdong.domain.action; + +import static org.assertj.core.api.Assertions.assertThat; +import static server.haengdong.domain.action.MemberActionStatus.IN; +import static server.haengdong.domain.action.MemberActionStatus.OUT; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import server.haengdong.domain.event.Event; + +class CurrentMembersTest { + + @DisplayName("인원 변동 이력으로 현재 참여 인원을 계산한다.") + @Test + void of() { + Event event = new Event("test", "TOKEN"); + List memberActions = List.of( + new MemberAction(new Action(event, 1L), "망쵸", IN, 1L), + new MemberAction(new Action(event, 2L), "백호", IN, 1L), + new MemberAction(new Action(event, 3L), "백호", OUT, 1L), + new MemberAction(new Action(event, 4L), "웨디", IN, 1L) + ); + + CurrentMembers currentMembers = CurrentMembers.of(memberActions); + + assertThat(currentMembers.getMembers()) + .containsExactlyInAnyOrder("망쵸", "웨디"); + } +} diff --git a/server/src/test/java/server/haengdong/presentation/MemberActionControllerTest.java b/server/src/test/java/server/haengdong/presentation/MemberActionControllerTest.java index 8f9e71182..be8b89182 100644 --- a/server/src/test/java/server/haengdong/presentation/MemberActionControllerTest.java +++ b/server/src/test/java/server/haengdong/presentation/MemberActionControllerTest.java @@ -1,5 +1,9 @@ package server.haengdong.presentation; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -13,7 +17,9 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import server.haengdong.application.MemberActionService; +import server.haengdong.application.response.CurrentMemberAppResponse; import server.haengdong.presentation.request.MemberActionSaveRequest; import server.haengdong.presentation.request.MemberActionsSaveRequest; @@ -46,4 +52,19 @@ void saveMemberActionTest() throws Exception { .andDo(print()) .andExpect(status().isOk()); } + + @DisplayName("현재 참여 인원을 조회합니다.") + @Test + void getCurrentMembers() throws Exception { + List currentMemberAppResponses = List.of(new CurrentMemberAppResponse("소하"), new CurrentMemberAppResponse("토다리")); + + given(memberActionService.getCurrentMembers(any())).willReturn(currentMemberAppResponses); + + mockMvc.perform(get("/api/events/{token}/members/current", "token") + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(MockMvcResultMatchers.jsonPath("$.members[0].name").value(equalTo("소하"))) + .andExpect(MockMvcResultMatchers.jsonPath("$.members[1].name").value(equalTo("토다리"))); + } }