diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 784f53f..331ec4d 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -2,7 +2,7 @@ name: Backend CD # actions 이름 on: push: - branches: [ fix/#28 ] + branches: [ feature/#29 ] jobs: deploy: diff --git a/api-module/src/main/java/com/foodgo/apimodule/community/application/ChallengeFindUseCase.java b/api-module/src/main/java/com/foodgo/apimodule/community/application/ChallengeFindUseCase.java index 9b810b9..5560e46 100644 --- a/api-module/src/main/java/com/foodgo/apimodule/community/application/ChallengeFindUseCase.java +++ b/api-module/src/main/java/com/foodgo/apimodule/community/application/ChallengeFindUseCase.java @@ -47,20 +47,20 @@ public ChallengeList findChallengeList(User user, Long challengeId) { } private double calculateRate(List reports, Challenge challenge) { - int totalSum = 0; + double totalSum = 0; switch (challenge.getType()) { case CALORIE: - totalSum = reports.stream().mapToInt(Report::getTotal).sum(); + totalSum = reports.stream().mapToDouble(Report::getTotal).sum(); break; case CARB: - totalSum = reports.stream().mapToInt(Report::getCarb).sum(); + totalSum = reports.stream().mapToDouble(Report::getCarb).sum(); break; case PROTEIN: - totalSum = reports.stream().mapToInt(Report::getProtein).sum(); + totalSum = reports.stream().mapToDouble(Report::getProtein).sum(); break; case FAT: - totalSum = reports.stream().mapToInt(Report::getFat).sum(); + totalSum = reports.stream().mapToDouble(Report::getFat).sum(); break; case FREQUENCY: totalSum = reports.size(); diff --git a/api-module/src/main/java/com/foodgo/apimodule/ingredient/application/IngredientFindUseCase.java b/api-module/src/main/java/com/foodgo/apimodule/ingredient/application/IngredientFindUseCase.java index feebb61..fdae240 100644 --- a/api-module/src/main/java/com/foodgo/apimodule/ingredient/application/IngredientFindUseCase.java +++ b/api-module/src/main/java/com/foodgo/apimodule/ingredient/application/IngredientFindUseCase.java @@ -1,7 +1,10 @@ package com.foodgo.apimodule.ingredient.application; +import com.foodgo.apimodule.report.mapper.ReportMapper; import com.foodgo.coremodule.ingredient.dto.request.IngredientGetRequest; import com.foodgo.coremodule.ingredient.dto.response.IngredientGetResponse; +import com.foodgo.coremodule.report.domain.Report; +import com.foodgo.coremodule.report.service.ReportQueryService; import com.foodgo.coremodule.user.domain.User; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -19,6 +22,8 @@ public class IngredientFindUseCase { @Value("${spring.openapi.key.ingredient}") private String apiKey; + private final ReportQueryService reportQueryService; + public IngredientGetResponse.Row getIngredient(User user, IngredientGetRequest request) throws URISyntaxException { String apiUrl = "http://openapi.foodsafetykorea.go.kr/api/" + apiKey + "/I2790/json/1/100/DESC_KOR=" + request.descKor(); @@ -39,6 +44,13 @@ public IngredientGetResponse.Row getIngredient(User user, IngredientGetRequest r .filter(row -> groupNameMatches(row, request.groupName())) .toList(); + // 리포트 저장 후 결과 반환 + if (!filteredRows.isEmpty()) { + IngredientGetResponse.Row selectedRow = filteredRows.get(0); + Report report = ReportMapper.mapToReport(user, selectedRow); + reportQueryService.saveReport(report); // 리포트 저장 + } + return filteredRows.isEmpty() ? null : filteredRows.get(0); } diff --git a/api-module/src/main/java/com/foodgo/apimodule/ingredient/presentation/IngredientController.java b/api-module/src/main/java/com/foodgo/apimodule/ingredient/presentation/IngredientController.java index e1ce4a1..bda044a 100644 --- a/api-module/src/main/java/com/foodgo/apimodule/ingredient/presentation/IngredientController.java +++ b/api-module/src/main/java/com/foodgo/apimodule/ingredient/presentation/IngredientController.java @@ -1,36 +1,49 @@ package com.foodgo.apimodule.ingredient.presentation; -import java.net.URISyntaxException; - import com.foodgo.apimodule.ingredient.application.IngredientFindUseCase; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - import com.foodgo.commonmodule.common.ApplicationResponse; import com.foodgo.coremodule.ingredient.dto.request.IngredientGetRequest; import com.foodgo.coremodule.ingredient.dto.response.IngredientGetResponse; import com.foodgo.coremodule.security.annotation.UserResolver; import com.foodgo.coremodule.user.domain.User; - +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.net.URISyntaxException; @Slf4j @RequiredArgsConstructor @RestController @RequestMapping("/api/v1/ingredients") +@Tag(name = "ingredient", description = "식재료 관련 API") public class IngredientController { - private final IngredientFindUseCase ingredientFindUseCase; + private final IngredientFindUseCase ingredientFindUseCase; - @PostMapping("") - public ApplicationResponse getIngredientInfo( - @UserResolver User user, - @RequestBody @Valid IngredientGetRequest request - ) throws URISyntaxException { - return ApplicationResponse.onSuccess(ingredientFindUseCase.getIngredient(user, request)); - } + @PostMapping("") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "식재료 정보 확인 성공", + useReturnTypeSchema = true + ) + } + ) + @Operation(summary = "식재료 정보 확인 API", description = "식재료 정보 확인 + 리포트 DB 저장 API 입니다.") + public ApplicationResponse getIngredientInfo( + @UserResolver User user, + @RequestBody @Valid IngredientGetRequest request + ) throws URISyntaxException { + return ApplicationResponse.onSuccess(ingredientFindUseCase.getIngredient(user, request)); + } } diff --git a/api-module/src/main/java/com/foodgo/apimodule/report/application/ReportFindUseCase.java b/api-module/src/main/java/com/foodgo/apimodule/report/application/ReportFindUseCase.java new file mode 100644 index 0000000..80dd446 --- /dev/null +++ b/api-module/src/main/java/com/foodgo/apimodule/report/application/ReportFindUseCase.java @@ -0,0 +1,19 @@ +package com.foodgo.apimodule.report.application; + +import com.foodgo.coremodule.report.dto.ReportComparisonDTO; +import com.foodgo.coremodule.report.service.ReportQueryService; +import com.foodgo.coremodule.user.domain.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ReportFindUseCase { + + private final ReportQueryService reportQueryService; + + // 통게 dto list 만들기 + public ReportComparisonDTO getStatistics(User user) { + return reportQueryService.getWeeklyReportComparison(user.getId()); + } +} diff --git a/api-module/src/main/java/com/foodgo/apimodule/report/dto/ReportStatistics.java b/api-module/src/main/java/com/foodgo/apimodule/report/dto/ReportStatistics.java new file mode 100644 index 0000000..1c34cd4 --- /dev/null +++ b/api-module/src/main/java/com/foodgo/apimodule/report/dto/ReportStatistics.java @@ -0,0 +1,8 @@ +package com.foodgo.apimodule.report.dto; + +public record ReportStatistics( + String name, + Double lastweek, + Double thisweek +) { +} diff --git a/api-module/src/main/java/com/foodgo/apimodule/report/mapper/ReportMapper.java b/api-module/src/main/java/com/foodgo/apimodule/report/mapper/ReportMapper.java new file mode 100644 index 0000000..0a1c673 --- /dev/null +++ b/api-module/src/main/java/com/foodgo/apimodule/report/mapper/ReportMapper.java @@ -0,0 +1,41 @@ +package com.foodgo.apimodule.report.mapper; + +import com.foodgo.coremodule.ingredient.dto.response.IngredientGetResponse; +import com.foodgo.coremodule.report.domain.MealType; +import com.foodgo.coremodule.report.domain.Report; +import com.foodgo.coremodule.user.domain.User; + +import java.time.LocalTime; + +public class ReportMapper { + + public static Report mapToReport(User user, IngredientGetResponse.Row row) { + MealType mealType = determineMealType(); // MealType을 결정하는 메서드 호출 + + return Report.builder() + .user(user) + .type(mealType) // MealType 저장 + .total(Double.parseDouble(row.getNutrCont1())) // 총 칼로리 + .carb(Double.parseDouble(row.getNutrCont2())) // 탄수화물 + .protein(Double.parseDouble(row.getNutrCont3())) // 단백질 + .fat(Double.parseDouble(row.getNutrCont4())) // 지방 + .sugar(Double.parseDouble(row.getNutrCont5())) // 당류 + .sodium(Double.parseDouble(row.getNutrCont6())) // 나트륨 + .cholesterol(Double.parseDouble(row.getNutrCont7())) // 콜레스테롤 + .saturatedFat(Double.parseDouble(row.getNutrCont8())) // 포화지방산 + .transFat(Double.parseDouble(row.getNutrCont9())) // 트랜스지방 + .build(); + } + + private static MealType determineMealType() { + LocalTime now = LocalTime.now(); + + if (now.isBefore(LocalTime.NOON)) { + return MealType.BREAKFAST; // 정오 이전이면 아침 + } else if (now.isBefore(LocalTime.of(15, 0))) { + return MealType.LUNCH; // 오후 3시 이전이면 점심 + } else { + return MealType.DINNER; // 그 이후는 저녁 + } + } +} diff --git a/api-module/src/main/java/com/foodgo/apimodule/report/presentation/ReportController.java b/api-module/src/main/java/com/foodgo/apimodule/report/presentation/ReportController.java new file mode 100644 index 0000000..2fab0b1 --- /dev/null +++ b/api-module/src/main/java/com/foodgo/apimodule/report/presentation/ReportController.java @@ -0,0 +1,45 @@ +package com.foodgo.apimodule.report.presentation; + +import com.foodgo.apimodule.report.application.ReportFindUseCase; +import com.foodgo.commonmodule.common.ApplicationResponse; +import com.foodgo.coremodule.report.dto.ReportComparisonDTO; +import com.foodgo.coremodule.security.annotation.UserResolver; +import com.foodgo.coremodule.user.domain.User; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/report") +@Tag(name = "report", description = "리포트 관련 API") +public class ReportController { + + private final ReportFindUseCase reportFindUseCase; + + @GetMapping() + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "리포트 통계 확인 성공", + useReturnTypeSchema = true + ) + } + ) + @Operation(summary = "리포트 통계 확인 API", description = "리포트 통계 확인 API 입니다.") + public ApplicationResponse findReportStatistics( + @UserResolver User user) { + + ReportComparisonDTO comparisonDTO = reportFindUseCase.getStatistics(user); + return ApplicationResponse.onSuccess(comparisonDTO); + } + +} diff --git a/core-module/src/main/java/com/foodgo/coremodule/report/ReportRepository.java b/core-module/src/main/java/com/foodgo/coremodule/report/ReportRepository.java index 85718fc..028716b 100644 --- a/core-module/src/main/java/com/foodgo/coremodule/report/ReportRepository.java +++ b/core-module/src/main/java/com/foodgo/coremodule/report/ReportRepository.java @@ -2,6 +2,7 @@ import com.foodgo.coremodule.report.domain.Report; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import java.time.LocalDateTime; import java.util.List; @@ -9,4 +10,9 @@ public interface ReportRepository extends JpaRepository { List findAllByUserIdAndCreatedAtBetween(Long userId, LocalDateTime start, LocalDateTime end); + + // 이번 주 총 레포트 조회 + @Query("SELECT r FROM Report r WHERE r.user.id = :userId AND r.createdAt >= :startOfWeek AND r.createdAt < :endOfWeek") + List findReportsByUserAndCreatedAtBetween(Long userId, LocalDateTime startOfWeek, LocalDateTime endOfWeek); + } diff --git a/core-module/src/main/java/com/foodgo/coremodule/report/domain/Report.java b/core-module/src/main/java/com/foodgo/coremodule/report/domain/Report.java index df73749..afac597 100644 --- a/core-module/src/main/java/com/foodgo/coremodule/report/domain/Report.java +++ b/core-module/src/main/java/com/foodgo/coremodule/report/domain/Report.java @@ -26,15 +26,29 @@ public class Report extends BaseEntity { private MealType type; @Column(name = "report_total", nullable = false) - private Integer total; + private Double total; @Column(name = "report_carb", nullable = false) - private Integer carb; + private Double carb; @Column(name = "report_protein", nullable = false) - private Integer protein; + private Double protein; @Column(name = "report_fat", nullable = false) - private Integer fat; + private Double fat; + @Column(name = "report_sugar", nullable = false) + private Double sugar; // 당류 + + @Column(name = "report_sodium", nullable = false) + private Double sodium; // 나트륨 + + @Column(name = "report_cholesterol", nullable = false) + private Double cholesterol; // 콜레스테롤 + + @Column(name = "report_saturated_fat", nullable = false) + private Double saturatedFat; // 포화지방산 + + @Column(name = "report_trans_fat", nullable = false) + private Double transFat; // 트랜스지방 } diff --git a/core-module/src/main/java/com/foodgo/coremodule/report/dto/ReportComparisonDTO.java b/core-module/src/main/java/com/foodgo/coremodule/report/dto/ReportComparisonDTO.java new file mode 100644 index 0000000..433066b --- /dev/null +++ b/core-module/src/main/java/com/foodgo/coremodule/report/dto/ReportComparisonDTO.java @@ -0,0 +1,23 @@ +package com.foodgo.coremodule.report.dto; + +public record ReportComparisonDTO( + Double lastWeekTotal, + Double thisWeekTotal, + Double lastWeekCarbs, + Double thisWeekCarbs, + Double lastWeekProteins, + Double thisWeekProteins, + Double lastWeekFats, + Double thisWeekFats, + Double lastWeekSugar, + Double thisWeekSugar, + Double lastWeekSodium, + Double thisWeekSodium, + Double lastWeekCholesterol, + Double thisWeekCholesterol, + Double lastWeekSaturatedFat, + Double thisWeekSaturatedFat, + Double lastWeekTransFat, + Double thisWeekTransFat +) { +} diff --git a/core-module/src/main/java/com/foodgo/coremodule/report/service/ReportQueryService.java b/core-module/src/main/java/com/foodgo/coremodule/report/service/ReportQueryService.java index 73639a5..e9c8c39 100644 --- a/core-module/src/main/java/com/foodgo/coremodule/report/service/ReportQueryService.java +++ b/core-module/src/main/java/com/foodgo/coremodule/report/service/ReportQueryService.java @@ -2,10 +2,13 @@ import com.foodgo.coremodule.report.ReportRepository; import com.foodgo.coremodule.report.domain.Report; +import com.foodgo.coremodule.report.dto.ReportComparisonDTO; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.temporal.TemporalAdjusters; import java.util.List; @Service @@ -17,4 +20,123 @@ public class ReportQueryService { public List findReportByDate(Long userId, LocalDateTime start, LocalDateTime end) { return reportRepository.findAllByUserIdAndCreatedAtBetween(userId, start, end); } + + public void saveReport(Report report) { + reportRepository.save(report); + } + + // 이번 주 시작과 끝 날짜 구하기 + private LocalDate getStartOfWeek(LocalDate date) { + return date.with(TemporalAdjusters.previousOrSame(java.time.DayOfWeek.MONDAY)); + } + + private LocalDate getEndOfWeek(LocalDate date) { + return date.with(TemporalAdjusters.nextOrSame(java.time.DayOfWeek.SUNDAY)); + } + + public ReportComparisonDTO getWeeklyReportComparison(Long userId) { + LocalDate today = LocalDate.now(); + + // 이번 주와 저번 주의 날짜 범위 계산 + LocalDate startOfThisWeek = getStartOfWeek(today); + LocalDate endOfThisWeek = getEndOfWeek(today); + LocalDate startOfLastWeek = startOfThisWeek.minusWeeks(1); + LocalDate endOfLastWeek = endOfThisWeek.minusWeeks(1); + + // 이번 주와 저번 주의 칼로리 합산 + double thisWeekTotal = calculateTotalForWeek(userId, startOfThisWeek, endOfThisWeek); + double lastWeekTotal = calculateTotalForWeek(userId, startOfLastWeek, endOfLastWeek); + + // 영양소별 합산 (탄수화물, 단백질, 지방, 당류, 나트륨 등) + double thisWeekCarbs = calculateCarbsForWeek(userId, startOfThisWeek, endOfThisWeek); + double lastWeekCarbs = calculateCarbsForWeek(userId, startOfLastWeek, endOfLastWeek); + + double thisWeekProteins = calculateProteinsForWeek(userId, startOfThisWeek, endOfThisWeek); + double lastWeekProteins = calculateProteinsForWeek(userId, startOfLastWeek, endOfLastWeek); + + double thisWeekFats = calculateFatsForWeek(userId, startOfThisWeek, endOfThisWeek); + double lastWeekFats = calculateFatsForWeek(userId, startOfLastWeek, endOfLastWeek); + + double thisWeekSugar = calculateSugarForWeek(userId, startOfThisWeek, endOfThisWeek); + double lastWeekSugar = calculateSugarForWeek(userId, startOfLastWeek, endOfLastWeek); + + double thisWeekSodium = calculateSodiumForWeek(userId, startOfThisWeek, endOfThisWeek); + double lastWeekSodium = calculateSodiumForWeek(userId, startOfLastWeek, endOfLastWeek); + + double thisWeekCholesterol = calculateCholesterolForWeek(userId, startOfThisWeek, endOfThisWeek); + double lastWeekCholesterol = calculateCholesterolForWeek(userId, startOfLastWeek, endOfLastWeek); + + double thisWeekSaturatedFat = calculateSaturatedFatForWeek(userId, startOfThisWeek, endOfThisWeek); + double lastWeekSaturatedFat = calculateSaturatedFatForWeek(userId, startOfLastWeek, endOfLastWeek); + + double thisWeekTransFat = calculateTransFatForWeek(userId, startOfThisWeek, endOfThisWeek); + double lastWeekTransFat = calculateTransFatForWeek(userId, startOfLastWeek, endOfLastWeek); + + // 결과 반환 + return new ReportComparisonDTO( + lastWeekTotal, thisWeekTotal, + lastWeekCarbs, thisWeekCarbs, + lastWeekProteins, thisWeekProteins, + lastWeekFats, thisWeekFats, + lastWeekSugar, thisWeekSugar, + lastWeekSodium, thisWeekSodium, + lastWeekCholesterol, thisWeekCholesterol, + lastWeekSaturatedFat, thisWeekSaturatedFat, + lastWeekTransFat, thisWeekTransFat + ); + } + + // 주간 칼로리 합계 계산 + private double calculateTotalForWeek(Long userId, LocalDate start, LocalDate end) { + List reports = reportRepository.findReportsByUserAndCreatedAtBetween(userId, start.atStartOfDay(), end.atTime(23, 59, 59)); + return reports.stream().mapToDouble(Report::getTotal).sum(); + } + + // 주간 탄수화물 합계 계산 + private double calculateCarbsForWeek(Long userId, LocalDate start, LocalDate end) { + List reports = reportRepository.findReportsByUserAndCreatedAtBetween(userId, start.atStartOfDay(), end.atTime(23, 59, 59)); + return reports.stream().mapToDouble(Report::getCarb).sum(); + } + + // 주간 단백질 합계 계산 + private double calculateProteinsForWeek(Long userId, LocalDate start, LocalDate end) { + List reports = reportRepository.findReportsByUserAndCreatedAtBetween(userId, start.atStartOfDay(), end.atTime(23, 59, 59)); + return reports.stream().mapToDouble(Report::getProtein).sum(); + } + + // 주간 지방 합계 계산 + private double calculateFatsForWeek(Long userId, LocalDate start, LocalDate end) { + List reports = reportRepository.findReportsByUserAndCreatedAtBetween(userId, start.atStartOfDay(), end.atTime(23, 59, 59)); + return reports.stream().mapToDouble(Report::getFat).sum(); + } + + // 주간 당류 합계 계산 + private double calculateSugarForWeek(Long userId, LocalDate start, LocalDate end) { + List reports = reportRepository.findReportsByUserAndCreatedAtBetween(userId, start.atStartOfDay(), end.atTime(23, 59, 59)); + return reports.stream().mapToDouble(Report::getSugar).sum(); + } + + // 주간 나트륨 합계 계산 + private double calculateSodiumForWeek(Long userId, LocalDate start, LocalDate end) { + List reports = reportRepository.findReportsByUserAndCreatedAtBetween(userId, start.atStartOfDay(), end.atTime(23, 59, 59)); + return reports.stream().mapToDouble(Report::getSodium).sum(); + } + + // 주간 콜레스테롤 합계 계산 + private double calculateCholesterolForWeek(Long userId, LocalDate start, LocalDate end) { + List reports = reportRepository.findReportsByUserAndCreatedAtBetween(userId, start.atStartOfDay(), end.atTime(23, 59, 59)); + return reports.stream().mapToDouble(Report::getCholesterol).sum(); + } + + // 주간 포화지방산 합계 계산 + private double calculateSaturatedFatForWeek(Long userId, LocalDate start, LocalDate end) { + List reports = reportRepository.findReportsByUserAndCreatedAtBetween(userId, start.atStartOfDay(), end.atTime(23, 59, 59)); + return reports.stream().mapToDouble(Report::getSaturatedFat).sum(); + } + + // 주간 트랜스지방 합계 계산 + private double calculateTransFatForWeek(Long userId, LocalDate start, LocalDate end) { + List reports = reportRepository.findReportsByUserAndCreatedAtBetween(userId, start.atStartOfDay(), end.atTime(23, 59, 59)); + return reports.stream().mapToDouble(Report::getTransFat).sum(); + } }