-
Notifications
You must be signed in to change notification settings - Fork 0
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] 약속 가능 시간 조회 API 구현 #65
Changes from 37 commits
d5a8326
dea5238
cd124c6
6cf5a94
e5d8d2e
4eb719e
765fa86
328157b
28db663
b9e592c
dbae3b6
9c05735
64751c6
2182261
11735d7
181ef2c
6f458a0
28c97c1
d30016b
c97f528
6f03f76
c51a751
dcaa6bf
97afb08
31dba7b
4eb1f36
42448b1
ebaab4c
5714545
1bb0b7a
91b6383
95083bb
070e82c
b1ad362
166f376
cba40d3
a50c873
2bfc91c
abf8364
ee8f75b
2421452
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package org.noostak.appointment.domain; | ||
|
||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
import java.util.List; | ||
|
||
public interface AppointmentHostSelectionTimeRepository extends JpaRepository<AppointmentHostSelectionTime, Long> { | ||
List<AppointmentHostSelectionTime> findByAppointmentId(Long appointmentId); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,14 @@ | ||
package org.noostak.appointment.dto.request; | ||
|
||
import org.noostak.appointment.domain.AppointmentHostSelectionTimes; | ||
|
||
import java.util.List; | ||
|
||
public record AppointmentCreateRequest( | ||
String appointmentName, | ||
String category, | ||
Long duration, | ||
List<AppointmentHostSelectionTimes> appointmentHostSelectionTimes | ||
List<AppointmentHostSelectionTimeRequest> appointmentHostSelectionTimes | ||
) { | ||
public static AppointmentCreateRequest of(String appointmentName, String category, Long duration, List<AppointmentHostSelectionTimes> appointmentHostSelectionTimes) { | ||
public static AppointmentCreateRequest of(String appointmentName, String category, Long duration, List<AppointmentHostSelectionTimeRequest> appointmentHostSelectionTimes) { | ||
return new AppointmentCreateRequest(appointmentName, category, duration, appointmentHostSelectionTimes); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package org.noostak.appointment.dto.request; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
public record AppointmentHostSelectionTimeRequest( | ||
LocalDateTime date, | ||
LocalDateTime startTime, | ||
LocalDateTime endTime | ||
) { | ||
public static AppointmentHostSelectionTimeRequest of(LocalDateTime date, LocalDateTime startTime, LocalDateTime endTime) { | ||
return new AppointmentHostSelectionTimeRequest(date, startTime, endTime); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,44 @@ | ||
package org.noostak.appointmentmember.api; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.noostak.appointmentmember.application.AppointmentSaveAvailableTimesService; | ||
import org.noostak.appointmentmember.dto.request.AvailableTimesRequest; | ||
import org.noostak.appointmentmember.application.AppointmentMemberRetrieveAvailableTimesService; | ||
import org.noostak.appointmentmember.application.AppointmentMemberSaveAvailableTimesService; | ||
import org.noostak.appointmentmember.dto.request.AppointmentMemberAvailableTimesRequest; | ||
import org.noostak.appointmentmember.dto.response.AppointmentMembersAvailableTimesResponse; | ||
import org.noostak.global.success.SuccessResponse; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
import static org.noostak.appointmentmember.common.success.AppointmentMemberSuccessCode.APPOINTMENT_MEMBER_AVAILABLE_TIMES_RETRIEVED; | ||
import static org.noostak.appointmentmember.common.success.AppointmentMemberSuccessCode.SUCCESS_SAVE_AVAILABLE_TIMES; | ||
|
||
@RestController | ||
@RequestMapping("/api/v1/appointment-members") | ||
@RequiredArgsConstructor | ||
public class AppointmentMemberController { | ||
|
||
private final AppointmentSaveAvailableTimesService appointmentSaveAvailableTimesService; | ||
private final AppointmentMemberSaveAvailableTimesService appointmentMemberSaveAvailableTimesService; | ||
private final AppointmentMemberRetrieveAvailableTimesService appointmentMemberRetrieveAvailableTimesService; | ||
|
||
|
||
@PostMapping("/{appointmentId}/timetable") | ||
public ResponseEntity<SuccessResponse> saveAvailableTimes( | ||
// @AuthenticationPrincipal Long memberId | ||
@PathVariable(name = "appointmentId") Long appointmentId, | ||
@RequestBody AvailableTimesRequest request | ||
@RequestBody AppointmentMemberAvailableTimesRequest request | ||
) { | ||
Long memberId = 2L; | ||
appointmentSaveAvailableTimesService.saveAvailableTimes(memberId, appointmentId, request); | ||
appointmentMemberSaveAvailableTimesService.saveAvailableTimes(memberId, appointmentId, request); | ||
return ResponseEntity.ok(SuccessResponse.of(SUCCESS_SAVE_AVAILABLE_TIMES)); | ||
} | ||
|
||
@GetMapping("/{appointmentId}/timetable") | ||
public ResponseEntity<SuccessResponse<AppointmentMembersAvailableTimesResponse>> getAvailableTimes( | ||
// @AuthenticationPrincipal Long memberId | ||
@PathVariable(name = "appointmentId") Long appointmentId | ||
) { | ||
Long memberId = 3L; | ||
AppointmentMembersAvailableTimesResponse response = appointmentMemberRetrieveAvailableTimesService.getAvailableTimes(memberId, appointmentId); | ||
return ResponseEntity.ok(SuccessResponse.of(APPOINTMENT_MEMBER_AVAILABLE_TIMES_RETRIEVED, response)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package org.noostak.appointmentmember.application; | ||
|
||
import org.noostak.appointmentmember.dto.response.AppointmentMembersAvailableTimesResponse; | ||
|
||
public interface AppointmentMemberRetrieveAvailableTimesService { | ||
AppointmentMembersAvailableTimesResponse getAvailableTimes(Long memberId, Long appointmentId); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package org.noostak.appointmentmember.application; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.noostak.appointment.domain.AppointmentHostSelectionTimeRepository; | ||
import org.noostak.appointmentmember.common.exception.AppointmentMemberErrorCode; | ||
import org.noostak.appointmentmember.common.exception.AppointmentMemberException; | ||
import org.noostak.appointmentmember.domain.AppointmentMember; | ||
import org.noostak.appointmentmember.domain.AppointmentMemberAvailableTimesRepository; | ||
import org.noostak.appointmentmember.domain.AppointmentMemberRepository; | ||
import org.noostak.appointmentmember.dto.response.*; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
@Transactional(readOnly = true) | ||
public class AppointmentMemberRetrieveAvailableTimesServiceImpl implements AppointmentMemberRetrieveAvailableTimesService { | ||
|
||
private final AppointmentHostSelectionTimeRepository appointmentHostSelectionTimeRepository; | ||
private final AppointmentMemberAvailableTimesRepository appointmentMemberAvailableTimesRepository; | ||
private final AppointmentMemberRepository appointmentMemberRepository; | ||
|
||
@Override | ||
public AppointmentMembersAvailableTimesResponse getAvailableTimes(Long memberId, Long appointmentId) { | ||
AppointmentMember appointmentMember = findAppointmentMember(memberId, appointmentId); | ||
|
||
boolean appointmentTimeSet = appointmentMember.isAppointmentTimeSet(); | ||
|
||
AppointmentHostSelectionTimesResponse hostSelectionTimesResponse = findHostSelectionTimes(appointmentId); | ||
|
||
List<AppointmentMemberInfoResponse> appointmentMembersInfo = findAppointmentMembersInfo(appointmentId); | ||
|
||
return createAppointmentResponse(appointmentTimeSet, hostSelectionTimesResponse, appointmentMembersInfo); | ||
} | ||
|
||
private AppointmentMember findAppointmentMember(Long memberId, Long appointmentId) { | ||
return appointmentMemberRepository.findByMemberIdAndAppointmentId(memberId, appointmentId) | ||
.orElseThrow(() -> new AppointmentMemberException(AppointmentMemberErrorCode.APPOINTMENT_MEMBER_NOT_FOUND)); | ||
} | ||
|
||
private AppointmentHostSelectionTimesResponse findHostSelectionTimes(Long appointmentId) { | ||
List<AppointmentHostSelectionTimeResponse> hostSelectionTimeDtos = | ||
appointmentHostSelectionTimeRepository.findByAppointmentId(appointmentId).stream() | ||
.map(time -> AppointmentHostSelectionTimeResponse.of( | ||
time.getDate(), | ||
time.getStartTime(), | ||
time.getEndTime() | ||
)) | ||
.collect(Collectors.toList()); | ||
|
||
return AppointmentHostSelectionTimesResponse.of(hostSelectionTimeDtos); | ||
} | ||
|
||
private List<AppointmentMemberInfoResponse> findAppointmentMembersInfo(Long appointmentId) { | ||
return appointmentMemberRepository.findAllWithAvailableTimes(appointmentId).stream() | ||
.map(member -> { | ||
List<AppointmentMemberAvailableTimeResponse> availableTimeDtos = findAvailableTimesForMember(member); | ||
return AppointmentMemberInfoResponse.of( | ||
member.getId(), | ||
member.getMember().getName().value(), | ||
AppointmentMemberAvailableTimesResponse.of(availableTimeDtos) | ||
); | ||
}) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
private List<AppointmentMemberAvailableTimeResponse> findAvailableTimesForMember(AppointmentMember member) { | ||
return appointmentMemberAvailableTimesRepository.findByAppointmentMember(member).stream() | ||
.map(time -> new AppointmentMemberAvailableTimeResponse( | ||
time.getDate(), | ||
time.getStartTime(), | ||
time.getEndTime() | ||
)) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
private AppointmentMembersAvailableTimesResponse createAppointmentResponse( | ||
boolean appointmentTimeSet, | ||
AppointmentHostSelectionTimesResponse hostSelectionTimesResponse, | ||
List<AppointmentMemberInfoResponse> appointmentMembersInfo) { | ||
|
||
AppointmentScheduleResponse appointmentScheduleResponse = AppointmentScheduleResponse.of( | ||
hostSelectionTimesResponse, | ||
appointmentMembersInfo | ||
); | ||
|
||
return AppointmentMembersAvailableTimesResponse.of(appointmentTimeSet, appointmentScheduleResponse); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package org.noostak.appointmentmember.application; | ||
|
||
import org.noostak.appointmentmember.dto.request.AppointmentMemberAvailableTimesRequest; | ||
|
||
public interface AppointmentMemberSaveAvailableTimesService { | ||
void saveAvailableTimes(Long memberId, Long appointmentId, AppointmentMemberAvailableTimesRequest request); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package org.noostak.appointmentmember.application; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.noostak.appointmentmember.common.exception.AppointmentMemberErrorCode; | ||
import org.noostak.appointmentmember.common.exception.AppointmentMemberException; | ||
import org.noostak.appointmentmember.domain.AppointmentMemberAvailableTime; | ||
import org.noostak.appointmentmember.domain.AppointmentMemberAvailableTimesRepository; | ||
import org.noostak.appointmentmember.dto.request.AppointmentMemberAvailableTimesRequest; | ||
import org.noostak.appointmentmember.domain.AppointmentMember; | ||
import org.noostak.appointmentmember.domain.AppointmentMemberRepository; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
@Transactional(readOnly = true) | ||
public class AppointmentMemberSaveAvailableTimesServiceImpl implements AppointmentMemberSaveAvailableTimesService { | ||
|
||
private final AppointmentMemberRepository appointmentMemberRepository; | ||
private final AppointmentMemberAvailableTimesRepository appointmentMemberAvailableTimesRepository; | ||
|
||
@Override | ||
@Transactional | ||
public void saveAvailableTimes(Long memberId, Long appointmentId, AppointmentMemberAvailableTimesRequest request) { | ||
AppointmentMember appointmentMember = findAppointmentMember(memberId, appointmentId); | ||
List<AppointmentMemberAvailableTime> newTimes = createNewAvailableTimes(appointmentMember, request); | ||
|
||
refreshAvailableTimes(appointmentMember, newTimes); | ||
} | ||
|
||
private AppointmentMember findAppointmentMember(Long memberId, Long appointmentId) { | ||
return appointmentMemberRepository.findByMemberIdAndAppointmentId(memberId, appointmentId) | ||
.orElseThrow(() -> new AppointmentMemberException(AppointmentMemberErrorCode.APPOINTMENT_MEMBER_NOT_FOUND)); | ||
} | ||
|
||
private List<AppointmentMemberAvailableTime> createNewAvailableTimes(AppointmentMember appointmentMember, AppointmentMemberAvailableTimesRequest request) { | ||
return request.appointmentMemberAvailableTimes().stream() | ||
.map(time -> AppointmentMemberAvailableTime.of(appointmentMember, time.date(), time.startTime(), time.endTime())) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
private void refreshAvailableTimes(AppointmentMember appointmentMember, List<AppointmentMemberAvailableTime> newTimes) { | ||
if (!isTimeUpdateRequired(appointmentMember, newTimes)) { | ||
return; | ||
} | ||
|
||
updateAvailableTimes(appointmentMember, newTimes); | ||
markAppointmentTimeIfNecessary(appointmentMember, newTimes); | ||
} | ||
|
||
private boolean isTimeUpdateRequired(AppointmentMember appointmentMember, List<AppointmentMemberAvailableTime> newTimes) { | ||
List<AppointmentMemberAvailableTime> existingTimes = appointmentMemberAvailableTimesRepository.findByAppointmentMember(appointmentMember); | ||
return !Set.copyOf(existingTimes).equals(Set.copyOf(newTimes)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 피드백 감사합니다! 네, copyOf를 사용하면 객체의 주소값을 기준으로 비교가 이루어지므로, 이를 고려하여 AppointmentMemberAvailableTime 클래스에 @EqualsAndHashCode 어노테이션을 추가했습니다. 해당 어노테이션을 통해 값 자체를 기준으로 비교하도록 설정하여, 기존 데이터와 새로운 데이터의 동등성 판단이 정확하게 이루어지도록 했습니다. 😊 |
||
} | ||
|
||
private void updateAvailableTimes(AppointmentMember appointmentMember, List<AppointmentMemberAvailableTime> newTimes) { | ||
appointmentMemberAvailableTimesRepository.deleteByAppointmentMember(appointmentMember); | ||
appointmentMemberAvailableTimesRepository.saveAll(newTimes); | ||
} | ||
|
||
private void markAppointmentTimeIfNecessary(AppointmentMember appointmentMember, List<AppointmentMemberAvailableTime> newTimes) { | ||
appointmentMember.updateAvailableTimes(newTimes); | ||
} | ||
} |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
검토 요청 사항이 이 부분인 듯 한데, findBy를 통해 찾은 List를 DTO 변환하는 메소드는..
findHostSelectionTimes
보다fetchHostSelectionTimes
를 사용하는게 적절해 보입니다 🙂로직 자체는 문제 없습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
피드백 감사합니다!
저도 find보다는 fetch가 이 상황에 더 적절한 네이밍이라는 의견에 공감합니다. 🙂
해당 부분 반영하겠습니다!