diff --git a/Alarm-Module/src/main/java/com/pawith/alarmmodule/exception/AlarmError.java b/Alarm-Module/src/main/java/com/pawith/alarmmodule/exception/AlarmError.java index dd668455..1ca2520f 100644 --- a/Alarm-Module/src/main/java/com/pawith/alarmmodule/exception/AlarmError.java +++ b/Alarm-Module/src/main/java/com/pawith/alarmmodule/exception/AlarmError.java @@ -1,23 +1,19 @@ package com.pawith.alarmmodule.exception; import com.pawith.commonmodule.exception.Error; -import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; -@AllArgsConstructor +@Getter +@RequiredArgsConstructor public enum AlarmError implements Error { - DEVICE_TOKEN_NOT_FOUND("FCM 디바이스 토큰이 없습니다.", 5000), - FCM_SEND_ERROR("FCM 전송에 실패하였습니다.", 5001),; + DEVICE_TOKEN_NOT_FOUND("FCM 디바이스 토큰이 없습니다.", 5000, HttpStatus.NOT_FOUND), + FCM_SEND_ERROR("FCM 전송에 실패하였습니다.", 5001, HttpStatus.INTERNAL_SERVER_ERROR), + ; - private String message; - private Integer errorCode; - - @Override - public String getMessage() { - return message; - } - - @Override - public int getErrorCode() { - return errorCode; - } + private final String message; + private final int errorCode; + private final HttpStatusCode httpStatusCode; } diff --git a/Common-Module/src/main/java/com/pawith/commonmodule/cache/operators/SetOperator.java b/Common-Module/src/main/java/com/pawith/commonmodule/cache/operators/SetOperator.java index ec436d97..43708e46 100644 --- a/Common-Module/src/main/java/com/pawith/commonmodule/cache/operators/SetOperator.java +++ b/Common-Module/src/main/java/com/pawith/commonmodule/cache/operators/SetOperator.java @@ -8,6 +8,8 @@ public interface SetOperator { void addWithExpire(K k, long expire, TimeUnit timeUnit); + void addWithExpireAfterToday(K k); + void remove(K k); boolean contains(K k); diff --git a/Common-Module/src/main/java/com/pawith/commonmodule/cache/operators/impl/DefaultSetOperator.java b/Common-Module/src/main/java/com/pawith/commonmodule/cache/operators/impl/DefaultSetOperator.java index be901c50..ab6b9f23 100644 --- a/Common-Module/src/main/java/com/pawith/commonmodule/cache/operators/impl/DefaultSetOperator.java +++ b/Common-Module/src/main/java/com/pawith/commonmodule/cache/operators/impl/DefaultSetOperator.java @@ -3,17 +3,25 @@ import com.pawith.commonmodule.cache.operators.SetOperator; import net.jodah.expiringmap.ExpirationPolicy; +import java.time.Duration; +import java.time.LocalDateTime; import java.util.concurrent.TimeUnit; -public class DefaultSetOperator extends ExpiredStorage implements SetOperator { +public class DefaultSetOperator extends ExpiredStorage implements SetOperator { @Override public void add(K k) { - storage.put(k,k); + storage.put(k, k); } @Override public void addWithExpire(K k, long expire, TimeUnit timeUnit) { - storage.put(k,k,ExpirationPolicy.CREATED,expire, TimeUnit.MINUTES); + storage.put(k, k, ExpirationPolicy.CREATED, expire, TimeUnit.MINUTES); + } + + @Override + public void addWithExpireAfterToday(K k) { + final long expiredDuration = Duration.between(LocalDateTime.now(), LocalDateTime.now().plusDays(1)).toMinutes(); + storage.put(k, k, ExpirationPolicy.CREATED, expiredDuration, TimeUnit.MINUTES); } @Override diff --git a/Common-Module/src/main/java/com/pawith/commonmodule/exception/BusinessException.java b/Common-Module/src/main/java/com/pawith/commonmodule/exception/BusinessException.java index ee26725a..7128a281 100644 --- a/Common-Module/src/main/java/com/pawith/commonmodule/exception/BusinessException.java +++ b/Common-Module/src/main/java/com/pawith/commonmodule/exception/BusinessException.java @@ -1,6 +1,7 @@ package com.pawith.commonmodule.exception; import lombok.Getter; +import org.springframework.http.HttpStatusCode; @Getter public class BusinessException extends RuntimeException{ @@ -17,4 +18,8 @@ public String getMessage(){ public int getErrorCode(){ return error.getErrorCode(); } + + public HttpStatusCode getHttpStatusCode(){ + return error.getHttpStatusCode(); + } } diff --git a/Common-Module/src/main/java/com/pawith/commonmodule/exception/Error.java b/Common-Module/src/main/java/com/pawith/commonmodule/exception/Error.java index 0e786d59..ccaf2d9d 100644 --- a/Common-Module/src/main/java/com/pawith/commonmodule/exception/Error.java +++ b/Common-Module/src/main/java/com/pawith/commonmodule/exception/Error.java @@ -1,6 +1,11 @@ package com.pawith.commonmodule.exception; +import org.springframework.http.HttpStatusCode; + + public interface Error { String getMessage(); int getErrorCode(); + + HttpStatusCode getHttpStatusCode(); } diff --git a/Common-Module/src/main/java/com/pawith/commonmodule/exception/GlobalExceptionHandler.java b/Common-Module/src/main/java/com/pawith/commonmodule/exception/GlobalExceptionHandler.java index bc9916c9..4439cd63 100644 --- a/Common-Module/src/main/java/com/pawith/commonmodule/exception/GlobalExceptionHandler.java +++ b/Common-Module/src/main/java/com/pawith/commonmodule/exception/GlobalExceptionHandler.java @@ -1,6 +1,5 @@ package com.pawith.commonmodule.exception; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -11,6 +10,6 @@ public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public ResponseEntity handleJwtException(BusinessException e) { final ErrorResponse errorResponse = ErrorResponse.from(e); - return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); + return new ResponseEntity<>(errorResponse, e.getHttpStatusCode()); } } diff --git a/Domain-Module/Auth-Module/Auth-Application/src/main/java/com/pawith/authapplication/service/impl/ReissueUseCaseImpl.java b/Domain-Module/Auth-Module/Auth-Application/src/main/java/com/pawith/authapplication/service/impl/ReissueUseCaseImpl.java index 8586dca6..fb6054be 100644 --- a/Domain-Module/Auth-Module/Auth-Application/src/main/java/com/pawith/authapplication/service/impl/ReissueUseCaseImpl.java +++ b/Domain-Module/Auth-Module/Auth-Application/src/main/java/com/pawith/authapplication/service/impl/ReissueUseCaseImpl.java @@ -29,6 +29,7 @@ public class ReissueUseCaseImpl implements ReissueUseCase { @Override public TokenReissueResponse reissue(String refreshTokenHeader) { final String refreshToken = TokenExtractUtils.extractToken(refreshTokenHeader); + jwtProvider.validateToken(refreshToken, TokenType.REFRESH_TOKEN); final String userEmail = jwtProvider.extractEmailFromToken(refreshToken, TokenType.REFRESH_TOKEN); return reissueToken(refreshToken, userEmail); } diff --git a/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/exception/AuthError.java b/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/exception/AuthError.java index 7efa2b30..ced46c11 100644 --- a/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/exception/AuthError.java +++ b/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/exception/AuthError.java @@ -2,22 +2,22 @@ import com.pawith.commonmodule.exception.Error; import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; @Getter +@RequiredArgsConstructor public enum AuthError implements Error { - INVALID_TOKEN("유효하지 않은 토큰입니다.", 1000), - EXPIRED_TOKEN("만료된 토큰입니다.", 1001), - NOT_EXIST_TOKEN("토큰이 존재하지 않습니다.", 1002), - INVALID_AUTHORIZATION_TYPE("유효하지 않은 Authorization Type 입니다.", 1003), - EMPTY_AUTHORIZATION_HEADER("Authorization Header가 비어있습니다.", 1004), - OAUTH_FAIL("OAuth 인증에 실패하였습니다.", 1005); + INVALID_TOKEN("유효하지 않은 토큰입니다.", 1000, HttpStatus.BAD_REQUEST), + EXPIRED_TOKEN("만료된 토큰입니다.", 1001, HttpStatus.BAD_REQUEST), + NOT_EXIST_TOKEN("토큰이 존재하지 않습니다.", 1002, HttpStatus.BAD_REQUEST), + INVALID_AUTHORIZATION_TYPE("유효하지 않은 Authorization Type 입니다.", 1003, HttpStatus.BAD_REQUEST), + EMPTY_AUTHORIZATION_HEADER("Authorization Header가 비어있습니다.", 1004, HttpStatus.BAD_REQUEST), + OAUTH_FAIL("OAuth 인증에 실패하였습니다.", 1005, HttpStatus.BAD_REQUEST); private final String message; private final int errorCode; - - AuthError(String message, int errorCode) { - this.message = message; - this.errorCode = errorCode; - } + private final HttpStatusCode httpStatusCode; } diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/IncompleteTodoCountNotificationHandler.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/IncompleteTodoCountNotificationHandler.java index 9a4ee387..4271947e 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/IncompleteTodoCountNotificationHandler.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/IncompleteTodoCountNotificationHandler.java @@ -10,15 +10,16 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Component; import java.util.List; import java.util.concurrent.TimeUnit; @Slf4j -//@Component +@Component public class IncompleteTodoCountNotificationHandler extends AbstractBatchSchedulingHandler { private static final Integer BATCH_SIZE = 100; - private static final String CRON_EXPRESSION = "0 0 0 0 0 0"; + private static final String CRON_EXPRESSION = "0 0 20 * * *"; // 매일 20시에 실행 private static final String NOTIFICATION_MESSAGE = "[%s] 오늘이 지나기 전, %s님에게 남은 %d개의 todo를 완료해주세요!"; private final RegisterRepository registerRepository; @@ -43,6 +44,7 @@ protected List extractBatchData(Pageable pageable) { protected void processBatch(List executionResult) { cachingUserInfo(executionResult); final List notificationEventList = executionResult.stream() + .filter(incompleteTodoCountInfoDao -> incompleteTodoCountInfoDao.getIncompleteTodoCount() > 0) .map(incompleteTodoCountInfoDao -> { final String userNickname = valueOperator.get(incompleteTodoCountInfoDao.getUserId()); final String message = String.format(NOTIFICATION_MESSAGE, incompleteTodoCountInfoDao.getTodoTeamName(), userNickname, incompleteTodoCountInfoDao.getIncompleteTodoCount()); diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/TodoCompletionCheckOnTodoHandler.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/TodoCompletionCheckOnTodoHandler.java index c408560c..dc7d91ba 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/TodoCompletionCheckOnTodoHandler.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/TodoCompletionCheckOnTodoHandler.java @@ -1,6 +1,6 @@ package com.pawith.todoapplication.handler; -import com.pawith.todoapplication.handler.event.TodoCompletionCheckEvent; +import com.pawith.todoapplication.handler.event.TodoAssignStatusChangeEvent; import com.pawith.tododomain.entity.Assign; import com.pawith.tododomain.entity.Todo; import com.pawith.tododomain.service.AssignQueryService; @@ -23,11 +23,11 @@ public class TodoCompletionCheckOnTodoHandler { private final TodoQueryService todoQueryService; @EventListener - public void changeTodoStatus(TodoCompletionCheckEvent todoCompletionCheckEvent) throws InterruptedException { + public void changeTodoStatus(TodoAssignStatusChangeEvent todoAssignStatusChangeEvent) throws InterruptedException { while(true) { try { - final List assigns = assignQueryService.findAllAssignByTodoId(todoCompletionCheckEvent.todoId()); - final Todo todo = todoQueryService.findTodoByTodoId(todoCompletionCheckEvent.todoId()); + final List assigns = assignQueryService.findAllAssignByTodoId(todoAssignStatusChangeEvent.todoId()); + final Todo todo = todoQueryService.findTodoByTodoId(todoAssignStatusChangeEvent.todoId()); final boolean isAllCompleteTodo = assigns.stream().allMatch(Assign::isCompleted); todo.updateCompletionStatus(isAllCompleteTodo); break; diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/TodoRemindHandler.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/TodoRemindHandler.java new file mode 100644 index 00000000..faf0a231 --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/TodoRemindHandler.java @@ -0,0 +1,64 @@ +package com.pawith.todoapplication.handler; + +import com.pawith.commonmodule.cache.CacheTemplate; +import com.pawith.commonmodule.event.MultiNotificationEvent; +import com.pawith.commonmodule.event.NotificationEvent; +import com.pawith.todoapplication.handler.event.TodoAssignStatusChangeEvent; +import com.pawith.tododomain.entity.CompletionStatus; +import com.pawith.tododomain.repository.dao.IncompleteAssignInfoDao; +import com.pawith.tododomain.service.AssignQueryService; +import com.pawith.userdomain.entity.User; +import com.pawith.userdomain.service.UserQueryService; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; + +@Component +@RequiredArgsConstructor +@Transactional +public class TodoRemindHandler { + private static final String NOTIFICATION_MESSAGE = "'%s'을 %d명이 완료했어요! %s님도 얼른 완료해볼까요?"; + private static final String REMIND_CACHE_KEY = "Remind:%s"; + + private final AssignQueryService assignQueryService; + private final UserQueryService userQueryService; + private final CacheTemplate cacheTemplate; + private final ApplicationEventPublisher applicationEventPublisher; + + @EventListener + public void remindTodo(final TodoAssignStatusChangeEvent todoAssignStatusChangeEvent){ + final Long todoId = todoAssignStatusChangeEvent.todoId(); + final String cacheKey = String.format(REMIND_CACHE_KEY, todoId); + final long completeAssignNumber = assignQueryService.countAssignByTodoIdAndCompleteStatus(todoId, CompletionStatus.COMPLETE); + if(isRemindable(todoId, completeAssignNumber)&& !cacheTemplate.opsForSet().contains(cacheKey)){ + cacheTemplate.opsForSet().addWithExpireAfterToday(cacheKey); + final List todoNotificationList = buildNotificationEvent(todoId, completeAssignNumber); + applicationEventPublisher.publishEvent(new MultiNotificationEvent(todoNotificationList)); + } + } + + private List buildNotificationEvent(final Long todoId, final long completeAssignNumber) { + final List incompleteAssignInfoDaoList = assignQueryService.findAllIncompleteAssignInfoByTodoId(todoId); + final List incompleteTodoUserIds = incompleteAssignInfoDaoList.stream() + .map(IncompleteAssignInfoDao::getUserId) + .toList(); + final Map incompleteTodoUserMap = userQueryService.findMapWithUserIdKeyByIds(incompleteTodoUserIds); + return incompleteAssignInfoDaoList.stream() + .map(incompleteAssignInfo -> { + final User incompleteTodoUser = incompleteTodoUserMap.get(incompleteAssignInfo.getUserId()); + final String notificationMessage = String.format(NOTIFICATION_MESSAGE, incompleteAssignInfo.getTodoDescription(), completeAssignNumber, incompleteTodoUser.getNickname()); + return new NotificationEvent(incompleteTodoUser.getId(),incompleteAssignInfo.getTodoTeamName(), notificationMessage, todoId); + }) + .toList(); + } + + private boolean isRemindable(final Long todoId, final long completeAssignNumber){ + final long totalAssignNumber = assignQueryService.countAssignByTodoId(todoId); + return (float) completeAssignNumber >= (float) totalAssignNumber * 0.5; + } +} diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/UserAccountDeleteOnTodoHandler.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/UserAccountDeleteOnTodoHandler.java index 53fb80ea..fb1ed868 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/UserAccountDeleteOnTodoHandler.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/UserAccountDeleteOnTodoHandler.java @@ -5,7 +5,6 @@ import com.pawith.tododomain.service.AssignDeleteService; import com.pawith.tododomain.service.RegisterDeleteService; import com.pawith.tododomain.service.RegisterQueryService; -import com.pawith.tododomain.service.RegisterValidateService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; @@ -22,7 +21,6 @@ public class UserAccountDeleteOnTodoHandler { private final RegisterQueryService registerQueryService; private final RegisterDeleteService registerDeleteService; - private final RegisterValidateService registerValidateService; private final AssignDeleteService assignDeleteService; @EventListener diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/event/TodoAssignStatusChangeEvent.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/event/TodoAssignStatusChangeEvent.java new file mode 100644 index 00000000..75d11af8 --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/event/TodoAssignStatusChangeEvent.java @@ -0,0 +1,4 @@ +package com.pawith.todoapplication.handler.event; + +public record TodoAssignStatusChangeEvent(Long todoId) { +} diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/event/TodoCompletionCheckEvent.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/event/TodoCompletionCheckEvent.java deleted file mode 100644 index e4900a9a..00000000 --- a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/event/TodoCompletionCheckEvent.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.pawith.todoapplication.handler.event; - -public record TodoCompletionCheckEvent(Long todoId) { -} diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/AssignChangeUseCase.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/AssignChangeUseCase.java index 37682395..25c2fbe5 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/AssignChangeUseCase.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/AssignChangeUseCase.java @@ -4,7 +4,7 @@ import com.pawith.commonmodule.annotation.ApplicationService; import com.pawith.todoapplication.dto.request.AssignChangeRequest; -import com.pawith.todoapplication.handler.event.TodoCompletionCheckEvent; +import com.pawith.todoapplication.handler.event.TodoAssignStatusChangeEvent; import com.pawith.tododomain.entity.Assign; import com.pawith.tododomain.entity.Register; import com.pawith.tododomain.entity.Todo; @@ -38,7 +38,7 @@ public void changeAssignStatus(Long todoId){ final User user = userUtils.getAccessUser(); final Assign assign = assignQueryService.findAssignByTodoIdAndUserId(todo.getId(), user.getId()); assign.updateCompletionStatus(); - applicationEventPublisher.publishEvent(new TodoCompletionCheckEvent(todo.getId())); + applicationEventPublisher.publishEvent(new TodoAssignStatusChangeEvent(todo.getId())); } public void changeAssign(Long todoId, AssignChangeRequest request) { diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoGetUseCase.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoGetUseCase.java index 2b0fb0b3..af4c266a 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoGetUseCase.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoGetUseCase.java @@ -34,11 +34,10 @@ public class TodoGetUseCase { private final AssignQueryService assignQueryService; private final TodoNotificationQueryService todoNotificationQueryService; - public ListResponse getTodoListByTodoTeamId(final Long todoTeamId) { final User user = userUtils.getAccessUser(); final List allAssigns = assignQueryService.findAllByUserIdAndTodoTeamIdAndScheduledDate(user.getId(), todoTeamId); - List todoInfoResponses = allAssigns.stream() + final List todoInfoResponses = allAssigns.stream() .map(assign -> { final Todo todo = assign.getTodo(); final Category category = todo.getCategory(); @@ -49,13 +48,13 @@ public ListResponse getTodoListByTodoTeamId(final Long todoTea return ListResponse.from(todoInfoResponses); } - public ListResponse getTodoListByCategoryId(Long categoryId, LocalDate moveDate) { + public ListResponse getTodoListByCategoryId(final Long categoryId,final LocalDate moveDate) { final User accessUser = userUtils.getAccessUser(); final List assignList = assignQueryService.findAllAssignByCategoryIdAndScheduledDate(categoryId, moveDate); final Map> todoAssignMap = AssignUtils.convertToTodoAssignMap(assignList); - final List todoList = List.copyOf(todoAssignMap.keySet()); + final Collection todoList = todoAssignMap.keySet(); final Map todoNotificationMap = todoNotificationQueryService.findMapTodoIdKeyAndTodoNotificationValueByTodoIdsAndUserId(todoList, accessUser.getId()); @@ -74,12 +73,12 @@ public ListResponse getTodoListByCategoryId(Long catego return ListResponse.from(subTodoResponseList); } - private List getAssignUserInfoResponses(List assigns, Map userMap, Long accessUserId, AtomicReference isAssigned) { + private List getAssignUserInfoResponses(final List assigns, final Map userMap, final Long accessUserId, final AtomicReference isAssigned) { return assigns.stream() .map(assign -> { final Register register = assign.getRegister(); final User findUser = userMap.get(register.getUserId()); - if (Objects.equals(findUser.getId(), accessUserId)) { + if (findUser.isMatchingUser(accessUserId)) { isAssigned.set(true); } return new AssignUserInfoResponse(findUser.getId(), findUser.getNickname(), assign.getCompletionStatus()); diff --git a/Domain-Module/Todo-Module/Todo-Application/src/test/java/com/pawith/todoapplication/handler/TodoCompletionCheckOnTodoHandlerTest.java b/Domain-Module/Todo-Module/Todo-Application/src/test/java/com/pawith/todoapplication/handler/TodoCompletionCheckOnTodoHandlerTest.java index cdec8c42..c7c09fd3 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/test/java/com/pawith/todoapplication/handler/TodoCompletionCheckOnTodoHandlerTest.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/test/java/com/pawith/todoapplication/handler/TodoCompletionCheckOnTodoHandlerTest.java @@ -3,7 +3,7 @@ import static org.mockito.BDDMockito.given; import com.pawith.commonmodule.UnitTestConfig; -import com.pawith.todoapplication.handler.event.TodoCompletionCheckEvent; +import com.pawith.todoapplication.handler.event.TodoAssignStatusChangeEvent; import com.pawith.commonmodule.utils.FixtureMonkeyUtils; import com.pawith.tododomain.entity.Assign; import com.pawith.tododomain.entity.CompletionStatus; @@ -36,8 +36,8 @@ void init(){ @DisplayName("Todo 완료 여부 변경 테스트-담당자가 모두 완료하면 Todo 완료") void changeTodoStatus() throws InterruptedException { // given - final TodoCompletionCheckEvent todoCompletionCheckEvent = FixtureMonkeyUtils.getConstructBasedFixtureMonkey() - .giveMeOne(TodoCompletionCheckEvent.class); + final TodoAssignStatusChangeEvent todoAssignStatusChangeEvent = FixtureMonkeyUtils.getConstructBasedFixtureMonkey() + .giveMeOne(TodoAssignStatusChangeEvent.class); final List mockAssigns = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey() .giveMeBuilder(Assign.class) .set("completionStatus", CompletionStatus.COMPLETE) @@ -46,10 +46,10 @@ void changeTodoStatus() throws InterruptedException { .giveMeBuilder(Todo.class) .set("completionStatus", CompletionStatus.INCOMPLETE) .sample(); - given(assignQueryService.findAllAssignByTodoId(todoCompletionCheckEvent.todoId())).willReturn(mockAssigns); - given(todoQueryService.findTodoByTodoId(todoCompletionCheckEvent.todoId())).willReturn(mockTodo); + given(assignQueryService.findAllAssignByTodoId(todoAssignStatusChangeEvent.todoId())).willReturn(mockAssigns); + given(todoQueryService.findTodoByTodoId(todoAssignStatusChangeEvent.todoId())).willReturn(mockTodo); // when - todoCompletionCheckOnTodoHandler.changeTodoStatus(todoCompletionCheckEvent); + todoCompletionCheckOnTodoHandler.changeTodoStatus(todoAssignStatusChangeEvent); // then Assertions.assertEquals(CompletionStatus.COMPLETE, mockTodo.getCompletionStatus()); } @@ -58,8 +58,8 @@ void changeTodoStatus() throws InterruptedException { @DisplayName("Todo 완료 여부 변경 테스트-담당자가 하나라도 미완료면 Todo 미완료") void changeTodoStatusWithIncompletedAssignee() throws InterruptedException { // given - final TodoCompletionCheckEvent todoCompletionCheckEvent = FixtureMonkeyUtils.getConstructBasedFixtureMonkey() - .giveMeOne(TodoCompletionCheckEvent.class); + final TodoAssignStatusChangeEvent todoAssignStatusChangeEvent = FixtureMonkeyUtils.getConstructBasedFixtureMonkey() + .giveMeOne(TodoAssignStatusChangeEvent.class); final List mockAssigns = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey() .giveMeBuilder(Assign.class) .set("completionStatus", CompletionStatus.COMPLETE) @@ -73,10 +73,10 @@ void changeTodoStatusWithIncompletedAssignee() throws InterruptedException { .giveMeBuilder(Todo.class) .set("completionStatus", CompletionStatus.INCOMPLETE) .sample(); - given(assignQueryService.findAllAssignByTodoId(todoCompletionCheckEvent.todoId())).willReturn(mockAssigns); - given(todoQueryService.findTodoByTodoId(todoCompletionCheckEvent.todoId())).willReturn(mockTodo); + given(assignQueryService.findAllAssignByTodoId(todoAssignStatusChangeEvent.todoId())).willReturn(mockAssigns); + given(todoQueryService.findTodoByTodoId(todoAssignStatusChangeEvent.todoId())).willReturn(mockTodo); // when - todoCompletionCheckOnTodoHandler.changeTodoStatus(todoCompletionCheckEvent); + todoCompletionCheckOnTodoHandler.changeTodoStatus(todoAssignStatusChangeEvent); // then Assertions.assertEquals(CompletionStatus.INCOMPLETE, mockTodo.getCompletionStatus()); } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/exception/TodoError.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/exception/TodoError.java index d9a2ede9..96851e76 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/exception/TodoError.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/exception/TodoError.java @@ -2,27 +2,27 @@ import com.pawith.commonmodule.exception.Error; import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; @Getter +@RequiredArgsConstructor public enum TodoError implements Error { - TODO_TEAM_NOT_FOUND("패밀리를 찾을 수 없습니다.", 3000), - NOT_REGISTER_USER("패밀리에 등록되지 않은 사용자입니다.", 3001), - REGISTER_NOT_FOUND("등록된 패밀리를 찾을 수 없습니다.", 3002), - ALREADY_REGISTER_TODO_TEAM("이미 등록된 패밀리입니다.", 3003), - TODO_NOT_FOUND("Todo를 찾을 수 없습니다.", 3004), - WRONG_SCHEDULED_DATE("예정일이 잘못되었습니다.", 3005), - CATEGORY_NOT_FOUND("카테고리를 찾을 수 없습니다.", 3006), - CANNOT_PRESIDENT_UNREGISTRABLE("팀장 탈퇴시 탈퇴 후 팀장이 1명 이상 있어야 합니다.", 3007), - CANNOT_CHANGE_AUTHORITY("권한을 변경할 수 없습니다", 3008), - ASSIGN_NOT_FOUND("할당된 Todo를 찾을 수 없습니다.", 3009), - TODO_MODIFICATION_NOT_ALLOWED("Todo 수정 및 삭제가 허용되지 않습니다.", 3010), + TODO_TEAM_NOT_FOUND("패밀리를 찾을 수 없습니다.", 3000, HttpStatus.NOT_FOUND), + NOT_REGISTER_USER("패밀리에 등록되지 않은 사용자입니다.", 3001, HttpStatus.BAD_REQUEST), + REGISTER_NOT_FOUND("등록된 패밀리를 찾을 수 없습니다.", 3002, HttpStatus.NOT_FOUND), + ALREADY_REGISTER_TODO_TEAM("이미 등록된 패밀리입니다.", 3003, HttpStatus.BAD_REQUEST), + TODO_NOT_FOUND("Todo를 찾을 수 없습니다.", 3004, HttpStatus.NOT_FOUND), + WRONG_SCHEDULED_DATE("예정일이 잘못되었습니다.", 3005, HttpStatus.BAD_REQUEST), + CATEGORY_NOT_FOUND("카테고리를 찾을 수 없습니다.", 3006, HttpStatus.NOT_FOUND), + CANNOT_PRESIDENT_UNREGISTRABLE("팀장 탈퇴시 탈퇴 후 팀장이 1명 이상 있어야 합니다.", 3007, HttpStatus.BAD_REQUEST), + CANNOT_CHANGE_AUTHORITY("권한을 변경할 수 없습니다", 3008, HttpStatus.BAD_REQUEST), + ASSIGN_NOT_FOUND("할당된 Todo를 찾을 수 없습니다.", 3009, HttpStatus.NOT_FOUND), + TODO_MODIFICATION_NOT_ALLOWED("Todo 수정 및 삭제가 허용되지 않습니다.", 3010, HttpStatus.BAD_REQUEST), ; private final String message; private final int errorCode; - - TodoError(String message, int errorCode) { - this.message = message; - this.errorCode = errorCode; - } + private final HttpStatusCode httpStatusCode; } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/AssignQueryRepository.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/AssignQueryRepository.java index 1133179e..a2e35c0f 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/AssignQueryRepository.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/AssignQueryRepository.java @@ -1,6 +1,9 @@ package com.pawith.tododomain.repository; import com.pawith.tododomain.entity.Assign; +import com.pawith.tododomain.entity.CompletionStatus; +import com.pawith.tododomain.repository.dao.IncompleteAssignInfoDao; +import jakarta.annotation.Nullable; import java.time.LocalDate; import java.util.List; @@ -15,4 +18,8 @@ public interface AssignQueryRepository { List findAllByTodoIdQuery(Long todoId); List findAllByTodoIdWithRegisterFetchQuery(Long todoId); void deleteAllByCategoryIdQuery(final Long categoryId); + + Long countByTodoIdAndCompletedQuery(Long todoId, @Nullable CompletionStatus completionStatus); + + List findAllAssignInfoByTodoIdAndCompleteStatusQuery(Long todoId, CompletionStatus completionStatus); } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/dao/IncompleteAssignInfoDao.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/dao/IncompleteAssignInfoDao.java new file mode 100644 index 00000000..ee8492b4 --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/dao/IncompleteAssignInfoDao.java @@ -0,0 +1,7 @@ +package com.pawith.tododomain.repository.dao; + +public interface IncompleteAssignInfoDao { + Long getUserId(); + String getTodoTeamName(); + String getTodoDescription(); +} diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/AssignQueryService.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/AssignQueryService.java index db9aaec4..e9beafda 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/AssignQueryService.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/AssignQueryService.java @@ -2,9 +2,11 @@ import com.pawith.commonmodule.annotation.DomainService; import com.pawith.tododomain.entity.Assign; +import com.pawith.tododomain.entity.CompletionStatus; import com.pawith.tododomain.exception.AssignNotFoundException; import com.pawith.tododomain.exception.TodoError; import com.pawith.tododomain.repository.AssignRepository; +import com.pawith.tododomain.repository.dao.IncompleteAssignInfoDao; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; @@ -41,4 +43,16 @@ public List findAllAssignWithRegisterByTodoId(Long todoId) { return assignRepository.findAllByTodoIdWithRegisterFetchQuery(todoId); } + public Long countAssignByTodoIdAndCompleteStatus(Long todoId, CompletionStatus completionStatus) { + return assignRepository.countByTodoIdAndCompletedQuery(todoId, completionStatus); + } + + public Long countAssignByTodoId(Long todoId) { + return assignRepository.countByTodoIdAndCompletedQuery(todoId, null); + } + + public List findAllIncompleteAssignInfoByTodoId(Long todoId) { + return assignRepository.findAllAssignInfoByTodoIdAndCompleteStatusQuery(todoId, CompletionStatus.INCOMPLETE); + } + } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoNotificationQueryService.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoNotificationQueryService.java index efa04aa7..80aad6dd 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoNotificationQueryService.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoNotificationQueryService.java @@ -8,6 +8,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -19,7 +20,7 @@ public class TodoNotificationQueryService { private final TodoNotificationRepository todoNotificationRepository; - public Map findMapTodoIdKeyAndTodoNotificationValueByTodoIdsAndUserId(List todoList, Long userId) { + public Map findMapTodoIdKeyAndTodoNotificationValueByTodoIdsAndUserId(Collection todoList, Long userId) { final List todoIds = todoList.stream().map(Todo::getId).collect(Collectors.toList()); return todoNotificationRepository.findAllByTodoIdsAndUserIdWithInCompleteAssignQuery(todoIds, userId) .stream() diff --git a/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/dao/IncompleteAssignInfoDaoImpl.java b/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/dao/IncompleteAssignInfoDaoImpl.java new file mode 100644 index 00000000..4a7f2fbd --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/dao/IncompleteAssignInfoDaoImpl.java @@ -0,0 +1,30 @@ +package com.pawith.todoinfrastructure.dao; + +import com.pawith.tododomain.repository.dao.IncompleteAssignInfoDao; +import com.querydsl.core.annotations.QueryProjection; + +public record IncompleteAssignInfoDaoImpl( + Long userId, + String todoTeamName, + String todoDescription +) implements IncompleteAssignInfoDao { + + @QueryProjection + public IncompleteAssignInfoDaoImpl { + } + + @Override + public Long getUserId() { + return userId; + } + + @Override + public String getTodoTeamName() { + return todoTeamName; + } + + @Override + public String getTodoDescription() { + return todoDescription; + } +} diff --git a/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/AssignRepositoryImpl.java b/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/AssignRepositoryImpl.java index a48fb4fb..bc410254 100644 --- a/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/AssignRepositoryImpl.java +++ b/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/AssignRepositoryImpl.java @@ -2,20 +2,25 @@ import com.pawith.tododomain.entity.*; import com.pawith.tododomain.repository.AssignQueryRepository; +import com.pawith.todoinfrastructure.dao.IncompleteAssignInfoDaoImpl; +import com.pawith.todoinfrastructure.dao.QIncompleteAssignInfoDaoImpl; import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.annotation.Nullable; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.util.List; +import java.util.Objects; import java.util.Optional; @Repository @RequiredArgsConstructor public class AssignRepositoryImpl implements AssignQueryRepository { private final JPAQueryFactory queryFactory; + @Override public List findAllByCategoryIdAndScheduledDateQuery(Long categoryId, LocalDate scheduledDate) { final QAssign qAssign = QAssign.assign; @@ -112,4 +117,31 @@ public void deleteAllByCategoryIdQuery(final Long categoryId) { .where(qTodo.category.id.eq(categoryId)))) .execute(); } + + @Override + public Long countByTodoIdAndCompletedQuery(Long todoId, @Nullable CompletionStatus completionStatus) { + final QAssign qAssign = QAssign.assign; + return queryFactory.select(qAssign.count()) + .from(qAssign) + .where(qAssign.todo.id.eq(todoId) + , Objects.nonNull(completionStatus) ? qAssign.completionStatus.eq(completionStatus) : null) + .fetchOne(); + } + + @Override + @SuppressWarnings("unchecked") + public List findAllAssignInfoByTodoIdAndCompleteStatusQuery(Long todoId, CompletionStatus completionStatus) { + final QAssign qAssign = QAssign.assign; + final QRegister qRegister = qAssign.register; + final QTodo qTodo = qAssign.todo; + final QTodoTeam qTodoTeam = qRegister.todoTeam; + + return queryFactory.select(new QIncompleteAssignInfoDaoImpl(qRegister.userId, qTodoTeam.teamName, qTodo.description)) + .from(qAssign) + .join(qRegister) + .join(qTodo).on(qTodo.id.eq(todoId)) + .join(qTodoTeam) + .where(qAssign.completionStatus.eq(completionStatus)) + .fetch(); + } } diff --git a/Domain-Module/User-Module/User-Domain/src/main/java/com/pawith/userdomain/entity/User.java b/Domain-Module/User-Module/User-Domain/src/main/java/com/pawith/userdomain/entity/User.java index 7bf9a38e..a4fe90e9 100644 --- a/Domain-Module/User-Module/User-Domain/src/main/java/com/pawith/userdomain/entity/User.java +++ b/Domain-Module/User-Module/User-Domain/src/main/java/com/pawith/userdomain/entity/User.java @@ -69,4 +69,8 @@ public Long getJoinTerm() { LocalDate joinDate = this.createdAt.toLocalDate(); return ChronoUnit.DAYS.between(joinDate, nowDate) + 1; } + + public Boolean isMatchingUser(Long userId) { + return this.id.equals(userId); + } } diff --git a/Domain-Module/User-Module/User-Domain/src/main/java/com/pawith/userdomain/exception/UserError.java b/Domain-Module/User-Module/User-Domain/src/main/java/com/pawith/userdomain/exception/UserError.java index 5e8ccb6e..9b106563 100644 --- a/Domain-Module/User-Module/User-Domain/src/main/java/com/pawith/userdomain/exception/UserError.java +++ b/Domain-Module/User-Module/User-Domain/src/main/java/com/pawith/userdomain/exception/UserError.java @@ -2,18 +2,18 @@ import com.pawith.commonmodule.exception.Error; import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; @Getter +@RequiredArgsConstructor public enum UserError implements Error { - USER_NOT_FOUND("사용자를 찾을 수 없습니다.", 2000), - USER_AUTHORITY_NOT_FOUND("사용자 권한을 찾을 수 없습니다.", 2001), - ACCOUNT_ALREADY_EXIST("이미 가입한 계정이 있습니다", 2002); + USER_NOT_FOUND("사용자를 찾을 수 없습니다.", 2000, HttpStatus.NOT_FOUND), + USER_AUTHORITY_NOT_FOUND("사용자 권한을 찾을 수 없습니다.", 2001, HttpStatus.NOT_FOUND), + ACCOUNT_ALREADY_EXIST("이미 가입한 계정이 있습니다", 2002, HttpStatus.BAD_REQUEST); private final String message; private final int errorCode; - - UserError(String message, int errorCode) { - this.message = message; - this.errorCode = errorCode; - } + private final HttpStatusCode httpStatusCode; } diff --git a/Image-Module/src/main/java/com/pawith/imagemodule/exception/FileError.java b/Image-Module/src/main/java/com/pawith/imagemodule/exception/FileError.java index 7c0d664f..69193a60 100644 --- a/Image-Module/src/main/java/com/pawith/imagemodule/exception/FileError.java +++ b/Image-Module/src/main/java/com/pawith/imagemodule/exception/FileError.java @@ -2,18 +2,18 @@ import com.pawith.commonmodule.exception.Error; import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; @Getter +@RequiredArgsConstructor public enum FileError implements Error { - FILE_EXTENTION_ERROR("잘못된 형식의 파일입니다.", 4000), - FILE_UPLOAD_ERROR("파일 업로드에 실패했습니다.", 4001), - FILE_DELETE_ERROR("파일 삭제에 실패했습니다.", 4002); + FILE_EXTENTION_ERROR("잘못된 형식의 파일입니다.", 4000, HttpStatus.BAD_REQUEST), + FILE_UPLOAD_ERROR("파일 업로드에 실패했습니다.", 4001, HttpStatus.INTERNAL_SERVER_ERROR), + FILE_DELETE_ERROR("파일 삭제에 실패했습니다.", 4002, HttpStatus.INTERNAL_SERVER_ERROR); private final String message; private final int errorCode; - - FileError(String message, int errorCode) { - this.message = message; - this.errorCode = errorCode; - } + private final HttpStatusCode httpStatusCode; }