ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,26 @@
+package com.frame2.server.core.board.infrastructure;
+
+import com.frame2.server.core.board.domain.ProductReview;
+import com.frame2.server.core.support.exception.DomainException;
+import com.frame2.server.core.support.exception.ExceptionType;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface ProductReviewRepository extends JpaRepository<ProductReview, Long> {
+    
+    default ProductReview findOne(Long id) {
+        return findById(id).orElseThrow(() -> new DomainException(ExceptionType.PRODUCT_REVIEW_NOT_FOUND));
+    }
+
+    @Override
+    @Query("SELECT pr FROM ProductReview pr WHERE pr.id = :id and pr.deleteStatus = false")
+    Optional<ProductReview> findById(@Param("id") Long id);
+
+    // deleteStatus가 false이고, saleProduct의 id가 주어진 값인 데이터를 조회
+    @Query("SELECT pr FROM ProductReview pr WHERE pr.deleteStatus = false AND pr.saleProduct.id = :saleProductId")
+    List<ProductReview> findAllBySaleProductId(@Param("saleProductId") Long saleProductId);
+}

리뷰: 삭제된 상품 리뷰의 경우 조회를 막아야 하지 않나요?
@@ -1,9 +1,13 @@
 package com.hanahakdangserver.faq.service;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
 
 import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -31,32 +35,45 @@ public class FaqService {
   private final LectureRepository lectureRepository;
   private final UserRepository userRepository;
 
+  private static final int PAGE_SIZE = 3; // 페이지 크기 상수 정의
+
+  /**
+   * FAQ + Answer를 합쳐서 페이지네이션 처리
+   */
+  public List<FaqResponse> getPaginatedFaqs(Long lectureId, int page) {
+    Pageable pageable = PageRequest.of(page, PAGE_SIZE); // Service에서 Pageable 생성
+    Page<Faq> faqPage = faqRepository.findByLectureId(lectureId, pageable);
+
+    // FAQ와 답변 데이터를 DTO로 변환
+    return faqPage.stream()
+        .map(faq -> {
+          List<Answer> answers = answerRepository.findByFaqId(faq.getId());
+          return FaqMapper.toDto(faq, answers);
+        })
+        .toList();
+  }
+
   @Transactional
   public FaqResponse createFaq(Long lectureId, FaqRequest request, Long userId) {
+    // 사용자 조회
     User user = userRepository.findById(userId)
         .orElseThrow(USER_NOT_FOUND::createResponseStatusException);
 
+    // 강의 조회
     Lecture lecture = lectureRepository.findById(lectureId)
         .orElseThrow(LECTURE_NOT_FOUND::createResponseStatusException);
 
+    // Faq 엔티티 생성 및 저장
     Faq faq = Faq.builder()
         .lecture(lecture)
+        .user(user) // user 설정
         .content(request.getContent())
         .build();
 
     Faq savedFaq = faqRepository.save(faq);
-    return FaqMapper.toDto(savedFaq, List.of());
-  }
-
-  public List<FaqResponse> getFaqsByLectureId(Long lectureId) {
-    List<Faq> faqs = faqRepository.findByLectureId(lectureId);
 
-    return faqs.stream()
-        .map(faq -> {
-          List<Answer> answers = answerRepository.findByFaqId(faq.getId());
-          return FaqMapper.toDto(faq, answers);
-        })
-        .collect(Collectors.toList());
+    // DTO 변환 후 반환
+    return FaqMapper.toDto(savedFaq, List.of());
   }
 
   @Transactional
@@ -70,5 +87,4 @@ public void deleteFaq(Long faqId, Long userId) {
 
     faqRepository.delete(faq);
   }
-
 }

