diff --git a/src/main/java/taco/klkl/domain/comment/controller/CommentController.java b/src/main/java/taco/klkl/domain/comment/controller/CommentController.java index 35855bdf..3fb20b66 100644 --- a/src/main/java/taco/klkl/domain/comment/controller/CommentController.java +++ b/src/main/java/taco/klkl/domain/comment/controller/CommentController.java @@ -37,8 +37,8 @@ public List findCommentsByProductId(@PathVariable final Long pr } @PostMapping - @Operation(summary = "댓글 등록", description = "작성한 댓글을 저장합니다.") @ResponseStatus(HttpStatus.CREATED) + @Operation(summary = "댓글 등록", description = "작성한 댓글을 저장합니다.") public CommentResponse addComment( @PathVariable final Long productId, @RequestBody @Valid final CommentCreateUpdateRequest commentCreateRequestDto @@ -50,8 +50,8 @@ public CommentResponse addComment( } @PutMapping("/{commentId}") - @Operation(summary = "댓글 수정", description = "작성한 댓글을 수정합니다.") @ResponseStatus(HttpStatus.OK) + @Operation(summary = "댓글 수정", description = "작성한 댓글을 수정합니다.") public CommentResponse updateComment( @PathVariable final Long productId, @PathVariable final Long commentId, @@ -65,8 +65,8 @@ public CommentResponse updateComment( } @DeleteMapping("/{commentId}") - @Operation(summary = "댓글 삭제", description = "작성한 댓글을 삭제합니다.") @ResponseStatus(HttpStatus.NO_CONTENT) + @Operation(summary = "댓글 삭제", description = "작성한 댓글을 삭제합니다.") public ResponseEntity deleteComment( @PathVariable final Long productId, @PathVariable final Long commentId diff --git a/src/main/java/taco/klkl/domain/comment/domain/Comment.java b/src/main/java/taco/klkl/domain/comment/domain/Comment.java index 7a0b2653..61a01bf5 100644 --- a/src/main/java/taco/klkl/domain/comment/domain/Comment.java +++ b/src/main/java/taco/klkl/domain/comment/domain/Comment.java @@ -2,7 +2,6 @@ import java.time.LocalDateTime; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -11,11 +10,9 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToOne; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import taco.klkl.domain.notification.domain.Notification; import taco.klkl.domain.product.domain.Product; import taco.klkl.domain.user.domain.User; @@ -36,13 +33,6 @@ public class Comment { @JoinColumn(name = "user_id", nullable = false) private User user; - @OneToOne( - mappedBy = "comment", - cascade = CascadeType.ALL, - orphanRemoval = true - ) - private Notification notifications; - @Column( name = "content", nullable = false, diff --git a/src/main/java/taco/klkl/domain/comment/service/CommentService.java b/src/main/java/taco/klkl/domain/comment/service/CommentService.java index 96401e16..ddf70315 100644 --- a/src/main/java/taco/klkl/domain/comment/service/CommentService.java +++ b/src/main/java/taco/klkl/domain/comment/service/CommentService.java @@ -81,7 +81,7 @@ private Comment createCommentEntity( final CommentCreateUpdateRequest commentCreateUpdateRequest ) { //TODO: getCurrentUser() 함수로 교채 - final User user = userUtil.findTestUser(); + final User user = userUtil.getCurrentUser(); final Product product = productUtil.findProductEntityById(productId); return Comment.of( product, diff --git a/src/main/java/taco/klkl/domain/image/controller/ImageController.java b/src/main/java/taco/klkl/domain/image/controller/ImageController.java index e3334168..fec40a31 100644 --- a/src/main/java/taco/klkl/domain/image/controller/ImageController.java +++ b/src/main/java/taco/klkl/domain/image/controller/ImageController.java @@ -2,9 +2,11 @@ import java.util.List; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import io.swagger.v3.oas.annotations.Operation; @@ -29,22 +31,24 @@ public class ImageController { private final ImageService imageService; + @PostMapping("/v1/users/me/upload-url") + @ResponseStatus(HttpStatus.CREATED) @Operation( summary = "유저 이미지 업로드 Presigned URL 생성", description = "유저 이미지 업로드를 위한 Presigned URL를 생성합니다." ) - @PostMapping("/v1/users/me/upload-url") public PresignedUrlResponse createUserImageUploadUrl( @Valid @RequestBody final SingleImageUploadRequest request ) { return imageService.createUserImageUploadUrl(request); } + @PostMapping("/v1/products/{productId}/upload-url") + @ResponseStatus(HttpStatus.CREATED) @Operation( summary = "상품 이미지 업로드 Presigned URL 생성", description = "상품 이미지 업로드를 위한 Presigned URL를 생성합니다." ) - @PostMapping("/v1/products/{productId}/upload-url") public List createProductImageUploadUrls( @PathVariable final Long productId, @Valid @RequestBody final MultipleImagesUploadRequest request @@ -52,22 +56,22 @@ public List createProductImageUploadUrls( return imageService.createProductImageUploadUrls(productId, request); } + @PostMapping("/v1/users/me/upload-complete") @Operation( summary = "유저 이미지 업로드 완료 처리", description = "유저 이미지 업로드를 완료 처리합니다." ) - @PostMapping("/v1/users/me/upload-complete") public SingleUploadCompleteResponse uploadCompleteUserImage( @Valid @RequestBody final SingleImageUpdateRequest request ) { return imageService.uploadCompleteUserImage(request); } + @PostMapping("/v1/products/{productId}/upload-complete") @Operation( summary = "상품 이미지 업로드 완료 처리", description = "상품 이미지 업로드를 완료 처리합니다." ) - @PostMapping("/v1/products/{productId}/upload-complete") public MultipleUploadCompleteResponse uploadCompleteProductImages( @PathVariable final Long productId, @Valid @RequestBody final MultipleImagesUpdateRequest request diff --git a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java index b7903499..ed319743 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java @@ -58,7 +58,7 @@ public class ImageServiceImpl implements ImageService { @Override @Transactional public PresignedUrlResponse createUserImageUploadUrl(final SingleImageUploadRequest uploadRequest) { - final User currentUser = userUtil.findCurrentUser(); + final User currentUser = userUtil.getCurrentUser(); final FileExtension fileExtension = FileExtension.from(uploadRequest.fileExtension()); return createImageUploadUrl(ImageType.USER_IMAGE, currentUser.getId(), fileExtension); } @@ -80,7 +80,7 @@ public List createProductImageUploadUrls( public SingleUploadCompleteResponse uploadCompleteUserImage( final SingleImageUpdateRequest updateRequest ) { - final User currentUser = userUtil.findCurrentUser(); + final User currentUser = userUtil.getCurrentUser(); expireOldImages(ImageType.USER_IMAGE, currentUser.getId()); Image updatedImage = imageUtil.findImageEntityByImageTypeAndId(ImageType.USER_IMAGE, updateRequest.imageId()); diff --git a/src/main/java/taco/klkl/domain/like/controller/LikeController.java b/src/main/java/taco/klkl/domain/like/controller/LikeController.java index 89e45c36..fa5dad86 100644 --- a/src/main/java/taco/klkl/domain/like/controller/LikeController.java +++ b/src/main/java/taco/klkl/domain/like/controller/LikeController.java @@ -1,9 +1,11 @@ package taco.klkl.domain.like.controller; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import io.swagger.v3.oas.annotations.Operation; @@ -23,6 +25,7 @@ public class LikeController { private final LikeService likeService; @PostMapping + @ResponseStatus(HttpStatus.CREATED) @Operation(summary = "좋아요 누르기", description = "상품에 좋아요를 누릅니다.") public LikeResponse addLike(@PathVariable final Long productId) { return likeService.createLike(productId); diff --git a/src/main/java/taco/klkl/domain/like/dao/LikeRepository.java b/src/main/java/taco/klkl/domain/like/dao/LikeRepository.java index 57a4eee1..014d2c93 100644 --- a/src/main/java/taco/klkl/domain/like/dao/LikeRepository.java +++ b/src/main/java/taco/klkl/domain/like/dao/LikeRepository.java @@ -1,5 +1,7 @@ package taco.klkl.domain.like.dao; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -9,8 +11,9 @@ @Repository public interface LikeRepository extends JpaRepository { + Page findByUserId(final Long userId, final Pageable pageable); - void deleteByProductAndUser(Product product, User user); + void deleteByProductAndUser(final Product product, final User user); - boolean existsByProductAndUser(Product product, User user); + boolean existsByProductAndUser(final Product product, final User user); } diff --git a/src/main/java/taco/klkl/domain/like/service/LikeServiceImpl.java b/src/main/java/taco/klkl/domain/like/service/LikeServiceImpl.java index 447a66ef..02801eeb 100644 --- a/src/main/java/taco/klkl/domain/like/service/LikeServiceImpl.java +++ b/src/main/java/taco/klkl/domain/like/service/LikeServiceImpl.java @@ -60,7 +60,7 @@ private Product findProductById(final Long productId) { } private User findCurrentUser() { - return userUtil.findCurrentUser(); + return userUtil.getCurrentUser(); } @Override diff --git a/src/main/java/taco/klkl/domain/notification/dao/NotificationRepository.java b/src/main/java/taco/klkl/domain/notification/dao/NotificationRepository.java index 7f3c7373..2a1e8f20 100644 --- a/src/main/java/taco/klkl/domain/notification/dao/NotificationRepository.java +++ b/src/main/java/taco/klkl/domain/notification/dao/NotificationRepository.java @@ -8,5 +8,5 @@ import taco.klkl.domain.user.domain.User; public interface NotificationRepository extends JpaRepository { - List findAllByComment_Product_User(User user); + List findByComment_Product_User(final User user); } diff --git a/src/main/java/taco/klkl/domain/notification/service/NotificationServiceImpl.java b/src/main/java/taco/klkl/domain/notification/service/NotificationServiceImpl.java index 536070a9..5ed4a9cb 100644 --- a/src/main/java/taco/klkl/domain/notification/service/NotificationServiceImpl.java +++ b/src/main/java/taco/klkl/domain/notification/service/NotificationServiceImpl.java @@ -60,10 +60,10 @@ public List findAllNotifications() { @Transactional public NotificationUpdateResponse readAllNotifications() { final User receiver = findReceiver(); - final List notifications = notificationRepository.findAllByComment_Product_User(receiver); + final List notifications = notificationRepository.findByComment_Product_User(receiver); notifications.forEach(Notification::read); - final Long notificationCount = notificationRepository.count(); - return NotificationUpdateResponse.of(notificationCount); + final Long updatedCount = (long)notifications.size(); + return NotificationUpdateResponse.of(updatedCount); } @Override @@ -71,6 +71,7 @@ public NotificationUpdateResponse readAllNotifications() { public NotificationUpdateResponse readNotificationById(final Long id) { final Notification notification = notificationRepository.findById(id) .orElseThrow(NotificationNotFoundException::new); + validateMyNotification(notification); notification.read(); return NotificationUpdateResponse.of(1L); } @@ -78,9 +79,11 @@ public NotificationUpdateResponse readNotificationById(final Long id) { @Override @Transactional public NotificationDeleteResponse deleteAllNotifications() { - final Long notificationCount = notificationRepository.count(); - notificationRepository.deleteAll(); - return NotificationDeleteResponse.of(notificationCount); + final User receiver = findReceiver(); + final List notifications = notificationRepository.findByComment_Product_User(receiver); + notificationRepository.deleteAll(notifications); + final Long deletedCount = (long)notifications.size(); + return NotificationDeleteResponse.of(deletedCount); } @Override @@ -92,6 +95,13 @@ public void createNotificationByComment(final Comment comment) { // TODO: 토큰으로 유저 가져오는 방식으로 수정하기 private User findReceiver() { - return userUtil.findTestUser(); + return userUtil.getCurrentUser(); + } + + private void validateMyNotification(final Notification notification) { + final User receiver = findReceiver(); + if (!notification.getComment().getProduct().getUser().equals(receiver)) { + throw new NotificationNotFoundException(); + } } } diff --git a/src/main/java/taco/klkl/domain/oauth/service/OauthKakaoLoginServiceImpl.java b/src/main/java/taco/klkl/domain/oauth/service/OauthKakaoLoginServiceImpl.java index a46bf50a..a15c77d4 100644 --- a/src/main/java/taco/klkl/domain/oauth/service/OauthKakaoLoginServiceImpl.java +++ b/src/main/java/taco/klkl/domain/oauth/service/OauthKakaoLoginServiceImpl.java @@ -9,7 +9,6 @@ import taco.klkl.domain.oauth.dao.OauthRepository; import taco.klkl.domain.oauth.domain.Oauth; import taco.klkl.domain.oauth.dto.request.KakaoUserInfoRequest; -import taco.klkl.domain.user.domain.Gender; import taco.klkl.domain.user.domain.User; import taco.klkl.domain.user.dto.request.UserCreateRequest; import taco.klkl.domain.user.dto.response.UserDetailResponse; @@ -58,8 +57,6 @@ private User registerUser(final KakaoUserInfoRequest userInfoRequest) { // TODO: 성별, 나이는 기본값으로 넣고 있습니다. final UserCreateRequest userCreateRequest = UserCreateRequest.of( name, - Gender.MALE.getValue(), - 0, "" ); diff --git a/src/main/java/taco/klkl/domain/product/controller/ProductController.java b/src/main/java/taco/klkl/domain/product/controller/ProductController.java index 2007287a..267d4423 100644 --- a/src/main/java/taco/klkl/domain/product/controller/ProductController.java +++ b/src/main/java/taco/klkl/domain/product/controller/ProductController.java @@ -5,6 +5,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -14,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; @@ -29,7 +31,7 @@ import taco.klkl.domain.product.dto.response.ProductSimpleResponse; import taco.klkl.domain.product.service.ProductService; import taco.klkl.global.common.constants.ProductConstants; -import taco.klkl.global.common.response.PagedResponseDto; +import taco.klkl.global.common.response.PagedResponse; @RestController @RequestMapping("/v1/products") @@ -41,20 +43,20 @@ public class ProductController { @GetMapping @Operation(summary = "상품 목록 조회", description = "상품 목록을 조회합니다.") - public PagedResponseDto findProductsByFilteringAndSorting( - @PageableDefault(size = ProductConstants.DEFAULT_PAGE_SIZE) Pageable pageable, - @RequestParam(name = "city_id", required = false) Set cityIds, - @RequestParam(name = "subcategory_id", required = false) Set subcategoryIds, - @RequestParam(name = "tag_id", required = false) Set tagIds, - @RequestParam(name = "sort_by", required = false, defaultValue = "created_at") String sortBy, - @RequestParam(name = "sort_direction", required = false, defaultValue = "DESC") String sortDirection + public PagedResponse findProductsByFilteringAndSorting( + @PageableDefault(size = ProductConstants.DEFAULT_PAGE_SIZE) final Pageable pageable, + @RequestParam(name = "city_id", required = false) final Set cityIds, + @RequestParam(name = "subcategory_id", required = false) final Set subcategoryIds, + @RequestParam(name = "tag_id", required = false) final Set tagIds, + @RequestParam(name = "sort_by", required = false, defaultValue = "created_at") final String sortBy, + @RequestParam(name = "sort_direction", required = false, defaultValue = "DESC") final String sortDirection ) { - ProductFilterOptions filterOptions = new ProductFilterOptions( + final ProductFilterOptions filterOptions = new ProductFilterOptions( cityIds, subcategoryIds, tagIds ); - ProductSortOptions sortOptions = new ProductSortOptions( + final ProductSortOptions sortOptions = new ProductSortOptions( sortBy, sortDirection ); @@ -63,42 +65,52 @@ public PagedResponseDto findProductsByFilteringAndSorting @GetMapping("/search") @Operation(summary = "제목으로 상품 목록 조회", description = "제목으로 상품 목록을 조회합니다.") - public PagedResponseDto findProductsByPartialNameAndSorting( - @RequestParam(value = "name") @NotBlank String partialName, - @PageableDefault(size = ProductConstants.DEFAULT_PAGE_SIZE) Pageable pageable, - @RequestParam(name = "sort_by", required = false, defaultValue = "created_at") String sortBy, - @RequestParam(name = "sort_direction", required = false, defaultValue = "DESC") String sortDirection + public PagedResponse findProductsByPartialNameAndSorting( + @RequestParam(value = "name") @NotBlank final String partialName, + @PageableDefault(size = ProductConstants.DEFAULT_PAGE_SIZE) final Pageable pageable, + @RequestParam(name = "sort_by", required = false, defaultValue = "created_at") final String sortBy, + @RequestParam(name = "sort_direction", required = false, defaultValue = "DESC") final String sortDirection ) { - ProductSortOptions sortOptions = new ProductSortOptions( + final ProductSortOptions sortOptions = new ProductSortOptions( sortBy, sortDirection ); return productService.findProductsByPartialName(partialName, pageable, sortOptions); } + @GetMapping("/following") + @Operation(summary = "내 팔로잉의 상품 목록 조회", description = "내 팔로잉 유저들의 상품 목록을 조회합니다.") + public PagedResponse findFollowingProducts( + @PageableDefault(size = ProductConstants.DEFAULT_PAGE_SIZE) final Pageable pageable, + @RequestParam(value = "following_id", required = false) final Set followingIds + ) { + return productService.findMyFollowingProducts(pageable, followingIds); + } + @GetMapping("/{productId}") @Operation(summary = "상품 상세 조회", description = "상품 상세 정보를 조회합니다.") public ProductDetailResponse findProductById( - @PathVariable Long productId + @PathVariable final Long productId ) { return productService.findProductById(productId); } @PostMapping + @ResponseStatus(HttpStatus.CREATED) @Operation(summary = "상품 등록", description = "상품을 등록합니다.") public ResponseEntity createProduct( - @Valid @RequestBody ProductCreateUpdateRequest createRequest + @Valid @RequestBody final ProductCreateUpdateRequest createRequest ) { - ProductDetailResponse createdProduct = productService.createProduct(createRequest); - URI location = createResourceLocation(createdProduct.id()); + final ProductDetailResponse createdProduct = productService.createProduct(createRequest); + final URI location = createResourceLocation(createdProduct.id()); return ResponseEntity.created(location).body(createdProduct); } @PutMapping("/{productId}") @Operation(summary = "상품 정보 수정", description = "상품 정보를 수정합니다.") public ProductDetailResponse updateProduct( - @PathVariable Long productId, - @Valid @RequestBody ProductCreateUpdateRequest updateRequest + @PathVariable final Long productId, + @Valid @RequestBody final ProductCreateUpdateRequest updateRequest ) { return productService.updateProduct(productId, updateRequest); } @@ -106,7 +118,7 @@ public ProductDetailResponse updateProduct( @DeleteMapping("/{productId}") @Operation(summary = "상품 삭제", description = "상품을 삭제합니다.") public ResponseEntity deleteProduct( - @PathVariable Long productId + @PathVariable final Long productId ) { productService.deleteProduct(productId); return ResponseEntity.noContent().build(); diff --git a/src/main/java/taco/klkl/domain/product/dao/ProductRepository.java b/src/main/java/taco/klkl/domain/product/dao/ProductRepository.java index 79991fb7..f7183e77 100644 --- a/src/main/java/taco/klkl/domain/product/dao/ProductRepository.java +++ b/src/main/java/taco/klkl/domain/product/dao/ProductRepository.java @@ -1,10 +1,29 @@ package taco.klkl.domain.product.dao; +import java.util.Set; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import taco.klkl.domain.product.domain.Product; +import taco.klkl.domain.user.domain.User; @Repository public interface ProductRepository extends JpaRepository { + Page findByUserId(final Long userId, final Pageable pageable); + + @Query( + "SELECT p FROM product p WHERE p.user IN " + + "(SELECT f.following FROM follow f WHERE f.follower = :follower " + + "AND (:followingIds IS NULL OR f.following.id IN :followingIds))" + ) + Page findProductsOfFollowedUsers( + @Param("follower") final User follower, + @Param("followingIds") final Set followingIds, + final Pageable pageable + ); } diff --git a/src/main/java/taco/klkl/domain/product/service/ProductService.java b/src/main/java/taco/klkl/domain/product/service/ProductService.java index ee5fbb09..46f9db10 100644 --- a/src/main/java/taco/klkl/domain/product/service/ProductService.java +++ b/src/main/java/taco/klkl/domain/product/service/ProductService.java @@ -1,5 +1,7 @@ package taco.klkl.domain.product.service; +import java.util.Set; + import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -10,17 +12,25 @@ import taco.klkl.domain.product.dto.response.ProductDetailResponse; import taco.klkl.domain.product.dto.response.ProductSimpleResponse; import taco.klkl.domain.product.exception.ProductNotFoundException; -import taco.klkl.global.common.response.PagedResponseDto; +import taco.klkl.global.common.response.PagedResponse; @Service public interface ProductService { - PagedResponseDto findProductsByFilterOptionsAndSortOptions( + PagedResponse findProductsByFilterOptionsAndSortOptions( final Pageable pageable, final ProductFilterOptions filterOptions, final ProductSortOptions sortOptions ); + PagedResponse findProductsByPartialName( + final String partialName, + final Pageable pageable, + final ProductSortOptions sortOptions + ); + + PagedResponse findMyFollowingProducts(final Pageable pageable, final Set followingIds); + ProductDetailResponse findProductById(final Long id) throws ProductNotFoundException; ProductDetailResponse createProduct(final ProductCreateUpdateRequest createRequest); @@ -33,7 +43,4 @@ PagedResponseDto findProductsByFilterOptionsAndSortOption void deleteProduct(final Long id) throws ProductNotFoundException; - PagedResponseDto findProductsByPartialName(String partialName, Pageable pageable, - ProductSortOptions sortOptions); - } diff --git a/src/main/java/taco/klkl/domain/product/service/ProductServiceImpl.java b/src/main/java/taco/klkl/domain/product/service/ProductServiceImpl.java index 44636873..0216d8f0 100644 --- a/src/main/java/taco/klkl/domain/product/service/ProductServiceImpl.java +++ b/src/main/java/taco/klkl/domain/product/service/ProductServiceImpl.java @@ -8,6 +8,7 @@ import org.springframework.context.annotation.Primary; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; @@ -50,7 +51,7 @@ import taco.klkl.domain.region.exception.city.CityNotFoundException; import taco.klkl.domain.region.exception.currency.CurrencyNotFoundException; import taco.klkl.domain.user.domain.User; -import taco.klkl.global.common.response.PagedResponseDto; +import taco.klkl.global.common.response.PagedResponse; import taco.klkl.global.util.CityUtil; import taco.klkl.global.util.CurrencyUtil; import taco.klkl.global.util.SubcategoryUtil; @@ -74,7 +75,7 @@ public class ProductServiceImpl implements ProductService { private final CurrencyUtil currencyUtil; @Override - public PagedResponseDto findProductsByFilterOptionsAndSortOptions( + public PagedResponse findProductsByFilterOptionsAndSortOptions( final Pageable pageable, final ProductFilterOptions filterOptions, final ProductSortOptions sortOptions @@ -86,7 +87,51 @@ public PagedResponseDto findProductsByFilterOptionsAndSor final List products = fetchProducts(baseQuery, pageable, sortOptions); final Page productPage = new PageImpl<>(products, pageable, total); - return PagedResponseDto.of(productPage, ProductSimpleResponse::from); + return PagedResponse.of(productPage, ProductSimpleResponse::from); + } + + @Override + public PagedResponse findProductsByPartialName( + final String partialName, + final Pageable pageable, + final ProductSortOptions sortOptions + ) { + final QProduct product = QProduct.product; + final QCity city = QCity.city; + final QCountry country = QCountry.country; + final QSubcategory subcategory = QSubcategory.subcategory; + final QCategory category = QCategory.category; + + final JPAQuery baseQuery = queryFactory + .from(product) + .where(product.name.contains(partialName)); + + final long total = getCount(baseQuery); + + baseQuery.join(product.city, city).fetchJoin() + .join(product.subcategory, subcategory).fetchJoin() + .join(city.country, country).fetchJoin() + .join(subcategory.category, category).fetchJoin(); + + final List products = fetchProducts(baseQuery, pageable, sortOptions); + final Page productPage = new PageImpl<>(products, pageable, total); + + return PagedResponse.of(productPage, ProductSimpleResponse::from); + } + + @Override + public PagedResponse findMyFollowingProducts( + final Pageable pageable, + final Set followingIds + ) { + final User me = userUtil.getCurrentUser(); + final Pageable sortedPageable = createPageableSortedByCreatedAtDesc(pageable); + final Page followingProducts = productRepository.findProductsOfFollowedUsers( + me, + followingIds, + sortedPageable + ); + return PagedResponse.of(followingProducts, ProductSimpleResponse::from); } @Override @@ -122,10 +167,13 @@ public int decreaseLikeCount(Product product) { @Override @Transactional - public ProductDetailResponse updateProduct(final Long id, final ProductCreateUpdateRequest updateRequest) - throws ProductNotFoundException { + public ProductDetailResponse updateProduct( + final Long id, + final ProductCreateUpdateRequest updateRequest + ) throws ProductNotFoundException { final Product product = productRepository.findById(id) .orElseThrow(ProductNotFoundException::new); + validateMyProduct(product); updateProductEntity(product, updateRequest); updateProductEntityTags(product, updateRequest.tagIds()); return ProductDetailResponse.from(product); @@ -136,38 +184,10 @@ public ProductDetailResponse updateProduct(final Long id, final ProductCreateUpd public void deleteProduct(final Long id) throws ProductNotFoundException { final Product product = productRepository.findById(id) .orElseThrow(ProductNotFoundException::new); + validateMyProduct(product); productRepository.delete(product); } - @Override - public PagedResponseDto findProductsByPartialName( - final String partialName, - final Pageable pageable, - final ProductSortOptions sortOptions - ) { - final QProduct product = QProduct.product; - final QCity city = QCity.city; - final QCountry country = QCountry.country; - final QSubcategory subcategory = QSubcategory.subcategory; - final QCategory category = QCategory.category; - - final JPAQuery baseQuery = queryFactory - .from(product) - .where(product.name.contains(partialName)); - - final long total = getCount(baseQuery); - - baseQuery.join(product.city, city).fetchJoin() - .join(product.subcategory, subcategory).fetchJoin() - .join(city.country, country).fetchJoin() - .join(subcategory.category, category).fetchJoin(); - - final List products = fetchProducts(baseQuery, pageable, sortOptions); - final Page productPage = new PageImpl<>(products, pageable, total); - - return PagedResponseDto.of(productPage, ProductSimpleResponse::from); - } - private JPAQuery createBaseQuery(final ProductFilterOptions filterOptions) { final QProduct product = QProduct.product; final QProductTag productTag = QProductTag.productTag; @@ -248,7 +268,7 @@ private Set createTagsByTagIds(final Set filterIds) { private Product createProductEntity(final ProductCreateUpdateRequest createRequest) { final Rating rating = Rating.from(createRequest.rating()); - final User user = userUtil.findTestUser(); + final User user = userUtil.getCurrentUser(); final City city = findCityById(createRequest.cityId()); final Subcategory subcategory = findSubcategoryById(createRequest.subcategoryId()); final Currency currency = findCurrencyById(createRequest.currencyId()); @@ -299,6 +319,14 @@ private Sort.Direction createSortDirectionByQuery(final String query) throws Sor } } + private Pageable createPageableSortedByCreatedAtDesc(final Pageable pageable) { + return PageRequest.of( + pageable.getPageNumber(), + pageable.getPageSize(), + Sort.by(Sort.Direction.DESC, "createdAt") + ); + } + private City findCityById(final Long cityId) throws CityNotFoundException { return cityUtil.findCityEntityById(cityId); } @@ -337,4 +365,11 @@ private void validateSubcategoryIds(final Set subcategoryIds) { private void validateTagIds(final Set tagIds) { tagIds.forEach(tagUtil::findTagEntityById); } + + private void validateMyProduct(final Product product) { + final User me = userUtil.getCurrentUser(); + if (!product.getUser().equals(me)) { + throw new ProductNotFoundException(); + } + } } diff --git a/src/main/java/taco/klkl/domain/user/controller/UserController.java b/src/main/java/taco/klkl/domain/user/controller/UserController.java index 696131d1..d3467204 100644 --- a/src/main/java/taco/klkl/domain/user/controller/UserController.java +++ b/src/main/java/taco/klkl/domain/user/controller/UserController.java @@ -1,9 +1,18 @@ package taco.klkl.domain.user.controller; +import java.util.List; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.HttpStatus; +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; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import io.swagger.v3.oas.annotations.Operation; @@ -11,9 +20,17 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import taco.klkl.domain.product.dto.response.ProductSimpleResponse; +import taco.klkl.domain.user.domain.User; +import taco.klkl.domain.user.dto.request.UserFollowRequest; import taco.klkl.domain.user.dto.request.UserUpdateRequest; import taco.klkl.domain.user.dto.response.UserDetailResponse; +import taco.klkl.domain.user.dto.response.UserFollowResponse; +import taco.klkl.domain.user.dto.response.UserSimpleResponse; import taco.klkl.domain.user.service.UserService; +import taco.klkl.global.common.constants.ProductConstants; +import taco.klkl.global.common.response.PagedResponse; +import taco.klkl.global.util.UserUtil; @Slf4j @RestController @@ -23,16 +40,71 @@ public class UserController { private final UserService userService; + private final UserUtil userUtil; - @Operation(summary = "내 정보 조회", description = "내 정보를 조회합니다. (테스트용)") @GetMapping("/me") + @Operation(summary = "내 정보 조회", description = "내 정보를 조회합니다.") public UserDetailResponse getMe() { - return userService.getCurrentUser(); + final User me = userUtil.getCurrentUser(); + return userService.getUserById(me.getId()); + } + + @GetMapping("/{userId}") + @Operation(summary = "유저 정보 조회", description = "유저 정보를 조회합니다.") + public UserDetailResponse getUser(@PathVariable final Long userId) { + return userService.getUserById(userId); + } + + @GetMapping("/me/products") + @Operation(summary = "내 상품 목록 조회", description = "내 상품 목록을 조회합니다.") + public PagedResponse getMyProducts( + @PageableDefault(size = ProductConstants.DEFAULT_PAGE_SIZE) Pageable pageable + ) { + final User me = userUtil.getCurrentUser(); + return userService.getUserProductsById(me.getId(), pageable); + } + + @GetMapping("/{userId}/products") + @Operation(summary = "유저의 상품 목록 조회", description = "유저의 상품 목록을 조회합니다.") + public PagedResponse getUserProducts( + @PathVariable final Long userId, + @PageableDefault(size = ProductConstants.DEFAULT_PAGE_SIZE) Pageable pageable + ) { + return userService.getUserProductsById(userId, pageable); + } + + @GetMapping("/me/likes") + @Operation(summary = "내 종아요 목록 조회", description = "내 좋아요 목록을 조회합니다.") + public PagedResponse getMyLikes( + @PageableDefault(size = ProductConstants.DEFAULT_PAGE_SIZE) Pageable pageable + ) { + final User me = userUtil.getCurrentUser(); + return userService.getUserLikesById(me.getId(), pageable); + } + + @GetMapping("/following") + @Operation(summary = "내 팔로잉 목록 조회", description = "내 팔로잉 목록을 조회합니다.") + public List getMyFollowing() { + final User me = userUtil.getCurrentUser(); + return userService.getUserFollowingById(me.getId()); + } + + @PostMapping("/following") + @ResponseStatus(HttpStatus.CREATED) + @Operation(summary = "유저 팔로우", description = "유저를 팔로우합니다.") + public UserFollowResponse followUser(@Valid @RequestBody final UserFollowRequest request) { + return userService.createUserFollow(request); + } + + @DeleteMapping("/following/{userId}") + @Operation(summary = "유저 팔로우 취소", description = "유저 팔로우를 취소합니다.") + public UserFollowResponse cancelUserFollow(@PathVariable final Long userId) { + return userService.removeUserFollow(userId); } - @Operation(summary = "내 정보 수정", description = "내 정보를 수정합니다.") @PutMapping("/me") - public UserDetailResponse updateMe(@Valid @RequestBody UserUpdateRequest request) { + @Operation(summary = "내 정보 수정", description = "내 정보를 수정합니다.") + public UserDetailResponse updateMe(@Valid @RequestBody final UserUpdateRequest request) { return userService.updateUser(request); } } diff --git a/src/main/java/taco/klkl/domain/user/dao/FollowRepository.java b/src/main/java/taco/klkl/domain/user/dao/FollowRepository.java new file mode 100644 index 00000000..4af51659 --- /dev/null +++ b/src/main/java/taco/klkl/domain/user/dao/FollowRepository.java @@ -0,0 +1,18 @@ +package taco.klkl.domain.user.dao; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import taco.klkl.domain.user.domain.Follow; +import taco.klkl.domain.user.domain.User; + +@Repository +public interface FollowRepository extends JpaRepository { + List findByFollowerId(final Long followerId); + + boolean existsByFollowerAndFollowing(final User follower, final User following); + + void deleteByFollowerAndFollowing(final User follower, final User following); +} diff --git a/src/main/java/taco/klkl/domain/user/dao/UserRepository.java b/src/main/java/taco/klkl/domain/user/dao/UserRepository.java index 67d34092..e45a03cd 100644 --- a/src/main/java/taco/klkl/domain/user/dao/UserRepository.java +++ b/src/main/java/taco/klkl/domain/user/dao/UserRepository.java @@ -7,7 +7,6 @@ @Repository public interface UserRepository extends JpaRepository { - User findFirstByName(String name); - boolean existsByName(String name); + boolean existsByName(final String name); } diff --git a/src/main/java/taco/klkl/domain/user/domain/Follow.java b/src/main/java/taco/klkl/domain/user/domain/Follow.java new file mode 100644 index 00000000..30812976 --- /dev/null +++ b/src/main/java/taco/klkl/domain/user/domain/Follow.java @@ -0,0 +1,69 @@ +package taco.klkl.domain.user.domain; + +import java.time.LocalDateTime; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@Entity(name = "follow") +@Table( + uniqueConstraints = { + @UniqueConstraint(columnNames = {"follower_id", "following_id"}) + } +) +public class Follow { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "follow_id") + private Long id; + + @ManyToOne( + fetch = FetchType.LAZY, + optional = false + ) + @JoinColumn( + name = "follower_id", + nullable = false + ) + private User follower; + + @ManyToOne( + fetch = FetchType.LAZY, + optional = false + ) + @JoinColumn( + name = "following_id", + nullable = false + ) + private User following; + + @Column( + name = "created_at", + nullable = false + ) + private LocalDateTime createdAt; + + private Follow(final User follower, final User following) { + this.follower = follower; + this.following = following; + this.createdAt = LocalDateTime.now(); + } + + public static Follow of(final User follower, final User following) { + return new Follow(follower, following); + } + +} diff --git a/src/main/java/taco/klkl/domain/user/domain/User.java b/src/main/java/taco/klkl/domain/user/domain/User.java index 46b54abf..65fbfd17 100644 --- a/src/main/java/taco/klkl/domain/user/domain/User.java +++ b/src/main/java/taco/klkl/domain/user/domain/User.java @@ -35,19 +35,6 @@ public class User { ) private String name; - @Column( - name = "gender", - length = 1, - nullable = false - ) - private Gender gender; - - @Column( - name = "age", - nullable = false - ) - private Integer age; - @Column( name = "description", length = 100 @@ -63,35 +50,25 @@ public class User { private User( final String name, - final Gender gender, - final Integer age, final String description ) { this.name = name; - this.gender = gender; - this.age = age; this.description = description; this.createdAt = LocalDateTime.now(); } public static User of( final String name, - final Gender gender, - final Integer age, final String description ) { - return new User(name, gender, age, description); + return new User(name, description); } public void update( final String name, - final Gender gender, - final Integer age, final String description ) { this.name = name; - this.gender = gender; - this.age = age; this.description = description; } diff --git a/src/main/java/taco/klkl/domain/user/dto/request/UserCreateRequest.java b/src/main/java/taco/klkl/domain/user/dto/request/UserCreateRequest.java index 072467a8..359e5253 100644 --- a/src/main/java/taco/klkl/domain/user/dto/request/UserCreateRequest.java +++ b/src/main/java/taco/klkl/domain/user/dto/request/UserCreateRequest.java @@ -1,20 +1,18 @@ package taco.klkl.domain.user.dto.request; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.PositiveOrZero; +import jakarta.validation.constraints.NotBlank; +import taco.klkl.global.common.constants.UserValidationMessages; public record UserCreateRequest( - @NotNull(message = "이름은 필수 항목입니다.") String name, - @NotNull(message = "성별은 필수 항목입니다.") String gender, - @PositiveOrZero(message = "나이는 0 이상이어야 합니다.") Integer age, + @NotBlank(message = UserValidationMessages.NAME_NOT_BLANK) + String name, + String description ) { public static UserCreateRequest of( final String name, - final String gender, - final Integer age, final String description ) { - return new UserCreateRequest(name, gender, age, description); + return new UserCreateRequest(name, description); } } diff --git a/src/main/java/taco/klkl/domain/user/dto/request/UserFollowRequest.java b/src/main/java/taco/klkl/domain/user/dto/request/UserFollowRequest.java new file mode 100644 index 00000000..5487726f --- /dev/null +++ b/src/main/java/taco/klkl/domain/user/dto/request/UserFollowRequest.java @@ -0,0 +1,10 @@ +package taco.klkl.domain.user.dto.request; + +import jakarta.validation.constraints.NotNull; +import taco.klkl.global.common.constants.UserValidationMessages; + +public record UserFollowRequest( + @NotNull(message = UserValidationMessages.USER_ID_NOT_NULL) + Long userId +) { +} diff --git a/src/main/java/taco/klkl/domain/user/dto/request/UserUpdateRequest.java b/src/main/java/taco/klkl/domain/user/dto/request/UserUpdateRequest.java index 42bede10..4e7a0dcd 100644 --- a/src/main/java/taco/klkl/domain/user/dto/request/UserUpdateRequest.java +++ b/src/main/java/taco/klkl/domain/user/dto/request/UserUpdateRequest.java @@ -1,12 +1,12 @@ package taco.klkl.domain.user.dto.request; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.PositiveOrZero; +import jakarta.validation.constraints.NotBlank; +import taco.klkl.global.common.constants.UserValidationMessages; public record UserUpdateRequest( - @NotNull(message = "이름은 필수 항목입니다.") String name, - @NotNull(message = "성별은 필수 항목입니다.") String gender, - @PositiveOrZero(message = "나이는 0 이상이어야 합니다.") Integer age, + @NotBlank(message = UserValidationMessages.NAME_NOT_BLANK) + String name, + String description ) { } diff --git a/src/main/java/taco/klkl/domain/user/dto/response/UserFollowResponse.java b/src/main/java/taco/klkl/domain/user/dto/response/UserFollowResponse.java new file mode 100644 index 00000000..019ad127 --- /dev/null +++ b/src/main/java/taco/klkl/domain/user/dto/response/UserFollowResponse.java @@ -0,0 +1,17 @@ +package taco.klkl.domain.user.dto.response; + +import taco.klkl.domain.user.domain.User; + +public record UserFollowResponse( + boolean isFollowing, + Long followerId, + Long followingId +) { + public static UserFollowResponse of(final boolean isFollowing, final User follower, final User following) { + return new UserFollowResponse( + isFollowing, + follower.getId(), + following.getId() + ); + } +} diff --git a/src/main/java/taco/klkl/domain/user/service/UserService.java b/src/main/java/taco/klkl/domain/user/service/UserService.java index 25017e81..d0398b68 100644 --- a/src/main/java/taco/klkl/domain/user/service/UserService.java +++ b/src/main/java/taco/klkl/domain/user/service/UserService.java @@ -1,17 +1,37 @@ package taco.klkl.domain.user.service; +import java.util.List; + +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import taco.klkl.domain.product.dto.response.ProductSimpleResponse; import taco.klkl.domain.user.domain.User; import taco.klkl.domain.user.dto.request.UserCreateRequest; +import taco.klkl.domain.user.dto.request.UserFollowRequest; import taco.klkl.domain.user.dto.request.UserUpdateRequest; import taco.klkl.domain.user.dto.response.UserDetailResponse; +import taco.klkl.domain.user.dto.response.UserFollowResponse; +import taco.klkl.domain.user.dto.response.UserSimpleResponse; +import taco.klkl.global.common.response.PagedResponse; @Service public interface UserService { - UserDetailResponse getCurrentUser(); + + UserDetailResponse getUserById(final Long id); + + PagedResponse getUserProductsById(final Long id, final Pageable pageable); + + PagedResponse getUserLikesById(final Long id, final Pageable pageable); + + List getUserFollowingById(final Long id); User createUser(final UserCreateRequest createRequest); + UserFollowResponse createUserFollow(final UserFollowRequest followRequest); + + UserFollowResponse removeUserFollow(final Long followerId); + UserDetailResponse updateUser(final UserUpdateRequest updateRequest); + } diff --git a/src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java b/src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java index 537732fb..bf007459 100644 --- a/src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java +++ b/src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java @@ -1,17 +1,34 @@ package taco.klkl.domain.user.service; +import java.util.List; + import org.springframework.context.annotation.Primary; +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.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import taco.klkl.domain.like.domain.Like; +import taco.klkl.domain.product.domain.Product; +import taco.klkl.domain.product.dto.response.ProductSimpleResponse; +import taco.klkl.domain.user.dao.FollowRepository; import taco.klkl.domain.user.dao.UserRepository; -import taco.klkl.domain.user.domain.Gender; +import taco.klkl.domain.user.domain.Follow; import taco.klkl.domain.user.domain.User; import taco.klkl.domain.user.dto.request.UserCreateRequest; +import taco.klkl.domain.user.dto.request.UserFollowRequest; import taco.klkl.domain.user.dto.request.UserUpdateRequest; import taco.klkl.domain.user.dto.response.UserDetailResponse; +import taco.klkl.domain.user.dto.response.UserFollowResponse; +import taco.klkl.domain.user.dto.response.UserSimpleResponse; +import taco.klkl.domain.user.exception.UserNotFoundException; +import taco.klkl.global.common.response.PagedResponse; +import taco.klkl.global.util.LikeUtil; +import taco.klkl.global.util.ProductUtil; import taco.klkl.global.util.UserUtil; @Slf4j @@ -22,17 +39,50 @@ public class UserServiceImpl implements UserService { private final UserRepository userRepository; + private final FollowRepository followRepository; + private final ProductUtil productUtil; private final UserUtil userUtil; + private final LikeUtil likeUtil; /** * 임시 나의 정보 조회 * name 속성이 "testUser"인 유저를 반환합니다. */ @Override - public UserDetailResponse getCurrentUser() { - final User currentUser = userUtil.findCurrentUser(); - return UserDetailResponse.from(currentUser); + public UserDetailResponse getUserById(final Long id) { + final User user = userRepository.findById(id) + .orElseThrow(UserNotFoundException::new); + return UserDetailResponse.from(user); + } + + @Override + public PagedResponse getUserProductsById(final Long id, final Pageable pageable) { + userRepository.findById(id) + .orElseThrow(UserNotFoundException::new); + final Pageable sortedPageable = createPageableSortedByCreatedAtDesc(pageable); + final Page userProducts = productUtil.findProductsByUserId(id, sortedPageable); + return PagedResponse.of(userProducts, ProductSimpleResponse::from); + } + + @Override + public PagedResponse getUserLikesById(final Long id, final Pageable pageable) { + userRepository.findById(id) + .orElseThrow(UserNotFoundException::new); + final Pageable sortedPageable = createPageableSortedByCreatedAtDesc(pageable); + final Page likes = likeUtil.findLikesByUserId(id, sortedPageable); + final Page likedProducts = likes.map(Like::getProduct); + return PagedResponse.of(likedProducts, ProductSimpleResponse::from); + } + + @Override + public List getUserFollowingById(final Long id) { + userRepository.findById(id) + .orElseThrow(UserNotFoundException::new); + return followRepository.findByFollowerId(id).stream() + .map(Follow::getFollowing) + .map(UserSimpleResponse::from) + .toList(); } @Override @@ -42,39 +92,69 @@ public User createUser(final UserCreateRequest createRequest) { return userRepository.save(user); } + @Override + @Transactional + public UserFollowResponse createUserFollow(final UserFollowRequest followRequest) { + final User follower = userUtil.getCurrentUser(); + final User following = userRepository.findById(followRequest.userId()) + .orElseThrow(UserNotFoundException::new); + if (isFollowPresent(follower, following)) { + return UserFollowResponse.of(true, follower, following); + } + final Follow follow = Follow.of(follower, following); + followRepository.save(follow); + return UserFollowResponse.of(true, follower, following); + } + + @Override + @Transactional + public UserFollowResponse removeUserFollow(final Long followerId) { + final User follower = userUtil.getCurrentUser(); + final User following = userRepository.findById(followerId) + .orElseThrow(UserNotFoundException::new); + if (isFollowPresent(follower, following)) { + followRepository.deleteByFollowerAndFollowing(follower, following); + } + return UserFollowResponse.of(false, follower, following); + } + @Override @Transactional public UserDetailResponse updateUser(final UserUpdateRequest updateRequest) { - User user = userUtil.findCurrentUser(); + User user = userUtil.getCurrentUser(); updateUserEntity(user, updateRequest); return UserDetailResponse.from(user); } private User createUserEntity(final UserCreateRequest createRequest) { final String name = createRequest.name(); - final Gender gender = Gender.from(createRequest.gender()); - final Integer age = createRequest.age(); final String description = createRequest.description(); return User.of( name, - gender, - age, description ); } private void updateUserEntity(final User user, final UserUpdateRequest updateRequest) { final String name = updateRequest.name(); - final Gender gender = Gender.from(updateRequest.gender()); - final Integer age = updateRequest.age(); final String description = updateRequest.description(); user.update( name, - gender, - age, description ); } + + private Pageable createPageableSortedByCreatedAtDesc(final Pageable pageable) { + return PageRequest.of( + pageable.getPageNumber(), + pageable.getPageSize(), + Sort.by(Sort.Direction.DESC, "createdAt") + ); + } + + private boolean isFollowPresent(final User follower, final User following) { + return followRepository.existsByFollowerAndFollowing(follower, following); + } } diff --git a/src/main/java/taco/klkl/global/common/constants/CommentValidationMessages.java b/src/main/java/taco/klkl/global/common/constants/CommentValidationMessages.java index 8547ae81..1e9c2655 100644 --- a/src/main/java/taco/klkl/global/common/constants/CommentValidationMessages.java +++ b/src/main/java/taco/klkl/global/common/constants/CommentValidationMessages.java @@ -5,4 +5,7 @@ public final class CommentValidationMessages { public static final String CONTENT_NOT_NULL = "댓글 내용은 필수 항목입니다."; public static final String CONTENT_NOT_BLANK = "댓글 내용은 비어있을 수 없습니다."; public static final String CONTENT_SIZE = "댓글 내용은 400자 이하여야 합니다."; + + private CommentValidationMessages() { + } } diff --git a/src/main/java/taco/klkl/global/common/constants/ProductConstants.java b/src/main/java/taco/klkl/global/common/constants/ProductConstants.java index 4e07fe80..c5211698 100644 --- a/src/main/java/taco/klkl/global/common/constants/ProductConstants.java +++ b/src/main/java/taco/klkl/global/common/constants/ProductConstants.java @@ -1,7 +1,5 @@ package taco.klkl.global.common.constants; -import java.util.Set; - public final class ProductConstants { public static final int DEFAULT_PAGE_SIZE = 9; diff --git a/src/main/java/taco/klkl/global/common/constants/UserConstants.java b/src/main/java/taco/klkl/global/common/constants/UserConstants.java index 85527612..70e768a0 100644 --- a/src/main/java/taco/klkl/global/common/constants/UserConstants.java +++ b/src/main/java/taco/klkl/global/common/constants/UserConstants.java @@ -1,18 +1,7 @@ package taco.klkl.global.common.constants; -import taco.klkl.domain.user.domain.Gender; -import taco.klkl.domain.user.domain.User; - public final class UserConstants { - public static final String TEST_USER_NAME = "testUser"; - public static final User TEST_USER = User.of( - TEST_USER_NAME, - Gender.MALE, - 20, - "테스트입니다." - ); - public static final int DEFAULT_TOTAL_LIKE_COUNT = 0; public static final int USERNAME_SUFFIX_MOD = 9973; diff --git a/src/main/java/taco/klkl/global/common/constants/UserValidationMessages.java b/src/main/java/taco/klkl/global/common/constants/UserValidationMessages.java new file mode 100644 index 00000000..943015a2 --- /dev/null +++ b/src/main/java/taco/klkl/global/common/constants/UserValidationMessages.java @@ -0,0 +1,10 @@ +package taco.klkl.global.common.constants; + +public final class UserValidationMessages { + + public static final String NAME_NOT_BLANK = "이름은 비워둘 수 없습니다."; + public static final String USER_ID_NOT_NULL = "유저 ID는 필수 항목입니다."; + + private UserValidationMessages() { + } +} diff --git a/src/main/java/taco/klkl/global/common/response/PagedResponseDto.java b/src/main/java/taco/klkl/global/common/response/PagedResponse.java similarity index 75% rename from src/main/java/taco/klkl/global/common/response/PagedResponseDto.java rename to src/main/java/taco/klkl/global/common/response/PagedResponse.java index 22cdf8c5..15c95264 100644 --- a/src/main/java/taco/klkl/global/common/response/PagedResponseDto.java +++ b/src/main/java/taco/klkl/global/common/response/PagedResponse.java @@ -5,7 +5,7 @@ import org.springframework.data.domain.Page; -public record PagedResponseDto( +public record PagedResponse( List content, int pageNumber, int pageSize, @@ -14,12 +14,12 @@ public record PagedResponseDto( boolean last ) { - public static PagedResponseDto of(Page page, Function mapper) { + public static PagedResponse of(Page page, Function mapper) { List content = page.getContent().stream() .map(mapper) .toList(); - return new PagedResponseDto<>( + return new PagedResponse<>( content, page.getNumber(), page.getSize(), diff --git a/src/main/java/taco/klkl/global/util/LikeUtil.java b/src/main/java/taco/klkl/global/util/LikeUtil.java new file mode 100644 index 00000000..6b52dbf4 --- /dev/null +++ b/src/main/java/taco/klkl/global/util/LikeUtil.java @@ -0,0 +1,20 @@ +package taco.klkl.global.util; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; +import taco.klkl.domain.like.dao.LikeRepository; +import taco.klkl.domain.like.domain.Like; + +@Component +@RequiredArgsConstructor +public class LikeUtil { + + private final LikeRepository likeRepository; + + public Page findLikesByUserId(final Long userId, final Pageable pageable) { + return likeRepository.findByUserId(userId, pageable); + } +} diff --git a/src/main/java/taco/klkl/global/util/ProductUtil.java b/src/main/java/taco/klkl/global/util/ProductUtil.java index ef17316a..e0642cf3 100644 --- a/src/main/java/taco/klkl/global/util/ProductUtil.java +++ b/src/main/java/taco/klkl/global/util/ProductUtil.java @@ -7,6 +7,8 @@ import java.util.Set; import java.util.stream.Collectors; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Component; import lombok.RequiredArgsConstructor; @@ -29,6 +31,10 @@ public Product findProductEntityById(final Long id) { .orElseThrow(ProductNotFoundException::new); } + public Page findProductsByUserId(final Long userId, final Pageable pageable) { + return productRepository.findByUserId(userId, pageable); + } + public void validateProductId(final Long id) { final boolean existsById = productRepository.existsById(id); if (!existsById) { diff --git a/src/main/java/taco/klkl/global/util/UserUtil.java b/src/main/java/taco/klkl/global/util/UserUtil.java index b0120c6d..5f36cae3 100644 --- a/src/main/java/taco/klkl/global/util/UserUtil.java +++ b/src/main/java/taco/klkl/global/util/UserUtil.java @@ -21,18 +21,13 @@ public class UserUtil { private final UserRepository userRepository; - public User findTestUser() { - return userRepository.findById(1L) - .orElseThrow(UserNotFoundException::new); - } - /** * TODO: 인증정보를 확인해 유저 엔티티를 리턴한다. * 현재 유저 조회 * @return */ - public User findCurrentUser() { - return findTestUser(); + public User getCurrentUser() { + return getTestUser(); } public String createUsername(final String name, final Long oauthMemberId) { @@ -52,6 +47,11 @@ public static String generateProfileUrlByUser(final User user) { .orElse(null); } + private User getTestUser() { + return userRepository.findById(1L) + .orElseThrow(UserNotFoundException::new); + } + private String generateUsername(final String name, final Long oauthMemberId) { final Long currentTimeMillis = Instant.now().toEpochMilli(); diff --git a/src/main/resources/database/data.sql b/src/main/resources/database/data.sql index 93b01920..ad6f222c 100644 --- a/src/main/resources/database/data.sql +++ b/src/main/resources/database/data.sql @@ -1,6 +1,8 @@ /* User */ -INSERT INTO klkl_user(user_id, name, gender, age, description, created_at) -VALUES (1, 'testUser', '남', 20, '테스트입니다.', now()); +INSERT INTO klkl_user(user_id, name, description, created_at) +VALUES (1, 'testUser', '테스트입니다.', now()), + (2, 'dummy', '덤덤댄스', now()), + (3, 'dumdummy', '덤더미댄스', now()); /* Like */ @@ -133,15 +135,16 @@ VALUES /* Product */ INSERT INTO product(product_id, user_id, name, description, address, price, like_count, rating, city_id, subcategory_id, currency_id, created_at) -VALUES (101, 1, '곤약젤리', '탱글탱글 맛있는 곤약젤리', '신사이바시 메가돈키호테', 1000, 100, 5.0, 414, 311, 438, now()), - (102, 1, '여름 원피스', '시원하고 여름 휴양지 느낌의 원피스', '방콕 짜뚜짝 시장', 300, 333, 4.5, 425, 323, 441, now()), +VALUES (101, 2, '곤약젤리', '탱글탱글 맛있는 곤약젤리', '신사이바시 메가돈키호테', 1000, 100, 5.0, 414, 311, 438, now()), + (102, 2, '여름 원피스', '시원하고 여름 휴양지 느낌의 원피스', '방콕 짜뚜짝 시장', 300, 333, 4.5, 425, 323, 441, now()), + (103, 3, '하오하오 봉지라면 핑크색', '새우맛이 나는 맛있는 라면', '롯데마트 나트랑', 15000, 500, 5.0, 429, 310, 442, now()), (390, 1, '왕족발 보쌈 과자', '맛있는 왕족발 보쌈 과자', '상하이 장충동', 3000, 10, 3.0, 422, 311, 439, now()); /* Comment */ INSERT INTO comment(comment_id, product_id, user_id, content, created_at) -VALUES (500, 390, 1, '이거 정말 맛있는데 표현할 방법이 읎네.', now()), - (501, 390, 1, '이거 정말 맛없는데 표현할 방법이 읎네.', now()), - (502, 390, 1, '이거 정말 좋은데 표현할 방법이 읎네.', now()); +VALUES (500, 101, 1, '이거 정말 맛있는데 표현할 방법이 읎네.', now()), + (501, 390, 2, '이거 정말 맛없는데 표현할 방법이 읎네.', now()), + (502, 390, 3, '이거 정말 좋은데 표현할 방법이 읎네.', now()); /* Notification */ INSERT INTO notification(notification_id, is_read, created_at, comment_id) diff --git a/src/test/java/taco/klkl/domain/comment/controller/CommentControllerTest.java b/src/test/java/taco/klkl/domain/comment/controller/CommentControllerTest.java index b6321890..484e8071 100644 --- a/src/test/java/taco/klkl/domain/comment/controller/CommentControllerTest.java +++ b/src/test/java/taco/klkl/domain/comment/controller/CommentControllerTest.java @@ -41,7 +41,6 @@ import taco.klkl.domain.region.domain.currency.CurrencyType; import taco.klkl.domain.region.domain.region.Region; import taco.klkl.domain.region.domain.region.RegionType; -import taco.klkl.domain.user.domain.Gender; import taco.klkl.domain.user.domain.User; import taco.klkl.domain.user.dto.request.UserCreateRequest; import taco.klkl.global.error.exception.ErrorCode; @@ -66,14 +65,10 @@ public class CommentControllerTest { private final UserCreateRequest requestDto = new UserCreateRequest( "이상화", - "남", - 19, "저는 이상화입니다." ); private final User user = User.of( requestDto.name(), - Gender.from(requestDto.gender()), - requestDto.age(), requestDto.description() ); diff --git a/src/test/java/taco/klkl/domain/comment/integration/CommentIntegrationTest.java b/src/test/java/taco/klkl/domain/comment/integration/CommentIntegrationTest.java index 1438c89f..9f406b78 100644 --- a/src/test/java/taco/klkl/domain/comment/integration/CommentIntegrationTest.java +++ b/src/test/java/taco/klkl/domain/comment/integration/CommentIntegrationTest.java @@ -35,7 +35,7 @@ public class CommentIntegrationTest { @Autowired private ObjectMapper objectMapper; - private final Long productId = 390L; + private final Long productId = 101L; private final Long commentId = 500L; private final CommentCreateUpdateRequest commentCreateRequestDto = new CommentCreateUpdateRequest( @@ -142,7 +142,7 @@ public void testUpdateCommentWhenProductNotFound() throws Exception { @DisplayName("댓글 수정시 존재하는 상품이지만 댓글에 저장된 상품 Id와 달라 실패하는 경우 테스트") public void testUpdateCommentWhenExistProductButNotMatchWithComment() throws Exception { //given - Long differentProductId = 101L; + Long differentProductId = 102L; //when & given mockMvc.perform(put("/v1/products/{wrongProductId}/comments/{commentId}", differentProductId, commentId) @@ -201,7 +201,7 @@ public void testDeleteCommentWhenProductNotFound() throws Exception { @DisplayName("댓글 삭제시 존재하는 상품이지만 댓글에 저장된 상품 Id와 달라 실패하는 경우 테스트") public void testDeleteCommentWhenExistProductButNotMatchWithComment() throws Exception { //given - Long differentProductId = 101L; + Long differentProductId = 102L; //when & given mockMvc.perform(delete("/v1/products/{wrongProductId}/comments/{commentId}", differentProductId, commentId) diff --git a/src/test/java/taco/klkl/domain/comment/service/CommentServiceTest.java b/src/test/java/taco/klkl/domain/comment/service/CommentServiceImplTest.java similarity index 97% rename from src/test/java/taco/klkl/domain/comment/service/CommentServiceTest.java rename to src/test/java/taco/klkl/domain/comment/service/CommentServiceImplTest.java index 19d63559..510017ff 100644 --- a/src/test/java/taco/klkl/domain/comment/service/CommentServiceTest.java +++ b/src/test/java/taco/klkl/domain/comment/service/CommentServiceImplTest.java @@ -24,7 +24,6 @@ import taco.klkl.domain.notification.service.NotificationService; import taco.klkl.domain.product.domain.Product; import taco.klkl.domain.product.exception.ProductNotFoundException; -import taco.klkl.domain.user.domain.Gender; import taco.klkl.domain.user.domain.User; import taco.klkl.domain.user.dto.request.UserCreateRequest; import taco.klkl.global.util.ProductUtil; @@ -32,7 +31,7 @@ @ExtendWith(MockitoExtension.class) @Transactional -public class CommentServiceTest { +public class CommentServiceImplTest { @Mock private CommentRepository commentRepository; @@ -50,15 +49,11 @@ public class CommentServiceTest { private final UserCreateRequest userRequestDto = new UserCreateRequest( "이상화", - "남", - 19, "저는 이상화입니다." ); private final User user = User.of( userRequestDto.name(), - Gender.from(userRequestDto.gender()), - userRequestDto.age(), userRequestDto.description() ); @@ -105,7 +100,7 @@ public void testCreateComment() { final Long productId = 1L; final Comment comment = Comment.of(product, user, "이거 진짜에요?"); - when(userUtil.findTestUser()).thenReturn(user); + when(userUtil.getCurrentUser()).thenReturn(user); when(commentRepository.save(any(Comment.class))).thenReturn(comment); //when diff --git a/src/test/java/taco/klkl/domain/like/controller/LikeControllerTest.java b/src/test/java/taco/klkl/domain/like/controller/LikeControllerTest.java index 292d66d5..39c815f7 100644 --- a/src/test/java/taco/klkl/domain/like/controller/LikeControllerTest.java +++ b/src/test/java/taco/klkl/domain/like/controller/LikeControllerTest.java @@ -48,7 +48,7 @@ void testPostLike() throws Exception { // when & then mockMvc.perform(post("/v1/products/{productId}/likes", productId)) - .andExpect(status().isOk()) + .andExpect(status().isCreated()) .andExpect(jsonPath("$.isSuccess", is(true))) .andExpect(jsonPath("$.data.isLiked", is(true))) .andExpect(jsonPath("$.data.likeCount", is(1))); diff --git a/src/test/java/taco/klkl/domain/like/integration/LikeIntegrationTest.java b/src/test/java/taco/klkl/domain/like/integration/LikeIntegrationTest.java index 5c18859b..67a8fac4 100644 --- a/src/test/java/taco/klkl/domain/like/integration/LikeIntegrationTest.java +++ b/src/test/java/taco/klkl/domain/like/integration/LikeIntegrationTest.java @@ -75,7 +75,7 @@ void afterEach() { void testPostLike() throws Exception { // when & then mockMvc.perform(post("/v1/products/{productId}/likes", product.getId())) - .andExpect(status().isOk()) + .andExpect(status().isCreated()) .andExpect(jsonPath("$.data.isLiked", is(true))) .andExpect(jsonPath("$.data.likeCount", is(1))); @@ -97,12 +97,12 @@ void testDeleteLike() throws Exception { void testPostLikeMultiple() throws Exception { // when & then mockMvc.perform(post("/v1/products/{productId}/likes", product.getId())) - .andExpect(status().isOk()) + .andExpect(status().isCreated()) .andExpect(jsonPath("$.data.isLiked", is(true))) .andExpect(jsonPath("$.data.likeCount", is(1))); mockMvc.perform(post("/v1/products/{productId}/likes", product.getId())) - .andExpect(status().isOk()) + .andExpect(status().isCreated()) .andExpect(jsonPath("$.data.isLiked", is(true))) .andExpect(jsonPath("$.data.likeCount", is(1))); diff --git a/src/test/java/taco/klkl/domain/like/service/LikeServiceImplTest.java b/src/test/java/taco/klkl/domain/like/service/LikeServiceImplTest.java index 9f836954..58367646 100644 --- a/src/test/java/taco/klkl/domain/like/service/LikeServiceImplTest.java +++ b/src/test/java/taco/klkl/domain/like/service/LikeServiceImplTest.java @@ -57,7 +57,7 @@ void testCreateLike() { Long productId = 1L; when(productUtil.findProductEntityById(productId)).thenReturn(product); - when(userUtil.findCurrentUser()).thenReturn(user); + when(userUtil.getCurrentUser()).thenReturn(user); when(likeRepository.existsByProductAndUser(product, user)).thenReturn(false); when(productService.increaseLikeCount(product)).thenReturn(1); LikeResponse likeResponse = LikeResponse.of(true, 1); @@ -79,7 +79,7 @@ void testCreateWhenAlreadyExists() { Long productId = 1L; when(productUtil.findProductEntityById(productId)).thenReturn(product); - when(userUtil.findCurrentUser()).thenReturn(user); + when(userUtil.getCurrentUser()).thenReturn(user); when(likeRepository.existsByProductAndUser(product, user)).thenReturn(true); when(product.getLikeCount()).thenReturn(1); LikeResponse likeResponse = LikeResponse.of(true, product.getLikeCount()); @@ -104,7 +104,7 @@ void testDeleteLike() { Long productId = 1L; when(productUtil.findProductEntityById(productId)).thenReturn(product); - when(userUtil.findCurrentUser()).thenReturn(user); + when(userUtil.getCurrentUser()).thenReturn(user); when(likeRepository.existsByProductAndUser(product, user)).thenReturn(true); when(productService.decreaseLikeCount(product)).thenReturn(0); when(product.getLikeCount()).thenReturn(0); @@ -126,7 +126,7 @@ void testDeleteLikeWhenNotExists() { Long productId = 1L; when(productUtil.findProductEntityById(productId)).thenReturn(product); - when(userUtil.findCurrentUser()).thenReturn(user); + when(userUtil.getCurrentUser()).thenReturn(user); when(likeRepository.existsByProductAndUser(product, user)).thenReturn(false); when(product.getLikeCount()).thenReturn(1); LikeResponse likeResponse = LikeResponse.of(false, product.getLikeCount()); @@ -151,7 +151,7 @@ void testCreateLikeMaximumError() { Long productId = 1L; when(productUtil.findProductEntityById(productId)).thenReturn(product); - when(userUtil.findCurrentUser()).thenReturn(user); + when(userUtil.getCurrentUser()).thenReturn(user); when(likeRepository.existsByProductAndUser(product, user)).thenReturn(false); when(productService.increaseLikeCount(product)).thenThrow(LikeCountOverMaximumException.class); doThrow(LikeCountOverMaximumException.class).when(product).increaseLikeCount(); @@ -169,7 +169,7 @@ void testCreateLikeMinimumError() { Long productId = 1L; when(productUtil.findProductEntityById(productId)).thenReturn(product); - when(userUtil.findCurrentUser()).thenReturn(user); + when(userUtil.getCurrentUser()).thenReturn(user); when(likeRepository.existsByProductAndUser(product, user)).thenReturn(true); when(productService.decreaseLikeCount(product)).thenThrow(LikeCountBelowMinimumException.class); doThrow(LikeCountBelowMinimumException.class).when(product).decreaseLikeCount(); diff --git a/src/test/java/taco/klkl/domain/notification/controller/NotificationControllerTest.java b/src/test/java/taco/klkl/domain/notification/controller/NotificationControllerTest.java index 1c7650b5..7f2006ab 100644 --- a/src/test/java/taco/klkl/domain/notification/controller/NotificationControllerTest.java +++ b/src/test/java/taco/klkl/domain/notification/controller/NotificationControllerTest.java @@ -35,7 +35,6 @@ import taco.klkl.domain.region.domain.currency.Currency; import taco.klkl.domain.region.domain.region.Region; import taco.klkl.domain.user.domain.User; -import taco.klkl.global.common.constants.UserConstants; @WebMvcTest(NotificationController.class) class NotificationControllerTest { @@ -55,7 +54,7 @@ class NotificationControllerTest { @MockBean NotificationService notificationService; - private final User user = UserConstants.TEST_USER; + private final User user = User.of("testUser", "테스트입니다."); private final Country country = Country.of(CountryType.MALAYSIA, region, "photo", currency); private final City city = City.of(CityType.BORACAY, country); private final Category category = Category.of(CategoryType.CLOTHES); diff --git a/src/test/java/taco/klkl/domain/notification/integration/NotificationIntegrationTest.java b/src/test/java/taco/klkl/domain/notification/integration/NotificationIntegrationTest.java index 8c4e705b..66b15288 100644 --- a/src/test/java/taco/klkl/domain/notification/integration/NotificationIntegrationTest.java +++ b/src/test/java/taco/klkl/domain/notification/integration/NotificationIntegrationTest.java @@ -59,7 +59,7 @@ public void testReadAllNotifications() throws Exception { @DisplayName("단일 알림 읽음 테스트 - 성공") public void testReadOneNotification() throws Exception { //given - Long notificationId = 700L; + Long notificationId = 701L; //when & then mockMvc.perform(put("/v1/notifications/{notificationId}/read", notificationId) diff --git a/src/test/java/taco/klkl/domain/notification/service/NotificationServiceTest.java b/src/test/java/taco/klkl/domain/notification/service/NotificationServiceImplTest.java similarity index 90% rename from src/test/java/taco/klkl/domain/notification/service/NotificationServiceTest.java rename to src/test/java/taco/klkl/domain/notification/service/NotificationServiceImplTest.java index 09ea8b7c..ec04c9b1 100644 --- a/src/test/java/taco/klkl/domain/notification/service/NotificationServiceTest.java +++ b/src/test/java/taco/klkl/domain/notification/service/NotificationServiceImplTest.java @@ -44,14 +44,13 @@ import taco.klkl.domain.region.domain.currency.CurrencyType; import taco.klkl.domain.region.domain.region.Region; import taco.klkl.domain.region.domain.region.RegionType; -import taco.klkl.domain.user.domain.Gender; import taco.klkl.domain.user.domain.QUser; import taco.klkl.domain.user.domain.User; import taco.klkl.global.util.UserUtil; @ExtendWith(MockitoExtension.class) @Transactional -public class NotificationServiceTest { +public class NotificationServiceImplTest { @Mock private NotificationRepository notificationRepository; @@ -79,8 +78,6 @@ public class NotificationServiceTest { public void setUp() { commentUser = User.of( "윤상정", - Gender.FEMALE, - 26, "나는 해적왕이 될 사나이다."); Region region = Region.from(RegionType.SOUTHEAST_ASIA); @@ -127,7 +124,7 @@ public void testFindAllNotifications() { QNotification notification = QNotification.notification; QUser user = QUser.user; - when(userUtil.findTestUser()).thenReturn(mockUser); + when(userUtil.getCurrentUser()).thenReturn(mockUser); when(mockNotification.getId()).thenReturn(1L); when(mockNotification.getIsRead()).thenReturn(false); when(mockNotification.getCreatedAt()).thenReturn(LocalDateTime.now()); @@ -157,7 +154,7 @@ public void testGetBlankNotifications() { QNotification notification = QNotification.notification; QUser user = QUser.user; - when(userUtil.findTestUser()).thenReturn(mockUser); + when(userUtil.getCurrentUser()).thenReturn(mockUser); when(queryFactory.selectFrom(any(QNotification.class))).thenReturn(mockQuery); when(mockQuery.join(notification.comment.product.user, user)).thenReturn(mockQuery); @@ -181,16 +178,18 @@ public void testReadAllNotifications() { List notificationList = List.of(notification1, notification2); - when(userUtil.findTestUser()).thenReturn(mockUser); - when(notificationRepository.findAllByComment_Product_User(mockUser)).thenReturn(notificationList); - when(notificationRepository.count()).thenReturn(2L); + when(userUtil.getCurrentUser()).thenReturn(mockUser); + when(notificationRepository.findByComment_Product_User(mockUser)).thenReturn(notificationList); //when NotificationUpdateResponse response = notificationService.readAllNotifications(); //then assertThat(response.updatedCount()).isEqualTo(2L); - verify(notificationRepository).findAllByComment_Product_User(mockUser); + verify(notificationRepository).findByComment_Product_User(mockUser); + + assertTrue(notification1.getIsRead()); + assertTrue(notification2.getIsRead()); } @Test @@ -199,14 +198,16 @@ public void testReadNotificationsById() { //given Notification notification = Notification.of(comment); - when(notificationRepository.findById(any(Long.class))).thenReturn(Optional.of(notification)); + when(userUtil.getCurrentUser()).thenReturn(mockUser); + when(notificationRepository.findById(1L)).thenReturn(Optional.of(notification)); //when - NotificationUpdateResponse response = notificationService.readNotificationById(any(Long.class)); + NotificationUpdateResponse response = notificationService.readNotificationById(1L); //then assertThat(response.updatedCount()).isEqualTo(1L); - verify(notificationRepository).findById(any(Long.class)); + verify(notificationRepository).findById(1L); + assertTrue(notification.getIsRead()); } @Test diff --git a/src/test/java/taco/klkl/domain/product/controller/ProductControllerTest.java b/src/test/java/taco/klkl/domain/product/controller/ProductControllerTest.java index 3084da8d..a0e472d6 100644 --- a/src/test/java/taco/klkl/domain/product/controller/ProductControllerTest.java +++ b/src/test/java/taco/klkl/domain/product/controller/ProductControllerTest.java @@ -38,7 +38,7 @@ import taco.klkl.domain.region.dto.response.city.CityResponse; import taco.klkl.domain.region.dto.response.currency.CurrencyResponse; import taco.klkl.domain.user.dto.response.UserDetailResponse; -import taco.klkl.global.common.response.PagedResponseDto; +import taco.klkl.global.common.response.PagedResponse; @WebMvcTest(ProductController.class) public class ProductControllerTest { @@ -132,7 +132,7 @@ void setUp() { void testFindProducts_ShouldReturnPagedProductsByFilteringAndSorting() throws Exception { // Given List products = List.of(productSimpleResponse); - PagedResponseDto pagedResponse = new PagedResponseDto<>( + PagedResponse pagedResponse = new PagedResponse<>( products, 0, 10, 1, 1, true ); when(productService.findProductsByFilterOptionsAndSortOptions( @@ -200,7 +200,7 @@ void testFindProducts_ShouldReturnPagedProductsByFilteringAndSorting() throws Ex void testFindProductsByPartialNameAndSortOption() throws Exception { // Given List products = List.of(productSimpleResponse); - PagedResponseDto pagedResponse = new PagedResponseDto<>( + PagedResponse pagedResponse = new PagedResponse<>( products, 0, 10, 1, 1, true ); when(productService.findProductsByPartialName( diff --git a/src/test/java/taco/klkl/domain/product/integration/ProductIntegrationTest.java b/src/test/java/taco/klkl/domain/product/integration/ProductIntegrationTest.java index c1c7b980..33e7f770 100644 --- a/src/test/java/taco/klkl/domain/product/integration/ProductIntegrationTest.java +++ b/src/test/java/taco/klkl/domain/product/integration/ProductIntegrationTest.java @@ -492,12 +492,13 @@ public void testGetProductsBySingleSubcategoryId() throws Exception { .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.isSuccess", is(true))) - .andExpect(jsonPath("$.data.content", hasSize(2))) - .andExpect(jsonPath("$.data.content[0].name", is(createRamenRequest1.name()))) - .andExpect(jsonPath("$.data.content[1].name", is(createRamenRequest2.name()))) + .andExpect(jsonPath("$.data.content", hasSize(3))) + .andExpect(jsonPath("$.data.content[0].name", is("하오하오 봉지라면 핑크색"))) + .andExpect(jsonPath("$.data.content[1].name", is(createRamenRequest1.name()))) + .andExpect(jsonPath("$.data.content[2].name", is(createRamenRequest2.name()))) .andExpect(jsonPath("$.data.pageNumber", is(0))) .andExpect(jsonPath("$.data.pageSize", is(ProductConstants.DEFAULT_PAGE_SIZE))) - .andExpect(jsonPath("$.data.totalElements", is(2))) + .andExpect(jsonPath("$.data.totalElements", is(3))) .andExpect(jsonPath("$.data.totalPages", is(1))) .andExpect(jsonPath("$.data.last", is(true))) .andExpect(jsonPath("$.timestamp", notNullValue())); @@ -564,13 +565,14 @@ public void testGetProductsByMultipleSubcategoryIds() throws Exception { .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.isSuccess", is(true))) - .andExpect(jsonPath("$.data.content", hasSize(3))) - .andExpect(jsonPath("$.data.content[0].name", is(createRamenRequest1.name()))) - .andExpect(jsonPath("$.data.content[1].name", is(createRamenRequest2.name()))) - .andExpect(jsonPath("$.data.content[2].name", is(createShoeRequest.name()))) + .andExpect(jsonPath("$.data.content", hasSize(4))) + .andExpect(jsonPath("$.data.content[0].name", is("하오하오 봉지라면 핑크색"))) + .andExpect(jsonPath("$.data.content[1].name", is(createRamenRequest1.name()))) + .andExpect(jsonPath("$.data.content[2].name", is(createRamenRequest2.name()))) + .andExpect(jsonPath("$.data.content[3].name", is(createShoeRequest.name()))) .andExpect(jsonPath("$.data.pageNumber", is(0))) .andExpect(jsonPath("$.data.pageSize", is(ProductConstants.DEFAULT_PAGE_SIZE))) - .andExpect(jsonPath("$.data.totalElements", is(3))) + .andExpect(jsonPath("$.data.totalElements", is(4))) .andExpect(jsonPath("$.data.totalPages", is(1))) .andExpect(jsonPath("$.data.last", is(true))) .andExpect(jsonPath("$.timestamp", notNullValue())); @@ -1056,9 +1058,10 @@ public void testSortProductsByLikeCountDesc() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.isSuccess", is(true))) .andExpect(jsonPath("$.data.content", hasSize(all.size()))) - .andExpect(jsonPath("$.data.content[0].name", is("여름 원피스"))) - .andExpect(jsonPath("$.data.content[1].name", is("곤약젤리"))) - .andExpect(jsonPath("$.data.content[2].name", is("왕족발 보쌈 과자"))) + .andExpect(jsonPath("$.data.content[0].name", is("하오하오 봉지라면 핑크색"))) + .andExpect(jsonPath("$.data.content[1].name", is("여름 원피스"))) + .andExpect(jsonPath("$.data.content[2].name", is("곤약젤리"))) + .andExpect(jsonPath("$.data.content[3].name", is("왕족발 보쌈 과자"))) .andExpect(jsonPath("$.data.pageNumber", is(0))) .andExpect(jsonPath("$.data.pageSize", is(ProductConstants.DEFAULT_PAGE_SIZE))) .andExpect(jsonPath("$.data.totalElements", is(all.size()))) diff --git a/src/test/java/taco/klkl/domain/product/service/ProductServiceImplTest.java b/src/test/java/taco/klkl/domain/product/service/ProductServiceImplTest.java index 2b9e9227..8dc9ef7d 100644 --- a/src/test/java/taco/klkl/domain/product/service/ProductServiceImplTest.java +++ b/src/test/java/taco/klkl/domain/product/service/ProductServiceImplTest.java @@ -62,8 +62,7 @@ import taco.klkl.domain.region.domain.region.Region; import taco.klkl.domain.region.domain.region.RegionType; import taco.klkl.domain.user.domain.User; -import taco.klkl.global.common.constants.UserConstants; -import taco.klkl.global.common.response.PagedResponseDto; +import taco.klkl.global.common.response.PagedResponse; import taco.klkl.global.util.CityUtil; import taco.klkl.global.util.CurrencyUtil; import taco.klkl.global.util.SubcategoryUtil; @@ -109,7 +108,7 @@ class ProductServiceImplTest { void setUp() { MockitoAnnotations.openMocks(this); - user = UserConstants.TEST_USER; + user = User.of("testUser", "테스트입니다."); Region region = Region.from(RegionType.SOUTHEAST_ASIA); currency = Currency.of( @@ -213,7 +212,7 @@ void testFindProductsByFilterOptionsAndSortOptions() { when(tagUtil.findTagEntityById(anyLong())).thenReturn(mockTag); // When - PagedResponseDto result = productService + PagedResponse result = productService .findProductsByFilterOptionsAndSortOptions(pageable, filterOptions, sortOptions); // Then @@ -294,7 +293,7 @@ void testFindProductsByPartialNameAndSortOption() { when(productQuery.orderBy(any(OrderSpecifier.class))).thenReturn(productQuery); // When - PagedResponseDto result = productService + PagedResponse result = productService .findProductsByPartialName("name", pageable, sortOptions); // Then @@ -376,7 +375,7 @@ void testFindProductById_NotFound() { @DisplayName("상품 생성 - 성공") void testCreateProduct() { // Given - when(userUtil.findTestUser()).thenReturn(user); + when(userUtil.getCurrentUser()).thenReturn(user); when(cityUtil.findCityEntityById(1L)).thenReturn(city); when(subcategoryUtil.findSubcategoryEntityById(1L)).thenReturn(subcategory); when(currencyUtil.findCurrencyEntityById(1L)).thenReturn(currency); @@ -414,6 +413,7 @@ void testCreateProduct() { void testUpdateProduct() { // Given when(productRepository.findById(1L)).thenReturn(Optional.of(testProduct)); + when(userUtil.getCurrentUser()).thenReturn(user); when(cityUtil.findCityEntityById(1L)).thenReturn(city); when(subcategoryUtil.findSubcategoryEntityById(1L)).thenReturn(subcategory); when(currencyUtil.findCurrencyEntityById(1L)).thenReturn(currency); @@ -442,6 +442,7 @@ void testUpdateProduct_NotFound() { void testDeleteProduct() { // Given when(productRepository.findById(1L)).thenReturn(Optional.of(testProduct)); + when(userUtil.getCurrentUser()).thenReturn(user); // When productService.deleteProduct(1L); diff --git a/src/test/java/taco/klkl/domain/user/controller/UserControllerTest.java b/src/test/java/taco/klkl/domain/user/controller/UserControllerTest.java index 7a0c16bf..047aea50 100644 --- a/src/test/java/taco/klkl/domain/user/controller/UserControllerTest.java +++ b/src/test/java/taco/klkl/domain/user/controller/UserControllerTest.java @@ -1,24 +1,24 @@ package taco.klkl.domain.user.controller; import static org.hamcrest.Matchers.*; +import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; -import taco.klkl.domain.user.domain.Gender; import taco.klkl.domain.user.domain.User; import taco.klkl.domain.user.dto.response.UserDetailResponse; import taco.klkl.domain.user.service.UserService; import taco.klkl.global.common.constants.UserConstants; +import taco.klkl.global.util.UserUtil; @WebMvcTest(UserController.class) class UserControllerTest { @@ -29,11 +29,15 @@ class UserControllerTest { @MockBean UserService userService; + @MockBean + UserUtil userUtil; + + private User user; private UserDetailResponse userDetailResponse; @BeforeEach public void setUp() { - final User user = User.of("name", Gender.MALE, 20, "description"); + user = User.of("name", "description"); userDetailResponse = UserDetailResponse.from(user); } @@ -41,7 +45,8 @@ public void setUp() { @DisplayName("내 정보 조회 API 테스트") public void testGetMe() throws Exception { // given - Mockito.when(userService.getCurrentUser()).thenReturn(userDetailResponse); + when(userUtil.getCurrentUser()).thenReturn(user); + when(userService.getUserById(any())).thenReturn(userDetailResponse); // when & then mockMvc.perform(get("/v1/users/me") diff --git a/src/test/java/taco/klkl/domain/user/dto/request/UserRequestDtoTest.java b/src/test/java/taco/klkl/domain/user/dto/request/UserRequestDtoTest.java index 599db180..d4aa28e4 100644 --- a/src/test/java/taco/klkl/domain/user/dto/request/UserRequestDtoTest.java +++ b/src/test/java/taco/klkl/domain/user/dto/request/UserRequestDtoTest.java @@ -24,7 +24,7 @@ public UserRequestDtoTest() { @DisplayName("유효한 UserCreateRequestDto에 대한 유효성 검사") public void testValidUserCreateRequestDto() { // given - UserCreateRequest requestDto = new UserCreateRequest("이름", "남", 20, "자기소개"); + UserCreateRequest requestDto = new UserCreateRequest("이름", "자기소개"); // when Set> violations = validator.validate(requestDto); @@ -37,20 +37,7 @@ public void testValidUserCreateRequestDto() { @DisplayName("이름이 null인 UserCreateRequest 유효성 검사") public void testInvalidUserCreateRequestDto_NameRequired() { // given - UserCreateRequest requestDto = new UserCreateRequest(null, "남", 20, "자기소개"); - - // when - Set> violations = validator.validate(requestDto); - - // then - assertThat(violations).isNotEmpty(); - } - - @Test - @DisplayName("나이가 음수인 UserCreateRequest 유효성 검사") - public void testInvalidUserCreateRequestDto_AgeNegative() { - // given - UserCreateRequest requestDto = new UserCreateRequest("이름", "남", -1, "자기소개"); + UserCreateRequest requestDto = new UserCreateRequest(null, "자기소개"); // when Set> violations = validator.validate(requestDto); diff --git a/src/test/java/taco/klkl/domain/user/dto/response/UserDetailResponseTest.java b/src/test/java/taco/klkl/domain/user/dto/response/UserDetailResponseTest.java index d8bcfa72..49d3b906 100644 --- a/src/test/java/taco/klkl/domain/user/dto/response/UserDetailResponseTest.java +++ b/src/test/java/taco/klkl/domain/user/dto/response/UserDetailResponseTest.java @@ -5,7 +5,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import taco.klkl.domain.user.domain.Gender; import taco.klkl.domain.user.domain.User; import taco.klkl.global.common.constants.UserConstants; @@ -36,12 +35,10 @@ public void testUserDetailResponseDto() { public void testFrom() { // given String name = "이름"; - Gender gender = Gender.MALE; - int age = 20; String description = "자기소개"; // when - User user = User.of(name, gender, age, description); + User user = User.of(name, description); UserDetailResponse userDetail = UserDetailResponse.from(user); // then diff --git a/src/test/java/taco/klkl/domain/user/dto/response/UserSimpleResponseTest.java b/src/test/java/taco/klkl/domain/user/dto/response/UserSimpleResponseTest.java index 64e93c5a..a1e4243d 100644 --- a/src/test/java/taco/klkl/domain/user/dto/response/UserSimpleResponseTest.java +++ b/src/test/java/taco/klkl/domain/user/dto/response/UserSimpleResponseTest.java @@ -5,7 +5,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import taco.klkl.domain.user.domain.Gender; import taco.klkl.domain.user.domain.User; class UserSimpleResponseTest { @@ -31,12 +30,10 @@ public void testUserSimpleResponseDto() { public void testFrom() { // given String name = "이름"; - Gender gender = Gender.MALE; - int age = 20; String description = "자기소개"; // when - User user = User.of(name, gender, age, description); + User user = User.of(name, description); UserSimpleResponse userSimple = UserSimpleResponse.from(user); // then diff --git a/src/test/java/taco/klkl/domain/user/integration/UserIntegrationTest.java b/src/test/java/taco/klkl/domain/user/integration/UserIntegrationTest.java index ece3cee4..f04a794a 100644 --- a/src/test/java/taco/klkl/domain/user/integration/UserIntegrationTest.java +++ b/src/test/java/taco/klkl/domain/user/integration/UserIntegrationTest.java @@ -12,8 +12,10 @@ import org.springframework.transaction.annotation.Transactional; import taco.klkl.domain.user.dao.UserRepository; +import taco.klkl.domain.user.domain.User; import taco.klkl.domain.user.dto.response.UserDetailResponse; import taco.klkl.domain.user.service.UserService; +import taco.klkl.global.util.UserUtil; @SpringBootTest @AutoConfigureMockMvc @@ -28,10 +30,14 @@ public class UserIntegrationTest { @Autowired UserRepository userRepository; + @Autowired + private UserUtil userUtil; + @Test public void testUserMe() throws Exception { // given, when - UserDetailResponse currentUser = userService.getCurrentUser(); + User me = userUtil.getCurrentUser(); + UserDetailResponse currentUser = userService.getUserById(me.getId()); // then mockMvc.perform(get("/v1/users/me")) diff --git a/src/test/java/taco/klkl/domain/user/service/UserServiceImplTest.java b/src/test/java/taco/klkl/domain/user/service/UserServiceImplTest.java index 5bc3084d..19d4413e 100644 --- a/src/test/java/taco/klkl/domain/user/service/UserServiceImplTest.java +++ b/src/test/java/taco/klkl/domain/user/service/UserServiceImplTest.java @@ -3,6 +3,8 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import java.util.Optional; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -13,7 +15,6 @@ import org.slf4j.LoggerFactory; import taco.klkl.domain.user.dao.UserRepository; -import taco.klkl.domain.user.domain.Gender; import taco.klkl.domain.user.domain.User; import taco.klkl.domain.user.dto.request.UserCreateRequest; import taco.klkl.domain.user.dto.response.UserDetailResponse; @@ -35,17 +36,17 @@ class UserServiceImplTest { @Test @DisplayName("내 정보 조회 서비스 테스트") - public void testGetCurrentUser() { + public void testGetUserById() { // given User user = mock(User.class); - when(userUtil.findCurrentUser()).thenReturn(user); when(user.getId()).thenReturn(1L); when(user.getName()).thenReturn("testUser"); when(user.getDescription()).thenReturn("테스트입니다."); + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); // when - UserDetailResponse userDto = userServiceImpl.getCurrentUser(); + UserDetailResponse userDto = userServiceImpl.getUserById(1L); // then assertThat(userDto.id()).isEqualTo(user.getId()); @@ -60,14 +61,10 @@ public void testCreateUser() { // given UserCreateRequest requestDto = new UserCreateRequest( "이상화", - "남", - 19, "저는 이상화입니다." ); User user = User.of( requestDto.name(), - Gender.from(requestDto.gender()), - requestDto.age(), requestDto.description() ); when(userRepository.save(any(User.class))).thenReturn(user);