Skip to content
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

사용자가 식품에 대한 리뷰를 작성한다. #74

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,16 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
// thymeleaf
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
// aws s3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
runtimeOnly 'com.h2database:h2'
// test containers
testImplementation 'org.testcontainers:testcontainers:1.20.0'
testImplementation 'org.testcontainers:junit-jupiter:1.20.0'
testImplementation "org.testcontainers:mysql:1.20.1"
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testCompileOnly 'org.projectlombok:lombok'
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/flab/nutridiary/commom/config/JdbcConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration;
import org.springframework.data.jdbc.repository.config.EnableJdbcAuditing;

import java.util.Arrays;

@EnableJdbcAuditing
@Configuration
public class JdbcConfig extends AbstractJdbcConfiguration {

Expand Down
27 changes: 27 additions & 0 deletions src/main/java/flab/nutridiary/commom/config/S3Config.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package flab.nutridiary.commom.config;

import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Profile({"prod", "dev"})
@Configuration
public class S3Config {

@Value("${cloud.aws.s3.endpoint}")
private String endPoint;

@Value("${cloud.aws.s3.regionName}")
private String regionName;

@Bean
public AmazonS3 amazonS3() {
return AmazonS3Client.builder()
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endPoint, regionName))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ public enum StatusConst {
DIARY_NOT_FOUND(4004, "해당 다이어리를 찾을 수 없습니다."),
DUPLICATED_DIARY(4005, "이미 등록된 다이어리입니다."),
VALIDATION_CHECK_FAIL(6001, "유효성 검사에 실패했습니다."),
NOT_ALLOWED_SERVING_UNIT(6002, "허용되지 않은 서빙 단위입니다.");
NOT_ALLOWED_SERVING_UNIT(6002, "허용되지 않은 서빙 단위입니다."),
DUPLICATED_PRODUCT_REVIEW(4006, "이미 등록된 리뷰입니다.");

private final int statusCode;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package flab.nutridiary.commom.file;

import org.springframework.web.multipart.MultipartFile;

public interface FileStoreService {
public String uploadReviewImage(MultipartFile image);
public void deleteImageFromS3(String imageAddress);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package flab.nutridiary.commom.file;


import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.DeleteObjectRequest;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import flab.nutridiary.commom.exception.SystemException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.UUID;

@Profile({"dev", "prod"})
@RequiredArgsConstructor
@Service
public class FileStoreServiceImpl implements FileStoreService {
private final AmazonS3 amazonS3;

@Value("${cloud.aws.s3.bucketName}")
private String bucketName;

public String uploadReviewImage(MultipartFile image) {
String directory = "review/";

String s3FileName = directory + generateS3FileName(image);
return uploadToS3(s3FileName, image);
}

public void deleteImageFromS3(String imageAddress){
String key = getKeyFromImageAddress(imageAddress);
try{
amazonS3.deleteObject(new DeleteObjectRequest(bucketName, key));
}catch (Exception e){
throw new SystemException("이미지 삭제 중 오류가 발생했습니다.");
}
}

private String getExtension(String fileName) {
return fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
}

private String generateS3FileName(MultipartFile file) {
String extension = getExtension(file.getOriginalFilename());
return UUID.randomUUID().toString().substring(0, 10) + "_" + LocalDateTime.now() + "." + extension;
}

private ObjectMetadata getObjectMetadata(MultipartFile file, String extension) {
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType("image/" + extension);
metadata.setContentLength(file.getSize());
return metadata;
}

private String getS3FileUrl(String s3FileName) {
return amazonS3.getUrl(bucketName, s3FileName).toString();
}

private String uploadToS3(String s3FileName, MultipartFile file) {
String extension = getExtension(file.getOriginalFilename());
ByteArrayInputStream inputStream = null;
try {
inputStream = new ByteArrayInputStream(file.getBytes());
} catch (IOException e) {
throw new SystemException("이미지처리 중 오류가 발생했습니다.");
}
ObjectMetadata metadata = getObjectMetadata(file, extension);
amazonS3.putObject(new PutObjectRequest(bucketName, s3FileName, inputStream, metadata));
return getS3FileUrl(s3FileName);
}

private String getKeyFromImageAddress(String imageAddress){
try{
URL url = new URL(imageAddress);
String decodingKey = URLDecoder.decode(url.getPath(), StandardCharsets.UTF_8);
return decodingKey.substring(1); // 맨 앞의 '/' 제거
}catch (MalformedURLException e){
throw new SystemException("이미지 주소가 올바르지 않습니다.");
}
}
}
29 changes: 29 additions & 0 deletions src/main/java/flab/nutridiary/dietTag/domain/DietTag.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package flab.nutridiary.dietTag.domain;

import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.relational.core.mapping.Column;

import java.time.LocalDateTime;

@NoArgsConstructor
@Getter
public class DietTag {
@Id @Column("diet_tag_id")
private Long id;

private String dietPlan;

@CreatedDate
private LocalDateTime createdAt;

@LastModifiedDate
private LocalDateTime updatedAt;

public DietTag(String dietPlan) {
this.dietPlan = dietPlan;
}
}
36 changes: 36 additions & 0 deletions src/main/java/flab/nutridiary/productDietTag/ProductDietTag.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package flab.nutridiary.productDietTag;

import flab.nutridiary.dietTag.domain.DietTag;
import flab.nutridiary.product.domain.Product;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.relational.core.mapping.Column;

import java.time.LocalDateTime;

@ToString
@Getter
@NoArgsConstructor
public class ProductDietTag {
@Id @Column("product_diet_tag_id")
private Long id;

private AggregateReference<DietTag, Long> dietTagId;

private AggregateReference<Product, Long> productId;

@CreatedDate
private LocalDateTime createdAt;

@LastModifiedDate
private LocalDateTime updatedAt;

@Builder
public ProductDietTag(Long dietTagId, Long productId) {
this.dietTagId = AggregateReference.to(dietTagId);
this.productId = AggregateReference.to(productId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package flab.nutridiary.productDietTag;

import org.springframework.data.repository.CrudRepository;

public interface ProductDietTagRepository extends CrudRepository<ProductDietTag, Long> {
}
36 changes: 36 additions & 0 deletions src/main/java/flab/nutridiary/productStore/ProductStore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package flab.nutridiary.productStore;

import flab.nutridiary.product.domain.Product;
import flab.nutridiary.store.domain.Store;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.relational.core.mapping.Column;

import java.time.LocalDateTime;

@ToString
@Getter
@NoArgsConstructor
public class ProductStore {
@Id @Column("product_store_id")
private Long id;

private AggregateReference<Store, Long> storeId;

private AggregateReference<Product, Long> productId;

@CreatedDate
private LocalDateTime createdAt;

@LastModifiedDate
private LocalDateTime updatedAt;

@Builder
public ProductStore(Long storeId, Long productId) {
this.storeId = AggregateReference.to(storeId);
this.productId = AggregateReference.to(productId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package flab.nutridiary.productStore;

import org.springframework.data.repository.CrudRepository;

public interface ProductStoreRepository extends CrudRepository<ProductStore, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package flab.nutridiary.review.controller;

import flab.nutridiary.commom.dto.ApiResponse;
import flab.nutridiary.review.dto.request.CreateReviewRequest;
import flab.nutridiary.review.dto.response.CreateReviewResponse;
import flab.nutridiary.review.service.ReviewResisterService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
public class ReviewController {
private final ReviewResisterService reviewResisterService;

@PostMapping("review/new")
public ApiResponse<CreateReviewResponse> createReview(@ModelAttribute @Valid CreateReviewRequest createReviewRequest) {
Long memberId = 1L;
return ApiResponse.success(reviewResisterService.create(memberId, createReviewRequest));
}
}
46 changes: 46 additions & 0 deletions src/main/java/flab/nutridiary/review/domain/Review.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package flab.nutridiary.review.domain;

import flab.nutridiary.product.domain.Product;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.relational.core.mapping.Column;

import java.time.LocalDateTime;

@NoArgsConstructor
@ToString
@Getter
public class Review {
@Id @Column("review_id")
private Long id;

private Long memberId = 1L;

private AggregateReference<Product, Long> productId;

private String content;

private Short rating;

private String imageUrl;

@CreatedDate
private LocalDateTime createdAt;

@LastModifiedDate
private LocalDateTime updatedAt;

@Builder
public Review(Long productId, String content, Short rating, String imageUrl) {
this.productId = AggregateReference.to(productId);
this.content = content;
this.rating = rating;
this.imageUrl = imageUrl;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package flab.nutridiary.review.dto.request;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
import org.springframework.web.multipart.MultipartFile;

@ToString
@Getter
public class CreateReviewRequest {
@NotNull(message = "상품 ID를 입력해주세요.")
private Long productId;

@NotNull(message = "리뷰 내용을 입력해주세요.")
private String content;

@NotNull(message = "식단 태그 ID를 입력해주세요.")
private Long dietTagId;

@NotNull(message = "매장 ID를 입력해주세요.")
private Long storeId;

private MultipartFile image;

@Max(value = 5, message = "평점은 5 이하이어야 합니다.")
@Min(value = 1, message = "평점은 1 이상이어야 합니다.")
@NotNull(message = "평점을 입력해주세요.")
private short rating;

@Builder
public CreateReviewRequest(Long productId, String content, Long dietTagId, Long storeId, MultipartFile image, short rating) {
this.productId = productId;
this.content = content;
this.dietTagId = dietTagId;
this.storeId = storeId;
this.image = image;
this.rating = rating;
}
}
Loading
Loading