Skip to content

Commit

Permalink
Merge branch 'weekly' into weekly
Browse files Browse the repository at this point in the history
  • Loading branch information
jupyter471 authored Nov 1, 2024
2 parents b3ed71b + c812e98 commit c1b4a02
Show file tree
Hide file tree
Showing 26 changed files with 648 additions and 31 deletions.
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
2 changes: 2 additions & 0 deletions src/main/java/com/helpmeCookies/Step3Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/com/helpmeCookies/global/entity/BaseTimeEntity.java
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package com.helpmeCookies.product.controller;

import com.helpmeCookies.product.dto.ImageUpload;
import static com.helpmeCookies.product.util.SortUtil.convertProductSort;

import com.helpmeCookies.product.controller.docs.ProductApiDocs;
import com.helpmeCookies.product.dto.ProductImageResponse;
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 org.springframework.data.domain.PageRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
Expand All @@ -17,7 +23,7 @@
@RestController
@RequestMapping("/v1/products")
@RequiredArgsConstructor
public class ProductController {
public class ProductController implements ProductApiDocs {

private final ProductService productService;
private final ProductImageService productImageService;
Expand Down Expand Up @@ -63,4 +69,17 @@ public ResponseEntity<Void> deleteProduct(@PathVariable("productId") Long produc
productService.delete(productId);
return ResponseEntity.noContent().build();
}

@GetMapping
public ResponseEntity<ProductPage.Paging> 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));
}
}
Original file line number Diff line number Diff line change
@@ -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<Paging> getProductsByPage(
String query,
@Parameter(description = "default value 20") int size,
int page,
ProductSort productSort
);

}
46 changes: 46 additions & 0 deletions src/main/java/com/helpmeCookies/product/dto/ProductPage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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,
String thumbnailUrl
) {
public static Info from(ProductSearch productSearch) {
return new Info(
productSearch.getId(),
productSearch.getName(),
productSearch.getArtist(),
productSearch.getPrice(),
productSearch.getThumbnailUrl()
);
}

public static List<Info> of(List<ProductSearch> content) {
return content.stream()
.map(Info::from)
.toList();
}
}

public record Paging (
boolean hasNext,
List<Info> products
) {

public static Paging from(Page<ProductSearch> productPage) {
return new Paging(
productPage.hasNext(),
Info.of(productPage.getContent())
);
}
}

}
11 changes: 10 additions & 1 deletion src/main/java/com/helpmeCookies/product/entity/Product.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -17,7 +19,7 @@
import lombok.Builder;

@Entity
public class Product {
public class Product extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand All @@ -42,12 +44,15 @@ public class Product {
@Column(nullable = false)
private String preferredLocation;

private String thumbnailUrl;

@ElementCollection(targetClass = HashTag.class)
@CollectionTable(name = "product_hashtags")
@Enumerated(EnumType.STRING)
private List<HashTag> hashTags;

@ManyToOne
@JoinColumn(name = "artist_info_id")
private ArtistInfo artistInfo;

public Product() {}
Expand Down Expand Up @@ -110,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;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
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<Product, Long> {

@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(*) " +
"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<ProductSearch> findByNameWithIdx(@Param("query") String query, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.helpmeCookies.product.repository.dto;


public interface ProductSearch {
Long getId();
String getName();
String getThumbnailUrl();
String getArtist();
Long getPrice();
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.helpmeCookies.product.entity.ProductImage;
import com.helpmeCookies.product.repository.ProductImageRepository;
import java.util.ArrayList;
import com.helpmeCookies.product.repository.ProductRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -17,6 +18,7 @@
public class ProductImageService {
private final AwsS3FileUtils awsS3FileUtils;
private final ProductImageRepository productImageRepository;
private final ProductRepository productRepository;

@Transactional
public List<ImageUpload> uploadMultiFiles(List<MultipartFile> files) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import com.helpmeCookies.product.repository.ProductRepository;
import com.helpmeCookies.user.entity.ArtistInfo;
import com.helpmeCookies.user.repository.ArtistInfoRepository;
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;

Expand All @@ -18,6 +20,12 @@ public class ProductService {
private final ProductImageRepository productImageRepository;
private final ArtistInfoRepository artistInfoRepository;

@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) {
ArtistInfo artistInfo = artistInfoRepository.findById(productSaveRequest.artistInfoId())
.orElseThrow(() -> new IllegalArgumentException("유효하지 않은 작가 정보입니다."));
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Info> of(List<ProductSearch> content) {
return content.stream()
.map(Info::from)
.toList();
}
}

public record Paging (
boolean hasNext,
List<Info> products
) {

public static Paging from(Page<ProductSearch> productPage) {
return new Paging(
productPage.hasNext(),
Info.of(productPage.getContent())
);
}
}

}
5 changes: 5 additions & 0 deletions src/main/java/com/helpmeCookies/product/util/ProductSort.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.helpmeCookies.product.util;

public enum ProductSort {
RELEVANCE, LATEST
}
14 changes: 14 additions & 0 deletions src/main/java/com/helpmeCookies/product/util/SortUtil.java
Original file line number Diff line number Diff line change
@@ -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("created_date"));
default -> Sort.unsorted();
};
}

}
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -62,4 +62,14 @@ public ArtistDetailsRes getArtist(
) {
return artistService.getArtistDetails(jwtUser.getId());
}

@GetMapping("/v1/artists")
public ResponseEntity<ArtistInfoPage.Paging> 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));
}
}
Loading

0 comments on commit c1b4a02

Please sign in to comment.