Skip to content
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

9조 BE 코드리뷰 5회차 #96

Merged
merged 39 commits into from
Nov 10, 2024
Merged
Changes from 20 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
814bb5f
feat:[#79]- add feed
yooonwodyd Nov 3, 2024
3b565d1
Merge pull request #80 from yooonwodyd/weekly
yooonwodyd Nov 3, 2024
daa049c
Merge pull request #81 from kakao-tech-campus-2nd-step3/Master
yooonwodyd Nov 3, 2024
a200558
Merge pull request #82 from kakao-tech-campus-2nd-step3/develop
yooonwodyd Nov 3, 2024
f011449
feat:[#84]- refact jwt
yooonwodyd Nov 6, 2024
1f79ee2
feat:[#84]- add userImage
yooonwodyd Nov 6, 2024
7b1104f
feat:[#84]- refact User
yooonwodyd Nov 6, 2024
6298063
feat:[#84]- refact dto
yooonwodyd Nov 6, 2024
0cc6353
feat : [#85] ApiResponse 통일 DTO 생성
jupyter471 Nov 7, 2024
5a789bb
feat : [#85] 예외, 성공 경우 ApiResponse 사용 예시
jupyter471 Nov 7, 2024
f01fcb6
Merge pull request #86 from jupyter471/weekly
yooonwodyd Nov 7, 2024
1a7da80
Merge branch 'weekly' into weekly
yooonwodyd Nov 7, 2024
1c14c81
feat:[#84]- refact Controller
yooonwodyd Nov 7, 2024
43c1309
feat:[#84]- add sql
yooonwodyd Nov 7, 2024
b4e882f
Merge pull request #87 from yooonwodyd/weekly
yooonwodyd Nov 7, 2024
3136eae
Merge pull request #88 from kakao-tech-campus-2nd-step3/weekly
yooonwodyd Nov 7, 2024
eff003f
Merge pull request #89 from kakao-tech-campus-2nd-step3/develop
yooonwodyd Nov 7, 2024
e0c5699
feat:[#84]- add sql
yooonwodyd Nov 8, 2024
03833d3
feat : [#85] ReviewController 응답 형식 수정
jupyter471 Nov 8, 2024
d2b9d08
refactor:[#84]- refact Artist API
yooonwodyd Nov 8, 2024
30e6488
feat:[#84]- add data.sql
yooonwodyd Nov 8, 2024
880f720
feat : [#62] 한 상품에 대한 리뷰 조회 기능
jupyter471 Nov 8, 2024
093b1db
#83 chore(application.yaml): redis 설정 추가
sim-mer Nov 8, 2024
91ea66e
#83 chore(build.gradle): redis 의존성 추가
sim-mer Nov 8, 2024
a3bc59d
#83 feat: 작가 검색 시 팔로우 여부 응답에 추가
sim-mer Nov 8, 2024
b4cdd1e
#83 test(search): 작가 검색 기능 변경에 따른 테스트 코드 수정
sim-mer Nov 8, 2024
27be18f
#83 feat(product): 인기 검색어 분석을 위한 검색 기록 저장
sim-mer Nov 8, 2024
42ca391
#83 chore(config): Redis Config 작성
sim-mer Nov 8, 2024
877ea6f
#83 feat(search): 인기 검색어 스케줄링 및 조회 기능 작성
sim-mer Nov 8, 2024
5c9aaac
#83 feat(user): following 작가 id 검색 메서드 추가
sim-mer Nov 8, 2024
21db729
feat : [#62] 작가별, 상품별 리뷰 조회
jupyter471 Nov 8, 2024
e423ca5
feat : [#93] 상품 좋아요 등록 및 삭제
jupyter471 Nov 8, 2024
c2ece8d
Merge pull request #90 from yooonwodyd/weekly
yooonwodyd Nov 8, 2024
0994938
Merge pull request #92 from jupyter471/weekly
yooonwodyd Nov 8, 2024
f1e321a
Merge branch 'weekly' into feat/#83-popular-search
yooonwodyd Nov 8, 2024
59daef7
Merge pull request #91 from kakao-tech-campus-2nd-step3/feat/#83-popu…
yooonwodyd Nov 8, 2024
73be0c2
Merge pull request #94 from kakao-tech-campus-2nd-step3/weekly
yooonwodyd Nov 8, 2024
ccc701b
Merge pull request #95 from kakao-tech-campus-2nd-step3/develop
yooonwodyd Nov 8, 2024
f0bd608
fix: Redis 환경변수 주입 경로 수정
sim-mer Nov 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -57,6 +57,8 @@ dependencies {
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

implementation 'org.springframework.boot:spring-boot-starter-data-redis'

// Spring docs
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0'

4 changes: 2 additions & 2 deletions src/main/java/com/helpmeCookies/Step3Application.java
Original file line number Diff line number Diff line change
@@ -2,10 +2,10 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class Step3Application {

public static void main(String[] args) {
Original file line number Diff line number Diff line change
@@ -8,7 +8,8 @@
@Getter
public enum SuccessCode {
OK(200, "OK"),
CREATED_SUCCESS(201, "저장에 성공했습니다");
CREATED_SUCCESS(201, "저장에 성공했습니다"),
NO_CONTENT(204, "삭제에 성공했습니다.");

private final int code;
private final String message;
40 changes: 40 additions & 0 deletions src/main/java/com/helpmeCookies/global/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.helpmeCookies.global.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableRedisRepositories
public class RedisConfig {

@Value("${spring.redis.host}")
private String host;

@Value("${spring.redis.port}")
private int port;

@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lettuce도 좋지만, Redission을 사용하시면 분산락 기능도 사용하실수 있어요~
분산락 기능이 필요없으시다면 Lettuce로도 충분한것 같습니다. 다만 분산락은 애플리케이션에서 많이 활용되는 측면이 있어서
나중에 한번 공부해보시면 좋을것 같습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 한번 공부해보겠습니다!

}

@Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<byte[], byte[]> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}



}
Original file line number Diff line number Diff line change
@@ -13,8 +13,8 @@
public class GlobalExceptionHandler {

@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ApiResponse<Void>> handleResourceNotFoundException() {
return ResponseEntity.badRequest().body(ApiResponse.error(HttpStatus.BAD_REQUEST,"해당 리소스를 찾을 수 없습니다."));
public ResponseEntity<ApiResponse<Void>> handleResourceNotFoundException(ResourceNotFoundException e) {
return ResponseEntity.badRequest().body(ApiResponse.error(HttpStatus.BAD_REQUEST, e.getMessage()));
}

@ExceptionHandler(DuplicateRequestException.class)
Original file line number Diff line number Diff line change
@@ -75,8 +75,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
"/v1/**",
"swagger-ui/**",
"/test/signup",
"/v1/artist",
"/v1/artists"
"/v1/artists/**"
).permitAll()
.anyRequest().authenticated()
).exceptionHandling((exception) -> exception
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@

import com.helpmeCookies.global.ApiResponse.ApiResponse;
import com.helpmeCookies.global.ApiResponse.SuccessCode;
import com.helpmeCookies.global.jwt.JwtUser;
import com.helpmeCookies.product.dto.ImageUpload;
import static com.helpmeCookies.product.util.SortUtil.convertProductSort;

@@ -12,12 +13,18 @@
import com.helpmeCookies.product.dto.ProductResponse;
import com.helpmeCookies.product.entity.Product;
import com.helpmeCookies.product.service.ProductImageService;
import com.helpmeCookies.product.service.ProductLikeService;
import com.helpmeCookies.product.service.ProductService;
import com.helpmeCookies.product.util.ProductSort;
import com.helpmeCookies.review.dto.ReviewResponse;
import com.helpmeCookies.review.service.ReviewService;
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.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@@ -30,6 +37,8 @@ public class ProductController implements ProductApiDocs {

private final ProductService productService;
private final ProductImageService productImageService;
private final ReviewService reviewService;
private final ProductLikeService productLikeService;

@PostMapping("/successTest")
public ResponseEntity<ApiResponse<Void>> saveTest() {
@@ -98,4 +107,28 @@ public ResponseEntity<ProductPage.Paging> getProductsWithRandomPaging(
Pageable pageable = PageRequest.of(0, size);
return ResponseEntity.ok(productService.getProductsWithRandomPaging(pageable));
}

@GetMapping("/{productId}/reviews")
public ResponseEntity<ApiResponse<Page<ReviewResponse>>> getAllReviewsByProduct(
@PathVariable("productId") Long productId,
@PageableDefault(size = 7) Pageable pageable) {
return ResponseEntity.ok(ApiResponse.success(SuccessCode.OK,reviewService.getAllReviewByProduct(productId,pageable)));
}

@PostMapping("/{productId}/likes")
public ResponseEntity<ApiResponse<Void>> postProductLike(
@PathVariable("productId") Long productId,
@AuthenticationPrincipal JwtUser jwtUser) {
productLikeService.productLike(jwtUser.getId(), productId);
return ResponseEntity.ok(ApiResponse.success(SuccessCode.OK));
}

@DeleteMapping("/{productId}/likes")
public ResponseEntity<ApiResponse<Void>> deleteProductlike(
@PathVariable("productId") Long productId,
@AuthenticationPrincipal JwtUser jwtUser) {
productLikeService.deleteProductLike(jwtUser.getId(), productId);

return ResponseEntity.ok(ApiResponse.success(SuccessCode.NO_CONTENT));
}
}
8 changes: 7 additions & 1 deletion src/main/java/com/helpmeCookies/product/entity/Like.java
Original file line number Diff line number Diff line change
@@ -8,7 +8,6 @@
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;

@Entity
@@ -25,4 +24,11 @@ public class Like {
@ManyToOne
@JoinColumn(name = "product_id")
private Product product;

protected Like(){};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


public Like(User user, Product product) {
this.user = user;
this.product = product;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.helpmeCookies.product.repository;

import com.helpmeCookies.product.entity.Like;
import com.helpmeCookies.product.entity.Product;
import com.helpmeCookies.user.entity.User;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ProductLikeRepository extends JpaRepository<Like, Long> {
Optional<Like> findDistinctFirstByUserAndProduct(User user, Product product);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.helpmeCookies.product.service;

import com.helpmeCookies.product.entity.Like;
import com.helpmeCookies.product.entity.Product;
import com.helpmeCookies.product.repository.ProductLikeRepository;
import com.helpmeCookies.product.repository.ProductRepository;
import com.helpmeCookies.user.entity.User;
import com.helpmeCookies.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class ProductLikeService {
private final ProductLikeRepository productLikeRepository;
private final UserRepository userRepository;
private final ProductRepository productRepository;

@Transactional
public void productLike(Long userId, Long productId) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like를 동시에 두번 요청하면 어떻게 될까요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아! 이미 좋아요 되었을 때 예외처리가 안되어 있네요..!! 감사합니다!! 관련 처리 반영하겠습니다

User user = userRepository.findById(userId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 유저Id입니다." + userId));
Product product = productRepository.findById(productId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 상품Id입니다." + productId));
Like like = new Like(user, product);
productLikeRepository.save(like);
}

@Transactional
public void deleteProductLike(Long userId, Long productId) {
User user = userRepository.findById(userId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 유저Id입니다." + userId));
Product product = productRepository.findById(productId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 상품Id입니다." + productId));

Like like = productLikeRepository.findDistinctFirstByUserAndProduct(user,product).orElseThrow(() -> new IllegalArgumentException("존재하지 않는 상품 찜 항목입니다."));
productLikeRepository.delete(like);
}
}
Original file line number Diff line number Diff line change
@@ -10,6 +10,8 @@
import com.helpmeCookies.product.dto.ProductPage;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
@@ -20,10 +22,17 @@ public class ProductService {
private final ProductRepository productRepository;
private final ProductImageRepository productImageRepository;
private final ArtistInfoRepository artistInfoRepository;
private final RedisTemplate<String, Object> redisTemplate;

@Transactional(readOnly = true)
public ProductPage.Paging getProductsByPage(String query, Pageable pageable) {
var productPage = productRepository.findByNameWithIdx(query, pageable);

ZSetOperations<String, Object> zSet = redisTemplate.opsForZSet();
var zSetKey = "search:" + query;
var time = System.currentTimeMillis();
zSet.add(zSetKey, String.valueOf(time), time);

return ProductPage.Paging.from(productPage);
}

Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.helpmeCookies.review.controller;

import com.helpmeCookies.global.ApiResponse.ApiResponse;
import com.helpmeCookies.global.ApiResponse.SuccessCode;
import com.helpmeCookies.review.dto.ReviewRequest;
import com.helpmeCookies.review.dto.ReviewResponse;
import com.helpmeCookies.review.entity.Review;
import com.helpmeCookies.review.service.ReviewService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@@ -21,25 +24,29 @@ public class ReviewController {

private final ReviewService reviewService;

//TODO 감상평 삭제
//TODO 해당 상품의 감상평 조회
//TODO 내 감상평 조회

@PostMapping("/{productId}")
public ResponseEntity<Void> postReview(@RequestBody ReviewRequest request, @PathVariable Long productId) {
public ResponseEntity<ApiResponse<Void>> postReview(@RequestBody ReviewRequest request, @PathVariable Long productId) {
reviewService.saveReview(request, productId);
return ResponseEntity.ok().build();
return ResponseEntity.ok(ApiResponse.success(SuccessCode.CREATED_SUCCESS));
}

@PutMapping("/{productId}/{reviewId}")
public ResponseEntity<Void> editReview(@RequestBody ReviewRequest request, @PathVariable Long productId, @PathVariable Long reviewId) {
public ResponseEntity<ApiResponse<Void>> editReview(@RequestBody ReviewRequest request, @PathVariable Long productId, @PathVariable Long reviewId) {
reviewService.editReview(request, productId, reviewId);
return ResponseEntity.ok().build();
return ResponseEntity.ok(ApiResponse.success(SuccessCode.CREATED_SUCCESS));
}

@GetMapping("/{reviewId}")
public ResponseEntity<ReviewResponse> getReview(@PathVariable Long reviewId) {
public ResponseEntity<ApiResponse<ReviewResponse>> getReview(@PathVariable Long reviewId) {
Review response = reviewService.getReview(reviewId);
return ResponseEntity.ok(ReviewResponse.fromEntity(response));
return ResponseEntity.ok(ApiResponse.success(SuccessCode.CREATED_SUCCESS,ReviewResponse.fromEntity(response)));
}

@DeleteMapping("/{reviewId}")
public ResponseEntity<ApiResponse<Void>> deleteView(@PathVariable Long reviewId) {
reviewService.deleteReview(reviewId);
return ResponseEntity.ok(ApiResponse.success(SuccessCode.NO_CONTENT));
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
package com.helpmeCookies.review.repository;

import com.helpmeCookies.product.entity.Product;
import com.helpmeCookies.review.entity.Review;
import com.helpmeCookies.user.entity.ArtistInfo;
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;

@Repository
public interface ReviewRepository extends JpaRepository<Review, Long> {
Page<Review> findAllByProduct(Product product, Pageable pageable);

@Query("SELECT r FROM Review r JOIN r.product p JOIN p.artistInfo a where a = :artistInfo")
Page<Review> findAllByArtistInfo(@Param("artistInfo") ArtistInfo artistInfo, Pageable pageable);
}
27 changes: 27 additions & 0 deletions src/main/java/com/helpmeCookies/review/service/ReviewService.java
Original file line number Diff line number Diff line change
@@ -3,33 +3,60 @@
import com.helpmeCookies.product.entity.Product;
import com.helpmeCookies.product.repository.ProductRepository;
import com.helpmeCookies.review.dto.ReviewRequest;
import com.helpmeCookies.review.dto.ReviewResponse;
import com.helpmeCookies.review.entity.Review;
import com.helpmeCookies.review.repository.ReviewRepository;
import com.helpmeCookies.user.entity.ArtistInfo;
import com.helpmeCookies.user.entity.User;
import com.helpmeCookies.user.repository.ArtistInfoRepository;
import com.helpmeCookies.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class ReviewService {
private final ReviewRepository reviewRepository;
private final UserRepository userRepository;
private final ProductRepository productRepository;
private final ArtistInfoRepository artistInfoRepository;

@Transactional
public void saveReview(ReviewRequest request, Long productId) {
User writer = userRepository.findById(request.writerId()).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 writerID입니다."));
Product product = productRepository.findById(productId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 productID입니다."));

reviewRepository.save(request.toEntity(writer,product));
}

@Transactional
public void editReview(ReviewRequest request, Long productId, Long reviewId) {
Review review = reviewRepository.findById(reviewId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 reviewId 입니다."));
review.updateContent(request.content());
}

@Transactional(readOnly = true)
public Review getReview(Long reviewId) {
return reviewRepository.findById(reviewId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 reviewId 입니다."));
}

@Transactional
public void deleteReview(Long reviewId) {
reviewRepository.deleteById(reviewId);
}

@Transactional(readOnly = true)
public Page<ReviewResponse> getAllReviewByProduct(Long productId, Pageable pageable) {
Product product = productRepository.findById(productId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 productId입니다." + productId));
return reviewRepository.findAllByProduct(product,pageable).map(ReviewResponse::fromEntity);
}

@Transactional(readOnly = true)
public Page<ReviewResponse> getAllReviewByArtist(Long artistId, Pageable pageable) {
ArtistInfo artistInfo = artistInfoRepository.findById(artistId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 ArtistId입니다." + artistId));
return reviewRepository.findAllByArtistInfo(artistInfo,pageable).map(ReviewResponse::fromEntity);
}
}
Loading