리뷰: `List<FaqResponse>`가 아니고 `List<Object>`라 한 이유가 있나요?
@@ -47,11 +47,15 @@ public class User extends BaseTimeEntity {
     @Enumerated(value = EnumType.STRING)
     private LoginMethod loginMethod = LoginMethod.LOCAL;
 
-    @OneToOne(cascade = CascadeType.ALL,orphanRemoval = true)
+    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
     @JoinColumn(name = "account_id")
     private Account account;
 
-    public User() {}
+    @Column(nullable = false, columnDefinition = "INT DEFAULT 0")
+    private int point;
+
+    public User() {
+    }
 
     public User(String email, String nickname, String password, String profileImage, String address, String phoneNumber, Role role) {
         this.email = email;
@@ -85,4 +89,8 @@ public User(String nickname, String email) {
         this.loginMethod = LoginMethod.KAKAO;
     }
 
+    public void addPoint(int point) {
+        this.point += point;
+    }
+
 }
\ No newline at end of file

리뷰: _:hammer_and_wrench: Refactor suggestion_

**포인트 증가 메소드에 유효성 검사가 필요합니다.**

현재 구현은 기본적인 기능은 제공하지만, 다음과 같은 보완이 필요합니다:
- 음수 포인트 검증
- 최대 포인트 제한
- 포인트 증가 로깅


다음과 같은 개선을 제안드립니다:

```diff
+    private static final int MAX_POINT = 1000000; // 100만 포인트 제한
+
     public void addPoint(int point) {
+        if (point < 0) {
+            throw new IllegalArgumentException("포인트는 음수일 수 없습니다");
+        }
+        if (this.point + point > MAX_POINT) {
+            throw new IllegalArgumentException("최대 포인트 제한을 초과했습니다");
+        }
         this.point += point;
     }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    private static final int MAX_POINT = 1000000; // 100만 포인트 제한

    public void addPoint(int point) {
        if (point < 0) {
            throw new IllegalArgumentException("포인트는 음수일 수 없습니다");
        }
        if (this.point + point > MAX_POINT) {
            throw new IllegalArgumentException("최대 포인트 제한을 초과했습니다");
        }
        this.point += point;
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
@@ -8,6 +8,7 @@
 import com.doubleowner.revibe.domain.review.entity.Review;
 import com.doubleowner.revibe.domain.review.repository.ReviewRepository;
 import com.doubleowner.revibe.domain.user.entity.User;
+import com.doubleowner.revibe.domain.user.repository.UserRepository;
 import com.doubleowner.revibe.global.config.auth.UserDetailsImpl;
 import com.doubleowner.revibe.global.exception.CommonException;
 import com.doubleowner.revibe.global.exception.errorCode.ErrorCode;
@@ -28,17 +29,23 @@
 public class ReviewService {
     private final ReviewRepository reviewRepository;
     private final ExecutionRepository executionRepository;
+    private final UserRepository userRepository;
     private final S3Uploader s3Uploader;
 
+    private static final int TEXT_ONLY_POINT=100;
+    private static final int TEXT_IMAGE_POINT=500;
+
     @Transactional
     public ReviewResponseDto write(ReviewRequestDto reviewRequestDto, MultipartFile file, User user) {
 
+
         Execution execution = executionRepository.findExecutionById(reviewRequestDto.getExecutionId(), reviewRequestDto.getPaymentId(), user.getEmail())
                 .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_VALUE, "내역을 찾을 수 없습니다"));
         String image = null;
-
+        int point=TEXT_ONLY_POINT;
         if (file != null) {
             image = uploadImage(file);
+            point=TEXT_IMAGE_POINT;
         }
 
 
@@ -53,6 +60,9 @@ public ReviewResponseDto write(ReviewRequestDto reviewRequestDto, MultipartFile
                 .build();
 
         Review save = reviewRepository.save(review);
+        user.addPoint(point);
+        userRepository.save(user);
+
 
         return toDto(save);
     }

리뷰: _:hammer_and_wrench: Refactor suggestion_

**포인트 적립 로깅 및 예외 처리가 필요합니다.**

포인트 적립 시 감사(audit) 목적의 로깅과 실패 시의 예외 처리가 필요합니다.


다음과 같은 개선을 제안드립니다:

```diff
+    @Transactional
+    private void awardPoints(User user, int point) {
+        try {
             user.addPoint(point);
             userRepository.save(user);
+            log.info("포인트 적립 완료 - 사용자: {}, 포인트: {}", user.getEmail(), point);
+        } catch (Exception e) {
+            log.error("포인트 적립 실패 - 사용자: {}, 포인트: {}", user.getEmail(), point, e);
+            throw new CommonException(ErrorCode.FAILED_POINT_AWARD, "포인트 적립에 실패했습니다");
+        }
+    }
```

그리고 write 메소드에서는 다음과 같이 호출:

```diff
-        user.addPoint(point);
-        userRepository.save(user);
+        awardPoints(user, point);
```


> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated comment by CodeRabbit -->
@@ -8,6 +8,7 @@
 import com.doubleowner.revibe.domain.review.entity.Review;
 import com.doubleowner.revibe.domain.review.repository.ReviewRepository;
 import com.doubleowner.revibe.domain.user.entity.User;
+import com.doubleowner.revibe.domain.user.repository.UserRepository;
 import com.doubleowner.revibe.global.config.auth.UserDetailsImpl;
 import com.doubleowner.revibe.global.exception.CommonException;
 import com.doubleowner.revibe.global.exception.errorCode.ErrorCode;
@@ -28,17 +29,23 @@
 public class ReviewService {
     private final ReviewRepository reviewRepository;
     private final ExecutionRepository executionRepository;
+    private final UserRepository userRepository;
     private final S3Uploader s3Uploader;
 
+    private static final int TEXT_ONLY_POINT=100;
+    private static final int TEXT_IMAGE_POINT=500;
+
     @Transactional
     public ReviewResponseDto write(ReviewRequestDto reviewRequestDto, MultipartFile file, User user) {
 
+
         Execution execution = executionRepository.findExecutionById(reviewRequestDto.getExecutionId(), reviewRequestDto.getPaymentId(), user.getEmail())
                 .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_VALUE, "내역을 찾을 수 없습니다"));
         String image = null;
-
+        int point=TEXT_ONLY_POINT;
         if (file != null) {
             image = uploadImage(file);
+            point=TEXT_IMAGE_POINT;
         }
 
 
@@ -53,6 +60,9 @@ public ReviewResponseDto write(ReviewRequestDto reviewRequestDto, MultipartFile
                 .build();
 
         Review save = reviewRepository.save(review);
+        user.addPoint(point);
+        userRepository.save(user);
+
 
         return toDto(save);
     }

리뷰: _:warning: Potential issue_

**포인트 적립 실패 시의 처리가 필요합니다.**

현재 구현에서 포인트 적립 실패 시의 예외 처리가 누락되어 있습니다.


다음과 같은 개선을 제안드립니다:

```diff
     int point=TEXT_ONLY_POINT;
     if (file != null) {
-        image = uploadImage(file);
-        point=TEXT_IMAGE_POINT;
+        try {
+            image = uploadImage(file);
+            point=TEXT_IMAGE_POINT;
+        } catch (Exception e) {
+            // 이미지 업로드 실패 시에도 텍스트 리뷰 포인트는 지급
+            log.warn("이미지 업로드 실패로 인해 기본 포인트만 지급됩니다", e);
+        }
     }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        int point=TEXT_ONLY_POINT;
        if (file != null) {
            try {
                image = uploadImage(file);
                point=TEXT_IMAGE_POINT;
            } catch (Exception e) {
                // 이미지 업로드 실패 시에도 텍스트 리뷰 포인트는 지급
                log.warn("이미지 업로드 실패로 인해 기본 포인트만 지급됩니다", e);
            }
        }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
@@ -0,0 +1,23 @@
+package kdt.prgrms.kazedon.everevent.common;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import kdt.prgrms.kazedon.everevent.domain.user.UserType;
+import org.springframework.security.test.context.support.WithSecurityContext;
+
+@Retention(RetentionPolicy.RUNTIME)
+@WithSecurityContext(factory = WithMockAuthUserSecurityContextFactory.class)
+public @interface WithMockAuthUser {
+
+  long id() default 1L;
+
+  String email() default "password";
+
+  String password() default "Password123!";
+
+  String nickname() default "kazedon";
+
+  String location() default "경기도 남양주시 와부읍";
+
+  UserType roles() default UserType.ROLE_USER;
+}

리뷰: Long 으로 하는게 더 좋아보이네요 :)
@@ -0,0 +1,23 @@
+package kdt.prgrms.kazedon.everevent.common;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import kdt.prgrms.kazedon.everevent.domain.user.UserType;
+import org.springframework.security.test.context.support.WithSecurityContext;
+
+@Retention(RetentionPolicy.RUNTIME)
+@WithSecurityContext(factory = WithMockAuthUserSecurityContextFactory.class)
+public @interface WithMockAuthUser {
+
+  long id() default 1L;
+
+  String email() default "password";
+
+  String password() default "Password123!";
+
+  String nickname() default "kazedon";
+
+  String location() default "경기도 남양주시 와부읍";
+
+  UserType roles() default UserType.ROLE_USER;
+}

