Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add(.remind): 피드백 가능한 계획 목록 조회 API 구현 #199

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package me.ajaja.module.feedback.application;

import java.util.List;

import me.ajaja.global.common.BaseTime;
import me.ajaja.module.feedback.application.model.UpdatableFeedback;

public interface FindUpdatableTargetService { // todo: 헥사고날 아키텍쳐 적용 후 네이밍 변경
List<UpdatableFeedback> findUpdatableTargetsByUserId(Long userId, BaseTime now);
2jie0516 marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package me.ajaja.module.feedback.application;

import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import lombok.RequiredArgsConstructor;
import me.ajaja.global.common.BaseTime;
import me.ajaja.module.feedback.domain.FeedbackQueryRepository;
import me.ajaja.module.feedback.dto.FeedbackResponse;
import me.ajaja.module.feedback.mapper.FeedbackMapper;

@Service
@Transactional
@RequiredArgsConstructor
public class LoadUpdatableFeedbackService {
private final FeedbackQueryRepository feedbackQueryRepository;
private final FindUpdatableTargetService findUpdatableTargetService;
private final FeedbackMapper mapper;

public List<FeedbackResponse.UpdatableFeedback> loadUpdatableFeedbacksByUserId(Long userId, BaseTime now) {
return findUpdatableTargetService.findUpdatableTargetsByUserId(userId, now).stream()
.filter(feedback -> !feedbackQueryRepository.existByPlanIdAndPeriod(feedback.planId(), feedback.deadLine()))
.map(mapper::toResponse)
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package me.ajaja.module.feedback.application.model;

import me.ajaja.global.common.BaseTime;

public record UpdatableFeedback(
String title,
Long planId,
BaseTime deadLine
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
public interface FeedbackQueryRepository {
List<Feedback> findAllFeedbackByPlanId(Long planId);

boolean existByPlanIdAndPeriod(Long feedbackId, BaseTime period);
boolean existByPlanIdAndPeriod(Long planId, BaseTime period);

List<FeedbackInfo> findFeedbackInfosByPlanId(Long planId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,13 @@ public static class RemindFeedback {
private final int endDate;
private final boolean reminded;
}

@Data
public static class UpdatableFeedback {
private final String title;
private final Long planId;
private final long remainPeriod;
private final int month;
private final int date;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package me.ajaja.module.feedback.mapper;

import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

import me.ajaja.global.common.BaseTime;
import me.ajaja.module.feedback.application.model.PlanFeedbackInfo;
import me.ajaja.module.feedback.application.model.UpdatableFeedback;
import me.ajaja.module.feedback.domain.Feedback;
import me.ajaja.module.feedback.dto.FeedbackResponse;
import me.ajaja.module.feedback.infra.FeedbackEntity;
Expand Down Expand Up @@ -59,4 +61,13 @@ default boolean isReminded(BaseTime sendDate) {
BaseTime now = BaseTime.now();
return now.isAfter(sendDate);
}

@Mapping(target = "remainPeriod", expression = "java(getRemainPeriod(feedback.deadLine()))")
@Mapping(target = "month", expression = "java(feedback.deadLine().getMonth())")
@Mapping(target = "date", expression = "java(feedback.deadLine().getDate())")
FeedbackResponse.UpdatableFeedback toResponse(UpdatableFeedback feedback);

default long getRemainPeriod(BaseTime deadLine) {
Hejow marked this conversation as resolved.
Show resolved Hide resolved
return ChronoUnit.DAYS.between(deadLine.getInstant(), BaseTime.now().getInstant());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import static org.springframework.http.HttpStatus.*;

import java.util.List;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -12,9 +14,11 @@

import lombok.RequiredArgsConstructor;
import me.ajaja.global.common.AjajaResponse;
import me.ajaja.global.common.BaseTime;
import me.ajaja.global.security.annotation.Authorization;
import me.ajaja.global.util.SecurityUtil;
import me.ajaja.module.feedback.application.LoadFeedbackInfoService;
import me.ajaja.module.feedback.application.LoadUpdatableFeedbackService;
import me.ajaja.module.feedback.application.UpdateFeedbackService;
import me.ajaja.module.feedback.dto.FeedbackRequest;
import me.ajaja.module.feedback.dto.FeedbackResponse;
Expand All @@ -25,6 +29,7 @@
public class FeedbackController {
private final UpdateFeedbackService updateFeedbackService;
private final LoadFeedbackInfoService loadFeedbackInfoService;
private final LoadUpdatableFeedbackService loadUpdatableFeedbackService;

@Authorization
@PostMapping("/{planId}")
Expand All @@ -46,4 +51,14 @@ public AjajaResponse<FeedbackResponse.FeedbackInfo> getFeedbackInfo(@PathVariabl
FeedbackResponse.FeedbackInfo feedbackInfo = loadFeedbackInfoService.loadFeedbackInfoByPlanId(userId, planId);
return AjajaResponse.ok(feedbackInfo);
}

@Authorization
@GetMapping("/updatable")
Copy link
Member

@Hejow Hejow Mar 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

하 뭔가 좋은 엔드포인트가 있을 것 같은데... 고민되네요
그냥 컨트롤 URI로 쓰는것도 괜찮을 것 같은데 어떤가요??

// 전
curl -X GET https://api.ajaja.me/feedbacks/updatable

// 후
curl -X GET https://api.ajaja.me/feedbacks/추천좀요

그리고 변수명 수정하면서 엔드포인트는 수정 안되었군요!? ㅋㅋ

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6b96c34
"평가 가능한 피드백" 을 나타낼 수 있는게 뭔가 고민해보았는데 흠흠
평가 + able + 복수형 = evaluables 로 해보았는데 어떤가유 ㅋㅋ
이번꺼 네이밍이 전반적으로 애매하네요..

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

evaluables 이게 나쁘지 않은 것 같기두용 ㅎㅎ?

@ResponseStatus(OK)
public AjajaResponse<List<FeedbackResponse.UpdatableFeedback>> getUpdatableFeedbacks() {
Long userId = SecurityUtil.getUserId();
List<FeedbackResponse.UpdatableFeedback> feedbacks
= loadUpdatableFeedbackService.loadUpdatableFeedbacksByUserId(userId, BaseTime.now());
return AjajaResponse.ok(feedbacks);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package me.ajaja.module.plan.adapter.out.persistence;

import static me.ajaja.module.plan.adapter.out.persistence.model.QPlanEntity.*;

import java.util.List;

import org.springframework.stereotype.Repository;

import com.querydsl.jpa.impl.JPAQueryFactory;

import lombok.RequiredArgsConstructor;
import me.ajaja.global.common.BaseTime;
import me.ajaja.module.feedback.application.FindUpdatableTargetService;
import me.ajaja.module.feedback.application.model.UpdatableFeedback;
import me.ajaja.module.plan.domain.Plan;
import me.ajaja.module.plan.mapper.PlanMapper;

@Repository
@RequiredArgsConstructor
public class FindUpdatableTargetsAdapter implements FindUpdatableTargetService {
Hejow marked this conversation as resolved.
Show resolved Hide resolved
private final JPAQueryFactory queryFactory;
private final PlanMapper mapper;

@Override
public List<UpdatableFeedback> findUpdatableTargetsByUserId(Long userId, BaseTime now) {
List<Plan> plans = queryFactory.selectFrom(planEntity)
.where(planEntity.userId.eq(userId)
.and(planEntity.createdAt.year().eq(now.getYear())))
.fetch()
.stream()
.map(mapper::toDomain)
.toList();

return getUpdatablePlans(now, plans);
}

private List<UpdatableFeedback> getUpdatablePlans(BaseTime now, List<Plan> plans) {
return plans.stream()
.filter(plan -> plan.isWithinPeriod(now))
.map(mapper::toFeedbackModel)
.toList();
}
Hejow marked this conversation as resolved.
Show resolved Hide resolved
}
20 changes: 12 additions & 8 deletions src/main/java/me/ajaja/module/plan/domain/Plan.java
Original file line number Diff line number Diff line change
Expand Up @@ -142,20 +142,24 @@ public void disable() {
this.status = status.disable();
}

public BaseTime getFeedbackPeriod(BaseTime current) {
public BaseTime getFeedbackPeriod(BaseTime now) {
Hejow marked this conversation as resolved.
Show resolved Hide resolved
return this.messages.stream()
.filter(message -> current.isWithinAMonth(
BaseTime.parse(
this.createdAt.getYear(),
message.getRemindDate().getRemindMonth(),
message.getRemindDate().getRemindDay(),
this.getRemindTime()))
)
.filter(message -> isWithinPeriod(now))
.findAny()
.map(message -> BaseTime.parse(this.createdAt.getYear(),
message.getRemindDate().getRemindMonth(),
message.getRemindDate().getRemindDay(),
this.getRemindTime()))
.orElseThrow(() -> new AjajaException(EXPIRED_FEEDBACK));
}

public boolean isWithinPeriod(BaseTime now) {
return this.messages.stream().anyMatch(message -> now.isWithinAMonth(
BaseTime.parse(
this.createdAt.getYear(),
message.getRemindDate().getRemindMonth(),
message.getRemindDate().getRemindDay(),
this.getRemindTime()))
);
Hejow marked this conversation as resolved.
Show resolved Hide resolved
}
}
10 changes: 10 additions & 0 deletions src/main/java/me/ajaja/module/plan/mapper/PlanMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import me.ajaja.module.ajaja.infra.AjajaEntity;
import me.ajaja.module.feedback.application.model.FeedbackPeriod;
import me.ajaja.module.feedback.application.model.PlanFeedbackInfo;
import me.ajaja.module.feedback.application.model.UpdatableFeedback;
import me.ajaja.module.plan.adapter.out.persistence.model.PlanEntity;
import me.ajaja.module.plan.domain.Content;
import me.ajaja.module.plan.domain.Message;
Expand Down Expand Up @@ -143,4 +144,13 @@ default List<FeedbackPeriod> getFeedbackPeriods(List<Message> messages) {
.map(remindDate -> new FeedbackPeriod(remindDate.getRemindMonth(), remindDate.getRemindDay()))
.toList();
}

@Mapping(source = "id", target = "planId")
@Mapping(source = "content.title", target = "title")
@Mapping(target = "deadLine", expression = "java(getRemindPeriod(plan))")
UpdatableFeedback toFeedbackModel(Plan plan);

default BaseTime getRemindPeriod(Plan plan) {
return plan.getFeedbackPeriod(BaseTime.now());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public RemindResponse.RemindInfo findByUserIdAndPlanId(Long userId, Long planId)
Plan plan = findTargetPort.findByUserIdAndPlanId(userId, planId);

List<RemindResponse.Message> messages = plan.getMessages()
.stream().map(mapper::toMessage)
.stream().map(message ->
mapper.toMessage(message, plan.getCreatedAt().getYear(), plan.getRemindTime()))
.toList();

return mapper.toRemindInfo(plan, messages);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ public interface RemindInfoMapper {
@Mapping(source = "plan.info.remindDate", target = "remindDate")
RemindResponse.RemindInfo toRemindInfo(Plan plan, List<RemindResponse.Message> messageResponses);

@Mapping(target = "reminded", expression = "java(isReminded(message.getRemindDate()))")
@Mapping(source = "remindDate.remindMonth", target = "remindMonth")
@Mapping(source = "remindDate.remindDay", target = "remindDay")
@Mapping(source = "content", target = "remindMessage")
RemindResponse.Message toMessage(Message message);
@Mapping(target = "reminded", expression = "java(isReminded(message.getRemindDate(),planYear,remindHour))")
@Mapping(source = "message.remindDate.remindMonth", target = "remindMonth")
@Mapping(source = "message.remindDate.remindDay", target = "remindDay")
@Mapping(source = "message.content", target = "remindMessage")
RemindResponse.Message toMessage(Message message, int planYear, int remindHour);

default boolean isReminded(RemindDate date) {
default boolean isReminded(RemindDate date, int planYear, int remindHour) {
BaseTime now = BaseTime.now();
BaseTime sendDate = BaseTime.parse(2024, date.getRemindMonth(), date.getRemindDay(), 9); // todo:수정
BaseTime sendDate = BaseTime.parse(planYear, date.getRemindMonth(), date.getRemindDay(), remindHour);
return now.isAfter(sendDate);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import me.ajaja.module.auth.application.port.in.ReissueTokenUseCase;
import me.ajaja.module.feedback.application.LoadFeedbackInfoService;
import me.ajaja.module.feedback.application.LoadTotalAchieveService;
import me.ajaja.module.feedback.application.LoadUpdatableFeedbackService;
import me.ajaja.module.feedback.application.UpdateFeedbackService;
import me.ajaja.module.footprint.application.port.in.CreateFootprintUseCase;
import me.ajaja.module.plan.application.port.in.CreatePlanUseCase;
Expand Down Expand Up @@ -161,6 +162,8 @@ protected static Stream<Arguments> authenticationFailResults() {
protected LoadTotalAchieveService loadTotalAchieveService;
@MockBean
protected LoadFeedbackInfoService loadFeedbackInfoService;
@MockBean
protected LoadUpdatableFeedbackService loadUpdatableFeedbackService;

// Remind
@MockBean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package me.ajaja.module.feedback.presentation;

import static org.mockito.ArgumentMatchers.*;
import static org.mockito.BDDMockito.*;
import static org.springframework.http.HttpStatus.*;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import java.util.List;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;

import me.ajaja.common.annotation.ApiTest;
import me.ajaja.common.annotation.ParameterizedApiTest;
import me.ajaja.common.support.WebMvcTestSupport;
import me.ajaja.common.util.ApiTag;
import me.ajaja.common.util.RestDocument;
import me.ajaja.global.exception.AjajaException;
import me.ajaja.global.exception.ErrorCode;
import me.ajaja.module.feedback.dto.FeedbackResponse;

public class GetUpdatableFeedbacksTest extends WebMvcTestSupport {
private final List<FeedbackResponse.UpdatableFeedback> response
= List.of(
new FeedbackResponse.UpdatableFeedback("계획1", 1L, 0, 5, 1),
new FeedbackResponse.UpdatableFeedback("계획2", 2L, 13, 5, 12)
);

@ApiTest
@DisplayName("업데이트 가능한 피드백 목록들을 불러온다.")
void getUpdatableFeedbacks_Success_WithNoException() throws Exception {
// given
given(loadUpdatableFeedbackService.loadUpdatableFeedbacksByUserId(anyLong(), any())).willReturn(response);

// when
var result = mockMvc.perform(get(FEEDBACK_END_POINT + "/updatable")
.header(HttpHeaders.AUTHORIZATION, BEARER_TOKEN)
.contentType(MediaType.APPLICATION_JSON));

// then
result.andExpectAll(
status().isOk(),
jsonPath("$.success").value(Boolean.TRUE),
jsonPath("$.data[0].title").value(response.get(0).getTitle()),
jsonPath("$.data[0].planId").value(response.get(0).getPlanId()),
jsonPath("$.data[0].remainPeriod").value(response.get(0).getRemainPeriod()),
jsonPath("$.data[0].month").value(response.get(0).getMonth()),
jsonPath("$.data[0].date").value(response.get(0).getDate()),
jsonPath("$.data[1].title").value(response.get(1).getTitle()),
jsonPath("$.data[1].planId").value(response.get(1).getPlanId()),
jsonPath("$.data[1].remainPeriod").value(response.get(1).getRemainPeriod()),
jsonPath("$.data[1].month").value(response.get(1).getMonth()),
jsonPath("$.data[1].date").value(response.get(1).getDate())
);

// docs
result.andDo(
RestDocument.builder()
.identifier("get-updatable-feedback")
.tag(ApiTag.FEEDBACK)
.summary("진행하지 않은 피드백 불러오기")
.description("메인페이지에서 진행하지 않은 피드백들을 불러온다.")
.secured(true)
.result(result)
.generateDocs()
);
}

@ParameterizedApiTest
@MethodSource("authenticationFailResults")
@DisplayName("토큰 검증에 실패하면 400에러를 반환한다.")
void getFeedbackInfo_Fail_ByInvalidToken(ErrorCode errorCode, String identifier) throws Exception {
// given
AjajaException tokenException = new AjajaException(errorCode);
doThrow(tokenException).when(loadUpdatableFeedbackService).loadUpdatableFeedbacksByUserId(anyLong(), any());

// when
var result = mockMvc.perform(get(FEEDBACK_END_POINT + "/updatable")
.header(HttpHeaders.AUTHORIZATION, BEARER_TOKEN)
.contentType(MediaType.APPLICATION_JSON));

// then
result.andExpectAll(
status().isBadRequest(),
jsonPath("$.httpStatus").value(BAD_REQUEST.name()),
jsonPath("$.errorName").value(errorCode.name()),
jsonPath("$.errorMessage").value(errorCode.getMessage())
);

// docs
result.andDo(
RestDocument.builder()
.identifier("get-updatable-feedbacks-fail-" + identifier)
.tag(ApiTag.FEEDBACK)
.secured(true)
.result(result)
.generateDocs());
}
}
Loading
Loading