diff --git a/src/main/java/com/ikdaman/domain/book/entity/Book.java b/src/main/java/com/ikdaman/domain/book/entity/Book.java index b861bc5..eb32f46 100644 --- a/src/main/java/com/ikdaman/domain/book/entity/Book.java +++ b/src/main/java/com/ikdaman/domain/book/entity/Book.java @@ -41,11 +41,14 @@ public class Book extends BaseTime { @Column(name = "category_id") private int categoryId; + @Column(name = "aladin_item_id") + private String aladinItemId; + @OneToMany(mappedBy = "book", fetch = FetchType.LAZY) private List author; @Builder - public Book(int bookId, String title, String publisher, String isbn, int page, String coverImage, int categoryId, List author) { + public Book(int bookId, String title, String publisher, String isbn, int page, String coverImage, int categoryId, String aladinItemId, List author) { this.bookId = bookId; this.title = title; this.publisher = publisher; @@ -53,6 +56,7 @@ public Book(int bookId, String title, String publisher, String isbn, int page, S this.page = page; this.coverImage = coverImage; this.categoryId = categoryId; + this.aladinItemId = aladinItemId; this.author = author; } } \ No newline at end of file diff --git a/src/main/java/com/ikdaman/domain/book/repository/BookRepository.java b/src/main/java/com/ikdaman/domain/book/repository/BookRepository.java index 6562398..1daeff8 100644 --- a/src/main/java/com/ikdaman/domain/book/repository/BookRepository.java +++ b/src/main/java/com/ikdaman/domain/book/repository/BookRepository.java @@ -4,10 +4,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; -import java.util.UUID; public interface BookRepository extends JpaRepository { Optional findByIsbn(String isbn); - - // boolean existsMyBookByMemberIdAndBook(UUID memberId, Book Book); } diff --git a/src/main/java/com/ikdaman/domain/member/controller/MemberController.java b/src/main/java/com/ikdaman/domain/member/controller/MemberController.java index baccff2..058e9de 100644 --- a/src/main/java/com/ikdaman/domain/member/controller/MemberController.java +++ b/src/main/java/com/ikdaman/domain/member/controller/MemberController.java @@ -30,22 +30,6 @@ public class MemberController { private final MemberService memberService; - // 회원 생성 테스트 - @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity createMember(@RequestBody MemberReq dto) { - Member member = memberService.createMember(dto); - System.out.println("aaaaaaaaa"); - return ResponseEntity.ok(member); - } - - // 회원 조회 테스트 - @GetMapping("/{memberId}") - public ResponseEntity getMember(@PathVariable UUID memberId) { - Optional member = memberService.findMemberById(memberId); - return member.map(ResponseEntity::ok) - .orElseGet(() -> ResponseEntity.notFound().build()); - } - // 닉네임 중복 확인 // TODO: 닉네임 형식에 대한 Validation (2025.05.01 기준 미정) @GetMapping("/check") diff --git a/src/main/java/com/ikdaman/domain/mybook/controller/MyBookController.java b/src/main/java/com/ikdaman/domain/mybook/controller/MyBookController.java index ca11931..9b3a18b 100644 --- a/src/main/java/com/ikdaman/domain/mybook/controller/MyBookController.java +++ b/src/main/java/com/ikdaman/domain/mybook/controller/MyBookController.java @@ -3,10 +3,17 @@ import com.ikdaman.domain.bookLog.model.BookLogListRes; import com.ikdaman.domain.mybook.model.*; import com.ikdaman.domain.mybook.service.MyBookService; +import com.ikdaman.global.auth.model.AuthMember; import lombok.RequiredArgsConstructor; +import com.ikdaman.global.auth.model.AuthMember; +import org.springframework.security.core.annotation.AuthenticationPrincipal; + +import java.util.UUID; + import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @RestController @@ -18,23 +25,26 @@ public class MyBookController { @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity addMyBook( - @RequestBody MyBookReq dto - // @RequestHeader("nickname") String nickname // 임시 인증 방식 + @RequestBody MyBookReq dto, + @AuthenticationPrincipal AuthMember authMember ) { - MyBookRes myBookRes = myBookService.addMyBook(dto); + MyBookRes myBookRes = myBookService.addMyBook(authMember.getMember().getMemberId(), dto); return ResponseEntity.status(HttpStatus.CREATED).build(); } @PostMapping("/{mybook_id}/impression") public ResponseEntity createImpression(@PathVariable("mybook_id") Integer myBookId, - @RequestBody ImpressionReq dto) { - myBookService.addImpression(myBookId, dto); + @RequestBody ImpressionReq dto, + @AuthenticationPrincipal AuthMember authMember) { + + myBookService.addImpression(authMember.getMember().getMemberId(), myBookId, dto); return ResponseEntity.status(HttpStatus.CREATED).build(); } @DeleteMapping("/{mybook_id}") - public ResponseEntity deleteMyBook(@PathVariable Integer mybook_id) { - myBookService.deleteMyBook(mybook_id); + public ResponseEntity deleteMyBook(@PathVariable Integer mybook_id, + @AuthenticationPrincipal AuthMember authMember) { + myBookService.deleteMyBook(authMember.getMember().getMemberId(), mybook_id); return ResponseEntity.status(HttpStatus.RESET_CONTENT).build(); } @@ -43,36 +53,41 @@ public MyBookSearchRes searchMyBooks( @RequestParam(required = false) String status, @RequestParam(required = false) String keyword, @RequestParam(required = false, defaultValue = "1") Integer page, - @RequestParam(required = false, defaultValue = "9") Integer limit + @RequestParam(required = false, defaultValue = "9") Integer limit, + @AuthenticationPrincipal AuthMember authMember ) { MyBookSearchReq request = new MyBookSearchReq(); request.setStatus(status); request.setKeyword(keyword); request.setPage(page); request.setLimit(limit); - return myBookService.searchMyBooks(request); + return myBookService.searchMyBooks(request, authMember); } // 독서중인 책 목록 조회 @GetMapping("/in-progress") - public InProgressBooksRes searchInProgressBooks() { - return myBookService.searchInProgressBooks(); + public InProgressBooksRes searchInProgressBooks( + @AuthenticationPrincipal AuthMember authMember + ) { + return myBookService.searchInProgressBooks(authMember); } // 나의 책 정보 조회 @GetMapping("/{mybookId}") - public ResponseEntity getMyBookDetail(@PathVariable Long mybookId) { - MyBookDetailRes res = myBookService.getMyBookDetail(mybookId); + public ResponseEntity getMyBookDetail(@AuthenticationPrincipal AuthMember authMember, + @PathVariable Long mybookId) { + MyBookDetailRes res = myBookService.getMyBookDetail(authMember.getMember().getMemberId(), mybookId); return ResponseEntity.ok(res); } // 나의 책 기록 조회 @GetMapping("/{mybookId}/booklog") public ResponseEntity getMyBookLogs( + @AuthenticationPrincipal AuthMember authMember, @PathVariable("mybookId") Long mybookId, @RequestParam(value = "page", required = false, defaultValue = "1") Integer page, @RequestParam(value = "limit", required = false, defaultValue = "9") Integer limit ) { - return ResponseEntity.ok(myBookService.getMyBookLogs(mybookId, page, limit)); + return ResponseEntity.ok(myBookService.getMyBookLogs(authMember.getMember().getMemberId(), mybookId, page, limit)); } } \ No newline at end of file diff --git a/src/main/java/com/ikdaman/domain/mybook/model/MyBookDetailRes.java b/src/main/java/com/ikdaman/domain/mybook/model/MyBookDetailRes.java index 485d083..c009911 100644 --- a/src/main/java/com/ikdaman/domain/mybook/model/MyBookDetailRes.java +++ b/src/main/java/com/ikdaman/domain/mybook/model/MyBookDetailRes.java @@ -22,6 +22,7 @@ public class MyBookDetailRes { @NoArgsConstructor @AllArgsConstructor public static class BookInfo { + private String itemId; private String title; private String author; private String coverImage; diff --git a/src/main/java/com/ikdaman/domain/mybook/repository/MyBookRepository.java b/src/main/java/com/ikdaman/domain/mybook/repository/MyBookRepository.java index 629f110..a54f349 100644 --- a/src/main/java/com/ikdaman/domain/mybook/repository/MyBookRepository.java +++ b/src/main/java/com/ikdaman/domain/mybook/repository/MyBookRepository.java @@ -1,5 +1,6 @@ package com.ikdaman.domain.mybook.repository; +import com.ikdaman.domain.book.entity.Book; import com.ikdaman.domain.mybook.entity.MyBook; import io.lettuce.core.dynamic.annotation.Param; import org.springframework.data.domain.Page; @@ -12,36 +13,12 @@ public interface MyBookRepository extends JpaRepository { - @Query(""" - SELECT m FROM MyBook m - LEFT JOIN m.book b - LEFT JOIN b.author a - LEFT JOIN a.writer w - WHERE m.memberId = :memberId - AND ( - :status IS NULL OR - (:status = 'completed' AND m.isReading = false) OR - (:status = 'in-progress' AND m.isReading = true) - ) - AND ( - :keyword IS NULL OR - b.title LIKE :keyword OR - w.writerName LIKE :keyword - ) - """) - Page searchMyBooks( - @Param("memberId") UUID memberId, - @Param("status") String status, - @Param("keyword") String keyword, - Pageable pageable - ); - @Query(value = """ SELECT m FROM MyBook m LEFT JOIN m.book b LEFT JOIN b.author a LEFT JOIN a.writer w - WHERE + WHERE m.memberId = :memberId AND ( :status IS NULL OR (:status = 'completed' AND m.isReading = false) OR @@ -59,6 +36,7 @@ SELECT COUNT(m) FROM MyBook m LEFT JOIN b.author a LEFT JOIN a.writer w WHERE + m.memberId = :memberId AND ( :status IS NULL OR (:status = 'completed' AND m.isReading = false) OR @@ -71,7 +49,8 @@ SELECT COUNT(m) FROM MyBook m ) """ ) - Page searchMyBooksWithoutMemberId( + Page searchMyBooks( + @Param("memberId") UUID memberId, @Param("status") String status, @Param("keyword") String keyword, Pageable pageable @@ -93,7 +72,8 @@ Page searchMyBooksWithoutMemberId( LEFT JOIN m.book b LEFT JOIN m.bookLogs bl WHERE - m.isReading = true + m.memberId = :memberId + AND m.isReading = true AND m.status = 'ACTIVE' """, countQuery = """ @@ -101,10 +81,13 @@ SELECT COUNT(m) FROM MyBook m LEFT JOIN m.book b LEFT JOIN m.bookLogs bl WHERE - m.isReading = true + m.memberId = :memberId + AND m.isReading = true AND m.status = 'ACTIVE' """ ) - List findAllActiveReadingBooks(); + List findAllActiveReadingBooks(UUID memberId); + + boolean existsMyBookByMemberIdAndBook(UUID memberId, Book Book); } diff --git a/src/main/java/com/ikdaman/domain/mybook/service/MyBookService.java b/src/main/java/com/ikdaman/domain/mybook/service/MyBookService.java index 6014fe9..8c7eeeb 100644 --- a/src/main/java/com/ikdaman/domain/mybook/service/MyBookService.java +++ b/src/main/java/com/ikdaman/domain/mybook/service/MyBookService.java @@ -2,24 +2,26 @@ import com.ikdaman.domain.bookLog.model.BookLogListRes; import com.ikdaman.domain.mybook.model.*; +import com.ikdaman.global.auth.model.AuthMember; + +import java.util.UUID; /** * 나의 책 서비스 */ public interface MyBookService { - MyBookRes addMyBook(MyBookReq dto); - // MyBookRes addMyBook(MyBookReq dto, String memberId); + MyBookRes addMyBook(UUID memberId, MyBookReq dto); // 첫인상 - MyBookRes addImpression(Integer myBookId, ImpressionReq dto); + MyBookRes addImpression(UUID memberId, Integer myBookId, ImpressionReq dto); - void deleteMyBook(Integer mybookId); + void deleteMyBook(UUID memberId, Integer mybookId); - MyBookSearchRes searchMyBooks(MyBookSearchReq request); + MyBookSearchRes searchMyBooks(MyBookSearchReq request, AuthMember authMember); - InProgressBooksRes searchInProgressBooks(); + InProgressBooksRes searchInProgressBooks(AuthMember authMember); - MyBookDetailRes getMyBookDetail(Long mybookId); + MyBookDetailRes getMyBookDetail(UUID memberId, Long mybookId); - BookLogListRes getMyBookLogs(Long mybookId, Integer page, Integer limit); + BookLogListRes getMyBookLogs(UUID memberId, Long mybookId, Integer page, Integer limit); } \ No newline at end of file diff --git a/src/main/java/com/ikdaman/domain/mybook/service/MyBookServiceImpl.java b/src/main/java/com/ikdaman/domain/mybook/service/MyBookServiceImpl.java index cc10b3f..1fbb464 100644 --- a/src/main/java/com/ikdaman/domain/mybook/service/MyBookServiceImpl.java +++ b/src/main/java/com/ikdaman/domain/mybook/service/MyBookServiceImpl.java @@ -4,12 +4,9 @@ import com.ikdaman.domain.bookLog.model.BookLogType; import com.ikdaman.domain.bookLog.repository.BookLogRepository; import com.ikdaman.domain.bookLog.model.BookLogListRes; -import com.ikdaman.domain.member.entity.Member; import com.ikdaman.domain.member.repository.MemberRepository; -import com.ikdaman.domain.bookLog.repository.BookLogRepository; import com.ikdaman.domain.book.entity.Author; import com.ikdaman.domain.book.entity.Book; -import com.ikdaman.domain.bookLog.entity.BookLog; import com.ikdaman.domain.mybook.entity.MyBook; import com.ikdaman.domain.book.entity.Writer; import com.ikdaman.domain.mybook.model.*; @@ -18,6 +15,8 @@ import com.ikdaman.domain.mybook.repository.MyBookRepository; import com.ikdaman.domain.book.repository.WriterRepository; import com.ikdaman.global.exception.BaseException; +import com.ikdaman.global.auth.model.AuthMember; +import lombok.extern.slf4j.Slf4j; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -37,6 +36,7 @@ * 나의 책 서비스 구현체 */ @Service +@Slf4j @RequiredArgsConstructor public class MyBookServiceImpl implements MyBookService { @@ -49,9 +49,7 @@ public class MyBookServiceImpl implements MyBookService { @Override @Transactional - public MyBookRes addMyBook(MyBookReq dto) { -// UUID memberId = UUID.fromString("d290f1ee-6c54-4b01-90e6-d701748f0851"); - + public MyBookRes addMyBook(UUID memberId, MyBookReq dto) { Writer writer = writerRepository.findByWriterName(dto.getWriter()) .orElseGet(() -> writerRepository.save( Writer.builder() @@ -67,6 +65,7 @@ public MyBookRes addMyBook(MyBookReq dto) { .isbn(dto.getIsbn()) .page(dto.getPage()) .coverImage(dto.getCoverImage()) + .aladinItemId(String.valueOf(dto.getItemId())) .build(); return bookRepository.save(newBook); @@ -79,16 +78,13 @@ public MyBookRes addMyBook(MyBookReq dto) { .build()); } -// Member member = memberRepository.findByNickname(nickname) -// .orElseThrow(() -> new RuntimeException("회원이 존재하지 않습니다.")); - -// if(bookRepository.existsMyBookByMemberIdAndBook(memberId, book)) { -// throw new BaseException(MY_BOOK_ALREADY_EXISTS); -// } + // 한 사용자가 책장에 같은 책을 중복으로 저장할 수 없음 + if(myBookRepository.existsMyBookByMemberIdAndBook(memberId, book)) { + throw new BaseException(MY_BOOK_ALREADY_EXISTS); + } MyBook myBook = MyBook.builder() -// .memberId(member.getMemberId()) -// .memberId(memberId) + .memberId(memberId) .book(book) .nowPage(0) .isReading(true) @@ -119,10 +115,14 @@ public MyBookRes addMyBook(MyBookReq dto) { @Override @Transactional - public MyBookRes addImpression(Integer myBookId, ImpressionReq dto) { + public MyBookRes addImpression(UUID memberId, Integer myBookId, ImpressionReq dto) { MyBook myBook = myBookRepository.findById(Long.valueOf(myBookId)) .orElseThrow(() -> new BaseException(NOT_FOUND_MY_BOOK)); + if (!myBook.getMemberId().equals(memberId)) { + throw new BaseException(BOOK_NOT_OWNED_BY_MEMBER); + } + Book book = bookRepository.findById(Long.valueOf(myBook.getBook().getBookId())) .orElseThrow(() -> new BaseException(NOT_FOUND_BOOK)); @@ -156,17 +156,17 @@ public MyBookRes addImpression(Integer myBookId, ImpressionReq dto) { } @Override - public MyBookSearchRes searchMyBooks(MyBookSearchReq request) { + public MyBookSearchRes searchMyBooks(MyBookSearchReq request, AuthMember authMember) { int page = request.getPage() - 1; // PageRequest는 0부터 시작 int limit = request.getLimit(); Pageable pageable = PageRequest.of(page, limit); - //UUID memberId = request.getMemberId(); - //UUID memberId = UUID.fromString("d290f1ee-6c54-4b01-90e6-d701748f0851"); String keyword = request.getKeyword(); - System.out.println("keyword: " + keyword); - Page resultPage = myBookRepository.searchMyBooksWithoutMemberId( + log.info("keyword: {}", keyword); + + Page resultPage = myBookRepository.searchMyBooks( + authMember.getMember().getMemberId(), request.getStatus(), keyword, pageable @@ -196,11 +196,10 @@ public MyBookSearchRes searchMyBooks(MyBookSearchReq request) { } @Override - public InProgressBooksRes searchInProgressBooks() { - //UUID memberId = request.getMemberId(); - //UUID memberId = UUID.fromString("d290f1ee-6c54-4b01-90e6-d701748f0851"); - - List myBooks = myBookRepository.findAllActiveReadingBooks(); + public InProgressBooksRes searchInProgressBooks(AuthMember authMember) { + List myBooks = myBookRepository.findAllActiveReadingBooks( + authMember.getMember().getMemberId() + ); List bookDtos = myBooks.stream() .map(myBook -> { @@ -235,9 +234,10 @@ public InProgressBooksRes searchInProgressBooks() { // 나의 책 정보 조회 @Override @Transactional(readOnly = true) - public MyBookDetailRes getMyBookDetail(Long mybookId) { - MyBook myBook = myBookRepository.findById(mybookId) - .orElseThrow(() -> new BaseException(NOT_FOUND_BOOK)); + public MyBookDetailRes getMyBookDetail(UUID memberId, Long mybookId) { + + // 책 주인 확인 + MyBook myBook = getMyBookIfOwner(mybookId, memberId); Book book = myBook.getBook(); // 작가열 생성 @@ -246,13 +246,15 @@ public MyBookDetailRes getMyBookDetail(Long mybookId) { .map(a -> a.getWriter().getWriterName()) .collect(Collectors.joining(", ")); - // 첫인상 추가 + // 첫인상 조회 String impression = bookLogRepository.findFirstByMyBookAndBooklogType(myBook, "IMPRESSION") .map(BookLog::getContent) .orElse(null); - // 책 정보 추가 + // 책 정보 객체 생성 + // TODO: itemId 알라딘 item id로 변경해서 전달 필요(isbn -> aladinItemId) MyBookDetailRes.BookInfo bookInfo = MyBookDetailRes.BookInfo.builder() + .itemId(book.getIsbn()) .title(book.getTitle()) .author(authorNames) .coverImage(book.getCoverImage()) @@ -260,7 +262,7 @@ public MyBookDetailRes getMyBookDetail(Long mybookId) { .totalPage(book.getPage()) .build(); - // 나의책 정보 추가 + // 나의책 정보 객체 생성 return MyBookDetailRes.builder() .bookInfo(bookInfo) .mybookId(String.valueOf(myBook.getMybookId())) @@ -274,10 +276,16 @@ public MyBookDetailRes getMyBookDetail(Long mybookId) { // 나의 책 기록 조회 @Override @Transactional(readOnly = true) - public BookLogListRes getMyBookLogs(Long mybookId, Integer page, Integer limit) { + public BookLogListRes getMyBookLogs(UUID memberId, Long mybookId, Integer page, Integer limit) { + + // 책 주인 확인 + getMyBookIfOwner(mybookId, memberId); + + // 페이지네이션 Pageable pageable = PageRequest.of(page - 1, limit, Sort.by(Sort.Direction.DESC, "createdAt")); Page resultPage = bookLogRepository.findByMyBook_MybookId(mybookId, pageable); + // 응답용 DTO 리스트 변환 List booklogs = resultPage.getContent().stream() .map(log -> new BookLogListRes.BookLogDTO( log.getBooklogId(), @@ -292,11 +300,25 @@ public BookLogListRes getMyBookLogs(Long mybookId, Integer page, Integer limit) @Override @Transactional - public void deleteMyBook(Integer id) { + public void deleteMyBook(UUID memberId, Integer id) { MyBook myBook = myBookRepository.findById(Long.valueOf(id)) .orElseThrow(() -> new BaseException(NOT_FOUND_MY_BOOK)); + if (!myBook.getMemberId().equals(memberId)) { + throw new BaseException(BOOK_NOT_OWNED_BY_MEMBER); + } + myBook.updateToInactive(); myBookRepository.save(myBook); } + + // 책 주인 확인 + private MyBook getMyBookIfOwner(Long mybookId, UUID memberId) { + MyBook myBook = myBookRepository.findById(mybookId) + .orElseThrow(() -> new BaseException(NOT_FOUND_BOOK)); + if (!myBook.getMemberId().equals(memberId)) { + throw new BaseException(BOOK_NOT_OWNED_BY_MEMBER); + } + return myBook; + } } \ No newline at end of file diff --git a/src/main/java/com/ikdaman/global/exception/ErrorCode.java b/src/main/java/com/ikdaman/global/exception/ErrorCode.java index 35658a6..ffc0c7f 100644 --- a/src/main/java/com/ikdaman/global/exception/ErrorCode.java +++ b/src/main/java/com/ikdaman/global/exception/ErrorCode.java @@ -28,6 +28,18 @@ public enum ErrorCode { // Notice(04) + /** + * 403 Forbidden + */ + // Auth(01) + + // Member(02) + + // MyBook(03) + BOOK_NOT_OWNED_BY_MEMBER(HttpStatus.FORBIDDEN.value(), 4030301, "본인의 책이 아닙니다."), + + // Notice(04) + /** * 404 Not found