-
Notifications
You must be signed in to change notification settings - Fork 6
Salem B. #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Salem B. #4
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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() | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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> PRODUCT_ROW_MAPPER = (rs, _) -> { | ||
| var product = new Product(); | ||
|
|
@@ -18,20 +24,29 @@ 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<Map<String, Object>>() {})); | ||
| } | ||
| } catch (Exception e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| return product; | ||
| }; | ||
|
|
||
| public List<Product> getAllProducts() { | ||
| return jdbcClient | ||
| .sql("SELECT id, title, price, category, image_url FROM products") | ||
| .sql("SELECT * FROM products") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SELECT * grabs every column, which is problematic because: Wastes resources — fetches data you don't need Fix: name only the columns you use → SELECT id, name, price FROM products |
||
| .query(PRODUCT_ROW_MAPPER) | ||
| .list(); | ||
|
|
||
| } | ||
|
|
||
| public Product findById(int id) { | ||
| return jdbcClient | ||
| .sql("SELECT id, title, price, category, image_url FROM products WHERE id = :id") | ||
| .sql("SELECT * FROM products WHERE id = :id") | ||
| .param("id", id) | ||
| .query(PRODUCT_ROW_MAPPER) | ||
| .single(); | ||
|
|
@@ -49,12 +64,18 @@ public void setImageUrl(int id, String imageUrl) { | |
| } | ||
|
|
||
| public List<Product> findByColor(String color) { | ||
| // TODO: Implement | ||
| throw new UnsupportedOperationException("Not implemented yet"); | ||
| return jdbcClient | ||
| .sql("SELECT * FROM products WHERE details @> CAST(? AS jsonb)") | ||
| .param("{\"color\": \"" + color + "\"}") | ||
| .query(PRODUCT_ROW_MAPPER) | ||
| .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}', ?::jsonb) WHERE id = ?") | ||
| .param("\"" + size + "\"") | ||
| .param(id) | ||
| .update(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| package net.hackyourfuture.hyfshop.product; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
| import net.hackyourfuture.hyfshop.file.FileService; | ||
| import net.hackyourfuture.hyfshop.product.dto.ProductResponse; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.web.multipart.MultipartFile; | ||
|
|
@@ -11,6 +12,7 @@ | |
| @RequiredArgsConstructor | ||
| public class ProductService { | ||
| private final ProductRepository productRepository; | ||
| private final FileService fileService; | ||
|
|
||
| public List<ProductResponse> getAllProducts() { | ||
| return productRepository.getAllProducts().stream().map(ProductResponse::from).toList(); | ||
|
|
@@ -26,14 +28,32 @@ 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 { | ||
| Product product = productRepository.findById(id); | ||
| if (product.getImageUrl() != null) { | ||
| fileService.deleteByUrl(product.getImageUrl()); | ||
| } | ||
| String imageUrl = fileService.upload(file); | ||
| productRepository.setImageUrl(id, imageUrl); | ||
|
|
||
| Product updatedProduct = productRepository.findById(id); | ||
| return ProductResponse.from(updatedProduct); | ||
|
|
||
| } catch (Exception e) { | ||
| throw new RuntimeException("Failed to upload product image", e); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would be nice to log the exception here
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks @dilero, for your review and time. |
||
| } | ||
| } | ||
|
|
||
| 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); | ||
|
|
||
| if (product.getImageUrl() != null) { | ||
| fileService.deleteByUrl(product.getImageUrl()); | ||
| } | ||
|
|
||
| productRepository.setImageUrl(id, null); | ||
|
|
||
| Product updatedProduct = productRepository.findById(id); | ||
| return ProductResponse.from(updatedProduct); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice that you use these values as config.