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

달력 조회 API를 작성 #50

Open
wants to merge 23 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
52eec9f
feat: 캘린더 조회 전략 패턴 적용
jiwon83 Oct 6, 2024
8689bf2
Merge remote-tracking branch 'origin/dev' into feat/#38
jiwon83 Oct 20, 2024
1c7b4c9
chore: querydsl 의존성 추가
jiwon83 Oct 20, 2024
271708e
feat: ScheduleRepository 에서 calendarId, startDate, endDate 로 List<Sch…
jiwon83 Oct 20, 2024
00d8042
feat: CalendarService 추가
jiwon83 Oct 20, 2024
42ec4e4
feat: CalendarServiceUtil의 Day, Week 반복 일정 조회 기능 구현
jiwon83 Nov 9, 2024
85a1f70
Test: Calendar, Schedule, Scheduleinfo Fixture 생성
jiwon83 Nov 9, 2024
3c99aa5
chore: wypl-core 모듈 lombok 설정
jiwon83 Nov 9, 2024
6d2f219
feat: 반복 주기 = Month 조회 기능 구현 in CalendarServiceUtil
jiwon83 Nov 10, 2024
979305d
fix: 반복 주기 = Week 조회 기능의 버그 수정
jiwon83 Nov 10, 2024
014915d
feat: 반복 주기 = Year 조회 기능 구현 in CalendarServiceUtil
jiwon83 Nov 17, 2024
0720586
refactor: 반복주기에 따른 할일 조회 기능 리팩토링
jiwon83 Nov 17, 2024
01c0a28
feat: Calendar Type에 따른 전략 클래스 구현
jiwon83 Nov 17, 2024
ed2643d
refactor: Calendar Type에 따른 전략 클래스 구현
jiwon83 Nov 23, 2024
3378cff
feat: ScheduleFindResponse.java 구현
jiwon83 Nov 23, 2024
f19b02a
Merge remote-tracking branch 'refs/remotes/origin/dev' into feat/#38
jiwon83 Nov 23, 2024
2c62b29
fix: fix build error
jiwon83 Nov 23, 2024
0ddc92b
Refactor(#38): refactor code related to calendar strategy
jiwon83 Nov 24, 2024
e023533
Merge remote-tracking branch 'origin/dev' into feat/#38
jiwon83 Dec 1, 2024
7527b60
refactor(#38): refactor & remove unnecessary code
jiwon83 Dec 1, 2024
697c02e
chore(#38): add dependency "testAnnotationProcessor"
jiwon83 Dec 1, 2024
2721398
feat(#38): add CalendarRepository
jiwon83 Dec 1, 2024
f609235
refactor(#38): reformat code for applying code convention
jiwon83 Dec 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions application/wypl-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ dependencies {
implementation project(':domain:jpa-calendar-domain')
implementation project(':domain:jpa-member-domain')
implementation project(':common')
compileOnly 'org.projectlombok:lombok:1.18.24'
annotationProcessor 'org.projectlombok:lombok:1.18.24'
testCompileOnly 'org.projectlombok:lombok:1.18.24'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.24'
jiwon83 marked this conversation as resolved.
Show resolved Hide resolved

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package com.wypl.wyplcore.calendar;

import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.List;

import com.wypl.jpacalendardomain.calendar.data.RepetitionCycle;
import com.wypl.jpacalendardomain.calendar.domain.Schedule;
import com.wypl.wyplcore.schedule.data.response.ScheduleFindResponse;

public class CalendarServiceUtil {

public static List<ScheduleFindResponse> getScheduleResponses(Schedule schedule, LocalDate startDate, LocalDate endDate) {
if (!schedule.isRepetition()) return List.of(ScheduleFindResponse.of(schedule, schedule.getStartDateTime(), schedule.getEndDateTime()));
jiwon83 marked this conversation as resolved.
Show resolved Hide resolved

if(schedule.getRepetitionCycle().equals(RepetitionCycle.DAY)) {
return getDayRepetitionSchedules(schedule, startDate, endDate);
}
if(schedule.getRepetitionCycle().equals(RepetitionCycle.WEEK)) {
return getWeekRepetitionSchedules(schedule, startDate, endDate);
}
if(schedule.getRepetitionCycle().equals(RepetitionCycle.MONTH)) {
return getMonthRepetitionSchedules(schedule, startDate, endDate);
}
if(schedule.getRepetitionCycle().equals(RepetitionCycle.YEAR)) {
// Todo: return getYearRepetitionSchedule(schedule, startDate);
}
throw new IllegalArgumentException("Invalid RepetitionCycle");
}

private static List<ScheduleFindResponse> getDayRepetitionSchedules(Schedule schedule, LocalDate searchStartDate, LocalDate searchEndDate) {

List<ScheduleFindResponse> responses = new ArrayList<>();
searchStartDate = getMaxDate(searchStartDate, schedule.getRepetitionStartDate());
searchEndDate = getMinDate(searchEndDate, schedule.getRepetitionEndDate());

for( ; !searchStartDate.isAfter(searchEndDate); searchStartDate = searchStartDate.plusDays(1)) {
LocalDateTime startDateTime = LocalDateTime.of(searchStartDate, schedule.getStartDateTime().toLocalTime());
LocalDateTime endDateTime = LocalDateTime.of(searchStartDate, schedule.getEndDateTime().toLocalTime());
responses.add(ScheduleFindResponse.of(schedule, startDateTime, endDateTime));
}
jiwon83 marked this conversation as resolved.
Show resolved Hide resolved
return responses;
}

private static List<ScheduleFindResponse> getWeekRepetitionSchedules(Schedule schedule, LocalDate searchStartDate, LocalDate searchEndDate) {

List<ScheduleFindResponse> responses = new ArrayList<>();

// 검색할 시작일자와 끝일자 설정
searchEndDate = getMinDate(searchEndDate, schedule.getRepetitionEndDate());
searchStartDate = getMaxDate(searchStartDate, schedule.getRepetitionStartDate());

// 반복 주에 포함되도록 가공
searchStartDate = getNearestDateUsingWeekInterval(searchStartDate, schedule.getWeekInterval(), schedule);

if(!schedule.existsDayOfWeek()){ // 반복 요일을 설정하지 않았을 경우

searchStartDate = resetForBeforeStarted(searchStartDate, schedule);
Duration duration = Duration.between(schedule.getStartDateTime(), schedule.getEndDateTime());

// 설정한 weekInterval 만큼씩 증가하면서 endDate까지 반복
for( LocalDate date = searchStartDate; !date.isAfter(searchEndDate); date = date.plusWeeks(schedule.getWeekInterval())) {
LocalDateTime startDateTime = LocalDateTime.of(date, schedule.getStartDateTime().toLocalTime());
LocalDateTime endDateTime = startDateTime.plus(duration);
responses.add(ScheduleFindResponse.of(schedule, startDateTime, endDateTime));
}
return responses;
}
// 반복 요일을 설정했을 경우
int repetitionDayOfWeek = schedule.getDayOfWeek();

for (int dayOfWeek = 1; dayOfWeek <= 7; dayOfWeek++) {
if( isSelectedDayOfWeek(repetitionDayOfWeek, dayOfWeek) ) {

// Find nearest day by dayOfWeek and increase weekInterval
LocalDate date = getMaxDate(searchStartDate, schedule.getRepetitionStartDate()).with(TemporalAdjusters.nextOrSame(DayOfWeek.of(dayOfWeek)));
jiwon83 marked this conversation as resolved.
Show resolved Hide resolved

for(; !date.isAfter(searchEndDate); date = date.plusWeeks(schedule.getWeekInterval())) {
responses.add(ScheduleFindResponse
.of(schedule, LocalDateTime.of(date, schedule.getStartDateTime().toLocalTime()), LocalDateTime.of(date, schedule.getEndDateTime().toLocalTime())));
}
}
}
return responses;
}

/**
* 검색 조건에는 포함되지만, 검색이 시작된 날짜보다 이전에 시작한 Schedule 반영
* @param searchStartDate
* @param schedule
* @return searchStartDate
* Todo: 일정이 일주일 이상 지속될 경우에는 처리 불가, 생각해 보기
*/
private static LocalDate resetForBeforeStarted(LocalDate searchStartDate, Schedule schedule) {

// 끝나는 요일 찾기
DayOfWeek endDayOfWeek = schedule.getEndDateTime().getDayOfWeek();
Duration duration = Duration.between(schedule.getStartDateTime(), schedule.getEndDateTime());

// 탐색을 시작할 일자 설정
LocalDate endDateForSearch = searchStartDate.with(TemporalAdjusters.nextOrSame(endDayOfWeek));
LocalDateTime endDateTimeForSearch = LocalDateTime.of(endDateForSearch, schedule.getEndDateTime().toLocalTime()); // 탐색을 시작할 끝 일시 설정
LocalDateTime startDateTimeForSearch = endDateTimeForSearch.minus(duration); // 탐색을 시작할 시작 일시 설정

return startDateTimeForSearch.toLocalDate();
}

/**
* 검색 시작일자에 WeekInterval을 적용
* @param searchStartDate
* @param weekInterval
* @param schedule
* @return searchStartDate
*/
private static LocalDate getNearestDateUsingWeekInterval(LocalDate searchStartDate, Integer weekInterval, Schedule schedule){

// searchStartDate를 해당 주의 월요일로 설정
LocalDateTime searchStartMonday = LocalDateTime.of(searchStartDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)), LocalTime.of(0, 0));
LocalDateTime scheduleStartMonday = LocalDateTime.of(schedule.getRepetitionStartDate().with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)), LocalTime.of(0, 0));

int gapOfWeek = (int) Duration.between(scheduleStartMonday, searchStartMonday).toDays() / 7;

if (gapOfWeek % weekInterval == 0) return searchStartDate;

int addWeek = (gapOfWeek / weekInterval + 1) * weekInterval - gapOfWeek;

return searchStartDate.plusWeeks(addWeek);
}

private static List<ScheduleFindResponse> getMonthRepetitionSchedules(Schedule schedule, LocalDate startDate, LocalDate endDate) {
List<ScheduleFindResponse> responses = new ArrayList<>();

// 끝나는 날짜
int endDayOfMonth = schedule.getEndDateTime().getDayOfMonth();

// 탐색할 시작 일시와 끝 일시 설정
LocalDateTime searchEndDateTime = LocalDateTime.of(getMaxDate(startDate, schedule.getRepetitionStartDate()).withDayOfMonth(endDayOfMonth), schedule.getEndDateTime().toLocalTime());
Duration duration = Duration.between(schedule.getStartDateTime(), schedule.getEndDateTime());
LocalDateTime searchStartDateTime = searchEndDateTime.minus(duration);

for ( LocalDate date = searchStartDateTime.toLocalDate(); !date.isAfter(getMinDate(endDate, schedule.getRepetitionEndDate())); date = date.plusMonths(1)) {
LocalDateTime startDateTime = LocalDateTime.of(date, schedule.getStartDateTime().toLocalTime());
LocalDateTime endDateTime = startDateTime.plus(duration);
responses.add(ScheduleFindResponse.of(schedule, startDateTime, endDateTime));
}
return responses;
}

private static List<ScheduleFindResponse> getYearRepetitionSchedule(Schedule schedule, LocalDate startDate, LocalDate endDate) {
List<ScheduleFindResponse> responses = new ArrayList<>();

startDate = getMaxDate(startDate, schedule.getRepetitionStartDate());
endDate = getMinDate(endDate, schedule.getRepetitionEndDate());

// startDate와 같거나 가장 가까운 schedule.endDateTime의 날짜, 시간을 가진 LocalDateTime 생성
LocalDateTime nearestEndDateTime = LocalDateTime.of(startDate.withDayOfYear(schedule.getEndDateTime().getDayOfYear()), schedule.getEndDateTime().toLocalTime());
LocalDateTime nearestStartDateTime = nearestEndDateTime.minus(Duration.between(schedule.getStartDateTime(), schedule.getEndDateTime()));

for(LocalDate date = nearestStartDateTime.toLocalDate(); !date.isAfter(endDate); date = date.plusYears(1)) {
LocalDateTime startDateTime = LocalDateTime.of(date, schedule.getStartDateTime().toLocalTime());
LocalDateTime endDateTime = startDateTime.plus(Duration.between(schedule.getStartDateTime(), schedule.getEndDateTime()));
responses.add(ScheduleFindResponse.of(schedule, startDateTime, endDateTime));
}

return responses;
}

/**
* repetitionDayOfWeek에 해당하는 요일이 선택되었는지 확인
* @param repetitionDayOfWeek of Schedule
* @param dayOfWeek (1: 월요일, 2: 화요일, 3: 수요일, 4: 목요일, 5: 금요일, 6: 토요일, 7: 일요일)
* @return boolean
*/
static boolean isSelectedDayOfWeek(int repetitionDayOfWeek, int dayOfWeek) {
return (repetitionDayOfWeek & (1 << dayOfWeek)) != 0;
}

static LocalDate getMaxDate(LocalDate date1, LocalDate date2) {
return date1.isBefore(date2) ? date2 : date1;
}

static LocalDate getMinDate(LocalDate date1, LocalDate date2) {
return date1.isBefore(date2) ? date1 : date2;
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.wypl.wyplcore.calendar.data.response;

import com.wypl.wyplcore.schedule.data.response.ScheduleFindResponse;

import java.util.List;

public record FindCalendarResponse(

CalendarFindResponse calender,

List<ScheduleFindResponse> schedules
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.wypl.wyplcore.calendar.service;

import com.wypl.googleoauthclient.domain.AuthMember;
import com.wypl.jpacalendardomain.calendar.domain.Calendar;
import com.wypl.jpacalendardomain.calendar.domain.MemberCalendar;
import com.wypl.jpacalendardomain.calendar.repository.ScheduleRepository;
import com.wypl.jpamemberdomain.member.domain.Member;
import com.wypl.wyplcore.calendar.data.response.CalendarFindResponse;
import com.wypl.wyplcore.calendar.data.response.FindCalendarResponse;
import com.wypl.wyplcore.calendar.service.strategy.CalendarStrategy;
import com.wypl.wyplcore.schedule.data.CalendarType;
import com.wypl.wyplcore.calendar.data.request.CalendarFindRequest;
import com.wypl.wyplcore.schedule.data.response.ScheduleFindResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.List;
import java.util.Map;

@Service
@RequiredArgsConstructor
public class CalendarService {

private final ScheduleRepository scheduleRepository;

private final Map<CalendarType, CalendarStrategy> calendarStrategyMap;

/**
* 회원의 캘린더를 조회한다.
* @param authMember
* @param calendarId
* @param calendarFindRequest
* @return FindCalendarResponse
*/
@Transactional
jiwon83 marked this conversation as resolved.
Show resolved Hide resolved
public FindCalendarResponse findCalendar(AuthMember authMember, long calendarId, CalendarFindRequest calendarFindRequest) {

Calendar foundCalendar = null; // FIXME: calendarId로 foundCalendar 엔티티 검증 필요.
MemberCalendar foundMemberCalendar = null; // FIXME: memberCalendar 엔티티 검증 필요.
Member foundMember = null; // FIXME: member 엔티티 검증 필요.

CalendarType calendarType = calendarFindRequest.calendarType();
LocalDate startDate = calendarFindRequest.startDate();

CalendarStrategy calendarStrategy = calendarStrategyMap.get(calendarType);
List<ScheduleFindResponse> foundScheduleFindResponses = calendarStrategy.getAllSchedule(scheduleRepository, foundCalendar.getId(), startDate);

CalendarFindResponse calendarFindResponse = new CalendarFindResponse(foundCalendar.getId(), foundMemberCalendar.getColor(), foundCalendar.getName());
return new FindCalendarResponse(calendarFindResponse, foundScheduleFindResponses);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.wypl.wyplcore.calendar.service.strategy;


import com.wypl.jpacalendardomain.calendar.repository.ScheduleRepository;
import com.wypl.wyplcore.schedule.data.CalendarType;
import com.wypl.wyplcore.schedule.data.response.ScheduleFindResponse;

import java.time.LocalDate;
import java.util.List;

public interface CalendarStrategy {

CalendarType getCalendarType();

List<ScheduleFindResponse> getAllSchedule(ScheduleRepository repository, long calendarId, LocalDate startDate);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.wypl.wyplcore.calendar.service.strategy;

import com.wypl.wyplcore.schedule.data.CalendarType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Configuration
public class CalendarStrategyConfig {

private final Map<CalendarType, CalendarStrategy> calendarStrategyMap = new HashMap<>();

@Autowired
public CalendarStrategyConfig(List<CalendarStrategy> calendarStrategies) {
calendarStrategies.forEach(calendarStrategy -> {
calendarStrategyMap.put(calendarStrategy.getCalendarType(), calendarStrategy);
});
}

@Bean
public Map<CalendarType, CalendarStrategy> calendarStrategyMap() {
return calendarStrategyMap;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.wypl.wyplcore.calendar.service.strategy;


import com.wypl.jpacalendardomain.calendar.domain.Schedule;
import com.wypl.jpacalendardomain.calendar.repository.ScheduleRepository;
import com.wypl.wyplcore.schedule.data.CalendarType;
import com.wypl.wyplcore.schedule.data.response.ScheduleFindResponse;
import com.wypl.wyplcore.schedule.service.repetition.RepetitionService;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

@Component
@RequiredArgsConstructor
public class DayCalendarStrategy implements CalendarStrategy {

@Override
public CalendarType getCalendarType() {
return CalendarType.DAY;
}

/**
* Day 달력의 전체 일정을 조회한다.
* @param calendarId
* @param startDate
* @return List<ScheduleFindResponse>
*/
@Override
public List<ScheduleFindResponse> getAllSchedule(ScheduleRepository repository, long calendarId, LocalDate startDate) {

List<Schedule> schedules = repository.findByCalendarIdAndBetweenStartDateAndEndDate(calendarId, startDate, startDate);

List<ScheduleFindResponse> scheduleFindResponses = new ArrayList<>();

for (Schedule schedule : schedules) {
RepetitionService.getScheduleResponses(schedule, startDate, startDate);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런 것도 추후 stream으로 간단하게 처리할 수 있는데 추후 한번 도전해보시면 좋을 것 같아요!

return scheduleFindResponses;
}

}
Loading
Loading