리뷰: 앗 그러네요! 감사함니당 👍👍
@@ -0,0 +1,68 @@
+package com.example.review.controller;
+
+import com.example.review.dto.request.ReviewCreateRequestDto;
+import com.example.review.dto.request.ReviewUpdateRequestDto;
+import com.example.review.dto.response.ReviewResponseSimpleDto;
+import com.example.review.dto.response.ReviewResponseDetailDto;
+import com.example.review.service.ReviewService;
+import com.example.utils.AuthUtil;
+import com.example.utils.PageQuery;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/reviews")
+@RequiredArgsConstructor
+public class ReviewController {
+
+    private final ReviewService reviewService;
+
+    // 리뷰 생성
+    @PostMapping
+    public ResponseEntity<ReviewResponseDetailDto> create(
+            @RequestBody ReviewCreateRequestDto requestDto
+    ) {
+        ReviewResponseDetailDto responseDto = reviewService.create(requestDto);
+        return ResponseEntity.status(HttpStatus.CREATED).body(responseDto);
+    }
+
+    // 리뷰 수정
+    @PatchMapping("/{id}")
+    public ResponseEntity<ReviewResponseDetailDto> update(
+            @PathVariable int id,
+            @RequestBody ReviewUpdateRequestDto requestDto
+    ) {
+        ReviewResponseDetailDto responseDto = reviewService.update(id, requestDto);
+        return ResponseEntity.ok(responseDto);
+    }
+
+    // 특정 상점의 리뷰 페이지 조회
+    @GetMapping("/shops/{shopId}")
+    public ResponseEntity<Page<ReviewResponseSimpleDto>> findAllReviewByShopId(
+            @PathVariable int shopId, @RequestParam int page) {
+        Pageable pageable = PageRequest.of(page, 10, Sort.by(Sort.Direction.DESC, "updatedAt"));
+        Page<ReviewResponseSimpleDto> reviews = reviewService.getReviewsByShopId(shopId, pageable);
+        return ResponseEntity.ok(reviews);
+    }
+
+    // 특정 유저의 모든 리뷰 조회
+    @GetMapping
+    public ResponseEntity<com.example.utils.Page<ReviewResponseSimpleDto>> findAllReviewByUserId(PageQuery pageQuery){
+        return ResponseEntity.ok(reviewService.findAllByUserId(pageQuery));
+    }
+
+    // 리뷰 삭제
+    @DeleteMapping("/{reviewId}")
+    public ResponseEntity<Void> delete(@PathVariable int reviewId) {
+        reviewService.delete(reviewId);
+        return ResponseEntity.noContent().build();
+    }
+}

