💡 .java를 클릭시 관련 커밋으로 이동💡
스터디원의 Todo 완료여부를 조회하려면 일단 해당 스터디의 스터디원들을 조회해야하고 각각의 팀원들이 Todo에 대해 완료했는지 확인해볼 수 있어야했다.
StudyTodoMapping 테이블과 StudyMember 테이블은 직접적인 연관관계 설정이 되어있지 않았다.
그럼에도 조회가 가능한 방법이있다 🙀 ✨
엔티티 간의 직접적인 연관관계가 없다고 해도 조회가 가능한 간접적 연관관계 ORM(Object-Relational Mapping)의 개념을 통해서 구현했다.
// 스터디 active 멤버들 찾기
List<StudyMember> activeMembers = studyMemberRepository.findActiveMembersByStudyInfoId(studyInfoId);
// active 멤버들의 userId 추출
List<Long> userIds = extractUserIds(activeMembers);
// active 멤버들에 대한 특정 Todo의 완료 상태를 조회
List<StudyTodoMapping> todoMappings = studyTodoMappingRepository.findByTodoIdAndUserIds(todoId, userIds);
// 조회된 정보를 바탕으로 응답 객체를 생성
return todoMappings.stream()
.map(mapping -> new StudyTodoStatusResponse(mapping.getUserId(), mapping.getStatus()))
.collect(Collectors.toList());
두 테이블 간에 직접적인 ORM 매핑이 정의되어 있지 않더라도 공통 필드(userId)를 사용하여 두 도메인 객체의 관련 데이터를 연결하고, 이를 기반으로 비즈니스 로직을 수행할 수 있다!!!
StudyTodoMappingRepositoryCustom.java
public interface StudyTodoMappingRepositoryCustom {
// 스터디 멤버들의 userId와 todoId로 해당 StudyTodoMapping 객체를 조회한다.
List<StudyTodoMapping> findByTodoIdAndUserIds(Long todoId, List<Long> userIds);
}
StudyTodoMappingRepositoryImpl.java
@Override
public List<StudyTodoMapping> findByTodoIdAndUserIds(Long todoId, List<Long> userIds) {
return queryFactory.selectFrom(studyTodoMapping)
.where(studyTodoMapping.todoId.eq(todoId)
.and(studyTodoMapping.userId.in(userIds)))
.fetch();
}
스터디 멤버들의 userId와 todoId로 해당 StudyTodoMapping 객체를 조회하도록 작성한 QueryDsl 이다.
// 스터디원들의 Todo 완료여부 조회
@ApiResponse(responseCode = "200", description = "Todo 완료조회 성공", content = @Content(schema = @Schema(implementation = StudyTodoStatusResponse.class)))
@GetMapping("/{studyInfoId}/todo/{todoId}")
public JsonResult<?> readStudyTodoStatus(@AuthenticationPrincipal User user,
@PathVariable(name = "studyInfoId") Long studyInfoId,
@PathVariable(name = "todoId") Long todoId) {
// 스터디 멤버인지 검증
studyMemberService.isValidateStudyMember(user, studyInfoId);
return JsonResult.successOf(studyTodoService.readStudyTodoStatus(studyInfoId, todoId));
}
Todo 완료 여부 조회를 위한 studyInfoId와 해당 todoId를 @PathVariable 어노테이션을 통해 받고있다.
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class StudyTodoStatusResponse {
private Long userId; // 스터디멤버 Id
private StudyTodoStatus status; // to do 진행 상황
}
조회를 위한 DTO response 부분이다.
// 스터디원들의 Todo 완료여부 조회
public List<StudyTodoStatusResponse> readStudyTodoStatus(Long studyInfoId, Long todoId) {
// 스터디와 관련된 To do 예외처리
studyTodoRepository.findByIdAndStudyInfoId(todoId, studyInfoId).orElseThrow(() -> {
log.warn(">>>> {} : {} <<<<", todoId, ExceptionMessage.TODO_NOT_FOUND);
return new TodoException(ExceptionMessage.TODO_NOT_FOUND);
});
// 스터디 active 멤버들 찾기
List<StudyMember> activeMembers = studyMemberRepository.findActiveMembersByStudyInfoId(studyInfoId);
// active 멤버들의 userId 추출
List<Long> userIds = extractUserIds(activeMembers);
// active 멤버들에 대한 특정 Todo의 완료 상태를 조회
List<StudyTodoMapping> todoMappings = studyTodoMappingRepository.findByTodoIdAndUserIds(todoId, userIds);
// 조회된 정보를 바탕으로 응답 객체를 생성
return todoMappings.stream()
.map(mapping -> new StudyTodoStatusResponse(mapping.getUserId(), mapping.getStatus()))
.collect(Collectors.toList());
}
// 멤버들의 userId만 추출
private List<Long> extractUserIds(List<StudyMember> activeMembers) {
return activeMembers.stream()
.map(StudyMember::getUserId)
.toList();
}
우선 스터디가 존재하는지 예외처리를 해주고 findActiveMembersByStudyInfoId 메서드를 통해 해당 스터디의 활동중인 멤버만 따로 찾아주고 있다.
테스트
// 테스트용 studyTodoMapping 생성
public static StudyTodoMapping createStudyTodoDefaultMapping(Long userId) {
return StudyTodoMapping.builder()
.userId(userId)
.status(expectedStatus)
.build();
}
테스트를 위한 studyTodoMapping 생성 Fixture이다.
@Test
public void Todo_스터디원들의_완료여부_조회() throws Exception {
// given
User savedUser = userRepository.save(generateAuthUser());
Map<String, String> map = TokenUtil.createTokenMap(savedUser);
String accessToken = jwtService.generateAccessToken(map, savedUser);
String refreshToken = jwtService.generateRefreshToken(map, savedUser);
StudyInfo studyInfo = StudyInfoFixture.createDefaultPublicStudyInfo(savedUser.getId());
studyInfoRepository.save(studyInfo);
List<StudyTodoStatusResponse> response = Arrays.asList(
StudyTodoStatusResponse.builder()
.userId(1L)
.status(StudyTodoStatus.TODO_COMPLETE)
.build(),
StudyTodoStatusResponse.builder()
.userId(2L)
.status(StudyTodoStatus.TODO_INCOMPLETE)
.build()
);
when(studyMemberService.isValidateStudyMember(any(User.class), any(Long.class)))
.thenReturn(UserInfoResponse.of(savedUser));
when(studyTodoService.readStudyTodoStatus(any(Long.class), any(Long.class))).thenReturn(response);
// when
mockMvc.perform(get("/study/" + studyInfo.getId() + "/todo/" + 1L)
.contentType(MediaType.APPLICATION_JSON)
.header(AUTHORIZATION, createAuthorizationHeader(accessToken, refreshToken)))
// then
.andExpect(status().isOk())
.andExpect(jsonPath("$.res_code").value(200))
.andExpect(jsonPath("$.res_msg").value("OK"))
.andExpect(jsonPath("$.res_obj").isNotEmpty())
.andDo(print());
}
완료 여부 조회 컨트롤러 동작 테스트
@Test
@DisplayName("스터디원들의 특정 Todo에 대한 완료여부 조회 테스트")
void readStudyTodo_status() {
// given
User leader = userRepository.save(generateAuthUser());
User member1 = userRepository.save(generateKaKaoUser());
User member2 = userRepository.save(generateGoogleUser());
StudyInfo studyInfo = StudyInfoFixture.createDefaultPublicStudyInfo(leader.getId());
studyInfoRepository.save(studyInfo);
// 스터디장 To do 생성
StudyTodo studyTodo = StudyTodoFixture.createStudyTodo(studyInfo.getId());
studyTodoRepository.save(studyTodo);
StudyMember koo = StudyMemberFixture.createDefaultStudyMember(member1.getId(), studyInfo.getId());
StudyMember Lee = StudyMemberFixture.createDefaultStudyMember(member2.getId(), studyInfo.getId());
studyMemberRepository.saveAll(List.of(koo, Lee));
StudyTodoMapping studyTodoMapping1 = StudyTodoFixture.createStudyTodoMapping(studyTodo.getId(), koo.getUserId());
StudyTodoMapping studyTodoMapping2 = StudyTodoFixture.createCompleteStudyTodoMapping(studyTodo.getId(), Lee.getUserId());
studyTodoMappingRepository.saveAll(List.of(studyTodoMapping1, studyTodoMapping2));
// when
List<StudyTodoStatusResponse> results = studyTodoService.readStudyTodoStatus(studyInfo.getId(), studyTodo.getId());
// then
assertEquals(2, results.size());
assertTrue(results.stream().anyMatch(r -> r.getUserId().equals(koo.getUserId()) && r.getStatus() == StudyTodoStatus.TODO_INCOMPLETE));
assertTrue(results.stream().anyMatch(r -> r.getUserId().equals(Lee.getUserId()) && r.getStatus() == StudyTodoStatus.TODO_COMPLETE));
}
활동중인 멤버의 userId가 잘 추출되는지 + 추출된 멤버들의 todo 완료 여부 상태를 조회가능한지 테스트한다.
'Project > 깃터디 (gitudy)' 카테고리의 다른 글
[깃터디/Member] 스터디 가입 취소 api 구현 (0) | 2024.05.11 |
---|---|
[깃터디/Member] 스터디 가입 신청 api 구현 (0) | 2024.05.11 |
[깃터디/Todo] Todo 삭제 api 구현 (0) | 2024.05.11 |
[깃터디/Todo] Todo 수정 api 구현 (0) | 2024.05.11 |
[깃터디/Todo] Todo 조회 api 구현 (0) | 2024.05.11 |
댓글