From e36a9c6735fa341f7e8b1dba414d5e103b9ac3b9 Mon Sep 17 00:00:00 2001 From: sim-mer Date: Thu, 3 Oct 2024 03:55:06 +0900 Subject: [PATCH 01/60] =?UTF-8?q?#27=20add:=20JpaAuditing=EC=9D=84=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=95=9C=20BaseTimeEntity=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/helpmeCookies/Step3Application.java | 2 ++ .../global/entity/BaseTimeEntity.java | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 src/main/java/com/helpmeCookies/global/entity/BaseTimeEntity.java diff --git a/src/main/java/com/helpmeCookies/Step3Application.java b/src/main/java/com/helpmeCookies/Step3Application.java index c6bad29..a594415 100644 --- a/src/main/java/com/helpmeCookies/Step3Application.java +++ b/src/main/java/com/helpmeCookies/Step3Application.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication +@EnableJpaAuditing public class Step3Application { public static void main(String[] args) { diff --git a/src/main/java/com/helpmeCookies/global/entity/BaseTimeEntity.java b/src/main/java/com/helpmeCookies/global/entity/BaseTimeEntity.java new file mode 100644 index 0000000..9279fc3 --- /dev/null +++ b/src/main/java/com/helpmeCookies/global/entity/BaseTimeEntity.java @@ -0,0 +1,21 @@ +package com.helpmeCookies.global.entity; + +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import java.time.LocalDateTime; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +@Getter +public abstract class BaseTimeEntity { + + @CreatedDate + private LocalDateTime createdDate; + + @LastModifiedDate + private LocalDateTime modifiedDate; +} From 05c59068ae52819226879e16124cb714d10a9017 Mon Sep 17 00:00:00 2001 From: sim-mer Date: Thu, 3 Oct 2024 04:24:49 +0900 Subject: [PATCH 02/60] =?UTF-8?q?#27=20fix,=20add:=20ArtistInfo=20Id=20Gen?= =?UTF-8?q?erator=20=EC=88=98=EC=A0=95,=20name=20column=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/helpmeCookies/user/entity/ArtistInfo.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/helpmeCookies/user/entity/ArtistInfo.java b/src/main/java/com/helpmeCookies/user/entity/ArtistInfo.java index 83d86e7..4d74c5e 100644 --- a/src/main/java/com/helpmeCookies/user/entity/ArtistInfo.java +++ b/src/main/java/com/helpmeCookies/user/entity/ArtistInfo.java @@ -5,18 +5,23 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.validation.constraints.NotNull; @Entity public class ArtistInfo { @Id - @GeneratedValue(strategy = GenerationType.AUTO) + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @NotNull + private String name; + @Column(nullable = false) private Long totalFollowers; @Column(nullable = false) private Long totalLikes; + private String about; } From 3a3751b5b1d8f3d824dd23739af2b02dffa266b9 Mon Sep 17 00:00:00 2001 From: sim-mer Date: Thu, 3 Oct 2024 04:25:27 +0900 Subject: [PATCH 03/60] =?UTF-8?q?#27=20add:=20Product=20BaseTimeEntity=20?= =?UTF-8?q?=EC=83=81=EC=86=8D,=20ArtistInfo=20JoinColumn=20=EB=AA=85?= =?UTF-8?q?=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/helpmeCookies/product/entity/Product.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/helpmeCookies/product/entity/Product.java b/src/main/java/com/helpmeCookies/product/entity/Product.java index d44b972..584b32a 100644 --- a/src/main/java/com/helpmeCookies/product/entity/Product.java +++ b/src/main/java/com/helpmeCookies/product/entity/Product.java @@ -1,5 +1,7 @@ package com.helpmeCookies.product.entity; +import com.helpmeCookies.global.entity.BaseTimeEntity; +import jakarta.persistence.JoinColumn; import java.util.List; import com.helpmeCookies.user.entity.ArtistInfo; @@ -17,7 +19,7 @@ import lombok.Builder; @Entity -public class Product { +public class Product extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -48,6 +50,7 @@ public class Product { private List hashTags; @ManyToOne + @JoinColumn(name = "artist_info_id") private ArtistInfo artistInfo; public Product() {} From 2c9e4999b88899a0c79874268e80e334e544a85e Mon Sep 17 00:00:00 2001 From: sim-mer Date: Thu, 3 Oct 2024 04:26:55 +0900 Subject: [PATCH 04/60] =?UTF-8?q?#27=20feat(persistence):=20=EC=83=81?= =?UTF-8?q?=ED=92=88=20=EA=B2=80=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1(imageUrl=20=EC=B6=94=EA=B0=80=20=EC=98=88=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/repository/ProductRepository.java | 19 +++++++++++++++++++ .../product/repository/dto/ProductSearch.java | 9 +++++++++ 2 files changed, 28 insertions(+) create mode 100644 src/main/java/com/helpmeCookies/product/repository/dto/ProductSearch.java diff --git a/src/main/java/com/helpmeCookies/product/repository/ProductRepository.java b/src/main/java/com/helpmeCookies/product/repository/ProductRepository.java index d8b05f3..aca10a1 100644 --- a/src/main/java/com/helpmeCookies/product/repository/ProductRepository.java +++ b/src/main/java/com/helpmeCookies/product/repository/ProductRepository.java @@ -1,9 +1,28 @@ package com.helpmeCookies.product.repository; import com.helpmeCookies.product.entity.Product; +import com.helpmeCookies.product.repository.dto.ProductSearch; +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 ProductRepository extends JpaRepository { + + @Query("SELECT p.id AS id, p.name AS name, a.name AS artist, p.price AS price " + + "FROM Product p JOIN p.artistInfo a " + + "WHERE p.name LIKE %:query%") // Index 미사용 + Page findByName(@Param("query") String query, Pageable pageable); + + @Query(value = "SELECT p.id, p.name, a.name AS artist, p.price " + + "FROM product p JOIN artist_info a ON p.artist_info_id = a.id " + + "WHERE MATCH(p.name) AGAINST (:query IN BOOLEAN MODE)", + countQuery = "SELECT COUNT(*) " + + "FROM product p JOIN artist_info a ON p.artist_info_id = a.id " + + "WHERE MATCH(p.name) AGAINST (:query IN BOOLEAN MODE)", + nativeQuery = true) // Index 사용 + Page findByNameWithIdx(@Param("query") String query, Pageable pageable); } diff --git a/src/main/java/com/helpmeCookies/product/repository/dto/ProductSearch.java b/src/main/java/com/helpmeCookies/product/repository/dto/ProductSearch.java new file mode 100644 index 0000000..3c5fcee --- /dev/null +++ b/src/main/java/com/helpmeCookies/product/repository/dto/ProductSearch.java @@ -0,0 +1,9 @@ +package com.helpmeCookies.product.repository.dto; + + +public interface ProductSearch { + Long getId(); + String getName(); + String getArtist(); + Long getPrice(); +} From e5c750e646c28556721c50b260b281c1869ba92e Mon Sep 17 00:00:00 2001 From: sim-mer Date: Thu, 3 Oct 2024 04:27:10 +0900 Subject: [PATCH 05/60] =?UTF-8?q?#27=20feat(service):=20=EC=83=81=ED=92=88?= =?UTF-8?q?=20=EA=B2=80=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?(imageUrl=20=EC=B6=94=EA=B0=80=20=EC=98=88=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/service/ProductService.java | 11 ++++- .../product/service/dto/ProductPage.java | 44 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/helpmeCookies/product/service/dto/ProductPage.java diff --git a/src/main/java/com/helpmeCookies/product/service/ProductService.java b/src/main/java/com/helpmeCookies/product/service/ProductService.java index 1a16699..6b3c443 100644 --- a/src/main/java/com/helpmeCookies/product/service/ProductService.java +++ b/src/main/java/com/helpmeCookies/product/service/ProductService.java @@ -4,15 +4,22 @@ import com.helpmeCookies.product.entity.Category; import com.helpmeCookies.product.entity.Product; import com.helpmeCookies.product.repository.ProductRepository; +import com.helpmeCookies.product.service.dto.ProductPage; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service +@RequiredArgsConstructor public class ProductService { + private final ProductRepository productRepository; - public ProductService(ProductRepository productRepository) { - this.productRepository = productRepository; + @Transactional(readOnly = true) + public ProductPage.Paging getProductsByPage(String query, Pageable pageable) { + var productPage = productRepository.findByNameWithIdx(query, pageable); + return ProductPage.Paging.from(productPage); } public Product save(ProductRequest productSaveRequest) { diff --git a/src/main/java/com/helpmeCookies/product/service/dto/ProductPage.java b/src/main/java/com/helpmeCookies/product/service/dto/ProductPage.java new file mode 100644 index 0000000..0b2b920 --- /dev/null +++ b/src/main/java/com/helpmeCookies/product/service/dto/ProductPage.java @@ -0,0 +1,44 @@ +package com.helpmeCookies.product.service.dto; + +import com.helpmeCookies.product.repository.dto.ProductSearch; +import java.util.List; +import org.springframework.data.domain.Page; + +public class ProductPage { + + public record Info( + Long id, + String name, + String artist, + Long price + ) { + public static Info from(ProductSearch productSearch) { + return new Info( + productSearch.getId(), + productSearch.getName(), + productSearch.getArtist(), + productSearch.getPrice() + ); + } + + public static List of(List content) { + return content.stream() + .map(Info::from) + .toList(); + } + } + + public record Paging ( + boolean hasNext, + List products + ) { + + public static Paging from(Page productPage) { + return new Paging( + productPage.hasNext(), + Info.of(productPage.getContent()) + ); + } + } + +} From fd66d6d279cd72e41345a260e7ee65fcbe9a8e36 Mon Sep 17 00:00:00 2001 From: sim-mer Date: Thu, 3 Oct 2024 04:27:18 +0900 Subject: [PATCH 06/60] =?UTF-8?q?#27=20feat(presentation):=20=EC=83=81?= =?UTF-8?q?=ED=92=88=20=EA=B2=80=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1(imageUrl=20=EC=B6=94=EA=B0=80=20=EC=98=88=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/controller/ProductController.java | 36 +++++++++++++++---- .../product/util/ProductSort.java | 5 +++ .../helpmeCookies/product/util/SortUtil.java | 14 ++++++++ 3 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/helpmeCookies/product/util/ProductSort.java create mode 100644 src/main/java/com/helpmeCookies/product/util/SortUtil.java diff --git a/src/main/java/com/helpmeCookies/product/controller/ProductController.java b/src/main/java/com/helpmeCookies/product/controller/ProductController.java index c87732e..884a774 100644 --- a/src/main/java/com/helpmeCookies/product/controller/ProductController.java +++ b/src/main/java/com/helpmeCookies/product/controller/ProductController.java @@ -1,22 +1,33 @@ package com.helpmeCookies.product.controller; +import static com.helpmeCookies.product.util.SortUtil.convertProductSort; + import com.helpmeCookies.product.dto.ProductRequest; import com.helpmeCookies.product.dto.ProductResponse; import com.helpmeCookies.product.entity.Product; import com.helpmeCookies.product.service.ProductService; +import com.helpmeCookies.product.service.dto.ProductPage; +import com.helpmeCookies.product.util.ProductSort; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping("/api/v1/products") +@RequestMapping("/v1/products") +@RequiredArgsConstructor public class ProductController { private final ProductService productService; - public ProductController(ProductService productService) { - this.productService = productService; - } - @PostMapping public ResponseEntity saveProduct(@RequestBody ProductRequest productRequest) { Product product = productService.save(productRequest); @@ -41,4 +52,17 @@ public ResponseEntity deleteProduct(@PathVariable("productId") Long produc productService.delete(productId); return ResponseEntity.noContent().build(); } + + @GetMapping + public ResponseEntity getProductsByPage( + @RequestParam("query") String query, + @RequestParam(name = "size", required = false, defaultValue = "20") int size, + @RequestParam("page") int page, + @RequestParam("sort") ProductSort productSort + ) { + var sort = convertProductSort(productSort); + var pageable = PageRequest.of(page, size, sort); + + return ResponseEntity.ok(productService.getProductsByPage(query, pageable)); + } } diff --git a/src/main/java/com/helpmeCookies/product/util/ProductSort.java b/src/main/java/com/helpmeCookies/product/util/ProductSort.java new file mode 100644 index 0000000..1a37cec --- /dev/null +++ b/src/main/java/com/helpmeCookies/product/util/ProductSort.java @@ -0,0 +1,5 @@ +package com.helpmeCookies.product.util; + +public enum ProductSort { + RELEVANCE, LATEST +} diff --git a/src/main/java/com/helpmeCookies/product/util/SortUtil.java b/src/main/java/com/helpmeCookies/product/util/SortUtil.java new file mode 100644 index 0000000..3186acf --- /dev/null +++ b/src/main/java/com/helpmeCookies/product/util/SortUtil.java @@ -0,0 +1,14 @@ +package com.helpmeCookies.product.util; + +import org.springframework.data.domain.Sort; + +public class SortUtil { + + public static Sort convertProductSort(ProductSort productSort) { + return switch (productSort) { + case LATEST -> Sort.by(Sort.Order.desc("createdDate")); + default -> Sort.unsorted(); + }; + } + +} From a69772000d9f30c6055d3db584331654fc06e325 Mon Sep 17 00:00:00 2001 From: bokyeong Date: Wed, 16 Oct 2024 22:40:50 +0900 Subject: [PATCH 07/60] =?UTF-8?q?feat:=20[#45]=20=EA=B0=9C=EB=B3=84=20?= =?UTF-8?q?=EC=83=81=ED=92=88=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EB=A7=81=ED=81=AC=20=ED=8F=AC=ED=95=A8?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/controller/ProductController.java | 5 +++-- .../com/helpmeCookies/product/entity/ProductImage.java | 5 +++++ .../helpmeCookies/product/service/ProductImageService.java | 7 ++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/helpmeCookies/product/controller/ProductController.java b/src/main/java/com/helpmeCookies/product/controller/ProductController.java index c655ee7..379b545 100644 --- a/src/main/java/com/helpmeCookies/product/controller/ProductController.java +++ b/src/main/java/com/helpmeCookies/product/controller/ProductController.java @@ -30,7 +30,7 @@ public ResponseEntity saveProduct(@RequestBody ProductRequest productReque } @PostMapping("/{productId}/images") - public ResponseEntity uploadImages(@PathVariable("productId") Long productId, List files) throws IOException { + public ResponseEntity uploadImages(@PathVariable("productId") Long productId, List files) { List responses = productImageService.uploadMultiFiles(productId,files); return ResponseEntity.ok(new ProductImageResponse(responses.stream().map(FileUploadResponse::photoUrl).toList())); } @@ -38,7 +38,8 @@ public ResponseEntity uploadImages(@PathVariable("productI @GetMapping("/{productId}") public ResponseEntity getProductInfo(@PathVariable("productId") Long productId) { Product product = productService.find(productId); - return ResponseEntity.ok(ProductResponse.from(product)); + List urls = productImageService.getImages(productId); + return ResponseEntity.ok(ProductResponse.from(product,urls)); } @PutMapping("/{productId}") diff --git a/src/main/java/com/helpmeCookies/product/entity/ProductImage.java b/src/main/java/com/helpmeCookies/product/entity/ProductImage.java index 0a23f4f..ed6cf36 100644 --- a/src/main/java/com/helpmeCookies/product/entity/ProductImage.java +++ b/src/main/java/com/helpmeCookies/product/entity/ProductImage.java @@ -25,4 +25,9 @@ public ProductImage(String photoUrl, Long productId, String uuid) { this.productId = productId; this.uuid = uuid; } + + public String getPhotoUrl() { + return photoUrl; + } } + diff --git a/src/main/java/com/helpmeCookies/product/service/ProductImageService.java b/src/main/java/com/helpmeCookies/product/service/ProductImageService.java index 25215c5..10bfe36 100644 --- a/src/main/java/com/helpmeCookies/product/service/ProductImageService.java +++ b/src/main/java/com/helpmeCookies/product/service/ProductImageService.java @@ -2,6 +2,7 @@ import com.helpmeCookies.global.utils.AwsS3FileUtils; import com.helpmeCookies.product.dto.FileUploadResponse; +import com.helpmeCookies.product.entity.ProductImage; import com.helpmeCookies.product.repository.ProductImageRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -18,7 +19,7 @@ public class ProductImageService { private final ProductImageRepository productImageRepository; @Transactional - public List uploadMultiFiles(Long productId, List files) throws IOException { + public List uploadMultiFiles(Long productId, List files) { List uploadResponses = awsS3FileUtils.uploadMultiImages(files); uploadResponses.forEach(response -> productImageRepository.save(response.toEntity(productId))); @@ -32,4 +33,8 @@ public void editImages(Long productId, List files) throws IOExcep productImageRepository.deleteAllByProductId(productId); uploadMultiFiles(productId, files); } + + public List getImages(Long productId) { + return productImageRepository.findAllByProductId(productId).stream().map(ProductImage::getPhotoUrl).toList(); + } } From 2c63651e3652f71e0259aa66c48b553965753027 Mon Sep 17 00:00:00 2001 From: yooonwodyd Date: Sat, 19 Oct 2024 19:58:49 +0900 Subject: [PATCH 08/60] refactor:[#54]- refact userDomain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UserInfo에 대한 정보를 변경한다. --- .../com/helpmeCookies/user/dto/UserDto.java | 4 ++++ .../helpmeCookies/user/dto/UserInfoDto.java | 6 ------ .../user/dto/response/UserCommonInfoRes.java | 9 +++++---- .../user/dto/response/UserDetailsInfoRes.java | 19 ++++++++++--------- .../com/helpmeCookies/user/entity/User.java | 4 ++++ .../helpmeCookies/user/entity/UserInfo.java | 4 ---- .../user/service/ArtistService.java | 6 ++++-- 7 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/helpmeCookies/user/dto/UserDto.java b/src/main/java/com/helpmeCookies/user/dto/UserDto.java index d5544d5..b3c46e2 100644 --- a/src/main/java/com/helpmeCookies/user/dto/UserDto.java +++ b/src/main/java/com/helpmeCookies/user/dto/UserDto.java @@ -9,12 +9,16 @@ public record UserDto( Long id, UserInfoDto userInfo, + String userImageUrl, + String nickname, LocalDateTime createdAt ) { public static UserDto fromEntity(User user) { return new UserDto( user.getId(), UserInfoDto.fromEntity(user.getUserInfo()), + user.getUserImageUrl(), + user.getNickname(), user.getCreatedAt() ); } diff --git a/src/main/java/com/helpmeCookies/user/dto/UserInfoDto.java b/src/main/java/com/helpmeCookies/user/dto/UserInfoDto.java index 28099ff..ef1bedd 100644 --- a/src/main/java/com/helpmeCookies/user/dto/UserInfoDto.java +++ b/src/main/java/com/helpmeCookies/user/dto/UserInfoDto.java @@ -8,8 +8,6 @@ public record UserInfoDto( String name, - String userImageUrl, - String nickname, String email, String birthdate, String phone, @@ -19,8 +17,6 @@ public record UserInfoDto( public static UserInfoDto fromEntity(UserInfo userInfo) { return new UserInfoDto( userInfo.getName(), - userInfo.getUserImageUrl(), - userInfo.getNickname(), userInfo.getEmail(), userInfo.getBirthdate(), userInfo.getPhone(), @@ -32,8 +28,6 @@ public static UserInfoDto fromEntity(UserInfo userInfo) { public UserInfo toEntity() { return new UserInfo( name, - userImageUrl, - nickname, email, birthdate, phone, diff --git a/src/main/java/com/helpmeCookies/user/dto/response/UserCommonInfoRes.java b/src/main/java/com/helpmeCookies/user/dto/response/UserCommonInfoRes.java index 5cc9ce4..0f270df 100644 --- a/src/main/java/com/helpmeCookies/user/dto/response/UserCommonInfoRes.java +++ b/src/main/java/com/helpmeCookies/user/dto/response/UserCommonInfoRes.java @@ -3,6 +3,7 @@ import java.util.List; import com.helpmeCookies.product.entity.HashTag; +import com.helpmeCookies.user.dto.UserDto; import com.helpmeCookies.user.dto.UserInfoDto; public record UserCommonInfoRes( @@ -10,11 +11,11 @@ public record UserCommonInfoRes( List hashTags, String userImageUrl ) { - public static UserCommonInfoRes fromDto(UserInfoDto userInfoDto) { + public static UserCommonInfoRes fromDto(UserDto userDto) { return new UserCommonInfoRes( - userInfoDto.nickname(), - userInfoDto.hashTags(), - userInfoDto.userImageUrl() + userDto.userInfo().name(), + userDto.userInfo().hashTags(), + userDto.userImageUrl() ); } diff --git a/src/main/java/com/helpmeCookies/user/dto/response/UserDetailsInfoRes.java b/src/main/java/com/helpmeCookies/user/dto/response/UserDetailsInfoRes.java index 67fface..579e250 100644 --- a/src/main/java/com/helpmeCookies/user/dto/response/UserDetailsInfoRes.java +++ b/src/main/java/com/helpmeCookies/user/dto/response/UserDetailsInfoRes.java @@ -3,6 +3,7 @@ import java.util.List; import com.helpmeCookies.product.entity.HashTag; +import com.helpmeCookies.user.dto.UserDto; import com.helpmeCookies.user.dto.UserInfoDto; import lombok.Builder; @@ -18,16 +19,16 @@ public record UserDetailsInfoRes( String address, List hashTags ) { - public static UserDetailsInfoRes fromDto(UserInfoDto userInfoDto) { + public static UserDetailsInfoRes fromDto(UserDto userDto) { return UserDetailsInfoRes.builder() - .name(userInfoDto.name()) - .userImageUrl(userInfoDto.userImageUrl()) - .nickname(userInfoDto.nickname()) - .email(userInfoDto.email()) - .birthdate(userInfoDto.birthdate()) - .phone(userInfoDto.phone()) - .address(userInfoDto.address()) - .hashTags(userInfoDto.hashTags()) + .name(userDto.userInfo().name()) + .userImageUrl(userDto.userImageUrl()) + .nickname(userDto.nickname()) + .email(userDto.userInfo().email()) + .birthdate(userDto.userInfo().birthdate()) + .phone(userDto.userInfo().phone()) + .address(userDto.userInfo().address()) + .hashTags(userDto.userInfo().hashTags()) .build(); } } diff --git a/src/main/java/com/helpmeCookies/user/entity/User.java b/src/main/java/com/helpmeCookies/user/entity/User.java index 4fee3a0..1e26b75 100644 --- a/src/main/java/com/helpmeCookies/user/entity/User.java +++ b/src/main/java/com/helpmeCookies/user/entity/User.java @@ -41,6 +41,10 @@ public class User { @Column(nullable = false) private Long id; + private String nickname; + + private String userImageUrl; + @Embedded private UserInfo userInfo; diff --git a/src/main/java/com/helpmeCookies/user/entity/UserInfo.java b/src/main/java/com/helpmeCookies/user/entity/UserInfo.java index e70ac05..8b5453b 100644 --- a/src/main/java/com/helpmeCookies/user/entity/UserInfo.java +++ b/src/main/java/com/helpmeCookies/user/entity/UserInfo.java @@ -25,10 +25,6 @@ public class UserInfo { private String name; - private String nickname; - - private String userImageUrl; - private String email; private String birthdate; diff --git a/src/main/java/com/helpmeCookies/user/service/ArtistService.java b/src/main/java/com/helpmeCookies/user/service/ArtistService.java index 015f319..3f63d32 100644 --- a/src/main/java/com/helpmeCookies/user/service/ArtistService.java +++ b/src/main/java/com/helpmeCookies/user/service/ArtistService.java @@ -45,7 +45,8 @@ public void registerStudentsArtist(StudentArtistReq studentArtistReq, Long userI ArtistInfo artistInfo = ArtistInfo.builder() .userId(userId) .artistType(ArtistType.STUDENT) - .nickname(user.getUserInfo().getNickname()) + .artistImageUrl(user.getUserImageUrl()) + .nickname(user.getNickname()) .totalFollowers(0L) .totalLikes(0L) .about(studentArtistReq.about()) @@ -74,8 +75,9 @@ public void registerBusinessArtist(BusinessArtistReq businessArtistReq, Long use // BusinessArtist 생성 ArtistInfo artistInfo = ArtistInfo.builder() .userId(userId) + .artistImageUrl(user.getUserImageUrl()) .artistType(ArtistType.BUSINESS) - .nickname(user.getUserInfo().getNickname()) + .nickname(user.getNickname()) .totalFollowers(0L) .totalLikes(0L) .about(businessArtistReq.about()) From 17b78fdadebf3bcb09fd51bee634b1dc055f64a4 Mon Sep 17 00:00:00 2001 From: yooonwodyd Date: Sat, 19 Oct 2024 19:59:46 +0900 Subject: [PATCH 09/60] refactor:[#54]- refact Swagger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 스웨거 관리 인터페이스를 추가하고 변경한다. --- .../user/controller/ArtistController.java | 5 +- .../user/controller/LoginController.java | 4 +- .../user/controller/UserController.java | 41 +++++-------- .../controller/apiDocs/ArtistApiDocs.java | 47 +++++++++++++++ .../user/controller/apiDocs/UserApiDocs.java | 57 +++++++++++++++++++ .../querydsl/UserCustomRepositoryImpl.java | 3 +- .../user/service/UserService.java | 9 ++- 7 files changed, 128 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/helpmeCookies/user/controller/apiDocs/ArtistApiDocs.java create mode 100644 src/main/java/com/helpmeCookies/user/controller/apiDocs/UserApiDocs.java diff --git a/src/main/java/com/helpmeCookies/user/controller/ArtistController.java b/src/main/java/com/helpmeCookies/user/controller/ArtistController.java index 607f425..32a71f7 100644 --- a/src/main/java/com/helpmeCookies/user/controller/ArtistController.java +++ b/src/main/java/com/helpmeCookies/user/controller/ArtistController.java @@ -10,6 +10,7 @@ import com.helpmeCookies.global.jwt.JwtProvider; import com.helpmeCookies.global.jwt.JwtUser; +import com.helpmeCookies.user.controller.apiDocs.ArtistApiDocs; import com.helpmeCookies.user.dto.request.BusinessArtistReq; import com.helpmeCookies.user.dto.request.StudentArtistReq; import com.helpmeCookies.user.dto.response.ArtistDetailsRes; @@ -22,10 +23,8 @@ @RestController @RequiredArgsConstructor @Tag(name = "작가 관련 기능", description = "작가 관련 API") -public class ArtistController { +public class ArtistController implements ArtistApiDocs { private final ArtistService artistService; - private final JwtProvider jwtProvider; - @Operation(summary = "학생 작가 등록", description = "학생 작가 등록") @PostMapping("/v1/artists/students") diff --git a/src/main/java/com/helpmeCookies/user/controller/LoginController.java b/src/main/java/com/helpmeCookies/user/controller/LoginController.java index 319af2a..35e74ac 100644 --- a/src/main/java/com/helpmeCookies/user/controller/LoginController.java +++ b/src/main/java/com/helpmeCookies/user/controller/LoginController.java @@ -29,18 +29,18 @@ public class LoginController { @GetMapping("/test/signup") public JwtToken signup() { UserInfo userInfo = UserInfo.builder() - .userImageUrl("https://www.naver.com") .email("test@test") .birthdate("1995-01-01") .phone("010-1234-5678") .hashTags(List.of(HashTag.DREAMLIKE)) .name("test") - .nickname("test") .address("서울시 강남구") .build(); User user = User.builder() .userInfo(userInfo) + .nickname("test") + .userImageUrl("test") .build(); userRepository.save(user); diff --git a/src/main/java/com/helpmeCookies/user/controller/UserController.java b/src/main/java/com/helpmeCookies/user/controller/UserController.java index dbbe184..058fccb 100644 --- a/src/main/java/com/helpmeCookies/user/controller/UserController.java +++ b/src/main/java/com/helpmeCookies/user/controller/UserController.java @@ -4,6 +4,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.DeleteMapping; @@ -15,6 +16,7 @@ import org.springframework.web.bind.annotation.RestController; import com.helpmeCookies.global.jwt.JwtUser; +import com.helpmeCookies.user.controller.apiDocs.UserApiDocs; import com.helpmeCookies.user.dto.UserFollowingDto; import com.helpmeCookies.user.dto.response.UserCommonInfoRes; import com.helpmeCookies.user.dto.request.UserInfoReq; @@ -29,39 +31,31 @@ @RestController @RequiredArgsConstructor -@Tag(name = "유저 및 팔로우 기능", description = "유저 및 팔로우 기능과 관련된 API") -public class UserController { +public class UserController implements UserApiDocs { private final UserService userService; - @Operation(summary = "유저 일반 정보 조회", description = "로그인한 유저의 username, imageUrl, hashtag를 조회한다.") @GetMapping("/v1/users") - public UserCommonInfoRes getUsers( + public ResponseEntity getUsers( @AuthenticationPrincipal JwtUser jwtUser ) { - return UserCommonInfoRes.fromDto(userService.getUserInfo(jwtUser.getId())); + return ResponseEntity.ok(UserCommonInfoRes.fromDto(userService.getUserInfo(jwtUser.getId()))); } - // 유저 상세 정보 조회 - @Operation(summary = "유저 상세 정보 조회", description = "로그인한 유저의 이름, 주소를 비롯한 개인정보를 함께 조회한다.") @GetMapping("/v1/users/details") - public UserDetailsInfoRes getUserDetails( + public ResponseEntity getUserDetails( @AuthenticationPrincipal JwtUser jwtUser ) { - return UserDetailsInfoRes.fromDto(userService.getUserInfo(jwtUser.getId())); + return ResponseEntity.ok(UserDetailsInfoRes.fromDto(userService.getUserInfo(jwtUser.getId()))); } - // 유저 타입 조회 - @Operation(summary = "유저 타입 조회", description = "로그인한 유저의 타입과 권한을 조회한다.") @GetMapping("/v1/users/type") - public UserTypeDto getUserType( + public ResponseEntity getUserType( @AuthenticationPrincipal JwtUser jwtUser ) { - return userService.getUserType(jwtUser.getId()); + return ResponseEntity.ok(userService.getUserType(jwtUser.getId())); } - // 유저 정보 수정 - @Operation(summary = "유저 정보 수정", description = "로그인한 유저의 개인정보를 수정한다.") @PutMapping("/v1/users") public String updateUserInfo( @AuthenticationPrincipal JwtUser jwtUser, @@ -72,37 +66,32 @@ public String updateUserInfo( return "ok"; } - @Operation(summary = "아티스트 팔로우하기", description = "로그인한 유저가 특정 아티스트를 팔로우한다.") @PostMapping("/v1/users/following/{artistId}") - public String followArtist( + public ResponseEntity followArtist( @AuthenticationPrincipal JwtUser jwtUser, @PathVariable Long artistId ) { userService.followArtist(jwtUser.getId(), artistId); - return "ok"; + return ResponseEntity.ok().build(); } - @Operation(summary = "아티스트 팔로우 취소하기", description = "로그인한 유저가 특정 아티스트를 팔로우 취소한다.") @DeleteMapping("/v1/users/following/{artistId}") - public String unfollowArtist( + public ResponseEntity unfollowArtist( @AuthenticationPrincipal JwtUser jwtUser, @PathVariable Long artistId ) { userService.unfollowArtist(jwtUser.getId(), artistId); - return "ok"; + return ResponseEntity.ok().build(); } - @Operation(summary = "팔로잉 목록 조회", description = "로그인한 유저의 팔로우한 아티스트 목록을 조회한다.") @GetMapping("/v1/users/following") - public Page getFollowingList( + public ResponseEntity> getFollowingList( @AuthenticationPrincipal JwtUser jwtUser, @PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable ) { - return userService.getFollowingWithPaging(jwtUser.getId(),pageable); + return ResponseEntity.ok(userService.getFollowingWithPaging(jwtUser.getId(), pageable)); } - // 유저 탈퇴 - @Operation(summary = "유저 탈퇴", description = "로그인한 유저의 정보를 삭제한다.") @DeleteMapping("/v1/users") public String deleteUser( @AuthenticationPrincipal JwtUser jwtUser diff --git a/src/main/java/com/helpmeCookies/user/controller/apiDocs/ArtistApiDocs.java b/src/main/java/com/helpmeCookies/user/controller/apiDocs/ArtistApiDocs.java new file mode 100644 index 0000000..3f77c43 --- /dev/null +++ b/src/main/java/com/helpmeCookies/user/controller/apiDocs/ArtistApiDocs.java @@ -0,0 +1,47 @@ +package com.helpmeCookies.user.controller.apiDocs; + +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +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.RequestBody; + +import com.helpmeCookies.global.jwt.JwtUser; +import com.helpmeCookies.user.dto.request.BusinessArtistReq; +import com.helpmeCookies.user.dto.request.StudentArtistReq; +import com.helpmeCookies.user.dto.response.ArtistDetailsRes; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "작가 관련 기능", description = "작가 관련 API") +public interface ArtistApiDocs { + + @Operation(summary = "학생 작가 등록", description = "학생 작가 등록") + @PostMapping("/v1/artists/students") + ResponseEntity registerStudents( + @RequestBody StudentArtistReq artistDetailsReq, + @AuthenticationPrincipal JwtUser jwtUser + ); + + @Operation(summary = "사업자 작가 등록", description = "사업자 작가 등록") + @PostMapping("/v1/artists/bussinesses") + ResponseEntity registerbussinsess( + @RequestBody BusinessArtistReq businessArtistReq, + @AuthenticationPrincipal JwtUser jwtUser + ); + + @Operation(summary = "작가 프로필 조회", description = "작가 프로필 조회") + @GetMapping("/v1/artists/{userId}") + ArtistDetailsRes getArtist( + @AuthenticationPrincipal JwtUser jwtUser, + @PathVariable Long userId + ); + + @Operation(summary = "작가 프로필 조회", description = "자신의 작가 프로필 조회") + @GetMapping("/v1/artist") + ArtistDetailsRes getArtist( + @AuthenticationPrincipal JwtUser jwtUser + ); +} diff --git a/src/main/java/com/helpmeCookies/user/controller/apiDocs/UserApiDocs.java b/src/main/java/com/helpmeCookies/user/controller/apiDocs/UserApiDocs.java new file mode 100644 index 0000000..bfbdde4 --- /dev/null +++ b/src/main/java/com/helpmeCookies/user/controller/apiDocs/UserApiDocs.java @@ -0,0 +1,57 @@ +package com.helpmeCookies.user.controller.apiDocs; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +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 com.helpmeCookies.global.jwt.JwtUser; +import com.helpmeCookies.user.dto.UserTypeDto; +import com.helpmeCookies.user.dto.response.UserCommonInfoRes; +import com.helpmeCookies.user.dto.response.UserDetailsInfoRes; +import com.helpmeCookies.user.dto.response.UserFollowingRes; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "유저 및 팔로우 기능", description = "유저 및 팔로우 기능과 관련된 API") +public interface UserApiDocs { + + @Operation(summary = "유저 일반 정보 조회", description = "로그인한 유저의 username, imageUrl, hashtag를 조회한다.") + @GetMapping("/v1/users") + ResponseEntity getUsers(@AuthenticationPrincipal JwtUser jwtUser); + + @Operation(summary = "유저 상세 정보 조회", description = "로그인한 유저의 상세 정보를 조회한다.") + @GetMapping("/v1/users/details") + ResponseEntity getUserDetails(@AuthenticationPrincipal JwtUser jwtUser); + + @Operation(summary = "유저 타입 조회", description = "로그인한 유저의 타입과 권한을 조회한다.") + @GetMapping("/v1/users/type") + public ResponseEntity getUserType(@AuthenticationPrincipal JwtUser jwtUser); + + @Operation(summary = "아티스트 팔로우하기", description = "로그인한 유저가 특정 아티스트를 팔로우한다.") + @PostMapping("/v1/users/following/{artistId}") + public ResponseEntity followArtist( + @AuthenticationPrincipal JwtUser jwtUser, @PathVariable Long artistId + ); + + @Operation(summary = "아티스트 팔로우 취소하기", description = "로그인한 유저가 특정 아티스트를 팔로우 취소한다.") + @DeleteMapping("/v1/users/following/{artistId}") + public ResponseEntity unfollowArtist( + @AuthenticationPrincipal JwtUser jwtUser, + @PathVariable Long artistId + ); + + @Operation(summary = "팔로잉 목록 조회", description = "로그인한 유저의 팔로우한 아티스트 목록을 조회한다.") + @GetMapping("/v1/users/following") + public ResponseEntity> getFollowingList( + @AuthenticationPrincipal JwtUser jwtUser, + @PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable + ); +} diff --git a/src/main/java/com/helpmeCookies/user/repository/querydsl/UserCustomRepositoryImpl.java b/src/main/java/com/helpmeCookies/user/repository/querydsl/UserCustomRepositoryImpl.java index ca3164e..bd31f40 100644 --- a/src/main/java/com/helpmeCookies/user/repository/querydsl/UserCustomRepositoryImpl.java +++ b/src/main/java/com/helpmeCookies/user/repository/querydsl/UserCustomRepositoryImpl.java @@ -27,9 +27,8 @@ public class UserCustomRepositoryImpl implements UserCustomRepository { public Page findFollowingUsers(Long userId, Pageable pageable) { QUser user = QUser.user; QArtistInfo artistInfo = QArtistInfo.artistInfo; - QSocial social = QSocial.social; // Social 테이블 추가 + QSocial social = QSocial.social; - // JPQLQuery query = queryFactory .select(Projections.constructor( UserFollowingDto.class, diff --git a/src/main/java/com/helpmeCookies/user/service/UserService.java b/src/main/java/com/helpmeCookies/user/service/UserService.java index 139405b..8bbf410 100644 --- a/src/main/java/com/helpmeCookies/user/service/UserService.java +++ b/src/main/java/com/helpmeCookies/user/service/UserService.java @@ -30,12 +30,11 @@ public class UserService { @Transactional - public UserInfoDto getUserInfo(Long userId) { - UserInfo userInfo = userRepository.findById(userId) - .orElseThrow(() -> new ResourceNotFoundException("존재하지 않는 유저입니다.")) - .getUserInfo(); + public UserDto getUserInfo(Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new ResourceNotFoundException("존재하지 않는 유저입니다.")); - return UserInfoDto.fromEntity(userInfo); + return UserDto.fromEntity(user); } @Transactional From 677d27af7d974f088ff48424d31f6016c2c07c65 Mon Sep 17 00:00:00 2001 From: yooonwodyd Date: Sat, 19 Oct 2024 20:07:09 +0900 Subject: [PATCH 10/60] refactor:[#54]- refact userDto MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit userDto를 수정한다 --- .../java/com/helpmeCookies/user/dto/request/UserInfoReq.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/helpmeCookies/user/dto/request/UserInfoReq.java b/src/main/java/com/helpmeCookies/user/dto/request/UserInfoReq.java index d5c44a1..eee632c 100644 --- a/src/main/java/com/helpmeCookies/user/dto/request/UserInfoReq.java +++ b/src/main/java/com/helpmeCookies/user/dto/request/UserInfoReq.java @@ -22,8 +22,6 @@ public UserInfoDto toDto() { nickname, email, birthdate, - phone, - address, hashTags ); } From f735928ebc148521c7664be39f71c8077299885c Mon Sep 17 00:00:00 2001 From: bokyeong Date: Tue, 22 Oct 2024 02:34:55 +0900 Subject: [PATCH 11/60] =?UTF-8?q?refactor:=20[#61]=20s3=EC=97=90=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=EC=8B=9C=20=ED=95=98=EB=82=98?= =?UTF-8?q?=EC=94=A9=20=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/utils/AwsS3FileUtils.java | 32 ++++++++----------- ...leUploadResponse.java => ImageUpload.java} | 2 +- .../product/service/ProductImageService.java | 23 ++++++++----- 3 files changed, 29 insertions(+), 28 deletions(-) rename src/main/java/com/helpmeCookies/product/dto/{FileUploadResponse.java => ImageUpload.java} (91%) diff --git a/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java b/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java index afc9043..ed16def 100644 --- a/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java +++ b/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java @@ -4,7 +4,7 @@ import com.amazonaws.services.s3.model.CannedAccessControlList; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; -import com.helpmeCookies.product.dto.FileUploadResponse; +import com.helpmeCookies.product.dto.ImageUpload; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; @@ -14,7 +14,6 @@ import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; @@ -28,26 +27,21 @@ public class AwsS3FileUtils { private String bucket; //다중파일 업로드후 url 반환 - public List uploadMultiImages(List multipartFiles) { - List fileList = new ArrayList<>(); + public ImageUpload uploadMultiImages(MultipartFile multipartFile) { - multipartFiles.forEach(file -> { - String fileName = createFileName(file.getOriginalFilename()); //파일 이름 난수화 - ObjectMetadata objectMetadata = new ObjectMetadata(); - objectMetadata.setContentLength(file.getSize()); - objectMetadata.setContentType(file.getContentType()); + String fileName = createFileName(multipartFile.getOriginalFilename()); //파일 이름 난수화 + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentLength(multipartFile.getSize()); + objectMetadata.setContentType(multipartFile.getContentType()); - try (InputStream inputStream = file.getInputStream()) { - amazonS3.putObject(new PutObjectRequest(bucket, fileName, inputStream, objectMetadata) - .withCannedAcl(CannedAccessControlList.PublicRead)); - } catch (IOException e) { - throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "파일 업로드 실패" + fileName); - } - - fileList.add(new FileUploadResponse(amazonS3.getUrl(bucket,fileName).toString(),fileName)); - }); + try (InputStream inputStream = multipartFile.getInputStream()) { + amazonS3.putObject(new PutObjectRequest(bucket, fileName, inputStream, objectMetadata) + .withCannedAcl(CannedAccessControlList.PublicRead)); + } catch (IOException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "파일 업로드 실패" + fileName); + } - return fileList; + return new ImageUpload(amazonS3.getUrl(bucket,fileName).toString(),fileName); } public String createFileName(String fileName) { diff --git a/src/main/java/com/helpmeCookies/product/dto/FileUploadResponse.java b/src/main/java/com/helpmeCookies/product/dto/ImageUpload.java similarity index 91% rename from src/main/java/com/helpmeCookies/product/dto/FileUploadResponse.java rename to src/main/java/com/helpmeCookies/product/dto/ImageUpload.java index ad03aff..83f29f9 100644 --- a/src/main/java/com/helpmeCookies/product/dto/FileUploadResponse.java +++ b/src/main/java/com/helpmeCookies/product/dto/ImageUpload.java @@ -2,7 +2,7 @@ import com.helpmeCookies.product.entity.ProductImage; -public record FileUploadResponse( +public record ImageUpload( String photoUrl, String uuid ) { diff --git a/src/main/java/com/helpmeCookies/product/service/ProductImageService.java b/src/main/java/com/helpmeCookies/product/service/ProductImageService.java index 10bfe36..d537a23 100644 --- a/src/main/java/com/helpmeCookies/product/service/ProductImageService.java +++ b/src/main/java/com/helpmeCookies/product/service/ProductImageService.java @@ -1,15 +1,16 @@ package com.helpmeCookies.product.service; import com.helpmeCookies.global.utils.AwsS3FileUtils; -import com.helpmeCookies.product.dto.FileUploadResponse; +import com.helpmeCookies.product.dto.ImageUpload; +import com.helpmeCookies.product.dto.ProductRequest; import com.helpmeCookies.product.entity.ProductImage; import com.helpmeCookies.product.repository.ProductImageRepository; +import java.util.ArrayList; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; import java.util.List; @Service @@ -19,15 +20,21 @@ public class ProductImageService { private final ProductImageRepository productImageRepository; @Transactional - public List uploadMultiFiles(Long productId, List files) { - List uploadResponses = awsS3FileUtils.uploadMultiImages(files); - uploadResponses.forEach(response -> - productImageRepository.save(response.toEntity(productId))); - return uploadResponses; + public List uploadMultiFiles(Long productId, List files) { + List imageUploads = new ArrayList<>(); + for (MultipartFile multipartFile:files) { + imageUploads.add(awsS3FileUtils.uploadMultiImages(multipartFile)); + } + return imageUploads; } @Transactional - public void editImages(Long productId, List files) throws IOException { + public void saveImages(Long productId,ProductRequest productRequest) { + productRequest.productImages().forEach(image -> productImageRepository.save(image.toEntity(productId))); + } + + @Transactional + public void editImages(Long productId, List files) { //우선은 전부 삭제하고 다시 업로드 //추후에 개선 예정 productImageRepository.deleteAllByProductId(productId); From a0da26fd42f09cd6bcaf62eace4b92f5ebc35b45 Mon Sep 17 00:00:00 2001 From: bokyeong Date: Tue, 22 Oct 2024 02:46:42 +0900 Subject: [PATCH 12/60] =?UTF-8?q?refactor:=20[#61]=20=EC=82=AC=EC=A7=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EC=82=AC=EC=A7=84=20=EC=88=98=EC=A0=95=EC=8B=9C=20s3=EC=97=90?= =?UTF-8?q?=20=EB=A8=BC=EC=A0=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20=ED=9B=84?= =?UTF-8?q?=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=B2=A0=EC=9D=B4=EC=8A=A4?= =?UTF-8?q?=EC=97=90=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/controller/ProductController.java | 18 ++++++++++-------- .../product/service/ProductImageService.java | 9 ++++----- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/helpmeCookies/product/controller/ProductController.java b/src/main/java/com/helpmeCookies/product/controller/ProductController.java index 379b545..333d8d4 100644 --- a/src/main/java/com/helpmeCookies/product/controller/ProductController.java +++ b/src/main/java/com/helpmeCookies/product/controller/ProductController.java @@ -1,6 +1,6 @@ package com.helpmeCookies.product.controller; -import com.helpmeCookies.product.dto.FileUploadResponse; +import com.helpmeCookies.product.dto.ImageUpload; import com.helpmeCookies.product.dto.ProductImageResponse; import com.helpmeCookies.product.dto.ProductRequest; import com.helpmeCookies.product.dto.ProductResponse; @@ -12,7 +12,6 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; import java.util.List; @RestController @@ -25,14 +24,15 @@ public class ProductController { @PostMapping public ResponseEntity saveProduct(@RequestBody ProductRequest productRequest) { - productService.save(productRequest); + Product product = productService.save(productRequest); + productImageService.saveImages(product.getId(),productRequest.productImages()); return ResponseEntity.ok().build(); } - @PostMapping("/{productId}/images") - public ResponseEntity uploadImages(@PathVariable("productId") Long productId, List files) { - List responses = productImageService.uploadMultiFiles(productId,files); - return ResponseEntity.ok(new ProductImageResponse(responses.stream().map(FileUploadResponse::photoUrl).toList())); + @PostMapping("/images") + public ResponseEntity uploadImages(List files) { + List responses = productImageService.uploadMultiFiles(files); + return ResponseEntity.ok(new ProductImageResponse(responses.stream().map(ImageUpload::photoUrl).toList())); } @GetMapping("/{productId}") @@ -50,8 +50,10 @@ public ResponseEntity editProductInfo(@PathVariable("productId") Long prod } @PutMapping("/{productId}/images") - public ResponseEntity editImages(@PathVariable("productId") Long productId, List files) throws IOException { + public ResponseEntity editImages(@PathVariable("productId") Long productId, List files) { productImageService.editImages(productId, files); + List images = productImageService.uploadMultiFiles(files); + productImageService.saveImages(productId,images); return ResponseEntity.ok().build(); } diff --git a/src/main/java/com/helpmeCookies/product/service/ProductImageService.java b/src/main/java/com/helpmeCookies/product/service/ProductImageService.java index d537a23..2553acd 100644 --- a/src/main/java/com/helpmeCookies/product/service/ProductImageService.java +++ b/src/main/java/com/helpmeCookies/product/service/ProductImageService.java @@ -2,7 +2,6 @@ import com.helpmeCookies.global.utils.AwsS3FileUtils; import com.helpmeCookies.product.dto.ImageUpload; -import com.helpmeCookies.product.dto.ProductRequest; import com.helpmeCookies.product.entity.ProductImage; import com.helpmeCookies.product.repository.ProductImageRepository; import java.util.ArrayList; @@ -20,7 +19,7 @@ public class ProductImageService { private final ProductImageRepository productImageRepository; @Transactional - public List uploadMultiFiles(Long productId, List files) { + public List uploadMultiFiles(List files) { List imageUploads = new ArrayList<>(); for (MultipartFile multipartFile:files) { imageUploads.add(awsS3FileUtils.uploadMultiImages(multipartFile)); @@ -29,16 +28,16 @@ public List uploadMultiFiles(Long productId, List fi } @Transactional - public void saveImages(Long productId,ProductRequest productRequest) { - productRequest.productImages().forEach(image -> productImageRepository.save(image.toEntity(productId))); + public void saveImages(Long productId,List files) { + files.forEach(image -> productImageRepository.save(image.toEntity(productId))); } @Transactional public void editImages(Long productId, List files) { //우선은 전부 삭제하고 다시 업로드 //추후에 개선 예정 + //TODO s3서버에서 기존 사진들을 제거하는 기능 productImageRepository.deleteAllByProductId(productId); - uploadMultiFiles(productId, files); } public List getImages(Long productId) { From ec89e148d4efc122fc9c28100813d40637362740 Mon Sep 17 00:00:00 2001 From: bokyeong Date: Tue, 22 Oct 2024 15:27:20 +0900 Subject: [PATCH 13/60] =?UTF-8?q?fix=20:=20[#37]=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=EC=88=98=EC=A0=95=20=EC=8B=9C=20artistInf?= =?UTF-8?q?o=20=EA=B4=80=EB=A0=A8=20=EC=BD=94=EB=93=9C=20=EB=B3=B4?= =?UTF-8?q?=EC=99=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/dto/ProductRequest.java | 4 +++- .../product/dto/ProductResponse.java | 22 ++----------------- .../product/service/ProductService.java | 13 +++++++---- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/helpmeCookies/product/dto/ProductRequest.java b/src/main/java/com/helpmeCookies/product/dto/ProductRequest.java index e1dfa2d..a187b82 100644 --- a/src/main/java/com/helpmeCookies/product/dto/ProductRequest.java +++ b/src/main/java/com/helpmeCookies/product/dto/ProductRequest.java @@ -15,7 +15,9 @@ public record ProductRequest( String description, String preferredLocation, List hashTags, - Long artistInfo + Long artistInfoId, + List productImages + ) { public Product toEntity(ArtistInfo artistInfo) { return Product.builder() diff --git a/src/main/java/com/helpmeCookies/product/dto/ProductResponse.java b/src/main/java/com/helpmeCookies/product/dto/ProductResponse.java index 36b6e95..80c78bf 100644 --- a/src/main/java/com/helpmeCookies/product/dto/ProductResponse.java +++ b/src/main/java/com/helpmeCookies/product/dto/ProductResponse.java @@ -16,26 +16,10 @@ public record ProductResponse( List hashTags, ArtistInfo artistInfo ) { - public static class ArtistInfo { - private final Long artistId; - private final String name; - - public ArtistInfo(Long artistId, String name) { - this.artistId = artistId; - this.name = name; - } - - public Long getArtistId() { - return artistId; - } - - public String getName() { - return name; - } + public record ArtistInfo(Long artistId, String artistName) { } public static ProductResponse from(Product product) { - //TODO artistInfo 코드 개발 이후 수정 예정 return new ProductResponse( product.getId(), product.getName(), @@ -45,9 +29,7 @@ public static ProductResponse from(Product product) { product.getDescription(), product.getPreferredLocation(), product.getHashTags(), - new ArtistInfo( - 1L, "임시" - ) + new ArtistInfo(product.getArtistInfo().getId(),product.getArtistInfo().getNickname()) ); } } diff --git a/src/main/java/com/helpmeCookies/product/service/ProductService.java b/src/main/java/com/helpmeCookies/product/service/ProductService.java index c9b22d4..b288c53 100644 --- a/src/main/java/com/helpmeCookies/product/service/ProductService.java +++ b/src/main/java/com/helpmeCookies/product/service/ProductService.java @@ -5,6 +5,8 @@ import com.helpmeCookies.product.entity.Product; import com.helpmeCookies.product.repository.ProductImageRepository; import com.helpmeCookies.product.repository.ProductRepository; +import com.helpmeCookies.user.entity.ArtistInfo; +import com.helpmeCookies.user.repository.ArtistInfoRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -14,10 +16,12 @@ public class ProductService { private final ProductRepository productRepository; private final ProductImageRepository productImageRepository; + private final ArtistInfoRepository artistInfoRepository; public Product save(ProductRequest productSaveRequest) { - //TODO ArtistInfo 코드 병합시 수정 예정 - Product product = productSaveRequest.toEntity(null); + ArtistInfo artistInfo = artistInfoRepository.findById(productSaveRequest.artistInfoId()) + .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 작가 정보입니다.")); + Product product = productSaveRequest.toEntity(artistInfo); productRepository.save(product); return product; } @@ -29,7 +33,8 @@ public Product find(Long productId) { @Transactional public void edit(Long productId, ProductRequest productRequest) { Product product = productRepository.findById(productId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 id입니다")); - //TODO ArtistInfo 코드 병합시 수정 예정 + ArtistInfo artistInfo = artistInfoRepository.findById(productRequest.artistInfoId()) + .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 작가 정보입니다.")); product.update( productRequest.name(), Category.fromString(productRequest.category()), @@ -38,7 +43,7 @@ public void edit(Long productId, ProductRequest productRequest) { productRequest.description(), productRequest.preferredLocation(), productRequest.hashTags(), - null); + artistInfo); } public void delete(Long productId) { From dcd12d180771b67d661da21c461144886161c0ba Mon Sep 17 00:00:00 2001 From: bokyeong Date: Tue, 22 Oct 2024 15:34:13 +0900 Subject: [PATCH 14/60] =?UTF-8?q?feat=20:=20[#45]=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=EC=8B=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20url=20=EB=B0=98?= =?UTF-8?q?=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/helpmeCookies/product/dto/ProductResponse.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/helpmeCookies/product/dto/ProductResponse.java b/src/main/java/com/helpmeCookies/product/dto/ProductResponse.java index 80c78bf..4e927e6 100644 --- a/src/main/java/com/helpmeCookies/product/dto/ProductResponse.java +++ b/src/main/java/com/helpmeCookies/product/dto/ProductResponse.java @@ -14,12 +14,13 @@ public record ProductResponse( String description, String preferredLocation, List hashTags, - ArtistInfo artistInfo + ArtistInfo artistInfo, + List imageUrls ) { public record ArtistInfo(Long artistId, String artistName) { } - public static ProductResponse from(Product product) { + public static ProductResponse from(Product product, List urls) { return new ProductResponse( product.getId(), product.getName(), @@ -29,7 +30,8 @@ public static ProductResponse from(Product product) { product.getDescription(), product.getPreferredLocation(), product.getHashTags(), - new ArtistInfo(product.getArtistInfo().getId(),product.getArtistInfo().getNickname()) + new ArtistInfo(product.getArtistInfo().getId(),product.getArtistInfo().getNickname()), + urls ); } } From 84381f1cbd3c0c76f40027b124b6e869f0c23b66 Mon Sep 17 00:00:00 2001 From: bokyeong Date: Tue, 22 Oct 2024 16:10:23 +0900 Subject: [PATCH 15/60] =?UTF-8?q?feat=20:=20[#61]=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=A0=80=EC=9E=A5=EC=8B=9C=20url=EB=A7=8C?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5=20uuid=EB=A1=9C=20url=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=ED=96=88=EA=B8=B0=EC=97=90=20uuid=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=ED=95=A0=20=ED=95=84=EC=9A=94=20=EC=97=86=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/helpmeCookies/global/utils/AwsS3FileUtils.java | 2 +- src/main/java/com/helpmeCookies/product/dto/ImageUpload.java | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java b/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java index ed16def..f6d76d3 100644 --- a/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java +++ b/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java @@ -41,7 +41,7 @@ public ImageUpload uploadMultiImages(MultipartFile multipartFile) { throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "파일 업로드 실패" + fileName); } - return new ImageUpload(amazonS3.getUrl(bucket,fileName).toString(),fileName); + return new ImageUpload(amazonS3.getUrl(bucket,fileName).toString()); } public String createFileName(String fileName) { diff --git a/src/main/java/com/helpmeCookies/product/dto/ImageUpload.java b/src/main/java/com/helpmeCookies/product/dto/ImageUpload.java index 83f29f9..fa847b3 100644 --- a/src/main/java/com/helpmeCookies/product/dto/ImageUpload.java +++ b/src/main/java/com/helpmeCookies/product/dto/ImageUpload.java @@ -3,14 +3,12 @@ import com.helpmeCookies.product.entity.ProductImage; public record ImageUpload( - String photoUrl, - String uuid + String photoUrl ) { public ProductImage toEntity(Long productId) { return ProductImage.builder() .productId(productId) .photoUrl(photoUrl) - .uuid(uuid) .build(); } } From 9aa22810498ca5513491738573a51782a07e832f Mon Sep 17 00:00:00 2001 From: bokyeong Date: Tue, 22 Oct 2024 16:11:50 +0900 Subject: [PATCH 16/60] =?UTF-8?q?refactor=20:=20[#61]=20image=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EB=B3=80=ED=99=94=EB=A1=9C=20DTO=20=EB=B3=80?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../helpmeCookies/product/controller/ProductController.java | 5 +++-- .../java/com/helpmeCookies/product/dto/ProductRequest.java | 2 +- .../helpmeCookies/product/service/ProductImageService.java | 4 +++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/helpmeCookies/product/controller/ProductController.java b/src/main/java/com/helpmeCookies/product/controller/ProductController.java index 333d8d4..e16082e 100644 --- a/src/main/java/com/helpmeCookies/product/controller/ProductController.java +++ b/src/main/java/com/helpmeCookies/product/controller/ProductController.java @@ -25,7 +25,7 @@ public class ProductController { @PostMapping public ResponseEntity saveProduct(@RequestBody ProductRequest productRequest) { Product product = productService.save(productRequest); - productImageService.saveImages(product.getId(),productRequest.productImages()); + productImageService.saveImages(product.getId(),productRequest.imageUrls()); return ResponseEntity.ok().build(); } @@ -52,7 +52,8 @@ public ResponseEntity editProductInfo(@PathVariable("productId") Long prod @PutMapping("/{productId}/images") public ResponseEntity editImages(@PathVariable("productId") Long productId, List files) { productImageService.editImages(productId, files); - List images = productImageService.uploadMultiFiles(files); + List images = productImageService.uploadMultiFiles(files).stream() + .map(ImageUpload::photoUrl).toList(); productImageService.saveImages(productId,images); return ResponseEntity.ok().build(); } diff --git a/src/main/java/com/helpmeCookies/product/dto/ProductRequest.java b/src/main/java/com/helpmeCookies/product/dto/ProductRequest.java index a187b82..b0f73d5 100644 --- a/src/main/java/com/helpmeCookies/product/dto/ProductRequest.java +++ b/src/main/java/com/helpmeCookies/product/dto/ProductRequest.java @@ -16,7 +16,7 @@ public record ProductRequest( String preferredLocation, List hashTags, Long artistInfoId, - List productImages + List imageUrls ) { public Product toEntity(ArtistInfo artistInfo) { diff --git a/src/main/java/com/helpmeCookies/product/service/ProductImageService.java b/src/main/java/com/helpmeCookies/product/service/ProductImageService.java index 2553acd..42948a1 100644 --- a/src/main/java/com/helpmeCookies/product/service/ProductImageService.java +++ b/src/main/java/com/helpmeCookies/product/service/ProductImageService.java @@ -28,7 +28,9 @@ public List uploadMultiFiles(List files) { } @Transactional - public void saveImages(Long productId,List files) { + public void saveImages(Long productId,List urls) { + //DTO 변환 + List files = urls.stream().map(ImageUpload::new).toList(); files.forEach(image -> productImageRepository.save(image.toEntity(productId))); } From 369c5633546aa29829b2ede000dfca650a023188 Mon Sep 17 00:00:00 2001 From: bokyeong Date: Wed, 23 Oct 2024 23:45:50 +0900 Subject: [PATCH 17/60] =?UTF-8?q?feat=20:=20[#62]=20=EA=B0=90=EC=83=81?= =?UTF-8?q?=ED=8F=89=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{product => review}/entity/Review.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) rename src/main/java/com/helpmeCookies/{product => review}/entity/Review.java (67%) diff --git a/src/main/java/com/helpmeCookies/product/entity/Review.java b/src/main/java/com/helpmeCookies/review/entity/Review.java similarity index 67% rename from src/main/java/com/helpmeCookies/product/entity/Review.java rename to src/main/java/com/helpmeCookies/review/entity/Review.java index 854fdbb..b3035d3 100644 --- a/src/main/java/com/helpmeCookies/product/entity/Review.java +++ b/src/main/java/com/helpmeCookies/review/entity/Review.java @@ -1,5 +1,6 @@ -package com.helpmeCookies.product.entity; +package com.helpmeCookies.review.entity; +import com.helpmeCookies.product.entity.Product; import com.helpmeCookies.user.entity.User; import jakarta.persistence.Column; @@ -9,6 +10,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import lombok.Builder; @Entity public class Review { @@ -27,4 +29,14 @@ public class Review { @ManyToOne @JoinColumn(name = "product_id", nullable = false) private Product product; + + @Builder + public Review(Long id, String content, User writer, Product product) { + this.id = id; + this.content = content; + this.writer = writer; + this.product = product; + } + + public Review() {} } From 3e0fc6b7f08c4f3c3a4596f773d77234b3c9bbdf Mon Sep 17 00:00:00 2001 From: bokyeong Date: Thu, 24 Oct 2024 01:27:38 +0900 Subject: [PATCH 18/60] =?UTF-8?q?feat=20:=20[#62]=20=EA=B0=90=EC=83=81?= =?UTF-8?q?=ED=8F=89=20=EC=9E=91=EC=84=B1=20=EA=B8=B0=EB=8A=A5=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=EA=B0=80=20=EC=9E=91=ED=92=88=EC=9D=84=20?= =?UTF-8?q?=EA=B5=AC=EB=A7=A4=ED=95=98=EC=98=80=EB=8A=94=EC=A7=80=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8X?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/controller/ReviewController.java | 30 +++++++++++++++++++ .../review/dto/ReviewRequest.java | 15 ++++++++++ .../review/repository/ReviewRepository.java | 9 ++++++ .../review/service/ReviewService.java | 25 ++++++++++++++++ 4 files changed, 79 insertions(+) create mode 100644 src/main/java/com/helpmeCookies/review/controller/ReviewController.java create mode 100644 src/main/java/com/helpmeCookies/review/dto/ReviewRequest.java create mode 100644 src/main/java/com/helpmeCookies/review/repository/ReviewRepository.java create mode 100644 src/main/java/com/helpmeCookies/review/service/ReviewService.java diff --git a/src/main/java/com/helpmeCookies/review/controller/ReviewController.java b/src/main/java/com/helpmeCookies/review/controller/ReviewController.java new file mode 100644 index 0000000..d4c944a --- /dev/null +++ b/src/main/java/com/helpmeCookies/review/controller/ReviewController.java @@ -0,0 +1,30 @@ +package com.helpmeCookies.review.controller; + +import com.helpmeCookies.review.dto.ReviewRequest; +import com.helpmeCookies.review.service.ReviewService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/v1/reviews") +@RequiredArgsConstructor +public class ReviewController { + + private final ReviewService reviewService; + + //TODO 감상평 등록 + //TODO 감상평 수정 + //TODO 감상평 삭제 + //TODO 해당 상품의 감상평 조회 + //TODO 내 감상평 조회 + + @PostMapping("/{productId}") + public ResponseEntity postReview(@RequestBody ReviewRequest request, Long productId) { + reviewService.saveReview(request,productId); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/com/helpmeCookies/review/dto/ReviewRequest.java b/src/main/java/com/helpmeCookies/review/dto/ReviewRequest.java new file mode 100644 index 0000000..17a2600 --- /dev/null +++ b/src/main/java/com/helpmeCookies/review/dto/ReviewRequest.java @@ -0,0 +1,15 @@ +package com.helpmeCookies.review.dto; + +import com.helpmeCookies.product.entity.Product; +import com.helpmeCookies.review.entity.Review; +import com.helpmeCookies.user.entity.User; + +public record ReviewRequest(Long writerId, String content) { + public Review toEntity(User writer, Product product) { + return Review.builder() + .content(content) + .writer(writer) + .product(product) + .build(); + } +} diff --git a/src/main/java/com/helpmeCookies/review/repository/ReviewRepository.java b/src/main/java/com/helpmeCookies/review/repository/ReviewRepository.java new file mode 100644 index 0000000..a64c2ff --- /dev/null +++ b/src/main/java/com/helpmeCookies/review/repository/ReviewRepository.java @@ -0,0 +1,9 @@ +package com.helpmeCookies.review.repository; + +import com.helpmeCookies.review.entity.Review; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ReviewRepository extends JpaRepository { +} diff --git a/src/main/java/com/helpmeCookies/review/service/ReviewService.java b/src/main/java/com/helpmeCookies/review/service/ReviewService.java new file mode 100644 index 0000000..885e461 --- /dev/null +++ b/src/main/java/com/helpmeCookies/review/service/ReviewService.java @@ -0,0 +1,25 @@ +package com.helpmeCookies.review.service; + +import com.helpmeCookies.product.entity.Product; +import com.helpmeCookies.product.repository.ProductRepository; +import com.helpmeCookies.review.dto.ReviewRequest; +import com.helpmeCookies.review.repository.ReviewRepository; +import com.helpmeCookies.user.entity.User; +import com.helpmeCookies.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ReviewService { + private final ReviewRepository reviewRepository; + private final UserRepository userRepository; + private final ProductRepository productRepository; + + 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)); + } + } From 2b89c6b05a37d14a566d650b95432f9c50665b20 Mon Sep 17 00:00:00 2001 From: bokyeong Date: Thu, 24 Oct 2024 17:20:59 +0900 Subject: [PATCH 19/60] =?UTF-8?q?fix=20:=20[#62]=20=EA=B0=90=EC=83=81?= =?UTF-8?q?=ED=8F=89=20=EB=93=B1=EB=A1=9D=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/helpmeCookies/review/controller/ReviewController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/helpmeCookies/review/controller/ReviewController.java b/src/main/java/com/helpmeCookies/review/controller/ReviewController.java index d4c944a..9dec78c 100644 --- a/src/main/java/com/helpmeCookies/review/controller/ReviewController.java +++ b/src/main/java/com/helpmeCookies/review/controller/ReviewController.java @@ -4,6 +4,7 @@ import com.helpmeCookies.review.service.ReviewService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +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.RequestMapping; @@ -16,14 +17,13 @@ public class ReviewController { private final ReviewService reviewService; - //TODO 감상평 등록 //TODO 감상평 수정 //TODO 감상평 삭제 //TODO 해당 상품의 감상평 조회 //TODO 내 감상평 조회 @PostMapping("/{productId}") - public ResponseEntity postReview(@RequestBody ReviewRequest request, Long productId) { + public ResponseEntity postReview(@RequestBody ReviewRequest request, @PathVariable Long productId) { reviewService.saveReview(request,productId); return ResponseEntity.ok().build(); } From 6aa7250463187d97e34f23d1e9792b824c52c98f Mon Sep 17 00:00:00 2001 From: bokyeong Date: Thu, 24 Oct 2024 17:32:36 +0900 Subject: [PATCH 20/60] =?UTF-8?q?fix=20:=20[#62]=20=EA=B0=90=EC=83=81?= =?UTF-8?q?=ED=8F=89=20=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EA=B0=90?= =?UTF-8?q?=EC=83=81=ED=8F=89=EC=9D=80=20content=EB=A7=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EA=B0=80=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/controller/ReviewController.java | 10 ++++++++-- .../java/com/helpmeCookies/review/entity/Review.java | 4 ++++ .../helpmeCookies/review/service/ReviewService.java | 6 ++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/helpmeCookies/review/controller/ReviewController.java b/src/main/java/com/helpmeCookies/review/controller/ReviewController.java index 9dec78c..d225e1f 100644 --- a/src/main/java/com/helpmeCookies/review/controller/ReviewController.java +++ b/src/main/java/com/helpmeCookies/review/controller/ReviewController.java @@ -6,6 +6,7 @@ import org.springframework.http.ResponseEntity; 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.RestController; @@ -17,14 +18,19 @@ public class ReviewController { private final ReviewService reviewService; - //TODO 감상평 수정 //TODO 감상평 삭제 //TODO 해당 상품의 감상평 조회 //TODO 내 감상평 조회 @PostMapping("/{productId}") public ResponseEntity postReview(@RequestBody ReviewRequest request, @PathVariable Long productId) { - reviewService.saveReview(request,productId); + reviewService.saveReview(request, productId); + return ResponseEntity.ok().build(); + } + + @PutMapping("/{productId}/{reviewId}") + public ResponseEntity editReview(@RequestBody ReviewRequest request, @PathVariable Long productId, @PathVariable Long reviewId) { + reviewService.editReview(request, productId, reviewId); return ResponseEntity.ok().build(); } } diff --git a/src/main/java/com/helpmeCookies/review/entity/Review.java b/src/main/java/com/helpmeCookies/review/entity/Review.java index b3035d3..da4f5d8 100644 --- a/src/main/java/com/helpmeCookies/review/entity/Review.java +++ b/src/main/java/com/helpmeCookies/review/entity/Review.java @@ -39,4 +39,8 @@ public Review(Long id, String content, User writer, Product product) { } public Review() {} + + public void updateContent(String content) { + this.content = content; + } } diff --git a/src/main/java/com/helpmeCookies/review/service/ReviewService.java b/src/main/java/com/helpmeCookies/review/service/ReviewService.java index 885e461..4cf9c0e 100644 --- a/src/main/java/com/helpmeCookies/review/service/ReviewService.java +++ b/src/main/java/com/helpmeCookies/review/service/ReviewService.java @@ -3,6 +3,7 @@ import com.helpmeCookies.product.entity.Product; import com.helpmeCookies.product.repository.ProductRepository; import com.helpmeCookies.review.dto.ReviewRequest; +import com.helpmeCookies.review.entity.Review; import com.helpmeCookies.review.repository.ReviewRepository; import com.helpmeCookies.user.entity.User; import com.helpmeCookies.user.repository.UserRepository; @@ -22,4 +23,9 @@ public void saveReview(ReviewRequest request, Long productId) { reviewRepository.save(request.toEntity(writer,product)); } + + public void editReview(ReviewRequest request, Long productId, Long reviewId) { + Review review = reviewRepository.findById(reviewId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 reviewId 입니다.")); + review.updateContent(request.content()); + } } From 73f68e5339696e090ca9a195040e8c05cbdec1c6 Mon Sep 17 00:00:00 2001 From: sim-mer Date: Thu, 3 Oct 2024 03:55:06 +0900 Subject: [PATCH 21/60] =?UTF-8?q?#27=20add:=20JpaAuditing=EC=9D=84=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=95=9C=20BaseTimeEntity=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/helpmeCookies/Step3Application.java | 2 ++ .../global/entity/BaseTimeEntity.java | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 src/main/java/com/helpmeCookies/global/entity/BaseTimeEntity.java diff --git a/src/main/java/com/helpmeCookies/Step3Application.java b/src/main/java/com/helpmeCookies/Step3Application.java index c6bad29..a594415 100644 --- a/src/main/java/com/helpmeCookies/Step3Application.java +++ b/src/main/java/com/helpmeCookies/Step3Application.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication +@EnableJpaAuditing public class Step3Application { public static void main(String[] args) { diff --git a/src/main/java/com/helpmeCookies/global/entity/BaseTimeEntity.java b/src/main/java/com/helpmeCookies/global/entity/BaseTimeEntity.java new file mode 100644 index 0000000..9279fc3 --- /dev/null +++ b/src/main/java/com/helpmeCookies/global/entity/BaseTimeEntity.java @@ -0,0 +1,21 @@ +package com.helpmeCookies.global.entity; + +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import java.time.LocalDateTime; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +@Getter +public abstract class BaseTimeEntity { + + @CreatedDate + private LocalDateTime createdDate; + + @LastModifiedDate + private LocalDateTime modifiedDate; +} From b753e67e8a9fe3d96371fd7892c11e68a1a1d620 Mon Sep 17 00:00:00 2001 From: sim-mer Date: Thu, 3 Oct 2024 04:25:27 +0900 Subject: [PATCH 22/60] =?UTF-8?q?#27=20add:=20Product=20BaseTimeEntity=20?= =?UTF-8?q?=EC=83=81=EC=86=8D,=20ArtistInfo=20JoinColumn=20=EB=AA=85?= =?UTF-8?q?=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/helpmeCookies/product/entity/Product.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/helpmeCookies/product/entity/Product.java b/src/main/java/com/helpmeCookies/product/entity/Product.java index d44b972..584b32a 100644 --- a/src/main/java/com/helpmeCookies/product/entity/Product.java +++ b/src/main/java/com/helpmeCookies/product/entity/Product.java @@ -1,5 +1,7 @@ package com.helpmeCookies.product.entity; +import com.helpmeCookies.global.entity.BaseTimeEntity; +import jakarta.persistence.JoinColumn; import java.util.List; import com.helpmeCookies.user.entity.ArtistInfo; @@ -17,7 +19,7 @@ import lombok.Builder; @Entity -public class Product { +public class Product extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -48,6 +50,7 @@ public class Product { private List hashTags; @ManyToOne + @JoinColumn(name = "artist_info_id") private ArtistInfo artistInfo; public Product() {} From 2c29a01bbb1c5cc5e5ea143623aa929549f5e3c4 Mon Sep 17 00:00:00 2001 From: sim-mer Date: Thu, 3 Oct 2024 04:26:55 +0900 Subject: [PATCH 23/60] =?UTF-8?q?#27=20feat(persistence):=20=EC=83=81?= =?UTF-8?q?=ED=92=88=20=EA=B2=80=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1(imageUrl=20=EC=B6=94=EA=B0=80=20=EC=98=88=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/repository/ProductRepository.java | 19 +++++++++++++++++++ .../product/repository/dto/ProductSearch.java | 9 +++++++++ 2 files changed, 28 insertions(+) create mode 100644 src/main/java/com/helpmeCookies/product/repository/dto/ProductSearch.java diff --git a/src/main/java/com/helpmeCookies/product/repository/ProductRepository.java b/src/main/java/com/helpmeCookies/product/repository/ProductRepository.java index d8b05f3..aca10a1 100644 --- a/src/main/java/com/helpmeCookies/product/repository/ProductRepository.java +++ b/src/main/java/com/helpmeCookies/product/repository/ProductRepository.java @@ -1,9 +1,28 @@ package com.helpmeCookies.product.repository; import com.helpmeCookies.product.entity.Product; +import com.helpmeCookies.product.repository.dto.ProductSearch; +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 ProductRepository extends JpaRepository { + + @Query("SELECT p.id AS id, p.name AS name, a.name AS artist, p.price AS price " + + "FROM Product p JOIN p.artistInfo a " + + "WHERE p.name LIKE %:query%") // Index 미사용 + Page findByName(@Param("query") String query, Pageable pageable); + + @Query(value = "SELECT p.id, p.name, a.name AS artist, p.price " + + "FROM product p JOIN artist_info a ON p.artist_info_id = a.id " + + "WHERE MATCH(p.name) AGAINST (:query IN BOOLEAN MODE)", + countQuery = "SELECT COUNT(*) " + + "FROM product p JOIN artist_info a ON p.artist_info_id = a.id " + + "WHERE MATCH(p.name) AGAINST (:query IN BOOLEAN MODE)", + nativeQuery = true) // Index 사용 + Page findByNameWithIdx(@Param("query") String query, Pageable pageable); } diff --git a/src/main/java/com/helpmeCookies/product/repository/dto/ProductSearch.java b/src/main/java/com/helpmeCookies/product/repository/dto/ProductSearch.java new file mode 100644 index 0000000..3c5fcee --- /dev/null +++ b/src/main/java/com/helpmeCookies/product/repository/dto/ProductSearch.java @@ -0,0 +1,9 @@ +package com.helpmeCookies.product.repository.dto; + + +public interface ProductSearch { + Long getId(); + String getName(); + String getArtist(); + Long getPrice(); +} From 1d2d527c91d46d197ed72895ec0aade02c99f06e Mon Sep 17 00:00:00 2001 From: sim-mer Date: Thu, 3 Oct 2024 04:27:10 +0900 Subject: [PATCH 24/60] =?UTF-8?q?#27=20feat(product.service):=20=EC=83=81?= =?UTF-8?q?=ED=92=88=20=EA=B2=80=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/service/ProductService.java | 8 ++++ .../product/service/dto/ProductPage.java | 44 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 src/main/java/com/helpmeCookies/product/service/dto/ProductPage.java diff --git a/src/main/java/com/helpmeCookies/product/service/ProductService.java b/src/main/java/com/helpmeCookies/product/service/ProductService.java index c9b22d4..b0b156d 100644 --- a/src/main/java/com/helpmeCookies/product/service/ProductService.java +++ b/src/main/java/com/helpmeCookies/product/service/ProductService.java @@ -5,7 +5,9 @@ import com.helpmeCookies.product.entity.Product; import com.helpmeCookies.product.repository.ProductImageRepository; import com.helpmeCookies.product.repository.ProductRepository; +import com.helpmeCookies.product.dto.ProductPage; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -15,6 +17,12 @@ public class ProductService { private final ProductRepository productRepository; private final ProductImageRepository productImageRepository; + @Transactional(readOnly = true) + public ProductPage.Paging getProductsByPage(String query, Pageable pageable) { + var productPage = productRepository.findByNameWithIdx(query, pageable); + return ProductPage.Paging.from(productPage); + } + public Product save(ProductRequest productSaveRequest) { //TODO ArtistInfo 코드 병합시 수정 예정 Product product = productSaveRequest.toEntity(null); diff --git a/src/main/java/com/helpmeCookies/product/service/dto/ProductPage.java b/src/main/java/com/helpmeCookies/product/service/dto/ProductPage.java new file mode 100644 index 0000000..0b2b920 --- /dev/null +++ b/src/main/java/com/helpmeCookies/product/service/dto/ProductPage.java @@ -0,0 +1,44 @@ +package com.helpmeCookies.product.service.dto; + +import com.helpmeCookies.product.repository.dto.ProductSearch; +import java.util.List; +import org.springframework.data.domain.Page; + +public class ProductPage { + + public record Info( + Long id, + String name, + String artist, + Long price + ) { + public static Info from(ProductSearch productSearch) { + return new Info( + productSearch.getId(), + productSearch.getName(), + productSearch.getArtist(), + productSearch.getPrice() + ); + } + + public static List of(List content) { + return content.stream() + .map(Info::from) + .toList(); + } + } + + public record Paging ( + boolean hasNext, + List products + ) { + + public static Paging from(Page productPage) { + return new Paging( + productPage.hasNext(), + Info.of(productPage.getContent()) + ); + } + } + +} From e4e393ae1b922e8a1b482974efed075ce78728a3 Mon Sep 17 00:00:00 2001 From: sim-mer Date: Thu, 3 Oct 2024 04:27:18 +0900 Subject: [PATCH 25/60] =?UTF-8?q?#27=20feat(product.controller):=20?= =?UTF-8?q?=EC=83=81=ED=92=88=20=EA=B2=80=EC=83=89=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/controller/ProductController.java | 18 ++++++++++++++++++ .../product/util/ProductSort.java | 5 +++++ .../helpmeCookies/product/util/SortUtil.java | 14 ++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 src/main/java/com/helpmeCookies/product/util/ProductSort.java create mode 100644 src/main/java/com/helpmeCookies/product/util/SortUtil.java diff --git a/src/main/java/com/helpmeCookies/product/controller/ProductController.java b/src/main/java/com/helpmeCookies/product/controller/ProductController.java index c655ee7..e3b03c9 100644 --- a/src/main/java/com/helpmeCookies/product/controller/ProductController.java +++ b/src/main/java/com/helpmeCookies/product/controller/ProductController.java @@ -2,12 +2,17 @@ import com.helpmeCookies.product.dto.FileUploadResponse; import com.helpmeCookies.product.dto.ProductImageResponse; +import static com.helpmeCookies.product.util.SortUtil.convertProductSort; + import com.helpmeCookies.product.dto.ProductRequest; import com.helpmeCookies.product.dto.ProductResponse; import com.helpmeCookies.product.entity.Product; import com.helpmeCookies.product.service.ProductImageService; import com.helpmeCookies.product.service.ProductService; import lombok.RequiredArgsConstructor; +import com.helpmeCookies.product.dto.ProductPage; +import com.helpmeCookies.product.util.ProductSort; +import org.springframework.data.domain.PageRequest; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -59,4 +64,17 @@ public ResponseEntity deleteProduct(@PathVariable("productId") Long produc productService.delete(productId); return ResponseEntity.noContent().build(); } + + @GetMapping + public ResponseEntity getProductsByPage( + @RequestParam("query") String query, + @RequestParam(name = "size", required = false, defaultValue = "20") int size, + @RequestParam("page") int page, + @RequestParam("sort") ProductSort productSort + ) { + var sort = convertProductSort(productSort); + var pageable = PageRequest.of(page, size, sort); + + return ResponseEntity.ok(productService.getProductsByPage(query, pageable)); + } } diff --git a/src/main/java/com/helpmeCookies/product/util/ProductSort.java b/src/main/java/com/helpmeCookies/product/util/ProductSort.java new file mode 100644 index 0000000..1a37cec --- /dev/null +++ b/src/main/java/com/helpmeCookies/product/util/ProductSort.java @@ -0,0 +1,5 @@ +package com.helpmeCookies.product.util; + +public enum ProductSort { + RELEVANCE, LATEST +} diff --git a/src/main/java/com/helpmeCookies/product/util/SortUtil.java b/src/main/java/com/helpmeCookies/product/util/SortUtil.java new file mode 100644 index 0000000..3186acf --- /dev/null +++ b/src/main/java/com/helpmeCookies/product/util/SortUtil.java @@ -0,0 +1,14 @@ +package com.helpmeCookies.product.util; + +import org.springframework.data.domain.Sort; + +public class SortUtil { + + public static Sort convertProductSort(ProductSort productSort) { + return switch (productSort) { + case LATEST -> Sort.by(Sort.Order.desc("createdDate")); + default -> Sort.unsorted(); + }; + } + +} From e2456f8b01f7d55fea5dac9cd20d518fbcbc585c Mon Sep 17 00:00:00 2001 From: sim-mer Date: Fri, 25 Oct 2024 04:16:51 +0900 Subject: [PATCH 26/60] =?UTF-8?q?#27=20chore(build.gradle):=20testcontaine?= =?UTF-8?q?rs=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/build.gradle b/build.gradle index 2000e5e..615d23c 100644 --- a/build.gradle +++ b/build.gradle @@ -70,7 +70,14 @@ dependencies { //S3 implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + + //Test + testImplementation 'org.testcontainers:testcontainers:1.20.2' + testImplementation 'org.testcontainers:junit-jupiter:1.20.2' + testImplementation 'org.testcontainers:mysql:1.20.2' + } + tasks.named('test') { useJUnitPlatform() } From 46e885e9fb8e33afa6a4c860f726d53750a7a8e9 Mon Sep 17 00:00:00 2001 From: sim-mer Date: Fri, 25 Oct 2024 04:19:44 +0900 Subject: [PATCH 27/60] =?UTF-8?q?#27=20refactor(ProductPage.java):=20Produ?= =?UTF-8?q?ctPage=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/controller/ProductController.java | 3 +- .../product/service/dto/ProductPage.java | 44 ------------------- 2 files changed, 2 insertions(+), 45 deletions(-) delete mode 100644 src/main/java/com/helpmeCookies/product/service/dto/ProductPage.java diff --git a/src/main/java/com/helpmeCookies/product/controller/ProductController.java b/src/main/java/com/helpmeCookies/product/controller/ProductController.java index e3b03c9..f87b105 100644 --- a/src/main/java/com/helpmeCookies/product/controller/ProductController.java +++ b/src/main/java/com/helpmeCookies/product/controller/ProductController.java @@ -4,14 +4,15 @@ import com.helpmeCookies.product.dto.ProductImageResponse; import static com.helpmeCookies.product.util.SortUtil.convertProductSort; +import com.helpmeCookies.product.dto.ProductPage; import com.helpmeCookies.product.dto.ProductRequest; import com.helpmeCookies.product.dto.ProductResponse; import com.helpmeCookies.product.entity.Product; import com.helpmeCookies.product.service.ProductImageService; import com.helpmeCookies.product.service.ProductService; +import com.helpmeCookies.product.util.ProductSort; import lombok.RequiredArgsConstructor; import com.helpmeCookies.product.dto.ProductPage; -import com.helpmeCookies.product.util.ProductSort; import org.springframework.data.domain.PageRequest; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/com/helpmeCookies/product/service/dto/ProductPage.java b/src/main/java/com/helpmeCookies/product/service/dto/ProductPage.java deleted file mode 100644 index 0b2b920..0000000 --- a/src/main/java/com/helpmeCookies/product/service/dto/ProductPage.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.helpmeCookies.product.service.dto; - -import com.helpmeCookies.product.repository.dto.ProductSearch; -import java.util.List; -import org.springframework.data.domain.Page; - -public class ProductPage { - - public record Info( - Long id, - String name, - String artist, - Long price - ) { - public static Info from(ProductSearch productSearch) { - return new Info( - productSearch.getId(), - productSearch.getName(), - productSearch.getArtist(), - productSearch.getPrice() - ); - } - - public static List of(List content) { - return content.stream() - .map(Info::from) - .toList(); - } - } - - public record Paging ( - boolean hasNext, - List products - ) { - - public static Paging from(Page productPage) { - return new Paging( - productPage.hasNext(), - Info.of(productPage.getContent()) - ); - } - } - -} From be44e0fa2211c205418e3fedd019baa959134b1f Mon Sep 17 00:00:00 2001 From: sim-mer Date: Fri, 25 Oct 2024 04:21:08 +0900 Subject: [PATCH 28/60] =?UTF-8?q?#27=20fix(SortUtil.java):=20=EC=83=81?= =?UTF-8?q?=ED=92=88=20=EC=A0=95=EB=A0=AC=20=EB=B3=80=ED=99=98=20=EC=9C=A0?= =?UTF-8?q?=ED=8B=B8=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=8A=A4=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=ED=81=AC=20=EC=BC=80=EC=9D=B4=EC=8A=A4=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/helpmeCookies/product/util/SortUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/helpmeCookies/product/util/SortUtil.java b/src/main/java/com/helpmeCookies/product/util/SortUtil.java index 3186acf..e85e068 100644 --- a/src/main/java/com/helpmeCookies/product/util/SortUtil.java +++ b/src/main/java/com/helpmeCookies/product/util/SortUtil.java @@ -6,7 +6,7 @@ public class SortUtil { public static Sort convertProductSort(ProductSort productSort) { return switch (productSort) { - case LATEST -> Sort.by(Sort.Order.desc("createdDate")); + case LATEST -> Sort.by(Sort.Order.desc("created_date")); default -> Sort.unsorted(); }; } From 8f56e6a6f85bb824ee095625a2fe4bb4e44e8a16 Mon Sep 17 00:00:00 2001 From: sim-mer Date: Fri, 25 Oct 2024 04:32:47 +0900 Subject: [PATCH 29/60] =?UTF-8?q?#27=20feat(product):=20=EC=83=81=ED=92=88?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=95=20DTO=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/dto/ProductPage.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/java/com/helpmeCookies/product/dto/ProductPage.java diff --git a/src/main/java/com/helpmeCookies/product/dto/ProductPage.java b/src/main/java/com/helpmeCookies/product/dto/ProductPage.java new file mode 100644 index 0000000..1648c19 --- /dev/null +++ b/src/main/java/com/helpmeCookies/product/dto/ProductPage.java @@ -0,0 +1,44 @@ +package com.helpmeCookies.product.dto; + +import com.helpmeCookies.product.repository.dto.ProductSearch; +import java.util.List; +import org.springframework.data.domain.Page; + +public class ProductPage { + + public record Info( + Long id, + String name, + String artist, + Long price + ) { + public static Info from(ProductSearch productSearch) { + return new Info( + productSearch.getId(), + productSearch.getName(), + productSearch.getArtist(), + productSearch.getPrice() + ); + } + + public static List of(List content) { + return content.stream() + .map(Info::from) + .toList(); + } + } + + public record Paging ( + boolean hasNext, + List products + ) { + + public static Paging from(Page productPage) { + return new Paging( + productPage.hasNext(), + Info.of(productPage.getContent()) + ); + } + } + +} From 39c345488a71ae67ff7537a1628485fea30772c0 Mon Sep 17 00:00:00 2001 From: Hyun <82355395+donghyuun@users.noreply.github.com> Date: Sat, 26 Oct 2024 22:10:52 +0900 Subject: [PATCH 30/60] =?UTF-8?q?feat:=20=EB=B0=B0=ED=8F=AC=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=EB=A5=BC=20ec2=20=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=EC=8B=9C=ED=82=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 무중단 배포, nginx 의 다중 서버 설정 등 코드가 많아져서 ec2 내부로 이동시키고, 배포 스크립트의 실행만 github action 에서 하도록 수정했습니다. --- .github/workflows/deploy.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index eb21e9f..7908236 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -66,10 +66,8 @@ jobs: key: ${{ secrets.AWS_EC2_PRIVATE_KEY }} # EC2 인스턴스 pem key port: ${{ secrets.REMOTE_SSH_PORT }} # 접속 포트(생략 시 22번 기본 사용) script: | - echo '${{ secrets.APPLICATION_YAML }}' > test.yaml - cat test.yaml docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} docker pull ${{ secrets.DOCKER_USERNAME }}/katecam-backend:latest - docker stop katecam-backend - docker rm $(docker ps --filter 'status=exited' -a -q) - docker run -d --name katecam-backend --network katecam-backend --log-driver=syslog -p 8080:8080 ${{ secrets.DOCKER_USERNAME }}/katecam-backend:latest + cd /home/ubuntu # EC2 인스턴스의 배포 스크립트 파일 경로로 이동 + chmod +x deploy.sh # 배포 스크립트 실행 권한 부여 + ./deploy.sh # 배포 스크립트 실행 From 36bd84f82e8906af013e264eb91889983c2f1e90 Mon Sep 17 00:00:00 2001 From: sim-mer Date: Sun, 27 Oct 2024 10:20:08 +0900 Subject: [PATCH 31/60] =?UTF-8?q?#27=20docs(product):=20=EC=83=81=ED=92=88?= =?UTF-8?q?=20api=20=EB=AC=B8=EC=84=9C=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?url=20mapping=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/controller/ProductController.java | 10 ++++----- .../controller/docs/ProductApiDocs.java | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/helpmeCookies/product/controller/docs/ProductApiDocs.java diff --git a/src/main/java/com/helpmeCookies/product/controller/ProductController.java b/src/main/java/com/helpmeCookies/product/controller/ProductController.java index f87b105..e575c09 100644 --- a/src/main/java/com/helpmeCookies/product/controller/ProductController.java +++ b/src/main/java/com/helpmeCookies/product/controller/ProductController.java @@ -1,9 +1,10 @@ package com.helpmeCookies.product.controller; -import com.helpmeCookies.product.dto.FileUploadResponse; -import com.helpmeCookies.product.dto.ProductImageResponse; import static com.helpmeCookies.product.util.SortUtil.convertProductSort; +import com.helpmeCookies.product.controller.docs.ProductApiDocs; +import com.helpmeCookies.product.dto.FileUploadResponse; +import com.helpmeCookies.product.dto.ProductImageResponse; import com.helpmeCookies.product.dto.ProductPage; import com.helpmeCookies.product.dto.ProductRequest; import com.helpmeCookies.product.dto.ProductResponse; @@ -12,7 +13,6 @@ import com.helpmeCookies.product.service.ProductService; import com.helpmeCookies.product.util.ProductSort; import lombok.RequiredArgsConstructor; -import com.helpmeCookies.product.dto.ProductPage; import org.springframework.data.domain.PageRequest; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -22,9 +22,9 @@ import java.util.List; @RestController -@RequestMapping("/api/v1/products") +@RequestMapping("/v1/products") @RequiredArgsConstructor -public class ProductController { +public class ProductController implements ProductApiDocs { private final ProductService productService; private final ProductImageService productImageService; diff --git a/src/main/java/com/helpmeCookies/product/controller/docs/ProductApiDocs.java b/src/main/java/com/helpmeCookies/product/controller/docs/ProductApiDocs.java new file mode 100644 index 0000000..e0097b5 --- /dev/null +++ b/src/main/java/com/helpmeCookies/product/controller/docs/ProductApiDocs.java @@ -0,0 +1,21 @@ +package com.helpmeCookies.product.controller.docs; + +import com.helpmeCookies.product.dto.ProductPage.Paging; +import com.helpmeCookies.product.util.ProductSort; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; + +@Tag(name = "상품 관련 기능", description = "상품 관련 API") +public interface ProductApiDocs { + + @Operation(summary = "상품 검색") + ResponseEntity getProductsByPage( + String query, + @Parameter(description = "default value 20") int size, + int page, + ProductSort productSort + ); + +} From 30769a7bae59e862cc0d219b72604cc543922270 Mon Sep 17 00:00:00 2001 From: sim-mer Date: Sun, 27 Oct 2024 10:20:54 +0900 Subject: [PATCH 32/60] =?UTF-8?q?#27=20fix(product):=20ArtistInfo=20entity?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../helpmeCookies/product/repository/ProductRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/helpmeCookies/product/repository/ProductRepository.java b/src/main/java/com/helpmeCookies/product/repository/ProductRepository.java index aca10a1..512722d 100644 --- a/src/main/java/com/helpmeCookies/product/repository/ProductRepository.java +++ b/src/main/java/com/helpmeCookies/product/repository/ProductRepository.java @@ -12,12 +12,12 @@ @Repository public interface ProductRepository extends JpaRepository { - @Query("SELECT p.id AS id, p.name AS name, a.name AS artist, p.price AS price " + + @Query("SELECT p.id AS id, p.name AS name, a.nickname AS artist, p.price AS price " + "FROM Product p JOIN p.artistInfo a " + "WHERE p.name LIKE %:query%") // Index 미사용 Page findByName(@Param("query") String query, Pageable pageable); - @Query(value = "SELECT p.id, p.name, a.name AS artist, p.price " + + @Query(value = "SELECT p.id, p.name, a.nickname AS artist, p.price " + "FROM product p JOIN artist_info a ON p.artist_info_id = a.id " + "WHERE MATCH(p.name) AGAINST (:query IN BOOLEAN MODE)", countQuery = "SELECT COUNT(*) " + From 803d9dfb773b5a07b1abc0d23fdd297161a202e7 Mon Sep 17 00:00:00 2001 From: sim-mer Date: Sun, 27 Oct 2024 10:22:37 +0900 Subject: [PATCH 33/60] =?UTF-8?q?#27=20test(product):=20=EC=83=81=ED=92=88?= =?UTF-8?q?=20=EA=B2=80=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/search/SearchControllerTest.java | 76 +++++++++++++++++++ .../product/search/SearchRepositoryTest.java | 54 +++++++++++++ .../product/search/SearchServiceTest.java | 63 +++++++++++++++ 3 files changed, 193 insertions(+) create mode 100644 src/test/java/com/helpmeCookies/product/search/SearchControllerTest.java create mode 100644 src/test/java/com/helpmeCookies/product/search/SearchRepositoryTest.java create mode 100644 src/test/java/com/helpmeCookies/product/search/SearchServiceTest.java diff --git a/src/test/java/com/helpmeCookies/product/search/SearchControllerTest.java b/src/test/java/com/helpmeCookies/product/search/SearchControllerTest.java new file mode 100644 index 0000000..be84884 --- /dev/null +++ b/src/test/java/com/helpmeCookies/product/search/SearchControllerTest.java @@ -0,0 +1,76 @@ +package com.helpmeCookies.product.search; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.helpmeCookies.global.jwt.JwtProvider; +import com.helpmeCookies.product.controller.ProductController; +import com.helpmeCookies.product.dto.ProductPage; +import com.helpmeCookies.product.repository.dto.ProductSearch; +import com.helpmeCookies.product.service.ProductImageService; +import com.helpmeCookies.product.service.ProductService; +import com.helpmeCookies.product.util.ProductSort; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(ProductController.class) +@AutoConfigureMockMvc(addFilters = false) +@Import(JwtProvider.class) +public class SearchControllerTest { + + @Autowired + private MockMvc mvc; + + @MockBean + private ProductService productService; + + @MockBean + private ProductImageService productImageService; + + + @Test + @DisplayName("상품 검색 컨트롤러") + void 상품_검색() throws Exception { + // given + String query = "product"; + int size = 10; + int page = 0; + ProductSort productSort = ProductSort.LATEST; + + var productSearch = mock(ProductSearch.class); + given(productSearch.getId()).willReturn(1L); + given(productSearch.getName()).willReturn("product1"); + given(productSearch.getArtist()).willReturn("artist"); + given(productSearch.getPrice()).willReturn(10000L); + var paging = ProductPage.Paging.from(new PageImpl<>(List.of(productSearch))); + given(productService.getProductsByPage(eq(query), any(Pageable.class))) + .willReturn(paging); + + // when & then + mvc.perform(get("/test/v1/products") + .param("query", query) + .param("size", String.valueOf(size)) + .param("page", String.valueOf(page)) + .param("sort", productSort.name())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.hasNext").value(false)) + .andExpect(jsonPath("$.products[0].name").value("product1")) + .andExpect(jsonPath("$.products[0].artist").value("artist")) + .andExpect(jsonPath("$.products[0].price").value(10000L)); + } + +} diff --git a/src/test/java/com/helpmeCookies/product/search/SearchRepositoryTest.java b/src/test/java/com/helpmeCookies/product/search/SearchRepositoryTest.java new file mode 100644 index 0000000..b5f2ad0 --- /dev/null +++ b/src/test/java/com/helpmeCookies/product/search/SearchRepositoryTest.java @@ -0,0 +1,54 @@ +package com.helpmeCookies.product.search; + +import static com.helpmeCookies.product.util.SortUtil.convertProductSort; +import static org.assertj.core.api.Assertions.assertThat; + +import com.helpmeCookies.global.config.QueryDSLConfig; +import com.helpmeCookies.product.repository.ProductRepository; +import com.helpmeCookies.product.util.ProductSort; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.context.annotation.Import; +import org.springframework.data.domain.PageRequest; + +@DataJpaTest +@ExtendWith(OutputCaptureExtension.class) +@Import(QueryDSLConfig.class) +public class SearchRepositoryTest { + + @Autowired + private ProductRepository productRepository; + + + @Test + @DisplayName("상품 검색 쿼리 확인") + void 상품_검색(CapturedOutput out) { + // given + var sort = convertProductSort(ProductSort.LATEST); + var pageRequest = PageRequest.of(0, 10, sort); + + // when + try{ + productRepository.findByNameWithIdx("product", pageRequest); + } catch (Exception ignored) { + } + + // then + assertThat(out.getOut()) + .contains("SELECT") + .contains("p.id,") + .contains("p.name,") + .contains("a.nickname AS artist,") + .contains("p.price") + .contains("FROM product p") + .contains("JOIN artist_info a ON p.artist_info_id = a.id") + .contains("WHERE MATCH(p.name) AGAINST (? IN BOOLEAN MODE)") + .contains("order by p.created_date desc"); + //.contains("limit ?") 테스트 dbh2 db에서는 limit이 없어서 제외 + } +} diff --git a/src/test/java/com/helpmeCookies/product/search/SearchServiceTest.java b/src/test/java/com/helpmeCookies/product/search/SearchServiceTest.java new file mode 100644 index 0000000..65c1edb --- /dev/null +++ b/src/test/java/com/helpmeCookies/product/search/SearchServiceTest.java @@ -0,0 +1,63 @@ +package com.helpmeCookies.product.search; + +import static com.helpmeCookies.product.util.SortUtil.convertProductSort; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +import com.helpmeCookies.product.repository.ProductRepository; +import com.helpmeCookies.product.repository.dto.ProductSearch; +import com.helpmeCookies.product.service.ProductService; +import com.helpmeCookies.product.util.ProductSort; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.test.context.TestComponent; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; + +@TestComponent +@ExtendWith(MockitoExtension.class) +public class SearchServiceTest { + + @Mock + private ProductRepository productRepository; + + @InjectMocks + private ProductService productService; + + @Test + @DisplayName("상품 검색 서비스") + void 상품_검색() { + // given + var sort = convertProductSort(ProductSort.LATEST); + var pageRequest = PageRequest.of(0, 10, sort); + var productSearch = mock(ProductSearch.class); + given(productSearch.getId()).willReturn(1L); + given(productSearch.getName()).willReturn("product1"); + given(productSearch.getArtist()).willReturn("artist"); + given(productSearch.getPrice()).willReturn(10000L); + + var productPage = new PageImpl<>(List.of(productSearch)); + given(productRepository.findByNameWithIdx("roduct", pageRequest)) + .willReturn(productPage); + + // when + var result = productService.getProductsByPage("roduct", pageRequest); + + // then + assertAll( + () -> assertThat(result.hasNext()).isFalse(), + () -> assertThat(result.products().size()).isEqualTo(1L), + () -> assertThat(result.products().get(0).name()).isEqualTo("product1"), + () -> assertThat(result.products().get(0).artist()).isEqualTo("artist"), + () -> assertThat(result.products().get(0).price()).isEqualTo(10000L) + ); + } + +} From 78b904f69b9e620c2e114af95fb43e1d3150910a Mon Sep 17 00:00:00 2001 From: donghyuun Date: Sun, 27 Oct 2024 15:10:58 +0900 Subject: [PATCH 34/60] =?UTF-8?q?feat:=20=EC=8A=A4=ED=94=84=EB=A7=81=20?= =?UTF-8?q?=EC=95=A1=EC=B6=94=EC=97=90=EC=9D=B4=ED=84=B0=EB=A5=BC=20?= =?UTF-8?q?=ED=86=B5=ED=95=9C=20DB=20=ED=85=8C=EC=9D=B4=EB=B8=94=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=83=81=ED=83=9C=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/CustomDatabaseHealthIndicator.java | 27 +++++++++++++++++++ src/main/resources/application.yaml | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/helpmeCookies/global/infra/CustomDatabaseHealthIndicator.java diff --git a/src/main/java/com/helpmeCookies/global/infra/CustomDatabaseHealthIndicator.java b/src/main/java/com/helpmeCookies/global/infra/CustomDatabaseHealthIndicator.java new file mode 100644 index 0000000..fabd642 --- /dev/null +++ b/src/main/java/com/helpmeCookies/global/infra/CustomDatabaseHealthIndicator.java @@ -0,0 +1,27 @@ +package com.helpmeCookies.global.infra; + +import com.helpmeCookies.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class CustomDatabaseHealthIndicator implements HealthIndicator { + + private final UserRepository userRepository; + @Override + public Health health() { + try { + // 테이블이 존재하는지 확인하는 쿼리 (Users 테이블 사용) + long count = userRepository.count(); + // 테이블 존재 시 0 이상을 반환 + return Health.up().withDetail("Users table exists, count: ", count).build(); + } catch (Exception e) { + // 테이블이 없거나 데이터베이스 연결에 문제가 있는 경우 + return Health.down(e).withDetail("Users table", "Table missing or database issue") + .build(); + } + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 7922dd4..6827d3e 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -21,7 +21,7 @@ management: endpoints: web: exposure: # 외부에 노출할 엔드포인트 - include: prometheus, health, info, swagger-ui + include: prometheus, health, info metrics: tags: application: "katecam" # 메트릭 데이터에 태그를 추가 \ No newline at end of file From daaf61eb2bd9c2b848a8fdcd0e0bde19b432f05c Mon Sep 17 00:00:00 2001 From: sim-mer Date: Tue, 29 Oct 2024 12:03:32 +0900 Subject: [PATCH 35/60] =?UTF-8?q?#27=20feat(product):=20Product=20thumbnai?= =?UTF-8?q?lUrl=20=EC=82=BD=EC=9E=85=20=EB=A1=9C=EC=A7=81=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/helpmeCookies/product/entity/Product.java | 6 ++++++ .../helpmeCookies/product/service/ProductImageService.java | 6 ++++++ .../product/service/ProductImageServiceTest.java | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/helpmeCookies/product/entity/Product.java b/src/main/java/com/helpmeCookies/product/entity/Product.java index 584b32a..7f07fa3 100644 --- a/src/main/java/com/helpmeCookies/product/entity/Product.java +++ b/src/main/java/com/helpmeCookies/product/entity/Product.java @@ -44,6 +44,8 @@ public class Product extends BaseTimeEntity { @Column(nullable = false) private String preferredLocation; + private String thumbnailUrl; + @ElementCollection(targetClass = HashTag.class) @CollectionTable(name = "product_hashtags") @Enumerated(EnumType.STRING) @@ -113,4 +115,8 @@ public void update(String name, Category category, String size, Long price, Stri this.hashTags = hashTags; this.artistInfo = artistInfo; } + + public void updateThumbnail(String thumbnailUrl) { + this.thumbnailUrl = thumbnailUrl; + } } diff --git a/src/main/java/com/helpmeCookies/product/service/ProductImageService.java b/src/main/java/com/helpmeCookies/product/service/ProductImageService.java index 25215c5..0e34e78 100644 --- a/src/main/java/com/helpmeCookies/product/service/ProductImageService.java +++ b/src/main/java/com/helpmeCookies/product/service/ProductImageService.java @@ -3,6 +3,7 @@ import com.helpmeCookies.global.utils.AwsS3FileUtils; import com.helpmeCookies.product.dto.FileUploadResponse; import com.helpmeCookies.product.repository.ProductImageRepository; +import com.helpmeCookies.product.repository.ProductRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -16,12 +17,17 @@ public class ProductImageService { private final AwsS3FileUtils awsS3FileUtils; private final ProductImageRepository productImageRepository; + private final ProductRepository productRepository; @Transactional public List uploadMultiFiles(Long productId, List files) throws IOException { List uploadResponses = awsS3FileUtils.uploadMultiImages(files); uploadResponses.forEach(response -> productImageRepository.save(response.toEntity(productId))); + + var product = productRepository.findById(productId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 id입니다")); + product.updateThumbnail(uploadResponses.getFirst().photoUrl()); + return uploadResponses; } diff --git a/src/test/java/com/helpmeCookies/product/service/ProductImageServiceTest.java b/src/test/java/com/helpmeCookies/product/service/ProductImageServiceTest.java index b0b3653..23cc74d 100644 --- a/src/test/java/com/helpmeCookies/product/service/ProductImageServiceTest.java +++ b/src/test/java/com/helpmeCookies/product/service/ProductImageServiceTest.java @@ -41,7 +41,7 @@ class ProductImageServiceTest { @BeforeEach void setUp() { - productImageService = new ProductImageService(awsS3FileUtils, productImageRepository); + productImageService = new ProductImageService(awsS3FileUtils, productImageRepository, productRepository); } @AfterEach From 58dad0c9e991cd015b978106b39552bd9f219414 Mon Sep 17 00:00:00 2001 From: sim-mer Date: Tue, 29 Oct 2024 12:07:50 +0900 Subject: [PATCH 36/60] =?UTF-8?q?#27=20feat(product):=20=EC=83=81=ED=92=88?= =?UTF-8?q?=20=EA=B2=80=EC=83=89=20=EC=8B=9C=20Product=20thumbnailUrl=20?= =?UTF-8?q?=ED=8F=AC=ED=95=A8=EB=90=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/helpmeCookies/product/dto/ProductPage.java | 6 ++++-- .../product/repository/ProductRepository.java | 7 +------ .../product/repository/dto/ProductSearch.java | 1 + 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/helpmeCookies/product/dto/ProductPage.java b/src/main/java/com/helpmeCookies/product/dto/ProductPage.java index 1648c19..9778e2f 100644 --- a/src/main/java/com/helpmeCookies/product/dto/ProductPage.java +++ b/src/main/java/com/helpmeCookies/product/dto/ProductPage.java @@ -10,14 +10,16 @@ public record Info( Long id, String name, String artist, - Long price + Long price, + String thumbnailUrl ) { public static Info from(ProductSearch productSearch) { return new Info( productSearch.getId(), productSearch.getName(), productSearch.getArtist(), - productSearch.getPrice() + productSearch.getPrice(), + productSearch.getThumbnailUrl() ); } diff --git a/src/main/java/com/helpmeCookies/product/repository/ProductRepository.java b/src/main/java/com/helpmeCookies/product/repository/ProductRepository.java index 512722d..4b23b79 100644 --- a/src/main/java/com/helpmeCookies/product/repository/ProductRepository.java +++ b/src/main/java/com/helpmeCookies/product/repository/ProductRepository.java @@ -12,12 +12,7 @@ @Repository public interface ProductRepository extends JpaRepository { - @Query("SELECT p.id AS id, p.name AS name, a.nickname AS artist, p.price AS price " + - "FROM Product p JOIN p.artistInfo a " + - "WHERE p.name LIKE %:query%") // Index 미사용 - Page findByName(@Param("query") String query, Pageable pageable); - - @Query(value = "SELECT p.id, p.name, a.nickname AS artist, p.price " + + @Query(value = "SELECT p.id, p.name, p.thumbnail_url, a.nickname AS artist, p.price " + "FROM product p JOIN artist_info a ON p.artist_info_id = a.id " + "WHERE MATCH(p.name) AGAINST (:query IN BOOLEAN MODE)", countQuery = "SELECT COUNT(*) " + diff --git a/src/main/java/com/helpmeCookies/product/repository/dto/ProductSearch.java b/src/main/java/com/helpmeCookies/product/repository/dto/ProductSearch.java index 3c5fcee..c997acd 100644 --- a/src/main/java/com/helpmeCookies/product/repository/dto/ProductSearch.java +++ b/src/main/java/com/helpmeCookies/product/repository/dto/ProductSearch.java @@ -6,4 +6,5 @@ public interface ProductSearch { String getName(); String getArtist(); Long getPrice(); + String getThumbnailUrl(); } From 2be345745c1f41308d0f8d271bb3ec6e3293328f Mon Sep 17 00:00:00 2001 From: sim-mer Date: Tue, 29 Oct 2024 12:23:19 +0900 Subject: [PATCH 37/60] =?UTF-8?q?#27=20test(product):=20=EC=83=81=ED=92=88?= =?UTF-8?q?=20=EA=B2=80=EC=83=89=20thumbnailUrl=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../helpmeCookies/product/repository/dto/ProductSearch.java | 2 +- .../helpmeCookies/product/search/SearchControllerTest.java | 6 ++++-- .../helpmeCookies/product/search/SearchRepositoryTest.java | 1 + .../com/helpmeCookies/product/search/SearchServiceTest.java | 4 +++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/helpmeCookies/product/repository/dto/ProductSearch.java b/src/main/java/com/helpmeCookies/product/repository/dto/ProductSearch.java index c997acd..60ca2e0 100644 --- a/src/main/java/com/helpmeCookies/product/repository/dto/ProductSearch.java +++ b/src/main/java/com/helpmeCookies/product/repository/dto/ProductSearch.java @@ -4,7 +4,7 @@ public interface ProductSearch { Long getId(); String getName(); + String getThumbnailUrl(); String getArtist(); Long getPrice(); - String getThumbnailUrl(); } diff --git a/src/test/java/com/helpmeCookies/product/search/SearchControllerTest.java b/src/test/java/com/helpmeCookies/product/search/SearchControllerTest.java index be84884..4566fe1 100644 --- a/src/test/java/com/helpmeCookies/product/search/SearchControllerTest.java +++ b/src/test/java/com/helpmeCookies/product/search/SearchControllerTest.java @@ -56,12 +56,13 @@ public class SearchControllerTest { given(productSearch.getName()).willReturn("product1"); given(productSearch.getArtist()).willReturn("artist"); given(productSearch.getPrice()).willReturn(10000L); + given(productSearch.getThumbnailUrl()).willReturn("thumbnailUrl"); var paging = ProductPage.Paging.from(new PageImpl<>(List.of(productSearch))); given(productService.getProductsByPage(eq(query), any(Pageable.class))) .willReturn(paging); // when & then - mvc.perform(get("/test/v1/products") + mvc.perform(get("/v1/products") .param("query", query) .param("size", String.valueOf(size)) .param("page", String.valueOf(page)) @@ -70,7 +71,8 @@ public class SearchControllerTest { .andExpect(jsonPath("$.hasNext").value(false)) .andExpect(jsonPath("$.products[0].name").value("product1")) .andExpect(jsonPath("$.products[0].artist").value("artist")) - .andExpect(jsonPath("$.products[0].price").value(10000L)); + .andExpect(jsonPath("$.products[0].price").value(10000L)) + .andExpect(jsonPath("$.products[0].thumbnailUrl").value("thumbnailUrl")); } } diff --git a/src/test/java/com/helpmeCookies/product/search/SearchRepositoryTest.java b/src/test/java/com/helpmeCookies/product/search/SearchRepositoryTest.java index b5f2ad0..e914d94 100644 --- a/src/test/java/com/helpmeCookies/product/search/SearchRepositoryTest.java +++ b/src/test/java/com/helpmeCookies/product/search/SearchRepositoryTest.java @@ -43,6 +43,7 @@ public class SearchRepositoryTest { .contains("SELECT") .contains("p.id,") .contains("p.name,") + .contains("p.thumbnail_url,") .contains("a.nickname AS artist,") .contains("p.price") .contains("FROM product p") diff --git a/src/test/java/com/helpmeCookies/product/search/SearchServiceTest.java b/src/test/java/com/helpmeCookies/product/search/SearchServiceTest.java index 65c1edb..a13d83d 100644 --- a/src/test/java/com/helpmeCookies/product/search/SearchServiceTest.java +++ b/src/test/java/com/helpmeCookies/product/search/SearchServiceTest.java @@ -42,6 +42,7 @@ public class SearchServiceTest { given(productSearch.getName()).willReturn("product1"); given(productSearch.getArtist()).willReturn("artist"); given(productSearch.getPrice()).willReturn(10000L); + given(productSearch.getThumbnailUrl()).willReturn("thumbnailUrl"); var productPage = new PageImpl<>(List.of(productSearch)); given(productRepository.findByNameWithIdx("roduct", pageRequest)) @@ -56,7 +57,8 @@ public class SearchServiceTest { () -> assertThat(result.products().size()).isEqualTo(1L), () -> assertThat(result.products().get(0).name()).isEqualTo("product1"), () -> assertThat(result.products().get(0).artist()).isEqualTo("artist"), - () -> assertThat(result.products().get(0).price()).isEqualTo(10000L) + () -> assertThat(result.products().get(0).price()).isEqualTo(10000L), + () -> assertThat(result.products().get(0).thumbnailUrl()).isEqualTo("thumbnailUrl") ); } From b9ac98aca46a3f31a42adcd7c53a048184f18b72 Mon Sep 17 00:00:00 2001 From: sim-mer Date: Tue, 29 Oct 2024 13:21:16 +0900 Subject: [PATCH 38/60] =?UTF-8?q?#27=20feat(user.repository):=20=EC=9E=91?= =?UTF-8?q?=EA=B0=80=20=EA=B2=80=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/repository/ArtistInfoRepository.java | 18 +++++++++++++++--- .../user/repository/dto/ArtistSearch.java | 9 +++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/helpmeCookies/user/repository/dto/ArtistSearch.java diff --git a/src/main/java/com/helpmeCookies/user/repository/ArtistInfoRepository.java b/src/main/java/com/helpmeCookies/user/repository/ArtistInfoRepository.java index 49847d2..86da115 100644 --- a/src/main/java/com/helpmeCookies/user/repository/ArtistInfoRepository.java +++ b/src/main/java/com/helpmeCookies/user/repository/ArtistInfoRepository.java @@ -1,12 +1,24 @@ package com.helpmeCookies.user.repository; +import com.helpmeCookies.user.entity.ArtistInfo; +import com.helpmeCookies.user.repository.dto.ArtistSearch; import java.util.Optional; - +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; - -import com.helpmeCookies.user.entity.ArtistInfo; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface ArtistInfoRepository extends JpaRepository { Optional findByUserId(Long userId); boolean existsByUserId(Long userId); + + @Query(value = "SELECT a.id, a.nickname, a.artist_image_url, a.total_followers, a.total_likes " + + "FROM artist_info a " + + "WHERE MATCH(a.nickname) AGAINST (:query IN BOOLEAN MODE)", + countQuery = "SELECT COUNT(*) " + + "FROM artist_info a " + + "WHERE MATCH(a.nickname) AGAINST (:query IN BOOLEAN MODE)", + nativeQuery = true) // Index 사용 + Page findByNicknameWithIdx(@Param("query") String query, Pageable pageable); } diff --git a/src/main/java/com/helpmeCookies/user/repository/dto/ArtistSearch.java b/src/main/java/com/helpmeCookies/user/repository/dto/ArtistSearch.java new file mode 100644 index 0000000..777ed6a --- /dev/null +++ b/src/main/java/com/helpmeCookies/user/repository/dto/ArtistSearch.java @@ -0,0 +1,9 @@ +package com.helpmeCookies.user.repository.dto; + +public interface ArtistSearch { + Long getId(); + String getNickname(); + String getArtistImageUrl(); + Long getTotalFollowers(); + Long getTotalLikes(); +} From 135135b01d3c11063b2380725d10cf05c36e8f6f Mon Sep 17 00:00:00 2001 From: sim-mer Date: Tue, 29 Oct 2024 13:22:29 +0900 Subject: [PATCH 39/60] =?UTF-8?q?#27=20feat(user.service):=20=EC=9E=91?= =?UTF-8?q?=EA=B0=80=20=EA=B2=80=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../helpmeCookies/user/service/ArtistService.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/helpmeCookies/user/service/ArtistService.java b/src/main/java/com/helpmeCookies/user/service/ArtistService.java index 3f63d32..c36475e 100644 --- a/src/main/java/com/helpmeCookies/user/service/ArtistService.java +++ b/src/main/java/com/helpmeCookies/user/service/ArtistService.java @@ -1,10 +1,8 @@ package com.helpmeCookies.user.service; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import com.helpmeCookies.global.exception.user.ResourceNotFoundException; import com.helpmeCookies.user.dto.ArtistInfoDto; +import com.helpmeCookies.user.dto.ArtistInfoPage; import com.helpmeCookies.user.dto.BusinessArtistDto; import com.helpmeCookies.user.dto.StudentArtistDto; import com.helpmeCookies.user.dto.request.BusinessArtistReq; @@ -20,8 +18,10 @@ import com.helpmeCookies.user.repository.StudentArtistRepository; import com.helpmeCookies.user.repository.UserRepository; import com.sun.jdi.request.DuplicateRequestException; - import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor @Service @@ -115,4 +115,10 @@ public ArtistDetailsRes getArtistDetails(Long userId) { throw new ResourceNotFoundException("존재하지 않는 아티스트입니다."); } } + + @Transactional(readOnly = true) + public ArtistInfoPage.Paging getArtistsByPage(String query, Pageable pageable) { + var artistInfoPage = artistInfoRepository.findByNicknameWithIdx(query, pageable); + return ArtistInfoPage.Paging.from(artistInfoPage); + } } From 3e94711e67cf550b469f38f4eeebe5ee262877a0 Mon Sep 17 00:00:00 2001 From: sim-mer Date: Tue, 29 Oct 2024 13:22:35 +0900 Subject: [PATCH 40/60] =?UTF-8?q?#27=20feat(user.controller):=20=EC=9E=91?= =?UTF-8?q?=EA=B0=80=20=EA=B2=80=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/ArtistController.java | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/helpmeCookies/user/controller/ArtistController.java b/src/main/java/com/helpmeCookies/user/controller/ArtistController.java index 32a71f7..6e94168 100644 --- a/src/main/java/com/helpmeCookies/user/controller/ArtistController.java +++ b/src/main/java/com/helpmeCookies/user/controller/ArtistController.java @@ -1,24 +1,24 @@ package com.helpmeCookies.user.controller; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -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.RequestBody; -import org.springframework.web.bind.annotation.RestController; - -import com.helpmeCookies.global.jwt.JwtProvider; import com.helpmeCookies.global.jwt.JwtUser; import com.helpmeCookies.user.controller.apiDocs.ArtistApiDocs; +import com.helpmeCookies.user.dto.ArtistInfoPage; import com.helpmeCookies.user.dto.request.BusinessArtistReq; import com.helpmeCookies.user.dto.request.StudentArtistReq; import com.helpmeCookies.user.dto.response.ArtistDetailsRes; import com.helpmeCookies.user.service.ArtistService; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor @@ -62,4 +62,14 @@ public ArtistDetailsRes getArtist( ) { return artistService.getArtistDetails(jwtUser.getId()); } + + @GetMapping("/v1/artists") + public ResponseEntity getArtistsByPage( + @RequestParam("query") String query, + @RequestParam(name = "size", required = false, defaultValue = "20") int size, + @RequestParam("page") int page + ) { + var pageable = PageRequest.of(page, size); + return ResponseEntity.ok(artistService.getArtistsByPage(query, pageable)); + } } From aff8acfc266f2811dc13e7e871f261b36687fa04 Mon Sep 17 00:00:00 2001 From: sim-mer Date: Tue, 29 Oct 2024 13:22:50 +0900 Subject: [PATCH 41/60] =?UTF-8?q?#27=20feat(user.dto):=20=EC=9E=91?= =?UTF-8?q?=EA=B0=80=20=EA=B2=80=EC=83=89=20=ED=8E=98=EC=9D=B4=EC=A7=95=20?= =?UTF-8?q?DTO=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/dto/ArtistInfoPage.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/main/java/com/helpmeCookies/user/dto/ArtistInfoPage.java diff --git a/src/main/java/com/helpmeCookies/user/dto/ArtistInfoPage.java b/src/main/java/com/helpmeCookies/user/dto/ArtistInfoPage.java new file mode 100644 index 0000000..725fb2e --- /dev/null +++ b/src/main/java/com/helpmeCookies/user/dto/ArtistInfoPage.java @@ -0,0 +1,46 @@ +package com.helpmeCookies.user.dto; + +import com.helpmeCookies.user.repository.dto.ArtistSearch; +import java.util.List; +import org.springframework.data.domain.Page; + +public class ArtistInfoPage { + + public record Info( + Long id, + String nickname, + String artistImageUrl, + Long totalFollowers, + Long totalLikes + ) { + + private static Info from(ArtistSearch artistSearch) { + return new Info( + artistSearch.getId(), + artistSearch.getNickname(), + artistSearch.getArtistImageUrl(), + artistSearch.getTotalFollowers(), + artistSearch.getTotalLikes() + ); + } + + public static List of(List content) { + return content.stream() + .map(Info::from) + .toList(); + } + } + + public record Paging ( + boolean hasNext, + List artists + ) { + public static Paging from(Page artistPage) { + return new Paging( + artistPage.hasNext(), + Info.of(artistPage.getContent()) + ); + } + } + +} From 285c4ce4130b4185681e3a15f36203c8edc8a1a3 Mon Sep 17 00:00:00 2001 From: sim-mer Date: Tue, 29 Oct 2024 13:23:07 +0900 Subject: [PATCH 42/60] =?UTF-8?q?#27=20docs(user):=20=EC=9E=91=EA=B0=80=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20API=20=EB=AC=B8=EC=84=9C=EC=97=90=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/apiDocs/ArtistApiDocs.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/helpmeCookies/user/controller/apiDocs/ArtistApiDocs.java b/src/main/java/com/helpmeCookies/user/controller/apiDocs/ArtistApiDocs.java index 3f77c43..254c5b7 100644 --- a/src/main/java/com/helpmeCookies/user/controller/apiDocs/ArtistApiDocs.java +++ b/src/main/java/com/helpmeCookies/user/controller/apiDocs/ArtistApiDocs.java @@ -1,19 +1,19 @@ package com.helpmeCookies.user.controller.apiDocs; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -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.RequestBody; - import com.helpmeCookies.global.jwt.JwtUser; +import com.helpmeCookies.user.dto.ArtistInfoPage.Paging; import com.helpmeCookies.user.dto.request.BusinessArtistReq; import com.helpmeCookies.user.dto.request.StudentArtistReq; import com.helpmeCookies.user.dto.response.ArtistDetailsRes; - import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +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.RequestBody; @Tag(name = "작가 관련 기능", description = "작가 관련 API") public interface ArtistApiDocs { @@ -44,4 +44,11 @@ ArtistDetailsRes getArtist( ArtistDetailsRes getArtist( @AuthenticationPrincipal JwtUser jwtUser ); + + @Operation(summary = "작가 검색") + ResponseEntity getArtistsByPage( + String query, + @Parameter(description = "default value 20") int size, + int page + ); } From 425881a950b1eda7e25e86aa2f70ac6ee5ba242c Mon Sep 17 00:00:00 2001 From: sim-mer Date: Tue, 29 Oct 2024 13:37:01 +0900 Subject: [PATCH 43/60] =?UTF-8?q?#27=20test(user):=20=EC=9E=91=EA=B0=80=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../search/SearchControllerTest.java | 42 +++++++++++++++- .../search/SearchRepositoryTest.java | 31 +++++++++++- .../search/SearchServiceTest.java | 49 +++++++++++++++++-- 3 files changed, 114 insertions(+), 8 deletions(-) rename src/test/java/com/helpmeCookies/{product => }/search/SearchControllerTest.java (64%) rename src/test/java/com/helpmeCookies/{product => }/search/SearchRepositoryTest.java (68%) rename src/test/java/com/helpmeCookies/{product => }/search/SearchServiceTest.java (51%) diff --git a/src/test/java/com/helpmeCookies/product/search/SearchControllerTest.java b/src/test/java/com/helpmeCookies/search/SearchControllerTest.java similarity index 64% rename from src/test/java/com/helpmeCookies/product/search/SearchControllerTest.java rename to src/test/java/com/helpmeCookies/search/SearchControllerTest.java index 4566fe1..f212587 100644 --- a/src/test/java/com/helpmeCookies/product/search/SearchControllerTest.java +++ b/src/test/java/com/helpmeCookies/search/SearchControllerTest.java @@ -1,4 +1,4 @@ -package com.helpmeCookies.product.search; +package com.helpmeCookies.search; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -15,6 +15,10 @@ import com.helpmeCookies.product.service.ProductImageService; import com.helpmeCookies.product.service.ProductService; import com.helpmeCookies.product.util.ProductSort; +import com.helpmeCookies.user.controller.ArtistController; +import com.helpmeCookies.user.dto.ArtistInfoPage; +import com.helpmeCookies.user.repository.dto.ArtistSearch; +import com.helpmeCookies.user.service.ArtistService; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -27,7 +31,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.test.web.servlet.MockMvc; -@WebMvcTest(ProductController.class) +@WebMvcTest({ProductController.class, ArtistController.class}) @AutoConfigureMockMvc(addFilters = false) @Import(JwtProvider.class) public class SearchControllerTest { @@ -41,6 +45,9 @@ public class SearchControllerTest { @MockBean private ProductImageService productImageService; + @MockBean + private ArtistService artistService; + @Test @DisplayName("상품 검색 컨트롤러") @@ -75,4 +82,35 @@ public class SearchControllerTest { .andExpect(jsonPath("$.products[0].thumbnailUrl").value("thumbnailUrl")); } + @Test + @DisplayName("작가 검색 컨트롤러") + void 작가_검색() throws Exception { + // given + String query = "nickname"; + int size = 10; + int page = 0; + + var artistSearch = mock(ArtistSearch.class); + given(artistSearch.getId()).willReturn(1L); + given(artistSearch.getNickname()).willReturn("nickname"); + given(artistSearch.getArtistImageUrl()).willReturn("artistImageUrl"); + given(artistSearch.getTotalFollowers()).willReturn(10000L); + given(artistSearch.getTotalLikes()).willReturn(10000L); + var paging = ArtistInfoPage.Paging.from(new PageImpl<>(List.of(artistSearch))); + given(artistService.getArtistsByPage(eq(query), any(Pageable.class))) + .willReturn(paging); + + // when & then + mvc.perform(get("/v1/artists") + .param("query", query) + .param("size", String.valueOf(size)) + .param("page", String.valueOf(page))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.hasNext").value(false)) + .andExpect(jsonPath("$.artists[0].nickname").value("nickname")) + .andExpect(jsonPath("$.artists[0].artistImageUrl").value("artistImageUrl")) + .andExpect(jsonPath("$.artists[0].totalFollowers").value(10000L)) + .andExpect(jsonPath("$.artists[0].totalLikes").value(10000L)); + } + } diff --git a/src/test/java/com/helpmeCookies/product/search/SearchRepositoryTest.java b/src/test/java/com/helpmeCookies/search/SearchRepositoryTest.java similarity index 68% rename from src/test/java/com/helpmeCookies/product/search/SearchRepositoryTest.java rename to src/test/java/com/helpmeCookies/search/SearchRepositoryTest.java index e914d94..9b00725 100644 --- a/src/test/java/com/helpmeCookies/product/search/SearchRepositoryTest.java +++ b/src/test/java/com/helpmeCookies/search/SearchRepositoryTest.java @@ -1,4 +1,4 @@ -package com.helpmeCookies.product.search; +package com.helpmeCookies.search; import static com.helpmeCookies.product.util.SortUtil.convertProductSort; import static org.assertj.core.api.Assertions.assertThat; @@ -6,6 +6,7 @@ import com.helpmeCookies.global.config.QueryDSLConfig; import com.helpmeCookies.product.repository.ProductRepository; import com.helpmeCookies.product.util.ProductSort; +import com.helpmeCookies.user.repository.ArtistInfoRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -24,6 +25,9 @@ public class SearchRepositoryTest { @Autowired private ProductRepository productRepository; + @Autowired + private ArtistInfoRepository artistInfoRepository; + @Test @DisplayName("상품 검색 쿼리 확인") @@ -52,4 +56,29 @@ public class SearchRepositoryTest { .contains("order by p.created_date desc"); //.contains("limit ?") 테스트 dbh2 db에서는 limit이 없어서 제외 } + + @Test + @DisplayName("작가 검색 쿼리 확인") + void 작가_검색(CapturedOutput out) { + // given + var pageRequest = PageRequest.of(0, 10); + + // when + try{ + artistInfoRepository.findByNicknameWithIdx("nickname", pageRequest); + } catch (Exception ignored) { + } + + // then + assertThat(out.getOut()) + .contains("SELECT") + .contains("a.id,") + .contains("a.nickname,") + .contains("a.artist_image_url,") + .contains("a.total_followers,") + .contains("a.total_likes") + .contains("FROM artist_info a") + .contains("WHERE MATCH(a.nickname) AGAINST (? IN BOOLEAN MODE)"); + } + } diff --git a/src/test/java/com/helpmeCookies/product/search/SearchServiceTest.java b/src/test/java/com/helpmeCookies/search/SearchServiceTest.java similarity index 51% rename from src/test/java/com/helpmeCookies/product/search/SearchServiceTest.java rename to src/test/java/com/helpmeCookies/search/SearchServiceTest.java index a13d83d..4a9b738 100644 --- a/src/test/java/com/helpmeCookies/product/search/SearchServiceTest.java +++ b/src/test/java/com/helpmeCookies/search/SearchServiceTest.java @@ -1,4 +1,4 @@ -package com.helpmeCookies.product.search; +package com.helpmeCookies.search; import static com.helpmeCookies.product.util.SortUtil.convertProductSort; import static org.assertj.core.api.Assertions.assertThat; @@ -10,6 +10,9 @@ import com.helpmeCookies.product.repository.dto.ProductSearch; import com.helpmeCookies.product.service.ProductService; import com.helpmeCookies.product.util.ProductSort; +import com.helpmeCookies.user.repository.ArtistInfoRepository; +import com.helpmeCookies.user.repository.dto.ArtistSearch; +import com.helpmeCookies.user.service.ArtistService; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -31,6 +34,12 @@ public class SearchServiceTest { @InjectMocks private ProductService productService; + @Mock + private ArtistInfoRepository artistInfoRepository; + + @InjectMocks + private ArtistService artistService; + @Test @DisplayName("상품 검색 서비스") void 상품_검색() { @@ -55,10 +64,40 @@ public class SearchServiceTest { assertAll( () -> assertThat(result.hasNext()).isFalse(), () -> assertThat(result.products().size()).isEqualTo(1L), - () -> assertThat(result.products().get(0).name()).isEqualTo("product1"), - () -> assertThat(result.products().get(0).artist()).isEqualTo("artist"), - () -> assertThat(result.products().get(0).price()).isEqualTo(10000L), - () -> assertThat(result.products().get(0).thumbnailUrl()).isEqualTo("thumbnailUrl") + () -> assertThat(result.products().getFirst().name()).isEqualTo("product1"), + () -> assertThat(result.products().getFirst().artist()).isEqualTo("artist"), + () -> assertThat(result.products().getFirst().price()).isEqualTo(10000L), + () -> assertThat(result.products().getFirst().thumbnailUrl()).isEqualTo("thumbnailUrl") + ); + } + + @Test + @DisplayName("작가 검색 서비스") + void 작가_검색() { + // given + var pageRequest = PageRequest.of(0, 10); + var artistSearch = mock(ArtistSearch.class); + given(artistSearch.getId()).willReturn(1L); + given(artistSearch.getNickname()).willReturn("nickname"); + given(artistSearch.getArtistImageUrl()).willReturn("artistImageUrl"); + given(artistSearch.getTotalFollowers()).willReturn(10000L); + given(artistSearch.getTotalLikes()).willReturn(10000L); + + var artistPage = new PageImpl<>(List.of(artistSearch)); + given(artistInfoRepository.findByNicknameWithIdx("nickname", pageRequest)) + .willReturn(artistPage); + + // when + var result = artistService.getArtistsByPage("nickname", pageRequest); + + // then + assertAll( + () -> assertThat(result.hasNext()).isFalse(), + () -> assertThat(result.artists().size()).isEqualTo(1L), + () -> assertThat(result.artists().getFirst().nickname()).isEqualTo("nickname"), + () -> assertThat(result.artists().getFirst().artistImageUrl()).isEqualTo("artistImageUrl"), + () -> assertThat(result.artists().getFirst().totalFollowers()).isEqualTo(10000L), + () -> assertThat(result.artists().getFirst().totalLikes()).isEqualTo(10000L) ); } From 8478defed061f516220aada72d2840893ff8a7f5 Mon Sep 17 00:00:00 2001 From: sim-mer Date: Tue, 29 Oct 2024 13:37:32 +0900 Subject: [PATCH 44/60] =?UTF-8?q?#27=20test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/repository/querydsl/UserCustomRepositoryImpl.java | 4 ++-- src/test/java/com/helpmeCookies/e2e/ArtistE2Etest.java | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/helpmeCookies/user/repository/querydsl/UserCustomRepositoryImpl.java b/src/main/java/com/helpmeCookies/user/repository/querydsl/UserCustomRepositoryImpl.java index bd31f40..2b176f0 100644 --- a/src/main/java/com/helpmeCookies/user/repository/querydsl/UserCustomRepositoryImpl.java +++ b/src/main/java/com/helpmeCookies/user/repository/querydsl/UserCustomRepositoryImpl.java @@ -33,8 +33,8 @@ public Page findFollowingUsers(Long userId, Pageable pageable) .select(Projections.constructor( UserFollowingDto.class, user.id, - user.userInfo.userImageUrl, - user.userInfo.nickname, + user.userImageUrl, + user.nickname, artistInfo.totalFollowers, artistInfo.totalLikes )) diff --git a/src/test/java/com/helpmeCookies/e2e/ArtistE2Etest.java b/src/test/java/com/helpmeCookies/e2e/ArtistE2Etest.java index 6d76c7b..3522fdc 100644 --- a/src/test/java/com/helpmeCookies/e2e/ArtistE2Etest.java +++ b/src/test/java/com/helpmeCookies/e2e/ArtistE2Etest.java @@ -45,7 +45,8 @@ public void testRegisterStudents_withValidToken() throws Exception { StudentArtistReq request = new StudentArtistReq( "student@example.com", "Example University", - "Computer Science" + "Computer Science", + "" ); String requestJson = objectMapper.writeValueAsString(request); From 447a2d4f78cdf1115f5d15cd13b2c84d17677294 Mon Sep 17 00:00:00 2001 From: yooonwodyd Date: Fri, 1 Nov 2024 17:04:39 +0900 Subject: [PATCH 45/60] chore:[#65]- add Oauth2 Lib MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 카카오 로그인을 위한 Oauth2 라이브러리 추가 --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 615d23c..a8dbafa 100644 --- a/build.gradle +++ b/build.gradle @@ -42,6 +42,7 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.security:spring-security-oauth2-client' // Lombok compileOnly 'org.projectlombok:lombok' From ac75818f06d91860c2cc403764bb8c464f004ab8 Mon Sep 17 00:00:00 2001 From: yooonwodyd Date: Fri, 1 Nov 2024 17:05:30 +0900 Subject: [PATCH 46/60] feat:[#65]- add Oauth2 login logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 카카오로그인을 위한 로직 추가. /oauth2의 경우 별도의 보안 로직을 가짐 --- .../global/security/WebSecurityConfig.java | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/helpmeCookies/global/security/WebSecurityConfig.java b/src/main/java/com/helpmeCookies/global/security/WebSecurityConfig.java index c91beed..20b7f52 100644 --- a/src/main/java/com/helpmeCookies/global/security/WebSecurityConfig.java +++ b/src/main/java/com/helpmeCookies/global/security/WebSecurityConfig.java @@ -19,8 +19,6 @@ @Configuration @EnableWebSecurity @RequiredArgsConstructor - - @Controller public class WebSecurityConfig { private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @@ -29,9 +27,30 @@ public class WebSecurityConfig { @Bean public WebSecurityCustomizer configure() { - return (web) -> web.ignoring() - .requestMatchers("/static/**") - .requestMatchers("/test/**"); + return (web) -> web.ignoring(); + } + + @Bean + public SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http) throws Exception { + http + .securityMatcher("/oauth2/**") + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests((authorize) -> authorize + .requestMatchers("/oauth2/authorization/**", + "/oauth2/code/kakao/**" + ).permitAll() + .anyRequest().authenticated() + ) + .oauth2Login((oauth2) -> oauth2 + .redirectionEndpoint(redirection -> redirection + .baseUri("/oauth2/code/*")) + .userInfoEndpoint((userInfo) -> userInfo + .userService(new Oauth2CustomUserService()) + ) + .defaultSuccessUrl("/oauth2/login/kakao") + ); + + return http.build(); } @Bean @@ -43,7 +62,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests((authorize) -> authorize .requestMatchers( - "/login", "/signup", "/", "/user", + "/login", "/signup", "/ttt/*", "/user", "/api/auth/**", "/swagger-ui/**", "/actuator/**", @@ -51,9 +70,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "swagger-ui/**" ).permitAll() .anyRequest().authenticated() - ); - - http.exceptionHandling((exception) -> exception + ).exceptionHandling((exception) -> exception .authenticationEntryPoint(jwtAuthenticationEntryPoint) .accessDeniedHandler(jwtAccessDeniedHandler) ); From 238adf17870a42ae3f8a95fc77ee4d56fa8ee373 Mon Sep 17 00:00:00 2001 From: yooonwodyd Date: Fri, 1 Nov 2024 17:06:53 +0900 Subject: [PATCH 47/60] refact:[#65]- refact jwt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 전체적인 주석과 간단한 띄어쓰기 변경 --- .../helpmeCookies/global/jwt/JwtProvider.java | 29 ++++++++----------- .../security/JwtAccessDeniedHandler.java | 2 -- .../security/JwtAuthenticationEntryPoint.java | 1 - .../security/JwtAuthenticationFilter.java | 1 - 4 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/helpmeCookies/global/jwt/JwtProvider.java b/src/main/java/com/helpmeCookies/global/jwt/JwtProvider.java index 7a1e9c2..3fa9c37 100644 --- a/src/main/java/com/helpmeCookies/global/jwt/JwtProvider.java +++ b/src/main/java/com/helpmeCookies/global/jwt/JwtProvider.java @@ -47,15 +47,16 @@ public JwtToken createToken(JwtUser jwtUser) { .build(); } - // 유요한 토큰인지 확인 + /* + 토큰 검증시 rawToken을 Claims로 변환하고, 해당 토큰이 accessToken이면서 만료되어있지 않다면 True를 반환한다. + */ + public boolean validateToken(String rawToken, boolean isAccessToken) { try { - // 엑세스 토큰인지 확인 Claims claims = extractClaims(rawToken); if (claims.get(IS_ACCESS_TOKEN, Boolean.class) != isAccessToken) { return false; } - // 만료시간 확인 return !claims.getExpiration().before(new Date()); } catch (Exception e) { return false; @@ -65,22 +66,24 @@ public boolean validateToken(String rawToken, boolean isAccessToken) { /** * refreshToken을 통해, accessToken을 재발급하는 메서드. * refreshToken의 유효성을 검사하고, isAccessToken이 true일때만 accessToken을 재발급한다. - * TODO: refreshToken을 저장하고, 저장된 refreshToken과 비교하는 로직 필요 + * TODO: refreshToken을 저장하고, 저장된 refreshToken과 비교하는 로직 필요 redis 추가 후 구현 */ public String reissueAccessToken(String refreshToken) { Claims claims = extractClaims(refreshToken); if (claims.get(IS_ACCESS_TOKEN, Boolean.class)) { throw new IllegalArgumentException("리프레시 토큰이 아닙니다."); } + + Date expiration = claims.getExpiration(); + if (expiration.before(new Date())) { + throw new IllegalArgumentException("리프레시 토큰이 만료되었습니다."); + } + JwtUser jwtUser = claimsToJwtUser(claims); return generateToken(jwtUser, true); } - /** - * [validateToken] 이후 호출하는 메서드. - * rawToken을 통해 JwtUser를 추출한다. - * [jwtUser]는 userId와 role을 가지고 있다. 즉 JWT에 저장된 정보를 추출한다. - */ + public JwtUser getJwtUser(String rawToken) { Claims claims = extractClaims(rawToken); return claimsToJwtUser(claims); @@ -91,10 +94,6 @@ private JwtUser claimsToJwtUser(Claims claims) { return JwtUser.of(Long.parseLong(userId)); } - /** - * Jwt 토큰생성 - * accessToken과 refreshToken의 다른점은 만료시간과, isAccessToken이다. - */ private String generateToken(JwtUser jwtUser, boolean isAccessToken) { long expireTime = isAccessToken ? accessTokenExpireTime : refreshTokenExpireTime; Date expireDate = new Date(System.currentTimeMillis() + expireTime); @@ -106,7 +105,6 @@ private String generateToken(JwtUser jwtUser, boolean isAccessToken) { .compact(); } - private Claims extractClaims(String rawToken) { return Jwts.parserBuilder() .setSigningKey(secretKey) @@ -115,9 +113,6 @@ private Claims extractClaims(String rawToken) { .getBody(); } - /** - * HS256방식의 키를 생성한다. - */ @Override public void afterPropertiesSet() { secretKey = new SecretKeySpec(secret.getBytes(), SignatureAlgorithm.HS256.getJcaName()); diff --git a/src/main/java/com/helpmeCookies/global/security/JwtAccessDeniedHandler.java b/src/main/java/com/helpmeCookies/global/security/JwtAccessDeniedHandler.java index f6dde1c..f11567c 100644 --- a/src/main/java/com/helpmeCookies/global/security/JwtAccessDeniedHandler.java +++ b/src/main/java/com/helpmeCookies/global/security/JwtAccessDeniedHandler.java @@ -26,7 +26,5 @@ public class JwtAccessDeniedHandler implements AccessDeniedHandler { public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) { log.error("Token : {}", request.getHeader("Authorization")); - // TODO: 에러코드 추가 - response.setStatus(403); } } \ No newline at end of file diff --git a/src/main/java/com/helpmeCookies/global/security/JwtAuthenticationEntryPoint.java b/src/main/java/com/helpmeCookies/global/security/JwtAuthenticationEntryPoint.java index 2072bb2..2f9a5a8 100644 --- a/src/main/java/com/helpmeCookies/global/security/JwtAuthenticationEntryPoint.java +++ b/src/main/java/com/helpmeCookies/global/security/JwtAuthenticationEntryPoint.java @@ -24,6 +24,5 @@ public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { log.debug("Token : {}", request.getHeader("Authorization")); - response.setStatus(401); } } diff --git a/src/main/java/com/helpmeCookies/global/security/JwtAuthenticationFilter.java b/src/main/java/com/helpmeCookies/global/security/JwtAuthenticationFilter.java index 1e70986..4570b65 100644 --- a/src/main/java/com/helpmeCookies/global/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/helpmeCookies/global/security/JwtAuthenticationFilter.java @@ -41,7 +41,6 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse return; } - // TODO: UserDetailsService를 통해 사용자 정보를 가져와 인증을 진행한다. if (jwtProvider.validateToken(rawToken, true)) { JwtUser jwtUser = jwtProvider.getJwtUser(rawToken); Authentication authentication = new UsernamePasswordAuthenticationToken(jwtUser, null, From 43931c81cde4bfad60b9bc4133c8e90e073743dc Mon Sep 17 00:00:00 2001 From: yooonwodyd Date: Fri, 1 Nov 2024 17:07:35 +0900 Subject: [PATCH 48/60] refact:[#65]- refact userupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Put 메서드에 맞게 모든 유저 정보를 수정 할 수 있도록 변경 --- .../helpmeCookies/user/dto/UserInfoDto.java | 2 ++ .../{UserInfoReq.java => UserReq.java} | 22 ++++++------------- .../com/helpmeCookies/user/entity/User.java | 8 +++++-- .../user/service/UserService.java | 14 ++++++++++-- 4 files changed, 27 insertions(+), 19 deletions(-) rename src/main/java/com/helpmeCookies/user/dto/request/{UserInfoReq.java => UserReq.java} (56%) diff --git a/src/main/java/com/helpmeCookies/user/dto/UserInfoDto.java b/src/main/java/com/helpmeCookies/user/dto/UserInfoDto.java index ef1bedd..618903a 100644 --- a/src/main/java/com/helpmeCookies/user/dto/UserInfoDto.java +++ b/src/main/java/com/helpmeCookies/user/dto/UserInfoDto.java @@ -6,6 +6,8 @@ import com.helpmeCookies.product.entity.HashTag; import com.helpmeCookies.user.entity.UserInfo; +import lombok.Builder; + public record UserInfoDto( String name, String email, diff --git a/src/main/java/com/helpmeCookies/user/dto/request/UserInfoReq.java b/src/main/java/com/helpmeCookies/user/dto/request/UserReq.java similarity index 56% rename from src/main/java/com/helpmeCookies/user/dto/request/UserInfoReq.java rename to src/main/java/com/helpmeCookies/user/dto/request/UserReq.java index eee632c..ff9055b 100644 --- a/src/main/java/com/helpmeCookies/user/dto/request/UserInfoReq.java +++ b/src/main/java/com/helpmeCookies/user/dto/request/UserReq.java @@ -3,26 +3,18 @@ import java.util.List; import com.helpmeCookies.product.entity.HashTag; +import com.helpmeCookies.user.dto.UserDto; import com.helpmeCookies.user.dto.UserInfoDto; -public record UserInfoReq( +public record UserReq( String name, - String userImageUrl, - String nickname, String email, String birthdate, String phone, String address, - List hashTags + List hashTags, + String userImageUrl, + String nickname ) { - public UserInfoDto toDto() { - return new UserInfoDto( - name, - userImageUrl, - nickname, - email, - birthdate, - hashTags - ); - } -} + +} \ No newline at end of file diff --git a/src/main/java/com/helpmeCookies/user/entity/User.java b/src/main/java/com/helpmeCookies/user/entity/User.java index 1e26b75..2ca3903 100644 --- a/src/main/java/com/helpmeCookies/user/entity/User.java +++ b/src/main/java/com/helpmeCookies/user/entity/User.java @@ -53,9 +53,13 @@ public class User { @Column(nullable = false, updatable = false) protected LocalDateTime createdAt; + public void updateUserCommonInfo(String nickname, String userImageUrl) { + this.nickname = nickname; + this.userImageUrl = userImageUrl; + } + public void updateUserInfo(UserInfo userInfo) { - // TODO: 유저 정보 업데이트시 유효성 검사 - setUserInfo(userInfo); + this.userInfo = userInfo; } private User setUserInfo(UserInfo userInfo) { diff --git a/src/main/java/com/helpmeCookies/user/service/UserService.java b/src/main/java/com/helpmeCookies/user/service/UserService.java index 8bbf410..39abc2a 100644 --- a/src/main/java/com/helpmeCookies/user/service/UserService.java +++ b/src/main/java/com/helpmeCookies/user/service/UserService.java @@ -9,6 +9,7 @@ import com.helpmeCookies.user.dto.UserDto; import com.helpmeCookies.user.dto.UserInfoDto; import com.helpmeCookies.user.dto.UserTypeDto; +import com.helpmeCookies.user.dto.request.UserReq; import com.helpmeCookies.user.dto.response.UserFollowingRes; import com.helpmeCookies.user.entity.ArtistInfo; import com.helpmeCookies.user.entity.Social; @@ -38,12 +39,21 @@ public UserDto getUserInfo(Long userId) { } @Transactional - public UserDto updateUserInfo(UserInfoDto userInfoDto, Long userId) { + public UserDto updateUser(UserReq userReq, Long userId) { User existingUser = userRepository.findById(userId) .orElseThrow(() -> new ResourceNotFoundException("존재하지 않는 유저입니다.")); - existingUser.updateUserInfo(userInfoDto.toEntity()); + existingUser.updateUserCommonInfo(userReq.nickname(), userReq.userImageUrl()); + UserInfo userInfo = UserInfo.builder().name(userReq.name()) + .email(userReq.email()) + .birthdate(userReq.birthdate()) + .phone(userReq.phone()) + .address(userReq.address()) + .hashTags(userReq.hashTags()) + .build(); + + existingUser.updateUserInfo(userInfo); return UserDto.fromEntity(userRepository.save(existingUser)); } From fcbce1572ef1307df997d1127e9689b78637ed19 Mon Sep 17 00:00:00 2001 From: yooonwodyd Date: Fri, 1 Nov 2024 17:07:51 +0900 Subject: [PATCH 49/60] refact:[#65]- refact userupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Put 메서드에 맞게 모든 유저 정보를 수정 할 수 있도록 변경 --- .../user/controller/UserController.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/helpmeCookies/user/controller/UserController.java b/src/main/java/com/helpmeCookies/user/controller/UserController.java index 058fccb..945471d 100644 --- a/src/main/java/com/helpmeCookies/user/controller/UserController.java +++ b/src/main/java/com/helpmeCookies/user/controller/UserController.java @@ -6,7 +6,6 @@ import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -17,16 +16,13 @@ import com.helpmeCookies.global.jwt.JwtUser; import com.helpmeCookies.user.controller.apiDocs.UserApiDocs; -import com.helpmeCookies.user.dto.UserFollowingDto; import com.helpmeCookies.user.dto.response.UserCommonInfoRes; -import com.helpmeCookies.user.dto.request.UserInfoReq; +import com.helpmeCookies.user.dto.request.UserReq; import com.helpmeCookies.user.dto.response.UserDetailsInfoRes; import com.helpmeCookies.user.dto.UserTypeDto; import com.helpmeCookies.user.dto.response.UserFollowingRes; import com.helpmeCookies.user.service.UserService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @RestController @@ -57,12 +53,12 @@ public ResponseEntity getUserType( } @PutMapping("/v1/users") - public String updateUserInfo( + public String updateUser( @AuthenticationPrincipal JwtUser jwtUser, - @RequestBody UserInfoReq userInfoReq + @RequestBody UserReq userReq ) { // UserInfoDto를 통해서 유저 정보를 수정한다. - userService.updateUserInfo(userInfoReq.toDto(), jwtUser.getId()); + userService.updateUser(userReq, jwtUser.getId()); return "ok"; } From d9eee4b3722825f3c538dfcd7c0abf96d0aca6aa Mon Sep 17 00:00:00 2001 From: yooonwodyd Date: Fri, 1 Nov 2024 17:08:29 +0900 Subject: [PATCH 50/60] feat:[#65]- add kakao login MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 카카오 로그인을 수행하는 로직 추가 --- .../user/controller/LoginController.java | 16 +++++++++++----- .../user/repository/UserRepository.java | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/helpmeCookies/user/controller/LoginController.java b/src/main/java/com/helpmeCookies/user/controller/LoginController.java index 35e74ac..69e2d36 100644 --- a/src/main/java/com/helpmeCookies/user/controller/LoginController.java +++ b/src/main/java/com/helpmeCookies/user/controller/LoginController.java @@ -1,9 +1,12 @@ package com.helpmeCookies.user.controller; import java.util.List; +import java.util.Map; +import java.util.Objects; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -11,10 +14,12 @@ import com.helpmeCookies.global.jwt.JwtProvider; import com.helpmeCookies.global.jwt.JwtToken; import com.helpmeCookies.global.jwt.JwtUser; +import com.helpmeCookies.global.security.UserDetailService; import com.helpmeCookies.product.entity.HashTag; import com.helpmeCookies.user.entity.User; import com.helpmeCookies.user.entity.UserInfo; import com.helpmeCookies.user.repository.UserRepository; +import com.helpmeCookies.user.service.UserService; import lombok.RequiredArgsConstructor; @@ -23,6 +28,7 @@ //Todo: Swagger 추가 public class LoginController { private final UserRepository userRepository; + private final UserDetailService userDetailsService; private final JwtProvider jwtProvider; // 임시 회원가입 url. 유저를 생성하고 jwt 토큰을 반환한다. @@ -47,10 +53,10 @@ public JwtToken signup() { return jwtProvider.createToken(JwtUser.of(user.getId())); } - // 임시 로그인 url. 로그인한 유저의 정보의 일부를 반환한다. - @GetMapping("/login") - public String login(@AuthenticationPrincipal JwtUser jwtUser) { - User user = userRepository.findById(jwtUser.getId()).orElseThrow(); - return user.getUserInfo().getEmail(); + @GetMapping("/oauth2/login/kakao") + public JwtToken ttt(@AuthenticationPrincipal OAuth2User oAuth2User) { + Map attributes = oAuth2User.getAttributes(); + String email = (String) attributes.get("email"); + return jwtProvider.createToken(userDetailsService.loadUserByEmail(email)); } } diff --git a/src/main/java/com/helpmeCookies/user/repository/UserRepository.java b/src/main/java/com/helpmeCookies/user/repository/UserRepository.java index 7aa80d0..b6ce04c 100644 --- a/src/main/java/com/helpmeCookies/user/repository/UserRepository.java +++ b/src/main/java/com/helpmeCookies/user/repository/UserRepository.java @@ -11,4 +11,5 @@ @Repository public interface UserRepository extends JpaRepository, UserCustomRepository { Optional findById(Long id); + Optional findByUserInfoEmail(String email); } From 63e0215a9de77f873496f7a4fca413c1247b4ec5 Mon Sep 17 00:00:00 2001 From: yooonwodyd Date: Fri, 1 Nov 2024 17:08:51 +0900 Subject: [PATCH 51/60] feat:[#65]- add kakao login MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 카카오 로그인을 수행하는 로직 추가 --- .../security/Oauth2CustomUserService.java | 22 ++++++++++++++ .../global/security/UserDetailService.java | 30 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 src/main/java/com/helpmeCookies/global/security/Oauth2CustomUserService.java create mode 100644 src/main/java/com/helpmeCookies/global/security/UserDetailService.java diff --git a/src/main/java/com/helpmeCookies/global/security/Oauth2CustomUserService.java b/src/main/java/com/helpmeCookies/global/security/Oauth2CustomUserService.java new file mode 100644 index 0000000..6041900 --- /dev/null +++ b/src/main/java/com/helpmeCookies/global/security/Oauth2CustomUserService.java @@ -0,0 +1,22 @@ +package com.helpmeCookies.global.security; + +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; + +import com.helpmeCookies.user.service.UserService; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class Oauth2CustomUserService implements OAuth2UserService { + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + final DefaultOAuth2UserService delegate = new DefaultOAuth2UserService(); + OAuth2User oAuth2User = delegate.loadUser(userRequest); + return oAuth2User; + } +} diff --git a/src/main/java/com/helpmeCookies/global/security/UserDetailService.java b/src/main/java/com/helpmeCookies/global/security/UserDetailService.java new file mode 100644 index 0000000..73976a7 --- /dev/null +++ b/src/main/java/com/helpmeCookies/global/security/UserDetailService.java @@ -0,0 +1,30 @@ +package com.helpmeCookies.global.security; + +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import com.helpmeCookies.global.jwt.JwtUser; +import com.helpmeCookies.user.entity.User; +import com.helpmeCookies.user.entity.UserInfo; +import com.helpmeCookies.user.repository.UserRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class UserDetailService { + private final UserRepository userRepository; + + public JwtUser loadUserByEmail(String email) throws UsernameNotFoundException { + // 만약 유저가 존재하지 않는다면 저장 + User user = userRepository.findByUserInfoEmail(email) + .orElseGet(() -> { + User newUser = User.builder() + .userInfo(UserInfo.builder().email(email).build()) + .build(); + return userRepository.save(newUser); + }); + return JwtUser.of(user.getId()); + } +} From 4cbd3a81321625958b210cdebc21b24bcf26b76f Mon Sep 17 00:00:00 2001 From: yooonwodyd Date: Fri, 1 Nov 2024 17:35:17 +0900 Subject: [PATCH 52/60] refactor:[#65]- refact swagger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 스웨거 문서 변경 --- .../com/helpmeCookies/user/controller/ArtistController.java | 6 ------ .../user/controller/apiDocs/ArtistApiDocs.java | 5 ++--- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/helpmeCookies/user/controller/ArtistController.java b/src/main/java/com/helpmeCookies/user/controller/ArtistController.java index 6e94168..4b00d3b 100644 --- a/src/main/java/com/helpmeCookies/user/controller/ArtistController.java +++ b/src/main/java/com/helpmeCookies/user/controller/ArtistController.java @@ -22,11 +22,9 @@ @RestController @RequiredArgsConstructor -@Tag(name = "작가 관련 기능", description = "작가 관련 API") public class ArtistController implements ArtistApiDocs { private final ArtistService artistService; - @Operation(summary = "학생 작가 등록", description = "학생 작가 등록") @PostMapping("/v1/artists/students") public ResponseEntity registerStudents( @RequestBody StudentArtistReq artistDetailsReq, @@ -36,7 +34,6 @@ public ResponseEntity registerStudents( return ResponseEntity.ok().build(); } - @Operation(summary = "사업자 작가 등록", description = "사업자 작가 등록") @PostMapping("/v1/artists/bussinesses") public ResponseEntity registerbussinsess( @RequestBody BusinessArtistReq businessArtistReq, @@ -46,16 +43,13 @@ public ResponseEntity registerbussinsess( return ResponseEntity.ok().build(); } - @Operation(summary = "작가 프로필 조회", description = "작가 프로필 조회") @GetMapping("/v1/artists/{userId}") public ArtistDetailsRes getArtist( - @AuthenticationPrincipal JwtUser jwtUser, @PathVariable Long userId ) { return artistService.getArtistDetails(userId); } - @Operation(summary = "작가 프로필 조회", description = "자신의 작가 프로필 조회") @GetMapping("/v1/artist") public ArtistDetailsRes getArtist( @AuthenticationPrincipal JwtUser jwtUser diff --git a/src/main/java/com/helpmeCookies/user/controller/apiDocs/ArtistApiDocs.java b/src/main/java/com/helpmeCookies/user/controller/apiDocs/ArtistApiDocs.java index 254c5b7..4df8e2d 100644 --- a/src/main/java/com/helpmeCookies/user/controller/apiDocs/ArtistApiDocs.java +++ b/src/main/java/com/helpmeCookies/user/controller/apiDocs/ArtistApiDocs.java @@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; -@Tag(name = "작가 관련 기능", description = "작가 관련 API") +@Tag(name = "작가 관련 기능", description = "작가 관련 API, 작가 프로필 조회 API를 제외한 모든 API는 인증 이후 사용할 수 있습니다.(Authorization: Bearer {token}이 필요합니다.)") public interface ArtistApiDocs { @Operation(summary = "학생 작가 등록", description = "학생 작가 등록") @@ -35,11 +35,10 @@ ResponseEntity registerbussinsess( @Operation(summary = "작가 프로필 조회", description = "작가 프로필 조회") @GetMapping("/v1/artists/{userId}") ArtistDetailsRes getArtist( - @AuthenticationPrincipal JwtUser jwtUser, @PathVariable Long userId ); - @Operation(summary = "작가 프로필 조회", description = "자신의 작가 프로필 조회") + @Operation(summary = "작가 자신의 프로필 조회", description = "작가 자신의 프로필 조회") @GetMapping("/v1/artist") ArtistDetailsRes getArtist( @AuthenticationPrincipal JwtUser jwtUser From b4cc6f48f5753564a91323c55de98990d72ece78 Mon Sep 17 00:00:00 2001 From: yooonwodyd Date: Fri, 1 Nov 2024 17:35:23 +0900 Subject: [PATCH 53/60] refactor:[#65]- refact swagger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 스웨거 문서 변경 --- .../com/helpmeCookies/user/controller/apiDocs/UserApiDocs.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/helpmeCookies/user/controller/apiDocs/UserApiDocs.java b/src/main/java/com/helpmeCookies/user/controller/apiDocs/UserApiDocs.java index bfbdde4..1eaff9c 100644 --- a/src/main/java/com/helpmeCookies/user/controller/apiDocs/UserApiDocs.java +++ b/src/main/java/com/helpmeCookies/user/controller/apiDocs/UserApiDocs.java @@ -27,7 +27,7 @@ public interface UserApiDocs { @GetMapping("/v1/users") ResponseEntity getUsers(@AuthenticationPrincipal JwtUser jwtUser); - @Operation(summary = "유저 상세 정보 조회", description = "로그인한 유저의 상세 정보를 조회한다.") + @Operation(summary = "유저 상세 정보 조회", description = "로그인한 유저의 상세 정보를 조회한다. 유저의 모든 정보를 조회 할 수 있다.") @GetMapping("/v1/users/details") ResponseEntity getUserDetails(@AuthenticationPrincipal JwtUser jwtUser); From f38dd448df949fe6b72be717595b884b7e546566 Mon Sep 17 00:00:00 2001 From: bokyeong Date: Fri, 1 Nov 2024 18:19:44 +0900 Subject: [PATCH 54/60] =?UTF-8?q?fix=20:=20[#67]=20api=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/helpmeCookies/product/controller/ProductController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/helpmeCookies/product/controller/ProductController.java b/src/main/java/com/helpmeCookies/product/controller/ProductController.java index e16082e..81afa0a 100644 --- a/src/main/java/com/helpmeCookies/product/controller/ProductController.java +++ b/src/main/java/com/helpmeCookies/product/controller/ProductController.java @@ -15,7 +15,7 @@ import java.util.List; @RestController -@RequestMapping("/api/v1/products") +@RequestMapping("/v1/products") @RequiredArgsConstructor public class ProductController { From b3ed71bd924e43f050a9a9669ec4963a062c1a2d Mon Sep 17 00:00:00 2001 From: bokyeong Date: Fri, 1 Nov 2024 18:21:35 +0900 Subject: [PATCH 55/60] =?UTF-8?q?feat=20:=20[#62]=20=EA=B0=90=EC=83=81?= =?UTF-8?q?=ED=8F=89=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/controller/ReviewController.java | 9 +++++++++ .../helpmeCookies/review/dto/ReviewResponse.java | 11 +++++++++++ .../com/helpmeCookies/review/entity/Review.java | 16 ++++++++++++++++ .../review/service/ReviewService.java | 4 ++++ 4 files changed, 40 insertions(+) create mode 100644 src/main/java/com/helpmeCookies/review/dto/ReviewResponse.java diff --git a/src/main/java/com/helpmeCookies/review/controller/ReviewController.java b/src/main/java/com/helpmeCookies/review/controller/ReviewController.java index d225e1f..9987947 100644 --- a/src/main/java/com/helpmeCookies/review/controller/ReviewController.java +++ b/src/main/java/com/helpmeCookies/review/controller/ReviewController.java @@ -1,9 +1,12 @@ package com.helpmeCookies.review.controller; 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.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -33,4 +36,10 @@ public ResponseEntity editReview(@RequestBody ReviewRequest request, @Path reviewService.editReview(request, productId, reviewId); return ResponseEntity.ok().build(); } + + @GetMapping("/{reviewId}") + public ResponseEntity getReview(@PathVariable Long reviewId) { + Review response = reviewService.getReview(reviewId); + return ResponseEntity.ok(ReviewResponse.fromEntity(response)); + } } diff --git a/src/main/java/com/helpmeCookies/review/dto/ReviewResponse.java b/src/main/java/com/helpmeCookies/review/dto/ReviewResponse.java new file mode 100644 index 0000000..044f00f --- /dev/null +++ b/src/main/java/com/helpmeCookies/review/dto/ReviewResponse.java @@ -0,0 +1,11 @@ +package com.helpmeCookies.review.dto; + +import com.helpmeCookies.product.entity.Product; +import com.helpmeCookies.review.entity.Review; +import com.helpmeCookies.user.entity.User; + +public record ReviewResponse(Long id, String content,User writer, Product product) { + public static ReviewResponse fromEntity(Review review) { + return new ReviewResponse(review.getId(), review.getContent(),review.getWriter(), review.getProduct()); + } +} diff --git a/src/main/java/com/helpmeCookies/review/entity/Review.java b/src/main/java/com/helpmeCookies/review/entity/Review.java index da4f5d8..1c9af76 100644 --- a/src/main/java/com/helpmeCookies/review/entity/Review.java +++ b/src/main/java/com/helpmeCookies/review/entity/Review.java @@ -43,4 +43,20 @@ public Review() {} public void updateContent(String content) { this.content = content; } + + public Long getId() { + return id; + } + + public String getContent() { + return content; + } + + public User getWriter() { + return writer; + } + + public Product getProduct() { + return product; + } } diff --git a/src/main/java/com/helpmeCookies/review/service/ReviewService.java b/src/main/java/com/helpmeCookies/review/service/ReviewService.java index 4cf9c0e..a7f873a 100644 --- a/src/main/java/com/helpmeCookies/review/service/ReviewService.java +++ b/src/main/java/com/helpmeCookies/review/service/ReviewService.java @@ -28,4 +28,8 @@ public void editReview(ReviewRequest request, Long productId, Long reviewId) { Review review = reviewRepository.findById(reviewId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 reviewId 입니다.")); review.updateContent(request.content()); } + + public Review getReview(Long reviewId) { + return reviewRepository.findById(reviewId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 reviewId 입니다.")); + } } From 88427c8ab80e0b6cc3286af80bdaacf3c9ba45c7 Mon Sep 17 00:00:00 2001 From: yooonwodyd Date: Sat, 2 Nov 2024 10:04:25 +0900 Subject: [PATCH 56/60] refactor:[#65]- refact jpaAuditing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit config로 이동 --- src/main/java/com/helpmeCookies/Step3Application.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/helpmeCookies/Step3Application.java b/src/main/java/com/helpmeCookies/Step3Application.java index a594415..bc273eb 100644 --- a/src/main/java/com/helpmeCookies/Step3Application.java +++ b/src/main/java/com/helpmeCookies/Step3Application.java @@ -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; @SpringBootApplication -@EnableJpaAuditing public class Step3Application { public static void main(String[] args) { From 3e7b7661e1cb213fa16bca997e36b984884fb756 Mon Sep 17 00:00:00 2001 From: yooonwodyd Date: Sat, 2 Nov 2024 10:05:37 +0900 Subject: [PATCH 57/60] refactor:[#65]- add ignoring url MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit swagger-ui ignoring 추가 --- .../com/helpmeCookies/global/security/WebSecurityConfig.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/helpmeCookies/global/security/WebSecurityConfig.java b/src/main/java/com/helpmeCookies/global/security/WebSecurityConfig.java index 20b7f52..9ef964e 100644 --- a/src/main/java/com/helpmeCookies/global/security/WebSecurityConfig.java +++ b/src/main/java/com/helpmeCookies/global/security/WebSecurityConfig.java @@ -27,7 +27,9 @@ public class WebSecurityConfig { @Bean public WebSecurityCustomizer configure() { - return (web) -> web.ignoring(); + return (web) -> web.ignoring() + .requestMatchers("/swagger-ui") + .requestMatchers("/static/**"); } @Bean From 14c4f03d9cf929641ee6904e31d75c3fa61fa4be Mon Sep 17 00:00:00 2001 From: yooonwodyd Date: Sat, 2 Nov 2024 10:49:53 +0900 Subject: [PATCH 58/60] refactor:[#65]- refact kakaoLogin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit login 로직 변경. email 및 닉네임 모두 저장 별도의 Record 파일로 변경 --- .../global/security/UserDetailService.java | 7 ++- .../global/security/WebSecurityConfig.java | 5 +- .../user/controller/LoginController.java | 6 +- .../user/dto/KakaoOAuth2Response.java | 58 +++++++++++++++++++ 4 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/helpmeCookies/user/dto/KakaoOAuth2Response.java diff --git a/src/main/java/com/helpmeCookies/global/security/UserDetailService.java b/src/main/java/com/helpmeCookies/global/security/UserDetailService.java index 73976a7..d4f63ee 100644 --- a/src/main/java/com/helpmeCookies/global/security/UserDetailService.java +++ b/src/main/java/com/helpmeCookies/global/security/UserDetailService.java @@ -16,12 +16,15 @@ public class UserDetailService { private final UserRepository userRepository; - public JwtUser loadUserByEmail(String email) throws UsernameNotFoundException { + public JwtUser loadUserByEmail(String email,String nickname) throws UsernameNotFoundException { // 만약 유저가 존재하지 않는다면 저장 User user = userRepository.findByUserInfoEmail(email) .orElseGet(() -> { User newUser = User.builder() - .userInfo(UserInfo.builder().email(email).build()) + .userInfo(UserInfo.builder() + .email(email) + .build()) + .nickname(nickname) .build(); return userRepository.save(newUser); }); diff --git a/src/main/java/com/helpmeCookies/global/security/WebSecurityConfig.java b/src/main/java/com/helpmeCookies/global/security/WebSecurityConfig.java index 9ef964e..0fb75b9 100644 --- a/src/main/java/com/helpmeCookies/global/security/WebSecurityConfig.java +++ b/src/main/java/com/helpmeCookies/global/security/WebSecurityConfig.java @@ -49,7 +49,10 @@ public SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http) throws E .userInfoEndpoint((userInfo) -> userInfo .userService(new Oauth2CustomUserService()) ) - .defaultSuccessUrl("/oauth2/login/kakao") + // 추후 로그인 방식이 다양해지면, Handler의 세부 내용을 변경. + .successHandler((request, response, authentication) -> { + response.sendRedirect("/oauth2/login/kakao"); + }) ); return http.build(); diff --git a/src/main/java/com/helpmeCookies/user/controller/LoginController.java b/src/main/java/com/helpmeCookies/user/controller/LoginController.java index 69e2d36..71e4391 100644 --- a/src/main/java/com/helpmeCookies/user/controller/LoginController.java +++ b/src/main/java/com/helpmeCookies/user/controller/LoginController.java @@ -16,6 +16,7 @@ import com.helpmeCookies.global.jwt.JwtUser; import com.helpmeCookies.global.security.UserDetailService; import com.helpmeCookies.product.entity.HashTag; +import com.helpmeCookies.user.dto.KakaoOAuth2Response; import com.helpmeCookies.user.entity.User; import com.helpmeCookies.user.entity.UserInfo; import com.helpmeCookies.user.repository.UserRepository; @@ -55,8 +56,7 @@ public JwtToken signup() { @GetMapping("/oauth2/login/kakao") public JwtToken ttt(@AuthenticationPrincipal OAuth2User oAuth2User) { - Map attributes = oAuth2User.getAttributes(); - String email = (String) attributes.get("email"); - return jwtProvider.createToken(userDetailsService.loadUserByEmail(email)); + KakaoOAuth2Response kakaoUser = KakaoOAuth2Response.from(oAuth2User.getAttributes()); + return jwtProvider.createToken(userDetailsService.loadUserByEmail(kakaoUser.email(), kakaoUser.nickname())); } } diff --git a/src/main/java/com/helpmeCookies/user/dto/KakaoOAuth2Response.java b/src/main/java/com/helpmeCookies/user/dto/KakaoOAuth2Response.java new file mode 100644 index 0000000..034e722 --- /dev/null +++ b/src/main/java/com/helpmeCookies/user/dto/KakaoOAuth2Response.java @@ -0,0 +1,58 @@ +package com.helpmeCookies.user.dto; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Map; + +public record KakaoOAuth2Response( + Long id, + LocalDateTime connectedAt, + Map properties, + KakaoAccount kakaoAccount +) { + public record KakaoAccount( + Boolean profileNicknameNeedsAgreement, + Profile profile, + Boolean hasEmail, + Boolean emailNeedsAgreement, + Boolean isEmailValid, + Boolean isEmailVerified, + String email + ) { + public record Profile(String nickname) { + public static Profile from(Map attributes) { + return new Profile(String.valueOf(attributes.get("nickname"))); + } + } + + public static KakaoAccount from(Map attributes) { + return new KakaoAccount( + Boolean.valueOf(String.valueOf(attributes.get("profile_nickname_needs_agreement"))), + Profile.from((Map) attributes.get("profile")), + Boolean.valueOf(String.valueOf(attributes.get("has_email"))), + Boolean.valueOf(String.valueOf(attributes.get("email_needs_agreement"))), + Boolean.valueOf(String.valueOf(attributes.get("is_email_valid"))), + Boolean.valueOf(String.valueOf(attributes.get("is_email_verified"))), + String.valueOf(attributes.get("email")) + ); + } + + public String nickname() { return this.profile().nickname(); } + } + + public static KakaoOAuth2Response from(Map attributes) { + return new KakaoOAuth2Response( + Long.valueOf(String.valueOf(attributes.get("id"))), + LocalDateTime.parse( + String.valueOf(attributes.get("connected_at")), + DateTimeFormatter.ISO_INSTANT.withZone(ZoneId.systemDefault()) + ), + (Map) attributes.get("properties"), + KakaoAccount.from((Map) attributes.get("kakao_account")) + ); + } + + public String email() { return this.kakaoAccount().email(); } + public String nickname() { return this.kakaoAccount().nickname(); } +} From 0092f46552231d9b3b950b6c197e404734755afd Mon Sep 17 00:00:00 2001 From: Hyun <82355395+donghyuun@users.noreply.github.com> Date: Sun, 3 Nov 2024 01:03:38 +0900 Subject: [PATCH 59/60] =?UTF-8?q?feat:=20deploy.sh=20=EC=97=90=EC=84=9C=20?= =?UTF-8?q?docker=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20pull=20=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit deploy.sh 실행 시 각 ec2 에 접근하여, 해당 ec2 내에서 도커 이미지를 pull 하도록 수정 --- .github/workflows/deploy.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 7908236..851f6e2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -66,8 +66,6 @@ jobs: key: ${{ secrets.AWS_EC2_PRIVATE_KEY }} # EC2 인스턴스 pem key port: ${{ secrets.REMOTE_SSH_PORT }} # 접속 포트(생략 시 22번 기본 사용) script: | - docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} - docker pull ${{ secrets.DOCKER_USERNAME }}/katecam-backend:latest cd /home/ubuntu # EC2 인스턴스의 배포 스크립트 파일 경로로 이동 chmod +x deploy.sh # 배포 스크립트 실행 권한 부여 ./deploy.sh # 배포 스크립트 실행 From ece63e6c6749b6a180f3bfe578a58b9b4663bdc2 Mon Sep 17 00:00:00 2001 From: yooonwodyd Date: Sun, 3 Nov 2024 13:17:31 +0900 Subject: [PATCH 60/60] refactor:[#65]- refact SecurityFilter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /test/login 보안 예외 설정 추가 --- .../com/helpmeCookies/global/security/WebSecurityConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/helpmeCookies/global/security/WebSecurityConfig.java b/src/main/java/com/helpmeCookies/global/security/WebSecurityConfig.java index 0fb75b9..a1e0697 100644 --- a/src/main/java/com/helpmeCookies/global/security/WebSecurityConfig.java +++ b/src/main/java/com/helpmeCookies/global/security/WebSecurityConfig.java @@ -54,7 +54,6 @@ public SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http) throws E response.sendRedirect("/oauth2/login/kakao"); }) ); - return http.build(); } @@ -72,7 +71,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/swagger-ui/**", "/actuator/**", "/v1/**", - "swagger-ui/**" + "swagger-ui/**", + "/test/signup" ).permitAll() .anyRequest().authenticated() ).exceptionHandling((exception) -> exception