From a11ce97541acccef2d1fb46657437099524ea8ba Mon Sep 17 00:00:00 2001 From: Sj0-0i Date: Thu, 21 Mar 2024 14:03:58 +0900 Subject: [PATCH 01/24] =?UTF-8?q?feat:=20alarm=20=EB=8F=99=EC=9D=98=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related-to:#17 --- .../controller/MemberAlarmController.java | 13 +++++++------ .../member/dto/AlarmAgreementResponse.java | 17 +++++++++++++++++ .../member/service/MemberAlarmService.java | 17 +++++++++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/umc5th/muffler/domain/member/dto/AlarmAgreementResponse.java diff --git a/src/main/java/com/umc5th/muffler/domain/member/controller/MemberAlarmController.java b/src/main/java/com/umc5th/muffler/domain/member/controller/MemberAlarmController.java index f1ba01e9..b3e0ca20 100644 --- a/src/main/java/com/umc5th/muffler/domain/member/controller/MemberAlarmController.java +++ b/src/main/java/com/umc5th/muffler/domain/member/controller/MemberAlarmController.java @@ -1,18 +1,14 @@ package com.umc5th.muffler.domain.member.controller; import com.umc5th.muffler.domain.member.dto.AlarmAgreeUpdateRequest; +import com.umc5th.muffler.domain.member.dto.AlarmAgreementResponse; import com.umc5th.muffler.domain.member.dto.TokenEnrollRequest; import com.umc5th.muffler.domain.member.service.MemberAlarmService; import com.umc5th.muffler.global.response.Response; import java.security.Principal; import javax.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @@ -37,4 +33,9 @@ public Response deleteAlarm(Principal principal) { memberAlarmService.deleteAlarmToken(principal.getName()); return Response.success(); } + + @GetMapping("/agree") + public Response getAlarmAgreement(Principal principal) { + return Response.success(memberAlarmService.getAlarmAgreement(principal.getName())); + } } diff --git a/src/main/java/com/umc5th/muffler/domain/member/dto/AlarmAgreementResponse.java b/src/main/java/com/umc5th/muffler/domain/member/dto/AlarmAgreementResponse.java new file mode 100644 index 00000000..d2746439 --- /dev/null +++ b/src/main/java/com/umc5th/muffler/domain/member/dto/AlarmAgreementResponse.java @@ -0,0 +1,17 @@ +package com.umc5th.muffler.domain.member.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Builder +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class AlarmAgreementResponse { + private Boolean isDailyPlanRemindAgree; + private Boolean isTodayEnrollRemindAgree; + private Boolean isYesterdayEnrollRemindAgree; + private Boolean isGoalEndReportRemindAgree; +} diff --git a/src/main/java/com/umc5th/muffler/domain/member/service/MemberAlarmService.java b/src/main/java/com/umc5th/muffler/domain/member/service/MemberAlarmService.java index 5f69cacc..f6869c1d 100644 --- a/src/main/java/com/umc5th/muffler/domain/member/service/MemberAlarmService.java +++ b/src/main/java/com/umc5th/muffler/domain/member/service/MemberAlarmService.java @@ -1,10 +1,12 @@ package com.umc5th.muffler.domain.member.service; import com.umc5th.muffler.domain.member.dto.AlarmAgreeUpdateRequest; +import com.umc5th.muffler.domain.member.dto.AlarmAgreementResponse; import com.umc5th.muffler.domain.member.dto.MemberConverter; import com.umc5th.muffler.domain.member.dto.TokenEnrollRequest; import com.umc5th.muffler.domain.member.repository.MemberRepository; import com.umc5th.muffler.entity.Member; +import com.umc5th.muffler.entity.MemberAlarm; import com.umc5th.muffler.global.response.code.ErrorCode; import com.umc5th.muffler.global.response.exception.MemberException; import lombok.RequiredArgsConstructor; @@ -37,4 +39,19 @@ public void deleteAlarmToken(String memberId) { .orElseThrow(() -> new MemberException(ErrorCode.MEMBER_NOT_FOUND)); member.deleteToken(); } + + @Transactional(readOnly = true) + public AlarmAgreementResponse getAlarmAgreement(String memberId) { + Member member = memberRepository.findMemberFetchAlarm(memberId) + .orElseThrow(() -> new MemberException(ErrorCode.MEMBER_NOT_FOUND)); + + MemberAlarm memberAlarm = member.getMemberAlarm(); + + return AlarmAgreementResponse.builder() + .isTodayEnrollRemindAgree(memberAlarm.getIsTodayEnrollRemindAgree()) + .isDailyPlanRemindAgree(memberAlarm.getIsDailyPlanRemindAgree()) + .isGoalEndReportRemindAgree(memberAlarm.getIsGoalEndReportRemindAgree()) + .isYesterdayEnrollRemindAgree(memberAlarm.getIsYesterdayEnrollRemindAgree()) + .build(); + } } From e65de9a4c7414dabb989e7bf3ab7d6ea041ab06e Mon Sep 17 00:00:00 2001 From: hajungIm Date: Fri, 22 Mar 2024 20:13:42 +0900 Subject: [PATCH 02/24] =?UTF-8?q?feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B8=B0=EB=B3=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 추후 검증 추가 예정 Related to: #16 --- .../domain/member/service/KakaoService.java | 25 ++++++++++++ .../domain/member/service/MemberService.java | 4 ++ .../global/security/jwt/JwtOICDProvider.java | 40 +++++++++++++++++++ src/main/resources/application.yml | 2 + 4 files changed, 71 insertions(+) create mode 100644 src/main/java/com/umc5th/muffler/domain/member/service/KakaoService.java create mode 100644 src/main/java/com/umc5th/muffler/global/security/jwt/JwtOICDProvider.java diff --git a/src/main/java/com/umc5th/muffler/domain/member/service/KakaoService.java b/src/main/java/com/umc5th/muffler/domain/member/service/KakaoService.java new file mode 100644 index 00000000..f9f71def --- /dev/null +++ b/src/main/java/com/umc5th/muffler/domain/member/service/KakaoService.java @@ -0,0 +1,25 @@ +package com.umc5th.muffler.domain.member.service; + +import com.umc5th.muffler.global.security.jwt.JwtOICDProvider; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Header; +import io.jsonwebtoken.Jwt; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class KakaoService { + + private final String ISS = "https://kauth.kakao.com"; + @Value("${jwt.aud}") + private String AUD; + + private final JwtOICDProvider jwtOICDProvider; + + public String login(String idToken) { + Jwt jwt = jwtOICDProvider.getUnsignedTokenClaims(idToken, ISS, AUD); + return jwt.getBody().getSubject(); + } +} diff --git a/src/main/java/com/umc5th/muffler/domain/member/service/MemberService.java b/src/main/java/com/umc5th/muffler/domain/member/service/MemberService.java index 90193653..525568fa 100644 --- a/src/main/java/com/umc5th/muffler/domain/member/service/MemberService.java +++ b/src/main/java/com/umc5th/muffler/domain/member/service/MemberService.java @@ -34,6 +34,7 @@ public class MemberService { private final MemberRepository memberRepository; private final AppleService appleService; + private final KakaoService kakaoService; private final JwtTokenUtils jwtTokenUtils; private final BatchUpdateCategoryRepository batchUpdateCategoryRepository; private final EntityManager entityManager; @@ -79,6 +80,9 @@ private String socialLogin(LoginRequest request) { if (request.getSocialType() == SocialType.APPLE) { return appleService.login(request); } + if (request.getSocialType() == SocialType.KAKAO) { + return kakaoService.login(request.getIdToken()); + } throw new CommonException(BAD_REQUEST, "지원하지 않는 소셜 로그인 입니다."); } diff --git a/src/main/java/com/umc5th/muffler/global/security/jwt/JwtOICDProvider.java b/src/main/java/com/umc5th/muffler/global/security/jwt/JwtOICDProvider.java new file mode 100644 index 00000000..73cbc4f1 --- /dev/null +++ b/src/main/java/com/umc5th/muffler/global/security/jwt/JwtOICDProvider.java @@ -0,0 +1,40 @@ +package com.umc5th.muffler.global.security.jwt; + +import static com.umc5th.muffler.global.response.code.ErrorCode.INVALID_TOKEN; + +import com.umc5th.muffler.global.response.exception.CommonException; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Header; +import io.jsonwebtoken.Jwt; +import io.jsonwebtoken.Jwts; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class JwtOICDProvider { + private String getUnsignedToken(String token) { + String[] splitToken = token.split("\\."); + if (splitToken.length != 3) { + throw new CommonException(INVALID_TOKEN, "Invalid KakaoIdToken"); + } + return splitToken[0] + "." + splitToken[1] + "."; + } + + public Jwt getUnsignedTokenClaims(String token, String iss, String aud) { + try { + return Jwts.parserBuilder() + .requireAudience(aud) + .requireIssuer(iss) + .build() + .parseClaimsJwt(getUnsignedToken(token)); + } catch (ExpiredJwtException e) { + log.error("Expired JWT Token", e); + throw new CommonException(INVALID_TOKEN, "Expired JWT Token"); + } catch (Exception e) { + log.error(e.toString()); + throw new CommonException(INVALID_TOKEN, "Invalid KakaoIdToken"); + } + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 18a4f78d..5c9485c6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -73,6 +73,8 @@ spring: enable: true jwt: secret: ${JWT_SECRET} + aud: ${JWT_AUD} + server: servlet: encoding: From 3faebd4d84fd752624446125b44746356b7b7e2a Mon Sep 17 00:00:00 2001 From: hajungIm Date: Fri, 22 Mar 2024 20:25:52 +0900 Subject: [PATCH 03/24] =?UTF-8?q?fix:=20=EC=84=A4=EC=A0=95=20=EB=AF=B8?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=97=90=EB=9F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 2 +- src/test/resources/application.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 5c9485c6..85b91a2f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -97,4 +97,4 @@ alarm: image-url: daily-plan-remind: "daily-plan-remind" expense-enroll-remind: "expense-enroll-remind" - goal-end-remind: "goal-end-remind" \ No newline at end of file + goal-end-remind: "goal-end-remind" diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index eb172909..855b4956 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -64,6 +64,7 @@ spring: enable: true jwt: secret: testtesttesttesttesttesttesttest + aud: ${JWT_AUD} server: servlet: encoding: From 8642a14d6482a39f0871d676f606afc7182c13d4 Mon Sep 17 00:00:00 2001 From: hajungIm Date: Fri, 22 Mar 2024 20:34:39 +0900 Subject: [PATCH 04/24] =?UTF-8?q?fix:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20fake?= =?UTF-8?q?=20=EA=B0=92=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 855b4956..ec628577 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -64,7 +64,7 @@ spring: enable: true jwt: secret: testtesttesttesttesttesttesttest - aud: ${JWT_AUD} + aud: testAUD server: servlet: encoding: From bc49700a27c7b83c92a9a81d2d4da3b6295d6164 Mon Sep 17 00:00:00 2001 From: hajungIm Date: Wed, 27 Mar 2024 17:51:57 +0900 Subject: [PATCH 05/24] =?UTF-8?q?feat:=20=EB=AA=A9=ED=91=9C=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=A4=91=20=EC=86=8C=EB=B9=84=20=EB=90=98=EC=82=B4?= =?UTF-8?q?=EB=A6=AC=EA=B8=B0=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to: #4 --- .../expense/repository/ExpenseRepository.java | 7 ++++ .../repository/ExpenseRepositoryCustom.java | 7 ++-- .../repository/ExpenseRepositoryImpl.java | 42 +++++++++++++++---- .../goal/controller/GoalController.java | 21 ++++++++++ .../domain/goal/service/GoalService.java | 18 ++++++++ 5 files changed, 85 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepository.java b/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepository.java index a6b238bc..9fee2ddc 100644 --- a/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepository.java +++ b/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepository.java @@ -3,6 +3,7 @@ import com.umc5th.muffler.entity.Expense; import com.umc5th.muffler.entity.Member; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @@ -10,6 +11,7 @@ import java.time.LocalDate; import java.util.Optional; +import org.springframework.transaction.annotation.Transactional; @Repository public interface ExpenseRepository extends JpaRepository, ExpenseRepositoryCustom { @@ -27,4 +29,9 @@ public interface ExpenseRepository extends JpaRepository, Expense @Query("SELECT SUM(e.cost) FROM Expense e WHERE e.member.id = :memberId AND e.category.id = :categoryId AND e.date BETWEEN :startDate AND :endDate") Optional sumTotalCategoryCostByMemberAndDateBetween(String memberId, Long categoryId, LocalDate startDate, LocalDate endDate); + + @Transactional + @Modifying + @Query("DELETE FROM Expense e WHERE e.id in :ids") + void deleteByIds(List ids); } diff --git a/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryCustom.java b/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryCustom.java index e3c18062..302c2104 100644 --- a/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryCustom.java +++ b/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryCustom.java @@ -2,13 +2,12 @@ import com.umc5th.muffler.entity.Category; import com.umc5th.muffler.entity.Expense; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; import com.umc5th.muffler.entity.Goal; import java.time.LocalDate; -import java.time.LocalDateTime; import java.util.List; import java.util.Map; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; public interface ExpenseRepositoryCustom { Map> findByMemberAndDateRangeGroupedByDate(String memberId, LocalDate startDate, LocalDate endDate); @@ -18,4 +17,6 @@ public interface ExpenseRepositoryCustom { Long sumCategoryExpenseWithinGoal(String memberId, Category category, Goal goal); Long sumCostByMemberAndDateBetween(String memberId, LocalDate startDate, LocalDate endDate); Slice findByMemberAndTitleContaining(String memberId, String searchKeyword, LocalDate lastDate, Long lastExpenseId, int size, String order); + boolean existsExpense(String memberId, LocalDate startDate, LocalDate endDate); + List findByMemberIdAndDateRange(String memberId, LocalDate startDate, LocalDate endDate); } diff --git a/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java b/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java index ea1add88..97704775 100644 --- a/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java +++ b/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java @@ -1,21 +1,27 @@ package com.umc5th.muffler.domain.expense.repository; +import static com.umc5th.muffler.entity.QCategory.category; +import static com.umc5th.muffler.entity.QExpense.expense; + import com.querydsl.core.types.Order; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; -import com.umc5th.muffler.entity.*; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.*; - +import com.umc5th.muffler.entity.Category; +import com.umc5th.muffler.entity.Expense; +import com.umc5th.muffler.entity.Goal; +import com.umc5th.muffler.entity.QExpense; import java.time.LocalDate; import java.util.List; import java.util.Map; import java.util.stream.Collectors; - -import static com.umc5th.muffler.entity.QCategory.category; -import static com.umc5th.muffler.entity.QExpense.expense; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import org.springframework.data.domain.Sort; @RequiredArgsConstructor public class ExpenseRepositoryImpl implements ExpenseRepositoryCustom { @@ -171,6 +177,28 @@ public Slice findByMemberAndTitleContaining(String memberId, String sea return new SliceImpl<>(results, pageable, hasNext); } + @Override + public boolean existsExpense(String memberId, LocalDate startDate, LocalDate endDate) { + QExpense expense = QExpense.expense; + + return queryFactory.select(expense.id) + .from(expense) + .where(expense.member.id.eq(memberId) + .and(expense.date.between(startDate, endDate))) + .fetchFirst() != null; + } + + @Override + public List findByMemberIdAndDateRange(String memberId, LocalDate startDate, LocalDate endDate) { + QExpense expense = QExpense.expense; + + return queryFactory.select(expense.id) + .from(expense) + .where(expense.member.id.eq(memberId) + .and(expense.date.between(startDate, endDate))) + .fetch(); + } + private BooleanExpression searchTitle(String searchKeyword){ if (searchKeyword != null && !searchKeyword.trim().isEmpty()) { return expense.title.containsIgnoreCase(searchKeyword); diff --git a/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java b/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java index 34d3b886..349d8a47 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java @@ -21,6 +21,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -53,6 +54,26 @@ public Response getPrevious(Authentication authentication) return Response.success(GoalConverter.getGoalPreviousResponse(goals)); } + @GetMapping("/restore") + public ResponseEntity checkRestore(Authentication authentication, + @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate, + @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) { + boolean isExists = goalService.checkRestore(authentication.getName(), startDate, endDate); + if (isExists) { + return ResponseEntity.ok().build(); + } + return ResponseEntity.noContent().build(); + } + + @PostMapping("/restore") + public Response restore(Authentication authentication, + @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate, + @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate, + @RequestParam boolean restore) { + goalService.restore(authentication.getName(), startDate, endDate, restore); + return Response.success(); + } + @PatchMapping("/{goalId}") public Response updateTitle(@PathVariable Long goalId, @RequestBody @Valid GoalTitleRequest request, Authentication authentication) { goalService.updateTitle(goalId, request.getTitle(), authentication.getName()); diff --git a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java index c5cbfa2e..996c5cc8 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java @@ -46,12 +46,30 @@ public class GoalService { private final GoalRepository goalRepository; private final ExpenseRepository expenseRepository; + @Transactional(readOnly = true) public List getGoals(String memberId) { Member member = memberRepository.findById(memberId) .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); return member.getGoals(); } + @Transactional(readOnly = true) + public boolean checkRestore(String memberId, LocalDate startDate, LocalDate endDate) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); + return expenseRepository.existsExpense(memberId, startDate, endDate); + } + + public void restore(String memberId, LocalDate startDate, LocalDate endDate, boolean restore) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); + if (restore) { + return; + } + List expenseIds = expenseRepository.findByMemberIdAndDateRange(memberId, startDate, endDate); + expenseRepository.deleteByIds(expenseIds); + } + public void updateTitle(Long goalId, String title, String memberId) { Member member = memberRepository.findById(memberId) .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); From a6dc42a4adc0ae484409fae4838343d68c9251d5 Mon Sep 17 00:00:00 2001 From: hajungIm Date: Thu, 28 Mar 2024 23:18:38 +0900 Subject: [PATCH 06/24] =?UTF-8?q?fix:=20=EB=90=98=EC=82=B4=EB=A6=AC?= =?UTF-8?q?=EA=B8=B0=20PatchMapping=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to: #4 --- .../umc5th/muffler/domain/goal/controller/GoalController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java b/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java index 349d8a47..c742c648 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java @@ -65,7 +65,7 @@ public ResponseEntity checkRestore(Authentication authentication, return ResponseEntity.noContent().build(); } - @PostMapping("/restore") + @PatchMapping("/restore") public Response restore(Authentication authentication, @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate, @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate, From 7fd9f857f925b544ef9120858572a8b19ab01912 Mon Sep 17 00:00:00 2001 From: shortboy7 Date: Fri, 29 Mar 2024 21:07:51 +0900 Subject: [PATCH 07/24] =?UTF-8?q?refactor=20:=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20api=EC=99=80=20=EC=86=8C=EB=B9=84=20?= =?UTF-8?q?=EC=9E=AC=EC=83=9D=20api=20=ED=95=A9=EC=B9=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related-to : #45 --- .../domain/goal/controller/GoalController.java | 12 +++--------- .../muffler/domain/goal/dto/GoalCreateRequest.java | 2 ++ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java b/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java index 349d8a47..b5fba20c 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java @@ -45,6 +45,9 @@ public class GoalController { @PostMapping public Response create(@RequestBody @Valid GoalCreateRequest request, Authentication authentication) { goalCreateService.create(request, authentication.getName()); + if (request.getRestore()) { + goalService.restore(authentication.getName(), request.getStartDate(), request.getEndDate(), request.getRestore()); + } return Response.success(); } @@ -65,15 +68,6 @@ public ResponseEntity checkRestore(Authentication authentication, return ResponseEntity.noContent().build(); } - @PostMapping("/restore") - public Response restore(Authentication authentication, - @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate, - @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate, - @RequestParam boolean restore) { - goalService.restore(authentication.getName(), startDate, endDate, restore); - return Response.success(); - } - @PatchMapping("/{goalId}") public Response updateTitle(@PathVariable Long goalId, @RequestBody @Valid GoalTitleRequest request, Authentication authentication) { goalService.updateTitle(goalId, request.getTitle(), authentication.getName()); diff --git a/src/main/java/com/umc5th/muffler/domain/goal/dto/GoalCreateRequest.java b/src/main/java/com/umc5th/muffler/domain/goal/dto/GoalCreateRequest.java index 3267d769..41cc0dd6 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/dto/GoalCreateRequest.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/dto/GoalCreateRequest.java @@ -24,6 +24,8 @@ public class GoalCreateRequest { private LocalDate endDate; @NotNull private Long totalBudget; + @NotNull + private Boolean restore; @NotNull private List categoryGoals; From 5b50269403ba874e5d978a0d0f61bdcd69cf7358 Mon Sep 17 00:00:00 2001 From: shortboy7 Date: Fri, 29 Mar 2024 21:09:38 +0900 Subject: [PATCH 08/24] =?UTF-8?q?refactor=20:=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20api=EC=99=80=20=EC=86=8C=EB=B9=84=20?= =?UTF-8?q?=EC=9E=AC=EC=83=9D=20api=20=ED=95=A9=EC=B9=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related-to : #45 --- .../muffler/domain/goal/controller/GoalController.java | 4 ++-- .../com/umc5th/muffler/domain/goal/service/GoalService.java | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java b/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java index b5fba20c..1086b306 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java @@ -45,8 +45,8 @@ public class GoalController { @PostMapping public Response create(@RequestBody @Valid GoalCreateRequest request, Authentication authentication) { goalCreateService.create(request, authentication.getName()); - if (request.getRestore()) { - goalService.restore(authentication.getName(), request.getStartDate(), request.getEndDate(), request.getRestore()); + if (!request.getRestore()) { + goalService.deleteLeftExpense(authentication.getName(), request.getStartDate(), request.getEndDate()); } return Response.success(); } diff --git a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java index 996c5cc8..ce69cf9c 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java @@ -60,12 +60,9 @@ public boolean checkRestore(String memberId, LocalDate startDate, LocalDate endD return expenseRepository.existsExpense(memberId, startDate, endDate); } - public void restore(String memberId, LocalDate startDate, LocalDate endDate, boolean restore) { + public void deleteLeftExpense(String memberId, LocalDate startDate, LocalDate endDate) { Member member = memberRepository.findById(memberId) .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); - if (restore) { - return; - } List expenseIds = expenseRepository.findByMemberIdAndDateRange(memberId, startDate, endDate); expenseRepository.deleteByIds(expenseIds); } From f63ca23a705b1c56dfe3df13400f076b81007347 Mon Sep 17 00:00:00 2001 From: shortboy7 Date: Fri, 29 Mar 2024 21:28:48 +0900 Subject: [PATCH 09/24] =?UTF-8?q?feature=20:=20=EC=A3=BC=EC=96=B4=EC=A7=84?= =?UTF-8?q?=20=EA=B8=B0=EA=B0=84=20=EB=82=B4=EC=97=90=20=EB=82=A0=EC=A7=9C?= =?UTF-8?q?=EB=B3=84=20=EC=86=8C=EB=B9=84=20=EA=B8=88=EC=95=A1=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20repository=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related-to : #45 --- .../repository/ExpenseRepositoryCustom.java | 1 + .../repository/ExpenseRepositoryImpl.java | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryCustom.java b/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryCustom.java index 302c2104..ca35bccb 100644 --- a/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryCustom.java +++ b/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryCustom.java @@ -19,4 +19,5 @@ public interface ExpenseRepositoryCustom { Slice findByMemberAndTitleContaining(String memberId, String searchKeyword, LocalDate lastDate, Long lastExpenseId, int size, String order); boolean existsExpense(String memberId, LocalDate startDate, LocalDate endDate); List findByMemberIdAndDateRange(String memberId, LocalDate startDate, LocalDate endDate); + Map findTotalCostDate(String memberId, LocalDate startDate, LocalDate endDate); } diff --git a/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java b/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java index 97704775..334585ae 100644 --- a/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java +++ b/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java @@ -3,6 +3,7 @@ import static com.umc5th.muffler.entity.QCategory.category; import static com.umc5th.muffler.entity.QExpense.expense; +import com.querydsl.core.Tuple; import com.querydsl.core.types.Order; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.dsl.BooleanExpression; @@ -13,6 +14,7 @@ import com.umc5th.muffler.entity.Goal; import com.umc5th.muffler.entity.QExpense; import java.time.LocalDate; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -198,6 +200,26 @@ public List findByMemberIdAndDateRange(String memberId, LocalDate startDat .and(expense.date.between(startDate, endDate))) .fetch(); } + @Override + public Map findTotalCostDate(String memberId, LocalDate startDate, LocalDate endDate) { + QExpense expense = QExpense.expense; + + Map expenseMap = new HashMap<>(); + List tuples = queryFactory + .select(expense.date, expense.cost.sum()) + .from(expense) + .where(expense.date.between(startDate, endDate)) + .groupBy(expense.date) + .fetch(); + for (Tuple tuple : tuples) { + LocalDate date = tuple.get(expense.date); + Long sum = tuple.get(expense.cost.sum()); + expenseMap.put(date, sum); + } + return expenseMap; + } + + private BooleanExpression searchTitle(String searchKeyword){ if (searchKeyword != null && !searchKeyword.trim().isEmpty()) { From 2b9116b2b84dfb2f4a60d6716d639eda577aa80f Mon Sep 17 00:00:00 2001 From: hajungIm Date: Fri, 29 Mar 2024 14:24:03 +0900 Subject: [PATCH 10/24] =?UTF-8?q?feat:=20=EB=AA=A9=ED=91=9C=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=EC=BD=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to: #4 --- .../muffler/domain/goal/controller/GoalController.java | 5 +++++ .../umc5th/muffler/domain/goal/dto/GoalCreateRequest.java | 2 +- .../dto/{GoalTitleRequest.java => GoalUpdateRequest.java} | 4 +++- .../com/umc5th/muffler/domain/goal/service/GoalService.java | 3 ++- src/main/java/com/umc5th/muffler/entity/Goal.java | 4 ++++ 5 files changed, 15 insertions(+), 3 deletions(-) rename src/main/java/com/umc5th/muffler/domain/goal/dto/{GoalTitleRequest.java => GoalUpdateRequest.java} (78%) diff --git a/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java b/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java index c742c648..8b18c5fa 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java @@ -77,6 +77,11 @@ public Response restore(Authentication authentication, @PatchMapping("/{goalId}") public Response updateTitle(@PathVariable Long goalId, @RequestBody @Valid GoalTitleRequest request, Authentication authentication) { goalService.updateTitle(goalId, request.getTitle(), authentication.getName()); + public Response updateTitleAndIcon(@PathVariable Long goalId, @RequestBody @Valid GoalTitleRequest request, Authentication authentication) { + goalService.updateTitleAndIcon(goalId, request.getTitle(), request.getIcon(), authentication.getName()); + return Response.success(); + } + return Response.success(); } diff --git a/src/main/java/com/umc5th/muffler/domain/goal/dto/GoalCreateRequest.java b/src/main/java/com/umc5th/muffler/domain/goal/dto/GoalCreateRequest.java index 3267d769..80e147af 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/dto/GoalCreateRequest.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/dto/GoalCreateRequest.java @@ -14,7 +14,7 @@ @NoArgsConstructor @Builder public class GoalCreateRequest { - @NotNull + @NotBlank private String icon; @NotBlank private String title; diff --git a/src/main/java/com/umc5th/muffler/domain/goal/dto/GoalTitleRequest.java b/src/main/java/com/umc5th/muffler/domain/goal/dto/GoalUpdateRequest.java similarity index 78% rename from src/main/java/com/umc5th/muffler/domain/goal/dto/GoalTitleRequest.java rename to src/main/java/com/umc5th/muffler/domain/goal/dto/GoalUpdateRequest.java index 1f32268d..b24187f2 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/dto/GoalTitleRequest.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/dto/GoalUpdateRequest.java @@ -8,7 +8,9 @@ @AllArgsConstructor @NoArgsConstructor @Getter -public class GoalTitleRequest { +public class GoalUpdateRequest { @NotBlank private String title; + @NotBlank + private String icon; } diff --git a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java index 996c5cc8..e67d8728 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java @@ -70,12 +70,13 @@ public void restore(String memberId, LocalDate startDate, LocalDate endDate, boo expenseRepository.deleteByIds(expenseIds); } - public void updateTitle(Long goalId, String title, String memberId) { + public void updateTitleAndIcon(Long goalId, String title, String icon, String memberId) { Member member = memberRepository.findById(memberId) .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); Goal goal = goalRepository.findById(goalId) .orElseThrow(() -> new GoalException(GOAL_NOT_FOUND)); goal.updateTitle(title); + goal.updateIcon(icon); } @Transactional diff --git a/src/main/java/com/umc5th/muffler/entity/Goal.java b/src/main/java/com/umc5th/muffler/entity/Goal.java index 62f40d2f..a41e0ce4 100644 --- a/src/main/java/com/umc5th/muffler/entity/Goal.java +++ b/src/main/java/com/umc5th/muffler/entity/Goal.java @@ -72,6 +72,10 @@ public void updateTitle(String title) { this.title = title; } + public void updateIcon(String icon) { + this.icon = icon; + } + public Boolean isPossibleToAlarm(Long sum, Long addition) { return sum <= totalBudget && totalBudget < sum + addition; } From 5032b6dc7243da01f7efcac193d87509dbdacd5a Mon Sep 17 00:00:00 2001 From: hajungIm Date: Fri, 29 Mar 2024 22:02:50 +0900 Subject: [PATCH 11/24] =?UTF-8?q?refactor:=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=84=B1=EB=8A=A5=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to: #4 --- .../goal/repository/GoalJdbcRepository.java | 60 +++++++++++++++++++ .../goal/repository/GoalRepository.java | 3 + .../goal/service/GoalCreateService.java | 28 +++++---- .../member/repository/MemberRepository.java | 2 + .../umc5th/muffler/entity/CategoryGoal.java | 3 +- .../com/umc5th/muffler/entity/DailyPlan.java | 3 +- .../com/umc5th/muffler/entity/Member.java | 3 +- 7 files changed, 87 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/umc5th/muffler/domain/goal/repository/GoalJdbcRepository.java diff --git a/src/main/java/com/umc5th/muffler/domain/goal/repository/GoalJdbcRepository.java b/src/main/java/com/umc5th/muffler/domain/goal/repository/GoalJdbcRepository.java new file mode 100644 index 00000000..c5f7730b --- /dev/null +++ b/src/main/java/com/umc5th/muffler/domain/goal/repository/GoalJdbcRepository.java @@ -0,0 +1,60 @@ +package com.umc5th.muffler.domain.goal.repository; + +import com.umc5th.muffler.entity.CategoryGoal; +import com.umc5th.muffler.entity.DailyPlan; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class GoalJdbcRepository { + + private final JdbcTemplate jdbcTemplate; + + public void batchInsertCategoryGoals(List categoryGoals) { + String sql = "INSERT INTO category_goal (budget, category_id, goal_id) VALUES (?, ?, ?)"; + + jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + CategoryGoal categoryGoal = categoryGoals.get(i); + ps.setLong(1, categoryGoal.getBudget()); + ps.setLong(2, categoryGoal.getCategory().getId()); + ps.setLong(3, categoryGoal.getGoal().getId()); + } + + @Override + public int getBatchSize() { + return categoryGoals.size(); + } + }); + } + + public void batchInsertDailyPlans(List dailyPlans) { + String sql = "INSERT INTO daily_plan (date, budget, is_zero_day, total_cost, goal_id) VALUES (?, ?, ?, ?, ?)"; + + jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + DailyPlan dailyPlan = dailyPlans.get(i); + ps.setDate(1, Date.valueOf(dailyPlan.getDate())); + ps.setLong(2, dailyPlan.getBudget()); + ps.setBoolean(3, dailyPlan.getIsZeroDay()); + ps.setLong(4, dailyPlan.getTotalCost()); + ps.setLong(5, dailyPlan.getGoal().getId()); + } + + @Override + public int getBatchSize() { + return dailyPlans.size(); + } + }); + } + +} diff --git a/src/main/java/com/umc5th/muffler/domain/goal/repository/GoalRepository.java b/src/main/java/com/umc5th/muffler/domain/goal/repository/GoalRepository.java index 18193aa8..de55ce0c 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/repository/GoalRepository.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/repository/GoalRepository.java @@ -17,4 +17,7 @@ public interface GoalRepository extends JpaRepository, GoalRepositor @Query("SELECT goal from Goal goal join fetch goal.dailyPlans where :date BETWEEN goal.startDate and goal.endDate AND goal.member.id = :memberId") Optional findByDateBetweenAndDailyPlans(LocalDate date, String memberId); + + @Query("SELECT g FROM Goal g JOIN FETCH g.dailyPlans WHERE g.id = :goalId") + Optional findByIdAndFetchDailyPlans(Long goalId); } diff --git a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java index 568b0f7c..9cd916a2 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java @@ -1,12 +1,14 @@ package com.umc5th.muffler.domain.goal.service; import static com.umc5th.muffler.global.response.code.ErrorCode.CATEGORY_NOT_FOUND; +import static com.umc5th.muffler.global.response.code.ErrorCode.GOAL_NOT_FOUND; import static com.umc5th.muffler.global.response.code.ErrorCode.INVALID_GOAL_INPUT; import static com.umc5th.muffler.global.response.code.ErrorCode.MEMBER_NOT_FOUND; import com.umc5th.muffler.domain.category.repository.CategoryRepository; import com.umc5th.muffler.domain.goal.dto.CategoryGoalRequest; import com.umc5th.muffler.domain.goal.dto.GoalCreateRequest; +import com.umc5th.muffler.domain.goal.repository.GoalJdbcRepository; import com.umc5th.muffler.domain.goal.repository.GoalRepository; import com.umc5th.muffler.domain.member.repository.MemberRepository; import com.umc5th.muffler.entity.Category; @@ -30,26 +32,28 @@ @Service @RequiredArgsConstructor +@Transactional public class GoalCreateService { private final GoalRepository goalRepository; + private final GoalJdbcRepository goalJdbcRepository; private final MemberRepository memberRepository; private final CategoryRepository categoryRepository; - @Transactional public void create(GoalCreateRequest request, String memberId) { - Member member = memberRepository.findById(memberId) + Member member = memberRepository.findByIdAndFetchGoals(memberId) .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); validateGoalInput(request, member); - List categoryGoals = createCategoryGoals(request.getCategoryGoals()); - List dailyPlans = createDailyPlans(request.getStartDate(), request.getDailyBudgets()); - Goal goal = Goal.of(request.getStartDate(), request.getEndDate(), request.getTitle(), request.getIcon(), request.getTotalBudget(), member); - goal.setCategoryGoals(categoryGoals); - goal.setDailyPlans(dailyPlans); - Goal savedGoal = goalRepository.save(goal); + + List categoryGoals = createCategoryGoals(savedGoal, request.getCategoryGoals()); + List dailyPlans = createDailyPlans(savedGoal, request.getStartDate(), request.getDailyBudgets()); + + goalJdbcRepository.batchInsertCategoryGoals(categoryGoals); + goalJdbcRepository.batchInsertDailyPlans(dailyPlans); + member.addGoal(savedGoal); } @@ -59,20 +63,20 @@ private void validateGoalInput(GoalCreateRequest request, Member member) { validateDailyPlans(request.getStartDate(), request.getEndDate(), request.getDailyBudgets(), request.getTotalBudget()); } - private List createCategoryGoals(List categoryGoals) { + private List createCategoryGoals(Goal goal, List categoryGoals) { List result = new ArrayList<>(); for (CategoryGoalRequest categoryGoal : categoryGoals) { Category category = categoryRepository.findById(categoryGoal.getCategoryId()) .orElseThrow(() -> new CategoryException(CATEGORY_NOT_FOUND)); - result.add(CategoryGoal.of(category, categoryGoal.getCategoryBudget())); + result.add(CategoryGoal.of(categoryGoal.getCategoryBudget(), category, goal)); } return result; } - private List createDailyPlans(LocalDate startDate, List dailyBudgets) { + private List createDailyPlans(Goal goal, LocalDate startDate, List dailyBudgets) { return IntStream.range(0, dailyBudgets.size()) - .mapToObj(i -> DailyPlan.of(startDate.plusDays(i), dailyBudgets.get(i))) + .mapToObj(i -> DailyPlan.of(startDate.plusDays(i), dailyBudgets.get(i), goal)) .collect(Collectors.toList()); } diff --git a/src/main/java/com/umc5th/muffler/domain/member/repository/MemberRepository.java b/src/main/java/com/umc5th/muffler/domain/member/repository/MemberRepository.java index 7487bfb2..c84d0a61 100644 --- a/src/main/java/com/umc5th/muffler/domain/member/repository/MemberRepository.java +++ b/src/main/java/com/umc5th/muffler/domain/member/repository/MemberRepository.java @@ -12,4 +12,6 @@ public interface MemberRepository extends JpaRepository, MemberR Optional findByRefreshToken(String refreshToken); @Query("select m from Member m join fetch m.memberAlarm where m.id = :memberId") Optional findMemberFetchAlarm(@Param("memberId") String memberId); + @Query("SELECT m FROM Member m LEFT JOIN FETCH m.goals WHERE m.id = :memberId") + Optional findByIdAndFetchGoals(String memberId); } diff --git a/src/main/java/com/umc5th/muffler/entity/CategoryGoal.java b/src/main/java/com/umc5th/muffler/entity/CategoryGoal.java index b5a6545d..480d17d8 100644 --- a/src/main/java/com/umc5th/muffler/entity/CategoryGoal.java +++ b/src/main/java/com/umc5th/muffler/entity/CategoryGoal.java @@ -38,10 +38,11 @@ public class CategoryGoal extends BaseTimeEntity { @JoinColumn(name = "goal_id") private Goal goal; - public static CategoryGoal of(Category category, Long budget) { + public static CategoryGoal of(Long budget, Category category, Goal goal) { return CategoryGoal.builder() .budget(budget) .category(category) + .goal(goal) .build(); } public void setGoal(Goal goal) { diff --git a/src/main/java/com/umc5th/muffler/entity/DailyPlan.java b/src/main/java/com/umc5th/muffler/entity/DailyPlan.java index 7740dc3b..d1b8e827 100644 --- a/src/main/java/com/umc5th/muffler/entity/DailyPlan.java +++ b/src/main/java/com/umc5th/muffler/entity/DailyPlan.java @@ -57,10 +57,11 @@ public class DailyPlan extends BaseTimeEntity { @Column(length = 1024) private String rateMemo; - public static DailyPlan of(LocalDate date, Long budget) { + public static DailyPlan of(LocalDate date, Long budget, Goal goal) { return DailyPlan.builder() .date(date) .budget(budget) + .goal(goal) .build(); } diff --git a/src/main/java/com/umc5th/muffler/entity/Member.java b/src/main/java/com/umc5th/muffler/entity/Member.java index 768c8b4e..d78c7f06 100644 --- a/src/main/java/com/umc5th/muffler/entity/Member.java +++ b/src/main/java/com/umc5th/muffler/entity/Member.java @@ -59,8 +59,9 @@ public class Member extends BaseTimeEntity implements Persistable, UserD @Enumerated(EnumType.STRING) private Status status = ACTIVE; + @Builder.Default @OneToMany(mappedBy = "member") - private List goals; + private List goals = new ArrayList<>(); @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) @Builder.Default From c62e487c3b1e61ee92f89c8dd725694e6bd7bdc4 Mon Sep 17 00:00:00 2001 From: hajungIm Date: Fri, 29 Mar 2024 22:47:20 +0900 Subject: [PATCH 12/24] =?UTF-8?q?refactor:=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EC=84=B1=EB=8A=A5=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to: #4 --- .../repository/DailyPlanRepository.java | 10 ++++++++ .../repository/CategoryGoalRepository.java | 12 ++++++++++ .../goal/repository/GoalRepository.java | 13 +++++++--- .../domain/goal/service/GoalService.java | 24 ++++++++++--------- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/umc5th/muffler/domain/dailyplan/repository/DailyPlanRepository.java b/src/main/java/com/umc5th/muffler/domain/dailyplan/repository/DailyPlanRepository.java index fd5d3571..ac3f2fda 100644 --- a/src/main/java/com/umc5th/muffler/domain/dailyplan/repository/DailyPlanRepository.java +++ b/src/main/java/com/umc5th/muffler/domain/dailyplan/repository/DailyPlanRepository.java @@ -5,8 +5,10 @@ import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; @Repository public interface DailyPlanRepository extends JpaRepository, DailyPlanRepositoryCustom { @@ -23,4 +25,12 @@ public interface DailyPlanRepository extends JpaRepository, Dai + "WHERE dailyPlan.date = :date " + "AND dailyPlan.goal.member.id = :memberId") Optional findDailyPlanWithGoalByDateAndMember(String memberId, LocalDate date); + + @Query("SELECT dp.id FROM DailyPlan dp WHERE dp.goal.id = :goalId") + List findByGoalId(Long goalId); + + @Transactional + @Modifying + @Query("DELETE FROM DailyPlan dp WHERE dp.id in :ids") + void deleteByIds(List ids); } diff --git a/src/main/java/com/umc5th/muffler/domain/goal/repository/CategoryGoalRepository.java b/src/main/java/com/umc5th/muffler/domain/goal/repository/CategoryGoalRepository.java index 7e70142c..e708824e 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/repository/CategoryGoalRepository.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/repository/CategoryGoalRepository.java @@ -1,11 +1,23 @@ package com.umc5th.muffler.domain.goal.repository; import com.umc5th.muffler.entity.CategoryGoal; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; @Repository public interface CategoryGoalRepository extends JpaRepository { Optional findByGoalIdAndCategoryId(Long goalId, Long categoryId); + + @Query("SELECT cg.id FROM CategoryGoal cg WHERE cg.goal.id = :goalId") + List findByGoalId(Long goalId); + + @Transactional + @Modifying + @Query("DELETE FROM CategoryGoal cg WHERE cg.id in :ids") + void deleteByIds(List ids); } diff --git a/src/main/java/com/umc5th/muffler/domain/goal/repository/GoalRepository.java b/src/main/java/com/umc5th/muffler/domain/goal/repository/GoalRepository.java index de55ce0c..4a5f7b73 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/repository/GoalRepository.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/repository/GoalRepository.java @@ -1,13 +1,13 @@ package com.umc5th.muffler.domain.goal.repository; import com.umc5th.muffler.entity.Goal; +import java.time.LocalDate; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.time.LocalDate; -import java.util.Optional; - public interface GoalRepository extends JpaRepository, GoalRepositoryCustom { @Query("SELECT goal from Goal goal where :date BETWEEN goal.startDate and goal.endDate AND goal.member.id = :memberId") Optional findByDateBetween(@Param("date")LocalDate date, @Param("memberId")String memberId); @@ -20,4 +20,11 @@ public interface GoalRepository extends JpaRepository, GoalRepositor @Query("SELECT g FROM Goal g JOIN FETCH g.dailyPlans WHERE g.id = :goalId") Optional findByIdAndFetchDailyPlans(Long goalId); + + @Query("SELECT g FROM Goal g WHERE g.id = :goalId AND g.member.id = :memberId") + Optional findByIdAndMemberId(Long goalId, String memberId); + + @Modifying + @Query("DELETE FROM Goal g WHERE g.id = :goalId AND g.member.id = :memberId") + void deleteByIdAndMemberId(Long goalId, String memberId); } diff --git a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java index e67d8728..3fe586fc 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java @@ -1,9 +1,9 @@ package com.umc5th.muffler.domain.goal.service; import static com.umc5th.muffler.global.response.code.ErrorCode.GOAL_NOT_FOUND; -import static com.umc5th.muffler.global.response.code.ErrorCode.INVALID_PERMISSION; import static com.umc5th.muffler.global.response.code.ErrorCode.MEMBER_NOT_FOUND; +import com.umc5th.muffler.domain.dailyplan.repository.DailyPlanRepository; import com.umc5th.muffler.domain.expense.repository.ExpenseRepository; import com.umc5th.muffler.domain.goal.dto.GoalConverter; import com.umc5th.muffler.domain.goal.dto.GoalGetResponse; @@ -11,6 +11,7 @@ import com.umc5th.muffler.domain.goal.dto.GoalListResponse; import com.umc5th.muffler.domain.goal.dto.GoalPreviewResponse; import com.umc5th.muffler.domain.goal.dto.GoalReportResponse; +import com.umc5th.muffler.domain.goal.repository.CategoryGoalRepository; import com.umc5th.muffler.domain.goal.repository.GoalRepository; import com.umc5th.muffler.domain.member.repository.MemberRepository; import com.umc5th.muffler.entity.CategoryGoal; @@ -18,7 +19,6 @@ import com.umc5th.muffler.entity.Expense; import com.umc5th.muffler.entity.Goal; import com.umc5th.muffler.entity.Member; -import com.umc5th.muffler.global.response.exception.CommonException; import com.umc5th.muffler.global.response.exception.GoalException; import com.umc5th.muffler.global.response.exception.MemberException; import com.umc5th.muffler.global.util.CalcUtils; @@ -28,7 +28,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; @@ -44,11 +43,13 @@ public class GoalService { private final DateTimeProvider dateTimeProvider; private final MemberRepository memberRepository; private final GoalRepository goalRepository; + private final CategoryGoalRepository categoryGoalRepository; + private final DailyPlanRepository dailyPlanRepository; private final ExpenseRepository expenseRepository; @Transactional(readOnly = true) public List getGoals(String memberId) { - Member member = memberRepository.findById(memberId) + Member member = memberRepository.findByIdAndFetchGoals(memberId) .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); return member.getGoals(); } @@ -73,7 +74,7 @@ public void restore(String memberId, LocalDate startDate, LocalDate endDate, boo public void updateTitleAndIcon(Long goalId, String title, String icon, String memberId) { Member member = memberRepository.findById(memberId) .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); - Goal goal = goalRepository.findById(goalId) + Goal goal = goalRepository.findByIdAndMemberId(goalId, memberId) .orElseThrow(() -> new GoalException(GOAL_NOT_FOUND)); goal.updateTitle(title); goal.updateIcon(icon); @@ -83,15 +84,16 @@ public void updateTitleAndIcon(Long goalId, String title, String icon, String me public void delete(Long goalId, String memberId) { Member member = memberRepository.findById(memberId) .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); - Goal goal = goalRepository.findById(goalId) + Goal goal = goalRepository.findByIdAndMemberId(goalId, memberId) .orElseThrow(() -> new GoalException(GOAL_NOT_FOUND)); - if (!Objects.equals(member.getId(), goal.getMember().getId())) { - throw new CommonException(INVALID_PERMISSION); - } + List categoryGoalIds = categoryGoalRepository.findByGoalId(goalId); + categoryGoalRepository.deleteByIds(categoryGoalIds); + + List dailyPlanIds = dailyPlanRepository.findByGoalId(goalId); + dailyPlanRepository.deleteByIds(dailyPlanIds); - goalRepository.delete(goal); - member.removeGoal(goal); + goalRepository.deleteByIdAndMemberId(goalId, memberId); } public GoalReportResponse getReport(Long goalId, String memberId){ From dc2732d3f2804f58360ffbac878c1ef2c3e0d5bc Mon Sep 17 00:00:00 2001 From: hajungIm Date: Fri, 29 Mar 2024 23:12:52 +0900 Subject: [PATCH 13/24] =?UTF-8?q?feat:=20=EC=9D=BC=EC=9D=BC=EB=AA=A9?= =?UTF-8?q?=ED=91=9C=EA=B8=88=EC=95=A1=20=EC=88=98=EC=A0=95=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to: #4 --- .../repository/DailyPlanJdbcRepository.java | 36 +++++++++++++++++++ .../goal/controller/GoalController.java | 10 +++--- .../domain/goal/dto/DailyBudgetsRequest.java | 15 ++++++++ .../goal/service/GoalCreateService.java | 13 +++++++ 4 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/umc5th/muffler/domain/dailyplan/repository/DailyPlanJdbcRepository.java create mode 100644 src/main/java/com/umc5th/muffler/domain/goal/dto/DailyBudgetsRequest.java diff --git a/src/main/java/com/umc5th/muffler/domain/dailyplan/repository/DailyPlanJdbcRepository.java b/src/main/java/com/umc5th/muffler/domain/dailyplan/repository/DailyPlanJdbcRepository.java new file mode 100644 index 00000000..5e11b68b --- /dev/null +++ b/src/main/java/com/umc5th/muffler/domain/dailyplan/repository/DailyPlanJdbcRepository.java @@ -0,0 +1,36 @@ +package com.umc5th.muffler.domain.dailyplan.repository; + +import com.umc5th.muffler.entity.DailyPlan; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class DailyPlanJdbcRepository { + + private final JdbcTemplate jdbcTemplate; + + public void batchUpdateBudget(List dailyPlans, List dailyBudgets) { + String sql = "UPDATE daily_plan SET budget = ? WHERE id = ?"; + + jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + DailyPlan dailyPlan = dailyPlans.get(i); + Long budget = dailyBudgets.get(i); + ps.setLong(1, budget); + ps.setLong(2, dailyPlan.getId()); + } + + @Override + public int getBatchSize() { + return dailyPlans.size(); + } + }); + } +} diff --git a/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java b/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java index 8b18c5fa..3845c532 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java @@ -1,5 +1,6 @@ package com.umc5th.muffler.domain.goal.controller; +import com.umc5th.muffler.domain.goal.dto.DailyBudgetsRequest; import com.umc5th.muffler.domain.goal.dto.GoalConverter; import com.umc5th.muffler.domain.goal.dto.GoalCreateRequest; import com.umc5th.muffler.domain.goal.dto.GoalGetResponse; @@ -8,7 +9,7 @@ import com.umc5th.muffler.domain.goal.dto.GoalPreviewResponse; import com.umc5th.muffler.domain.goal.dto.GoalPreviousResponse; import com.umc5th.muffler.domain.goal.dto.GoalReportResponse; -import com.umc5th.muffler.domain.goal.dto.GoalTitleRequest; +import com.umc5th.muffler.domain.goal.dto.GoalUpdateRequest; import com.umc5th.muffler.domain.goal.service.GoalCreateService; import com.umc5th.muffler.domain.goal.service.GoalService; import com.umc5th.muffler.entity.Goal; @@ -75,13 +76,14 @@ public Response restore(Authentication authentication, } @PatchMapping("/{goalId}") - public Response updateTitle(@PathVariable Long goalId, @RequestBody @Valid GoalTitleRequest request, Authentication authentication) { - goalService.updateTitle(goalId, request.getTitle(), authentication.getName()); - public Response updateTitleAndIcon(@PathVariable Long goalId, @RequestBody @Valid GoalTitleRequest request, Authentication authentication) { + public Response updateTitleAndIcon(@PathVariable Long goalId, @RequestBody @Valid GoalUpdateRequest request, Authentication authentication) { goalService.updateTitleAndIcon(goalId, request.getTitle(), request.getIcon(), authentication.getName()); return Response.success(); } + @PatchMapping("/{goalId}/daily-budgets") + public Response updateDailyBudgets(@PathVariable Long goalId, @RequestBody @Valid DailyBudgetsRequest request, Authentication authentication) { + goalCreateService.updateDailyBudgets(authentication.getName(), goalId, request.getDailyBudgets()); return Response.success(); } diff --git a/src/main/java/com/umc5th/muffler/domain/goal/dto/DailyBudgetsRequest.java b/src/main/java/com/umc5th/muffler/domain/goal/dto/DailyBudgetsRequest.java new file mode 100644 index 00000000..4c2ea6a0 --- /dev/null +++ b/src/main/java/com/umc5th/muffler/domain/goal/dto/DailyBudgetsRequest.java @@ -0,0 +1,15 @@ +package com.umc5th.muffler.domain.goal.dto; + +import java.util.List; +import javax.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class DailyBudgetsRequest { + @NotNull + private List dailyBudgets; +} diff --git a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java index 9cd916a2..1cf80f8a 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java @@ -6,6 +6,7 @@ import static com.umc5th.muffler.global.response.code.ErrorCode.MEMBER_NOT_FOUND; import com.umc5th.muffler.domain.category.repository.CategoryRepository; +import com.umc5th.muffler.domain.dailyplan.repository.DailyPlanJdbcRepository; import com.umc5th.muffler.domain.goal.dto.CategoryGoalRequest; import com.umc5th.muffler.domain.goal.dto.GoalCreateRequest; import com.umc5th.muffler.domain.goal.repository.GoalJdbcRepository; @@ -37,6 +38,7 @@ public class GoalCreateService { private final GoalRepository goalRepository; private final GoalJdbcRepository goalJdbcRepository; + private final DailyPlanJdbcRepository dailyPlanJdbcRepository; private final MemberRepository memberRepository; private final CategoryRepository categoryRepository; @@ -57,6 +59,17 @@ public void create(GoalCreateRequest request, String memberId) { member.addGoal(savedGoal); } + public void updateDailyBudgets(String memberId, Long goalId, List dailyBudgets) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); + Goal goal = goalRepository.findByIdAndFetchDailyPlans(goalId) + .orElseThrow(() -> new GoalException(GOAL_NOT_FOUND)); + validateDailyPlans(goal.getStartDate(), goal.getEndDate(), dailyBudgets, goal.getTotalBudget()); + + List dailyPlans = goal.getDailyPlans(); + dailyPlanJdbcRepository.batchUpdateBudget(dailyPlans, dailyBudgets); + } + private void validateGoalInput(GoalCreateRequest request, Member member) { validateGoalPeriod(member.getGoals(), request.getStartDate(), request.getEndDate()); validateCategoryGoals(request.getCategoryGoals(), request.getTotalBudget()); From 0aba45b8417a3c48695b9e4cff39516cce9bd660 Mon Sep 17 00:00:00 2001 From: hajungIm Date: Fri, 29 Mar 2024 23:45:39 +0900 Subject: [PATCH 14/24] =?UTF-8?q?fix:=20=EB=AA=A9=ED=91=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to: #4 --- .../goal/service/GoalCreateServiceTest.java | 40 +++++++++++-------- .../domain/goal/service/GoalServiceTest.java | 21 +++++----- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/test/java/com/umc5th/muffler/domain/goal/service/GoalCreateServiceTest.java b/src/test/java/com/umc5th/muffler/domain/goal/service/GoalCreateServiceTest.java index e6b312a3..1a73af90 100644 --- a/src/test/java/com/umc5th/muffler/domain/goal/service/GoalCreateServiceTest.java +++ b/src/test/java/com/umc5th/muffler/domain/goal/service/GoalCreateServiceTest.java @@ -1,7 +1,17 @@ package com.umc5th.muffler.domain.goal.service; +import static com.umc5th.muffler.global.response.code.ErrorCode.CATEGORY_NOT_FOUND; +import static com.umc5th.muffler.global.response.code.ErrorCode.INVALID_GOAL_INPUT; +import static com.umc5th.muffler.global.response.code.ErrorCode.MEMBER_NOT_FOUND; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.umc5th.muffler.domain.category.repository.CategoryRepository; import com.umc5th.muffler.domain.goal.dto.GoalCreateRequest; +import com.umc5th.muffler.domain.goal.repository.GoalJdbcRepository; import com.umc5th.muffler.domain.goal.repository.GoalRepository; import com.umc5th.muffler.domain.member.repository.MemberRepository; import com.umc5th.muffler.entity.Category; @@ -12,20 +22,14 @@ import com.umc5th.muffler.global.response.exception.CategoryException; import com.umc5th.muffler.global.response.exception.GoalException; import com.umc5th.muffler.global.response.exception.MemberException; +import java.time.LocalDate; +import java.util.Optional; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; -import java.time.LocalDate; -import java.util.Optional; - -import static com.umc5th.muffler.global.response.code.ErrorCode.*; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - @SpringBootTest class GoalCreateServiceTest { @@ -37,6 +41,8 @@ class GoalCreateServiceTest { private CategoryRepository categoryRepository; @MockBean private GoalRepository goalRepository; + @MockBean + private GoalJdbcRepository goalJdbcRepository; @Test @@ -45,7 +51,7 @@ class GoalCreateServiceTest { Member member = MemberFixture.create(); Goal mockGoal = mock(Goal.class); - when(memberRepository.findById(member.getId())).thenReturn(Optional.of(member)); + when(memberRepository.findByIdAndFetchGoals(member.getId())).thenReturn(Optional.of(member)); when(categoryRepository.findById(any())).thenReturn(Optional.of(mock(Category.class))); when(goalRepository.save(any())).thenReturn(mockGoal); @@ -72,7 +78,7 @@ class GoalCreateServiceTest { String memberId = "1"; GoalCreateRequest request = GoalCreateRequestFixture.create(LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 1)); - when(memberRepository.findById(memberId)).thenReturn(Optional.of(mock(Member.class))); + when(memberRepository.findByIdAndFetchGoals(memberId)).thenReturn(Optional.of(mock(Member.class))); assertThatThrownBy(() -> goalCreateService.create(request, memberId)) .isInstanceOf(GoalException.class) @@ -85,7 +91,7 @@ class GoalCreateServiceTest { String memberId = "1"; GoalCreateRequest request = GoalCreateRequestFixture.create(LocalDate.of(2024, 1, 2), LocalDate.of(2024, 1, 1)); - when(memberRepository.findById(memberId)).thenReturn(Optional.of(mock(Member.class))); + when(memberRepository.findByIdAndFetchGoals(memberId)).thenReturn(Optional.of(mock(Member.class))); assertThatThrownBy(() -> goalCreateService.create(request, memberId)) .isInstanceOf(GoalException.class) @@ -98,7 +104,7 @@ class GoalCreateServiceTest { GoalCreateRequest request = GoalCreateRequestFixture.create(LocalDate.of(2024, 1, 2), LocalDate.of(2024, 1, 3)); Member member = MemberFixture.create(); - when(memberRepository.findById(member.getId())).thenReturn(Optional.of(member)); + when(memberRepository.findByIdAndFetchGoals(member.getId())).thenReturn(Optional.of(member)); assertThatThrownBy(() -> goalCreateService.create(request, member.getId())) .isInstanceOf(GoalException.class) @@ -111,7 +117,7 @@ class GoalCreateServiceTest { GoalCreateRequest request = GoalCreateRequestFixture.createDuplicatedCategoryGoals(); Member member = MemberFixture.create(); - when(memberRepository.findById(member.getId())).thenReturn(Optional.of(member)); + when(memberRepository.findByIdAndFetchGoals(member.getId())).thenReturn(Optional.of(member)); assertThatThrownBy(() -> goalCreateService.create(request, member.getId())) .isInstanceOf(GoalException.class) @@ -124,7 +130,7 @@ class GoalCreateServiceTest { GoalCreateRequest request = GoalCreateRequestFixture.createInvalidCategoryBudget(); Member member = MemberFixture.create(); - when(memberRepository.findById(member.getId())).thenReturn(Optional.of(member)); + when(memberRepository.findByIdAndFetchGoals(member.getId())).thenReturn(Optional.of(member)); assertThatThrownBy(() -> goalCreateService.create(request, member.getId())) .isInstanceOf(GoalException.class) @@ -137,7 +143,7 @@ class GoalCreateServiceTest { GoalCreateRequest request = GoalCreateRequestFixture.createInvalidDailyPlanPeriod(); Member member = MemberFixture.create(); - when(memberRepository.findById(member.getId())).thenReturn(Optional.of(mock(Member.class))); + when(memberRepository.findByIdAndFetchGoals(member.getId())).thenReturn(Optional.of(mock(Member.class))); assertThatThrownBy(() -> goalCreateService.create(request, member.getId())) .isInstanceOf(GoalException.class) @@ -150,7 +156,7 @@ class GoalCreateServiceTest { GoalCreateRequest request = GoalCreateRequestFixture.createInvalidDailyBudgetSum(); Member member = MemberFixture.create(); - when(memberRepository.findById(member.getId())).thenReturn(Optional.of(mock(Member.class))); + when(memberRepository.findByIdAndFetchGoals(member.getId())).thenReturn(Optional.of(mock(Member.class))); assertThatThrownBy(() -> goalCreateService.create(request, member.getId())) .isInstanceOf(GoalException.class) @@ -163,7 +169,7 @@ class GoalCreateServiceTest { GoalCreateRequest request = GoalCreateRequestFixture.create(); Member member = MemberFixture.create(); - when(memberRepository.findById(member.getId())).thenReturn(Optional.of(mock(Member.class))); + when(memberRepository.findByIdAndFetchGoals(member.getId())).thenReturn(Optional.of(mock(Member.class))); when(categoryRepository.findById(any())).thenReturn(Optional.empty()); assertThatThrownBy(() -> goalCreateService.create(request, member.getId())) diff --git a/src/test/java/com/umc5th/muffler/domain/goal/service/GoalServiceTest.java b/src/test/java/com/umc5th/muffler/domain/goal/service/GoalServiceTest.java index d7df4761..9286f8ea 100644 --- a/src/test/java/com/umc5th/muffler/domain/goal/service/GoalServiceTest.java +++ b/src/test/java/com/umc5th/muffler/domain/goal/service/GoalServiceTest.java @@ -1,7 +1,6 @@ package com.umc5th.muffler.domain.goal.service; import static com.umc5th.muffler.global.response.code.ErrorCode.GOAL_NOT_FOUND; -import static com.umc5th.muffler.global.response.code.ErrorCode.INVALID_PERMISSION; import static com.umc5th.muffler.global.response.code.ErrorCode.MEMBER_NOT_FOUND; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -13,10 +12,12 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.umc5th.muffler.domain.dailyplan.repository.DailyPlanRepository; import com.umc5th.muffler.domain.goal.dto.GoalGetResponse; import com.umc5th.muffler.domain.goal.dto.GoalInfo; import com.umc5th.muffler.domain.goal.dto.GoalPreviewResponse; import com.umc5th.muffler.domain.goal.dto.GoalReportResponse; +import com.umc5th.muffler.domain.goal.repository.CategoryGoalRepository; import com.umc5th.muffler.domain.goal.repository.GoalRepository; import com.umc5th.muffler.domain.member.repository.MemberRepository; import com.umc5th.muffler.entity.CategoryGoal; @@ -27,7 +28,6 @@ import com.umc5th.muffler.fixture.DailyPlanFixture; import com.umc5th.muffler.fixture.GoalFixture; import com.umc5th.muffler.fixture.MemberFixture; -import com.umc5th.muffler.global.response.exception.CommonException; import com.umc5th.muffler.global.response.exception.GoalException; import com.umc5th.muffler.global.response.exception.MemberException; import com.umc5th.muffler.global.util.DateTimeProvider; @@ -53,16 +53,20 @@ class GoalServiceTest { @MockBean private GoalRepository goalRepository; @MockBean + private CategoryGoalRepository categoryGoalRepository; + @MockBean + private DailyPlanRepository dailyPlanRepository; + @MockBean private DateTimeProvider dateTimeProvider; @Test void 전체_목표조회가_성공한경우() { String memberId = "1"; - when(memberRepository.findById(memberId)).thenReturn(Optional.of(mock(Member.class))); + when(memberRepository.findByIdAndFetchGoals(memberId)).thenReturn(Optional.of(mock(Member.class))); assertThatCode(() -> goalService.getGoals(memberId)).doesNotThrowAnyException(); - verify(memberRepository).findById(memberId); + verify(memberRepository).findByIdAndFetchGoals(memberId); } @Test @@ -87,12 +91,11 @@ class GoalServiceTest { when(mockGoal.getMember()).thenReturn(mockMember); when(memberRepository.findById(memberId)).thenReturn(Optional.of(mockMember)); - when(goalRepository.findById(goalId)).thenReturn(Optional.of(mockGoal)); + when(goalRepository.findByIdAndMemberId(goalId, memberId)).thenReturn(Optional.of(mockGoal)); goalService.delete(goalId, memberId); - verify(goalRepository).delete(mockGoal); - verify(mockMember).removeGoal(mockGoal); + verify(goalRepository).deleteByIdAndMemberId(goalId, memberId); } @Test @@ -135,8 +138,8 @@ class GoalServiceTest { when(goalRepository.findById(goalId)).thenReturn(Optional.of(mockGoal)); assertThatThrownBy(() -> goalService.delete(goalId, memberId)) - .isInstanceOf(CommonException.class) - .hasFieldOrPropertyWithValue("errorCode", INVALID_PERMISSION); + .isInstanceOf(GoalException.class) + .hasFieldOrPropertyWithValue("errorCode", GOAL_NOT_FOUND); } @Test From de812f25df4e091795b019d3bebbee94157883a4 Mon Sep 17 00:00:00 2001 From: shortboy7 Date: Sat, 30 Mar 2024 00:56:16 +0900 Subject: [PATCH 15/24] =?UTF-8?q?feature=20:=20goal=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=EC=97=90=20restore=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related-to : #45 --- .../goal/controller/GoalController.java | 3 --- .../goal/service/GoalCreateService.java | 22 +++++++++++++++++++ .../domain/goal/service/GoalService.java | 7 ------ 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java b/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java index 1086b306..6aefc859 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java @@ -45,9 +45,6 @@ public class GoalController { @PostMapping public Response create(@RequestBody @Valid GoalCreateRequest request, Authentication authentication) { goalCreateService.create(request, authentication.getName()); - if (!request.getRestore()) { - goalService.deleteLeftExpense(authentication.getName(), request.getStartDate(), request.getEndDate()); - } return Response.success(); } diff --git a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java index 568b0f7c..f9def870 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java @@ -5,6 +5,7 @@ import static com.umc5th.muffler.global.response.code.ErrorCode.MEMBER_NOT_FOUND; import com.umc5th.muffler.domain.category.repository.CategoryRepository; +import com.umc5th.muffler.domain.expense.repository.ExpenseRepository; import com.umc5th.muffler.domain.goal.dto.CategoryGoalRequest; import com.umc5th.muffler.domain.goal.dto.GoalCreateRequest; import com.umc5th.muffler.domain.goal.repository.GoalRepository; @@ -22,6 +23,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import java.util.stream.IntStream; import lombok.RequiredArgsConstructor; @@ -35,6 +37,7 @@ public class GoalCreateService { private final GoalRepository goalRepository; private final MemberRepository memberRepository; private final CategoryRepository categoryRepository; + private final ExpenseRepository expenseRepository; @Transactional public void create(GoalCreateRequest request, String memberId) { @@ -44,6 +47,7 @@ public void create(GoalCreateRequest request, String memberId) { List categoryGoals = createCategoryGoals(request.getCategoryGoals()); List dailyPlans = createDailyPlans(request.getStartDate(), request.getDailyBudgets()); + handleRestore(request, memberId, dailyPlans); Goal goal = Goal.of(request.getStartDate(), request.getEndDate(), request.getTitle(), request.getIcon(), request.getTotalBudget(), member); goal.setCategoryGoals(categoryGoals); @@ -53,6 +57,24 @@ public void create(GoalCreateRequest request, String memberId) { member.addGoal(savedGoal); } + private void handleRestore(GoalCreateRequest request, String memberId, List dailyPlans) { + if (request.getRestore()){ + Map costMap = expenseRepository.findTotalCostDate(memberId, request.getStartDate(), + request.getEndDate()); + dailyPlans.forEach(dailyPlan -> { + if (costMap.containsKey(dailyPlan.getDate())) { + Long cost = costMap.get(dailyPlan.getDate()); + dailyPlan.updateTotalCost(cost); + } + }); + } + else { + List expenseIds = expenseRepository.findByMemberIdAndDateRange(memberId, request.getStartDate() + ,request.getEndDate()); + expenseRepository.deleteByIds(expenseIds); + } + } + private void validateGoalInput(GoalCreateRequest request, Member member) { validateGoalPeriod(member.getGoals(), request.getStartDate(), request.getEndDate()); validateCategoryGoals(request.getCategoryGoals(), request.getTotalBudget()); diff --git a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java index ce69cf9c..da8b7615 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java @@ -60,13 +60,6 @@ public boolean checkRestore(String memberId, LocalDate startDate, LocalDate endD return expenseRepository.existsExpense(memberId, startDate, endDate); } - public void deleteLeftExpense(String memberId, LocalDate startDate, LocalDate endDate) { - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); - List expenseIds = expenseRepository.findByMemberIdAndDateRange(memberId, startDate, endDate); - expenseRepository.deleteByIds(expenseIds); - } - public void updateTitle(Long goalId, String title, String memberId) { Member member = memberRepository.findById(memberId) .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); From 0419c32593f89654b52fe1e2a230b3b86aac82e5 Mon Sep 17 00:00:00 2001 From: shortboy7 Date: Sat, 30 Mar 2024 18:16:06 +0900 Subject: [PATCH 16/24] =?UTF-8?q?fix=20:=20=EB=82=A0=EC=A7=9C=EB=B3=84=20?= =?UTF-8?q?=EC=86=8C=EB=B9=84=EA=B8=88=EC=95=A1=EC=9D=98=20=ED=95=A9?= =?UTF-8?q?=EC=9D=84=20=EA=B5=AC=ED=95=B4=EC=98=AC=20=EB=95=8C=20memberId?= =?UTF-8?q?=20=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related-to : #45 --- .../domain/expense/repository/ExpenseRepositoryImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java b/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java index 334585ae..2ab495e8 100644 --- a/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java +++ b/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java @@ -208,7 +208,7 @@ public Map findTotalCostDate(String memberId, LocalDate startDa List tuples = queryFactory .select(expense.date, expense.cost.sum()) .from(expense) - .where(expense.date.between(startDate, endDate)) + .where(expense.date.between(startDate, endDate), expense.member.id.eq(memberId)) .groupBy(expense.date) .fetch(); for (Tuple tuple : tuples) { From 694ff4fcfc254f54c248757b0f039e8c0856362d Mon Sep 17 00:00:00 2001 From: shortboy7 Date: Sat, 30 Mar 2024 18:23:24 +0900 Subject: [PATCH 17/24] =?UTF-8?q?fix=20:=20tuple=20=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EA=B0=92=EC=9D=84=20=EA=B0=80=EC=A0=B8=EC=98=AC=20=EB=95=8C=20?= =?UTF-8?q?null=20=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related-to : #45 --- .../domain/expense/repository/ExpenseRepositoryImpl.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java b/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java index 2ab495e8..0d58446e 100644 --- a/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java +++ b/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java @@ -206,7 +206,7 @@ public Map findTotalCostDate(String memberId, LocalDate startDa Map expenseMap = new HashMap<>(); List tuples = queryFactory - .select(expense.date, expense.cost.sum()) + .select(expense.date, expense.cost.sum().as("totalCost")) .from(expense) .where(expense.date.between(startDate, endDate), expense.member.id.eq(memberId)) .groupBy(expense.date) @@ -214,7 +214,10 @@ public Map findTotalCostDate(String memberId, LocalDate startDa for (Tuple tuple : tuples) { LocalDate date = tuple.get(expense.date); Long sum = tuple.get(expense.cost.sum()); - expenseMap.put(date, sum); + if (date != null) { + if (sum == null) sum = 0L; + expenseMap.put(date, sum); + } } return expenseMap; } From fc80d19aebcf9c427b53fd0a6e9eb648614f6948 Mon Sep 17 00:00:00 2001 From: shortboy7 Date: Sat, 30 Mar 2024 18:24:05 +0900 Subject: [PATCH 18/24] =?UTF-8?q?fix=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EA=B3=A8=20=EC=83=9D=EC=84=B1=20=EC=9A=94=EC=B2=AD=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=EC=97=90=20restore=20=EA=B0=92=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related-to : #45 --- .../com/umc5th/muffler/fixture/GoalCreateRequestFixture.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/com/umc5th/muffler/fixture/GoalCreateRequestFixture.java b/src/test/java/com/umc5th/muffler/fixture/GoalCreateRequestFixture.java index f8fba485..d6cf0853 100644 --- a/src/test/java/com/umc5th/muffler/fixture/GoalCreateRequestFixture.java +++ b/src/test/java/com/umc5th/muffler/fixture/GoalCreateRequestFixture.java @@ -15,6 +15,7 @@ public static GoalCreateRequest create() { .totalBudget(10000L) .categoryGoals(List.of(new CategoryGoalRequest(1L, 1000L))) .dailyBudgets(List.of(5000L, 5000L)) + .restore(true) .build(); } From cb93946c24b3da104461aefaa2b74f9e456f8201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=EC=A7=80=EC=9B=85?= Date: Tue, 2 Apr 2024 01:03:01 +0900 Subject: [PATCH 19/24] =?UTF-8?q?feat:tomcat=20log=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 85b91a2f..b322da02 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -81,6 +81,14 @@ server: charset: UTF-8 enabled: true force: true + tomcat: + basedir: . + access log: + enabled: true + pattern: "%{yyyy-MM-dd HH:mm:ss}t:%s\t%b\t%a\t%{Referer}i\t%{User-Agent}i\t%r\t" + directory: logs + encoding: utf-8 + logging.level: org.hibernate.SQL: debug From 8846c4b57abeb10b1977d8a6c2e2e9c4b44a3c45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=EC=A7=80=EC=9B=85?= Date: Tue, 2 Apr 2024 01:59:25 +0900 Subject: [PATCH 20/24] =?UTF-8?q?feat:tomcat=20log=20=ED=8F=AC=EB=A7=B7=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EC=A0=80=EC=9E=A5=EA=B8=B0?= =?UTF-8?q?=EA=B0=84=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b322da02..309b93e9 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -85,9 +85,10 @@ server: basedir: . access log: enabled: true - pattern: "%{yyyy-MM-dd HH:mm:ss}t:%s\t%b\t%a\t%{Referer}i\t%{User-Agent}i\t%r\t" + pattern: "[%{yyyy-MM-dd HH:mm:ss.SSS}t]%{Referer}i(%a) %r -> [%s/%b/%Dms] | \t%{User-Agent}i" directory: logs encoding: utf-8 + max-days: 60 logging.level: org.hibernate.SQL: debug From 44b5581f536890109c5845bacc70122f5e078a33 Mon Sep 17 00:00:00 2001 From: hajungIm Date: Tue, 2 Apr 2024 17:46:17 +0900 Subject: [PATCH 21/24] =?UTF-8?q?refactor:=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=84=B1=EB=8A=A5=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to: #4 --- .../repository/DailyPlanJdbcRepository.java | 22 +++++++ .../repository/ExpenseRepositoryImpl.java | 3 +- .../domain/goal/dto/GoalCreateRequest.java | 8 ++- ...y.java => CategoryGoalJdbcRepository.java} | 27 +------- .../goal/service/GoalCreateService.java | 64 +++++++++++-------- .../domain/goal/service/GoalService.java | 4 +- .../member/repository/MemberRepository.java | 2 + .../com/umc5th/muffler/entity/Member.java | 23 +++---- 8 files changed, 82 insertions(+), 71 deletions(-) rename src/main/java/com/umc5th/muffler/domain/goal/repository/{GoalJdbcRepository.java => CategoryGoalJdbcRepository.java} (52%) diff --git a/src/main/java/com/umc5th/muffler/domain/dailyplan/repository/DailyPlanJdbcRepository.java b/src/main/java/com/umc5th/muffler/domain/dailyplan/repository/DailyPlanJdbcRepository.java index 5e11b68b..1c148ac2 100644 --- a/src/main/java/com/umc5th/muffler/domain/dailyplan/repository/DailyPlanJdbcRepository.java +++ b/src/main/java/com/umc5th/muffler/domain/dailyplan/repository/DailyPlanJdbcRepository.java @@ -1,6 +1,7 @@ package com.umc5th.muffler.domain.dailyplan.repository; import com.umc5th.muffler.entity.DailyPlan; +import java.sql.Date; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; @@ -15,6 +16,27 @@ public class DailyPlanJdbcRepository { private final JdbcTemplate jdbcTemplate; + public void batchInsert(List dailyPlans) { + String sql = "INSERT INTO daily_plan (date, budget, is_zero_day, total_cost, goal_id) VALUES (?, ?, ?, ?, ?)"; + + jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + DailyPlan dailyPlan = dailyPlans.get(i); + ps.setDate(1, Date.valueOf(dailyPlan.getDate())); + ps.setLong(2, dailyPlan.getBudget()); + ps.setBoolean(3, dailyPlan.getIsZeroDay()); + ps.setLong(4, dailyPlan.getTotalCost()); + ps.setLong(5, dailyPlan.getGoal().getId()); + } + + @Override + public int getBatchSize() { + return dailyPlans.size(); + } + }); + } + public void batchUpdateBudget(List dailyPlans, List dailyBudgets) { String sql = "UPDATE daily_plan SET budget = ? WHERE id = ?"; diff --git a/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java b/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java index 0d58446e..cdf3299d 100644 --- a/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java +++ b/src/main/java/com/umc5th/muffler/domain/expense/repository/ExpenseRepositoryImpl.java @@ -200,6 +200,7 @@ public List findByMemberIdAndDateRange(String memberId, LocalDate startDat .and(expense.date.between(startDate, endDate))) .fetch(); } + @Override public Map findTotalCostDate(String memberId, LocalDate startDate, LocalDate endDate) { QExpense expense = QExpense.expense; @@ -222,8 +223,6 @@ public Map findTotalCostDate(String memberId, LocalDate startDa return expenseMap; } - - private BooleanExpression searchTitle(String searchKeyword){ if (searchKeyword != null && !searchKeyword.trim().isEmpty()) { return expense.title.containsIgnoreCase(searchKeyword); diff --git a/src/main/java/com/umc5th/muffler/domain/goal/dto/GoalCreateRequest.java b/src/main/java/com/umc5th/muffler/domain/goal/dto/GoalCreateRequest.java index d36803c5..cb72cbb1 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/dto/GoalCreateRequest.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/dto/GoalCreateRequest.java @@ -2,6 +2,7 @@ import java.time.LocalDate; import java.util.List; +import javax.validation.Valid; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import lombok.AllArgsConstructor; @@ -24,12 +25,15 @@ public class GoalCreateRequest { private LocalDate endDate; @NotNull private Long totalBudget; - @NotNull - private Boolean restore; @NotNull + @Valid private List categoryGoals; @NotNull private List dailyBudgets; + + @NotNull + private Boolean canRestore; + private Boolean restore; } diff --git a/src/main/java/com/umc5th/muffler/domain/goal/repository/GoalJdbcRepository.java b/src/main/java/com/umc5th/muffler/domain/goal/repository/CategoryGoalJdbcRepository.java similarity index 52% rename from src/main/java/com/umc5th/muffler/domain/goal/repository/GoalJdbcRepository.java rename to src/main/java/com/umc5th/muffler/domain/goal/repository/CategoryGoalJdbcRepository.java index c5f7730b..c7d0e010 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/repository/GoalJdbcRepository.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/repository/CategoryGoalJdbcRepository.java @@ -1,8 +1,6 @@ package com.umc5th.muffler.domain.goal.repository; import com.umc5th.muffler.entity.CategoryGoal; -import com.umc5th.muffler.entity.DailyPlan; -import java.sql.Date; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; @@ -13,11 +11,11 @@ @Repository @RequiredArgsConstructor -public class GoalJdbcRepository { +public class CategoryGoalJdbcRepository { private final JdbcTemplate jdbcTemplate; - public void batchInsertCategoryGoals(List categoryGoals) { + public void batchInsert(List categoryGoals) { String sql = "INSERT INTO category_goal (budget, category_id, goal_id) VALUES (?, ?, ?)"; jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { @@ -36,25 +34,4 @@ public int getBatchSize() { }); } - public void batchInsertDailyPlans(List dailyPlans) { - String sql = "INSERT INTO daily_plan (date, budget, is_zero_day, total_cost, goal_id) VALUES (?, ?, ?, ?, ?)"; - - jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps, int i) throws SQLException { - DailyPlan dailyPlan = dailyPlans.get(i); - ps.setDate(1, Date.valueOf(dailyPlan.getDate())); - ps.setLong(2, dailyPlan.getBudget()); - ps.setBoolean(3, dailyPlan.getIsZeroDay()); - ps.setLong(4, dailyPlan.getTotalCost()); - ps.setLong(5, dailyPlan.getGoal().getId()); - } - - @Override - public int getBatchSize() { - return dailyPlans.size(); - } - }); - } - } diff --git a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java index 278fee9a..7f028d53 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java @@ -10,7 +10,8 @@ import com.umc5th.muffler.domain.expense.repository.ExpenseRepository; import com.umc5th.muffler.domain.goal.dto.CategoryGoalRequest; import com.umc5th.muffler.domain.goal.dto.GoalCreateRequest; -import com.umc5th.muffler.domain.goal.repository.GoalJdbcRepository; +import com.umc5th.muffler.domain.goal.repository.CategoryGoalJdbcRepository; +import com.umc5th.muffler.domain.goal.repository.CategoryGoalRepository; import com.umc5th.muffler.domain.goal.repository.GoalRepository; import com.umc5th.muffler.domain.member.repository.MemberRepository; import com.umc5th.muffler.entity.Category; @@ -27,6 +28,10 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; import lombok.RequiredArgsConstructor; @@ -39,46 +44,33 @@ public class GoalCreateService { private final GoalRepository goalRepository; - private final GoalJdbcRepository goalJdbcRepository; - private final DailyPlanJdbcRepository dailyPlanJdbcRepository; + private final CategoryGoalRepository categoryGoalRepository; private final MemberRepository memberRepository; private final CategoryRepository categoryRepository; private final ExpenseRepository expenseRepository; + private final CategoryGoalJdbcRepository categoryGoalJdbcRepository; + private final DailyPlanJdbcRepository dailyPlanJdbcRepository; public void create(GoalCreateRequest request, String memberId) { Member member = memberRepository.findByIdAndFetchGoals(memberId) .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); + memberRepository.findByIdAndFetchCategories(memberId); validateGoalInput(request, member); Goal goal = Goal.of(request.getStartDate(), request.getEndDate(), request.getTitle(), request.getIcon(), request.getTotalBudget(), member); Goal savedGoal = goalRepository.save(goal); - List categoryGoals = createCategoryGoals(savedGoal, request.getCategoryGoals()); + List categoryGoals = createCategoryGoals(member.getCategories(), savedGoal, request.getCategoryGoals()); List dailyPlans = createDailyPlans(savedGoal, request.getStartDate(), request.getDailyBudgets()); - handleRestore(request, memberId, dailyPlans); - goalJdbcRepository.batchInsertCategoryGoals(categoryGoals); - goalJdbcRepository.batchInsertDailyPlans(dailyPlans); + if (request.getCanRestore()) { + handleRestore(request, memberId, dailyPlans); + } - member.addGoal(savedGoal); + categoryGoalJdbcRepository.batchInsert(categoryGoals); + dailyPlanJdbcRepository.batchInsert(dailyPlans); } - private void handleRestore(GoalCreateRequest request, String memberId, List dailyPlans) { - if (request.getRestore()){ - Map costMap = expenseRepository.findTotalCostDate(memberId, request.getStartDate(), - request.getEndDate()); - dailyPlans.forEach(dailyPlan -> { - if (costMap.containsKey(dailyPlan.getDate())) { - Long cost = costMap.get(dailyPlan.getDate()); - dailyPlan.updateTotalCost(cost); - } - }); - } - else { - List expenseIds = expenseRepository.findByMemberIdAndDateRange(memberId, request.getStartDate() - ,request.getEndDate()); - expenseRepository.deleteByIds(expenseIds); - } } public void updateDailyBudgets(String memberId, Long goalId, List dailyBudgets) { @@ -92,16 +84,36 @@ public void updateDailyBudgets(String memberId, Long goalId, List dailyBud dailyPlanJdbcRepository.batchUpdateBudget(dailyPlans, dailyBudgets); } + private void handleRestore(GoalCreateRequest request, String memberId, List dailyPlans) { + if (request.getRestore()) { + Map costMap = expenseRepository + .findTotalCostDate(memberId, request.getStartDate(), request.getEndDate()); + + dailyPlans.forEach(dailyPlan -> { + if (costMap.containsKey(dailyPlan.getDate())) { + dailyPlan.updateTotalCost(costMap.get(dailyPlan.getDate())); + }}); + return; + } + + List expenseIds = expenseRepository + .findByMemberIdAndDateRange(memberId, request.getStartDate(), request.getEndDate()); + expenseRepository.deleteByIds(expenseIds); + } + private void validateGoalInput(GoalCreateRequest request, Member member) { validateGoalPeriod(member.getGoals(), request.getStartDate(), request.getEndDate()); validateCategoryGoals(request.getCategoryGoals(), request.getTotalBudget()); validateDailyPlans(request.getStartDate(), request.getEndDate(), request.getDailyBudgets(), request.getTotalBudget()); } - private List createCategoryGoals(Goal goal, List categoryGoals) { + private List createCategoryGoals(List categories, Goal goal, List categoryGoals) { List result = new ArrayList<>(); + Map categoryMap = categories.stream() + .collect(Collectors.toMap(Category::getId, Function.identity())); + for (CategoryGoalRequest categoryGoal : categoryGoals) { - Category category = categoryRepository.findById(categoryGoal.getCategoryId()) + Category category = Optional.ofNullable(categoryMap.get(categoryGoal.getCategoryId())) .orElseThrow(() -> new CategoryException(CATEGORY_NOT_FOUND)); result.add(CategoryGoal.of(categoryGoal.getCategoryBudget(), category, goal)); diff --git a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java index 52ae7e6b..628a6051 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalService.java @@ -64,7 +64,7 @@ public boolean checkRestore(String memberId, LocalDate startDate, LocalDate endD public void updateTitleAndIcon(Long goalId, String title, String icon, String memberId) { Member member = memberRepository.findById(memberId) .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); - Goal goal = goalRepository.findByIdAndMemberId(goalId, memberId) + Goal goal = goalRepository.findByIdAndMemberId(memberId, goalId) .orElseThrow(() -> new GoalException(GOAL_NOT_FOUND)); goal.updateTitle(title); goal.updateIcon(icon); @@ -74,7 +74,7 @@ public void updateTitleAndIcon(Long goalId, String title, String icon, String me public void delete(Long goalId, String memberId) { Member member = memberRepository.findById(memberId) .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); - Goal goal = goalRepository.findByIdAndMemberId(goalId, memberId) + Goal goal = goalRepository.findByIdAndMemberId(memberId, goalId) .orElseThrow(() -> new GoalException(GOAL_NOT_FOUND)); List categoryGoalIds = categoryGoalRepository.findByGoalId(goalId); diff --git a/src/main/java/com/umc5th/muffler/domain/member/repository/MemberRepository.java b/src/main/java/com/umc5th/muffler/domain/member/repository/MemberRepository.java index c84d0a61..0023930f 100644 --- a/src/main/java/com/umc5th/muffler/domain/member/repository/MemberRepository.java +++ b/src/main/java/com/umc5th/muffler/domain/member/repository/MemberRepository.java @@ -14,4 +14,6 @@ public interface MemberRepository extends JpaRepository, MemberR Optional findMemberFetchAlarm(@Param("memberId") String memberId); @Query("SELECT m FROM Member m LEFT JOIN FETCH m.goals WHERE m.id = :memberId") Optional findByIdAndFetchGoals(String memberId); + @Query("SELECT m FROM Member m LEFT JOIN FETCH m.categories WHERE m.id = :memberId") + Optional findByIdAndFetchCategories(String memberId); } diff --git a/src/main/java/com/umc5th/muffler/entity/Member.java b/src/main/java/com/umc5th/muffler/entity/Member.java index d78c7f06..acdb89fd 100644 --- a/src/main/java/com/umc5th/muffler/entity/Member.java +++ b/src/main/java/com/umc5th/muffler/entity/Member.java @@ -6,12 +6,6 @@ import com.umc5th.muffler.entity.constant.Role; import com.umc5th.muffler.entity.constant.SocialType; import com.umc5th.muffler.entity.constant.Status; -import org.springframework.data.domain.Persistable; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; - -import javax.persistence.*; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -21,13 +15,21 @@ import javax.persistence.EntityListeners; import javax.persistence.EnumType; import javax.persistence.Enumerated; +import javax.persistence.FetchType; import javax.persistence.Id; +import javax.persistence.JoinColumn; import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.springframework.data.domain.Persistable; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @@ -62,7 +64,7 @@ public class Member extends BaseTimeEntity implements Persistable, UserD @Builder.Default @OneToMany(mappedBy = "member") private List goals = new ArrayList<>(); - + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) @Builder.Default private List categories = new ArrayList<>(); @@ -80,13 +82,6 @@ public void setNameAndProfile(String name, String profileImg) { this.profileImg = profileImg; } - public void addGoal(Goal goal) { - this.goals.add(goal); - } - - public void removeGoal(Goal goal) { - this.goals.remove(goal); - } public void addCategory(Category category) { category.setMember(this); this.categories.add(category); From be89a36d6631a3631ea5edbd509aa88308960d6c Mon Sep 17 00:00:00 2001 From: hajungIm Date: Tue, 2 Apr 2024 17:47:51 +0900 Subject: [PATCH 22/24] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EB=AA=A9=ED=91=9C=20=EC=88=98=EC=A0=95=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to: #4 --- .../goal/controller/GoalController.java | 7 +++ .../domain/goal/dto/CategoryGoalRequest.java | 2 + .../domain/goal/dto/CategoryGoalsRequest.java | 16 ++++++ .../CategoryGoalJdbcRepository.java | 18 +++++++ .../goal/repository/GoalRepository.java | 9 ++-- .../goal/service/GoalCreateService.java | 50 ++++++++++++++++++- .../umc5th/muffler/entity/CategoryGoal.java | 5 ++ 7 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/umc5th/muffler/domain/goal/dto/CategoryGoalsRequest.java diff --git a/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java b/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java index 5a10f0cf..47727160 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/controller/GoalController.java @@ -1,5 +1,6 @@ package com.umc5th.muffler.domain.goal.controller; +import com.umc5th.muffler.domain.goal.dto.CategoryGoalsRequest; import com.umc5th.muffler.domain.goal.dto.DailyBudgetsRequest; import com.umc5th.muffler.domain.goal.dto.GoalConverter; import com.umc5th.muffler.domain.goal.dto.GoalCreateRequest; @@ -72,6 +73,12 @@ public Response updateTitleAndIcon(@PathVariable Long goalId, @RequestBody return Response.success(); } + @PatchMapping("/{goalId}/category-goal") + public Response updateCategoryGoals(@PathVariable Long goalId, @RequestBody @Valid CategoryGoalsRequest request, Authentication authentication) { + goalCreateService.updateCategoryGoals(authentication.getName(), goalId, request.getCategoryGoals()); + return Response.success(); + } + @PatchMapping("/{goalId}/daily-budgets") public Response updateDailyBudgets(@PathVariable Long goalId, @RequestBody @Valid DailyBudgetsRequest request, Authentication authentication) { goalCreateService.updateDailyBudgets(authentication.getName(), goalId, request.getDailyBudgets()); diff --git a/src/main/java/com/umc5th/muffler/domain/goal/dto/CategoryGoalRequest.java b/src/main/java/com/umc5th/muffler/domain/goal/dto/CategoryGoalRequest.java index 22db4988..f27c41bc 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/dto/CategoryGoalRequest.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/dto/CategoryGoalRequest.java @@ -7,6 +7,8 @@ @Getter @AllArgsConstructor public class CategoryGoalRequest { + @NotNull + private Long categoryGoalId; @NotNull private Long categoryId; @NotNull diff --git a/src/main/java/com/umc5th/muffler/domain/goal/dto/CategoryGoalsRequest.java b/src/main/java/com/umc5th/muffler/domain/goal/dto/CategoryGoalsRequest.java new file mode 100644 index 00000000..db6c281c --- /dev/null +++ b/src/main/java/com/umc5th/muffler/domain/goal/dto/CategoryGoalsRequest.java @@ -0,0 +1,16 @@ +package com.umc5th.muffler.domain.goal.dto; + +import java.util.List; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class CategoryGoalsRequest { + @NotNull @Valid + private List categoryGoals; +} diff --git a/src/main/java/com/umc5th/muffler/domain/goal/repository/CategoryGoalJdbcRepository.java b/src/main/java/com/umc5th/muffler/domain/goal/repository/CategoryGoalJdbcRepository.java index c7d0e010..0a29b261 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/repository/CategoryGoalJdbcRepository.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/repository/CategoryGoalJdbcRepository.java @@ -34,4 +34,22 @@ public int getBatchSize() { }); } + public void batchUpdateBudget(List categoryGoals) { + String sql = "UPDATE category_goal SET budget = ? WHERE id = ?"; + + jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + CategoryGoal categoryGoal = categoryGoals.get(i); + Long budget = categoryGoal.getBudget(); + ps.setLong(1, budget); + ps.setLong(2, categoryGoal.getId()); + } + + @Override + public int getBatchSize() { + return categoryGoals.size(); + } + }); + } } diff --git a/src/main/java/com/umc5th/muffler/domain/goal/repository/GoalRepository.java b/src/main/java/com/umc5th/muffler/domain/goal/repository/GoalRepository.java index 4a5f7b73..09989e4c 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/repository/GoalRepository.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/repository/GoalRepository.java @@ -18,11 +18,14 @@ public interface GoalRepository extends JpaRepository, GoalRepositor @Query("SELECT goal from Goal goal join fetch goal.dailyPlans where :date BETWEEN goal.startDate and goal.endDate AND goal.member.id = :memberId") Optional findByDateBetweenAndDailyPlans(LocalDate date, String memberId); - @Query("SELECT g FROM Goal g JOIN FETCH g.dailyPlans WHERE g.id = :goalId") - Optional findByIdAndFetchDailyPlans(Long goalId); + @Query("SELECT g FROM Goal g LEFT JOIN FETCH g.categoryGoals WHERE g.id = :goalId AND g.member.id = :memberId") + Optional findByIdAndFetchCategoryGoals(String memberId, Long goalId); + + @Query("SELECT g FROM Goal g LEFT JOIN FETCH g.dailyPlans WHERE g.id = :goalId AND g.member.id = :memberId") + Optional findByIdAndFetchDailyPlans(String memberId, Long goalId); @Query("SELECT g FROM Goal g WHERE g.id = :goalId AND g.member.id = :memberId") - Optional findByIdAndMemberId(Long goalId, String memberId); + Optional findByIdAndMemberId(String memberId, Long goalId); @Modifying @Query("DELETE FROM Goal g WHERE g.id = :goalId AND g.member.id = :memberId") diff --git a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java index 7f028d53..497eedac 100644 --- a/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java +++ b/src/main/java/com/umc5th/muffler/domain/goal/service/GoalCreateService.java @@ -71,12 +71,30 @@ public void create(GoalCreateRequest request, String memberId) { dailyPlanJdbcRepository.batchInsert(dailyPlans); } + public void updateCategoryGoals(String memberId, Long goalId, List request) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); + Goal goal = goalRepository.findByIdAndFetchCategoryGoals(memberId, goalId) + .orElseThrow(() -> new GoalException(GOAL_NOT_FOUND)); + validateCategoryGoals(request, goal.getTotalBudget()); + + List categoryGoals = goal.getCategoryGoals(); + + List toInsert = new ArrayList<>(); + List toUpdate = new ArrayList<>(); + getInsertAndUpdate(request, categoryGoals, goal, toUpdate, toInsert); + + List toDelete = getDelete(request, categoryGoals); + + categoryGoalJdbcRepository.batchInsert(toInsert); + categoryGoalJdbcRepository.batchUpdateBudget(toUpdate); + categoryGoalRepository.deleteByIds(toDelete); } public void updateDailyBudgets(String memberId, Long goalId, List dailyBudgets) { Member member = memberRepository.findById(memberId) .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); - Goal goal = goalRepository.findByIdAndFetchDailyPlans(goalId) + Goal goal = goalRepository.findByIdAndFetchDailyPlans(memberId, goalId) .orElseThrow(() -> new GoalException(GOAL_NOT_FOUND)); validateDailyPlans(goal.getStartDate(), goal.getEndDate(), dailyBudgets, goal.getTotalBudget()); @@ -84,6 +102,36 @@ public void updateDailyBudgets(String memberId, Long goalId, List dailyBud dailyPlanJdbcRepository.batchUpdateBudget(dailyPlans, dailyBudgets); } + private void getInsertAndUpdate(List request, List categoryGoals, Goal goal, + List toUpdate, List toInsert) { + Map categoryGoalMap = categoryGoals.stream() + .collect(Collectors.toMap(CategoryGoal::getId, Function.identity())); + + for (CategoryGoalRequest req : request) { + if (categoryGoalMap.containsKey(req.getCategoryGoalId())) { + CategoryGoal categoryGoal = categoryGoalMap.get(req.getCategoryGoalId()); + if (!Objects.equals(categoryGoal.getBudget(), req.getCategoryBudget())) { + categoryGoal.setBudget(req.getCategoryBudget()); + toUpdate.add(categoryGoal); + } + continue; + } + + Category category = categoryRepository.findById(req.getCategoryId()) + .orElseThrow(() -> new CategoryException(CATEGORY_NOT_FOUND)); + toInsert.add(CategoryGoal.of(req.getCategoryBudget(), category, goal)); + } + } + + private List getDelete(List request, List categoryGoals) { + Set requestIds = request.stream() + .map(CategoryGoalRequest::getCategoryGoalId).collect(Collectors.toSet()); + + return categoryGoals.stream() + .filter(cg -> !requestIds.contains(cg.getId())) + .map(CategoryGoal::getId).collect(Collectors.toList()); + } + private void handleRestore(GoalCreateRequest request, String memberId, List dailyPlans) { if (request.getRestore()) { Map costMap = expenseRepository diff --git a/src/main/java/com/umc5th/muffler/entity/CategoryGoal.java b/src/main/java/com/umc5th/muffler/entity/CategoryGoal.java index 480d17d8..1e3461ed 100644 --- a/src/main/java/com/umc5th/muffler/entity/CategoryGoal.java +++ b/src/main/java/com/umc5th/muffler/entity/CategoryGoal.java @@ -45,6 +45,11 @@ public static CategoryGoal of(Long budget, Category category, Goal goal) { .goal(goal) .build(); } + + public void setBudget(Long budget) { + this.budget = budget; + } + public void setGoal(Goal goal) { this.goal = goal; } From 9931e1169ac163e1ffc448c354347d49f1e4a58a Mon Sep 17 00:00:00 2001 From: hajungIm Date: Tue, 2 Apr 2024 21:21:44 +0900 Subject: [PATCH 23/24] =?UTF-8?q?fix:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/goal/service/GoalCreateServiceTest.java | 14 ++++++-------- .../domain/goal/service/GoalServiceTest.java | 2 +- .../muffler/fixture/GoalCreateRequestFixture.java | 14 +++++++------- .../com/umc5th/muffler/fixture/MemberFixture.java | 11 ++++++++++- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/test/java/com/umc5th/muffler/domain/goal/service/GoalCreateServiceTest.java b/src/test/java/com/umc5th/muffler/domain/goal/service/GoalCreateServiceTest.java index 1a73af90..7dac1640 100644 --- a/src/test/java/com/umc5th/muffler/domain/goal/service/GoalCreateServiceTest.java +++ b/src/test/java/com/umc5th/muffler/domain/goal/service/GoalCreateServiceTest.java @@ -10,11 +10,11 @@ import static org.mockito.Mockito.when; import com.umc5th.muffler.domain.category.repository.CategoryRepository; +import com.umc5th.muffler.domain.dailyplan.repository.DailyPlanJdbcRepository; import com.umc5th.muffler.domain.goal.dto.GoalCreateRequest; -import com.umc5th.muffler.domain.goal.repository.GoalJdbcRepository; +import com.umc5th.muffler.domain.goal.repository.CategoryGoalJdbcRepository; import com.umc5th.muffler.domain.goal.repository.GoalRepository; import com.umc5th.muffler.domain.member.repository.MemberRepository; -import com.umc5th.muffler.entity.Category; import com.umc5th.muffler.entity.Goal; import com.umc5th.muffler.entity.Member; import com.umc5th.muffler.fixture.GoalCreateRequestFixture; @@ -24,7 +24,6 @@ import com.umc5th.muffler.global.response.exception.MemberException; import java.time.LocalDate; import java.util.Optional; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -42,23 +41,22 @@ class GoalCreateServiceTest { @MockBean private GoalRepository goalRepository; @MockBean - private GoalJdbcRepository goalJdbcRepository; - + private CategoryGoalJdbcRepository categoryGoalJdbcRepository; + @MockBean + private DailyPlanJdbcRepository dailyPlanJdbcRepository; @Test void 목표등록이_성공한경우() { GoalCreateRequest request = GoalCreateRequestFixture.create(); - Member member = MemberFixture.create(); + Member member = MemberFixture.createWithCategory(); Goal mockGoal = mock(Goal.class); when(memberRepository.findByIdAndFetchGoals(member.getId())).thenReturn(Optional.of(member)); - when(categoryRepository.findById(any())).thenReturn(Optional.of(mock(Category.class))); when(goalRepository.save(any())).thenReturn(mockGoal); goalCreateService.create(request, member.getId()); verify(goalRepository).save(any(Goal.class)); - Assertions.assertThat(member.getGoals()).contains(mockGoal); } @Test diff --git a/src/test/java/com/umc5th/muffler/domain/goal/service/GoalServiceTest.java b/src/test/java/com/umc5th/muffler/domain/goal/service/GoalServiceTest.java index 9286f8ea..2bc7995b 100644 --- a/src/test/java/com/umc5th/muffler/domain/goal/service/GoalServiceTest.java +++ b/src/test/java/com/umc5th/muffler/domain/goal/service/GoalServiceTest.java @@ -91,7 +91,7 @@ class GoalServiceTest { when(mockGoal.getMember()).thenReturn(mockMember); when(memberRepository.findById(memberId)).thenReturn(Optional.of(mockMember)); - when(goalRepository.findByIdAndMemberId(goalId, memberId)).thenReturn(Optional.of(mockGoal)); + when(goalRepository.findByIdAndMemberId(memberId, goalId)).thenReturn(Optional.of(mockGoal)); goalService.delete(goalId, memberId); diff --git a/src/test/java/com/umc5th/muffler/fixture/GoalCreateRequestFixture.java b/src/test/java/com/umc5th/muffler/fixture/GoalCreateRequestFixture.java index d6cf0853..c2b5e4e6 100644 --- a/src/test/java/com/umc5th/muffler/fixture/GoalCreateRequestFixture.java +++ b/src/test/java/com/umc5th/muffler/fixture/GoalCreateRequestFixture.java @@ -13,9 +13,9 @@ public static GoalCreateRequest create() { .title("title") .icon("icon") .totalBudget(10000L) - .categoryGoals(List.of(new CategoryGoalRequest(1L, 1000L))) + .categoryGoals(List.of(new CategoryGoalRequest(-1L, 1L, 1000L))) .dailyBudgets(List.of(5000L, 5000L)) - .restore(true) + .canRestore(false) .build(); } @@ -26,7 +26,7 @@ public static GoalCreateRequest create(LocalDate startDate, LocalDate endDate) { .title("title") .icon("icon") .totalBudget(10000L) - .categoryGoals(List.of(new CategoryGoalRequest(1L, 1000L))) + .categoryGoals(List.of(new CategoryGoalRequest(-1L, 1L, 1000L))) .dailyBudgets(List.of(5000L, 5000L)) .build(); } @@ -38,7 +38,7 @@ public static GoalCreateRequest createDuplicatedCategoryGoals() { .title("title") .icon("icon") .totalBudget(10000L) - .categoryGoals(List.of(new CategoryGoalRequest(1L, 1000L), new CategoryGoalRequest(1L, 1000L))) + .categoryGoals(List.of(new CategoryGoalRequest(-1L, 1L, 1000L), new CategoryGoalRequest(-1L, 1L, 1000L))) .dailyBudgets(List.of(5000L, 5000L)) .build(); } @@ -50,7 +50,7 @@ public static GoalCreateRequest createInvalidCategoryBudget() { .title("title") .icon("icon") .totalBudget(10000L) - .categoryGoals(List.of(new CategoryGoalRequest(1L, 20000L))) + .categoryGoals(List.of(new CategoryGoalRequest(-1L, 1L, 20000L))) .dailyBudgets(List.of(5000L, 5000L)) .build(); } @@ -62,7 +62,7 @@ public static GoalCreateRequest createInvalidDailyPlanPeriod() { .title("title") .icon("icon") .totalBudget(10000L) - .categoryGoals(List.of(new CategoryGoalRequest(1L, 1000L))) + .categoryGoals(List.of(new CategoryGoalRequest(-1L, 1L, 1000L))) .dailyBudgets(List.of(10000L)) .build(); } @@ -74,7 +74,7 @@ public static GoalCreateRequest createInvalidDailyBudgetSum() { .title("title") .icon("icon") .totalBudget(10000L) - .categoryGoals(List.of(new CategoryGoalRequest(1L, 1000L))) + .categoryGoals(List.of(new CategoryGoalRequest(-1L, 1L, 1000L))) .dailyBudgets(List.of(5000L, 1000L)) .build(); } diff --git a/src/test/java/com/umc5th/muffler/fixture/MemberFixture.java b/src/test/java/com/umc5th/muffler/fixture/MemberFixture.java index 90ea511d..1d89328e 100644 --- a/src/test/java/com/umc5th/muffler/fixture/MemberFixture.java +++ b/src/test/java/com/umc5th/muffler/fixture/MemberFixture.java @@ -2,7 +2,6 @@ import com.umc5th.muffler.entity.Member; import com.umc5th.muffler.entity.constant.SocialType; - import java.util.ArrayList; import java.util.List; @@ -34,4 +33,14 @@ public static Member create(String id) { .socialType(SocialType.KAKAO) .build(); } + + public static Member createWithCategory() { + return Member.builder() + .id("1") + .name("name") + .socialType(SocialType.APPLE) + .goals(new ArrayList<>(List.of(GoalFixture.create()))) + .categories(List.of(CategoryFixture.CATEGORY_ONE)) + .build(); + } } From 5e3d35c841ea3907f5a053e634b63cdc904768e7 Mon Sep 17 00:00:00 2001 From: shortboy7 Date: Wed, 3 Apr 2024 00:29:46 +0900 Subject: [PATCH 24/24] =?UTF-8?q?feat:=20push=20alarm=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20api=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related-to : #17 --- .../umc5th/muffler/global/TestController.java | 35 +++++++++++++++++++ .../muffler/global/config/AppConfig.java | 3 +- 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/umc5th/muffler/global/TestController.java diff --git a/src/main/java/com/umc5th/muffler/global/TestController.java b/src/main/java/com/umc5th/muffler/global/TestController.java new file mode 100644 index 00000000..0ffe11c2 --- /dev/null +++ b/src/main/java/com/umc5th/muffler/global/TestController.java @@ -0,0 +1,35 @@ +package com.umc5th.muffler.global; + +import com.umc5th.muffler.domain.goal.dto.FinishedGoal; +import com.umc5th.muffler.domain.member.repository.MemberRepository; +import com.umc5th.muffler.entity.Member; +import com.umc5th.muffler.global.response.code.ErrorCode; +import com.umc5th.muffler.global.response.exception.MemberException; +import com.umc5th.muffler.message.service.AlarmService; +import java.security.Principal; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +// push alarm test용 테스트 완료 후 제거 예정 +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/test") +public class TestController { + private final AlarmService alarmService; + private final MemberRepository memberRepository; + @GetMapping("/alarm") + public ResponseEntity testAlarm(Principal principal) { + Member member = memberRepository.findById(principal.getName()) + .orElseThrow(() -> new MemberException(ErrorCode.MEMBER_NOT_FOUND)); + String token = member.getMemberAlarm().getToken(); + if (token == null || token.isEmpty()) { + throw new MemberException(ErrorCode.BAD_REQUEST); + } + alarmService.sendEndGoals(List.of(new FinishedGoal("goal title", "goal icon", token))); + return ResponseEntity.ok("success"); + } +} diff --git a/src/main/java/com/umc5th/muffler/global/config/AppConfig.java b/src/main/java/com/umc5th/muffler/global/config/AppConfig.java index 1386c031..00a7af39 100644 --- a/src/main/java/com/umc5th/muffler/global/config/AppConfig.java +++ b/src/main/java/com/umc5th/muffler/global/config/AppConfig.java @@ -4,6 +4,7 @@ import com.umc5th.muffler.global.util.DefaultDateTimeProvider; import com.umc5th.muffler.message.service.sender.Sender; import com.umc5th.muffler.message.service.sender.impl.ConsoleMessageSender; +import com.umc5th.muffler.message.service.sender.impl.FirebaseMessageSender; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @@ -17,5 +18,5 @@ public DateTimeProvider dateTimeProvider() { } @Bean - public Sender Sender() { return new ConsoleMessageSender(); } + public Sender Sender() { return new FirebaseMessageSender(); } }