diff --git a/oversweet-api/src/main/java/com/depromeet/oversweet/record/controller/RecordController.java b/oversweet-api/src/main/java/com/depromeet/oversweet/record/controller/RecordController.java new file mode 100644 index 0000000..8df7bcc --- /dev/null +++ b/oversweet-api/src/main/java/com/depromeet/oversweet/record/controller/RecordController.java @@ -0,0 +1,42 @@ +package com.depromeet.oversweet.record.controller; + +import com.depromeet.oversweet.record.dto.request.DrinkRecordSaveRequest; +import com.depromeet.oversweet.record.dto.response.DrinkRecordSaveResponse; +import com.depromeet.oversweet.record.service.DrinkRecordSaveService; +import com.depromeet.oversweet.response.DataResponse; +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 org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +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; + +@Tag(name = "기록", description = "마신 음료 당 기록 관련 API") +@RestController +@RequestMapping("/api/v1/record") +@RequiredArgsConstructor +public class RecordController { + + private final DrinkRecordSaveService drinkRecordSaveService; + + /** + * 마신 음료 당 기록 + * 추후 로그인 기능 구현 후, 로그인한 유저의 ID를 받아와야 함 (ex. @AuthenticationPrincipal User user) + */ + @Operation(summary = "마신 음료 당 기록", description = "유저가 마신 음료의 당을 기록합니다.") + @ApiResponses(@ApiResponse(responseCode = "201", description = "마신 음료 당 기록 성공")) + @PostMapping("/drink") + public ResponseEntity> saveDrinkRecord( + @RequestBody @Valid final DrinkRecordSaveRequest drinkRecordSaveRequest + ) { + DrinkRecordSaveResponse response = drinkRecordSaveService.saveDrinkRecord(100L, drinkRecordSaveRequest); + return ResponseEntity.ok().body(DataResponse.of(HttpStatus.CREATED, "마신 음료 당 기록 성공", response)); + } + +} diff --git a/oversweet-api/src/main/java/com/depromeet/oversweet/record/dto/request/DrinkRecordSaveRequest.java b/oversweet-api/src/main/java/com/depromeet/oversweet/record/dto/request/DrinkRecordSaveRequest.java new file mode 100644 index 0000000..a85aad0 --- /dev/null +++ b/oversweet-api/src/main/java/com/depromeet/oversweet/record/dto/request/DrinkRecordSaveRequest.java @@ -0,0 +1,23 @@ +package com.depromeet.oversweet.record.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; + +@Builder +public record DrinkRecordSaveRequest( + + @NotNull + @Schema(description = "음료 ID", example = "1") Long drinkId, + + @NotNull + @Min(1) + @Schema(description = "마신 잔의 수", example = "2") Integer count, + + @NotNull + @Min(0) + @Schema(description = "1잔 기준 최종 섭취 당 함량", example = "10") Integer intakeSugar + +) { +} diff --git a/oversweet-api/src/main/java/com/depromeet/oversweet/record/dto/response/DrinkRecordSaveResponse.java b/oversweet-api/src/main/java/com/depromeet/oversweet/record/dto/response/DrinkRecordSaveResponse.java new file mode 100644 index 0000000..6c60c76 --- /dev/null +++ b/oversweet-api/src/main/java/com/depromeet/oversweet/record/dto/response/DrinkRecordSaveResponse.java @@ -0,0 +1,24 @@ +package com.depromeet.oversweet.record.dto.response; + +import com.depromeet.oversweet.drink.dto.response.DrinkDailySugarTotalStatisticsInfo; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; + +@Builder +public record DrinkRecordSaveResponse( + + @Schema(description = "지금 마신 음료 당", example = "10") int intakeSugar, + + @Schema(description = "하루 적정 당 섭취량", example = "24") int dailySugar, + + @Schema(description = "방금 기록한 당 성분까지 포함한, 오늘 먹은 총 당 섭취량", example = "20") int dailyTotalSugar, + + @Schema(description = "적정 당 섭취량을 초과 했는지 안했는지 여부", example = "false") boolean isExcess + +) { + public static DrinkRecordSaveResponse of(int intakeSugar, DrinkDailySugarTotalStatisticsInfo info) { + return new DrinkRecordSaveResponse( + intakeSugar, info.dailySugar(), info.dailyTotalSugar(), info.isExcess() + ); + } +} diff --git a/oversweet-api/src/main/java/com/depromeet/oversweet/record/service/DrinkRecordSaveService.java b/oversweet-api/src/main/java/com/depromeet/oversweet/record/service/DrinkRecordSaveService.java new file mode 100644 index 0000000..0e5a547 --- /dev/null +++ b/oversweet-api/src/main/java/com/depromeet/oversweet/record/service/DrinkRecordSaveService.java @@ -0,0 +1,57 @@ +package com.depromeet.oversweet.record.service; + +import com.depromeet.oversweet.domain.drink.entity.DrinkEntity; +import com.depromeet.oversweet.domain.drink.repository.FindDrinkRepository; +import com.depromeet.oversweet.domain.member.entity.MemberEntity; +import com.depromeet.oversweet.domain.member.repository.FindMemberRepository; +import com.depromeet.oversweet.domain.record.entity.RecordEntity; +import com.depromeet.oversweet.domain.record.repository.FindRecordsRepository; +import com.depromeet.oversweet.domain.record.repository.SaveRecordRepository; +import com.depromeet.oversweet.drink.dto.response.DrinkDailySugarTotalStatisticsInfo; +import com.depromeet.oversweet.drink.vo.DrinkStatisticsTotalInfo; +import com.depromeet.oversweet.drink.vo.LocalDateTimeInfo; +import com.depromeet.oversweet.record.dto.request.DrinkRecordSaveRequest; +import com.depromeet.oversweet.record.dto.response.DrinkRecordSaveResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class DrinkRecordSaveService { + + private final FindMemberRepository findMemberRepository; + private final FindDrinkRepository findDrinkRepository; + private final FindRecordsRepository findRecordsRepository; + private final SaveRecordRepository saveRecordRepository; + + public DrinkRecordSaveResponse saveDrinkRecord(Long memberId, DrinkRecordSaveRequest drinkRecordSaveRequest) { + + final MemberEntity findMember = findMemberRepository.findMemberById(memberId); + + final DrinkEntity findDrink = findDrinkRepository.findDrinkById(drinkRecordSaveRequest.drinkId()); + + RecordEntity record = RecordEntity.builder() + .member(findMember) + .drink(findDrink) + .count(drinkRecordSaveRequest.count()) + .intakeSugar(drinkRecordSaveRequest.intakeSugar()) + .build(); + + saveRecordRepository.saveRecord(record); + + // 오늘 (데일리 날짜 확인) 00:00 ~ 23:59 + final LocalDateTimeInfo dateTimeInfo = LocalDateTimeInfo.getDailyDateTime(); + + // 해당 유저의 오늘(데일리) 섭취 음료 조회 Repository + final List dailyRecords = findRecordsRepository.findRecordsByLocalDateTime(findMember.getId(), dateTimeInfo.startDateTime(), dateTimeInfo.endDateTime()); + + DrinkStatisticsTotalInfo drinkStatisticsTotalInfo = new DrinkStatisticsTotalInfo(dailyRecords); + + DrinkDailySugarTotalStatisticsInfo dailyTotalStatisticsInfo = drinkStatisticsTotalInfo.getDailyTotalStatisticsInfo(findMember.getDailySugar()); + + // 기록 관련 response 로 반환 + return DrinkRecordSaveResponse.of(record.getIntakeSugar(), dailyTotalStatisticsInfo); + } +} diff --git a/oversweet-api/src/test/java/com/depromeet/oversweet/common/TestSetup.java b/oversweet-api/src/test/java/com/depromeet/oversweet/common/TestSetup.java index fb29118..e06bb74 100644 --- a/oversweet-api/src/test/java/com/depromeet/oversweet/common/TestSetup.java +++ b/oversweet-api/src/test/java/com/depromeet/oversweet/common/TestSetup.java @@ -107,6 +107,4 @@ public RecordEntity getSecondRecord() { return secondRecordEntity; } - - } diff --git a/oversweet-api/src/test/java/com/depromeet/oversweet/record/service/DrinkRecordSaveServiceTest.java b/oversweet-api/src/test/java/com/depromeet/oversweet/record/service/DrinkRecordSaveServiceTest.java new file mode 100644 index 0000000..f89923e --- /dev/null +++ b/oversweet-api/src/test/java/com/depromeet/oversweet/record/service/DrinkRecordSaveServiceTest.java @@ -0,0 +1,78 @@ +package com.depromeet.oversweet.record.service; + +import com.depromeet.oversweet.common.TestSetup; +import com.depromeet.oversweet.domain.drink.entity.DrinkEntity; +import com.depromeet.oversweet.domain.drink.repository.FindDrinkRepository; +import com.depromeet.oversweet.domain.member.entity.MemberEntity; +import com.depromeet.oversweet.domain.member.repository.FindMemberRepository; +import com.depromeet.oversweet.domain.record.entity.RecordEntity; +import com.depromeet.oversweet.domain.record.repository.FindRecordsRepository; +import com.depromeet.oversweet.domain.record.repository.SaveRecordRepository; +import com.depromeet.oversweet.record.dto.request.DrinkRecordSaveRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoSettings; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +@DisplayName("마신 음료 당 기록 테스트") +@MockitoSettings +class DrinkRecordSaveServiceTest { + + @Mock + private FindMemberRepository findMemberRepository; + @Mock + private FindDrinkRepository findDrinkRepository; + @Mock + private FindRecordsRepository findRecordsRepository; + @Mock + private SaveRecordRepository saveRecordRepository; + + @InjectMocks + DrinkRecordSaveService drinkRecordSaveService; + + private MemberEntity memberEntity; + private DrinkEntity drinkEntity; + private RecordEntity recordEntity; + + @BeforeEach + void setUp() { + TestSetup testSetup = new TestSetup(); + testSetup.setUp(); + memberEntity = testSetup.getMember(); + drinkEntity = testSetup.getFirstDrink(); + recordEntity = testSetup.getFirstRecord(); + } + + @Test + @DisplayName("마신 음료 당 기록을 한다.") + void saveDrinkRecordTest() { + //given + + //when + when(findMemberRepository.findMemberById(memberEntity.getId())).thenReturn(memberEntity); + when(findDrinkRepository.findDrinkById(drinkEntity.getId())).thenReturn(drinkEntity); + when(findRecordsRepository.findRecordById(recordEntity.getId())).thenReturn(Optional.ofNullable(recordEntity)); + + DrinkRecordSaveRequest saveRequest = DrinkRecordSaveRequest.builder() + .drinkId(drinkEntity.getId()) + .count(recordEntity.getCount()) + .intakeSugar(recordEntity.getIntakeSugar()) + .build(); + + drinkRecordSaveService.saveDrinkRecord(memberEntity.getId(), saveRequest); + + Optional findRecordOpt = findRecordsRepository.findRecordById(recordEntity.getId()); + + assertThat(findRecordOpt.isPresent()); + assertEquals(recordEntity.getId(), findRecordOpt.get().getId()); + } + +} \ No newline at end of file diff --git a/oversweet-domain/src/main/java/com/depromeet/oversweet/domain/record/entity/RecordEntity.java b/oversweet-domain/src/main/java/com/depromeet/oversweet/domain/record/entity/RecordEntity.java index a559a6a..cc04679 100644 --- a/oversweet-domain/src/main/java/com/depromeet/oversweet/domain/record/entity/RecordEntity.java +++ b/oversweet-domain/src/main/java/com/depromeet/oversweet/domain/record/entity/RecordEntity.java @@ -52,8 +52,6 @@ public RecordEntity(final Long id, final MemberEntity member, final DrinkEntity this.intakeSugar = intakeSugar; } - - public int totalSugar() { return this.count * this.intakeSugar; } diff --git a/oversweet-domain/src/main/java/com/depromeet/oversweet/domain/record/repository/FindRecordsRepository.java b/oversweet-domain/src/main/java/com/depromeet/oversweet/domain/record/repository/FindRecordsRepository.java index 70a6da1..81432aa 100644 --- a/oversweet-domain/src/main/java/com/depromeet/oversweet/domain/record/repository/FindRecordsRepository.java +++ b/oversweet-domain/src/main/java/com/depromeet/oversweet/domain/record/repository/FindRecordsRepository.java @@ -4,10 +4,13 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; /** * 유저의 해당 기간 음료 당 조회 Interface */ public interface FindRecordsRepository { List findRecordsByLocalDateTime(final Long memberId, final LocalDateTime startDate, LocalDateTime endDate); + + Optional findRecordById(Long id); } diff --git a/oversweet-domain/src/main/java/com/depromeet/oversweet/domain/record/repository/FindRecordsRepositoryImpl.java b/oversweet-domain/src/main/java/com/depromeet/oversweet/domain/record/repository/FindRecordsRepositoryImpl.java index d2354a3..50f5338 100644 --- a/oversweet-domain/src/main/java/com/depromeet/oversweet/domain/record/repository/FindRecordsRepositoryImpl.java +++ b/oversweet-domain/src/main/java/com/depromeet/oversweet/domain/record/repository/FindRecordsRepositoryImpl.java @@ -8,6 +8,7 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; import static com.depromeet.oversweet.domain.drink.entity.QDrinkEntity.drinkEntity; import static com.depromeet.oversweet.domain.franchise.entity.QFranchiseEntity.franchiseEntity; @@ -39,4 +40,15 @@ public List findRecordsByLocalDateTime(final Long memberId, final .fetch(); } + + @Override + @Transactional(readOnly = true) + public Optional findRecordById(Long id) { + return Optional.ofNullable(queryFactory.selectFrom(recordEntity) + .join(recordEntity.drink, drinkEntity).fetchJoin() + .join(recordEntity.drink.franchise, franchiseEntity).fetchJoin() + .where(recordEntity.member.id.eq(id)) + .fetchOne()); + } } + diff --git a/oversweet-domain/src/main/java/com/depromeet/oversweet/domain/record/repository/SaveRecordRepository.java b/oversweet-domain/src/main/java/com/depromeet/oversweet/domain/record/repository/SaveRecordRepository.java new file mode 100644 index 0000000..9774d29 --- /dev/null +++ b/oversweet-domain/src/main/java/com/depromeet/oversweet/domain/record/repository/SaveRecordRepository.java @@ -0,0 +1,11 @@ +package com.depromeet.oversweet.domain.record.repository; + +import com.depromeet.oversweet.domain.record.entity.RecordEntity; + +/** + * 유저가 마신 음료 당 기록 Interface + */ +public interface SaveRecordRepository { + + void saveRecord(RecordEntity record); +} diff --git a/oversweet-domain/src/main/java/com/depromeet/oversweet/domain/record/repository/SaveRecordRepositoryImpl.java b/oversweet-domain/src/main/java/com/depromeet/oversweet/domain/record/repository/SaveRecordRepositoryImpl.java new file mode 100644 index 0000000..3822bde --- /dev/null +++ b/oversweet-domain/src/main/java/com/depromeet/oversweet/domain/record/repository/SaveRecordRepositoryImpl.java @@ -0,0 +1,22 @@ +package com.depromeet.oversweet.domain.record.repository; + +import com.depromeet.oversweet.domain.record.entity.RecordEntity; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +/** + * 유저가 마신 음료 당 기록 구현체 + */ +@Repository +@RequiredArgsConstructor +public class SaveRecordRepositoryImpl implements SaveRecordRepository { + + private final RecordJpaRepository recordJpaRepository; + + @Override + @Transactional + public void saveRecord(RecordEntity record) { + recordJpaRepository.save(record); + } +}