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

Release 1.0.0 #41

Merged
merged 73 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
c73616c
Develop Product save
koo995 Jul 21, 2024
e9d8bee
Add getters to NutritionFacts and NutritionFactsPerGram classes
koo995 Jul 22, 2024
7dfe043
Make constructors private and introduce builder pattern for object cr…
koo995 Jul 22, 2024
5f0483b
[test] Add test cases for product registration
koo995 Jul 22, 2024
5fd9126
Define initial database schema for NutriDiary
koo995 Jul 24, 2024
0958549
Merge pull request #19 from f-lab-edu/nutri-diary-6
koo995 Jul 25, 2024
2dc3527
Merge branch 'develop' into nutri-diary-22
koo995 Jul 25, 2024
79793c5
Merge pull request #24 from f-lab-edu/nutri-diary-22
koo995 Jul 25, 2024
8ec02f2
chore: liquibase 제거
koo995 Jul 27, 2024
37a3829
chore: validation 라이브러리 추가
koo995 Jul 27, 2024
f27d456
feat: Api 응답 포멧 통일
koo995 Jul 28, 2024
9eb55fb
refactor: 상품 등록 리팩토링
koo995 Jul 28, 2024
0a9ae90
test: 상품 등록 테스트 코드 리팩토링
koo995 Jul 28, 2024
cf7c398
remove: 불필요한 파일 삭제
koo995 Jul 28, 2024
1c9b252
remove: 불필요한 파일 삭제
koo995 Jul 28, 2024
5211311
feat: 개발과 테스트 profile 분리
koo995 Jul 28, 2024
29bde2f
feat: 상품 중복 등록 체크.
koo995 Jul 28, 2024
cc8bf27
test: 상품 중복 등록 테스트 코드 작성
koo995 Jul 28, 2024
e08b3ea
chore: 매직넘버를 상수로 선언
koo995 Jul 28, 2024
a345677
test: Product Validator 에 단위 테스트 추가
koo995 Jul 29, 2024
63352ed
refactor: ProductValidator 인터페이스 도입으로 DIP 적용
koo995 Jul 29, 2024
5cfd43d
remove: Product 와 Json 사이의 컨버터 테스트 제거
koo995 Jul 30, 2024
2e21743
rename: common 패키지에서 product 패키지로 이동
koo995 Jul 30, 2024
6638ac8
refactor: Product 상품에 유저의 mock id 값 추가
koo995 Jul 30, 2024
caafda8
refactor: 상품의 Validator 을 위한 리포지토리 인터페이스 분리
koo995 Jul 30, 2024
8daea5a
fix: db 에서 읽은 json 컬럼이 NutritionFacts 객체로 변환 안되는 문제 해결
koo995 Jul 30, 2024
5b29624
refactor: NutritionFact 의 필드명 수정과 ProductService 통합테스트 수정
koo995 Jul 30, 2024
9e75f9c
rename: ProductValidatorRepository 의 중복체크 메서드명 변경
koo995 Jul 30, 2024
1b5a0d7
chore: 오타수정
koo995 Jul 30, 2024
60359be
refactor: 예외처리를 봉투패턴으로 단순화 함.
koo995 Jul 31, 2024
ec68569
test: 상품조회 통합 테스트 추가
koo995 Jul 31, 2024
9a36b08
refactor: 예외 응답시 응답 객체 지네릭을 와일드 카드 대신 Void 사용.
koo995 Jul 31, 2024
9738c16
fix: 중복된 상품 예외의 타입 변경.
koo995 Jul 31, 2024
05d6314
chore: 사용하지 않는 애너테이션과 클래스 제거
koo995 Jul 31, 2024
ad16fcd
refactor: 컨테이너에 등록된 ObjectMapper 빈을 사용하도록 변경
koo995 Jul 31, 2024
2382f9f
refactor: 응답 상태 코드를 enum 으로 관리.
koo995 Aug 7, 2024
cbfce77
Merge pull request #26 from f-lab-edu/feature/#25
koo995 Aug 7, 2024
ce480ce
feat: 다이어리 작성
koo995 Jul 22, 2024
bc943a5
test: DiaryRepository 테스트 작성
koo995 Aug 11, 2024
ef0ed1c
refactor: NewDiaryRequest 에 생성자 추가
koo995 Aug 11, 2024
42f5c2e
test: DiaryRegisterService 테스트 추가
koo995 Aug 11, 2024
6faf40e
refactor: 계산 전략에 컴포넌트 애너테이션 제거
koo995 Aug 11, 2024
b0c0acb
rename: NewDiaryRequest 을 DiaryRegisterRequest 로 변경
koo995 Aug 11, 2024
3f08c38
refactor: 다이어리 등록 응답 결과 수정
koo995 Aug 11, 2024
da0c5ca
refactor: DIARY 테이블에 MEMBER_ID 와 DIARY_DATE 유니크 제약 조건 추가
koo995 Aug 11, 2024
020c662
refactor: DiaryRegisterService 리팩토링
koo995 Aug 11, 2024
438730f
refactor: NutritionFacts 의 나눗셈 결과 불필요한 소수점 제거
koo995 Aug 14, 2024
b41409e
refactor: Diary 클래스에서 DiaryRecord 을 Set 자료구조를 이용하여 매핑한다.
koo995 Aug 14, 2024
41b2921
rename: Calculator 관련 패키지 따로 정의
koo995 Aug 14, 2024
73b53da
fix: 중복된 필드명 제거
koo995 Aug 14, 2024
2d739f7
test: ProductIntakeInfoExtractorTest 추가
koo995 Aug 14, 2024
106a1ea
refactor: 영양성분 계산 결과 불필요한 소수점 제거
koo995 Aug 14, 2024
7ab81ac
refactor: 영양성분 계산 결과 불필요한 소수점 제거
koo995 Aug 14, 2024
5b64bf8
test: NutritionCalculator 테스트 추가
koo995 Aug 14, 2024
b741407
test: DiaryController 통합 테스트 추가
koo995 Aug 16, 2024
5bda085
refactor: DiaryRecord 의 productId 참조필드에 AggregateReference 적용
koo995 Aug 17, 2024
2d0fa87
feat: 유효성 검증 실패 response 반환
koo995 Aug 17, 2024
c68251c
refactor: request dto 에서 MealType enum 대신 문자열을 받도록 변경
koo995 Aug 17, 2024
609b542
rename: DiaryRepository.findByMemberIdAndDate 을 findByMemberIdAndDiar…
koo995 Aug 18, 2024
47576e7
Merge pull request #37 from f-lab-edu/feature/#36
koo995 Aug 18, 2024
eb5f2fc
build.gradle test 롬복 추가
koo995 Aug 24, 2024
87a31c1
rename: ProductIntakeInfoExtractor to ProductIntakeInfoMapper
koo995 Aug 24, 2024
2b98dde
refactor: Nutrition VO 도입
koo995 Aug 24, 2024
4c98801
test: 컨버터 Profile 분리
koo995 Aug 24, 2024
98083e1
refactor: Nutrition 객체의 불필요한 메서드 타입 제거
koo995 Aug 24, 2024
3f29994
refactor: 새로운 다이어리 레코드 저장 분리
koo995 Aug 24, 2024
2a65e02
refactor: 영양성분의 Json 필드 대신 varchar 사용
koo995 Aug 24, 2024
d10c3cf
feat: 다이어리 중복 validator 추가
koo995 Aug 25, 2024
11aebb5
refactor: ServingUnit 추상화
koo995 Aug 30, 2024
8865e20
Merge branch 'develop' into feature/#20
koo995 Sep 1, 2024
b7d638f
Merge pull request #27 from f-lab-edu/feature/#20
koo995 Sep 1, 2024
2b637ae
test: 유효성 검사 테스트 추가
koo995 Sep 1, 2024
35fa8cb
Merge pull request #40 from f-lab-edu/feature/#39
koo995 Sep 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
}

