Skip to content

Commit

Permalink
Merge pull request #17 from onetime-with-members/feature/#5/event-api
Browse files Browse the repository at this point in the history
[feat] : 가장 많이 되는 시간 조회 API 구현
  • Loading branch information
bbbang105 authored Aug 20, 2024
2 parents 255ee8e + cfd062b commit af91797
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 4 deletions.
11 changes: 11 additions & 0 deletions src/main/java/side/onetime/controller/EventController.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -40,4 +42,13 @@ public ResponseEntity<ApiResponse<EventDto.GetParticipantsResponse>> getParticip
EventDto.GetParticipantsResponse getParticipantsResponse = eventService.getParticipants(eventId);
return ApiResponse.onSuccess(SuccessStatus._GET_PARTICIPANTS, getParticipantsResponse);
}

// 가장 많이 되는 시간 조회 API
@GetMapping("/{event_id}/most")
public ResponseEntity<ApiResponse<List<EventDto.GetMostPossibleTime>>> getMostPossibleTime(
@PathVariable("event_id") String eventId) {

List<EventDto.GetMostPossibleTime> getMostPossibleTimes = eventService.getMostPossibleTime(eventId);
return ApiResponse.onSuccess(SuccessStatus._GET_MOST_POSSIBLE_TIME, getMostPossibleTimes);
}
}
46 changes: 45 additions & 1 deletion src/main/java/side/onetime/dto/EventDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -99,4 +101,46 @@ public static GetParticipantsResponse of(List<Member> 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<String> possibleNames;
private List<String> impossibleNames;

public static GetMostPossibleTime dayOf(Schedule schedule, List<String> possibleNames, List<String> 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<String> possibleNames, List<String> 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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", "멤버 로그인에 성공했습니다."),
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Selection, Long> {
void deleteAllByMember(Member member);
@Query("SELECT s FROM Selection s JOIN FETCH s.schedule sc WHERE sc.event = :event")
List<Selection> findAllSelectionsByEvent(@Param("event") Event event);
}
92 changes: 89 additions & 3 deletions src/main/java/side/onetime/service/EventService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

// 이벤트 생성 메서드
Expand Down Expand Up @@ -122,4 +124,88 @@ public EventDto.GetParticipantsResponse getParticipants(String eventId) {

return EventDto.GetParticipantsResponse.of(members);
}

// 가장 많이 되는 시간 조회 메서드
@Transactional
public List<EventDto.GetMostPossibleTime> getMostPossibleTime(String eventId) {
Event event = eventRepository.findByEventId(UUID.fromString(eventId))
.orElseThrow(() -> new EventException(EventErrorResult._NOT_FOUND_EVENT));

List<Member> members = event.getMembers();
List<String> allMembersName = members.stream()
.map(Member::getName)
.toList();

List<Selection> selections = selectionRepository.findAllSelectionsByEvent(event);

Map<Schedule, List<String>> scheduleToNamesMap = buildScheduleToNamesMap(selections);
int mostPossibleCnt = scheduleToNamesMap.values().stream()
.mapToInt(List::size)
.max()
.orElse(0);

return buildMostPossibleTimes(scheduleToNamesMap, mostPossibleCnt, allMembersName, event.getCategory());
}

// 스케줄과 선택된 참여자 이름 매핑
private Map<Schedule, List<String>> buildScheduleToNamesMap(List<Selection> selections) {
Map<Schedule, List<String>> 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<EventDto.GetMostPossibleTime> buildMostPossibleTimes(Map<Schedule, List<String>> scheduleToNamesMap, int mostPossibleCnt, List<String> allMembersName, Category category) {
List<EventDto.GetMostPossibleTime> mostPossibleTimes = new ArrayList<>();
EventDto.GetMostPossibleTime previousTime = null;

for (Map.Entry<Schedule, List<String>> entry : scheduleToNamesMap.entrySet()) {
Schedule schedule = entry.getKey();
List<String> curNames = entry.getValue();

if (curNames.size() == mostPossibleCnt) {
if (canMergeWithPrevious(previousTime, schedule, curNames, category)) {
previousTime.updateEndTime(schedule.getTime());
} else {
List<String> 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<String> 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<String> curNames, List<String> impossibleNames, Category category) {
return category.equals(Category.DAY)
? EventDto.GetMostPossibleTime.dayOf(schedule, curNames, impossibleNames)
: EventDto.GetMostPossibleTime.dateOf(schedule, curNames, impossibleNames);
}
}

0 comments on commit af91797

Please sign in to comment.