From 67c9d0c851899aa01678911446e3a65547f98a0a Mon Sep 17 00:00:00 2001 From: chae_1 <129128205+sungchaewon@users.noreply.github.com> Date: Fri, 11 Oct 2024 14:01:30 +0900 Subject: [PATCH] =?UTF-8?q?[BOOK-16]-=EB=8F=85=ED=9B=84=EA=B0=90=20?= =?UTF-8?q?=EC=93=B0=EA=B8=B0=20=EC=88=98=EC=A0=95=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add review functionality * Add review functionality * [BOOK-16]-refactor: 독후감 생성 및 조회 api 수정 * [BOOK-16]-refactor: 코멘트 수정 사항 반영 * [BOOK-16]-refactor: 수정 기능 관련 수정 * [BOOK-16]-refactor: 추가 코멘트 사항 수정 --------- Co-authored-by: sunny2you --- .../booklog/common/config/SecurityConfig.java | 2 +- .../domain/book/application/BookService.java | 44 ++++++-- .../unit/booklog/domain/book/domain/Book.java | 8 +- .../domain/book/domain/BookRepository.java | 10 ++ .../infrastructure/BookRepositoryImpl.java | 17 +++ .../infrastructure/JpaBookRepository.java | 5 +- .../presentation/response/BookResponse.java | 6 +- .../unit/booklog/domain/file/domain/File.java | 4 + .../review/application/ReviewService.java | 74 ++++++++++++ .../booklog/domain/review/domain/Review.java | 75 +++++++++++++ .../review/domain/ReviewRepository.java | 11 ++ .../domain/review/domain/ReviewStatus.java | 13 +++ .../infrastructure/JpaReviewRepository.java | 7 ++ .../infrastructure/ReviewRepositoryImpl.java | 25 +++++ .../review/presentation/ReviewController.java | 105 ++++++++++++++++++ .../exception/ReviewExceptionCode.java | 22 ++++ .../exception/ReviewNotFoundException.java | 11 ++ .../request/ReviewCreateRequest.java | 43 +++++++ .../response/ReviewPersistResponse.java | 15 +++ .../presentation/response/ReviewResponse.java | 50 +++++++++ .../domain/user/application/UserService.java | 13 +++ .../unit/booklog/domain/user/domain/User.java | 15 ++- .../UserNotAuthenticatedException.java | 13 +++ 23 files changed, 565 insertions(+), 23 deletions(-) create mode 100644 src/main/java/goorm/unit/booklog/domain/review/application/ReviewService.java create mode 100644 src/main/java/goorm/unit/booklog/domain/review/domain/Review.java create mode 100644 src/main/java/goorm/unit/booklog/domain/review/domain/ReviewRepository.java create mode 100644 src/main/java/goorm/unit/booklog/domain/review/domain/ReviewStatus.java create mode 100644 src/main/java/goorm/unit/booklog/domain/review/infrastructure/JpaReviewRepository.java create mode 100644 src/main/java/goorm/unit/booklog/domain/review/infrastructure/ReviewRepositoryImpl.java create mode 100644 src/main/java/goorm/unit/booklog/domain/review/presentation/ReviewController.java create mode 100644 src/main/java/goorm/unit/booklog/domain/review/presentation/exception/ReviewExceptionCode.java create mode 100644 src/main/java/goorm/unit/booklog/domain/review/presentation/exception/ReviewNotFoundException.java create mode 100644 src/main/java/goorm/unit/booklog/domain/review/presentation/request/ReviewCreateRequest.java create mode 100644 src/main/java/goorm/unit/booklog/domain/review/presentation/response/ReviewPersistResponse.java create mode 100644 src/main/java/goorm/unit/booklog/domain/review/presentation/response/ReviewResponse.java create mode 100644 src/main/java/goorm/unit/booklog/domain/user/presentation/exception/UserNotAuthenticatedException.java diff --git a/src/main/java/goorm/unit/booklog/common/config/SecurityConfig.java b/src/main/java/goorm/unit/booklog/common/config/SecurityConfig.java index 722c043..67361b5 100644 --- a/src/main/java/goorm/unit/booklog/common/config/SecurityConfig.java +++ b/src/main/java/goorm/unit/booklog/common/config/SecurityConfig.java @@ -69,7 +69,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/favicon.ico", "/index.html", "/api/v1/users/signup", - "/api/v1/auth/login" + "/api/v1/auth/login", "/api/v1/users/duplication", }; diff --git a/src/main/java/goorm/unit/booklog/domain/book/application/BookService.java b/src/main/java/goorm/unit/booklog/domain/book/application/BookService.java index 20a481d..1cdbb88 100644 --- a/src/main/java/goorm/unit/booklog/domain/book/application/BookService.java +++ b/src/main/java/goorm/unit/booklog/domain/book/application/BookService.java @@ -3,7 +3,11 @@ import java.net.URI; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import goorm.unit.booklog.domain.book.domain.Book; +import goorm.unit.booklog.domain.file.domain.File; +import goorm.unit.booklog.domain.file.infrastructure.FileRepository; import org.json.JSONArray; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Value; @@ -23,6 +27,7 @@ @RequiredArgsConstructor public class BookService { private final BookRepository bookRepository; + private final FileRepository fileRepository; @Value("${naver.api.clientId}") private String clientId; @@ -57,18 +62,43 @@ public BookPageResponse searchBooks(int page, int size, String keyword) { List bookResponses = new ArrayList<>(); for (int i = 0; i < items.length(); i++) { JSONObject item = items.getJSONObject(i); - BookResponse bookResponse = BookResponse.of( - (long)(i + 1), - item.getString("title"), - item.getString("author"), - item.getString("description"), - item.getString("link") - ); + + Book book; + Long fileId; + String title = item.getString("title"); + String author = item.getString("author"); + String description=item.getString("description"); + String link=item.getString("link"); + + Optional existingBook = bookRepository.findByTitleAndAuthor(title, author); + + if (!existingBook.isPresent()) { + File file = File.of(title, link); + + book = Book.create( + title, + author, + description, + file + ); + bookRepository.save(book); + } + else{ + book= existingBook.get(); + } + + BookResponse bookResponse = BookResponse.from(book); bookResponses.add(bookResponse); + } int total = jsonResponse.getInt("total"); return BookPageResponse.of(bookResponses, PageableResponse.of(PageRequest.of(page, size), (long)total)); } + + public Book getBook(Long id) { + return bookRepository.findById(id).orElse(null); + } + } diff --git a/src/main/java/goorm/unit/booklog/domain/book/domain/Book.java b/src/main/java/goorm/unit/booklog/domain/book/domain/Book.java index eca743e..d571071 100644 --- a/src/main/java/goorm/unit/booklog/domain/book/domain/Book.java +++ b/src/main/java/goorm/unit/booklog/domain/book/domain/Book.java @@ -20,6 +20,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import jakarta.persistence.CascadeType; @Getter @Entity @@ -38,19 +39,18 @@ public class Book extends BaseTimeEntity { @Column(nullable = false) private String author; - @Column(nullable = false, length = 1000) + @Column(nullable = false, length = 2000) private String description; - @OneToOne + @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "file_id") private File file; @ManyToMany(mappedBy = "books") private List users = new ArrayList<>(); - public static Book create(Long id, String title, String author, String description, File file) { + public static Book create(String title, String author, String description, File file) { return Book.builder() - .id(id) .title(title) .author(author) .description(description) diff --git a/src/main/java/goorm/unit/booklog/domain/book/domain/BookRepository.java b/src/main/java/goorm/unit/booklog/domain/book/domain/BookRepository.java index 52f860d..5ec3cca 100644 --- a/src/main/java/goorm/unit/booklog/domain/book/domain/BookRepository.java +++ b/src/main/java/goorm/unit/booklog/domain/book/domain/BookRepository.java @@ -1,4 +1,14 @@ package goorm.unit.booklog.domain.book.domain; + +import java.util.Optional; + public interface BookRepository { + + Book save(Book book); + + Optional findById(Long id); + + Optional findByTitleAndAuthor(String title, String author); + } diff --git a/src/main/java/goorm/unit/booklog/domain/book/infrastructure/BookRepositoryImpl.java b/src/main/java/goorm/unit/booklog/domain/book/infrastructure/BookRepositoryImpl.java index 9e9b828..1620159 100644 --- a/src/main/java/goorm/unit/booklog/domain/book/infrastructure/BookRepositoryImpl.java +++ b/src/main/java/goorm/unit/booklog/domain/book/infrastructure/BookRepositoryImpl.java @@ -1,13 +1,30 @@ package goorm.unit.booklog.domain.book.infrastructure; +import goorm.unit.booklog.domain.book.domain.Book; import org.springframework.stereotype.Repository; import goorm.unit.booklog.domain.book.domain.BookRepository; import lombok.RequiredArgsConstructor; +import java.util.Optional; + @Repository @RequiredArgsConstructor public class BookRepositoryImpl implements BookRepository { private final JpaBookRepository jpaBookRepository; + @Override + public Book save(Book book) { + return jpaBookRepository.save(book); + } + + @Override + public Optional findById(Long id) { + return jpaBookRepository.findById(id); + } + + @Override + public Optional findByTitleAndAuthor(String title, String author){ + return jpaBookRepository.findByTitleAndAuthor(title,author); + }; } diff --git a/src/main/java/goorm/unit/booklog/domain/book/infrastructure/JpaBookRepository.java b/src/main/java/goorm/unit/booklog/domain/book/infrastructure/JpaBookRepository.java index eb3dd3d..16309d6 100644 --- a/src/main/java/goorm/unit/booklog/domain/book/infrastructure/JpaBookRepository.java +++ b/src/main/java/goorm/unit/booklog/domain/book/infrastructure/JpaBookRepository.java @@ -3,6 +3,9 @@ import org.springframework.data.jpa.repository.JpaRepository; import goorm.unit.booklog.domain.book.domain.Book; +import java.util.Optional; -public interface JpaBookRepository extends JpaRepository { + +public interface JpaBookRepository extends JpaRepository { + Optional findByTitleAndAuthor(String title, String author); } diff --git a/src/main/java/goorm/unit/booklog/domain/book/presentation/response/BookResponse.java b/src/main/java/goorm/unit/booklog/domain/book/presentation/response/BookResponse.java index ca70039..204297e 100644 --- a/src/main/java/goorm/unit/booklog/domain/book/presentation/response/BookResponse.java +++ b/src/main/java/goorm/unit/booklog/domain/book/presentation/response/BookResponse.java @@ -4,6 +4,7 @@ import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; import goorm.unit.booklog.domain.book.domain.Book; +import goorm.unit.booklog.domain.file.domain.File; import goorm.unit.booklog.domain.file.presentation.response.FileResponse; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; @@ -28,6 +29,7 @@ public record BookResponse( requiredMode = NOT_REQUIRED) FileResponse file ) { + public static BookResponse from(Book book) { return BookResponse.builder() .id(book.getId()) @@ -38,13 +40,13 @@ public static BookResponse from(Book book) { .build(); } - public static BookResponse of (Long id, String title, String author, String description, String physicalPath) { + public static BookResponse of (Long id, String title, String author, String description, File file) { return BookResponse.builder() .id(id) .title(title) .author(author) .description(description) - .file(FileResponse.of(title, physicalPath)) + .file(FileResponse.from(file)) .build(); } } diff --git a/src/main/java/goorm/unit/booklog/domain/file/domain/File.java b/src/main/java/goorm/unit/booklog/domain/file/domain/File.java index 1da5632..2eafff1 100644 --- a/src/main/java/goorm/unit/booklog/domain/file/domain/File.java +++ b/src/main/java/goorm/unit/booklog/domain/file/domain/File.java @@ -33,4 +33,8 @@ public static File of(String logicalName, String filePath) { .physicalPath(filePath) .build(); } + + public void updateFile(String filePath) { + this.physicalPath = filePath; + } } diff --git a/src/main/java/goorm/unit/booklog/domain/review/application/ReviewService.java b/src/main/java/goorm/unit/booklog/domain/review/application/ReviewService.java new file mode 100644 index 0000000..7ea5de0 --- /dev/null +++ b/src/main/java/goorm/unit/booklog/domain/review/application/ReviewService.java @@ -0,0 +1,74 @@ +package goorm.unit.booklog.domain.review.application; + +import goorm.unit.booklog.domain.book.application.BookService; +import goorm.unit.booklog.domain.book.domain.Book; +import goorm.unit.booklog.domain.book.infrastructure.BookRepositoryImpl; +import goorm.unit.booklog.domain.file.domain.File; +import goorm.unit.booklog.domain.review.domain.Review; +import goorm.unit.booklog.domain.review.domain.ReviewRepository; +import goorm.unit.booklog.domain.review.domain.ReviewStatus; +import goorm.unit.booklog.domain.review.presentation.request.ReviewCreateRequest; +import goorm.unit.booklog.domain.review.presentation.response.ReviewPersistResponse; +import goorm.unit.booklog.domain.review.presentation.response.ReviewResponse; +import goorm.unit.booklog.domain.user.application.UserService; +import goorm.unit.booklog.domain.user.domain.User; +import goorm.unit.booklog.domain.review.presentation.exception.ReviewNotFoundException; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + + + +@Service +@RequiredArgsConstructor +public class ReviewService { + private final ReviewRepository reviewRepository; + private final BookRepositoryImpl bookRepository; + private final UserService userService; + private final BookService bookService; + + @Transactional + public ReviewPersistResponse createReview(ReviewCreateRequest request) { + User user = userService.me(); + File file=File.of(request.title(), request.img()); + Book book = bookService.getBook(request.bookResponse().id()); + Review review = Review.create( + request.title(), + request.content(), + file, + user, + book + ); + Long id = reviewRepository.save(review).getId(); + return ReviewPersistResponse.of(id); + } + + @Transactional + public ReviewResponse getReview(Long id) { + Review review= reviewRepository.findById(id).orElseThrow(ReviewNotFoundException::new); + return ReviewResponse.of(review); + } + + @Transactional + public ReviewResponse updateReview(Long id, ReviewCreateRequest request) { + Review review = reviewRepository.findById(id) + .orElseThrow(ReviewNotFoundException::new); + Book book = bookService.getBook(request.bookResponse().id()); + review.updateTitle(request.title()); + review.updateContent(request.content()); + review.updateBook(book); + + File file=review.getFile(); + file.updateFile(request.img()); + + return ReviewResponse.of(review); + } + + @Transactional + public void deleteReview(Long id) { + Review review = reviewRepository.findById(id).orElseThrow(ReviewNotFoundException::new); + review.updateStatus(ReviewStatus.INACTIVE); + } + +} diff --git a/src/main/java/goorm/unit/booklog/domain/review/domain/Review.java b/src/main/java/goorm/unit/booklog/domain/review/domain/Review.java new file mode 100644 index 0000000..d4e0e5b --- /dev/null +++ b/src/main/java/goorm/unit/booklog/domain/review/domain/Review.java @@ -0,0 +1,75 @@ +package goorm.unit.booklog.domain.review.domain; + +import static jakarta.persistence.GenerationType.IDENTITY; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import goorm.unit.booklog.common.domain.BaseTimeEntity; +import goorm.unit.booklog.domain.book.domain.Book; +import goorm.unit.booklog.domain.file.domain.File; +import goorm.unit.booklog.domain.user.domain.User; +import jakarta.persistence.*; +import lombok.*; + +@Getter +@Entity +@Builder +@AllArgsConstructor +@NoArgsConstructor(access= AccessLevel.PROTECTED) +public class Review extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = IDENTITY) + private Long id; + + @Column(nullable = false) + private String title; + + @Column(nullable = false, length = 2000) + private String content; + + @OneToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "file_id") + private File file; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 10) + private ReviewStatus status = ReviewStatus.ACTIVE; // 기본값 설정 + + @ManyToOne + @JoinColumn(name = "user_id",referencedColumnName = "id") // 외래 키 설정 + private User user; + + @ManyToOne + @JoinColumn(name="book_id") + private Book book; + + public static Review create( String title, String content, File file,User user, Book book ) { + return Review.builder() + .title(title) + .content(content) + .file(file) + .user(user) + .status(ReviewStatus.ACTIVE) + .book(book) + .build(); + } + + public void updateTitle( String title) { + this.title = title; + } + + public void updateContent(String content) { + this.content = content; + } + + public void updateBook( Book book ) { + this.book = book; + } + + public void updateStatus(ReviewStatus status) { + this.status = status; + } +} diff --git a/src/main/java/goorm/unit/booklog/domain/review/domain/ReviewRepository.java b/src/main/java/goorm/unit/booklog/domain/review/domain/ReviewRepository.java new file mode 100644 index 0000000..0add71d --- /dev/null +++ b/src/main/java/goorm/unit/booklog/domain/review/domain/ReviewRepository.java @@ -0,0 +1,11 @@ +package goorm.unit.booklog.domain.review.domain; + +import java.util.Optional; + +public interface ReviewRepository { + + Review save(Review review); + + Optional findById(Long id); + +} diff --git a/src/main/java/goorm/unit/booklog/domain/review/domain/ReviewStatus.java b/src/main/java/goorm/unit/booklog/domain/review/domain/ReviewStatus.java new file mode 100644 index 0000000..bb18e17 --- /dev/null +++ b/src/main/java/goorm/unit/booklog/domain/review/domain/ReviewStatus.java @@ -0,0 +1,13 @@ +package goorm.unit.booklog.domain.review.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum ReviewStatus { + ACTIVE("활성화된 게시물 입니다."), + INACTIVE("삭제된 게시물 입니다."); + + private final String description; +} diff --git a/src/main/java/goorm/unit/booklog/domain/review/infrastructure/JpaReviewRepository.java b/src/main/java/goorm/unit/booklog/domain/review/infrastructure/JpaReviewRepository.java new file mode 100644 index 0000000..424d1f7 --- /dev/null +++ b/src/main/java/goorm/unit/booklog/domain/review/infrastructure/JpaReviewRepository.java @@ -0,0 +1,7 @@ +package goorm.unit.booklog.domain.review.infrastructure; + +import goorm.unit.booklog.domain.review.domain.Review; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface JpaReviewRepository extends JpaRepository { +} diff --git a/src/main/java/goorm/unit/booklog/domain/review/infrastructure/ReviewRepositoryImpl.java b/src/main/java/goorm/unit/booklog/domain/review/infrastructure/ReviewRepositoryImpl.java new file mode 100644 index 0000000..8a8dc95 --- /dev/null +++ b/src/main/java/goorm/unit/booklog/domain/review/infrastructure/ReviewRepositoryImpl.java @@ -0,0 +1,25 @@ +package goorm.unit.booklog.domain.review.infrastructure; + +import goorm.unit.booklog.domain.review.domain.Review; +import goorm.unit.booklog.domain.review.domain.ReviewRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +@RequiredArgsConstructor +public class ReviewRepositoryImpl implements ReviewRepository { + private final JpaReviewRepository jpaReviewRepository; + + @Override + public Review save(Review review) { + return jpaReviewRepository.save(review); + } + + @Override + public Optional findById(Long id) { + return jpaReviewRepository.findById(id); + } + +} diff --git a/src/main/java/goorm/unit/booklog/domain/review/presentation/ReviewController.java b/src/main/java/goorm/unit/booklog/domain/review/presentation/ReviewController.java new file mode 100644 index 0000000..d7f96fe --- /dev/null +++ b/src/main/java/goorm/unit/booklog/domain/review/presentation/ReviewController.java @@ -0,0 +1,105 @@ +package goorm.unit.booklog.domain.review.presentation; + +import goorm.unit.booklog.common.exception.ExceptionResponse; +import goorm.unit.booklog.domain.review.application.ReviewService; +import goorm.unit.booklog.domain.review.presentation.request.ReviewCreateRequest; +import goorm.unit.booklog.domain.review.presentation.response.ReviewPersistResponse; +import goorm.unit.booklog.domain.review.presentation.response.ReviewResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import static org.springframework.http.HttpStatus.CREATED; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/reviews") +@Tag(name = "Review", description = "독후감 관리 api / 담당자 : 장선우") +public class ReviewController { + private final ReviewService reviewService; + + @Operation(summary = "독후감 생성", description = "독후감을 생성합니다.") + @ApiResponses({ + @ApiResponse( + responseCode = "201", + description = "독후감 생성 성공", + content = @Content(schema = @Schema(implementation = ReviewPersistResponse.class)) + ) + }) + @ResponseStatus(CREATED) + @PostMapping + public ResponseEntity createReview( + @Valid @RequestBody ReviewCreateRequest request) { + ReviewPersistResponse response = reviewService.createReview(request); + return ResponseEntity.status(CREATED).body(response); + } + + @Operation(summary = "독후감 조회", description = "review_id에 일치하는 독후감을 조회합니다.") + @ApiResponses({ + @ApiResponse( + responseCode = "200", + description = "독후감 조회 성공", + content = @Content(schema = @Schema(implementation = ReviewResponse.class)) + ), + @ApiResponse( + responseCode="404", + description="해당 reviewId가 존재하지 않습니다.", + content=@Content(schema=@Schema(implementation = ExceptionResponse.class)) + ) + }) + @GetMapping("/{reviewId}") + public ResponseEntity getReview( + @Parameter(description = "독후감 ID", example = "1", required = true) @PathVariable("reviewId") @Positive Long reviewId) { + ReviewResponse response = reviewService.getReview(reviewId); + return ResponseEntity.ok(response); + } + + @Operation(summary = "독후감 수정", description = "review_id에 일치하는 독후감을 수정합니다.") + @ApiResponses({ + @ApiResponse( + responseCode = "200", + description = "독후감 수정 성공", + content = @Content(schema = @Schema(implementation = ReviewResponse.class)) + ), + @ApiResponse( + responseCode="404", + description="해당 reviewId가 존재하지 않습니다.", + content=@Content(schema=@Schema(implementation = ExceptionResponse.class)) + ) + }) + @PatchMapping("/{reviewId}") + public ResponseEntity updateReview( + @Parameter(description = "독후감 ID", example = "1", required = true) @PathVariable("reviewId") @Positive Long reviewId, + @Valid @RequestBody ReviewCreateRequest request) { + ReviewResponse response = reviewService.updateReview(reviewId,request); + return ResponseEntity.ok(response); + } + + @Operation(summary = "독후감 삭제", description = "review_id에 일치하는 독후감을 삭제합니다.") + @ApiResponses({ + @ApiResponse( + responseCode = "200", + description = "독후감 삭제 성공" + ), + @ApiResponse( + responseCode="404", + description="해당 reviewId가 존재하지 않습니다.", + content=@Content(schema=@Schema(implementation = ExceptionResponse.class)) + ) + }) + @PatchMapping("/delete/{reviewId}") + public ResponseEntity deleteReview(@Parameter(description = "독후감 ID", example = "1", required = true) @PathVariable("reviewId") @Positive Long reviewId){ + reviewService.deleteReview(reviewId); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/goorm/unit/booklog/domain/review/presentation/exception/ReviewExceptionCode.java b/src/main/java/goorm/unit/booklog/domain/review/presentation/exception/ReviewExceptionCode.java new file mode 100644 index 0000000..729d4b4 --- /dev/null +++ b/src/main/java/goorm/unit/booklog/domain/review/presentation/exception/ReviewExceptionCode.java @@ -0,0 +1,22 @@ +package goorm.unit.booklog.domain.review.presentation.exception; + +import goorm.unit.booklog.common.exception.ExceptionCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.NOT_FOUND; + +@Getter +@AllArgsConstructor +public enum ReviewExceptionCode implements ExceptionCode { + REVIEW_NOT_FOUND(NOT_FOUND, "독후감을 찾을 수 없습니다."),; + + private final HttpStatus status; + private final String message; + + @Override + public String getCode() { + return this.name(); + } +} diff --git a/src/main/java/goorm/unit/booklog/domain/review/presentation/exception/ReviewNotFoundException.java b/src/main/java/goorm/unit/booklog/domain/review/presentation/exception/ReviewNotFoundException.java new file mode 100644 index 0000000..27dead8 --- /dev/null +++ b/src/main/java/goorm/unit/booklog/domain/review/presentation/exception/ReviewNotFoundException.java @@ -0,0 +1,11 @@ +package goorm.unit.booklog.domain.review.presentation.exception; + +import goorm.unit.booklog.common.exception.CustomException; + +import static goorm.unit.booklog.domain.review.presentation.exception.ReviewExceptionCode.REVIEW_NOT_FOUND; + +public class ReviewNotFoundException extends CustomException { + public ReviewNotFoundException() { + super(REVIEW_NOT_FOUND); + } +} diff --git a/src/main/java/goorm/unit/booklog/domain/review/presentation/request/ReviewCreateRequest.java b/src/main/java/goorm/unit/booklog/domain/review/presentation/request/ReviewCreateRequest.java new file mode 100644 index 0000000..409fcea --- /dev/null +++ b/src/main/java/goorm/unit/booklog/domain/review/presentation/request/ReviewCreateRequest.java @@ -0,0 +1,43 @@ +package goorm.unit.booklog.domain.review.presentation.request; + +import goorm.unit.booklog.domain.book.presentation.response.BookResponse; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +public record ReviewCreateRequest ( + @Schema(description = "제목", example = "해리포터를 읽고", requiredMode = REQUIRED) + @NotNull + @Size(max = 50, message = "독후감 제목은 50자 이내여야합니다.") + String title, + + @Schema(description = "본문", example="해리포터라는 책을 읽었다.", requiredMode = REQUIRED) + @NotNull + String content, + + @Schema(description = "썸네일 이미지", example = "", requiredMode = REQUIRED) + @NotNull + String img, + + @Schema(description = "책 정보", + example = "{" + + "\"id\": 1," + + "\"title\": \"어린 왕자\"," + + "\"author\": \"생텍쥐페리\"," + + "\"description\": \"책 어린왕자는 완전하지 못한 너와 내가 서로 어우러져 살아가는 방법에 대한 메시지를 전한다\"," + + "\"file\": {" + + " \"id\": 1," + + " \"logicalName\": \"어린 왕자 대표 이미지\"," + + " \"physicalPath\": \"https://search.shopping.naver.com/book/catalog/48953406622\"" + + "}" + + "}", + requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull + BookResponse bookResponse + +){ + +} diff --git a/src/main/java/goorm/unit/booklog/domain/review/presentation/response/ReviewPersistResponse.java b/src/main/java/goorm/unit/booklog/domain/review/presentation/response/ReviewPersistResponse.java new file mode 100644 index 0000000..680015f --- /dev/null +++ b/src/main/java/goorm/unit/booklog/domain/review/presentation/response/ReviewPersistResponse.java @@ -0,0 +1,15 @@ +package goorm.unit.booklog.domain.review.presentation.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +public record ReviewPersistResponse( + @Schema(description = "독후감 아이디", example = "1", requiredMode = REQUIRED) + Long id +) +{ + public static ReviewPersistResponse of(Long id) { + return new ReviewPersistResponse(id); + } +} diff --git a/src/main/java/goorm/unit/booklog/domain/review/presentation/response/ReviewResponse.java b/src/main/java/goorm/unit/booklog/domain/review/presentation/response/ReviewResponse.java new file mode 100644 index 0000000..5965be0 --- /dev/null +++ b/src/main/java/goorm/unit/booklog/domain/review/presentation/response/ReviewResponse.java @@ -0,0 +1,50 @@ +package goorm.unit.booklog.domain.review.presentation.response; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED; +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; +import io.swagger.v3.oas.annotations.media.Schema; +import goorm.unit.booklog.domain.review.domain.Review; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.format.DateTimeFormatter; + + +public record ReviewResponse( + @Schema( + description = "게시물 썸네일", + example = "https://example.domain.com/files/example.jpg", + requiredMode = REQUIRED) + String thumbImg, + + @Schema(description = "독후감 제목", example = "해리포터를 읽고", requiredMode = REQUIRED) + String title, + + @Schema(description = "작성자 이름", example = "홍길동", requiredMode = REQUIRED) + String name, + + @Schema(description = "독후감 본문", example = "이번 기회에 해리포터라는 책을 읽게 되었다.", requiredMode = NOT_REQUIRED) + String content, + + @Schema(description = "책 제목", example = "해리포터", requiredMode = NOT_REQUIRED) + String book_name, + + @Schema(description = "작성 일자", example = "2024-10-10", requiredMode = NOT_REQUIRED) + @DateTimeFormat(pattern = "yyyy-MM-dd") + String createdAt, + + @Schema(description = "수정 일자", example = "2024-10-10", requiredMode = NOT_REQUIRED) + @DateTimeFormat(pattern = "yyyy-MM-dd") + String updatedAt +) { + public static ReviewResponse of(Review review) { + DateTimeFormatter formatter=DateTimeFormatter.ofPattern("yyyy-MM-dd"); + return new ReviewResponse( + review.getFile().getPhysicalPath(), + review.getTitle(), + review.getUser().getName(), + review.getContent(), + review.getBook().getTitle(), + review.getCreatedAt().format(formatter), + review.getUpdatedAt().format(formatter) + ); + } +} diff --git a/src/main/java/goorm/unit/booklog/domain/user/application/UserService.java b/src/main/java/goorm/unit/booklog/domain/user/application/UserService.java index fd85cf9..c7dbcbb 100644 --- a/src/main/java/goorm/unit/booklog/domain/user/application/UserService.java +++ b/src/main/java/goorm/unit/booklog/domain/user/application/UserService.java @@ -1,6 +1,7 @@ package goorm.unit.booklog.domain.user.application; import goorm.unit.booklog.domain.user.domain.User; +import goorm.unit.booklog.domain.user.presentation.exception.UserNotAuthenticatedException; import goorm.unit.booklog.domain.user.presentation.request.UserCreateRequest; import goorm.unit.booklog.domain.user.domain.UserRepository; import goorm.unit.booklog.domain.user.presentation.exception.UserIdDuplicatedException; @@ -8,6 +9,8 @@ import goorm.unit.booklog.domain.user.presentation.response.UserPersistResponse; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; @@ -40,5 +43,15 @@ public User getUserById(String id) { .orElseThrow(UserNotFoundException::new); } + public User me() { + try { + Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + String id= ((UserDetails)principal).getUsername(); + return getUserById(id); + } catch (Exception e) { + throw new UserNotAuthenticatedException(); + } + } + } diff --git a/src/main/java/goorm/unit/booklog/domain/user/domain/User.java b/src/main/java/goorm/unit/booklog/domain/user/domain/User.java index a410b0d..c4a4022 100644 --- a/src/main/java/goorm/unit/booklog/domain/user/domain/User.java +++ b/src/main/java/goorm/unit/booklog/domain/user/domain/User.java @@ -7,19 +7,13 @@ import goorm.unit.booklog.common.domain.BaseTimeEntity; import goorm.unit.booklog.domain.book.domain.Book; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.ManyToMany; +import goorm.unit.booklog.domain.review.domain.Review; +import jakarta.persistence.*; import lombok.*; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; - @Getter @Entity @@ -39,6 +33,7 @@ public class User extends BaseTimeEntity implements UserDetails { @Column(nullable=false) private String password; + //사용자 한명은 책 여러개 가능. 책 한개는 사용자 여러명 가능 @ManyToMany @JoinTable( name = "user_books", @@ -47,6 +42,10 @@ public class User extends BaseTimeEntity implements UserDetails { ) private List books = new ArrayList<>(); + //사용자 한명은 리뷰 여러개 가능. 리뷰 한개는 사용자 한명 가능. + @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) + private List reviews = new ArrayList<>(); + public static User create(String id, String name, String password) { return User.builder() .id(id) diff --git a/src/main/java/goorm/unit/booklog/domain/user/presentation/exception/UserNotAuthenticatedException.java b/src/main/java/goorm/unit/booklog/domain/user/presentation/exception/UserNotAuthenticatedException.java new file mode 100644 index 0000000..54a47c6 --- /dev/null +++ b/src/main/java/goorm/unit/booklog/domain/user/presentation/exception/UserNotAuthenticatedException.java @@ -0,0 +1,13 @@ +package goorm.unit.booklog.domain.user.presentation.exception; + + +import goorm.unit.booklog.common.exception.CustomException; + +import static goorm.unit.booklog.domain.user.presentation.exception.UserExceptionCode.USER_NOT_AUTHENTICATED; + +public class UserNotAuthenticatedException extends CustomException { + + public UserNotAuthenticatedException() { + super(USER_NOT_AUTHENTICATED); + } +}