tasks.named('test') {
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/flab/nutridiary/commom/config/JdbcConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package flab.nutridiary.commom.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import flab.nutridiary.commom.generic.converter.JsonToNutritionConverter;
import flab.nutridiary.commom.generic.converter.NutritionToJsonConverter;
import flab.nutridiary.product.domain.converter.JsonToNutritionFactsConverter;
import flab.nutridiary.product.domain.converter.NutritionFactsToJsonConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration;

import java.util.Arrays;

@Configuration
public class JdbcConfig extends AbstractJdbcConfiguration {

// JdbcCustomConversions 빈을 등록한다.
@Bean
public JdbcCustomConversions jdbcCustomConversions(ObjectMapper objectMapper) {
return new JdbcCustomConversions(Arrays.asList(
new NutritionFactsToJsonConverter(objectMapper),
new JsonToNutritionFactsConverter(objectMapper),
new NutritionToJsonConverter(objectMapper),
new JsonToNutritionConverter(objectMapper)
));
}
}
35 changes: 35 additions & 0 deletions src/main/java/flab/nutridiary/commom/dto/ApiResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package flab.nutridiary.commom.dto;

import lombok.Getter;

import static flab.nutridiary.commom.exception.StatusConst.OK;
import static flab.nutridiary.commom.exception.StatusConst.SYSTEM_ERROR;

@Getter
public class ApiResponse<T> {
private final int statusCode;
private final String message;
private final T Data;

private ApiResponse(Integer statusCode, String message, T data) {
this.statusCode = statusCode;
this.message = message;
this.Data = data;
}

public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(OK.getStatusCode(), OK.getMessage(), data);
}

public static ApiResponse<Void> bizException(Integer statusCode, String message) {
return new ApiResponse<>(statusCode, message, null);
}

public static <T> ApiResponse<T> bizException(Integer statusCode, String message, T errors) {
return new ApiResponse<>(statusCode, message, errors);
}

public static ApiResponse<Void> sysException() {
return new ApiResponse<>(SYSTEM_ERROR.getStatusCode(), SYSTEM_ERROR.getMessage(), null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package flab.nutridiary.commom.exception;

import lombok.Getter;

@Getter
public class BusinessException extends RuntimeException{
private final int statusCode;

public BusinessException(StatusConst statusConst) {
super(statusConst.getMessage());
this.statusCode = statusConst.getStatusCode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package flab.nutridiary.commom.exception;

import flab.nutridiary.commom.dto.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

import static flab.nutridiary.commom.exception.StatusConst.*;

@Slf4j
@RestControllerAdvice
public class ExceptionController {

@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(BusinessException.class)
public ApiResponse<Void> BusinessExceptionHandler(BusinessException e) {
return ApiResponse.bizException(e.getStatusCode(), e.getMessage());
}

@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(SystemException.class)
public ApiResponse<Void> SystemExceptionHandler(SystemException e) {
log.info("SystemException : {}", e.getMessage());
return ApiResponse.sysException();
}

@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResponse<Map<String, String>> MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
Map<String, String> errors = new HashMap<>();
e.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage()));
return ApiResponse.bizException(VALIDATION_CHECK_FAIL.getStatusCode(), VALIDATION_CHECK_FAIL.getMessage(), errors);
}
}
21 changes: 21 additions & 0 deletions src/main/java/flab/nutridiary/commom/exception/StatusConst.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package flab.nutridiary.commom.exception;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum StatusConst {
OK(2001, "OK"),
SYSTEM_ERROR(5001, "서버에서 에러가 발생했습니다."),
INVALID_INPUT_VALUE(4001, "입력값이 올바르지 않습니다."),
DUPLICATED_PRODUCT_NAME(4002, "이미 등록된 식품입니다."),
INVALID_PRODUCT_ID(4003, "존재하지 않는 식품입니다."),
DIARY_NOT_FOUND(4004, "해당 다이어리를 찾을 수 없습니다."),
DUPLICATED_DIARY(4005, "이미 등록된 다이어리입니다."),
VALIDATION_CHECK_FAIL(6001, "유효성 검사에 실패했습니다."),
NOT_ALLOWED_SERVING_UNIT(6002, "허용되지 않은 서빙 단위입니다.");

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

import lombok.Getter;

@Getter
public class SystemException extends RuntimeException{
private static final String MESSAGE = "서버에서 에러가 발생했습니다.";

public SystemException() {
super(MESSAGE);
}

public SystemException(String message) {
super(message);
}
}
83 changes: 83 additions & 0 deletions src/main/java/flab/nutridiary/commom/generic/Nutrition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package flab.nutridiary.commom.generic;

import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;

import java.math.BigDecimal;
import java.math.RoundingMode;


@Getter
@EqualsAndHashCode
@ToString
public class Nutrition {
public static final int SCALE = 2;
public static final RoundingMode ROUNDING_MODE = RoundingMode.HALF_UP;
private static final Nutrition ZERO = Nutrition.of(BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO);

private final BigDecimal calories;
private final BigDecimal carbohydrate;
private final BigDecimal protein;
private final BigDecimal fat;

@Builder
private Nutrition(BigDecimal calories, BigDecimal carbohydrate, BigDecimal protein, BigDecimal fat) {
this.calories = stripIfNecessary(calories);
this.carbohydrate = stripIfNecessary(carbohydrate);
this.protein = stripIfNecessary(protein);
this.fat = stripIfNecessary(fat);
}

public Nutrition add(Nutrition nutrition) {
return Nutrition.builder().
calories(stripIfNecessary(this.calories.add(nutrition.calories))).
carbohydrate(stripIfNecessary(this.carbohydrate.add(nutrition.carbohydrate))).
protein(stripIfNecessary(this.protein.add(nutrition.protein))).
fat(stripIfNecessary(this.fat.add(nutrition.fat))).
build();
}

public Nutrition multiply(BigDecimal amount) {
return Nutrition.builder().
calories(stripIfNecessary(this.calories.multiply(amount))).
carbohydrate(stripIfNecessary(this.carbohydrate.multiply(amount))).
protein(stripIfNecessary(this.protein.multiply(amount))).
fat(stripIfNecessary(this.fat.multiply(amount))).
build();
}

public Nutrition divide(BigDecimal amount) {
return Nutrition.builder().
calories(stripIfNecessary(this.calories.divide(amount, SCALE, ROUNDING_MODE))).
carbohydrate(stripIfNecessary(this.carbohydrate.divide(amount, SCALE, ROUNDING_MODE))).
protein(stripIfNecessary(this.protein.divide(amount, SCALE, ROUNDING_MODE))).
fat(stripIfNecessary(this.fat.divide(amount, SCALE, ROUNDING_MODE))).
build();
}

public Nutrition subtract(Nutrition nutrition) {
return Nutrition.builder().
calories(stripIfNecessary(this.calories.subtract(nutrition.calories))).
carbohydrate(stripIfNecessary(this.carbohydrate.subtract(nutrition.carbohydrate))).
protein(stripIfNecessary(this.protein.subtract(nutrition.protein))).
fat(stripIfNecessary(this.fat.subtract(nutrition.fat))).
build();
}

private BigDecimal stripIfNecessary(BigDecimal value) {
BigDecimal scaledValue = value.setScale(SCALE, ROUNDING_MODE);
BigDecimal strippedValue = scaledValue.stripTrailingZeros();
return strippedValue.scale() <= 0 ? strippedValue.setScale(0) : strippedValue;
}

public static Nutrition of(BigDecimal calories, BigDecimal carbohydrate, BigDecimal protein, BigDecimal fat) {
return Nutrition.builder().
calories(calories).
carbohydrate(carbohydrate).
protein(protein).
fat(fat).
build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package flab.nutridiary.commom.generic.converter;

import com.fasterxml.jackson.databind.ObjectMapper;
import flab.nutridiary.commom.exception.SystemException;
import flab.nutridiary.commom.generic.Nutrition;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;

import java.io.IOException;

@RequiredArgsConstructor
@ReadingConverter
public class JsonToNutritionConverter implements Converter<String, Nutrition> {

private final ObjectMapper objectMapper;

@Override
public Nutrition convert(String source) {
try {
return objectMapper.readValue(source, Nutrition.class);
} catch (IOException e) {
throw new SystemException("JSON 타입을 CalculatedNutrition 객체로 변경하는데 실패했습니다.");
}
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package flab.nutridiary.commom.generic.converter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import flab.nutridiary.commom.exception.SystemException;
import flab.nutridiary.commom.generic.Nutrition;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.WritingConverter;

@RequiredArgsConstructor
@WritingConverter
public class NutritionToJsonConverter implements Converter<Nutrition, String> {

private final ObjectMapper objectMapper;

@Override
public String convert(Nutrition nutrition) {
try {
return objectMapper.writeValueAsString(nutrition);
} catch (JsonProcessingException e) {
throw new SystemException("Nutrition 객체를 JSON 타입으로 변경에 실패했습니다.");
}
}
}
19 changes: 19 additions & 0 deletions src/main/java/flab/nutridiary/commom/validation/EnumValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package flab.nutridiary.commom.validation;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import jakarta.validation.constraints.NotNull;

import java.lang.annotation.*;

@Documented
@Constraint(validatedBy = EnumValidatorConstraint.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@NotNull
public @interface EnumValidator {
Class<? extends Enum<?>> enumClass();
String message() default "유효하지 않은 타입입니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package flab.nutridiary.commom.validation;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class EnumValidatorConstraint implements ConstraintValidator<EnumValidator, String> {

Set<String> values;

@Override
public void initialize(EnumValidator constraintAnnotation) {
values = Stream.of(constraintAnnotation.enumClass().getEnumConstants())
.map(Enum::name)
.collect(Collectors.toSet());
}

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return values.contains(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package flab.nutridiary.diary.controller;

import flab.nutridiary.commom.dto.ApiResponse;
import flab.nutridiary.diary.dto.AddDiaryRecordRequest;
import flab.nutridiary.diary.dto.DiaryRegisterRequest;
import flab.nutridiary.diary.dto.DiarySavedResponse;
import flab.nutridiary.diary.service.AddDiaryRecordService;
import flab.nutridiary.diary.service.DiaryRegisterService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
public class DiaryController {
private final DiaryRegisterService diaryRegisterService;
private final AddDiaryRecordService addDiaryRecordService;

@PostMapping("/diary/new")
public ApiResponse<DiarySavedResponse> createDiary(@Valid @RequestBody DiaryRegisterRequest diaryRegisterRequest) {
return ApiResponse.success(diaryRegisterService.createDiary(diaryRegisterRequest));
}

@PostMapping("/diary/{diaryId}")
public ApiResponse<DiarySavedResponse> addDiaryRecord(@Valid @RequestBody AddDiaryRecordRequest addDiaryRecordRequest,
@PathVariable Long diaryId) {
return ApiResponse.success(addDiaryRecordService.addDiaryRecord(addDiaryRecordRequest, diaryId));
}
}
Loading
Loading