리뷰: 각 URL 이 RESTful 하지 않습니다.
통일된 URL 을 사용하거나, Controller 분리가 필요해보입니다.
@@ -1,16 +1,18 @@
 package com.example.review.entity;
+
+import com.example.common.entity.BaseEntity;
 import com.example.order.entity.Order;
+import com.example.review.dto.request.ReviewUpdateRequestDto;
 import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
 import lombok.Getter;
-import org.springframework.data.annotation.CreatedDate;
-import org.springframework.data.annotation.LastModifiedDate;
-
-import java.time.LocalDateTime;
+import lombok.NoArgsConstructor;
 
 @Getter
-@Table(name = "reviews")
 @Entity
-public class Review {
+@NoArgsConstructor
+@AllArgsConstructor
+public class Review extends BaseEntity {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private int id;
@@ -20,15 +22,32 @@ public class Review {
     private Order order;
 
     @Column(nullable = false)
-    private Integer rating;
+    private int rating;
+
+    @Column(nullable = false, length = 50)
+    private String description;
 
-    @Column(length = 50)
-    private String content;
+    public Review(Order order, Integer rating, String description) {
+        this.order = order;
+        this.rating = rating;
+        this.description = description;
+    }
 
-    @Column(updatable = false)
-    @CreatedDate
-    private LocalDateTime createdAt;
+    public Review partialUpdate(ReviewUpdateRequestDto dto){
+        this.rating = dto.rating();
+        this.description = dto.description();
+        return this;
+    }
 
-    @LastModifiedDate
-    private LocalDateTime updatedAt;
+    public static Review from(
+            Order order,
+            int rating,
+            String description
+    ) {
+        return new Review(
+                order,
+                rating,
+                description
+        );
+    }
 }

리뷰: 해당 Entity 의 각 Setter 사용 지양부탁드립니다.
@@ -1,16 +1,18 @@
 package com.example.review.entity;
+
+import com.example.common.entity.BaseEntity;
 import com.example.order.entity.Order;
+import com.example.review.dto.request.ReviewUpdateRequestDto;
 import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
 import lombok.Getter;
-import org.springframework.data.annotation.CreatedDate;
-import org.springframework.data.annotation.LastModifiedDate;
-
-import java.time.LocalDateTime;
+import lombok.NoArgsConstructor;
 
 @Getter
-@Table(name = "reviews")
 @Entity
-public class Review {
+@NoArgsConstructor
+@AllArgsConstructor
+public class Review extends BaseEntity {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private int id;
@@ -20,15 +22,32 @@ public class Review {
     private Order order;
 
     @Column(nullable = false)
-    private Integer rating;
+    private int rating;
+
+    @Column(nullable = false, length = 50)
+    private String description;
 
-    @Column(length = 50)
-    private String content;
+    public Review(Order order, Integer rating, String description) {
+        this.order = order;
+        this.rating = rating;
+        this.description = description;
+    }
 
-    @Column(updatable = false)
-    @CreatedDate
-    private LocalDateTime createdAt;
+    public Review partialUpdate(ReviewUpdateRequestDto dto){
+        this.rating = dto.rating();
+        this.description = dto.description();
+        return this;
+    }
 
-    @LastModifiedDate
-    private LocalDateTime updatedAt;
+    public static Review from(
+            Order order,
+            int rating,
+            String description
+    ) {
+        return new Review(
+                order,
+                rating,
+                description
+        );
+    }
 }

리뷰: DTO 내용 변경 시, Entity 일관성도 해칠 가능성이 있습니다.
각 내용 추출하여 인자로 받아주시기 바랍니다.
@@ -0,0 +1,68 @@
+package com.example.review.controller;
+
+import com.example.review.dto.request.ReviewCreateRequestDto;
+import com.example.review.dto.request.ReviewUpdateRequestDto;
+import com.example.review.dto.response.ReviewResponseSimpleDto;
+import com.example.review.dto.response.ReviewResponseDetailDto;
+import com.example.review.service.ReviewService;
+import com.example.utils.AuthUtil;
+import com.example.utils.PageQuery;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/reviews")
+@RequiredArgsConstructor
+public class ReviewController {
+
+    private final ReviewService reviewService;
+
+    // 리뷰 생성
+    @PostMapping
+    public ResponseEntity<ReviewResponseDetailDto> create(
+            @RequestBody ReviewCreateRequestDto requestDto
+    ) {
+        ReviewResponseDetailDto responseDto = reviewService.create(requestDto);
+        return ResponseEntity.status(HttpStatus.CREATED).body(responseDto);
+    }
+
+    // 리뷰 수정
+    @PatchMapping("/{id}")
+    public ResponseEntity<ReviewResponseDetailDto> update(
+            @PathVariable int id,
+            @RequestBody ReviewUpdateRequestDto requestDto
+    ) {
+        ReviewResponseDetailDto responseDto = reviewService.update(id, requestDto);
+        return ResponseEntity.ok(responseDto);
+    }
+
+    // 특정 상점의 리뷰 페이지 조회
+    @GetMapping("/shops/{shopId}")
+    public ResponseEntity<Page<ReviewResponseSimpleDto>> findAllReviewByShopId(
+            @PathVariable int shopId, @RequestParam int page) {
+        Pageable pageable = PageRequest.of(page, 10, Sort.by(Sort.Direction.DESC, "updatedAt"));
+        Page<ReviewResponseSimpleDto> reviews = reviewService.getReviewsByShopId(shopId, pageable);
+        return ResponseEntity.ok(reviews);
+    }
+
+    // 특정 유저의 모든 리뷰 조회
+    @GetMapping
+    public ResponseEntity<com.example.utils.Page<ReviewResponseSimpleDto>> findAllReviewByUserId(PageQuery pageQuery){
+        return ResponseEntity.ok(reviewService.findAllByUserId(pageQuery));
+    }
+
+    // 리뷰 삭제
+    @DeleteMapping("/{reviewId}")
+    public ResponseEntity<Void> delete(@PathVariable int reviewId) {
+        reviewService.delete(reviewId);
+        return ResponseEntity.noContent().build();
+    }
+}

리뷰: Page 반환 부탁드립니다.