From 62045ea0dd16f1474cd3e1363422078a6e5cd58b Mon Sep 17 00:00:00 2001 From: Dagim02 Date: Thu, 2 Jul 2026 18:53:50 +0200 Subject: [PATCH 1/5] feat: create new PostgreSQL database --- .gitignore | 1 + src/main/resources/application.yaml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 667aaef..8cf54e8 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ build/ ### VS Code ### .vscode/ +.env \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 05b7788..072a9c7 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -5,5 +5,6 @@ spring: url: '${DB_URL}' username: '${DB_USERNAME}' password: '${DB_PASSWORD}' + driver-class-name: org.postgresql.Driver server: - port: '8080' + port: '8080' \ No newline at end of file From 0ea1bb349576fea044b7f741884a7dac1f6b3d61 Mon Sep 17 00:00:00 2001 From: Dagim02 Date: Thu, 2 Jul 2026 20:47:55 +0200 Subject: [PATCH 2/5] feat: return details JSONB in GET /products --- .../hyfshop/product/Product.java | 2 ++ .../hyfshop/product/ProductRepository.java | 20 +++++++++++++++++-- .../hyfshop/product/dto/ProductResponse.java | 7 +++++-- 3 files changed, 25 insertions(+), 4 deletions(-) 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..fa2d468 100644 --- a/src/main/java/net/hackyourfuture/hyfshop/product/ProductRepository.java +++ b/src/main/java/net/hackyourfuture/hyfshop/product/ProductRepository.java @@ -4,12 +4,18 @@ 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.lang.reflect.Type; 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,22 @@ 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>() {} + )); + } + } catch (Exception e) { + throw new RuntimeException(e); + } return product; }; public List getAllProducts() { return jdbcClient - .sql("SELECT id, title, price, category, image_url FROM products") + .sql("SELECT id, title, price, category, image_url, details FROM products") .query(PRODUCT_ROW_MAPPER) .list(); @@ -31,7 +47,7 @@ public List getAllProducts() { public Product findById(int id) { return jdbcClient - .sql("SELECT id, title, price, category, image_url FROM products WHERE id = :id") + .sql("SELECT id, title, price, category, image_url, details FROM products WHERE id = :id") .param("id", id) .query(PRODUCT_ROW_MAPPER) .single(); diff --git a/src/main/java/net/hackyourfuture/hyfshop/product/dto/ProductResponse.java b/src/main/java/net/hackyourfuture/hyfshop/product/dto/ProductResponse.java index 0c35ef1..2e4a45b 100644 --- a/src/main/java/net/hackyourfuture/hyfshop/product/dto/ProductResponse.java +++ b/src/main/java/net/hackyourfuture/hyfshop/product/dto/ProductResponse.java @@ -3,13 +3,15 @@ import net.hackyourfuture.hyfshop.product.Product; import java.math.BigDecimal; +import java.util.Map; public record ProductResponse ( int id, String title, BigDecimal price, String category, - String imageUrl + String imageUrl, + Map details ){ public static ProductResponse from(Product product) { return new ProductResponse( @@ -17,7 +19,8 @@ public static ProductResponse from(Product product) { product.getTitle(), product.getPrice(), product.getCategory(), - product.getImageUrl() + product.getImageUrl(), + product.getDetails() ); } } From baa6faf07fcf7727622c26a7943ed5f122b06325 Mon Sep 17 00:00:00 2001 From: Dagim02 Date: Thu, 2 Jul 2026 21:21:33 +0200 Subject: [PATCH 3/5] feat: implement findByColor with JSONB filter --- .../hyfshop/product/ProductRepository.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/hackyourfuture/hyfshop/product/ProductRepository.java b/src/main/java/net/hackyourfuture/hyfshop/product/ProductRepository.java index fa2d468..f9f9d0f 100644 --- a/src/main/java/net/hackyourfuture/hyfshop/product/ProductRepository.java +++ b/src/main/java/net/hackyourfuture/hyfshop/product/ProductRepository.java @@ -11,6 +11,8 @@ import java.util.List; import java.util.Map; +import static org.springframework.web.servlet.function.RequestPredicates.param; + @Repository @AllArgsConstructor public class ProductRepository { @@ -65,8 +67,15 @@ public void setImageUrl(int id, String imageUrl) { } public List findByColor(String color) { - // TODO: Implement - throw new UnsupportedOperationException("Not implemented yet"); + return jdbcClient.sql(""" + SELECT id, title, price, category, image_url, details + FROM products + WHERE details ->> 'color' = :color + LIMIT 10 + """) + .param("color", color) + .query(PRODUCT_ROW_MAPPER) + .list(); } public Product setSize(int id, String size) { From 2df323506291ad56700de712a8492c89785d713a Mon Sep 17 00:00:00 2001 From: Dagim02 Date: Thu, 2 Jul 2026 21:38:05 +0200 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20add=20SQL=20update=20to=20set=20pro?= =?UTF-8?q?duct=20size=20to=20=E2=80=9CM=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hyfshop/product/ProductRepository.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/hackyourfuture/hyfshop/product/ProductRepository.java b/src/main/java/net/hackyourfuture/hyfshop/product/ProductRepository.java index f9f9d0f..8414a19 100644 --- a/src/main/java/net/hackyourfuture/hyfshop/product/ProductRepository.java +++ b/src/main/java/net/hackyourfuture/hyfshop/product/ProductRepository.java @@ -30,7 +30,8 @@ public class ProductRepository { String json = rs.getString("details"); if (json != null) { product.setDetails(objectMapper.readValue(json, - new TypeReference>() {} + new TypeReference>() { + } )); } } catch (Exception e) { @@ -78,8 +79,14 @@ public List findByColor(String color) { .list(); } - public Product setSize(int id, String size) { - // TODO: Implement - throw new UnsupportedOperationException("Not implemented yet"); + public void setSize(int id, String size) { + jdbcClient.sql(""" + UPDATE products + SET details = jsonb_set(details, '{size}', to_jsonb(:size::text)) + WHERE id = :id + """) + .param("id", id) + .param("size", size) + .update(); } } From c783f69f2241edd2e9c7b6467c3246b859824e2b Mon Sep 17 00:00:00 2001 From: Dagim02 Date: Thu, 2 Jul 2026 23:09:09 +0200 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20implement=20Task=203=20=E2=80=94=20?= =?UTF-8?q?Backblaze=20upload=20+=20image=5Furl=20DB=20updates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 12 +++++- .../hyfshop/product/B2Config.java | 43 +++++++++++++++++++ .../hyfshop/product/FileService.java | 4 ++ .../hyfshop/product/ProductService.java | 22 +++++++--- src/main/resources/application.yaml | 20 ++++++--- 5 files changed, 89 insertions(+), 12 deletions(-) create mode 100644 src/main/java/net/hackyourfuture/hyfshop/product/B2Config.java create mode 100644 src/main/java/net/hackyourfuture/hyfshop/product/FileService.java diff --git a/pom.xml b/pom.xml index bbdda38..e7c219c 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,11 @@ org.springframework.boot spring-boot-starter-webmvc - + + software.amazon.awssdk + s3 + 2.25.0 + org.postgresql postgresql @@ -49,6 +53,12 @@ lombok true + + me.paulschwarz + spring-dotenv + 4.0.0 + + org.springframework.boot spring-boot-starter-webclient-test diff --git a/src/main/java/net/hackyourfuture/hyfshop/product/B2Config.java b/src/main/java/net/hackyourfuture/hyfshop/product/B2Config.java new file mode 100644 index 0000000..1ffef8e --- /dev/null +++ b/src/main/java/net/hackyourfuture/hyfshop/product/B2Config.java @@ -0,0 +1,43 @@ +package net.hackyourfuture.hyfshop.product; + +import java.net.URI; + +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; + + +@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(); + } +} diff --git a/src/main/java/net/hackyourfuture/hyfshop/product/FileService.java b/src/main/java/net/hackyourfuture/hyfshop/product/FileService.java new file mode 100644 index 0000000..bbd9667 --- /dev/null +++ b/src/main/java/net/hackyourfuture/hyfshop/product/FileService.java @@ -0,0 +1,4 @@ +package net.hackyourfuture.hyfshop.product; + +public class FileService { +} diff --git a/src/main/java/net/hackyourfuture/hyfshop/product/ProductService.java b/src/main/java/net/hackyourfuture/hyfshop/product/ProductService.java index 779d3b2..1303b8f 100644 --- a/src/main/java/net/hackyourfuture/hyfshop/product/ProductService.java +++ b/src/main/java/net/hackyourfuture/hyfshop/product/ProductService.java @@ -5,12 +5,14 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import java.io.File; import java.util.List; @Service @RequiredArgsConstructor public class ProductService { private final ProductRepository productRepository; + private final FileService fileService; public List getAllProducts() { return productRepository.getAllProducts().stream().map(ProductResponse::from).toList(); @@ -26,14 +28,22 @@ public ProductResponse setProductSize(int id, String size) { } public ProductResponse setProductImage(int id, MultipartFile file) { - // TODO: Implement - // call ProductRepository.setImageUrl() afterwards with the new URL - throw new UnsupportedOperationException("Not implemented yet"); + try { + String key = fileService.upload(file); + productRepository.setImageUrl(id, key); + return ProductResponse.from(productRepository.findById(id)); + } catch (Exception e) { + throw new RuntimeException("Failed to upload product image", e); + } } public ProductResponse deleteProductImage(int id) { - // TODO: Implement - // call ProductRepository.setImageUrl() to set the image url to null - throw new UnsupportedOperationException("Not implemented yet"); + Product product = productRepository.findById(id); + String key = product.getImageUrl(); + if (key != null) { + fileService.delete(key); + } + productRepository.setImageUrl(id, null); + return ProductResponse.from(productRepository.findById(id)); } } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 072a9c7..3c63cda 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,10 +1,20 @@ spring: application: name: hyf_shop + datasource: - url: '${DB_URL}' - username: '${DB_USERNAME}' - password: '${DB_PASSWORD}' - driver-class-name: org.postgresql.Driver + url: ${DB_URL} + username: ${DB_USERNAME} + password: ${DB_PASSWORD} + driver-class-name: org.postgresql.Driver + +b2: + endpoint: ${B2_ENDPOINT} + region: ${B2_REGION} + bucket: ${B2_BUCKET_NAME} + access-key: ${B2_ACCESS_KEY} + secret-key: ${B2_SECRET_KEY} + public-url: https://f003.backblazeb2.com/file/${B2_BUCKET_NAME} + server: - port: '8080' \ No newline at end of file + port: 8080