-
Notifications
You must be signed in to change notification settings - Fork 0
refactor: semester 기반 시간표/위시리스트 조작 정합성 강화 #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,9 +38,9 @@ public ResponseEntity<?> addToWishlist(@RequestBody Map<String, Object> request) | |
| } | ||
|
|
||
| @DeleteMapping("/remove") | ||
| public ResponseEntity<?> removeFromWishlist(@RequestParam Long userId, @RequestParam Long subjectId) { | ||
| public ResponseEntity<?> removeFromWishlist(@RequestParam Long userId, @RequestParam Long subjectId, @RequestParam String semester) { | ||
| try { | ||
| wishlistService.removeFromWishlist(userId, subjectId); | ||
| wishlistService.removeFromWishlist(userId, subjectId, semester); | ||
| return ResponseEntity.ok(Map.of("message", "위시리스트에서 제거되었습니다.")); | ||
|
|
||
| } catch (RuntimeException e) { | ||
|
|
@@ -75,9 +75,10 @@ public ResponseEntity<?> updatePriority(@RequestBody Map<String, Object> request | |
| try { | ||
| Long userId = Long.valueOf(request.get("userId").toString()); | ||
| Long subjectId = Long.valueOf(request.get("subjectId").toString()); | ||
| String semester = (String) request.get("semester"); | ||
| Integer priority = Integer.valueOf(request.get("priority").toString()); | ||
|
|
||
| wishlistService.updatePriority(userId, subjectId, priority); | ||
| wishlistService.updatePriority(userId, subjectId, semester, priority); | ||
| return ResponseEntity.ok(Map.of("message", "우선순위가 업데이트되었습니다.")); | ||
|
Comment on lines
75
to
82
|
||
|
|
||
| } catch (RuntimeException e) { | ||
|
|
@@ -90,9 +91,10 @@ public ResponseEntity<?> updateRequired(@RequestBody Map<String, Object> request | |
| try { | ||
| Long userId = Long.valueOf(request.get("userId").toString()); | ||
| Long subjectId = Long.valueOf(request.get("subjectId").toString()); | ||
| String semester = (String) request.get("semester"); | ||
| Boolean isRequired = Boolean.valueOf(request.get("isRequired").toString()); | ||
|
|
||
| wishlistService.updateRequired(userId, subjectId, isRequired); | ||
| wishlistService.updateRequired(userId, subjectId, semester, isRequired); | ||
| return ResponseEntity.ok(Map.of("message", "필수 과목 설정이 업데이트되었습니다.")); | ||
|
Comment on lines
91
to
98
|
||
|
|
||
| } catch (RuntimeException e) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -22,10 +22,10 @@ public interface WishlistRepository extends JpaRepository<WishlistItem, Long> { | |||||||||
| "ORDER BY w.priority") | ||||||||||
| List<WishlistItem> findByUserIdAndSemesterWithSubjectAndSchedules(@Param("userId") Long userId, @Param("semester") String semester); | ||||||||||
|
|
||||||||||
| @Query("SELECT w FROM WishlistItem w JOIN FETCH w.subject s WHERE w.user.id = :userId AND s.id = :subjectId") | ||||||||||
| Optional<WishlistItem> findByUserIdAndSubjectId(@Param("userId") Long userId, @Param("subjectId") Long subjectId); | ||||||||||
| @Query("SELECT w FROM WishlistItem w JOIN FETCH w.subject s WHERE w.user.id = :userId AND s.id = :subjectId AND w.semester = :semester") | ||||||||||
|
||||||||||
| @Query("SELECT w FROM WishlistItem w JOIN FETCH w.subject s WHERE w.user.id = :userId AND s.id = :subjectId AND w.semester = :semester") | |
| @Query("SELECT w FROM WishlistItem w JOIN FETCH w.subject s " + | |
| "WHERE w.user.id = :userId AND s.id = :subjectId " + | |
| "AND ((:semester IS NULL AND w.semester IS NULL) OR w.semester = :semester)") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,12 +8,20 @@ | |
| import inu.timetable.repository.UserTimetableRepository; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Optional; | ||
|
|
||
| @Service | ||
| public class TimetableService { | ||
|
|
||
| private String requireSemester(String semester) { | ||
| if (semester == null || semester.isBlank()) { | ||
| throw new RuntimeException("학기(semester)는 필수입니다."); | ||
| } | ||
| return semester; | ||
| } | ||
|
|
||
| private final UserTimetableRepository userTimetableRepository; | ||
| private final UserRepository userRepository; | ||
|
|
@@ -36,32 +44,35 @@ public UserTimetable addSubjectToTimetable(Long userId, Long subjectId, String s | |
| .orElseThrow(() -> new RuntimeException("과목을 찾을 수 없습니다.")); | ||
|
|
||
| // 이미 추가된 과목인지 확인 | ||
| Optional<UserTimetable> existing = userTimetableRepository.findByUserIdAndSubjectId(userId, subjectId); | ||
| String sem = requireSemester(semester); | ||
| Optional<UserTimetable> existing = userTimetableRepository.findByUserIdAndSubjectIdAndSemester(userId, subjectId, sem); | ||
| if (existing.isPresent()) { | ||
| throw new RuntimeException("이미 시간표에 추가된 과목입니다."); | ||
| } | ||
|
|
||
| // 시간표 겹침 확인 | ||
| List<UserTimetable> currentTimetable = userTimetableRepository.findByUserIdAndSemesterWithSubjectAndSchedules(userId, semester); | ||
| List<UserTimetable> currentTimetable = userTimetableRepository.findByUserIdAndSemesterWithSubjectAndSchedules(userId, sem); | ||
| if (hasTimeConflict(currentTimetable, subject)) { | ||
| throw new RuntimeException("시간표가 겹치는 과목이 있습니다."); | ||
| } | ||
|
|
||
| UserTimetable userTimetable = UserTimetable.builder() | ||
| .user(user) | ||
| .subject(subject) | ||
| .semester(semester) | ||
| .semester(sem) | ||
| .memo(memo) | ||
| .build(); | ||
|
|
||
| return userTimetableRepository.save(userTimetable); | ||
| } | ||
|
|
||
| public void removeSubjectFromTimetable(Long userId, Long subjectId) { | ||
| UserTimetable userTimetable = userTimetableRepository.findByUserIdAndSubjectId(userId, subjectId) | ||
| .orElseThrow(() -> new RuntimeException("시간표에서 해당 과목을 찾을 수 없습니다.")); | ||
|
|
||
| userTimetableRepository.delete(userTimetable); | ||
| @Transactional | ||
| public void removeSubjectFromTimetable(Long userId, Long subjectId, String semester) { | ||
| String sem = requireSemester(semester); | ||
| int deleted = userTimetableRepository.deleteByUserIdAndSubjectIdAndSemester(userId, subjectId, sem); | ||
| if (deleted == 0) { | ||
| throw new RuntimeException("시간표에서 해당 과목을 찾을 수 없습니다."); | ||
| } | ||
| } | ||
|
Comment on lines
+69
to
76
|
||
|
|
||
| public List<UserTimetable> getUserTimetable(Long userId, String semester) { | ||
|
|
@@ -72,8 +83,9 @@ public List<UserTimetable> getUserTimetable(Long userId, String semester) { | |
| } | ||
| } | ||
|
|
||
| public UserTimetable updateMemo(Long userId, Long subjectId, String memo) { | ||
| UserTimetable userTimetable = userTimetableRepository.findByUserIdAndSubjectId(userId, subjectId) | ||
| public UserTimetable updateMemo(Long userId, Long subjectId, String semester, String memo) { | ||
| String sem = requireSemester(semester); | ||
| UserTimetable userTimetable = userTimetableRepository.findByUserIdAndSubjectIdAndSemester(userId, subjectId, sem) | ||
| .orElseThrow(() -> new RuntimeException("시간표에서 해당 과목을 찾을 수 없습니다.")); | ||
|
|
||
| userTimetable.setMemo(memo); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -15,6 +15,13 @@ | |||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @Service | ||||||||||||||||||||||||||||||||||||
| public class WishlistService { | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| private String requireSemester(String semester) { | ||||||||||||||||||||||||||||||||||||
| if (semester == null || semester.isBlank()) { | ||||||||||||||||||||||||||||||||||||
| throw new RuntimeException("학기(semester)는 필수입니다."); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| return semester; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| private final WishlistRepository wishlistRepository; | ||||||||||||||||||||||||||||||||||||
| private final UserRepository userRepository; | ||||||||||||||||||||||||||||||||||||
|
|
@@ -41,15 +48,16 @@ public WishlistItem addToWishlist(Long userId, Long subjectId, String semester, | |||||||||||||||||||||||||||||||||||
| .orElseThrow(() -> new RuntimeException("과목을 찾을 수 없습니다.")); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // 이미 위시리스트에 있는지 확인 | ||||||||||||||||||||||||||||||||||||
| Optional<WishlistItem> existing = wishlistRepository.findByUserIdAndSubjectId(userId, subjectId); | ||||||||||||||||||||||||||||||||||||
| String sem = requireSemester(semester); | ||||||||||||||||||||||||||||||||||||
| Optional<WishlistItem> existing = wishlistRepository.findByUserIdAndSubjectIdAndSemester(userId, subjectId, sem); | ||||||||||||||||||||||||||||||||||||
| if (existing.isPresent()) { | ||||||||||||||||||||||||||||||||||||
| throw new RuntimeException("이미 위시리스트에 추가된 과목입니다."); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| WishlistItem wishlistItem = WishlistItem.builder() | ||||||||||||||||||||||||||||||||||||
| .user(user) | ||||||||||||||||||||||||||||||||||||
| .subject(subject) | ||||||||||||||||||||||||||||||||||||
| .semester(semester) | ||||||||||||||||||||||||||||||||||||
| .semester(sem) | ||||||||||||||||||||||||||||||||||||
| .priority(priority != null ? priority : 3) // 기본 우선순위: 중간 | ||||||||||||||||||||||||||||||||||||
| .isRequired(isRequired != null ? isRequired : false) | ||||||||||||||||||||||||||||||||||||
| .build(); | ||||||||||||||||||||||||||||||||||||
|
|
@@ -58,24 +66,30 @@ public WishlistItem addToWishlist(Long userId, Long subjectId, String semester, | |||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @Transactional | ||||||||||||||||||||||||||||||||||||
| public void removeFromWishlist(Long userId, Long subjectId) { | ||||||||||||||||||||||||||||||||||||
| wishlistRepository.deleteByUserIdAndSubjectId(userId, subjectId); | ||||||||||||||||||||||||||||||||||||
| public void removeFromWishlist(Long userId, Long subjectId, String semester) { | ||||||||||||||||||||||||||||||||||||
| String sem = requireSemester(semester); | ||||||||||||||||||||||||||||||||||||
| int deleted = wishlistRepository.deleteByUserIdAndSubjectIdAndSemester(userId, subjectId, sem); | ||||||||||||||||||||||||||||||||||||
| if (deleted == 0) { | ||||||||||||||||||||||||||||||||||||
| throw new RuntimeException("위시리스트에서 해당 과목을 찾을 수 없습니다."); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+69
to
+74
|
||||||||||||||||||||||||||||||||||||
| public void removeFromWishlist(Long userId, Long subjectId, String semester) { | |
| int deleted = wishlistRepository.deleteByUserIdAndSubjectIdAndSemester(userId, subjectId, semester); | |
| if (deleted == 0) { | |
| throw new RuntimeException("위시리스트에서 해당 과목을 찾을 수 없습니다."); | |
| } | |
| public void removeFromWishlist(Long userId, Long subjectId, String semester) { | |
| if (semester == null || semester.trim().isEmpty()) { | |
| throw new IllegalArgumentException("학기를 지정해야 위시리스트에서 과목을 삭제할 수 있습니다."); | |
| } | |
| int deleted = wishlistRepository.deleteByUserIdAndSubjectIdAndSemester(userId, subjectId, semester); | |
| if (deleted == 0) { | |
| throw new RuntimeException("위시리스트에서 해당 과목을 찾을 수 없습니다."); | |
| } | |
| if (deleted > 1) { | |
| throw new RuntimeException("위시리스트에서 여러 개의 항목이 삭제되었습니다. 관리자에게 문의하세요."); | |
| } |
Copilot
AI
Feb 14, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updatePriority now depends on a semester argument, but the controller can pass null (body field not required). With the current repository query, null semester will likely fail to find existing rows (or target only rows with null semester), breaking updates for existing data. Consider validating semester is present/non-blank for this endpoint (or providing a deterministic default) before calling the repository.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
semesteris optional in this request body, but the service update is semester-specific. Without validating presence/non-blank semester, clients that omit it will get inconsistent behavior or failures. Recommend rejecting missing/blanksemesterwith 400 (or implementing an explicit fallback semester selection).