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

[FEAT] 일정 리스트 조회 기능 #214

Merged
merged 14 commits into from
Dec 30, 2023
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ public enum ResponseMessage {
SUCCESS_CREATE_ALARM("알림 생성 성공"),
SUCCESS_GET_ALARMS("알림 리스트 조회 성공"),
SUCCESS_GET_ALARM("알림 상세 조회 성공"),
SUCCESS_DELETE_ALARM("알림 삭제 성공")
SUCCESS_DELETE_ALARM("알림 삭제 성공"),

/** schedule **/
SUCCESS_GET_SCHEDULES("일정 리스트 조회 성공")
;

private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.sopt.makers.operation.controller.app;

import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.sopt.makers.operation.common.ApiResponse;
import org.sopt.makers.operation.service.ScheduleService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;

import static org.sopt.makers.operation.common.ApiResponse.success;
import static org.sopt.makers.operation.common.ResponseMessage.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/app/schedules")
public class AppScheduleController {
private final ScheduleService scheduleService;

@ApiOperation("일정 리스트 조회")
@GetMapping
public ResponseEntity<ApiResponse> getAlarms(
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime end
) {
val response = scheduleService.getSchedules(start, end);
return ResponseEntity.ok(success(SUCCESS_GET_SCHEDULES.getMessage(), response));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.sopt.makers.operation.dto.schedule;

import lombok.*;
import org.sopt.makers.operation.entity.lecture.Attribute;
import org.sopt.makers.operation.entity.schedule.Schedule;

import java.time.LocalDate;
import java.time.format.TextStyle;
import java.util.List;
import java.util.Locale;
import java.util.Map;

public record SchedulesResponseDTO(
List<DateVO> dates
) {
public static SchedulesResponseDTO of(Map<LocalDate, List<Schedule>> scheduleMap) {
return new SchedulesResponseDTO(
scheduleMap.keySet().stream().sorted()
.map(key -> DateVO.of(key, scheduleMap.get(key)))
.toList()
);
}

@Builder
record DateVO(
String date,
String dayOfWeek,
List<ScheduleVO> schedules
) {
static DateVO of(LocalDate date, List<Schedule> schedules) {
return DateVO.builder()
.date(date.toString())
.dayOfWeek(date.getDayOfWeek().getDisplayName(TextStyle.SHORT, Locale.KOREAN))
.schedules(schedules.stream().map(ScheduleVO::of).toList())
.build();
}
}

@Builder
record ScheduleVO(
Long scheduleId,
String startDate,
String endDate,
Attribute attribute,
String title
) {
static ScheduleVO of(Schedule schedule) {
return ScheduleVO.builder()
.scheduleId(schedule.getId())
.startDate(schedule.getStartDate().toString())
.endDate(schedule.getEndDate().toString())
.attribute(schedule.getAttribute())
.title(schedule.getTitle())
.build();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.sopt.makers.operation.entity.schedule;

import lombok.Getter;
import lombok.NoArgsConstructor;
import org.sopt.makers.operation.entity.BaseEntity;
import org.sopt.makers.operation.entity.lecture.Attribute;

import javax.persistence.*;
import java.time.LocalDateTime;

import static javax.persistence.EnumType.STRING;
import static javax.persistence.GenerationType.IDENTITY;

@Entity
@NoArgsConstructor
@Getter
public class Schedule extends BaseEntity {
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "schedule_id")
private Long id;

private LocalDateTime startDate;

private LocalDateTime endDate;

@Column(nullable = false)
@Enumerated(value = STRING)
private Attribute attribute;

private String title;

private String location;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.sopt.makers.operation.repository.schedule;

import org.sopt.makers.operation.entity.schedule.Schedule;

import java.time.LocalDateTime;
import java.util.List;

public interface ScheduleCustomRepository {
List<Schedule> findBetweenStartAndEnd(LocalDateTime start, LocalDateTime end);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.sopt.makers.operation.repository.schedule;

import org.sopt.makers.operation.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ScheduleRepository extends JpaRepository<Member, Long>, ScheduleCustomRepository {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.sopt.makers.operation.repository.schedule;

import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;

import lombok.RequiredArgsConstructor;

import org.sopt.makers.operation.entity.schedule.Schedule;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.util.List;

import static org.sopt.makers.operation.entity.schedule.QSchedule.schedule;

@Repository
@RequiredArgsConstructor
public class ScheduleRepositoryImpl implements ScheduleCustomRepository {

private final JPAQueryFactory queryFactory;

@Override
public List<Schedule> findBetweenStartAndEnd(LocalDateTime start, LocalDateTime end) {
return queryFactory
.select(schedule)
.from(schedule)
.where(
schedule.startDate.between(start, end)
.or(schedule.endDate.between(start, end))
)
.orderBy(schedule.startDate.asc())
.fetch();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.sopt.makers.operation.service;

import org.sopt.makers.operation.dto.schedule.SchedulesResponseDTO;

import java.time.LocalDateTime;

public interface ScheduleService {
SchedulesResponseDTO getSchedules(LocalDateTime start, LocalDateTime end);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package org.sopt.makers.operation.service;

import lombok.RequiredArgsConstructor;
import lombok.val;
import org.sopt.makers.operation.dto.schedule.SchedulesResponseDTO;
import org.sopt.makers.operation.entity.schedule.Schedule;
import org.sopt.makers.operation.repository.schedule.ScheduleRepository;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.LongStream;

@Service
@RequiredArgsConstructor
public class ScheduleServiceImpl implements ScheduleService {
private final ScheduleRepository scheduleRepository;

@Override
public SchedulesResponseDTO getSchedules(LocalDateTime start, LocalDateTime end) {
val schedules = scheduleRepository.findBetweenStartAndEnd(start, end);
val scheduleMap = classifiedByDate(schedules, start, end);
return SchedulesResponseDTO.of(scheduleMap);
}

private Map<LocalDate, List<Schedule>> classifiedByDate(List<Schedule> schedules, LocalDateTime startAt, LocalDateTime endAt) {
val scheduleMap = initScheduleMap(startAt, endAt);
schedules.forEach(schedule -> putScheduleMap(scheduleMap, schedule));
return scheduleMap;
}

private Map<LocalDate, List<Schedule>> initScheduleMap(LocalDateTime startAt, LocalDateTime endAt) {
//TODO: 클라이언트 개발 시간 리소스 절약을 위해 해당 메소드 활용, 가능한 일정이 존재하는 날짜만 key로 가지는 HashMap로 변경 요망
val duration = Duration.between(startAt, endAt).toDays() + 1;
validDuration(duration);
return LongStream.range(0, duration)
.mapToObj(startAt::plusDays)
.collect(Collectors.toMap(
LocalDateTime::toLocalDate,
date -> new ArrayList<>()));
}

private void validDuration(long duration) {
//TODO: 추후 응답 값 형식 변경 후 삭제될 수 있는 메소드
if (duration > 50) {
throw new IllegalStateException("조회할 날짜 기간은 50일을 넘길 수 없습니다.");
}
}

private void putScheduleMap(Map<LocalDate, List<Schedule>> scheduleMap, Schedule schedule) {
val duration = ChronoUnit.DAYS.between(schedule.getStartDate(), schedule.getEndDate());
scheduleMap.computeIfAbsent(schedule.getStartDate().toLocalDate(), k -> new ArrayList<>()).add(schedule);
if (duration >= 1) {
scheduleMap.computeIfAbsent(schedule.getEndDate().toLocalDate(), k -> new ArrayList<>()).add(schedule);
if (duration >= 2) {
putScheduleMapBetween(scheduleMap, schedule, (int)duration);
}
}
}

private void putScheduleMapBetween(Map<LocalDate, List<Schedule>> scheduleMap, Schedule schedule, int duration) {
for (int i = 1; i < duration; i++) {
val date = schedule.getStartDate().plusDays(i).toLocalDate();
scheduleMap.computeIfAbsent(date, k -> new ArrayList<>()).add(schedule);
}
}
}