Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
79 changes: 64 additions & 15 deletions src/main/java/com/example/enjoy/controller/TrackController.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package com.example.enjoy.controller;

import com.example.enjoy.dto.TrackDetailDto;
import com.example.enjoy.dto.TrackProgressDto;
import com.example.enjoy.entity.Track;
import com.example.enjoy.service.TrackService;
import io.swagger.v3.oas.annotations.Operation;
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;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequiredArgsConstructor
Expand All @@ -15,18 +18,64 @@ public class TrackController {

private final TrackService trackService;

/**
* 특정 트랙의 상세 정보를 조회하는 API
* @param trackId 조회할 트랙의 ID
* @return TrackDetailDto - 트랙의 상세 정보
*/
// /**
// * 특정 트랙의 상세 정보를 조회하는 API
// * @param trackId 조회할 트랙의 ID
// * @return TrackDetailDto - 트랙의 상세 정보
// */
// @GetMapping("/{trackId}")
// public TrackDetailDto getTrackDetailsById(@PathVariable Long trackId) {
//
// // TODO: 추후 Spring Security 연동 후 실제 로그인한 학생 ID를 가져와야 함
// String currentStudentId = "1";
//
// // 5. 서비스의 메서드를 호출하여 결과를 받아온 후, 그대로 반환
// return trackService.getTrackDetails(currentStudentId, trackId);
// }

@Operation(summary = "트랙 상세 정보 조회", description = "트랙의 이름으로 상세 정보를 조회합니다.")
@GetMapping("/{trackId}")
public TrackDetailDto getTrackDetailsById(@PathVariable Long trackId) {
public ResponseEntity<TrackDetailDto> getTrackDetailsByName(@RequestParam String trackName) {
TrackDetailDto trackDetailDto = trackService.getTrackDetailsByName(trackName);
if (trackDetailDto == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(trackDetailDto);
}

// TODO: 추후 Spring Security 연동 후 실제 로그인한 학생 ID를 가져와야 함
String currentStudentId = "1";
@Operation(summary = "트랙 목록 조회", description = "모든 트랙의 목록을 조회합니다.")
@GetMapping("/list")
public ResponseEntity<List<TrackDetailDto>> getAllTracks() {
List<Track> tracks = trackService.getAllTracks();
if (tracks.isEmpty()) {
return ResponseEntity.noContent().build();
}
List<TrackDetailDto> trackDetails = tracks.stream()
.map(track -> new TrackDetailDto().from(track))
.toList();
return ResponseEntity.ok(trackDetails);
}

// 5. 서비스의 메서드를 호출하여 결과를 받아온 후, 그대로 반환
return trackService.getTrackDetails(currentStudentId, trackId);
@Operation(summary = "진척률에 따른 추천 트랙 조회", description = "학생의 진척률에 따라 추천 트랙을 조회합니다.")
@GetMapping("/recommendations/progress")
public ResponseEntity<TrackDetailDto> getRecommendedTrackByProgress(@RequestParam String studentId) {
Track recommendedTrack = trackService.getTopTrackByProgressScore(studentId);
if (recommendedTrack == null) {
return ResponseEntity.noContent().build();
}
TrackDetailDto trackDetailDto = new TrackDetailDto().from(recommendedTrack);
return ResponseEntity.ok(trackDetailDto);
}
}

@Operation(summary = "선호 과목에 따른 추천 트랙 조회", description = "학생의 선호 과목에 따라 추천 트랙을 조회합니다.")
@GetMapping("/recommendations/preferred-courses")
public ResponseEntity<TrackDetailDto> getRecommendedTrackByPreferredCourses(@RequestParam String studentId) {
Track recommendedTrack = trackService.getTopTrackByFavoriteScore(studentId);
if (recommendedTrack == null) {
return ResponseEntity.noContent().build();
}
TrackDetailDto trackDetailDto = new TrackDetailDto().from(recommendedTrack);
return ResponseEntity.ok(trackDetailDto);
}

}
10 changes: 10 additions & 0 deletions src/main/java/com/example/enjoy/controller/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,15 @@ public ResponseEntity<MemberDto> saveUserInfo(@Valid @RequestBody MemberDto memb
userService.saveUserInfo(memberDto);
return ResponseEntity.ok().build();
}

@Operation(summary = "학생이 이수 완료한 트랙 조회", description = "학생이 이수 완료한 트랙 목록을 조회합니다.")
@GetMapping("/{studentId}/tracks/completed")
public ResponseEntity<List<Track>> getCompletedTracks(@PathVariable String studentId) {
List<Track> completedTracks = userService.getCompletedTracks(studentId);
if (completedTracks.isEmpty()) {
return ResponseEntity.noContent().build();
}
return ResponseEntity.ok(completedTracks);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@
public interface FavoriteCourseRepository extends JpaRepository<FavoriteCourse, Long> {
Optional<FavoriteCourse> findByUserAndCourseName(User user, String courseName);
List<FavoriteCourse> findAllByUser(User user);

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ public interface TrackRepository extends JpaRepository<Track, Long> {
Optional<Track> findByIdWithCourses(@Param("trackId") Long trackId);

Optional<Track> findByName(String name);

}
104 changes: 101 additions & 3 deletions src/main/java/com/example/enjoy/service/TrackService.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package com.example.enjoy.service;

import ch.qos.logback.core.joran.sanity.Pair;
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.FavoriteCourse;
import com.example.enjoy.entity.StudentCourse;
import com.example.enjoy.entity.Track;
import com.example.enjoy.entity.TrackCourse;
import com.example.enjoy.entity.user.User;
import com.example.enjoy.repository.FavoriteCourseRepository;
import com.example.enjoy.repository.StudentCourseRepository;
import com.example.enjoy.repository.TrackRepository;
import com.example.enjoy.repository.UserRepository;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -24,7 +29,10 @@ public class TrackService {

private final TrackRepository trackRepository;
private final StudentCourseRepository studentCourseRepository; // 기존 기능
private final FavoriteCourseRepository favoriteCourseRepository;
private final UserRepository userRepository;

//진척률 계산
public List<TrackProgressDto> calculateTrackProgress(String studentId) {
Set<String> completedCourseNames = getCompletedCourseNames(studentId); // 이수 과목명 목록

Expand Down Expand Up @@ -60,13 +68,13 @@ public List<TrackProgressDto> calculateTrackProgress(String studentId) {
* 학생이 이수한 과목 이름을 Set으로 반환하는 메서드
*/
@Transactional(readOnly = true)
public TrackDetailDto getTrackDetails(String studentId, Long trackId) {
public TrackDetailDto getTrackDetails(String studentId, String trackName) {

// 1. [리팩토링] 학생 이수 과목 조회 로직을 private 메서드로 호출
Set<String> completedCourseNames = getCompletedCourseNames(studentId);

// 2. ID로 트랙 정보와 소속 과목들을 한번에 조회
Track track = trackRepository.findByIdWithCourses(trackId)
// 2. 트랙 정보와 소속 과목들을 한번에 조회
Track track = trackRepository.findByName(trackName)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 트랙입니다."));

// 3. 트랙의 과목 목록을 CourseStatusDto 리스트로 변환
Expand Down Expand Up @@ -101,6 +109,33 @@ public TrackDetailDto getTrackDetails(String studentId, Long trackId) {
return trackDetailDto;
}

public TrackDetailDto getTrackDetailsByName(String trackName) {
Track track = trackRepository.findByName(trackName)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 트랙입니다."));

// 트랙의 과목 목록을 CourseStatusDto 리스트로 변환
List<CourseStatusDto> courseStatusList = track.getCourses().stream()
.map(trackCourse -> {
CourseStatusDto dto = new CourseStatusDto();
dto.setTitle(trackCourse.getCourseName());
dto.setCode(trackCourse.getCourseCode());
dto.setYear(trackCourse.getAcademicYear());
dto.setSemester(trackCourse.getAcademicSemester());
dto.setStatus("NONE"); // 기본값 설정, 이수 여부는 별도로 처리
return dto;
})
.collect(Collectors.toList());

// 최종적으로 TrackDetailDto를 조립하여 반환
TrackDetailDto trackDetailDto = new TrackDetailDto();
trackDetailDto.setTrackId(track.getId());
trackDetailDto.setTrackName(track.getName());
trackDetailDto.setDepartment(track.getDepartment());
trackDetailDto.setCourses(courseStatusList);

return trackDetailDto;
}

/**
* 학생 ID로 해당 학생이 이수한 모든 과목명을 조회합니다.
*/
Expand All @@ -118,4 +153,67 @@ private boolean isCourseCompleted(TrackCourse course, Set<String> completedCours
return completedCourseNames.contains(course.getCourseName()) ||
(course.getCourseAlias() != null && completedCourseNames.contains(course.getCourseAlias()));
}

public Track getTopTrackByProgressScore(String studentId) {
User user = userRepository.findByStudentId(studentId)
.orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다."));
List<TrackProgressDto> trackProgress = calculateTrackProgress(studentId);

TrackProgressDto topProgress = trackProgress.stream()
.max((a, b) -> Double.compare(
(double) a.getCompletedCount() / a.getRequiredCount(),
(double) b.getCompletedCount() / b.getRequiredCount()))
.orElse(null);

if (topProgress == null) {
return null;
}

return trackRepository.findByName(topProgress.getTrackName())
.orElse(null);
}

// 선호과목 기준 추천 트랙 1개 반환
public Track getTopTrackByFavoriteScore(String studentId) {
User user = userRepository.findByStudentId(studentId)
.orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다."));
Set<String> favoriteCourses = favoriteCourseRepository.findAllByUser(user)
.stream()
.map(FavoriteCourse::getCourseName)
.collect(Collectors.toSet());

List<TrackProgressDto> trackProgress = calculateTrackProgress(studentId);

TrackProgressDto topFavorite = trackProgress.stream()
.max((a, b) -> Double.compare(
calculateFavoriteScore(a, favoriteCourses),
calculateFavoriteScore(b, favoriteCourses)))
.orElse(null);

if (topFavorite == null) {
return null;
}

return trackRepository.findByName(topFavorite.getTrackName())
.orElse(null);
}

private double calculateFavoriteScore(TrackProgressDto track, Set<String> favoriteCourses) {
List<CourseDto> allCourses = new ArrayList<>();
allCourses.addAll(track.getCompletedCourses());
allCourses.addAll(track.getRemainingCourses());

long matchCount = allCourses.stream()
.filter(course ->
(course.getCourseName() != null && favoriteCourses.contains(course.getCourseName())) ||
(course.getCourseAlias() != null && favoriteCourses.contains(course.getCourseAlias()))
)
.count();

return allCourses.isEmpty() ? 0.0 : (double) matchCount / allCourses.size();
}

public List<Track> getAllTracks() {
return trackRepository.findAll();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,16 @@ public void updateCourseStatus(String studentId, String courseName, StudentCours

course.updateStatus(newStatus);
}

public List<Track> getCompletedTracks (String studentId) {
List<StudentCourse> completedCourses = getCompletedCourses(studentId);
List<TrackCourse> trackCourses = trackCourseRepository.findAll();

return trackCourses.stream()
.filter(trackCourse -> completedCourses.stream()
.anyMatch(course -> course.getCourseName().equals(trackCourse.getCourseName())))
.map(TrackCourse::getTrack)
.distinct()
.collect(Collectors.toList());
}
}
Loading