diff --git a/src/main/java/com/danpoong/onchung/domain/user/domain/UserInfo.java b/src/main/java/com/danpoong/onchung/domain/user/domain/UserInfo.java index c1f6e51..e3d7991 100644 --- a/src/main/java/com/danpoong/onchung/domain/user/domain/UserInfo.java +++ b/src/main/java/com/danpoong/onchung/domain/user/domain/UserInfo.java @@ -52,6 +52,17 @@ public class UserInfo { ) private List favoritePolicies = new ArrayList<>(); + public void addFavoriteWord(Word word) { + if (!favoriteWords.contains(word)) { + favoriteWords.add(word); + } + } + + public void removeFavoriteWord(Word word) { + favoriteWords.remove(word); + } + + @ManyToMany @JoinTable( name = "user_favorite_word", diff --git a/src/main/java/com/danpoong/onchung/domain/word/controller/WordController.java b/src/main/java/com/danpoong/onchung/domain/word/controller/WordController.java index 8d9bd40..89660e3 100644 --- a/src/main/java/com/danpoong/onchung/domain/word/controller/WordController.java +++ b/src/main/java/com/danpoong/onchung/domain/word/controller/WordController.java @@ -9,6 +9,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.http.HttpStatus; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @RestController @@ -21,11 +22,11 @@ public class WordController { @Operation(summary = "카테고리 별 단어 조회", description = "한 페이지의 데이터 개수 설정 가능") @GetMapping("/category/{page_num}") public ResponseTemplate> getWords( -// @AuthenticationPrincipal Long userId, + @AuthenticationPrincipal Long userId, @RequestParam String category, @PathVariable("page_num") int pageNum ) { - return new ResponseTemplate<>(HttpStatus.OK, "단어 카테고리 조회 성공", wordService.getWordsByCategory(category, pageNum)); + return new ResponseTemplate<>(HttpStatus.OK, "단어 카테고리 조회 성공", wordService.getWordsByCategory(category, userId, pageNum)); } @Operation(summary = "단어 상세 조회") @@ -34,32 +35,32 @@ public ResponseTemplate getWord(@PathVariable("word_id") Long w return new ResponseTemplate<>(HttpStatus.OK, "단어 상세 조회 성공", wordService.getWord(wordId)); } -// @Operation(summary = "단어 북마크", description = "만약 기존에 북마크에 있다면 삭제, 아니라면 추가") -// @PatchMapping("/bookmark/{word_id}") -// public ResponseTemplate favoriteWord( -// @AuthenticationPrincipal Long userId, -// @PathVariable("word_id") Long wordId -// ) { -// wordService.favoriteWord(userId, wordId); -// return new ResponseTemplate<>(HttpStatus.OK, "북마크 추가 / 삭제 성공"); -// } -// -// @Operation(summary = "단어 북마크 조회") -// @GetMapping("/book-mark/{page_num}") -// public ResponseTemplate> getBookmarks( -// @AuthenticationPrincipal Long userId, -// @PathVariable("page_num") int pageNum -// ) { -// return new ResponseTemplate<>(HttpStatus.OK, "북마크 단어 조회 성공", wordService.getBookmarkedWords(userId, pageNum)); -// } + @Operation(summary = "단어 북마크", description = "만약 기존에 북마크에 있다면 삭제, 아니라면 추가") + @PatchMapping("/bookmark/{word_id}") + public ResponseTemplate favoriteWord( + @AuthenticationPrincipal Long userId, + @PathVariable("word_id") Long wordId + ) { + wordService.favoriteWord(userId, wordId); + return new ResponseTemplate<>(HttpStatus.OK, "북마크 추가 / 삭제 성공"); + } + + @Operation(summary = "단어 북마크 조회") + @GetMapping("/book-mark/{page_num}") + public ResponseTemplate> getBookmarks( + @AuthenticationPrincipal Long userId, + @PathVariable("page_num") int pageNum + ) { + return new ResponseTemplate<>(HttpStatus.OK, "북마크 단어 조회 성공", wordService.getBookmarkedWords(userId, pageNum)); + } @Operation(summary = "단어 검색", description = "type에는 카테고리 종류 중 하나 / 공백 중 하나가 들어갈 수 있다.") @GetMapping("/search") public ResponseTemplate searchWord( -// @AuthenticationPrincipal Long userId, + @AuthenticationPrincipal Long userId, @RequestParam String type, @RequestParam String word ) { - return new ResponseTemplate<>(HttpStatus.OK, "용어 검색 성공", wordService.searchWord(type, word)); + return new ResponseTemplate<>(HttpStatus.OK, "용어 검색 성공", wordService.searchWord(type, word, userId)); } } diff --git a/src/main/java/com/danpoong/onchung/domain/word/domain/Word.java b/src/main/java/com/danpoong/onchung/domain/word/domain/Word.java index e7a4d37..6204d94 100644 --- a/src/main/java/com/danpoong/onchung/domain/word/domain/Word.java +++ b/src/main/java/com/danpoong/onchung/domain/word/domain/Word.java @@ -7,6 +7,8 @@ import lombok.NoArgsConstructor; import lombok.Setter; +import java.util.Objects; + @Entity @Getter @@ -35,6 +37,19 @@ public class Word { @Column(name = "related_welfare") private String relatedWelfare; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Word word = (Word) o; + return Objects.equals(id, word.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + @Builder public Word(String category, String term, String description, String example, String relatedWelfare) { this.category = WordCategory.checkCategory(category); diff --git a/src/main/java/com/danpoong/onchung/domain/word/repository/WordRepository.java b/src/main/java/com/danpoong/onchung/domain/word/repository/WordRepository.java index 09d2c21..a4b74bb 100644 --- a/src/main/java/com/danpoong/onchung/domain/word/repository/WordRepository.java +++ b/src/main/java/com/danpoong/onchung/domain/word/repository/WordRepository.java @@ -5,12 +5,24 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; +@Repository public interface WordRepository extends JpaRepository { Page findByCategory(WordCategory category, Pageable pageable); List findAllByCategory(WordCategory category); Optional findByTerm(String term); + + @Query("SELECT w FROM Word w " + + "LEFT JOIN UserInfo u ON :userId MEMBER OF u.favoriteWords " + + "WHERE w.category = :category " + + "ORDER BY CASE WHEN w MEMBER OF u.favoriteWords THEN 0 ELSE 1 END, w.id") + Page findWordsByCategoryWithBookmarkFirst(@Param("category") WordCategory category, + @Param("userId") Long userId, + Pageable pageable); } diff --git a/src/main/java/com/danpoong/onchung/domain/word/service/WordService.java b/src/main/java/com/danpoong/onchung/domain/word/service/WordService.java index 1a1aacb..d221ab8 100644 --- a/src/main/java/com/danpoong/onchung/domain/word/service/WordService.java +++ b/src/main/java/com/danpoong/onchung/domain/word/service/WordService.java @@ -1,8 +1,8 @@ package com.danpoong.onchung.domain.word.service; -//import com.danpoong.onchung.domain.user.domain.UserInfo; -//import com.danpoong.onchung.domain.user.repository.UserRepository; - +import com.danpoong.onchung.domain.user.domain.UserInfo; +import com.danpoong.onchung.domain.user.exception.UserNotFoundException; +import com.danpoong.onchung.domain.user.repository.UserInfoRepository; import com.danpoong.onchung.domain.word.domain.Word; import com.danpoong.onchung.domain.word.domain.enums.WordCategory; import com.danpoong.onchung.domain.word.dto.WordResponseDto; @@ -10,6 +10,7 @@ import com.danpoong.onchung.domain.word.repository.WordRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,22 +23,26 @@ @Transactional(readOnly = true) public class WordService { private final WordRepository wordRepository; -// private final UserRepository userRepository; + private final UserInfoRepository userInfoRepository; private static final int WORD_PAGE_SIZE = 10; - public Page getWordsByCategory(String category, int page) { - Page words = wordRepository.findByCategory(WordCategory.checkCategory(category), PageRequest.of(page, WORD_PAGE_SIZE)); + public Page getWordsByCategory(String category, Long id, int page) { + Page words = wordRepository.findWordsByCategoryWithBookmarkFirst( + WordCategory.checkCategory(category), + id, + PageRequest.of(page, WORD_PAGE_SIZE) + ); return words.map(word -> { boolean isBookmarked = false; -// if (userId != null) { -// UserInfo userInfo = userRepository.findById(userId) -// .orElseThrow(() -> new RuntimeException("해당 ID의 사용자가 존재하지 않습니다.")); -// -// isBookmarked = userInfo.getFavoriteWords().contains(word); -// } + if (id != null) { + UserInfo userInfo = userInfoRepository.findById(id) + .orElseThrow(() -> new UserNotFoundException("해당 ID의 사용자가 존재하지 않습니다."); + + isBookmarked = userInfo.getFavoriteWords().contains(word); + } return WordSummaryResponseDto.builder() .wordId(word.getId()) @@ -49,7 +54,7 @@ public Page getWordsByCategory(String category, int page } public WordResponseDto getWord(Long wordId) { - Word word = wordRepository.findById(wordId).orElseThrow(() -> new RuntimeException("해당 ID의 단어가 존재하지 않습니다.")); + Word word = wordRepository.findById(wordId).orElseThrow(() -> new UserNotFoundException("해당 ID의 단어가 존재하지 않습니다.")); return WordResponseDto.builder() .term(word.getTerm()) @@ -60,81 +65,80 @@ public WordResponseDto getWord(Long wordId) { .build(); } -// @Transactional -// public void favoriteWord(Long userId, Long wordId) { -// UserInfo userInfo = userRepository.findById(userId).orElseThrow(() -> new RuntimeException("사용자가 존재하지 않습니다.")); -// Word word = wordRepository.findById(wordId).orElseThrow(() -> new RuntimeException("해당 ID의 단어가 존재하지 않습니다.")); -// -// boolean isPresent = userInfo.getFavoriteWords().contains(wordId); -// -// if (!isPresent) { -// userInfo.addFavoriteWord(word); -// } else { -// userInfo.removeFavoriteWord(word); -// } -// } + @Transactional + public void favoriteWord(Long id, Long wordId) { + UserInfo userInfo = userInfoRepository.findById(id).orElseThrow(() -> new UserNotFoundException("사용자가 존재하지 않습니다.")); + Word word = wordRepository.findById(wordId).orElseThrow(() -> new RuntimeException("해당 ID의 단어가 존재하지 않습니다.")); -// public Page getBookmarkedWords(Long userId, int page) { -// UserInfo userInfo = userRepository.findById(userId).orElseThrow(() -> new RuntimeException("해당 ID의 사용자가 존재하지 않습니다.")); -// List wordList = userInfo.getFavoriteWords(); -// -// int totalWords = wordList.size(); -// int start = Math.min(page * WORD_PAGE_SIZE, totalWords); -// int end = Math.min((page + 1) * WORD_PAGE_SIZE, totalWords); -// -// List pagedWords = wordList.subList(start, end); -// Page pageResult = new PageImpl<>(pagedWords, PageRequest.of(page, WORD_PAGE_SIZE), totalWords); -// -// return pageResult.map(word -> WordSummaryResponseDto.builder() -// .wordId(word.getId()) -// .term(word.getTerm()) -// .isBookmark(true) -// .relatedWelfare(word.getRelatedWelfare()) -// .build() -// ); -// } - - public WordSummaryResponseDto searchWord(String type, String term) { -// UserInfo userInfo = userRepository.findById(userId) -// .orElseThrow(() -> new RuntimeException("해당 ID의 사용자가 존재하지 않습니다.")); + boolean isPresent = userInfo.getFavoriteWords().contains(word); + + if (!isPresent) { + userInfo.addFavoriteWord(word); + } else { + userInfo.removeFavoriteWord(word); + } + } + + public Page getBookmarkedWords(Long id, int page) { + UserInfo userInfo = userInfoRepository.findById(id).orElseThrow(() -> new UserNotFoundException("해당 ID의 사용자가 존재하지 않습니다.")); + List wordList = userInfo.getFavoriteWords(); + + int totalWords = wordList.size(); + int start = Math.min(page * WORD_PAGE_SIZE, totalWords); + int end = Math.min((page + 1) * WORD_PAGE_SIZE, totalWords); + + List pagedWords = wordList.subList(start, end); + Page pageResult = new PageImpl<>(pagedWords, PageRequest.of(page, WORD_PAGE_SIZE), totalWords); + + return pageResult.map(word -> WordSummaryResponseDto.builder() + .wordId(word.getId()) + .term(word.getTerm()) + .isBookmark(true) + .relatedWelfare(word.getRelatedWelfare()) + .build() + ); + } + + public WordSummaryResponseDto searchWord(String type, String term, Long id) { + UserInfo userInfo = userInfoRepository.findById(id) + .orElseThrow(() -> new UserNotFoundException("해당 ID의 사용자가 존재하지 않습니다.")); Word word = wordRepository.findByTerm(term).orElse(null); if (word == null) { return WordSummaryResponseDto.empty(); } -// // 북마크 검색 -// if (type.equals("북마크")) { -// return searchBookmark(userInfo, word); -// } -// + // 북마크 검색 + if (type.equals("북마크")) { + return searchBookmark(userInfo, word); + } + // 전체 검색 if (type.isEmpty()) { - return searchAll(word); + return searchAll(word, userInfo); } // 카테고리 검색 - return searchByCategory(type, word); + return searchByCategory(type, word, userInfo); } // 북마크 내 검색 -// private WordSummaryResponseDto searchBookmark(UserInfo userInfo, Word word) { -// for (Word favoriteWord : userInfo.getFavoriteWords()) { -// if (favoriteWord.getTerm().equals(word.getTerm())) { -// return createWordSummaryResponse(favoriteWord, true); -// } -// } -// return WordSummaryResponseDto.empty(); -// } + private WordSummaryResponseDto searchBookmark(UserInfo userInfo, Word word) { + if (userInfo.getFavoriteWords().contains(word)) { + return createWordSummaryResponse(word, true); + } + return WordSummaryResponseDto.empty(); + } // 전체 검색 - private WordSummaryResponseDto searchAll(Word word) { -// boolean isBookmark = userInfo.getFavoriteWords().contains(word); - return createWordSummaryResponse(word, false); + private WordSummaryResponseDto searchAll(Word word, UserInfo userInfo) { + boolean isBookmark = userInfo.getFavoriteWords().contains(word); + return createWordSummaryResponse(word, isBookmark); } + // 카테고리 내 검색 - private WordSummaryResponseDto searchByCategory(String type, Word word) { + private WordSummaryResponseDto searchByCategory(String type, Word word, UserInfo userInfo) { WordCategory wordCategoryEng = WordCategory.checkCategory(type); List wordList = wordRepository.findAllByCategory(wordCategoryEng);