Skip to content

Commit

Permalink
feat: 수강생 명단 페이지 대시보드 조회 API 추가 (#796)
Browse files Browse the repository at this point in the history
* feat: 수강생 명단 조회를 위한 레포지토리 메서드 추가

* refactor: 조회 쿼리가 한번만 나가도록 수정

* feat: curriculum 휴강 여부 전달 메서드 추가

* feat: 페이징 처리 추가

* feat: 휴강 enum 값 추가

* feat: 수강생 명단 조회 서비스 구현

* fix: 휴강일 경우 발생하는 NPE 제거

* refactor: static import 하도록 수정

* refactor: 중복 로직 메서드로 추출

* refactor: 과제 휴강시에도 정적 팩토리 메서드 사용하도록 수정

* fix: 0으로 나누는 일이 발생하지 않도록 수정

* fix: 페이지의 content를 가져와서 forEach를 돌리도록 수정

* refactor: errorcode static import 하도록 수정

* refactor: filter를 map으로 대체

* refactor: where의 조건 순서 최적화

* refactor: 삼항 연산자 메서드로 추출
  • Loading branch information
Sangwook02 authored Oct 8, 2024
1 parent 76d2a19 commit 98d4681
Show file tree
Hide file tree
Showing 18 changed files with 283 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import jakarta.validation.Valid;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

Expand All @@ -35,10 +37,10 @@ public ResponseEntity<List<StudyResponse>> getStudiesInCharge() {
return ResponseEntity.ok(response);
}

@Operation(summary = "스터디 수강생 명단 조회", description = "해당 스터디의 수강생 명단을 조회합니다")
@Operation(summary = "스터디 수강생 관리", description = "해당 스터디의 수강생을 관리합니다")
@GetMapping("/{studyId}/students")
public ResponseEntity<List<StudyStudentResponse>> getStudyStudents(@PathVariable Long studyId) {
List<StudyStudentResponse> response = mentorStudyService.getStudyStudents(studyId);
public ResponseEntity<Page<StudyStudentResponse>> getStudyStudents(@PathVariable Long studyId, Pageable pageable) {
Page<StudyStudentResponse> response = mentorStudyService.getStudyStudents(studyId, pageable);
return ResponseEntity.ok(response);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package com.gdschongik.gdsc.domain.study.application;

import static com.gdschongik.gdsc.global.exception.ErrorCode.STUDY_NOT_FOUND;
import static com.gdschongik.gdsc.global.exception.ErrorCode.*;
import static java.util.stream.Collectors.*;

import com.gdschongik.gdsc.domain.member.domain.Member;
import com.gdschongik.gdsc.domain.study.dao.AssignmentHistoryRepository;
import com.gdschongik.gdsc.domain.study.dao.AttendanceRepository;
import com.gdschongik.gdsc.domain.study.dao.StudyAchievementRepository;
import com.gdschongik.gdsc.domain.study.dao.StudyAnnouncementRepository;
import com.gdschongik.gdsc.domain.study.dao.StudyDetailRepository;
import com.gdschongik.gdsc.domain.study.dao.StudyHistoryRepository;
Expand All @@ -17,14 +21,19 @@
import com.gdschongik.gdsc.domain.study.dto.request.StudyUpdateRequest;
import com.gdschongik.gdsc.domain.study.dto.response.StudyResponse;
import com.gdschongik.gdsc.domain.study.dto.response.StudyStudentResponse;
import com.gdschongik.gdsc.domain.study.dto.response.StudyTodoResponse;
import com.gdschongik.gdsc.global.exception.CustomException;
import com.gdschongik.gdsc.global.exception.ErrorCode;
import com.gdschongik.gdsc.global.util.MemberUtil;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -34,12 +43,15 @@
public class MentorStudyService {

private final MemberUtil memberUtil;
private final StudyValidator studyValidator;
private final StudyDetailValidator studyDetailValidator;
private final StudyRepository studyRepository;
private final StudyAnnouncementRepository studyAnnouncementRepository;
private final StudyHistoryRepository studyHistoryRepository;
private final StudyValidator studyValidator;
private final StudyDetailRepository studyDetailRepository;
private final StudyDetailValidator studyDetailValidator;
private final StudyAchievementRepository studyAchievementRepository;
private final AttendanceRepository attendanceRepository;
private final AssignmentHistoryRepository assignmentHistoryRepository;

@Transactional(readOnly = true)
public List<StudyResponse> getStudiesInCharge() {
Expand All @@ -49,15 +61,67 @@ public List<StudyResponse> getStudiesInCharge() {
}

@Transactional(readOnly = true)
public List<StudyStudentResponse> getStudyStudents(Long studyId) {
public Page<StudyStudentResponse> getStudyStudents(Long studyId, Pageable pageable) {
Member currentMember = memberUtil.getCurrentMember();
Study study =
studyRepository.findById(studyId).orElseThrow(() -> new CustomException(ErrorCode.STUDY_NOT_FOUND));

Study study = studyRepository.findById(studyId).orElseThrow(() -> new CustomException(STUDY_NOT_FOUND));
studyValidator.validateStudyMentor(currentMember, study);
List<StudyHistory> studyHistories = studyHistoryRepository.findByStudyId(studyId);

return studyHistories.stream().map(StudyStudentResponse::from).toList();
List<StudyDetail> studyDetails = studyDetailRepository.findAllByStudyId(studyId);
Page<StudyHistory> studyHistories = studyHistoryRepository.findByStudyId(studyId, pageable);
List<Long> studentIds = studyHistories.getContent().stream()
.map(studyHistory -> studyHistory.getStudent().getId())
.toList();
List<StudyAchievement> studyAchievements =
studyAchievementRepository.findByStudyIdAndMemberIds(studyId, studentIds);
List<Attendance> attendances = attendanceRepository.findByStudyIdAndMemberIds(studyId, studentIds);
List<AssignmentHistory> assignmentHistories =
assignmentHistoryRepository.findByStudyIdAndMemberIds(studyId, studentIds);

// StudyAchievement, Attendance, AssignmentHistory에 대해 Member의 id를 key로 하는 Map 생성
Map<Long, List<StudyAchievement>> studyAchievementMap = studyAchievements.stream()
.collect(groupingBy(
studyAchievement -> studyAchievement.getStudent().getId()));
Map<Long, List<Attendance>> attendanceMap = attendances.stream()
.collect(groupingBy(attendance -> attendance.getStudent().getId()));
Map<Long, List<AssignmentHistory>> assignmentHistoryMap = assignmentHistories.stream()
.collect(groupingBy(
assignmentHistory -> assignmentHistory.getMember().getId()));

List<StudyStudentResponse> response = new ArrayList<>();
studyHistories.getContent().forEach(studyHistory -> {
List<StudyAchievement> currentStudyAchievements =
studyAchievementMap.getOrDefault(studyHistory.getStudent().getId(), new ArrayList<>());
List<Attendance> currentAttendances =
attendanceMap.getOrDefault(studyHistory.getStudent().getId(), new ArrayList<>());
List<AssignmentHistory> currentAssignmentHistories =
assignmentHistoryMap.getOrDefault(studyHistory.getStudent().getId(), new ArrayList<>());

List<StudyTodoResponse> studyTodos = new ArrayList<>();
studyDetails.forEach(studyDetail -> {
studyTodos.add(StudyTodoResponse.createAttendanceType(
studyDetail, LocalDate.now(), isAttended(currentAttendances, studyDetail)));
studyTodos.add(StudyTodoResponse.createAssignmentType(
studyDetail, getSubmittedAssignment(currentAssignmentHistories, studyDetail)));
});

response.add(StudyStudentResponse.of(studyHistory, currentStudyAchievements, studyTodos));
});

return new PageImpl<>(response, pageable, studyHistories.getTotalElements());
}

private boolean isAttended(List<Attendance> attendances, StudyDetail studyDetail) {
return attendances.stream()
.anyMatch(attendance -> attendance.getStudyDetail().getId().equals(studyDetail.getId()));
}

private AssignmentHistory getSubmittedAssignment(
List<AssignmentHistory> assignmentHistories, StudyDetail studyDetail) {
return assignmentHistories.stream()
.filter(assignmentHistory ->
assignmentHistory.getStudyDetail().getId().equals(studyDetail.getId()))
.findFirst()
.orElse(null);
}

@Transactional
Expand Down Expand Up @@ -109,12 +173,12 @@ public void updateStudy(Long studyId, StudyUpdateRequest request) {

List<StudyDetail> studyDetails = studyDetailRepository.findAllByStudyIdOrderByWeekAsc(studyId);
// StudyDetail ID를 추출하여 Set으로 저장
Set<Long> studyDetailIds = studyDetails.stream().map(StudyDetail::getId).collect(Collectors.toSet());
Set<Long> studyDetailIds = studyDetails.stream().map(StudyDetail::getId).collect(toSet());

// 요청된 StudyCurriculumCreateRequest의 StudyDetail ID를 추출하여 Set으로 저장
Set<Long> requestIds = request.studyCurriculums().stream()
.map(StudyCurriculumCreateRequest::studyDetailId)
.collect(Collectors.toSet());
.collect(toSet());

studyDetailValidator.validateUpdateStudyDetail(studyDetailIds, requestIds);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ public interface AssignmentHistoryCustomRepository {

List<AssignmentHistory> findAssignmentHistoriesByStudentAndStudyId(Member member, Long studyId);

List<AssignmentHistory> findByStudyIdAndMemberIds(Long studyId, List<Long> memberIds);

void deleteByStudyIdAndMemberId(Long studyId, Long memberId);
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
package com.gdschongik.gdsc.domain.study.dao;

import static com.gdschongik.gdsc.domain.study.domain.AssignmentSubmissionStatus.*;
import static com.gdschongik.gdsc.domain.study.domain.QAssignmentHistory.*;
import static com.gdschongik.gdsc.domain.study.domain.QStudyDetail.studyDetail;

import com.gdschongik.gdsc.domain.member.domain.Member;
import com.gdschongik.gdsc.domain.study.domain.AssignmentHistory;
import com.gdschongik.gdsc.domain.study.domain.Study;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class AssignmentHistoryCustomRepositoryImpl implements AssignmentHistoryCustomRepository {
public class AssignmentHistoryCustomRepositoryImpl
implements AssignmentHistoryCustomRepository, AssignmentHistoryQueryMethod {

private final JPAQueryFactory queryFactory;

Expand All @@ -28,18 +27,6 @@ public boolean existsSubmittedAssignmentByMemberAndStudy(Member member, Study st
return fetchOne != null;
}

private BooleanExpression eqMember(Member member) {
return member == null ? null : assignmentHistory.member.eq(member);
}

private BooleanExpression eqStudy(Study study) {
return study == null ? null : assignmentHistory.studyDetail.study.eq(study);
}

private BooleanExpression isSubmitted() {
return assignmentHistory.submissionStatus.in(FAILURE, SUCCESS);
}

@Override
public List<AssignmentHistory> findAssignmentHistoriesByStudentAndStudyId(Member currentMember, Long studyId) {
return queryFactory
Expand All @@ -50,10 +37,6 @@ public List<AssignmentHistory> findAssignmentHistoriesByStudentAndStudyId(Member
.fetch();
}

private BooleanExpression eqStudyId(Long studyId) {
return studyId != null ? studyDetail.study.id.eq(studyId) : null;
}

@Override
public void deleteByStudyIdAndMemberId(Long studyId, Long memberId) {
queryFactory
Expand All @@ -62,7 +45,13 @@ public void deleteByStudyIdAndMemberId(Long studyId, Long memberId) {
.execute();
}

private BooleanExpression eqMemberId(Long memberId) {
return memberId != null ? assignmentHistory.member.id.eq(memberId) : null;
@Override
public List<AssignmentHistory> findByStudyIdAndMemberIds(Long studyId, List<Long> memberIds) {
return queryFactory
.selectFrom(assignmentHistory)
.innerJoin(assignmentHistory.studyDetail, studyDetail)
.fetchJoin()
.where(assignmentHistory.member.id.in(memberIds), eqStudyId(studyId))
.fetch();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.gdschongik.gdsc.domain.study.dao;

import static com.gdschongik.gdsc.domain.study.domain.AssignmentSubmissionStatus.*;
import static com.gdschongik.gdsc.domain.study.domain.QAssignmentHistory.*;
import static com.gdschongik.gdsc.domain.study.domain.QStudyDetail.*;

import com.gdschongik.gdsc.domain.member.domain.Member;
import com.gdschongik.gdsc.domain.study.domain.Study;
import com.querydsl.core.types.dsl.BooleanExpression;

public interface AssignmentHistoryQueryMethod {
default BooleanExpression eqMember(Member member) {
return member == null ? null : assignmentHistory.member.eq(member);
}

default BooleanExpression eqStudy(Study study) {
return study == null ? null : assignmentHistory.studyDetail.study.eq(study);
}

default BooleanExpression isSubmitted() {
return assignmentHistory.submissionStatus.in(FAILURE, SUCCESS);
}

default BooleanExpression eqStudyId(Long studyId) {
return studyId != null ? studyDetail.study.id.eq(studyId) : null;
}

default BooleanExpression eqMemberId(Long memberId) {
return memberId != null ? assignmentHistory.member.id.eq(memberId) : null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@

public interface AssignmentHistoryRepository
extends JpaRepository<AssignmentHistory, Long>, AssignmentHistoryCustomRepository {
// todo: public 제거
public Optional<AssignmentHistory> findByMemberAndStudyDetail(Member member, StudyDetail studyDetail);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@
public interface AttendanceCustomRepository {
List<Attendance> findByMemberAndStudyId(Member member, Long studyId);

List<Attendance> findByStudyIdAndMemberIds(Long studyId, List<Long> memberIds);

void deleteByStudyIdAndMemberId(Long studyId, Long memberId);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.gdschongik.gdsc.domain.study.dao;

import static com.gdschongik.gdsc.domain.member.domain.QMember.member;
import static com.gdschongik.gdsc.domain.study.domain.QAttendance.attendance;
import static com.gdschongik.gdsc.domain.study.domain.QStudyDetail.studyDetail;

Expand All @@ -26,6 +25,16 @@ public List<Attendance> findByMemberAndStudyId(Member member, Long studyId) {
.fetch();
}

@Override
public List<Attendance> findByStudyIdAndMemberIds(Long studyId, List<Long> memberIds) {
return queryFactory
.selectFrom(attendance)
.innerJoin(attendance.studyDetail, studyDetail)
.fetchJoin()
.where(attendance.student.id.in(memberIds), eqStudyId(studyId))
.fetch();
}

private BooleanExpression eqMemberId(Long memberId) {
return memberId != null ? attendance.student.id.eq(memberId) : null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.gdschongik.gdsc.domain.study.dao;

import com.gdschongik.gdsc.domain.study.domain.StudyAchievement;
import java.util.List;

public interface StudyAchievementCustomRepository {
List<StudyAchievement> findByStudyIdAndMemberIds(Long studyId, List<Long> memberIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.gdschongik.gdsc.domain.study.dao;

import static com.gdschongik.gdsc.domain.study.domain.QStudyAchievement.*;

import com.gdschongik.gdsc.domain.study.domain.StudyAchievement;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class StudyAchievementCustomRepositoryImpl implements StudyAchievementCustomRepository {

private final JPAQueryFactory queryFactory;

@Override
public List<StudyAchievement> findByStudyIdAndMemberIds(Long studyId, List<Long> memberIds) {
return queryFactory
.selectFrom(studyAchievement)
.where(eqStudyId(studyId), studyAchievement.student.id.in(memberIds))
.fetch();
}

private BooleanExpression eqStudyId(Long studyId) {
return studyId != null ? studyAchievement.study.id.eq(studyId) : null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.gdschongik.gdsc.domain.study.dao;

import com.gdschongik.gdsc.domain.study.domain.StudyAchievement;
import org.springframework.data.jpa.repository.JpaRepository;

public interface StudyAchievementRepository
extends JpaRepository<StudyAchievement, Long>, StudyAchievementCustomRepository {}
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@
import com.gdschongik.gdsc.domain.study.domain.StudyHistory;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface StudyHistoryRepository extends JpaRepository<StudyHistory, Long> {

List<StudyHistory> findByStudyId(Long studyId);

List<StudyHistory> findAllByStudent(Member member);

Optional<StudyHistory> findByStudentAndStudy(Member member, Study study);

boolean existsByStudentAndStudy(Member member, Study study);

Optional<StudyHistory> findByStudentAndStudyId(Member member, Long studyId);

Page<StudyHistory> findByStudyId(Long studyId, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,8 @@ public static Curriculum generateCurriculum(
public boolean isOpen() {
return status == StudyStatus.OPEN;
}

public boolean isCancelled() {
return status == StudyStatus.CANCELLED;
}
}
Loading

0 comments on commit 98d4681

Please sign in to comment.