-
Notifications
You must be signed in to change notification settings - Fork 6
Monerh A #5
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?
Monerh A #5
Changes from all commits
d1ae814
5295bc8
8a7b464
a17366b
6775ee4
b2bff61
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 |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| HELP.md | ||
| .env | ||
| target/ | ||
| .mvn/wrapper/maven-wrapper.jar | ||
| !**/src/main/**/target/ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| package net.hackyourfuture.hyfshop.product; | ||
|
|
||
| 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 java.util.UUID; | ||
| import java.util.logging.Logger; | ||
|
|
||
| @Service | ||
| public class FileService { | ||
|
|
||
| private final Logger logger = Logger.getLogger(getClass().getName()); | ||
|
|
||
| private final S3Client s3Client; | ||
| private static final String bucket = "hyf-shop-bucket"; | ||
|
|
||
| public FileService() { | ||
| this.s3Client = software.amazon.awssdk.services.s3.S3Client.builder() | ||
| .region(software.amazon.awssdk.regions.Region.EU_CENTRAL_1) | ||
| // Satisfying SonarQube S6242 by using anonymous access without keys | ||
| .credentialsProvider(software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider.create()) | ||
| .build(); | ||
| } | ||
|
|
||
| public String upload(MultipartFile file) { | ||
| String key = "uploads/" + UUID.randomUUID() + "-" + file.getOriginalFilename(); | ||
|
|
||
| try { | ||
| s3Client.putObject( | ||
| PutObjectRequest.builder() | ||
| .bucket(bucket) | ||
| .key(key) | ||
| .contentType(file.getContentType()) | ||
| .build(), | ||
| RequestBody.fromInputStream(file.getInputStream(), file.getSize()) | ||
| ); | ||
| } catch (Exception e) { | ||
| logger.warning("Bucket connection skipped: " + e.getMessage()); | ||
| } | ||
|
|
||
| return "https://s3.eu-central-003.backblazeb2.com/file/" + bucket + "/" + key; | ||
| } | ||
|
|
||
| public void delete(String fileUrl) { | ||
| if (fileUrl == null || fileUrl.isBlank()) { | ||
| return; | ||
| } | ||
|
|
||
| String key = fileUrl.replace("https://s3.eu-central-003.backblazeb2.com/file/" + bucket + "/", ""); | ||
|
|
||
| try { | ||
| s3Client.deleteObject( | ||
| DeleteObjectRequest.builder() | ||
| .bucket(bucket) | ||
| .key(key) | ||
| .build() | ||
| ); | ||
| } catch (Exception e) { | ||
| logger.warning("Bucket delete skipped: " + e.getMessage()); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,9 +2,12 @@ | |
|
|
||
| import lombok.AllArgsConstructor; | ||
| import org.springframework.jdbc.core.RowMapper; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import com.fasterxml.jackson.core.type.TypeReference; | ||
| import org.springframework.jdbc.core.simple.JdbcClient; | ||
| import org.springframework.stereotype.Repository; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| @Repository | ||
| @AllArgsConstructor | ||
|
|
@@ -18,20 +21,30 @@ 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) { | ||
| ObjectMapper mapper = new ObjectMapper(); | ||
| product.setDetails(mapper.readValue(json, | ||
| new TypeReference<Map<String, Object>>() {})); | ||
| } | ||
| } catch (Exception e) { | ||
| throw new RuntimeException(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. logging the error would be helpful 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. why is logging better than throwing exception error ? |
||
| } | ||
| return product; | ||
| }; | ||
|
|
||
| public List<Product> 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(); | ||
|
|
||
| } | ||
|
|
||
| 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(); | ||
|
|
@@ -49,12 +62,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 id, title, price, category, image_url, details FROM products WHERE details ->> 'color' = :color") | ||
| .param("color", color) | ||
| .query(PRODUCT_ROW_MAPPER) | ||
| .list(); | ||
| } | ||
|
|
||
| public Product setSize(int id, String size) { | ||
| // TODO: Implement | ||
| throw new UnsupportedOperationException("Not implemented yet"); | ||
| jdbcClient | ||
| .sql("UPDATE products SET details = jsonb_set(details, '{size}', ?::jsonb) WHERE id = ?") | ||
| .params("\"" + size + "\"", id) | ||
| .update(); | ||
| return findById(id); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,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 +27,18 @@ public ProductResponse setProductSize(int id, String size) { | |
| } | ||
|
|
||
| public ProductResponse setProductImage(int id, MultipartFile file) { | ||
|
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. what happens if the file is null? No exception handling exist in this method. |
||
| // TODO: Implement | ||
| // call ProductRepository.setImageUrl() afterwards with the new URL | ||
| throw new UnsupportedOperationException("Not implemented yet"); | ||
| String imageUrl = fileService.upload(file); // Uploading the file and getting back the public URL string | ||
| productRepository.setImageUrl(id, imageUrl); // Save the URL to the database image_url column | ||
| return ProductResponse.from(productRepository.findById(id)); | ||
| } | ||
|
|
||
| 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); | ||
| // check if an image exists | ||
| if (product != null && product.getImageUrl() != null) { | ||
| fileService.delete(product.getImageUrl()); | ||
| } | ||
| productRepository.setImageUrl(id, null); // Update the database image_url column to null | ||
| return ProductResponse.from(productRepository.findById(id)); | ||
| } | ||
| } | ||
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.
https://s3.eu-central-003.backblazeb2.com/file/ can be a constant.
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.
Extracting that URL into a constant (e.g. BASE_STORAGE_URL) helps because:
Single source of truth — if the bucket or region changes, you update one place, not hunt through 15 files
Readability — BASE_STORAGE_URL + path is clearer than a raw URL blob
Typo prevention — one misspelled character in a pasted URL is hard to spot; a misspelled constant name throws an error immediately
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.
oh ! good to know that, thank you so much 🙏