Skip to content

Commit

Permalink
✨ [Feature] 마신 음료에 대한 당 기록 기능을 구현했어요. (#50)
Browse files Browse the repository at this point in the history
* feat: 마신 음료 당 기입 기능 구현 (#41)

* test: 마신 음료 당 기입 관련 테스트 (#41)

* chore: 공백 제거 (#41)

* chore: 응답 status 변경 (#41)

* chore: 노션 API 명세서에 작성된 내용에 맞추어 dto 필드명 변경 (#41)

* refactor: 기록 단건 조회 시 Optional 로 감싸서 반환되도록 수정 - PR 리뷰 (#41)
  • Loading branch information
parkje0927 authored Jul 7, 2023
1 parent 3fde119 commit abdc4d5
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -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<DataResponse<DrinkRecordSaveResponse>> saveDrinkRecord(
@RequestBody @Valid final DrinkRecordSaveRequest drinkRecordSaveRequest
) {
DrinkRecordSaveResponse response = drinkRecordSaveService.saveDrinkRecord(100L, drinkRecordSaveRequest);
return ResponseEntity.ok().body(DataResponse.of(HttpStatus.CREATED, "마신 음료 당 기록 성공", response));
}

}
Original file line number Diff line number Diff line change
@@ -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

) {
}
Original file line number Diff line number Diff line change
@@ -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()
);
}
}
Original file line number Diff line number Diff line change
@@ -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<RecordEntity> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,4 @@ public RecordEntity getSecondRecord() {
return secondRecordEntity;
}



}
Original file line number Diff line number Diff line change
@@ -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<RecordEntity> findRecordOpt = findRecordsRepository.findRecordById(recordEntity.getId());

assertThat(findRecordOpt.isPresent());
assertEquals(recordEntity.getId(), findRecordOpt.get().getId());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

/**
* 유저의 해당 기간 음료 당 조회 Interface
*/
public interface FindRecordsRepository {
List<RecordEntity> findRecordsByLocalDateTime(final Long memberId, final LocalDateTime startDate, LocalDateTime endDate);

Optional<RecordEntity> findRecordById(Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -39,4 +40,15 @@ public List<RecordEntity> findRecordsByLocalDateTime(final Long memberId, final
.fetch();

}

@Override
@Transactional(readOnly = true)
public Optional<RecordEntity> 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());
}
}

Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit abdc4d5

Please sign in to comment.