From a102d8b0aa1d4561ca2110817aaae423f3245e72 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 21 Aug 2024 02:05:48 +0900 Subject: [PATCH 1/4] =?UTF-8?q?#5=20[feat]=20:=20=EA=B0=80=EC=9E=A5=20?= =?UTF-8?q?=EB=A7=8E=EC=9D=B4=20=EB=90=98=EB=8A=94=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=84=B1=EA=B3=B5=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/side/onetime/global/common/constant/SuccessStatus.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/side/onetime/global/common/constant/SuccessStatus.java b/src/main/java/side/onetime/global/common/constant/SuccessStatus.java index 7ea38f6..12ba135 100644 --- a/src/main/java/side/onetime/global/common/constant/SuccessStatus.java +++ b/src/main/java/side/onetime/global/common/constant/SuccessStatus.java @@ -16,6 +16,7 @@ public enum SuccessStatus implements BaseCode { _CREATED_EVENT(HttpStatus.CREATED, "201", "이벤트 생성에 성공했습니다."), _GET_EVENT(HttpStatus.OK, "200", "이벤트 조회에 성공했습니다."), _GET_PARTICIPANTS(HttpStatus.OK, "200", "참여자 조회에 성공했습니다."), + _GET_MOST_POSSIBLE_TIME(HttpStatus.OK, "200", "가장 많이 되는 시간 조회에 성공했습니다."), // Member _REGISTER_MEMBER(HttpStatus.CREATED, "201", "멤버 등록에 성공했습니다."), _LOGIN_MEMBER(HttpStatus.OK, "200", "멤버 로그인에 성공했습니다."), From 2c37c191d1bb520e9b0b0c0626ac0eaf08b7bf32 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 21 Aug 2024 02:06:14 +0900 Subject: [PATCH 2/4] =?UTF-8?q?#5=20[feat]=20:=20fetch=20join=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=97=AC=20=EC=84=B1=EB=8A=A5=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/side/onetime/repository/SelectionRepository.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/side/onetime/repository/SelectionRepository.java b/src/main/java/side/onetime/repository/SelectionRepository.java index 1b94ddc..66d9a29 100644 --- a/src/main/java/side/onetime/repository/SelectionRepository.java +++ b/src/main/java/side/onetime/repository/SelectionRepository.java @@ -1,9 +1,16 @@ package side.onetime.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import side.onetime.domain.Event; import side.onetime.domain.Member; import side.onetime.domain.Selection; +import java.util.List; + public interface SelectionRepository extends JpaRepository { void deleteAllByMember(Member member); + @Query("SELECT s FROM Selection s JOIN FETCH s.schedule sc WHERE sc.event = :event") + List findAllSelectionsByEvent(@Param("event") Event event); } \ No newline at end of file From 1b30125ced24c8b088cc97875fd3d9d27f1af5aa Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 21 Aug 2024 02:10:25 +0900 Subject: [PATCH 3/4] =?UTF-8?q?#5=20[feat]=20:=20=EA=B0=80=EC=9E=A5=20?= =?UTF-8?q?=EB=A7=8E=EC=9D=B4=20=EB=90=98=EB=8A=94=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20DTO=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/side/onetime/dto/EventDto.java | 46 +++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/main/java/side/onetime/dto/EventDto.java b/src/main/java/side/onetime/dto/EventDto.java index de1f9e0..1a6e14b 100644 --- a/src/main/java/side/onetime/dto/EventDto.java +++ b/src/main/java/side/onetime/dto/EventDto.java @@ -9,8 +9,10 @@ import lombok.NoArgsConstructor; import side.onetime.domain.Event; import side.onetime.domain.Member; +import side.onetime.domain.Schedule; import side.onetime.global.common.constant.Category; +import java.time.LocalTime; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @@ -99,4 +101,46 @@ public static GetParticipantsResponse of(List members) { .build(); } } -} + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + @JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class) + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class GetMostPossibleTime { + private String timePoint; + private String startTime; + private String endTime; + private int possibleCount; + private List possibleNames; + private List impossibleNames; + + public static GetMostPossibleTime dayOf(Schedule schedule, List possibleNames, List impossibleNames) { + return GetMostPossibleTime.builder() + .timePoint(schedule.getDay()) + .startTime(schedule.getTime()) + .endTime(String.valueOf(LocalTime.parse(schedule.getTime()).plusMinutes(30))) + .possibleCount(possibleNames.size()) + .possibleNames(possibleNames) + .impossibleNames(impossibleNames) + .build(); + } + + public static GetMostPossibleTime dateOf(Schedule schedule, List possibleNames, List impossibleNames) { + return GetMostPossibleTime.builder() + .timePoint(schedule.getDate()) + .startTime(schedule.getTime()) + .endTime(String.valueOf(LocalTime.parse(schedule.getTime()).plusMinutes(30))) + .possibleCount(possibleNames.size()) + .possibleNames(possibleNames) + .impossibleNames(impossibleNames) + .build(); + } + + public void updateEndTime(String endTime) { + endTime = String.valueOf(LocalTime.parse(endTime).plusMinutes(30)); + this.endTime = endTime; + } + } +} \ No newline at end of file From cfd062b22d2b8079b7b92528a2dfccf4e1228463 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Wed, 21 Aug 2024 02:10:43 +0900 Subject: [PATCH 4/4] =?UTF-8?q?#5=20[feat]=20:=20=EA=B0=80=EC=9E=A5=20?= =?UTF-8?q?=EB=A7=8E=EC=9D=B4=20=EB=90=98=EB=8A=94=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onetime/controller/EventController.java | 11 +++ .../side/onetime/service/EventService.java | 92 ++++++++++++++++++- 2 files changed, 100 insertions(+), 3 deletions(-) diff --git a/src/main/java/side/onetime/controller/EventController.java b/src/main/java/side/onetime/controller/EventController.java index edee4af..17aa2b4 100644 --- a/src/main/java/side/onetime/controller/EventController.java +++ b/src/main/java/side/onetime/controller/EventController.java @@ -8,6 +8,8 @@ import side.onetime.global.common.constant.SuccessStatus; import side.onetime.service.EventService; +import java.util.List; + @RestController @RequestMapping("/api/v1/events") @RequiredArgsConstructor @@ -40,4 +42,13 @@ public ResponseEntity> getParticip EventDto.GetParticipantsResponse getParticipantsResponse = eventService.getParticipants(eventId); return ApiResponse.onSuccess(SuccessStatus._GET_PARTICIPANTS, getParticipantsResponse); } + + // 가장 많이 되는 시간 조회 API + @GetMapping("/{event_id}/most") + public ResponseEntity>> getMostPossibleTime( + @PathVariable("event_id") String eventId) { + + List getMostPossibleTimes = eventService.getMostPossibleTime(eventId); + return ApiResponse.onSuccess(SuccessStatus._GET_MOST_POSSIBLE_TIME, getMostPossibleTimes); + } } diff --git a/src/main/java/side/onetime/service/EventService.java b/src/main/java/side/onetime/service/EventService.java index a376e7e..90dcfe8 100644 --- a/src/main/java/side/onetime/service/EventService.java +++ b/src/main/java/side/onetime/service/EventService.java @@ -6,6 +6,7 @@ import side.onetime.domain.Event; import side.onetime.domain.Member; import side.onetime.domain.Schedule; +import side.onetime.domain.Selection; import side.onetime.dto.EventDto; import side.onetime.exception.EventErrorResult; import side.onetime.exception.EventException; @@ -14,18 +15,19 @@ import side.onetime.global.common.constant.Category; import side.onetime.repository.EventRepository; import side.onetime.repository.ScheduleRepository; +import side.onetime.repository.SelectionRepository; import side.onetime.util.DateUtil; import java.time.LocalTime; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; +import java.util.*; @Service @RequiredArgsConstructor public class EventService { + private static final int MAX_MOST_POSSIBLE_TIMES_SIZE = 6; private final EventRepository eventRepository; private final ScheduleRepository scheduleRepository; + private final SelectionRepository selectionRepository; private final DateUtil dateUtil; // 이벤트 생성 메서드 @@ -122,4 +124,88 @@ public EventDto.GetParticipantsResponse getParticipants(String eventId) { return EventDto.GetParticipantsResponse.of(members); } + + // 가장 많이 되는 시간 조회 메서드 + @Transactional + public List getMostPossibleTime(String eventId) { + Event event = eventRepository.findByEventId(UUID.fromString(eventId)) + .orElseThrow(() -> new EventException(EventErrorResult._NOT_FOUND_EVENT)); + + List members = event.getMembers(); + List allMembersName = members.stream() + .map(Member::getName) + .toList(); + + List selections = selectionRepository.findAllSelectionsByEvent(event); + + Map> scheduleToNamesMap = buildScheduleToNamesMap(selections); + int mostPossibleCnt = scheduleToNamesMap.values().stream() + .mapToInt(List::size) + .max() + .orElse(0); + + return buildMostPossibleTimes(scheduleToNamesMap, mostPossibleCnt, allMembersName, event.getCategory()); + } + + // 스케줄과 선택된 참여자 이름 매핑 + private Map> buildScheduleToNamesMap(List selections) { + Map> map = new LinkedHashMap<>(); + for (Selection selection : selections) { + Schedule schedule = selection.getSchedule(); + String memberName = selection.getMember().getName(); + + map.computeIfAbsent(schedule, k -> new ArrayList<>()).add(memberName); + } + return map; + } + + // 최적 시간대 리스트 생성 + private List buildMostPossibleTimes(Map> scheduleToNamesMap, int mostPossibleCnt, List allMembersName, Category category) { + List mostPossibleTimes = new ArrayList<>(); + EventDto.GetMostPossibleTime previousTime = null; + + for (Map.Entry> entry : scheduleToNamesMap.entrySet()) { + Schedule schedule = entry.getKey(); + List curNames = entry.getValue(); + + if (curNames.size() == mostPossibleCnt) { + if (canMergeWithPrevious(previousTime, schedule, curNames, category)) { + previousTime.updateEndTime(schedule.getTime()); + } else { + List impossibleNames = allMembersName.stream() + .filter(name -> !curNames.contains(name)) + .toList(); + + EventDto.GetMostPossibleTime newTime = createMostPossibleTime(schedule, curNames, impossibleNames, category); + mostPossibleTimes.add(newTime); + previousTime = newTime; + } + } + + if (mostPossibleTimes.size() == MAX_MOST_POSSIBLE_TIMES_SIZE) { + break; + } + } + return mostPossibleTimes; + } + + // 이전 시간대와 병합이 가능한지 확인 + private boolean canMergeWithPrevious(EventDto.GetMostPossibleTime previousTime, Schedule schedule, List curNames, Category category) { + if (previousTime == null) return false; + + boolean isSameTimePoint = category.equals(Category.DAY) + ? previousTime.getTimePoint().equals(schedule.getDay()) + : previousTime.getTimePoint().equals(schedule.getDate()); + + return isSameTimePoint + && previousTime.getEndTime().equals(schedule.getTime()) + && new HashSet<>(previousTime.getPossibleNames()).containsAll(curNames); + } + + // 새로운 시간대 객체 생성 + private EventDto.GetMostPossibleTime createMostPossibleTime(Schedule schedule, List curNames, List impossibleNames, Category category) { + return category.equals(Category.DAY) + ? EventDto.GetMostPossibleTime.dayOf(schedule, curNames, impossibleNames) + : EventDto.GetMostPossibleTime.dateOf(schedule, curNames, impossibleNames); + } } \ No newline at end of file