From d7ba63ecc30e527eaf3bb2ee23ce989cd1f1a709 Mon Sep 17 00:00:00 2001 From: DaeunSon <130820902+DaeunSon@users.noreply.github.com> Date: Wed, 25 Jun 2025 17:26:36 +0900 Subject: [PATCH 1/2] =?UTF-8?q?Feat=20:=20=EC=9C=A0=EC=A0=80=20API=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../enjoy/controller/HomeController.java | 2 +- .../controller/StudentDataController.java | 3 + .../enjoy/controller/TrackController.java | 3 + .../enjoy/controller/UserController.java | 77 ++++++++++++++++ .../enjoy/dto/AddManualCourseRequest.java | 25 ++++++ .../enjoy/dto/StudentCourseStatus.java | 18 ++++ .../example/enjoy/entity/StudentCourse.java | 39 ++++++-- .../enjoy/entity/TrackCertification.java | 13 +++ .../repository/StudentCourseRepository.java | 17 +++- .../repository/TrackCourseRepository.java | 6 ++ .../enjoy/repository/TrackRepository.java | 4 +- .../enjoy/repository/UserRepository.java | 7 +- .../example/enjoy/service/TrackService.java | 2 +- .../service/userService/UserService.java | 88 +++++++++++++++++++ 14 files changed, 289 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/example/enjoy/controller/UserController.java create mode 100644 src/main/java/com/example/enjoy/dto/AddManualCourseRequest.java create mode 100644 src/main/java/com/example/enjoy/dto/StudentCourseStatus.java create mode 100644 src/main/java/com/example/enjoy/entity/TrackCertification.java create mode 100644 src/main/java/com/example/enjoy/service/userService/UserService.java diff --git a/src/main/java/com/example/enjoy/controller/HomeController.java b/src/main/java/com/example/enjoy/controller/HomeController.java index 25e966a..fbdb8dc 100644 --- a/src/main/java/com/example/enjoy/controller/HomeController.java +++ b/src/main/java/com/example/enjoy/controller/HomeController.java @@ -20,7 +20,7 @@ public class HomeController { @GetMapping("/home") public String showMyProgress(Model model) { // 1. Model 객체를 파라미터로 추가 // TODO: 추후 Spring Security 등과 연동하여 실제 로그인한 사용자 ID를 가져와야 함 - Long currentStudentId = 1L; // 2. 테스트용 임시 학생 ID 사용 + String currentStudentId = "1"; // 2. 테스트용 임시 학생 ID 사용 // 3. 학생의 이수 현황을 계산하는 새로운 서비스 메서드 호출 List progressData = trackService.calculateTrackProgress(currentStudentId); diff --git a/src/main/java/com/example/enjoy/controller/StudentDataController.java b/src/main/java/com/example/enjoy/controller/StudentDataController.java index 88e6f23..664b4f4 100644 --- a/src/main/java/com/example/enjoy/controller/StudentDataController.java +++ b/src/main/java/com/example/enjoy/controller/StudentDataController.java @@ -1,4 +1,7 @@ package com.example.enjoy.controller; +import org.springframework.web.bind.annotation.RestController; + +@RestController public class StudentDataController { } diff --git a/src/main/java/com/example/enjoy/controller/TrackController.java b/src/main/java/com/example/enjoy/controller/TrackController.java index 9460444..ed28f33 100644 --- a/src/main/java/com/example/enjoy/controller/TrackController.java +++ b/src/main/java/com/example/enjoy/controller/TrackController.java @@ -1,4 +1,7 @@ package com.example.enjoy.controller; +import org.springframework.web.bind.annotation.RestController; + +@RestController public class TrackController { } diff --git a/src/main/java/com/example/enjoy/controller/UserController.java b/src/main/java/com/example/enjoy/controller/UserController.java new file mode 100644 index 0000000..8a8be2e --- /dev/null +++ b/src/main/java/com/example/enjoy/controller/UserController.java @@ -0,0 +1,77 @@ +package com.example.enjoy.controller; + +import com.example.enjoy.dto.AddManualCourseRequest; +import com.example.enjoy.dto.StudentCourseStatus; +import com.example.enjoy.dto.loginDto.MemberCommand; +import com.example.enjoy.dto.loginDto.MemberDto; +import com.example.enjoy.entity.StudentCourse; +import com.example.enjoy.entity.Track; +import com.example.enjoy.service.loginService.SejongLoginService; +import com.example.enjoy.service.userService.UserService; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import okhttp3.OkHttpClient; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/student") +public class UserController { + + private final SejongLoginService sejongLoginService; + private final UserService userService; + + public UserController(SejongLoginService sejongLoginService, UserService userService) { + this.sejongLoginService = sejongLoginService; + this.userService = userService; + } + + @Operation(summary = "학생 정보 조회", description = "세종대학교 포털 인증을 통해 학생 정보를 조회합니다.") + @GetMapping("/detail") + public ResponseEntity getStudentDetail(@RequestBody MemberCommand command) throws IOException { + MemberDto memberInfo = sejongLoginService.getMemberAuthInfos(command); + return ResponseEntity.ok(memberInfo); + } + + @Operation(summary = "수동 과목 등록", description = "학생이 직접 수강한 과목을 등록합니다.") + @PostMapping("/courses") + public ResponseEntity addManualCourse(@Valid @RequestBody AddManualCourseRequest request) { + userService.addManualCourse(request); + return ResponseEntity.ok().build(); + } + + @Operation(summary = "수동 과목 삭제", description = "수동으로 등록한 과목을 삭제합니다.") + @DeleteMapping("/courses") + public ResponseEntity removeManualCourse( + @RequestParam String studentId, + @RequestParam String courseName) { + userService.removeManualCourse(studentId, courseName); + return ResponseEntity.ok().build(); + } + + @Operation(summary = "완료 과목 조회", description = "학생이 수강 완료한 과목 목록을 조회합니다.") + @GetMapping("/{studentId}/courses/completed") + public ResponseEntity> getCompletedCourses(@PathVariable String studentId) { + return ResponseEntity.ok(userService.getCompletedCourses(studentId)); + } + + @Operation(summary = "트랙 진행률 조회", description = "학생의 각 트랙별 진행률을 조회합니다.") + @GetMapping("/{studentId}/tracks/progress") + public ResponseEntity> getTrackProgress(@PathVariable String studentId) { + return ResponseEntity.ok(userService.getTrackProgress(studentId)); + } + + @Operation(summary = "과목 상태 변경", description = "등록된 과목의 수강 상태를 변경합니다.") + @PatchMapping("/courses/status") + public ResponseEntity updateCourseStatus( + @RequestParam String studentId, + @RequestParam String courseName, + @RequestParam StudentCourseStatus newStatus) { + userService.updateCourseStatus(studentId, courseName, newStatus); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/com/example/enjoy/dto/AddManualCourseRequest.java b/src/main/java/com/example/enjoy/dto/AddManualCourseRequest.java new file mode 100644 index 0000000..3547ce5 --- /dev/null +++ b/src/main/java/com/example/enjoy/dto/AddManualCourseRequest.java @@ -0,0 +1,25 @@ +package com.example.enjoy.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AddManualCourseRequest { + + @NotBlank(message = "학번은 필수입니다.") + private String studentId; + + @NotBlank(message = "과목명은 필수입니다.") + private String courseName; + + @NotNull(message = "수강 상태는 필수입니다.") + private StudentCourseStatus status; +} \ No newline at end of file diff --git a/src/main/java/com/example/enjoy/dto/StudentCourseStatus.java b/src/main/java/com/example/enjoy/dto/StudentCourseStatus.java new file mode 100644 index 0000000..e4631ca --- /dev/null +++ b/src/main/java/com/example/enjoy/dto/StudentCourseStatus.java @@ -0,0 +1,18 @@ +package com.example.enjoy.dto; + +public enum StudentCourseStatus { + COMPLETED("완료"), + IN_PROGRESS("수강중"), + PLANNED("수강예정"), + FAILED("미이수"); + + private final String description; + + StudentCourseStatus(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/enjoy/entity/StudentCourse.java b/src/main/java/com/example/enjoy/entity/StudentCourse.java index 674d50c..cf431ba 100644 --- a/src/main/java/com/example/enjoy/entity/StudentCourse.java +++ b/src/main/java/com/example/enjoy/entity/StudentCourse.java @@ -1,17 +1,40 @@ package com.example.enjoy.entity; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import lombok.Getter; +import com.example.enjoy.dto.StudentCourseStatus; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; @Entity @Getter -public class StudentCourse { +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class StudentCourse extends BaseTimeEntity { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - private Long studentId; // 학생 ID (로그인 연동) + + @Column(nullable = false) + private String studentId; + + @Column(nullable = false) private String courseName; -} + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private StudentCourseStatus status; + + @Column(nullable = false) + private boolean manual; + + @Column(nullable = false) + private LocalDateTime createdAt; + + public void updateStatus(StudentCourseStatus status) { + this.status = status; + } + +} \ No newline at end of file diff --git a/src/main/java/com/example/enjoy/entity/TrackCertification.java b/src/main/java/com/example/enjoy/entity/TrackCertification.java new file mode 100644 index 0000000..ebb7da0 --- /dev/null +++ b/src/main/java/com/example/enjoy/entity/TrackCertification.java @@ -0,0 +1,13 @@ +package com.example.enjoy.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +@Entity +public class TrackCertification { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; +} diff --git a/src/main/java/com/example/enjoy/repository/StudentCourseRepository.java b/src/main/java/com/example/enjoy/repository/StudentCourseRepository.java index 72e9871..f59ce74 100644 --- a/src/main/java/com/example/enjoy/repository/StudentCourseRepository.java +++ b/src/main/java/com/example/enjoy/repository/StudentCourseRepository.java @@ -1,15 +1,26 @@ package com.example.enjoy.repository; +import com.example.enjoy.dto.StudentCourseStatus; import com.example.enjoy.entity.StudentCourse; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Optional; +@Repository public interface StudentCourseRepository extends JpaRepository { - List findByStudentId(Long studentId); + List findByStudentId(String studentId); + + + boolean existsByStudentIdAndCourseName(String studentId, String courseName); + + Optional findByStudentIdAndCourseNameAndManualIsTrue(String studentId, String courseName); + + List findAllByStudentIdAndStatus(String studentId, StudentCourseStatus status); + + Optional findByStudentIdAndCourseName(String studentId, String courseName); - @Transactional - void deleteByStudentId(Long studentId); // 학생 ID로 이수과목 한번에 삭제 } \ No newline at end of file diff --git a/src/main/java/com/example/enjoy/repository/TrackCourseRepository.java b/src/main/java/com/example/enjoy/repository/TrackCourseRepository.java index d263ef0..f314330 100644 --- a/src/main/java/com/example/enjoy/repository/TrackCourseRepository.java +++ b/src/main/java/com/example/enjoy/repository/TrackCourseRepository.java @@ -1,7 +1,13 @@ package com.example.enjoy.repository; +import com.example.enjoy.entity.Track; import com.example.enjoy.entity.TrackCourse; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.List; +@Repository public interface TrackCourseRepository extends JpaRepository { + + List findAllByTrack(Track track); } \ No newline at end of file diff --git a/src/main/java/com/example/enjoy/repository/TrackRepository.java b/src/main/java/com/example/enjoy/repository/TrackRepository.java index 99c2f63..d594e79 100644 --- a/src/main/java/com/example/enjoy/repository/TrackRepository.java +++ b/src/main/java/com/example/enjoy/repository/TrackRepository.java @@ -3,8 +3,10 @@ import com.example.enjoy.entity.Track; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import java.util.List; +import org.springframework.stereotype.Repository; +import java.util.List; +@Repository public interface TrackRepository extends JpaRepository { @Query("SELECT DISTINCT t FROM Track t LEFT JOIN FETCH t.courses") diff --git a/src/main/java/com/example/enjoy/repository/UserRepository.java b/src/main/java/com/example/enjoy/repository/UserRepository.java index 01913a7..3cc6d9c 100644 --- a/src/main/java/com/example/enjoy/repository/UserRepository.java +++ b/src/main/java/com/example/enjoy/repository/UserRepository.java @@ -1,4 +1,9 @@ package com.example.enjoy.repository; -public interface UserRepository { +import com.example.enjoy.entity.user.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserRepository extends JpaRepository { } \ No newline at end of file diff --git a/src/main/java/com/example/enjoy/service/TrackService.java b/src/main/java/com/example/enjoy/service/TrackService.java index 7afb620..f223b96 100644 --- a/src/main/java/com/example/enjoy/service/TrackService.java +++ b/src/main/java/com/example/enjoy/service/TrackService.java @@ -37,7 +37,7 @@ public Map> getAllTracksGroupedByDepartment() { .collect(Collectors.groupingBy(Track::getDepartment)); } - public List calculateTrackProgress(Long studentId) { + public List calculateTrackProgress(String studentId) { // 1. 학생의 이수 과목 목록 조회 Set completedCourseNames = studentCourseRepository.findByStudentId(studentId) .stream() diff --git a/src/main/java/com/example/enjoy/service/userService/UserService.java b/src/main/java/com/example/enjoy/service/userService/UserService.java new file mode 100644 index 0000000..aab1f1a --- /dev/null +++ b/src/main/java/com/example/enjoy/service/userService/UserService.java @@ -0,0 +1,88 @@ +package com.example.enjoy.service.userService; + +import com.amazonaws.services.cloudformation.model.AlreadyExistsException; +import com.example.enjoy.dto.AddManualCourseRequest; +import com.example.enjoy.dto.StudentCourseStatus; +import com.example.enjoy.entity.StudentCourse; +import com.example.enjoy.entity.Track; +import com.example.enjoy.entity.TrackCourse; +import com.example.enjoy.repository.StudentCourseRepository; +import com.example.enjoy.repository.TrackCourseRepository; +import com.example.enjoy.repository.TrackRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class UserService { + + private final StudentCourseRepository studentCourseRepository; + private final TrackRepository trackRepository; + private final TrackCourseRepository trackCourseRepository; + + @Transactional + public void addManualCourse(AddManualCourseRequest request) { //수동으로 과목 등록 + if (studentCourseRepository.existsByStudentIdAndCourseName( + request.getStudentId(), + request.getCourseName())) { + throw new AlreadyExistsException("이미 등록된 과목입니다."); + } + + StudentCourse sc = StudentCourse.builder() //수강 여부 설정 가능 + .studentId(request.getStudentId()) + .courseName(request.getCourseName()) + .status(request.getStatus()) + .manual(true) + .build(); + studentCourseRepository.save(sc); + } + + + @Transactional + public void removeManualCourse(String studentId, String courseName) { //수동 등록 과목 삭제 + StudentCourse course = studentCourseRepository.findByStudentIdAndCourseNameAndManualIsTrue(studentId, courseName) + .orElseThrow(() -> new IllegalArgumentException("수동 등록된 과목을 찾을 수 없습니다.")); + studentCourseRepository.delete(course); + } + + public List getCompletedCourses(String studentId) { //수강 완료 과목 조회 + return studentCourseRepository.findAllByStudentIdAndStatus(studentId, StudentCourseStatus.COMPLETED); + } + + public Map getTrackProgress(String studentId) { //트랙별 진행률 조회 + List allTracks = trackRepository.findAll(); + List completedCourses = getCompletedCourses(studentId); + + return allTracks.stream().collect(Collectors.toMap( + track -> track, + track -> calculateTrackProgress(track, completedCourses) + )); + } + + private double calculateTrackProgress(Track track, List completedCourses) { + List trackCourses = trackCourseRepository.findAllByTrack(track); + if (trackCourses.isEmpty()) return 0.0; + + long completedCount = trackCourses.stream() + .filter(trackCourse -> completedCourses.stream() + .anyMatch(completed -> completed.getCourseName().equals(trackCourse.getCourseName()))) + .count(); + + return (double) completedCount / trackCourses.size() * 100; + } + + @Transactional + public void updateCourseStatus(String studentId, String courseName, StudentCourseStatus newStatus) { + StudentCourse course = studentCourseRepository + .findByStudentIdAndCourseName(studentId, courseName) + .orElseThrow(() -> new IllegalArgumentException("등록된 과목을 찾을 수 없습니다.")); + + course.updateStatus(newStatus); + } +} \ No newline at end of file From 288d6c19982a0fa5a1af737450cc1a64917359cd Mon Sep 17 00:00:00 2001 From: Jiwoo Date: Wed, 25 Jun 2025 17:36:50 +0900 Subject: [PATCH 2/2] feat: --- --- .../enjoy/controller/TrackController.java | 25 +++++++ .../example/enjoy/dto/CourseStatusDto.java | 5 +- .../com/example/enjoy/dto/TrackDetailDto.java | 18 ++--- .../com/example/enjoy/entity/TrackCourse.java | 3 +- .../repository/ScheduledCourseRepository.java | 4 - .../enjoy/repository/TrackRepository.java | 13 +++- .../example/enjoy/service/TrackService.java | 73 ++++++++++++++++++- 7 files changed, 119 insertions(+), 22 deletions(-) delete mode 100644 src/main/java/com/example/enjoy/repository/ScheduledCourseRepository.java diff --git a/src/main/java/com/example/enjoy/controller/TrackController.java b/src/main/java/com/example/enjoy/controller/TrackController.java index ed28f33..e77fa79 100644 --- a/src/main/java/com/example/enjoy/controller/TrackController.java +++ b/src/main/java/com/example/enjoy/controller/TrackController.java @@ -1,7 +1,32 @@ package com.example.enjoy.controller; +import com.example.enjoy.dto.TrackDetailDto; +import com.example.enjoy.service.TrackService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController +@RequiredArgsConstructor +@RequestMapping("/api/tracks") public class TrackController { + + private final TrackService trackService; + + /** + * 특정 트랙의 상세 정보를 조회하는 API + * @param trackId 조회할 트랙의 ID + * @return TrackDetailDto - 트랙의 상세 정보 + */ + @GetMapping("/{trackId}") + public TrackDetailDto getTrackDetailsById(@PathVariable Long trackId) { + + // TODO: 추후 Spring Security 연동 후 실제 로그인한 학생 ID를 가져와야 함 + Long currentStudentId = 1L; + + // 5. 서비스의 메서드를 호출하여 결과를 받아온 후, 그대로 반환 + return trackService.getTrackDetails(currentStudentId, trackId); + } } diff --git a/src/main/java/com/example/enjoy/dto/CourseStatusDto.java b/src/main/java/com/example/enjoy/dto/CourseStatusDto.java index dcbe8f9..5c8ca1a 100644 --- a/src/main/java/com/example/enjoy/dto/CourseStatusDto.java +++ b/src/main/java/com/example/enjoy/dto/CourseStatusDto.java @@ -1,5 +1,6 @@ package com.example.enjoy.dto; +import lombok.Data; import lombok.Getter; import lombok.Setter; @@ -7,8 +8,8 @@ * 과목 정보 DTO * (title, year, semester, code, status) */ -@Getter -@Setter + +@Data public class CourseStatusDto { private String title; // 과목명 (기존 courseName) diff --git a/src/main/java/com/example/enjoy/dto/TrackDetailDto.java b/src/main/java/com/example/enjoy/dto/TrackDetailDto.java index 9b1a5bb..f69fee5 100644 --- a/src/main/java/com/example/enjoy/dto/TrackDetailDto.java +++ b/src/main/java/com/example/enjoy/dto/TrackDetailDto.java @@ -1,21 +1,19 @@ package com.example.enjoy.dto; +import lombok.Data; import lombok.Getter; import lombok.Setter; import java.util.List; /** * 상세 UI 화면의 트랙 탭 하나의 전체 정보를 담는 DTO - * 새로 정의된 CourseStatusDto를 사용 */ -@Getter -@Setter -public class TrackDetailDto { - - private String trackName; // 트랙 이름 - private int completedCount; // 이수한 과목 수 - private int requiredCount = 6; // 이수 필요 과목 수 - // 리스트의 타입이 새로운 CourseStatusDto로 변경 - private List courses; +@Data // 또는 @Getter, @Setter 등 +public class TrackDetailDto { + private Long trackId; + private String trackName; + private String department; + private String description; // 트랙에 대한 설명 추가 + private List courses; // 트랙에 포함된 과목 목록 } diff --git a/src/main/java/com/example/enjoy/entity/TrackCourse.java b/src/main/java/com/example/enjoy/entity/TrackCourse.java index c9f0156..c50ee73 100644 --- a/src/main/java/com/example/enjoy/entity/TrackCourse.java +++ b/src/main/java/com/example/enjoy/entity/TrackCourse.java @@ -1,10 +1,11 @@ package com.example.enjoy.entity; import jakarta.persistence.*; +import lombok.Data; import lombok.Getter; @Entity -@Getter +@Data public class TrackCourse { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/example/enjoy/repository/ScheduledCourseRepository.java b/src/main/java/com/example/enjoy/repository/ScheduledCourseRepository.java deleted file mode 100644 index 2a82b5e..0000000 --- a/src/main/java/com/example/enjoy/repository/ScheduledCourseRepository.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.example.enjoy.repository; - -public interface ScheduledCourseRepository { -} diff --git a/src/main/java/com/example/enjoy/repository/TrackRepository.java b/src/main/java/com/example/enjoy/repository/TrackRepository.java index d594e79..c35d37c 100644 --- a/src/main/java/com/example/enjoy/repository/TrackRepository.java +++ b/src/main/java/com/example/enjoy/repository/TrackRepository.java @@ -3,12 +3,21 @@ import com.example.enjoy.entity.Track; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.stereotype.Repository; +import org.springframework.data.repository.query.Param; import java.util.List; -@Repository +import java.util.Optional; + public interface TrackRepository extends JpaRepository { @Query("SELECT DISTINCT t FROM Track t LEFT JOIN FETCH t.courses") List findAllWithCourses(); + + /** + * 특정 ID의 트랙을 과목 정보와 함께 조회합니다 (N+1 문제 해결). + * @param trackId 트랙 ID + * @return 트랙 정보 (Optional) + */ + @Query("SELECT t FROM Track t JOIN FETCH t.courses WHERE t.id = :trackId") + Optional findByIdWithCourses(@Param("trackId") Long trackId); } \ No newline at end of file diff --git a/src/main/java/com/example/enjoy/service/TrackService.java b/src/main/java/com/example/enjoy/service/TrackService.java index f223b96..dd1d328 100644 --- a/src/main/java/com/example/enjoy/service/TrackService.java +++ b/src/main/java/com/example/enjoy/service/TrackService.java @@ -1,6 +1,8 @@ package com.example.enjoy.service; import com.example.enjoy.dto.CourseDto; +import com.example.enjoy.dto.CourseStatusDto; +import com.example.enjoy.dto.TrackDetailDto; import com.example.enjoy.dto.TrackProgressDto; import com.example.enjoy.entity.StudentCourse; import com.example.enjoy.entity.Track; @@ -9,6 +11,7 @@ import com.example.enjoy.repository.TrackRepository; import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; +import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.List; @@ -23,8 +26,6 @@ public class TrackService { private final TrackRepository trackRepository; private final StudentCourseRepository studentCourseRepository; // 기존 기능 - // ... (기존의 saveStudentCoursesFromExcel, calculateTrackProgress 메서드) ... - /** * 모든 트랙 정보를 학과별로 그룹화하여 반환하는 메서드 */ @@ -37,7 +38,10 @@ public Map> getAllTracksGroupedByDepartment() { .collect(Collectors.groupingBy(Track::getDepartment)); } - public List calculateTrackProgress(String studentId) { + /** + * 학생이 이수한 과목 이름을 Set으로 반환하는 private 메서드 + */ + public List calculateTrackProgress(Long studentId) { // 1. 학생의 이수 과목 목록 조회 Set completedCourseNames = studentCourseRepository.findByStudentId(studentId) .stream() @@ -89,4 +93,67 @@ public List calculateTrackProgress(String studentId) { return progressList; } + + /** + * 학생이 이수한 과목 이름을 Set으로 반환하는 메서드 + */ + @Transactional(readOnly = true) + public TrackDetailDto getTrackDetails(Long studentId, Long trackId) { + + // 1. [리팩토링] 학생 이수 과목 조회 로직을 private 메서드로 호출 + Set completedCourseNames = getCompletedCourseNames(studentId); + + // 2. ID로 트랙 정보와 소속 과목들을 한번에 조회 + Track track = trackRepository.findByIdWithCourses(trackId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 트랙입니다.")); + + // 3. 트랙의 과목 목록을 CourseStatusDto 리스트로 변환 + List courseStatusList = track.getCourses().stream() + .map(trackCourse -> { + // 4. [수정] DTO 객체 생성 및 실제 필드에 맞게 데이터 세팅 + CourseStatusDto dto = new CourseStatusDto(); + dto.setTitle(trackCourse.getCourseName()); + // (TrackCourse 엔티티에 getCourseCode, getYear, getSemester가 있다고 가정합니다) + dto.setCode(trackCourse.getCourseCode()); + dto.setYear(trackCourse.getAcademicYear()); + dto.setSemester(trackCourse.getAcademicSemester()); + + // 5. [수정 & 리팩토링] 이수 여부를 판단하고, 그 결과에 따라 status(String) 값을 세팅 + if (isCourseCompleted(trackCourse, completedCourseNames)) { + dto.setStatus("COMPLETED"); + } else { + dto.setStatus("NONE"); + } + + return dto; + }) + .collect(Collectors.toList()); + + // 6. 최종적으로 TrackDetailDto를 조립하여 반환 + TrackDetailDto trackDetailDto = new TrackDetailDto(); + trackDetailDto.setTrackId(track.getId()); + trackDetailDto.setTrackName(track.getName()); + trackDetailDto.setDepartment(track.getDepartment()); + trackDetailDto.setCourses(courseStatusList); + + return trackDetailDto; + } + + /** + * 학생 ID로 해당 학생이 이수한 모든 과목명을 조회합니다. + */ + private Set getCompletedCourseNames(Long studentId) { + return studentCourseRepository.findByStudentId(studentId) + .stream() + .map(StudentCourse::getCourseName) + .collect(Collectors.toSet()); + } + + /** + * 특정 과목이 이수 완료되었는지 확인합니다. (과목 별칭 포함) + */ + private boolean isCourseCompleted(TrackCourse course, Set completedCourseNames) { + return completedCourseNames.contains(course.getCourseName()) || + (course.getCourseAlias() != null && completedCourseNames.contains(course.getCourseAlias())); + } }