diff --git a/pom.xml b/pom.xml
index bbdda38..ed8adf6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -68,6 +68,13 @@
jackson-core
3.2.0
+
+
+ software.amazon.awssdk
+ s3
+ 2.25.0
+
+
diff --git a/src/main/java/net/hackyourfuture/hyfshop/configuration/B2Config.java b/src/main/java/net/hackyourfuture/hyfshop/configuration/B2Config.java
new file mode 100644
index 0000000..5fc2688
--- /dev/null
+++ b/src/main/java/net/hackyourfuture/hyfshop/configuration/B2Config.java
@@ -0,0 +1,41 @@
+package net.hackyourfuture.hyfshop.configuration;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.presigner.S3Presigner;
+
+import java.net.URI;
+
+@Configuration
+public class B2Config {
+
+ @Value("${b2.endpoint}") private String endpoint;
+ @Value("${b2.region}") private String region;
+ @Value("${b2.access-key}") private String accessKey;
+ @Value("${b2.secret-key}") private String secretKey;
+
+ @Bean
+ public S3Client s3Client() {
+ return S3Client.builder()
+ .credentialsProvider(StaticCredentialsProvider.create(
+ AwsBasicCredentials.create(accessKey, secretKey)))
+ .endpointOverride(URI.create(endpoint))
+ .region(Region.of(region))
+ .build();
+ }
+
+ @Bean
+ public S3Presigner s3Presigner() {
+ return S3Presigner.builder()
+ .credentialsProvider(StaticCredentialsProvider.create(
+ AwsBasicCredentials.create(accessKey, secretKey)))
+ .endpointOverride(URI.create(endpoint))
+ .region(Region.of(region))
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/hackyourfuture/hyfshop/file/FileService.java b/src/main/java/net/hackyourfuture/hyfshop/file/FileService.java
new file mode 100644
index 0000000..9fd321c
--- /dev/null
+++ b/src/main/java/net/hackyourfuture/hyfshop/file/FileService.java
@@ -0,0 +1,62 @@
+package net.hackyourfuture.hyfshop.file;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+import software.amazon.awssdk.core.sync.RequestBody;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
+import software.amazon.awssdk.services.s3.model.PutObjectRequest;
+import software.amazon.awssdk.services.s3.presigner.S3Presigner;
+
+import java.util.UUID;
+
+@Service
+public class FileService {
+
+ private final S3Client s3Client;
+ private final S3Presigner s3Presigner;
+
+ @Value("${b2.bucket}")
+ private String bucket;
+
+ @Value("${b2.endpoint}")
+ private String endpoint;
+
+ public FileService(S3Client s3Client, S3Presigner s3Presigner) {
+ this.s3Client = s3Client;
+ this.s3Presigner = s3Presigner;
+ }
+
+
+ public String upload(MultipartFile file) throws Exception {
+ String key = UUID.randomUUID().toString() + "-" + file.getOriginalFilename();
+
+ s3Client.putObject(
+ PutObjectRequest.builder()
+ .bucket(bucket)
+ .key(key)
+ .contentType(file.getContentType())
+ .build(),
+ RequestBody.fromInputStream(file.getInputStream(), file.getSize())
+ );
+
+ return endpoint + "/file/" + bucket + "/" + key;
+ }
+
+
+ public void deleteByUrl(String imageUrl) {
+ if (imageUrl == null || imageUrl.isEmpty()) {
+ return;
+ }
+
+ String key = imageUrl.substring(imageUrl.lastIndexOf("/") + 1);
+
+ s3Client.deleteObject(
+ DeleteObjectRequest.builder()
+ .bucket(bucket)
+ .key(key)
+ .build()
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/hackyourfuture/hyfshop/product/Product.java b/src/main/java/net/hackyourfuture/hyfshop/product/Product.java
index ccc6204..a3108b9 100644
--- a/src/main/java/net/hackyourfuture/hyfshop/product/Product.java
+++ b/src/main/java/net/hackyourfuture/hyfshop/product/Product.java
@@ -6,6 +6,7 @@
import lombok.Setter;
import java.math.BigDecimal;
+import java.util.Map;
@AllArgsConstructor
@NoArgsConstructor
@@ -17,4 +18,5 @@ public class Product {
private BigDecimal price;
private String category;
private String imageUrl;
+ private Map details;
}
diff --git a/src/main/java/net/hackyourfuture/hyfshop/product/ProductRepository.java b/src/main/java/net/hackyourfuture/hyfshop/product/ProductRepository.java
index 2766734..2215695 100644
--- a/src/main/java/net/hackyourfuture/hyfshop/product/ProductRepository.java
+++ b/src/main/java/net/hackyourfuture/hyfshop/product/ProductRepository.java
@@ -1,15 +1,21 @@
package net.hackyourfuture.hyfshop.product;
import lombok.AllArgsConstructor;
+import net.hackyourfuture.hyfshop.product.dto.ProductResponse;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.JdbcClient;
import org.springframework.stereotype.Repository;
+import tools.jackson.core.type.TypeReference;
+import tools.jackson.databind.ObjectMapper;
+
import java.util.List;
+import java.util.Map;
@Repository
@AllArgsConstructor
public class ProductRepository {
private final JdbcClient jdbcClient;
+ private static final ObjectMapper objectMapper = new ObjectMapper();
public static final RowMapper PRODUCT_ROW_MAPPER = (rs, _) -> {
var product = new Product();
@@ -18,12 +24,21 @@ public class ProductRepository {
product.setPrice(rs.getBigDecimal("price"));
product.setCategory(rs.getString("category"));
product.setImageUrl(rs.getString("image_url"));
+ try {
+ String json = rs.getString("details");
+ if (json != null) {
+ product.setDetails(objectMapper.readValue(json,
+ new TypeReference