diff --git a/src/main/java/com/example/enjoy/controller/HomeController.java b/src/main/java/com/example/enjoy/controller/HomeController.java index be98c93..52d31d9 100644 --- a/src/main/java/com/example/enjoy/controller/HomeController.java +++ b/src/main/java/com/example/enjoy/controller/HomeController.java @@ -15,6 +15,18 @@ public class HomeController { private final TrackService trackService; @GetMapping("/home") + public String showMyProgress(Model model) { // 1. Model 객체를 파라미터로 추가 + // TODO: 추후 Spring Security 등과 연동하여 실제 로그인한 사용자 ID를 가져와야 함 + String currentStudentId = "1"; // 2. 테스트용 임시 학생 ID 사용 + + // 3. 학생의 이수 현황을 계산하는 새로운 서비스 메서드 호출 + List progressData = trackService.calculateTrackProgress(currentStudentId); + + // 4. 조회된 데이터를 "progressData"라는 이름으로 모델에 추가 + //model.addAttribute("progressData", progressData); + + // 5. 데이터를 표시할 뷰(html)의 이름을 반환 + return "home"; public List getProgress() { // 1. 반환 타입을 List로 변경 String currentStudentId = "1"; 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/UserController.java b/src/main/java/com/example/enjoy/controller/UserController.java index 368d040..68d349a 100644 --- a/src/main/java/com/example/enjoy/controller/UserController.java +++ b/src/main/java/com/example/enjoy/controller/UserController.java @@ -1,4 +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/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 88ab1d0..2320076 100644 --- a/src/main/java/com/example/enjoy/repository/StudentCourseRepository.java +++ b/src/main/java/com/example/enjoy/repository/StudentCourseRepository.java @@ -1,14 +1,25 @@ 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(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); } \ No newline at end of file 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