Skip to content

Commit

Permalink
Merge pull request #41 from f-lab-edu/release-1.0.0
Browse files Browse the repository at this point in the history
Release 1.0.0
  • Loading branch information
koo995 authored Sep 2, 2024
2 parents 5e21e82 + 35fa8cb commit b336e63
Show file tree
Hide file tree
Showing 66 changed files with 2,586 additions and 16 deletions.
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

0 comments on commit b336e63

Please sign in to comment.