diff --git a/.deploy/Dockerfile b/.deploy/Dockerfile index 2e08dc56..2c3f6f6e 100644 --- a/.deploy/Dockerfile +++ b/.deploy/Dockerfile @@ -1,4 +1,4 @@ -FROM amazoncorretto:17 +FROM amazoncorretto:17-alpine-jdk ARG JAR_FILE=/Api-Module/build/libs/Api-Module-0.0.1.jar COPY ${JAR_FILE} pawith.jar ENTRYPOINT ["java","-Dcom.mysql.cj.disableAbandonedConnectionCleanup=true","-jar","/pawith.jar"] \ No newline at end of file diff --git a/.deploy/promtail-dev.yaml b/.deploy/promtail-dev.yaml index 8f6482eb..ec5d38e0 100644 --- a/.deploy/promtail-dev.yaml +++ b/.deploy/promtail-dev.yaml @@ -13,7 +13,7 @@ scrape_configs: - job_name: dev-pawith docker_sd_configs: - host: unix:///var/run/docker.sock - refresh_interval: 10s + refresh_interval: 15s static_configs: - targets: - localhost diff --git a/.deploy/promtail-prod.yaml b/.deploy/promtail-prod.yaml index 58f100cf..8a66df9d 100644 --- a/.deploy/promtail-prod.yaml +++ b/.deploy/promtail-prod.yaml @@ -13,7 +13,7 @@ scrape_configs: - job_name: prod-pawith docker_sd_configs: - host: unix:///var/run/docker.sock - refresh_interval: 10s + refresh_interval: 15s static_configs: - targets: - localhost diff --git a/.github/docs/components-Modules.png b/.github/docs/components-Modules.png index 8b547d1b..f91c249e 100644 Binary files a/.github/docs/components-Modules.png and b/.github/docs/components-Modules.png differ diff --git a/.github/workflows/CD-prod.yml b/.github/workflows/CD-prod.yml index 3cdbc9da..883db780 100644 --- a/.github/workflows/CD-prod.yml +++ b/.github/workflows/CD-prod.yml @@ -95,7 +95,7 @@ jobs: target: "/home/ubuntu" strip_components: 2 - - name: deploy docker-compose-dev push + - name: deploy docker-compose-prod push uses: appleboy/scp-action@master with: host: ${{ secrets.PROD_HOST }} diff --git a/.gitignore b/.gitignore index 1eeb6c55..015a2154 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ out/ ### application ### **/src/main/resources/application.properties **/src/main/resources/application-test.yml +**/src/main/resources/application ### resources static docs ### **/src/main/resources/static/docs/ @@ -50,4 +51,7 @@ logs/logback/* **/.jqwik-database ### firebase ### -**/src/main/resources/firebase/firebase.json +**/src/main/resources/firebase.json + +### QueryDSL ### +**/src/main/generated/ diff --git a/Alarm-Module/build.gradle b/Alarm-Module/build.gradle index 3ef0b4c8..7cf0c2c0 100644 --- a/Alarm-Module/build.gradle +++ b/Alarm-Module/build.gradle @@ -2,4 +2,5 @@ dependencies { implementation project(':Common-Module') testImplementation(testFixtures(project(':Common-Module'))) implementation project(':Domain-Module:User-Module:User-Domain') + } \ No newline at end of file diff --git a/Alarm-Module/src/docs/Alarm-API.adoc b/Alarm-Module/src/docs/Alarm-API.adoc index a3169c29..489e5c23 100644 --- a/Alarm-Module/src/docs/Alarm-API.adoc +++ b/Alarm-Module/src/docs/Alarm-API.adoc @@ -11,6 +11,11 @@ operation::alarm-controller-test/get-alarms-exist[snippets='http-request,request operation::alarm-controller-test/get-alarms[snippets='http-request,request-headers,query-parameters,http-response,response-fields'] +[[알림-일괄-조회]] +=== 알림 일괄 조회 + +operation::alarm-controller-test/patch-alarms[snippets='http-request,request-headers,http-response'] + [[device-token-저장]] === 디바이스 토큰 저장 diff --git a/Alarm-Module/src/main/java/com/pawith/alarmmodule/controller/AlarmController.java b/Alarm-Module/src/main/java/com/pawith/alarmmodule/controller/AlarmController.java index b8ebfc7f..87b88734 100644 --- a/Alarm-Module/src/main/java/com/pawith/alarmmodule/controller/AlarmController.java +++ b/Alarm-Module/src/main/java/com/pawith/alarmmodule/controller/AlarmController.java @@ -1,12 +1,13 @@ package com.pawith.alarmmodule.controller; import com.pawith.alarmmodule.service.AlarmService; -import com.pawith.alarmmodule.service.dto.response.AlarmExistenceResponse; +import com.pawith.alarmmodule.service.dto.response.UnReadAlarmResponse; import com.pawith.alarmmodule.service.dto.response.AlarmInfoResponse; import com.pawith.commonmodule.response.SliceResponse; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.RestController; @RestController @@ -15,8 +16,8 @@ public class AlarmController { private final AlarmService alarmService; @GetMapping("/alarms/exist") - public AlarmExistenceResponse getAlarmsExist(){ - return alarmService.getAlarmsExist(); + public UnReadAlarmResponse getAlarmsExist(){ + return alarmService.getUnreadAlarmCount(); } @GetMapping("/alarms") @@ -24,4 +25,9 @@ public SliceResponse getAlarms(Pageable pageable){ return alarmService.getAlarms(pageable); } + @PatchMapping("/alarms") + public void patchAlarms(){ + alarmService.changeAllAlarmStatusToRead(); + } + } diff --git a/Alarm-Module/src/main/java/com/pawith/alarmmodule/entity/Alarm.java b/Alarm-Module/src/main/java/com/pawith/alarmmodule/entity/Alarm.java index f5cbd6fc..bdd99feb 100644 --- a/Alarm-Module/src/main/java/com/pawith/alarmmodule/entity/Alarm.java +++ b/Alarm-Module/src/main/java/com/pawith/alarmmodule/entity/Alarm.java @@ -2,7 +2,6 @@ import com.pawith.alarmmodule.entity.vo.AlarmBody; import com.pawith.commonmodule.domain.BaseEntity; -import com.pawith.commonmodule.enums.AlarmCategory; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Builder; @@ -22,15 +21,14 @@ public class Alarm extends BaseEntity { private AlarmBody alarmBody; private Boolean isRead = Boolean.FALSE; - @Enumerated(EnumType.STRING) - private AlarmCategory alarmCategory; + private String alarmCategory; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "alarm_user_id") private AlarmUser alarmUser; @Builder - public Alarm(AlarmBody alarmBody, AlarmCategory alarmCategory, AlarmUser alarmUser) { + public Alarm(AlarmBody alarmBody, String alarmCategory, AlarmUser alarmUser) { this.alarmBody = alarmBody; this.alarmCategory = alarmCategory; this.alarmUser = alarmUser; diff --git a/Alarm-Module/src/main/java/com/pawith/alarmmodule/entity/AlarmUser.java b/Alarm-Module/src/main/java/com/pawith/alarmmodule/entity/AlarmUser.java index d9add1b7..df704d04 100644 --- a/Alarm-Module/src/main/java/com/pawith/alarmmodule/entity/AlarmUser.java +++ b/Alarm-Module/src/main/java/com/pawith/alarmmodule/entity/AlarmUser.java @@ -1,14 +1,13 @@ package com.pawith.alarmmodule.entity; import com.pawith.commonmodule.domain.BaseEntity; +import com.pawith.commonmodule.util.DomainFieldUtils; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.util.Objects; - @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @@ -29,6 +28,9 @@ public AlarmUser(String deviceToken, Long userId) { } public void updateDeviceToken(String deviceToken) { - this.deviceToken = Objects.requireNonNull(deviceToken, "deviceToken must be not null"); + this.deviceToken = DomainFieldUtils.DomainValidateBuilder.builder(String.class) + .currentValue(this.deviceToken) + .newValue(deviceToken) + .validate(); } } diff --git a/Alarm-Module/src/main/java/com/pawith/alarmmodule/fcm/FcmSendMessageHandler.java b/Alarm-Module/src/main/java/com/pawith/alarmmodule/fcm/FcmSendMessageHandler.java new file mode 100644 index 00000000..b0040dd2 --- /dev/null +++ b/Alarm-Module/src/main/java/com/pawith/alarmmodule/fcm/FcmSendMessageHandler.java @@ -0,0 +1,53 @@ +package com.pawith.alarmmodule.fcm; + +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.FirebaseMessagingException; +import com.google.firebase.messaging.Message; +import com.google.firebase.messaging.Notification; +import com.pawith.alarmmodule.exception.AlarmError; +import com.pawith.alarmmodule.exception.FcmException; +import com.pawith.alarmmodule.handler.NotificationHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.Retryable; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Service +@RequiredArgsConstructor +public class FcmSendMessageHandler implements NotificationHandler { + private final FirebaseMessaging firebaseMessaging; + private final ExecutorService executorService = Executors.newFixedThreadPool(10); + + @Retryable( + retryFor = FcmException.class, + maxAttempts = 3, + backoff = @Backoff(delay = 1000) + ) + public void send(final NotificationInfo notificationInfo) { + final Message message = Message.builder() + .setToken(notificationInfo.deviceToken) + .setNotification(notificationInfo.notification) + .build(); + try { + firebaseMessaging.send(message); + } catch (FirebaseMessagingException e) { + throw new FcmException(AlarmError.FCM_SEND_ERROR); + } + } + + public void sendAsync(final NotificationInfo notificationInfo){ + executorService.submit(() -> { + send(notificationInfo); + }); + } + + public void sendMultiAsync(Collection notificationInfoList){ + notificationInfoList.forEach(this::sendAsync); + } + + public record NotificationInfo(String deviceToken, Notification notification){ } +} diff --git a/Alarm-Module/src/main/java/com/pawith/alarmmodule/handler/NotificationHandler.java b/Alarm-Module/src/main/java/com/pawith/alarmmodule/handler/NotificationHandler.java new file mode 100644 index 00000000..b7499e8a --- /dev/null +++ b/Alarm-Module/src/main/java/com/pawith/alarmmodule/handler/NotificationHandler.java @@ -0,0 +1,11 @@ +package com.pawith.alarmmodule.handler; + +import java.util.Collection; + +public interface NotificationHandler { + void send(T t); + + void sendMultiAsync(Collection t); + + void sendAsync(T t); +} diff --git a/Alarm-Module/src/main/java/com/pawith/alarmmodule/repository/AlarmBatchRepository.java b/Alarm-Module/src/main/java/com/pawith/alarmmodule/repository/AlarmBatchRepository.java new file mode 100644 index 00000000..bebde830 --- /dev/null +++ b/Alarm-Module/src/main/java/com/pawith/alarmmodule/repository/AlarmBatchRepository.java @@ -0,0 +1,11 @@ +package com.pawith.alarmmodule.repository; + +import com.pawith.alarmmodule.entity.Alarm; +import org.springframework.data.repository.NoRepositoryBean; + +import java.util.Collection; + +@NoRepositoryBean +public interface AlarmBatchRepository{ + void saveAllBatch(Collection entities); +} diff --git a/Alarm-Module/src/main/java/com/pawith/alarmmodule/repository/AlarmRepository.java b/Alarm-Module/src/main/java/com/pawith/alarmmodule/repository/AlarmRepository.java index da071d7d..f049437a 100644 --- a/Alarm-Module/src/main/java/com/pawith/alarmmodule/repository/AlarmRepository.java +++ b/Alarm-Module/src/main/java/com/pawith/alarmmodule/repository/AlarmRepository.java @@ -4,15 +4,18 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; public interface AlarmRepository extends JpaRepository { - @Query("select count(a) > 0 " + - "from Alarm a " + - "join AlarmUser au on au.userId = :userId and a.alarmUser = au " + - "where a.isRead = false") - Boolean existsByUserId(Long userId); + @Query(""" + select count(a) + from Alarm a + join AlarmUser au on au.userId = :userId and a.alarmUser = au + where a.isRead = false + """) + Integer countUnReadAlarm(Long userId); @Query( """ @@ -23,4 +26,13 @@ public interface AlarmRepository extends JpaRepository { """ ) Slice findAllByUserId(Long userId, Pageable pageable); + + + @Modifying + @Query(""" + update Alarm a + set a.isRead = true + where a.isRead = false and a.alarmUser in (select au from AlarmUser au where au.userId = :userId) + """) + void changeAllAlarmStatusToRead(Long userId); } diff --git a/Alarm-Module/src/main/java/com/pawith/alarmmodule/repository/AlarmUserRepository.java b/Alarm-Module/src/main/java/com/pawith/alarmmodule/repository/AlarmUserRepository.java index c80a487f..cd775c6d 100644 --- a/Alarm-Module/src/main/java/com/pawith/alarmmodule/repository/AlarmUserRepository.java +++ b/Alarm-Module/src/main/java/com/pawith/alarmmodule/repository/AlarmUserRepository.java @@ -3,8 +3,10 @@ import com.pawith.alarmmodule.entity.AlarmUser; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; import java.util.Optional; public interface AlarmUserRepository extends JpaRepository { Optional findByUserId(Long userId); + List findByUserIdIn(List userId); } diff --git a/Alarm-Module/src/main/java/com/pawith/alarmmodule/repository/impl/AlarmBatchRepositoryImpl.java b/Alarm-Module/src/main/java/com/pawith/alarmmodule/repository/impl/AlarmBatchRepositoryImpl.java new file mode 100644 index 00000000..e2abde4e --- /dev/null +++ b/Alarm-Module/src/main/java/com/pawith/alarmmodule/repository/impl/AlarmBatchRepositoryImpl.java @@ -0,0 +1,32 @@ +package com.pawith.alarmmodule.repository; + +import com.pawith.alarmmodule.entity.Alarm; +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collection; + +@Repository +@Transactional +@RequiredArgsConstructor +public class AlarmBatchRepositoryImpl implements AlarmBatchRepository{ + private final JdbcTemplate jdbcTemplate; + + @Override + public void saveAllBatch(Collection entities) { + final String insertSql = """ + INSERT INTO alarm (domain_id, message, alarm_category,is_read,alarm_user_id,created_at, updated_at) + VALUES (?, ?, ?, ?, ?,now(),now()) + """; + + jdbcTemplate.batchUpdate(insertSql, entities, 1000, (ps, alarm) -> { + ps.setLong(1, alarm.getAlarmBody().getDomainId()); + ps.setString(2, alarm.getAlarmBody().getMessage()); + ps.setString(3, alarm.getAlarmCategory().toString()); + ps.setBoolean(4, alarm.getIsRead()); + ps.setLong(5, alarm.getAlarmUser().getId()); + }); + } +} diff --git a/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/AlarmService.java b/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/AlarmService.java index b7b7c607..2c9f1365 100644 --- a/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/AlarmService.java +++ b/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/AlarmService.java @@ -1,9 +1,10 @@ package com.pawith.alarmmodule.service; import com.pawith.alarmmodule.entity.Alarm; +import com.pawith.alarmmodule.repository.AlarmBatchRepository; import com.pawith.alarmmodule.repository.AlarmRepository; -import com.pawith.alarmmodule.service.dto.response.AlarmExistenceResponse; import com.pawith.alarmmodule.service.dto.response.AlarmInfoResponse; +import com.pawith.alarmmodule.service.dto.response.UnReadAlarmResponse; import com.pawith.commonmodule.response.SliceResponse; import com.pawith.userdomain.utils.UserUtils; import lombok.RequiredArgsConstructor; @@ -11,17 +12,20 @@ import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; +import java.util.List; + @Service @RequiredArgsConstructor public class AlarmService { private final AlarmRepository alarmRepository; + private final AlarmBatchRepository alarmBatchRepository; private final UserUtils userUtils; - public AlarmExistenceResponse getAlarmsExist(){ + public UnReadAlarmResponse getUnreadAlarmCount(){ final Long userId = userUtils.getAccessUser().getId(); - final Boolean alarmExist = alarmRepository.existsByUserId(userId); - return new AlarmExistenceResponse(alarmExist); + final Integer unReadAlarmCount = alarmRepository.countUnReadAlarm(userId); + return new UnReadAlarmResponse(unReadAlarmCount); } public SliceResponse getAlarms(Pageable pageable){ @@ -40,4 +44,13 @@ public SliceResponse getAlarms(Pageable pageable){ public void saveAlarm(Alarm alarm){ alarmRepository.save(alarm); } + + public void changeAllAlarmStatusToRead(){ + final Long userId = userUtils.getAccessUser().getId(); + alarmRepository.changeAllAlarmStatusToRead(userId); + } + + public void saveAllAlarm(List alarms){ + alarmBatchRepository.saveAllBatch(alarms); + } } diff --git a/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/AlarmUserService.java b/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/AlarmUserService.java index 99dd2444..d6de2fbe 100644 --- a/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/AlarmUserService.java +++ b/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/AlarmUserService.java @@ -11,6 +11,11 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + @Service @Transactional @RequiredArgsConstructor @@ -39,4 +44,9 @@ public AlarmUser findDeviceTokenByUserId(Long userId) { return alarmUserRepository.findByUserId(userId) .orElseThrow(() -> new AlarmException(AlarmError.DEVICE_TOKEN_NOT_FOUND)); } + + public Map findAlarmUsersByUserIds(List userIds) { + return alarmUserRepository.findByUserIdIn(userIds).stream() + .collect(Collectors.toMap(AlarmUser::getUserId, Function.identity())); + } } diff --git a/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/MultiAlarmSendService.java b/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/MultiAlarmSendService.java new file mode 100644 index 00000000..4fa0ebf7 --- /dev/null +++ b/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/MultiAlarmSendService.java @@ -0,0 +1,5 @@ +package com.pawith.alarmmodule.service; + +public interface MultiAlarmSendService { + void sendAlarm(T t); +} diff --git a/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/dto/response/AlarmExistenceResponse.java b/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/dto/response/AlarmExistenceResponse.java deleted file mode 100644 index eebb35e5..00000000 --- a/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/dto/response/AlarmExistenceResponse.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.pawith.alarmmodule.service.dto.response; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public class AlarmExistenceResponse { - private final Boolean isExist; -} diff --git a/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/dto/response/AlarmInfoResponse.java b/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/dto/response/AlarmInfoResponse.java index 23708b63..b70120b8 100644 --- a/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/dto/response/AlarmInfoResponse.java +++ b/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/dto/response/AlarmInfoResponse.java @@ -1,6 +1,5 @@ package com.pawith.alarmmodule.service.dto.response; -import com.pawith.commonmodule.enums.AlarmCategory; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -12,7 +11,7 @@ @Builder public class AlarmInfoResponse { private final Long alarmId; - private final AlarmCategory title; + private final String title; private final String message; private final Boolean isRead; private final LocalDateTime createdAt; diff --git a/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/dto/response/UnReadAlarmResponse.java b/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/dto/response/UnReadAlarmResponse.java new file mode 100644 index 00000000..ead9e153 --- /dev/null +++ b/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/dto/response/UnReadAlarmResponse.java @@ -0,0 +1,4 @@ +package com.pawith.alarmmodule.service.dto.response; + +public record UnReadAlarmResponse(Integer unReadAlarmCount) { +} diff --git a/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/impl/AlarmSendServiceImpl.java b/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/impl/AlarmSendServiceImpl.java new file mode 100644 index 00000000..5375e7fa --- /dev/null +++ b/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/impl/AlarmSendServiceImpl.java @@ -0,0 +1,40 @@ +package com.pawith.alarmmodule.service.impl; + +import com.pawith.alarmmodule.entity.Alarm; +import com.pawith.alarmmodule.entity.AlarmUser; +import com.pawith.alarmmodule.entity.vo.AlarmBody; +import com.pawith.alarmmodule.fcm.FcmSendMessageHandler; +import com.pawith.alarmmodule.handler.NotificationHandler; +import com.pawith.alarmmodule.service.AlarmSendService; +import com.pawith.alarmmodule.service.AlarmService; +import com.pawith.alarmmodule.service.AlarmUserService; +import com.pawith.commonmodule.event.NotificationEvent; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AlarmSendServiceImpl implements AlarmSendService { + private final AlarmUserService alarmUserService; + private final AlarmService alarmService; + private final NotificationHandler notificationHandler; + + @Override + @EventListener + public void sendAlarm(NotificationEvent notificationEvent) { + final AlarmUser alarmUser = alarmUserService.findDeviceTokenByUserId(notificationEvent.userId()); + alarmService.saveAlarm(Alarm.builder() + .alarmCategory(notificationEvent.title()) + .alarmUser(alarmUser) + .alarmBody(new AlarmBody(notificationEvent.body(), notificationEvent.domainId())) + .build()); + notificationHandler.sendAsync( + new FcmSendMessageHandler.NotificationInfo( + alarmUser.getDeviceToken(), + notificationEvent.toNotification()) + ); + } +} diff --git a/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/impl/FcmSendService.java b/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/impl/FcmSendService.java deleted file mode 100644 index cd42d197..00000000 --- a/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/impl/FcmSendService.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.pawith.alarmmodule.service.impl; - -import com.google.firebase.messaging.FirebaseMessaging; -import com.google.firebase.messaging.FirebaseMessagingException; -import com.google.firebase.messaging.Message; -import com.google.firebase.messaging.Notification; -import com.pawith.alarmmodule.entity.Alarm; -import com.pawith.alarmmodule.entity.AlarmUser; -import com.pawith.alarmmodule.entity.vo.AlarmBody; -import com.pawith.alarmmodule.exception.AlarmError; -import com.pawith.alarmmodule.exception.FcmException; -import com.pawith.alarmmodule.service.AlarmSendService; -import com.pawith.alarmmodule.service.AlarmService; -import com.pawith.alarmmodule.service.AlarmUserService; -import com.pawith.commonmodule.event.NotificationEvent; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.event.EventListener; -import org.springframework.retry.annotation.Backoff; -import org.springframework.retry.annotation.Retryable; -import org.springframework.stereotype.Service; - -import java.util.concurrent.CompletableFuture; - -@Slf4j -@Service -@RequiredArgsConstructor -public class FcmSendService implements AlarmSendService { - private final FirebaseMessaging firebaseMessaging; - private final AlarmUserService alarmUserService; - private final AlarmService alarmService; - - @Override - @EventListener - public void sendAlarm(NotificationEvent notificationEvent) { - CompletableFuture.runAsync(() -> { - final AlarmUser alarmUser = alarmUserService.findDeviceTokenByUserId(notificationEvent.getUserId()); - alarmService.saveAlarm(Alarm.builder() - .alarmCategory(notificationEvent.getTitle()) - .alarmUser(alarmUser) - .alarmBody(new AlarmBody(notificationEvent.getBody(), notificationEvent.getDomainId())) - .build()); - sendFcmMessage(alarmUser.getDeviceToken(), notificationEvent.toNotification()); - }); - } - - @Retryable( - value = {FcmException.class}, - maxAttempts = 3, - backoff = @Backoff(delay = 1000) - ) - private void sendFcmMessage(final String deviceToken, final Notification notification) { - final Message message = Message.builder() - .setToken(deviceToken) - .setNotification(notification) - .build(); - try { - firebaseMessaging.send(message); - } catch (FirebaseMessagingException e) { - throw new FcmException(AlarmError.FCM_SEND_ERROR); - } - } -} diff --git a/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/impl/MultiAlarmSendServiceImpl.java b/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/impl/MultiAlarmSendServiceImpl.java new file mode 100644 index 00000000..2298d896 --- /dev/null +++ b/Alarm-Module/src/main/java/com/pawith/alarmmodule/service/impl/MultiAlarmSendServiceImpl.java @@ -0,0 +1,55 @@ +package com.pawith.alarmmodule.service.impl; + +import com.google.firebase.messaging.Notification; +import com.pawith.alarmmodule.entity.Alarm; +import com.pawith.alarmmodule.entity.AlarmUser; +import com.pawith.alarmmodule.entity.vo.AlarmBody; +import com.pawith.alarmmodule.fcm.FcmSendMessageHandler; +import com.pawith.alarmmodule.handler.NotificationHandler; +import com.pawith.alarmmodule.service.AlarmService; +import com.pawith.alarmmodule.service.AlarmUserService; +import com.pawith.alarmmodule.service.MultiAlarmSendService; +import com.pawith.commonmodule.event.MultiNotificationEvent; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +@Service +@RequiredArgsConstructor +public class MultiAlarmSendServiceImpl implements MultiAlarmSendService { + private final AlarmUserService alarmUserService; + private final AlarmService alarmService; + private final NotificationHandler notificationHandler; + + @Override + @EventListener + public void sendAlarm(MultiNotificationEvent multiNotificationEvent) { + final Map alarmUserMap = alarmUserService.findAlarmUsersByUserIds(multiNotificationEvent.extractAllUserIds()); + saveAllAlarm(multiNotificationEvent, alarmUserMap); + sendMultiNotification(multiNotificationEvent, alarmUserMap); + } + + private void sendMultiNotification(MultiNotificationEvent multiNotificationEvent, Map alarmUserMap) { + final List notificationInfoList = multiNotificationEvent.notificationEvents().stream() + .map(notificationEvent -> { + final String deviceToken = alarmUserMap.get(notificationEvent.userId()).getDeviceToken(); + final Notification notification = notificationEvent.toNotification(); + return new FcmSendMessageHandler.NotificationInfo(deviceToken, notification); + }).toList(); + notificationHandler.sendMultiAsync(notificationInfoList); + } + + private void saveAllAlarm(MultiNotificationEvent multiNotificationEvent, Map alarmUserMap) { + final List savedAlarmList = multiNotificationEvent.notificationEvents().stream() + .map(notificationEvent -> Alarm.builder() + .alarmCategory(notificationEvent.title()) + .alarmUser(alarmUserMap.get(notificationEvent.userId())) + .alarmBody(new AlarmBody(notificationEvent.body(), notificationEvent.domainId())) + .build()) + .toList(); + alarmService.saveAllAlarm(savedAlarmList); + } +} \ No newline at end of file diff --git a/Alarm-Module/src/test/java/com/pawith/alarmmodule/controller/AlarmControllerTest.java b/Alarm-Module/src/test/java/com/pawith/alarmmodule/controller/AlarmControllerTest.java index e0a53fff..e6aaa3ed 100644 --- a/Alarm-Module/src/test/java/com/pawith/alarmmodule/controller/AlarmControllerTest.java +++ b/Alarm-Module/src/test/java/com/pawith/alarmmodule/controller/AlarmControllerTest.java @@ -1,7 +1,7 @@ package com.pawith.alarmmodule.controller; import com.pawith.alarmmodule.service.AlarmService; -import com.pawith.alarmmodule.service.dto.response.AlarmExistenceResponse; +import com.pawith.alarmmodule.service.dto.response.UnReadAlarmResponse; import com.pawith.alarmmodule.service.dto.response.AlarmInfoResponse; import com.pawith.commonmodule.BaseRestDocsTest; import com.pawith.commonmodule.response.SliceResponse; @@ -42,8 +42,8 @@ class AlarmControllerTest extends BaseRestDocsTest { @DisplayName("알람 존재 여부 확인") void getAlarmsExist() throws Exception { // given - final AlarmExistenceResponse alarmExistenceResponse = FixtureMonkeyUtils.getConstructBasedFixtureMonkey().giveMeOne(AlarmExistenceResponse.class); - given(alarmService.getAlarmsExist()).willReturn(alarmExistenceResponse); + final UnReadAlarmResponse unReadAlarmResponse = FixtureMonkeyUtils.getConstructBasedFixtureMonkey().giveMeOne(UnReadAlarmResponse.class); + given(alarmService.getUnreadAlarmCount()).willReturn(unReadAlarmResponse); MockHttpServletRequestBuilder request = RestDocumentationRequestBuilders.get(BASE_URL + "/exist") .header("Authorization", "Bearer token"); // when @@ -55,7 +55,7 @@ void getAlarmsExist() throws Exception { headerWithName("Authorization").description("access 토큰") ), responseFields( - fieldWithPath("isExist").description("알람 존재 여부") + fieldWithPath("unReadAlarmCount").description("미열람 알람 수") ) )); } @@ -99,4 +99,22 @@ void getAlarms() throws Exception { )); } + @Test + @DisplayName("알람 일괄 읽음 처리") + void patchAlarms() throws Exception { + //given + MockHttpServletRequestBuilder request = RestDocumentationRequestBuilders.patch(BASE_URL) + .header("Authorization", "Bearer token"); + //when + ResultActions result = mvc.perform(request); + //then + result.andExpect(status().isOk()) + .andDo(resultHandler.document( + requestHeaders( + headerWithName("Authorization").description("access 토큰") + ) + )); + } + + } \ No newline at end of file diff --git a/Api-Module/build.gradle b/Api-Module/build.gradle index 9b3c352b..20655190 100644 --- a/Api-Module/build.gradle +++ b/Api-Module/build.gradle @@ -3,11 +3,13 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' runtimeOnly 'io.micrometer:micrometer-registry-prometheus' - implementation project(":Log-Module:Log-Aop") + implementation project(":Log-Module") implementation project(":Domain-Module:Todo-Module:Todo-Presentation") implementation project(":Domain-Module:Auth-Module:Auth-Presentation") implementation project(":Domain-Module:User-Module:User-Presentation") implementation project(':Alarm-Module') + + implementation project(':Event') } diff --git a/Api-Module/src/main/resources/application-auth.yml b/Api-Module/src/main/resources/application-auth.yml index 03c70d75..ff9778b3 100644 --- a/Api-Module/src/main/resources/application-auth.yml +++ b/Api-Module/src/main/resources/application-auth.yml @@ -1,6 +1,7 @@ jwt: secret: ${JWT_SECRET} # 환경 변수 - access-token-expiration-time: 86400000 # 24*60*60*1000 = 1일 + access-token-expiration-time: 3600000 # 60*60*1000 = 1시간 +# access-token-expiration-time: 60000 # 60*1000 = 1분 refresh-token-expiration-time: 604800000 # 7*24*60*60*1000 = 7일 # App id diff --git a/Api-Module/src/main/resources/logback.xml b/Api-Module/src/main/resources/logback.xml index cac34e17..c40de0b4 100644 --- a/Api-Module/src/main/resources/logback.xml +++ b/Api-Module/src/main/resources/logback.xml @@ -1,6 +1,8 @@ - - + + + + diff --git a/Common-Module/src/main/java/com/pawith/commonmodule/cache/CacheTemplate.java b/Common-Module/src/main/java/com/pawith/commonmodule/cache/CacheTemplate.java new file mode 100644 index 00000000..69728558 --- /dev/null +++ b/Common-Module/src/main/java/com/pawith/commonmodule/cache/CacheTemplate.java @@ -0,0 +1,9 @@ +package com.pawith.commonmodule.cache; + +import com.pawith.commonmodule.cache.operators.SetOperator; +import com.pawith.commonmodule.cache.operators.ValueOperator; + +public interface CacheTemplate { + SetOperator opsForSet(); + ValueOperator opsForValue(); +} diff --git a/Common-Module/src/main/java/com/pawith/commonmodule/cache/DefaultCacheTemplate.java b/Common-Module/src/main/java/com/pawith/commonmodule/cache/DefaultCacheTemplate.java new file mode 100644 index 00000000..673df3ea --- /dev/null +++ b/Common-Module/src/main/java/com/pawith/commonmodule/cache/DefaultCacheTemplate.java @@ -0,0 +1,24 @@ +package com.pawith.commonmodule.cache; + +import com.pawith.commonmodule.cache.operators.SetOperator; +import com.pawith.commonmodule.cache.operators.ValueOperator; +import com.pawith.commonmodule.cache.operators.impl.DefaultSetOperator; +import com.pawith.commonmodule.cache.operators.impl.DefaultValueOperator; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class DefaultCacheTemplate implements CacheTemplate { + + private final SetOperator SET_OPERATOR = new DefaultSetOperator<>(); + private final ValueOperator VALUE_OPERATOR = new DefaultValueOperator<>(); + + @Override + public SetOperator opsForSet() { + return SET_OPERATOR; + } + + @Override + public ValueOperator opsForValue() { + return VALUE_OPERATOR; + } +} diff --git a/Common-Module/src/main/java/com/pawith/commonmodule/cache/config/CacheConfig.java b/Common-Module/src/main/java/com/pawith/commonmodule/cache/config/CacheConfig.java new file mode 100644 index 00000000..ac548d82 --- /dev/null +++ b/Common-Module/src/main/java/com/pawith/commonmodule/cache/config/CacheConfig.java @@ -0,0 +1,27 @@ +package com.pawith.commonmodule.cache.config; + +import com.pawith.commonmodule.cache.CacheTemplate; +import com.pawith.commonmodule.cache.DefaultCacheTemplate; +import com.pawith.commonmodule.cache.operators.SetOperator; +import com.pawith.commonmodule.cache.operators.ValueOperator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CacheConfig { + + @Bean + public CacheTemplate cacheTemplate() { + return new DefaultCacheTemplate<>(); + } + + @Bean + public SetOperator setOperator() { + return cacheTemplate().opsForSet(); + } + + @Bean + public ValueOperator valueOperator() { + return cacheTemplate().opsForValue(); + } +} 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 new file mode 100644 index 00000000..ec436d97 --- /dev/null +++ b/Common-Module/src/main/java/com/pawith/commonmodule/cache/operators/SetOperator.java @@ -0,0 +1,14 @@ +package com.pawith.commonmodule.cache.operators; + +import java.util.concurrent.TimeUnit; + +public interface SetOperator { + + void add(K k); + + void addWithExpire(K k, long expire, TimeUnit timeUnit); + + void remove(K k); + + boolean contains(K k); +} diff --git a/Common-Module/src/main/java/com/pawith/commonmodule/cache/operators/ValueOperator.java b/Common-Module/src/main/java/com/pawith/commonmodule/cache/operators/ValueOperator.java new file mode 100644 index 00000000..3af225af --- /dev/null +++ b/Common-Module/src/main/java/com/pawith/commonmodule/cache/operators/ValueOperator.java @@ -0,0 +1,16 @@ +package com.pawith.commonmodule.cache.operators; + +import java.util.concurrent.TimeUnit; + +public interface ValueOperator { + + void set(K k, V v); + + void setWithExpire(K k, V v, long expire, TimeUnit timeUnit); + + V get(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 new file mode 100644 index 00000000..be901c50 --- /dev/null +++ b/Common-Module/src/main/java/com/pawith/commonmodule/cache/operators/impl/DefaultSetOperator.java @@ -0,0 +1,28 @@ +package com.pawith.commonmodule.cache.operators.impl; + +import com.pawith.commonmodule.cache.operators.SetOperator; +import net.jodah.expiringmap.ExpirationPolicy; + +import java.util.concurrent.TimeUnit; + +public class DefaultSetOperator extends ExpiredStorage implements SetOperator { + @Override + public void add(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); + } + + @Override + public void remove(K k) { + storage.remove(k); + } + + @Override + public boolean contains(K k) { + return storage.containsKey(k); + } +} diff --git a/Common-Module/src/main/java/com/pawith/commonmodule/cache/operators/impl/DefaultValueOperator.java b/Common-Module/src/main/java/com/pawith/commonmodule/cache/operators/impl/DefaultValueOperator.java new file mode 100644 index 00000000..1a3bd6aa --- /dev/null +++ b/Common-Module/src/main/java/com/pawith/commonmodule/cache/operators/impl/DefaultValueOperator.java @@ -0,0 +1,33 @@ +package com.pawith.commonmodule.cache.operators.impl; + +import com.pawith.commonmodule.cache.operators.ValueOperator; +import net.jodah.expiringmap.ExpirationPolicy; + +import java.util.concurrent.TimeUnit; + +public class DefaultValueOperator extends ExpiredStorage implements ValueOperator { + @Override + public void set(K k, V v) { + storage.put(k,v); + } + + @Override + public void setWithExpire(K k, V v, long expire, TimeUnit timeUnit) { + storage.put(k,v, ExpirationPolicy.CREATED,expire,timeUnit); + } + + @Override + public V get(K k) { + return storage.get(k); + } + + @Override + public void remove(K k) { + storage.remove(k); + } + + @Override + public boolean contains(K k) { + return storage.containsKey(k); + } +} diff --git a/Common-Module/src/main/java/com/pawith/commonmodule/cache/operators/impl/ExpiredStorage.java b/Common-Module/src/main/java/com/pawith/commonmodule/cache/operators/impl/ExpiredStorage.java new file mode 100644 index 00000000..1727c690 --- /dev/null +++ b/Common-Module/src/main/java/com/pawith/commonmodule/cache/operators/impl/ExpiredStorage.java @@ -0,0 +1,10 @@ +package com.pawith.commonmodule.cache.operators.impl; + + +import net.jodah.expiringmap.ExpiringMap; + +public abstract class ExpiredStorage { + protected final ExpiringMap storage = ExpiringMap.builder() + .variableExpiration() + .build(); +} diff --git a/Common-Module/src/main/java/com/pawith/commonmodule/config/QueryDslConfig.java b/Common-Module/src/main/java/com/pawith/commonmodule/config/QueryDslConfig.java new file mode 100644 index 00000000..48eb3ba7 --- /dev/null +++ b/Common-Module/src/main/java/com/pawith/commonmodule/config/QueryDslConfig.java @@ -0,0 +1,20 @@ +package com.pawith.commonmodule.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@RequiredArgsConstructor +public class QueryDslConfig { + + private final EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } + +} diff --git a/Common-Module/src/main/java/com/pawith/commonmodule/event/ChristmasEvent.java b/Common-Module/src/main/java/com/pawith/commonmodule/event/ChristmasEvent.java new file mode 100644 index 00000000..1a5c0f90 --- /dev/null +++ b/Common-Module/src/main/java/com/pawith/commonmodule/event/ChristmasEvent.java @@ -0,0 +1,14 @@ +package com.pawith.commonmodule.event; + +import lombok.Getter; + +@Getter +public class ChristmasEvent { + + public record ChristmasEventCreateNewTodoTeam(Long todoTeamId) { + } + + public record ChristmasEventCreateNewRegister(Long todoTeamId, Long userId) { + } + +} diff --git a/Common-Module/src/main/java/com/pawith/commonmodule/event/MultiNotificationEvent.java b/Common-Module/src/main/java/com/pawith/commonmodule/event/MultiNotificationEvent.java new file mode 100644 index 00000000..c3e63be4 --- /dev/null +++ b/Common-Module/src/main/java/com/pawith/commonmodule/event/MultiNotificationEvent.java @@ -0,0 +1,11 @@ +package com.pawith.commonmodule.event; + +import java.util.List; + +public record MultiNotificationEvent(List notificationEvents) { + public List extractAllUserIds(){ + return notificationEvents.stream() + .map(NotificationEvent::userId) + .toList(); + } +} \ No newline at end of file diff --git a/Common-Module/src/main/java/com/pawith/commonmodule/event/NotificationEvent.java b/Common-Module/src/main/java/com/pawith/commonmodule/event/NotificationEvent.java index e84229e1..91a675ad 100644 --- a/Common-Module/src/main/java/com/pawith/commonmodule/event/NotificationEvent.java +++ b/Common-Module/src/main/java/com/pawith/commonmodule/event/NotificationEvent.java @@ -1,30 +1,16 @@ package com.pawith.commonmodule.event; import com.google.firebase.messaging.Notification; -import com.pawith.commonmodule.enums.AlarmCategory; import lombok.Builder; -import lombok.Getter; -import lombok.ToString; - -@Getter -@ToString -public class NotificationEvent { - private Long userId; - private AlarmCategory title; - private String body; - private Long domainId; +public record NotificationEvent(Long userId, String title, String body, Long domainId) { @Builder - public NotificationEvent(Long userId, AlarmCategory title, String body, Long domainId) { - this.userId = userId; - this.title = title; - this.body = body; - this.domainId = domainId; + public NotificationEvent { } public Notification toNotification(){ return Notification.builder() - .setTitle(title.name()) + .setTitle(title) .setBody(body) .build(); } diff --git a/Common-Module/src/main/java/com/pawith/commonmodule/schedule/AbstractBatchSchedulingHandler.java b/Common-Module/src/main/java/com/pawith/commonmodule/schedule/AbstractBatchSchedulingHandler.java new file mode 100644 index 00000000..8776ddf5 --- /dev/null +++ b/Common-Module/src/main/java/com/pawith/commonmodule/schedule/AbstractBatchSchedulingHandler.java @@ -0,0 +1,58 @@ +package com.pawith.commonmodule.schedule; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Pageable; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.support.CronTrigger; + +import java.util.List; + +@Slf4j +public abstract class AbstractBatchSchedulingHandler implements BatchSchedulingHandler { + private final Integer batchSize; + + public AbstractBatchSchedulingHandler(Integer batchSize, String cronExpression) { + this.batchSize = batchSize; + final CronTrigger cronTrigger = new CronTrigger(cronExpression); + final ThreadPoolTaskScheduler taskExecutor = new ThreadPoolTaskScheduler(); + taskExecutor.initialize(); + taskExecutor.setPoolSize(1); + taskExecutor.schedule(this::execute, cronTrigger); + } + + @Override + public void execute() { + log.debug("Batch processing is started."); + final long startTime = System.currentTimeMillis(); + final Pageable pageable = Pageable.ofSize(batchSize); + List executionResult; + try { + do { + executionResult = extractBatchData(pageable); + processBatch(executionResult); + pageable.next(); + } while (!checkingIsFinished(executionResult)); + } catch (Exception e){ + errorHandle(e); + } + log.info("Batch processing is finished. Execution time: {}ms", System.currentTimeMillis() - startTime); + } + + private boolean checkingIsFinished(List executionResult){ + if(executionResult.size() < batchSize){ + log.debug("Batch processing is finished."); + return true; + } + log.debug("Batch processing is not finished. Continue processing..."); + return false; + } + + + + protected abstract List extractBatchData(Pageable pageable); + protected abstract void processBatch(List executionResult); + + protected void errorHandle(Exception e){ + log.error("Batch processing is failed. {}", e.getMessage()); + } +} diff --git a/Common-Module/src/main/java/com/pawith/commonmodule/schedule/BatchSchedulingHandler.java b/Common-Module/src/main/java/com/pawith/commonmodule/schedule/BatchSchedulingHandler.java new file mode 100644 index 00000000..1617ffe7 --- /dev/null +++ b/Common-Module/src/main/java/com/pawith/commonmodule/schedule/BatchSchedulingHandler.java @@ -0,0 +1,5 @@ +package com.pawith.commonmodule.schedule; + +public interface BatchSchedulingHandler { + void execute(); +} diff --git a/Common-Module/src/main/java/com/pawith/commonmodule/util/SliceUtils.java b/Common-Module/src/main/java/com/pawith/commonmodule/util/SliceUtils.java new file mode 100644 index 00000000..f29022bf --- /dev/null +++ b/Common-Module/src/main/java/com/pawith/commonmodule/util/SliceUtils.java @@ -0,0 +1,22 @@ +package com.pawith.commonmodule.util; + +import java.util.List; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class SliceUtils { + + public static Slice getSliceImpl(List list, Pageable pageable) { + boolean hasNext = false; + if (list.size() > pageable.getPageSize()) { + hasNext = true; + list.remove(list.size() - 1); + } + + return new SliceImpl<>(list, pageable, hasNext); + } +} diff --git a/Domain-Module/Auth-Module/Auth-Application/src/main/java/com/pawith/authapplication/service/command/OAuthInvoker.java b/Domain-Module/Auth-Module/Auth-Application/src/main/java/com/pawith/authapplication/service/command/OAuthInvoker.java index 3a190073..4c2c5c83 100644 --- a/Domain-Module/Auth-Module/Auth-Application/src/main/java/com/pawith/authapplication/service/command/OAuthInvoker.java +++ b/Domain-Module/Auth-Module/Auth-Application/src/main/java/com/pawith/authapplication/service/command/OAuthInvoker.java @@ -5,24 +5,24 @@ import com.pawith.authapplication.dto.OAuthResponse; import com.pawith.authapplication.dto.OAuthUserInfo; import com.pawith.authapplication.exception.AuthException; -import com.pawith.authapplication.handler.request.UnusedTokenExpireEvent; import com.pawith.authapplication.service.command.handler.AuthHandler; import com.pawith.authdomain.exception.AuthError; import com.pawith.authdomain.jwt.JWTProvider; import com.pawith.authdomain.jwt.TokenType; +import com.pawith.authdomain.service.TokenSaveService; import com.pawith.commonmodule.event.UserSignUpEvent; import lombok.RequiredArgsConstructor; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; import java.util.List; -import java.util.function.Function; @Component @RequiredArgsConstructor public class OAuthInvoker { private final List authHandlerList; private final JWTProvider jwtProvider; + private final TokenSaveService tokenSaveService; private final ApplicationEventPublisher publisher; public OAuthResponse execute(OAuthRequest request){ OAuthUserInfo oAuthUserInfo = attemptLogin(request); @@ -32,7 +32,7 @@ public OAuthResponse execute(OAuthRequest request){ private void publishEvent(OAuthRequest request, OAuthUserInfo oAuthUserInfo) { publisher.publishEvent(new UserSignUpEvent(oAuthUserInfo.getUsername(), oAuthUserInfo.getEmail(), request.getProvider())); - publisher.publishEvent(new UnusedTokenExpireEvent(oAuthUserInfo.getEmail(), TokenType.REFRESH_TOKEN)); +// publisher.publishEvent(new UnusedTokenExpireEvent(oAuthUserInfo.getEmail(), TokenType.REFRESH_TOKEN)); } private OAuthUserInfo attemptLogin(OAuthRequest request) { @@ -45,15 +45,13 @@ private OAuthUserInfo attemptLogin(OAuthRequest request) { } private OAuthResponse generateServerAuthenticationTokens(OAuthUserInfo oAuthUserInfo) { - final String accessToken = attachAuthenticationType(jwtProvider::generateAccessToken, oAuthUserInfo.getEmail()); - final String refreshToken = attachAuthenticationType(jwtProvider::generateRefreshToken, oAuthUserInfo.getEmail()); + final JWTProvider.Token token = jwtProvider.generateToken(oAuthUserInfo.getEmail()); + tokenSaveService.saveToken(token.refreshToken(), oAuthUserInfo.getEmail(), TokenType.REFRESH_TOKEN); + final String accessToken = AuthConsts.AUTHENTICATION_TYPE_PREFIX + token.accessToken(); + final String refreshToken = AuthConsts.AUTHENTICATION_TYPE_PREFIX + token.refreshToken(); return OAuthResponse.builder() .accessToken(accessToken) .refreshToken(refreshToken) .build(); } - - private String attachAuthenticationType(Function generateTokenMethod, T includeClaimData) { - return AuthConsts.AUTHENTICATION_TYPE_PREFIX + generateTokenMethod.apply(includeClaimData); - } } diff --git a/Domain-Module/Auth-Module/Auth-Application/src/main/java/com/pawith/authapplication/service/impl/JWTExtractEmailUseCaseImpl.java b/Domain-Module/Auth-Module/Auth-Application/src/main/java/com/pawith/authapplication/service/impl/JWTExtractEmailUseCaseImpl.java index 5ded55f6..9512f912 100644 --- a/Domain-Module/Auth-Module/Auth-Application/src/main/java/com/pawith/authapplication/service/impl/JWTExtractEmailUseCaseImpl.java +++ b/Domain-Module/Auth-Module/Auth-Application/src/main/java/com/pawith/authapplication/service/impl/JWTExtractEmailUseCaseImpl.java @@ -2,6 +2,7 @@ import com.pawith.authapplication.service.JWTExtractEmailUseCase; import com.pawith.authdomain.jwt.JWTProvider; +import com.pawith.authdomain.jwt.TokenType; import com.pawith.commonmodule.annotation.ApplicationService; import lombok.RequiredArgsConstructor; @@ -12,6 +13,6 @@ public class JWTExtractEmailUseCaseImpl implements JWTExtractEmailUseCase { @Override public String extractEmail(final String token){ - return jwtProvider.extractEmailFromAccessToken(token); + return jwtProvider.extractEmailFromToken(token, TokenType.ACCESS_TOKEN); } } 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 766cd14e..8586dca6 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 @@ -5,23 +5,58 @@ import com.pawith.authapplication.service.ReissueUseCase; import com.pawith.authapplication.utils.TokenExtractUtils; import com.pawith.authdomain.jwt.JWTProvider; +import com.pawith.authdomain.jwt.TokenType; import com.pawith.authdomain.service.TokenDeleteService; +import com.pawith.authdomain.service.TokenLockService; +import com.pawith.authdomain.service.TokenSaveService; +import com.pawith.authdomain.service.TokenValidateService; import com.pawith.commonmodule.annotation.ApplicationService; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; +import java.util.function.Function; + @Transactional @RequiredArgsConstructor @ApplicationService public class ReissueUseCaseImpl implements ReissueUseCase { private final JWTProvider jwtProvider; private final TokenDeleteService tokenDeleteService; + private final TokenSaveService tokenSaveService; + private final TokenValidateService tokenValidateService; + private final TokenLockService tokenLockService; + @Override public TokenReissueResponse reissue(String refreshTokenHeader) { final String refreshToken = TokenExtractUtils.extractToken(refreshTokenHeader); - final String reissueAccessToken = AuthConsts.AUTHENTICATION_TYPE_PREFIX+jwtProvider.reIssueAccessToken(refreshToken); - final String reissueRefreshToken = AuthConsts.AUTHENTICATION_TYPE_PREFIX+jwtProvider.reIssueRefreshToken(refreshToken); - tokenDeleteService.deleteTokenByValue(refreshToken); - return new TokenReissueResponse(reissueAccessToken, reissueRefreshToken); + final String userEmail = jwtProvider.extractEmailFromToken(refreshToken, TokenType.REFRESH_TOKEN); + return reissueToken(refreshToken, userEmail); + } + + private TokenReissueResponse reissueToken(final String refreshToken,final String userEmail) { + try { + tokenLockService.lockToken(userEmail); + if (jwtProvider.existsCachedRefreshToken(refreshToken)) { + return generateToken(jwtProvider::getCachedToken, refreshToken); + } + tokenValidateService.validateIsExistToken(refreshToken, TokenType.REFRESH_TOKEN); + tokenDeleteService.deleteTokenByValue(refreshToken); + return generateAndSaveToken(jwtProvider::reIssueToken, refreshToken, userEmail); + } finally { + tokenLockService.releaseLockToken(userEmail); + } } + + private TokenReissueResponse generateToken(final Function tokenGenerator, final String refreshToken) { + final JWTProvider.Token token = tokenGenerator.apply(refreshToken); + final String generatedAccessToken = AuthConsts.AUTHENTICATION_TYPE_PREFIX + token.accessToken(); + final String generatedRefreshToken = AuthConsts.AUTHENTICATION_TYPE_PREFIX + token.refreshToken(); + return new TokenReissueResponse(generatedAccessToken, generatedRefreshToken); + } + private TokenReissueResponse generateAndSaveToken(final Function tokenGenerator,final String refreshToken,final String userEmail) { + final JWTProvider.Token token = tokenGenerator.apply(refreshToken); + tokenSaveService.saveToken(token.refreshToken(), userEmail, TokenType.REFRESH_TOKEN); + return generateToken(inputRefreshToken -> token, refreshToken); + } + } diff --git a/Domain-Module/Auth-Module/Auth-Application/src/test/java/com/pawith/authapplication/service/JWTExtractEmailUseCaseImplTest.java b/Domain-Module/Auth-Module/Auth-Application/src/test/java/com/pawith/authapplication/service/JWTExtractEmailUseCaseImplTest.java index 7b8f1b61..b5a664a5 100644 --- a/Domain-Module/Auth-Module/Auth-Application/src/test/java/com/pawith/authapplication/service/JWTExtractEmailUseCaseImplTest.java +++ b/Domain-Module/Auth-Module/Auth-Application/src/test/java/com/pawith/authapplication/service/JWTExtractEmailUseCaseImplTest.java @@ -2,6 +2,7 @@ import com.navercorp.fixturemonkey.FixtureMonkey; import com.pawith.authapplication.service.impl.JWTExtractEmailUseCaseImpl; +import com.pawith.authdomain.jwt.TokenType; import com.pawith.commonmodule.UnitTestConfig; import com.pawith.authdomain.jwt.JWTProvider; import org.junit.jupiter.api.BeforeEach; @@ -30,11 +31,11 @@ void extractEmail(){ //given final String randomEmail = FixtureMonkey.create().giveMe(String.class).findFirst().get(); final String accessToken = jwtProvider.generateAccessToken(randomEmail); - given(jwtProvider.extractEmailFromAccessToken(accessToken)).willReturn(randomEmail); + given(jwtProvider.extractEmailFromToken(accessToken, TokenType.ACCESS_TOKEN)).willReturn(randomEmail); //when final String email = jwtExtractEmailUseCaseImpl.extractEmail(accessToken); //then - verify(jwtProvider).extractEmailFromAccessToken(accessToken); + verify(jwtProvider).extractEmailFromToken(accessToken, TokenType.ACCESS_TOKEN); assertEquals(email, randomEmail); } } diff --git a/Domain-Module/Auth-Module/Auth-Application/src/test/java/com/pawith/authapplication/service/ReissueUseCaseImplTest.java b/Domain-Module/Auth-Module/Auth-Application/src/test/java/com/pawith/authapplication/service/ReissueUseCaseImplTest.java new file mode 100644 index 00000000..ac491f74 --- /dev/null +++ b/Domain-Module/Auth-Module/Auth-Application/src/test/java/com/pawith/authapplication/service/ReissueUseCaseImplTest.java @@ -0,0 +1,92 @@ +package com.pawith.authapplication.service; + +import com.pawith.authapplication.consts.AuthConsts; +import com.pawith.authapplication.dto.TokenReissueResponse; +import com.pawith.authapplication.service.impl.ReissueUseCaseImpl; +import com.pawith.authdomain.jwt.JWTProvider; +import com.pawith.authdomain.jwt.TokenType; +import com.pawith.authdomain.service.TokenDeleteService; +import com.pawith.authdomain.service.TokenLockService; +import com.pawith.authdomain.service.TokenSaveService; +import com.pawith.authdomain.service.TokenValidateService; +import com.pawith.commonmodule.UnitTestConfig; +import com.pawith.commonmodule.utils.FixtureMonkeyUtils; +import lombok.extern.slf4j.Slf4j; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; + + +@Slf4j +@UnitTestConfig +@DisplayName("ReissueUseCaseImpl 테스트") +class ReissueUseCaseImplTest { + + @Mock + private JWTProvider jwtProvider; + @Mock + private TokenDeleteService tokenDeleteService; + @Mock + private TokenSaveService tokenSaveService; + @Mock + private TokenValidateService tokenValidateService; + @Mock + private TokenLockService tokenLockService; + + private ReissueUseCaseImpl reissueUseCase; + + @BeforeEach + void setUp() { + reissueUseCase = new ReissueUseCaseImpl(jwtProvider, tokenDeleteService,tokenSaveService, tokenValidateService, tokenLockService); + } + + @Test + @DisplayName("단일 캐싱된 토큰이 없을때는 새로운 토큰을 발급한다.") + void reissue() { + // given + final String refreshToken = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(String.class); + final String email = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(String.class); + final JWTProvider.Token token = FixtureMonkeyUtils.getConstructBasedFixtureMonkey().giveMeOne(JWTProvider.Token.class); + given(jwtProvider.existsCachedRefreshToken(refreshToken)).willReturn(false); + given(jwtProvider.reIssueToken(refreshToken)).willReturn(token); + given(jwtProvider.extractEmailFromToken(refreshToken, TokenType.REFRESH_TOKEN)).willReturn(email); + // when + TokenReissueResponse reissue = reissueUseCase.reissue(AuthConsts.AUTHENTICATION_TYPE_PREFIX + refreshToken); + // then + Assertions.assertThat(reissue.getAccessToken()).isEqualTo(AuthConsts.AUTHENTICATION_TYPE_PREFIX + token.accessToken()); + Assertions.assertThat(reissue.getRefreshToken()).isEqualTo(AuthConsts.AUTHENTICATION_TYPE_PREFIX + token.refreshToken()); + then(tokenValidateService).should().validateIsExistToken(refreshToken, TokenType.REFRESH_TOKEN); + then(tokenDeleteService).should().deleteTokenByValue(refreshToken); + then(tokenSaveService).should().saveToken(token.refreshToken(), email, TokenType.REFRESH_TOKEN); + then(tokenLockService).should().lockToken(email); + then(tokenLockService).should().releaseLockToken(email); + } + + @Test + @DisplayName("단일 캐싱된 토큰이 있을때는 캐싱된 토큰을 반환한다.") + void reissue2() { + // given + final String refreshToken = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(String.class); + final String email = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(String.class); + final JWTProvider.Token token = FixtureMonkeyUtils.getConstructBasedFixtureMonkey().giveMeOne(JWTProvider.Token.class); + given(jwtProvider.existsCachedRefreshToken(refreshToken)).willReturn(true); + given(jwtProvider.getCachedToken(refreshToken)).willReturn(token); + given(jwtProvider.extractEmailFromToken(refreshToken, TokenType.REFRESH_TOKEN)).willReturn(email); + // when + TokenReissueResponse reissue = reissueUseCase.reissue(AuthConsts.AUTHENTICATION_TYPE_PREFIX + refreshToken); + // then + Assertions.assertThat(reissue.getAccessToken()).isEqualTo(AuthConsts.AUTHENTICATION_TYPE_PREFIX + token.accessToken()); + Assertions.assertThat(reissue.getRefreshToken()).isEqualTo(AuthConsts.AUTHENTICATION_TYPE_PREFIX + token.refreshToken()); + then(tokenValidateService).shouldHaveNoInteractions(); + then(tokenDeleteService).shouldHaveNoInteractions(); + then(tokenSaveService).shouldHaveNoInteractions(); + then(tokenLockService).should().lockToken(email); + then(tokenLockService).should().releaseLockToken(email); + } + +} \ No newline at end of file diff --git a/Domain-Module/Auth-Module/Auth-Application/src/test/java/com/pawith/authapplication/service/command/OAuthInvokerTest.java b/Domain-Module/Auth-Module/Auth-Application/src/test/java/com/pawith/authapplication/service/command/OAuthInvokerTest.java index daed0237..9c2d2b92 100644 --- a/Domain-Module/Auth-Module/Auth-Application/src/test/java/com/pawith/authapplication/service/command/OAuthInvokerTest.java +++ b/Domain-Module/Auth-Module/Auth-Application/src/test/java/com/pawith/authapplication/service/command/OAuthInvokerTest.java @@ -7,6 +7,7 @@ import com.pawith.authapplication.service.command.handler.AuthHandler; import com.pawith.authapplication.utils.TokenExtractUtils; import com.pawith.authdomain.jwt.JWTProvider; +import com.pawith.authdomain.service.TokenSaveService; import com.pawith.commonmodule.UnitTestConfig; import com.pawith.commonmodule.utils.FixtureMonkeyUtils; import lombok.extern.slf4j.Slf4j; @@ -32,6 +33,8 @@ class OAuthInvokerTest { private JWTProvider jwtProvider; @Mock private ApplicationEventPublisher applicationEventPublisher; + @Mock + private TokenSaveService tokenSaveService; @Spy private List authHandlerList = new ArrayList<>(); @Mock @@ -42,7 +45,7 @@ class OAuthInvokerTest { @BeforeEach void setUp() { authHandlerList.add(authHandler); - oAuthInvoker=new OAuthInvoker(authHandlerList, jwtProvider, applicationEventPublisher); + oAuthInvoker=new OAuthInvoker(authHandlerList, jwtProvider, tokenSaveService,applicationEventPublisher); } @Test @@ -51,12 +54,10 @@ void execute(){ // given final OAuthRequest mockRequest = FixtureMonkeyUtils.getConstructBasedFixtureMonkey().giveMeOne(OAuthRequest.class); final OAuthUserInfo mockOAuthUserInfo = FixtureMonkeyUtils.getConstructBasedFixtureMonkey().giveMeOne(OAuthUserInfo.class); - final String accessToken = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(String.class); - final String refreshToken = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(String.class); + final JWTProvider.Token mockToken = FixtureMonkeyUtils.getConstructBasedFixtureMonkey().giveMeOne(JWTProvider.Token.class); given(authHandler.isAccessible(mockRequest)).willReturn(true); given(authHandler.handle(mockRequest)).willReturn(mockOAuthUserInfo); - given(jwtProvider.generateAccessToken(mockOAuthUserInfo.getEmail())).willReturn(accessToken); - given(jwtProvider.generateRefreshToken(mockOAuthUserInfo.getEmail())).willReturn(refreshToken); + given(jwtProvider.generateToken(mockOAuthUserInfo.getEmail())).willReturn(mockToken); // when final OAuthResponse result = oAuthInvoker.execute(mockRequest); // then @@ -64,8 +65,8 @@ void execute(){ Assertions.assertThat(result.getRefreshToken()).startsWith(AuthConsts.AUTHENTICATION_TYPE); Assertions.assertThat(result.getAccessToken()).startsWith(AuthConsts.AUTHENTICATION_TYPE_PREFIX); Assertions.assertThat(result.getRefreshToken()).startsWith(AuthConsts.AUTHENTICATION_TYPE_PREFIX); - Assertions.assertThat(TokenExtractUtils.extractToken(result.getAccessToken())).isEqualTo(accessToken); - Assertions.assertThat(TokenExtractUtils.extractToken(result.getRefreshToken())).isEqualTo(refreshToken); + Assertions.assertThat(TokenExtractUtils.extractToken(result.getAccessToken())).isEqualTo(mockToken.accessToken()); + Assertions.assertThat(TokenExtractUtils.extractToken(result.getRefreshToken())).isEqualTo(mockToken.refreshToken()); } } \ No newline at end of file diff --git a/Domain-Module/Auth-Module/Auth-Application/src/test/java/com/pawith/authapplication/service/impl/ReissueUseCaseImplTest.java b/Domain-Module/Auth-Module/Auth-Application/src/test/java/com/pawith/authapplication/service/impl/ReissueUseCaseImplTest.java deleted file mode 100644 index 21160c37..00000000 --- a/Domain-Module/Auth-Module/Auth-Application/src/test/java/com/pawith/authapplication/service/impl/ReissueUseCaseImplTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.pawith.authapplication.service.impl; - -import com.pawith.authapplication.consts.AuthConsts; -import com.pawith.authapplication.dto.TokenReissueResponse; -import com.pawith.authdomain.jwt.JWTProvider; -import com.pawith.authdomain.service.TokenDeleteService; -import com.pawith.commonmodule.UnitTestConfig; -import com.pawith.commonmodule.utils.FixtureMonkeyUtils; -import lombok.extern.slf4j.Slf4j; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; - -import static org.mockito.BDDMockito.given; -@Slf4j -@UnitTestConfig -@DisplayName("ReissueUseCaseImpl 테스트") -class ReissueUseCaseImplTest { - - @Mock - private JWTProvider jwtProvider; - @Mock - private TokenDeleteService tokenDeleteService; - - private ReissueUseCaseImpl reissueUseCase; - - @BeforeEach - void setUp() { - reissueUseCase = new ReissueUseCaseImpl(jwtProvider, tokenDeleteService); - } - - @Test - @DisplayName("reissue 테스트") - void reissue() { - // given - final String refreshToken = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(String.class); - final String reissueAccessToken = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(String.class); - final String reissueRefreshToken = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(String.class); - given(jwtProvider.reIssueAccessToken(refreshToken)).willReturn(reissueAccessToken); - given(jwtProvider.reIssueRefreshToken(refreshToken)).willReturn(reissueRefreshToken); - // when - TokenReissueResponse reissue = reissueUseCase.reissue(AuthConsts.AUTHENTICATION_TYPE_PREFIX + refreshToken); - // then - Assertions.assertThat(reissue.getAccessToken()).isEqualTo(AuthConsts.AUTHENTICATION_TYPE_PREFIX + reissueAccessToken); - Assertions.assertThat(reissue.getRefreshToken()).isEqualTo(AuthConsts.AUTHENTICATION_TYPE_PREFIX + reissueRefreshToken); - } - -} \ No newline at end of file diff --git a/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/jwt/JWTProvider.java b/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/jwt/JWTProvider.java index 62c118ed..5173d31f 100644 --- a/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/jwt/JWTProvider.java +++ b/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/jwt/JWTProvider.java @@ -1,11 +1,9 @@ package com.pawith.authdomain.jwt; import com.pawith.authdomain.exception.AuthError; -import com.pawith.authdomain.exception.NotExistTokenException; import com.pawith.authdomain.jwt.exception.ExpiredTokenException; import com.pawith.authdomain.jwt.exception.InvalidTokenException; -import com.pawith.authdomain.service.TokenQueryService; -import com.pawith.authdomain.service.TokenSaveService; +import com.pawith.commonmodule.cache.operators.ValueOperator; import io.jsonwebtoken.*; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; @@ -17,6 +15,7 @@ import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.TimeUnit; /** * jjwt 깃허브 : https://github.com/jwtk/jjwt @@ -25,8 +24,7 @@ @RequiredArgsConstructor public class JWTProvider { private final JWTProperties jwtProperties; - private final TokenSaveService tokenSaveService; - private final TokenQueryService tokenQueryService; + private final ValueOperator tokenCacheOperator; /** * access token 생성 @@ -45,9 +43,13 @@ public String generateAccessToken(final String email) { */ public String generateRefreshToken(final String email) { final Claims claims = createPrivateClaims(email, TokenType.REFRESH_TOKEN); - final String refreshToken = generateToken(claims, jwtProperties.getRefreshTokenExpirationTime()); - tokenSaveService.saveToken(refreshToken, email, TokenType.REFRESH_TOKEN); - return refreshToken; + return generateToken(claims, jwtProperties.getRefreshTokenExpirationTime()); + } + + public Token generateToken(final String email){ + final String accessToken = generateAccessToken(email); + final String refreshToken = generateRefreshToken(email); + return new Token(accessToken, refreshToken); } /** @@ -58,10 +60,21 @@ public String generateRefreshToken(final String email) { */ public String reIssueAccessToken(final String refreshToken) { validateToken(refreshToken, TokenType.REFRESH_TOKEN); - final String email = extractEmailFromRefreshToken(refreshToken); + final String email = extractEmailFromToken(refreshToken, TokenType.REFRESH_TOKEN); return generateAccessToken(email); } + public Token reIssueToken(final String refreshToken){ + validateToken(refreshToken, TokenType.REFRESH_TOKEN); + final String email = extractEmailFromToken(refreshToken, TokenType.REFRESH_TOKEN); + final String newAccessToken = generateAccessToken(email); + final String newRefreshToken = generateRefreshToken(email); + + tokenCacheOperator.setWithExpire(refreshToken, new Token(newAccessToken, newRefreshToken), 1, TimeUnit.MINUTES); + + return new Token(newAccessToken, newRefreshToken); + } + /** * refresh token을 이용하여 refresh token 재발급 * @@ -70,32 +83,24 @@ public String reIssueAccessToken(final String refreshToken) { */ public String reIssueRefreshToken(final String refreshToken) { validateToken(refreshToken, TokenType.REFRESH_TOKEN); - final String email = extractEmailFromRefreshToken(refreshToken); + final String email = extractEmailFromToken(refreshToken, TokenType.REFRESH_TOKEN); return generateRefreshToken(email); } - /** - * accessToken에서 email 추출 - * - * @throws InvalidTokenException : 잘못된 토큰 요청시 발생 - * @throws ExpiredTokenException : 만료된 토큰 요청시 발생 - */ - public String extractEmailFromAccessToken(final String accessToken) { - return initializeJwtParser(TokenType.ACCESS_TOKEN) - .parseClaimsJws(accessToken) + public String extractEmailFromToken(String token, TokenType tokenType) { + return initializeJwtParser(tokenType) + .parseClaimsJws(token) .getBody() .get(JWTConsts.EMAIL) .toString(); } + public boolean existsCachedRefreshToken(String refreshToken){ + return tokenCacheOperator.contains(refreshToken); + } - /** - * DB에서 refresh token(value)를 이용한 email(key) 추출 - * - * @throws NotExistTokenException : 존재하지 않는 토큰 요청시 발생 - */ - private String extractEmailFromRefreshToken(String refreshToken) { - return tokenQueryService.findEmailByValue(refreshToken, TokenType.REFRESH_TOKEN); + public Token getCachedToken(String refreshToken){ + return tokenCacheOperator.get(refreshToken); } /** @@ -165,4 +170,7 @@ private JwtParser initializeJwtParser(final TokenType tokenType) { .build(); } + public record Token(String accessToken, String refreshToken){ + } + } diff --git a/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/repository/TokenLockRepository.java b/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/repository/TokenLockRepository.java new file mode 100644 index 00000000..93f4525b --- /dev/null +++ b/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/repository/TokenLockRepository.java @@ -0,0 +1,15 @@ +package com.pawith.authdomain.repository; + +import com.pawith.authdomain.entity.Token; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface TokenLockRepository extends JpaRepository { + + @Query(value = "SELECT GET_LOCK(:value, 1000)", nativeQuery = true) + void getNamedLock(@Param("value") String value); + + @Query(value = "SELECT RELEASE_LOCK(:value)", nativeQuery = true) + void releaseNamedLock(@Param("value") String value); +} diff --git a/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/repository/TokenRepository.java b/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/repository/TokenRepository.java index 97b8c6d6..ce9f5329 100644 --- a/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/repository/TokenRepository.java +++ b/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/repository/TokenRepository.java @@ -13,4 +13,6 @@ public interface TokenRepository extends JpaRepository { List findAllByEmailAndTokenType(String email, TokenType tokenType); void deleteByValue(String value); + + Boolean existsByValueAndTokenType(String value, TokenType tokenType); } diff --git a/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/service/TokenLockService.java b/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/service/TokenLockService.java new file mode 100644 index 00000000..e27ab16b --- /dev/null +++ b/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/service/TokenLockService.java @@ -0,0 +1,20 @@ +package com.pawith.authdomain.service; + +import com.pawith.authdomain.repository.TokenLockRepository; +import com.pawith.commonmodule.annotation.DomainService; +import lombok.RequiredArgsConstructor; + +@DomainService +@RequiredArgsConstructor +public class TokenLockService { + + private final TokenLockRepository tokenLockRepository; + + public void lockToken(final String token){ + tokenLockRepository.getNamedLock(token); + } + + public void releaseLockToken(final String token){ + tokenLockRepository.releaseNamedLock(token); + } +} diff --git a/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/service/TokenValidateService.java b/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/service/TokenValidateService.java new file mode 100644 index 00000000..98c370ac --- /dev/null +++ b/Domain-Module/Auth-Module/Auth-Domain/src/main/java/com/pawith/authdomain/service/TokenValidateService.java @@ -0,0 +1,20 @@ +package com.pawith.authdomain.service; + +import com.pawith.authdomain.exception.AuthError; +import com.pawith.authdomain.exception.NotExistTokenException; +import com.pawith.authdomain.jwt.TokenType; +import com.pawith.authdomain.repository.TokenRepository; +import com.pawith.commonmodule.annotation.DomainService; +import lombok.RequiredArgsConstructor; + +@DomainService +@RequiredArgsConstructor +public class TokenValidateService { + private final TokenRepository tokenRepository; + + public void validateIsExistToken(final String token, final TokenType tokenType) { + if(!tokenRepository.existsByValueAndTokenType(token, tokenType)){ + throw new NotExistTokenException(AuthError.NOT_EXIST_TOKEN); + } + } +} diff --git a/Domain-Module/Auth-Module/Auth-Domain/src/test/java/com/pawith/authdomain/jwt/JWTProviderTest.java b/Domain-Module/Auth-Module/Auth-Domain/src/test/java/com/pawith/authdomain/jwt/JWTProviderTest.java index 1d8b03b4..3dca854b 100644 --- a/Domain-Module/Auth-Module/Auth-Domain/src/test/java/com/pawith/authdomain/jwt/JWTProviderTest.java +++ b/Domain-Module/Auth-Module/Auth-Domain/src/test/java/com/pawith/authdomain/jwt/JWTProviderTest.java @@ -1,11 +1,9 @@ package com.pawith.authdomain.jwt; -import com.pawith.authdomain.exception.NotExistTokenException; import com.pawith.authdomain.jwt.exception.ExpiredTokenException; import com.pawith.authdomain.jwt.exception.InvalidTokenException; -import com.pawith.authdomain.service.TokenQueryService; -import com.pawith.authdomain.service.TokenSaveService; import com.pawith.commonmodule.UnitTestConfig; +import com.pawith.commonmodule.cache.operators.ValueOperator; import com.pawith.commonmodule.utils.FixtureMonkeyUtils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtParser; @@ -19,22 +17,19 @@ import org.junit.jupiter.api.Test; import org.mockito.Mock; -import static org.mockito.BDDMockito.given; - @UnitTestConfig @DisplayName("JWTProvider 테스트") public class JWTProviderTest { private final JWTProperties jwtProperties = new JWTProperties(JWTTestConsts.SECRET, JWTTestConsts.ACCESS_TOKEN_EXPIRED_TIME, JWTTestConsts.REFRESH_TOKEN_EXPIRED_TIME); - @Mock - private TokenSaveService tokenSaveService; - @Mock - private TokenQueryService tokenQueryService; private JWTProvider jwtProvider; + @Mock + private ValueOperator tokenValueOperator; + @BeforeEach void init(){ - jwtProvider = new JWTProvider(jwtProperties, tokenSaveService, tokenQueryService); + jwtProvider = new JWTProvider(jwtProperties,tokenValueOperator); } @Test @@ -63,38 +58,6 @@ void generateRefreshTokenByEmail(){ Assertions.assertThat(body.get(JWTConsts.TOKEN_TYPE)).isEqualTo(TokenType.REFRESH_TOKEN.toString()); } - @Test - @DisplayName("refreshToken을 이용하여 accessToken을 재발급한다.") - void reIssueAccessToken(){ - //given - final String randomEmail = FixtureMonkeyUtils.getConstructBasedFixtureMonkey().giveMeOne(String.class); - final String refreshToken = jwtProvider.generateRefreshToken(randomEmail); - given(tokenQueryService.findEmailByValue(refreshToken, TokenType.REFRESH_TOKEN)).willReturn(randomEmail); - //when - final String accessToken = jwtProvider.reIssueAccessToken(refreshToken); - final Claims claims = extractClaimsFromToken(accessToken); - //then - Assertions.assertThat(accessToken).isNotNull(); - Assertions.assertThat(claims.get(JWTConsts.EMAIL)).isEqualTo(randomEmail); - Assertions.assertThat(claims.get(JWTConsts.TOKEN_TYPE)).isEqualTo(TokenType.ACCESS_TOKEN.toString()); - } - - @Test - @DisplayName("refreshToken을 이용하여 refreshToken을 재발급한다.") - void reIssueRefreshToken(){ - //given - final String randomEmail = FixtureMonkeyUtils.getConstructBasedFixtureMonkey().giveMeOne(String.class); - final String refreshToken = jwtProvider.generateRefreshToken(randomEmail); - given(tokenQueryService.findEmailByValue(refreshToken, TokenType.REFRESH_TOKEN)).willReturn(randomEmail); - //when - final String newRefreshToken = jwtProvider.reIssueRefreshToken(refreshToken); - final Claims claims = extractClaimsFromToken(newRefreshToken); - //then - Assertions.assertThat(newRefreshToken).isNotNull(); - Assertions.assertThat(claims.get(JWTConsts.EMAIL)).isEqualTo(randomEmail); - Assertions.assertThat(claims.get(JWTConsts.TOKEN_TYPE)).isEqualTo(TokenType.REFRESH_TOKEN.toString()); - } - @Test @DisplayName("refreshToken이 만료되었을 때, accessToken 재발급시 예외가 발생한다.") @SneakyThrows @@ -109,31 +72,6 @@ void reIssueAccessTokenWithExpiredRefreshToken(){ .isInstanceOf(ExpiredTokenException.class); } - @Test - @DisplayName("accessToken에서 email을 추출한다.") - void extractEmailFromAccessToken(){ - //given - final String randomEmail = FixtureMonkeyUtils.getConstructBasedFixtureMonkey().giveMeOne(String.class); - final String accessToken = jwtProvider.generateAccessToken(randomEmail); - //when - final String email = jwtProvider.extractEmailFromAccessToken(accessToken); - //then - Assertions.assertThat(email).isEqualTo(randomEmail); - } - - @Test - @DisplayName("refreshToken에서 email을 추출할때 잘못된 토큰이면 NotExistTokenException 예외가 발생한다.") - void extractEmailFromRefreshToken(){ - //given - final String randomEmail = FixtureMonkeyUtils.getConstructBasedFixtureMonkey().giveMeOne(String.class); - final String refreshToken = jwtProvider.generateRefreshToken(randomEmail); - given(tokenQueryService.findEmailByValue(refreshToken, TokenType.REFRESH_TOKEN)).willThrow(NotExistTokenException.class); - //when - //then - Assertions.assertThatCode(() -> jwtProvider.reIssueRefreshToken(refreshToken)) - .isInstanceOf(NotExistTokenException.class); - } - @Test @DisplayName("validateToken 메소드에 잘못된 token이 입력되면 InvalidTokenException 예외가 발생한다.") void validateTokenWithInvalidToken(){ diff --git a/Domain-Module/Auth-Module/Auth-Presentation/src/main/java/com/pawith/authpresentation/common/security/filter/JWTEntryPoint.java b/Domain-Module/Auth-Module/Auth-Presentation/src/main/java/com/pawith/authpresentation/common/security/filter/JWTEntryPoint.java index 2bd15bd0..5a338982 100644 --- a/Domain-Module/Auth-Module/Auth-Presentation/src/main/java/com/pawith/authpresentation/common/security/filter/JWTEntryPoint.java +++ b/Domain-Module/Auth-Module/Auth-Presentation/src/main/java/com/pawith/authpresentation/common/security/filter/JWTEntryPoint.java @@ -28,7 +28,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse private void handleException(HttpServletResponse response, BusinessException exception) throws IOException { ErrorResponse errorResponse = ErrorResponse.from(exception); - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(errorResponse)); diff --git a/Domain-Module/Todo-Module/Todo-Application/build.gradle b/Domain-Module/Todo-Module/Todo-Application/build.gradle index 267ad1bc..57d2abbf 100644 --- a/Domain-Module/Todo-Module/Todo-Application/build.gradle +++ b/Domain-Module/Todo-Module/Todo-Application/build.gradle @@ -1,5 +1,6 @@ dependencies { implementation project(":Domain-Module:Todo-Module:Todo-Domain") + implementation project(":Domain-Module:Todo-Module:Todo-Infrastructure") implementation project(":Domain-Module:User-Module:User-Domain") implementation project(":Image-Module") } \ No newline at end of file diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/dto/request/AssignChangeRequest.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/dto/request/AssignChangeRequest.java new file mode 100644 index 00000000..51d37349 --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/dto/request/AssignChangeRequest.java @@ -0,0 +1,14 @@ +package com.pawith.todoapplication.dto.request; + +import java.util.List; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AssignChangeRequest { + private List registerIds; +} diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/dto/request/TodoCreateRequest.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/dto/request/TodoCreateRequest.java index 6513713b..159d67b9 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/dto/request/TodoCreateRequest.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/dto/request/TodoCreateRequest.java @@ -1,15 +1,19 @@ package com.pawith.todoapplication.dto.request; +import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; import java.time.LocalDate; import java.util.List; +import lombok.NoArgsConstructor; @Getter @AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class TodoCreateRequest { private Long categoryId; + private Long todoTeamId; private String description; private LocalDate scheduledDate; private List registerIds; diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/dto/response/TodoValidateResponse.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/dto/response/TodoValidateResponse.java new file mode 100644 index 00000000..45ae068d --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/dto/response/TodoValidateResponse.java @@ -0,0 +1,15 @@ +package com.pawith.todoapplication.dto.response; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class TodoValidateResponse { + private Boolean isNotValidate; +} diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/CategoryDeleteOnTodoHandler.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/CategoryDeleteOnTodoHandler.java index bc64d283..8bfe2657 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/CategoryDeleteOnTodoHandler.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/CategoryDeleteOnTodoHandler.java @@ -19,7 +19,7 @@ public class CategoryDeleteOnTodoHandler { @EventListener public void deleteAllByCategoryId(CategoryDeleteEvent categoryDeleteEvent) { - todoDeleteService.deleteTodoByCategoryId(categoryDeleteEvent.getCategoryId()); assignDeleteService.deleteAssignByCategoryId(categoryDeleteEvent.getCategoryId()); + todoDeleteService.deleteTodoByCategoryId(categoryDeleteEvent.getCategoryId()); } } 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 new file mode 100644 index 00000000..474896ee --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/IncompleteTodoCountNotificationHandler.java @@ -0,0 +1,62 @@ +package com.pawith.todoapplication.handler; + +import com.pawith.commonmodule.cache.operators.ValueOperator; +import com.pawith.commonmodule.event.MultiNotificationEvent; +import com.pawith.commonmodule.event.NotificationEvent; +import com.pawith.commonmodule.schedule.AbstractBatchSchedulingHandler; +import com.pawith.tododomain.repository.RegisterRepository; +import com.pawith.tododomain.repository.dao.IncompleteTodoCountInfoDao; +import com.pawith.userdomain.service.UserQueryService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.domain.Pageable; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +@Slf4j +//@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 NOTIFICATION_MESSAGE = "[%s] 오늘이 지나기 전, %s님에게 남은 %d개의 todo를 완료해주세요!"; + + private final RegisterRepository registerRepository; + private final UserQueryService userQueryService; + private final ApplicationEventPublisher applicationEventPublisher; + private final ValueOperator valueOperator; + + public IncompleteTodoCountNotificationHandler(RegisterRepository registerRepository, UserQueryService userQueryService, ApplicationEventPublisher applicationEventPublisher, ValueOperator valueOperator) { + super(BATCH_SIZE, CRON_EXPRESSION); + this.registerRepository = registerRepository; + this.userQueryService = userQueryService; + this.applicationEventPublisher = applicationEventPublisher; + this.valueOperator = valueOperator; + } + + @Override + protected List extractBatchData(Pageable pageable) { + return registerRepository.findAllIncompleteTodoCountInfoQuery(pageable); + } + + @Override + protected void processBatch(List executionResult) { + cachingUserInfo(executionResult); + final List notificationEventList = executionResult.stream() + .map(incompleteTodoCountInfoDao -> { + final String userNickname = valueOperator.get(incompleteTodoCountInfoDao.getUserId()); + final String message = String.format(NOTIFICATION_MESSAGE, incompleteTodoCountInfoDao.getTodoTeamName(), userNickname, incompleteTodoCountInfoDao.getIncompleteTodoCount()); + return new NotificationEvent(incompleteTodoCountInfoDao.getUserId(), incompleteTodoCountInfoDao.getTodoTeamName(), message, incompleteTodoCountInfoDao.getTodoTeamId()); + }).toList(); + applicationEventPublisher.publishEvent(new MultiNotificationEvent(notificationEventList)); + } + + private void cachingUserInfo(List executionResult) { + final List userIds = executionResult.stream() + .map(IncompleteTodoCountInfoDao::getUserId) + .filter(userId -> !valueOperator.contains(userId)) + .toList(); + userQueryService.findUserMapByIds(userIds) + .forEach((userId, user) -> valueOperator.setWithExpire(userId, user.getNickname(), 1, TimeUnit.MINUTES)); + } +} 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 a4cb6141..bb3f79d4 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 @@ -2,18 +2,18 @@ import com.pawith.todoapplication.handler.event.TodoCompletionCheckEvent; import com.pawith.tododomain.entity.Assign; -import com.pawith.tododomain.entity.CompletionStatus; import com.pawith.tododomain.entity.Todo; import com.pawith.tododomain.service.AssignQueryService; import com.pawith.tododomain.service.TodoQueryService; import jakarta.persistence.OptimisticLockException; -import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Slf4j @Component @RequiredArgsConstructor @@ -28,11 +28,8 @@ public void changeTodoStatus(TodoCompletionCheckEvent todoCompletionCheckEvent) try { final List assigns = assignQueryService.findAllAssignByTodoId(todoCompletionCheckEvent.getTodoId()); final Todo todo = todoQueryService.findTodoByTodoId(todoCompletionCheckEvent.getTodoId()); - CompletionStatus newStatus = assigns.stream() - .allMatch(assign -> assign.getCompletionStatus() == CompletionStatus.COMPLETE) - ? CompletionStatus.COMPLETE - : CompletionStatus.INCOMPLETE; - todo.updateCompletionStatus(newStatus); + final boolean isAllCompleteTodo = assigns.stream().allMatch(Assign::isCompleted); + todo.updateCompletionStatus(isAllCompleteTodo); break; } catch (OptimisticLockException e) { Thread.sleep(20); diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/TodoNotificationHandler.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/TodoNotificationHandler.java index e62e7c28..a6b10ed4 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/TodoNotificationHandler.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/handler/TodoNotificationHandler.java @@ -1,73 +1,107 @@ package com.pawith.todoapplication.handler; -import com.pawith.commonmodule.enums.AlarmCategory; +import com.pawith.commonmodule.event.MultiNotificationEvent; import com.pawith.commonmodule.event.NotificationEvent; +import com.pawith.commonmodule.schedule.AbstractBatchSchedulingHandler; import com.pawith.tododomain.repository.TodoNotificationRepository; import com.pawith.tododomain.repository.dao.NotificationDao; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Slice; -import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StringUtils; import java.time.Duration; -import java.time.LocalTime; +import java.time.LocalDateTime; +import java.util.List; +import java.util.function.Function; +/** + * Batch Insert 전 : 1637ms + * Batch Insert 후 : 1293ms + * 20 건 기준 + */ @Slf4j @Component -@Transactional -@RequiredArgsConstructor -public class TodoNotificationHandler { +public class TodoNotificationHandler extends AbstractBatchSchedulingHandler { private static final Integer BATCH_SIZE = 100; - private static final String FIRST_NOTIFICATION_MESSAGE_FORMAT = "오늘 %s시 %s분, [%s] %s 잊지 않았죠?"; - private static final String SECOND_NOTIFICATION_MESSAGE_FORMAT = "[%s] %s 시작까지 1시간 남았어요!"; + private static final String CRON_EXPRESSION = "0 0 * * * *"; private final TodoNotificationRepository todoNotificationRepository; private final ApplicationEventPublisher applicationEventPublisher; - private static String buildMessageFromNotification(NotificationDao notification, long diffNotificationTimeWithCurrentTime) { - if (diffNotificationTimeWithCurrentTime == 3L) { - return String.format(FIRST_NOTIFICATION_MESSAGE_FORMAT, notification.getNotificationTime().getHour(), notification.getNotificationTime().getMinute(), notification.getCategoryName(), notification.getTodoDescription()); - } else if (diffNotificationTimeWithCurrentTime == 1L) { - return String.format(SECOND_NOTIFICATION_MESSAGE_FORMAT, notification.getCategoryName(), notification.getTodoDescription()); - } - return ""; + public TodoNotificationHandler(TodoNotificationRepository todoNotificationRepository, ApplicationEventPublisher applicationEventPublisher) { + super(BATCH_SIZE, CRON_EXPRESSION); + this.todoNotificationRepository = todoNotificationRepository; + this.applicationEventPublisher = applicationEventPublisher; } - @Scheduled(cron = "0 0 * * * *") - public void sendTodoNotification() { - int page = 0; - final LocalTime now = LocalTime.now().withMinute(0); - boolean hasNext = true; - do { - final PageRequest pageRequest = PageRequest.of(page, BATCH_SIZE); - final Slice notificationBatch = todoNotificationRepository.findAllWithNotCompletedAssignAndTodayScheduledTodo(Duration.ofHours(3), now, pageRequest); - handleNotification(notificationBatch); - hasNext = notificationBatch.hasNext(); - page++; - } while (hasNext); + @Override + protected List extractBatchData(Pageable pageable) { + return todoNotificationRepository.findAllWithNotCompletedAssignAndAlarmTimeQuery(Duration.ofHours(3), getCurrentHourDateTime(), pageable); } - private void handleNotification(Slice notificationBatch) { - notificationBatch.stream().parallel() - .forEach(notification -> { - final long diffNotificationTimeWithCurrentTime = diffNotificationTimeWithCurrentTime(notification.getNotificationTime()); - String message = buildMessageFromNotification(notification, diffNotificationTimeWithCurrentTime); - if (StringUtils.hasText(message)) { - applicationEventPublisher.publishEvent( - new NotificationEvent(notification.getUserId(), - AlarmCategory.TODO, - message, - notification.getTodoTeamId())); - } - }); + @Override + protected void processBatch(List executionResult) { + handleNotification(executionResult, getCurrentHourDateTime()); + } + + private void handleNotification(List notificationBatch, LocalDateTime executeTime) { + final List notificationEventList = notificationBatch.stream() + .filter(notification -> NotificationMessage.contains(calculateDiffTimeHour(executeTime, notification))) + .map(notification -> { + final long diffNotificationTimeWithCurrentTime = calculateDiffTimeHour(executeTime, notification); + final String message = NotificationMessage.buildMessage(notification, diffNotificationTimeWithCurrentTime); + return new NotificationEvent(notification.getUserId(), notification.getTodoTeamName(), message, notification.getTodoTeamId()); + }).toList(); + applicationEventPublisher.publishEvent(new MultiNotificationEvent(notificationEventList)); + } + + private long calculateDiffTimeHour(LocalDateTime executeTime, NotificationDao notification) { + return Duration.between(executeTime, LocalDateTime.of(notification.getScheduledDate(), notification.getNotificationTime())).toHours(); + } + + private LocalDateTime getCurrentHourDateTime() { + return LocalDateTime.now().withMinute(0).withSecond(0).withNano(0); } - private Long diffNotificationTimeWithCurrentTime(LocalTime notificationTime) { - return Duration.between(LocalTime.now(), notificationTime).toHours(); + private enum NotificationMessage { + FIRST_NOTIFICATION_MESSAGE_FORMAT(3L, + notification -> { + final String messageFormat = "오늘 %s시 %s분, [%s] %s 잊지 않았죠?"; + return String.format(messageFormat, + notification.getNotificationTime().getHour(), + notification.getNotificationTime().getMinute(), + notification.getCategoryName(), + notification.getTodoDescription()); + }), + SECOND_NOTIFICATION_MESSAGE_FORMAT(1L, + notification -> { + final String messageFormat = "[%s] %s 시작까지 1시간 남았어요!"; + return String.format(messageFormat, + notification.getCategoryName(), + notification.getTodoDescription()); + }); + + private static final List values = List.of(values()); + private final Long criticalTime; + private final Function buildMessage; + + NotificationMessage(Long criticalTime, Function buildMessage) { + this.criticalTime = criticalTime; + this.buildMessage = buildMessage; + } + + public static Boolean contains(Long diffTime) { + return values.stream() + .anyMatch(notificationMessage -> notificationMessage.criticalTime.equals(diffTime)); + } + + public static String buildMessage(NotificationDao notification, long diffTime) { + NotificationMessage messageBuilder = values.stream() + .filter(notificationMessage -> notificationMessage.criticalTime.equals(diffTime)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("해당 시간에 맞는 메시지가 없습니다.")); + return messageBuilder.buildMessage.apply(notification); + } } } diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/mapper/TodoMapper.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/mapper/TodoMapper.java index 889a38a6..41ecbbe4 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/mapper/TodoMapper.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/mapper/TodoMapper.java @@ -14,11 +14,12 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public class TodoMapper { - public static Todo mapToTodo(TodoCreateRequest request, Category category){ + public static Todo mapToTodo(TodoCreateRequest request, Category category, Long registerId){ return Todo.builder() .category(category) .description(request.getDescription()) .scheduledDate(request.getScheduledDate()) + .creatorId(registerId) .build(); } 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 7e09102a..37682395 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 @@ -1,14 +1,22 @@ package com.pawith.todoapplication.service; +import static java.util.stream.Collectors.toList; + import com.pawith.commonmodule.annotation.ApplicationService; +import com.pawith.todoapplication.dto.request.AssignChangeRequest; import com.pawith.todoapplication.handler.event.TodoCompletionCheckEvent; import com.pawith.tododomain.entity.Assign; +import com.pawith.tododomain.entity.Register; import com.pawith.tododomain.entity.Todo; +import com.pawith.tododomain.service.AssignDeleteService; import com.pawith.tododomain.service.AssignQueryService; +import com.pawith.tododomain.service.AssignSaveService; +import com.pawith.tododomain.service.RegisterQueryService; import com.pawith.tododomain.service.TodoQueryService; import com.pawith.userdomain.entity.User; import com.pawith.userdomain.utils.UserUtils; import jakarta.transaction.Transactional; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.context.ApplicationEventPublisher; @@ -19,6 +27,9 @@ public class AssignChangeUseCase { private final AssignQueryService assignQueryService; private final TodoQueryService todoQueryService; + private final AssignDeleteService assignDeleteService; + private final AssignSaveService assignSaveService; + private final RegisterQueryService registerQueryService; private final UserUtils userUtils; private final ApplicationEventPublisher applicationEventPublisher; @@ -29,4 +40,42 @@ public void changeAssignStatus(Long todoId){ assign.updateCompletionStatus(); applicationEventPublisher.publishEvent(new TodoCompletionCheckEvent(todo.getId())); } + + public void changeAssign(Long todoId, AssignChangeRequest request) { + final Todo todo = todoQueryService.findTodoByTodoId(todoId); + final List assignList = assignQueryService.findAllAssignWithRegisterByTodoId(todoId); + + List existingAssigneeIds = assignList.stream() + .map(assign -> assign.getRegister().getId()) + .collect(toList()); + + List addedAssigneeIds = findAddedAssignees(request.getRegisterIds(), existingAssigneeIds); + List removedAssigneeIds = findRemovedAssignees(request.getRegisterIds(), existingAssigneeIds); + + saveNewAssignees(todo, addedAssigneeIds); + deleteRemovedAssignees(removedAssigneeIds); + } + + private List findAddedAssignees(List requestAssigneeIds, List existingAssigneeIds) { + return requestAssigneeIds.stream() + .filter(assigneeId -> !existingAssigneeIds.contains(assigneeId)) + .collect(toList()); + } + + private List findRemovedAssignees(List requestAssigneeIds, List existingAssigneeIds) { + return existingAssigneeIds.stream() + .filter(assigneeId -> !requestAssigneeIds.contains(assigneeId)) + .collect(toList()); + } + + private void saveNewAssignees(Todo todo, List addedAssigneeIds) { + List addRegisterList = registerQueryService.findAllRegistersByIds(addedAssigneeIds); + addRegisterList.forEach(register -> + assignSaveService.saveAssignEntity(new Assign(todo, register)) + ); + } + + private void deleteRemovedAssignees(List removedAssigneeIds) { + assignDeleteService.deleteAssignByRegisterId(removedAssigneeIds); + } } diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/CategoryChangeUseCase.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/CategoryChangeUseCase.java index ffe21f1b..58bc22b4 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/CategoryChangeUseCase.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/CategoryChangeUseCase.java @@ -3,7 +3,6 @@ import com.pawith.commonmodule.annotation.ApplicationService; import com.pawith.todoapplication.dto.request.CategoryNameChageRequest; import com.pawith.tododomain.entity.Category; -import com.pawith.tododomain.entity.CategoryStatus; import com.pawith.tododomain.service.CategoryQueryService; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; @@ -17,12 +16,7 @@ public class CategoryChangeUseCase { public void changeCategoryStatus(Long categoryId) { Category category = categoryQueryService.findCategoryByCategoryId(categoryId); - if(category.getCategoryStatus().equals(CategoryStatus.ON) == true){ - category.updateCategoryStatus(CategoryStatus.OFF); - } - else { - category.updateCategoryStatus(CategoryStatus.ON); - } + category.updateCategoryStatus(); } public void changeCategoryName(Long categoryId, CategoryNameChageRequest categoryNameChageRequest) { diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/CategoryGetUseCase.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/CategoryGetUseCase.java index 90712b4c..b1214255 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/CategoryGetUseCase.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/CategoryGetUseCase.java @@ -6,6 +6,7 @@ import com.pawith.todoapplication.dto.response.CategoryManageInfoResponse; import com.pawith.tododomain.entity.Category; import com.pawith.tododomain.service.CategoryQueryService; +import java.time.LocalDate; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; @@ -19,8 +20,8 @@ public class CategoryGetUseCase { private final CategoryQueryService categoryQueryService; - public ListResponse getCategoryList(final Long todoTeamId) { - List categoryList = categoryQueryService.findCategoryListByTodoTeamIdAndStatus(todoTeamId); + public ListResponse getCategoryList(final Long todoTeamId, LocalDate moveDate) { + List categoryList = categoryQueryService.findCategoryListByTodoTeamIdAndStatus(todoTeamId, moveDate); List categorySimpleResponses = categoryList.stream() .map(category -> new CategoryInfoResponse(category.getId(), category.getName())) .collect(Collectors.toList()); diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/RegistersGetUseCase.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/RegistersGetUseCase.java index ab2ed468..6b90fe04 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/RegistersGetUseCase.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/RegistersGetUseCase.java @@ -2,18 +2,21 @@ import com.pawith.commonmodule.annotation.ApplicationService; import com.pawith.commonmodule.response.ListResponse; -import com.pawith.todoapplication.dto.response.*; +import com.pawith.todoapplication.dto.response.RegisterInfoResponse; +import com.pawith.todoapplication.dto.response.RegisterManageInfoResponse; +import com.pawith.todoapplication.dto.response.RegisterSearchInfoResponse; +import com.pawith.todoapplication.dto.response.RegisterTermResponse; import com.pawith.tododomain.entity.Authority; import com.pawith.tododomain.entity.Register; import com.pawith.tododomain.service.RegisterQueryService; import com.pawith.userdomain.entity.User; import com.pawith.userdomain.service.UserQueryService; import com.pawith.userdomain.utils.UserUtils; -import java.util.Comparator; -import java.util.LinkedHashMap; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; +import java.util.Comparator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -89,14 +92,12 @@ private Map getRegisterUserMap(List registers, List sortedRegisters = registers.stream() .sorted(Comparator.comparing(Register::getAuthority, authorityComparator)) - .collect(Collectors.toList()); + .toList(); - Map userRegisterMap = users.stream() + return users.stream() .flatMap(user -> sortedRegisters.stream() .filter(register -> user.getId().equals(register.getUserId())) .map(register -> Map.entry(user, register))) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (existing, replacement) -> existing, LinkedHashMap::new)); - - return userRegisterMap; } } diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoCreateUseCase.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoCreateUseCase.java index e9a1aa43..f75c3002 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoCreateUseCase.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoCreateUseCase.java @@ -11,6 +11,8 @@ import com.pawith.tododomain.service.CategoryQueryService; import com.pawith.tododomain.service.RegisterQueryService; import com.pawith.tododomain.service.TodoSaveService; +import com.pawith.userdomain.entity.User; +import com.pawith.userdomain.utils.UserUtils; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; @@ -25,10 +27,13 @@ public class TodoCreateUseCase { private final RegisterQueryService registerQueryService; private final CategoryQueryService categoryQueryService; private final AssignSaveService assignSaveService; + private final UserUtils userUtils; public void createTodo(TodoCreateRequest request) { + final User accessUser = userUtils.getAccessUser(); + final Long accessUserRegisterId = registerQueryService.findRegisterByTodoTeamIdAndUserId(request.getTodoTeamId(), accessUser.getId()).getId(); final Category category = categoryQueryService.findCategoryByCategoryId(request.getCategoryId()); - final Todo todo = TodoMapper.mapToTodo(request, category); + final Todo todo = TodoMapper.mapToTodo(request, category, accessUserRegisterId); todoSaveService.saveTodoEntity(todo); ListIterator registerListIterator = registerQueryService.findAllRegistersByIds(request.getRegisterIds()).listIterator(); request.getRegisterIds().forEach(registerId -> { diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoDeleteUseCase.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoDeleteUseCase.java index fd6bb2f2..125de1b3 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoDeleteUseCase.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoDeleteUseCase.java @@ -2,7 +2,15 @@ import com.pawith.commonmodule.annotation.ApplicationService; import com.pawith.todoapplication.handler.event.TodoDeleteEvent; +import com.pawith.tododomain.entity.Register; +import com.pawith.tododomain.entity.Todo; +import com.pawith.tododomain.service.RegisterQueryService; import com.pawith.tododomain.service.TodoDeleteService; +import com.pawith.tododomain.service.TodoQueryService; +import com.pawith.tododomain.service.TodoTeamQueryService; +import com.pawith.tododomain.service.TodoValidateService; +import com.pawith.userdomain.entity.User; +import com.pawith.userdomain.utils.UserUtils; import lombok.RequiredArgsConstructor; import org.springframework.context.ApplicationEventPublisher; import org.springframework.transaction.annotation.Transactional; @@ -13,10 +21,20 @@ public class TodoDeleteUseCase { private final TodoDeleteService todoDeleteService; + private final TodoQueryService todoQueryService; + private final TodoTeamQueryService todoTeamQueryService; + private final RegisterQueryService registerQueryService; + private final TodoValidateService todoValidateService; + private final UserUtils userUtils; private final ApplicationEventPublisher applicationEventPublisher; public void deleteTodoByTodoId(Long todoId) { + User accessUser = userUtils.getAccessUser(); + final Todo todo = todoQueryService.findTodoByTodoId(todoId); + final Long todoTeamId = todoTeamQueryService.findTodoTeamByTodoId(todoId).getId(); + final Register register = registerQueryService.findRegisterByTodoTeamIdAndUserId(todoTeamId, accessUser.getId()); + todoValidateService.validateTodoDeletable(todo, register); todoDeleteService.deleteTodoByTodoId(todoId); applicationEventPublisher.publishEvent(new TodoDeleteEvent(todoId)); } diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoTeamCreateUseCase.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoTeamCreateUseCase.java index 3988115b..6f671140 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoTeamCreateUseCase.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoTeamCreateUseCase.java @@ -31,16 +31,16 @@ public class TodoTeamCreateUseCase { private final PetSaveService petSaveService; private final ImageUploadService imageUploadService; - public void createTodoTeam(MultipartFile teamImageFile, List imageFiles, TodoTeamCreateRequest request) { - CompletableFuture teamImageAsync = imageUploadService.uploadImgAsync(teamImageFile); - List> petImageAsync = imageUploadService.uploadImgListAsync(imageFiles); + public void createTodoTeam(final MultipartFile teamImageFile,final List imageFiles,final TodoTeamCreateRequest request) { + final CompletableFuture teamImageAsync = imageUploadService.uploadImgAsync(teamImageFile); + final List> petImageAsync = imageUploadService.uploadImgListAsync(imageFiles); final User user = userUtils.getAccessUser(); final TodoTeam todoTeam = TodoTeamMapper.mapToTodoTeam(request, teamImageAsync.join()); todoTeamSaveService.saveTodoTeamEntity(todoTeam); registerSaveService.saveRegisterAboutPresident(todoTeam, user.getId()); - ListIterator> futureListIterator = petImageAsync.listIterator(); + final ListIterator> futureListIterator = petImageAsync.listIterator(); request.getPetRegisters().forEach(petRegister -> { String imageUrl = futureListIterator.next().join(); final Pet pet = PetMapper.mapToPet(petRegister, todoTeam, imageUrl); diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoTeamRandomCodeGetUseCase.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoTeamRandomCodeGetUseCase.java index 9ca64468..207c3dc4 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoTeamRandomCodeGetUseCase.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoTeamRandomCodeGetUseCase.java @@ -2,16 +2,16 @@ import com.pawith.commonmodule.annotation.ApplicationService; import com.pawith.todoapplication.dto.response.TodoTeamRandomCodeResponse; -import com.pawith.tododomain.service.TodoTeamCodeGenerateService; +import com.pawith.tododomain.service.TodoTeamCodeManageService; import lombok.RequiredArgsConstructor; @ApplicationService @RequiredArgsConstructor public class TodoTeamRandomCodeGetUseCase { - private final TodoTeamCodeGenerateService todoTeamCodeGenerateService; + private final TodoTeamCodeManageService todoTeamCodeManageService; public TodoTeamRandomCodeResponse generateRandomCode() { - return new TodoTeamRandomCodeResponse(todoTeamCodeGenerateService.generateRandomCode()); + return new TodoTeamRandomCodeResponse(todoTeamCodeManageService.generateRandomCode()); } } diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoTeamRegisterUseCase.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoTeamRegisterUseCase.java index 4b20d2ec..45489775 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoTeamRegisterUseCase.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoTeamRegisterUseCase.java @@ -19,7 +19,7 @@ public class TodoTeamRegisterUseCase { public void registerTodoTeam(String todoTeamCode) { final User user = userUtils.getAccessUser(); - TodoTeam todoTeam = todoTeamQueryService.findTodoTeamByCode(todoTeamCode); + final TodoTeam todoTeam = todoTeamQueryService.findTodoTeamByCode(todoTeamCode); registerSaveService.saveRegisterAboutMember(todoTeam, user.getId()); } } diff --git a/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoValidationUseCase.java b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoValidationUseCase.java new file mode 100644 index 00000000..0f961877 --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Application/src/main/java/com/pawith/todoapplication/service/TodoValidationUseCase.java @@ -0,0 +1,31 @@ +package com.pawith.todoapplication.service; + +import com.pawith.commonmodule.annotation.ApplicationService; +import com.pawith.todoapplication.dto.response.TodoValidateResponse; +import com.pawith.tododomain.entity.Register; +import com.pawith.tododomain.entity.Todo; +import com.pawith.tododomain.service.RegisterQueryService; +import com.pawith.tododomain.service.TodoQueryService; +import com.pawith.tododomain.service.TodoValidateService; +import com.pawith.userdomain.entity.User; +import com.pawith.userdomain.utils.UserUtils; +import lombok.RequiredArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +@ApplicationService +@RequiredArgsConstructor +@Transactional +public class TodoValidationUseCase { + private final UserUtils userUtils; + private final TodoQueryService todoQueryService; + private final RegisterQueryService registerQueryService; + private final TodoValidateService todoValidateService; + + public TodoValidateResponse validateDeleteAndUpdateTodoByTodoId(Long todoTeamId, Long todoId) { + final User accessUser = userUtils.getAccessUser(); + final Todo todo = todoQueryService.findTodoByTodoId(todoId); + final Register register = registerQueryService.findRegisterByTodoTeamIdAndUserId(todoTeamId, accessUser.getId()); + boolean isNotValidate = todoValidateService.validateDeleteAndUpdate(todo, register); + return new TodoValidateResponse(isNotValidate); + } +} diff --git a/Domain-Module/Todo-Module/Todo-Application/src/test/java/com/pawith/todoapplication/service/AssignChageUseCaseTest.java b/Domain-Module/Todo-Module/Todo-Application/src/test/java/com/pawith/todoapplication/service/AssignChageUseCaseTest.java index e5ff9613..e3c0125d 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/test/java/com/pawith/todoapplication/service/AssignChageUseCaseTest.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/test/java/com/pawith/todoapplication/service/AssignChageUseCaseTest.java @@ -7,7 +7,10 @@ import com.pawith.tododomain.entity.Assign; import com.pawith.tododomain.entity.CompletionStatus; import com.pawith.tododomain.entity.Todo; +import com.pawith.tododomain.service.AssignDeleteService; import com.pawith.tododomain.service.AssignQueryService; +import com.pawith.tododomain.service.AssignSaveService; +import com.pawith.tododomain.service.RegisterQueryService; import com.pawith.tododomain.service.TodoQueryService; import com.pawith.userdomain.entity.User; import com.pawith.userdomain.utils.UserUtils; @@ -27,6 +30,12 @@ public class AssignChageUseCaseTest { @Mock TodoQueryService todoQueryService; @Mock + AssignDeleteService assignDeleteService; + @Mock + AssignSaveService assignSaveService; + @Mock + RegisterQueryService registerQueryService; + @Mock private UserUtils userUtils; @Mock ApplicationEventPublisher applicationEventPublisher; @@ -35,7 +44,7 @@ public class AssignChageUseCaseTest { @BeforeEach void init(){ - assignChangeUseCase = new AssignChangeUseCase(assignQueryService, todoQueryService, userUtils, applicationEventPublisher); + assignChangeUseCase = new AssignChangeUseCase(assignQueryService, todoQueryService, assignDeleteService, assignSaveService, registerQueryService, userUtils, applicationEventPublisher); } @Test diff --git a/Domain-Module/Todo-Module/Todo-Application/src/test/java/com/pawith/todoapplication/service/CategoryChangeUseCaseTest.java b/Domain-Module/Todo-Module/Todo-Application/src/test/java/com/pawith/todoapplication/service/CategoryChangeUseCaseTest.java new file mode 100644 index 00000000..5e44eb56 --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Application/src/test/java/com/pawith/todoapplication/service/CategoryChangeUseCaseTest.java @@ -0,0 +1,78 @@ +package com.pawith.todoapplication.service; + +import com.pawith.commonmodule.UnitTestConfig; +import com.pawith.commonmodule.utils.FixtureMonkeyUtils; +import com.pawith.todoapplication.dto.request.CategoryNameChageRequest; +import com.pawith.tododomain.entity.Category; +import com.pawith.tododomain.entity.CategoryStatus; +import com.pawith.tododomain.service.CategoryQueryService; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import static org.mockito.BDDMockito.given; + +@UnitTestConfig +@DisplayName("CategoryChangeUseCase 테스트") +public class CategoryChangeUseCaseTest { + + @Mock + private CategoryQueryService categoryQueryService; + + private CategoryChangeUseCase categoryChangeUseCase; + + @BeforeEach + void init() { + categoryChangeUseCase = new CategoryChangeUseCase(categoryQueryService); + } + + @Test + @DisplayName("카테고리 상태가 ON 일때 OFF 로 변경 테스트") + void changeCategoryStatusOn() { + //given + Long categoryId = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeOne(Long.class); + Category category = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeBuilder(Category.class) + .set("categoryStatus", CategoryStatus.ON) + .sample(); + given(categoryQueryService.findCategoryByCategoryId(categoryId)).willReturn(category); + //when + categoryChangeUseCase.changeCategoryStatus(categoryId); + //then + Assertions.assertThat(category.getCategoryStatus()).isEqualTo(CategoryStatus.OFF); + } + + @Test + @DisplayName("카테고리 상태가 OFF 일때 ON 로 변경 테스트") + void changeCategoryStatusOff() { + //given + Long categoryId = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeOne(Long.class); + Category category = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeBuilder(Category.class) + .set("categoryStatus", CategoryStatus.OFF) + .sample(); + given(categoryQueryService.findCategoryByCategoryId(categoryId)).willReturn(category); + //when + categoryChangeUseCase.changeCategoryStatus(categoryId); + //then + Assertions.assertThat(category.getCategoryStatus()).isEqualTo(CategoryStatus.ON); + } + + @Test + @DisplayName("카테고리 이름 변경 테스트") + void changeCategoryName() { + //given + Long categoryId = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeOne(Long.class); + CategoryNameChageRequest categoryNameChageRequest = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeOne(CategoryNameChageRequest.class); + String categoryName = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeOne(String.class); + Category category = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeBuilder(Category.class) + .set("categoryName", categoryName) + .sample(); + given(categoryQueryService.findCategoryByCategoryId(categoryId)).willReturn(category); + //when + categoryChangeUseCase.changeCategoryName(categoryId, categoryNameChageRequest); + //then + Assertions.assertThat(category.getName()).isEqualTo(categoryNameChageRequest.getCategoryName()); + } + +} diff --git a/Domain-Module/Todo-Module/Todo-Application/src/test/java/com/pawith/todoapplication/service/CategoryGetUseCaseTest.java b/Domain-Module/Todo-Module/Todo-Application/src/test/java/com/pawith/todoapplication/service/CategoryGetUseCaseTest.java index 8eaf93d2..276869f1 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/test/java/com/pawith/todoapplication/service/CategoryGetUseCaseTest.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/test/java/com/pawith/todoapplication/service/CategoryGetUseCaseTest.java @@ -7,6 +7,7 @@ import com.pawith.todoapplication.dto.response.CategoryInfoResponse; import com.pawith.tododomain.entity.Category; import com.pawith.tododomain.service.CategoryQueryService; +import java.time.LocalDate; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -37,9 +38,10 @@ void getCategoryList() { final Long TodoTeamId = FixtureMonkey.create().giveMeOne(Long.class); final List categoryList = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey() .giveMe(Category.class, 5); - given(categoryQueryService.findCategoryListByTodoTeamIdAndStatus(TodoTeamId)).willReturn(categoryList); + final LocalDate moveDate = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeOne(LocalDate.class); + given(categoryQueryService.findCategoryListByTodoTeamIdAndStatus(TodoTeamId, moveDate)).willReturn(categoryList); // when - final ListResponse response = categoryGetUseCase.getCategoryList(TodoTeamId); + final ListResponse response = categoryGetUseCase.getCategoryList(TodoTeamId, moveDate); // then Assertions.assertThat(response.getContent().size()).isEqualTo(categoryList.size()); } diff --git a/Domain-Module/Todo-Module/Todo-Application/src/test/java/com/pawith/todoapplication/service/ChangeRegisterUseCaseTest.java b/Domain-Module/Todo-Module/Todo-Application/src/test/java/com/pawith/todoapplication/service/ChangeRegisterUseCaseTest.java index 33e726e9..2771e0f6 100644 --- a/Domain-Module/Todo-Module/Todo-Application/src/test/java/com/pawith/todoapplication/service/ChangeRegisterUseCaseTest.java +++ b/Domain-Module/Todo-Module/Todo-Application/src/test/java/com/pawith/todoapplication/service/ChangeRegisterUseCaseTest.java @@ -9,7 +9,6 @@ import com.pawith.tododomain.service.RegisterValidateService; import com.pawith.userdomain.entity.User; import com.pawith.userdomain.utils.UserUtils; -import lombok.extern.slf4j.Slf4j; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -49,7 +48,7 @@ void changeAuthorityToPresident() { final Long registerId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); final Long todoTeamId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); - final AuthorityChangeRequest authorityChangeRequest = FixtureMonkeyUtils.getConstructBasedFixtureMonkey() + final AuthorityChangeRequest authorityChangeRequest = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey() .giveMeBuilder(AuthorityChangeRequest.class) .set("authority", Authority.PRESIDENT) .sample(); diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/Assign.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/Assign.java index 4a2e7e5e..22c5e6ad 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/Assign.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/Assign.java @@ -47,4 +47,8 @@ public void updateCompletionStatus() { else this.completionStatus = CompletionStatus.COMPLETE; } + + public Boolean isCompleted() { + return this.completionStatus.equals(CompletionStatus.COMPLETE); + } } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/Category.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/Category.java index cc12a58c..4d28b027 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/Category.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/Category.java @@ -3,6 +3,7 @@ import com.pawith.commonmodule.domain.BaseEntity; import com.pawith.commonmodule.util.DomainFieldUtils; import jakarta.persistence.*; +import java.time.LocalDate; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -10,7 +11,6 @@ import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.Where; -import java.util.Objects; @Entity @Getter @@ -30,6 +30,8 @@ public class Category extends BaseEntity { private Boolean isDeleted=Boolean.FALSE; + private LocalDate disabledAt; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "team_id") private TodoTeam todoTeam; @@ -39,13 +41,19 @@ public Category(String name, CategoryStatus categoryStatus, TodoTeam todoTeam) { this.name = name; this.categoryStatus = categoryStatus; this.todoTeam = todoTeam; + this.disabledAt = LocalDate.now(); } - public void updateCategoryStatus(CategoryStatus categoryStatus) { + public void updateCategoryStatus() { this.categoryStatus = DomainFieldUtils.DomainValidateBuilder.builder(CategoryStatus.class) - .newValue(categoryStatus) + .newValue(this.categoryStatus.equals(CategoryStatus.ON) ? CategoryStatus.OFF : CategoryStatus.ON) .currentValue(this.categoryStatus) .validate(); + + this.disabledAt = DomainFieldUtils.DomainValidateBuilder.builder(LocalDate.class) + .newValue(LocalDate.now()) + .currentValue(this.disabledAt) + .validate(); } public void updateCategoryName(String categoryName) { diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/Register.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/Register.java index 7a9ea2a1..0f35a560 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/Register.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/Register.java @@ -69,6 +69,10 @@ public Boolean isPresident(){ return this.authority.equals(Authority.PRESIDENT); } + public Boolean isMember() { + return this.authority.equals(Authority.MEMBER); + } + public void reRegister(){ this.isRegistered = Boolean.TRUE; this.registerAt = LocalDateTime.now(); diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/Todo.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/Todo.java index 1afb8217..d4906d2f 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/Todo.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/Todo.java @@ -36,15 +36,18 @@ public class Todo extends BaseEntity { @JoinColumn(name = "category_id") private Category category; + private Long creatorId; + @Version private Long version; @Builder - public Todo(String description, LocalDate scheduledDate, Category category) { + public Todo(String description, LocalDate scheduledDate, Category category, Long creatorId) { this.description = description; this.scheduledDate = scheduledDate; this.completionStatus = CompletionStatus.INCOMPLETE; this.category = category; + this.creatorId = creatorId; } public void updateScheduledDate(LocalDate scheduledDate) { @@ -61,10 +64,11 @@ public void updateDescription(String description) { .validate(); } - public void updateCompletionStatus(CompletionStatus completionStatus){ - this.completionStatus = DomainFieldUtils.DomainValidateBuilder.builder(CompletionStatus.class) - .newValue(completionStatus) - .currentValue(this.completionStatus) - .validate(); + public void updateCompletionStatus(boolean isAllCompleteTodo) { + this.completionStatus = isAllCompleteTodo ? CompletionStatus.COMPLETE : CompletionStatus.INCOMPLETE; + } + + public boolean isTodoCreator(Long creatorId) { + return Objects.equals(this.creatorId, creatorId); } } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/TodoTeam.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/TodoTeam.java index 8c9076a9..5ecc465c 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/TodoTeam.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/entity/TodoTeam.java @@ -12,6 +12,10 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "todo_team", + uniqueConstraints = @UniqueConstraint(name = "team_code_unique", columnNames = "teamCode") +) + public class TodoTeam extends BaseEntity { @Id @@ -34,16 +38,16 @@ public TodoTeam(String teamCode, String teamName, String imageUrl, String descri public void updateTodoTeam(String teamName, String description, String imageUrl) { this.teamName = DomainFieldUtils.DomainValidateBuilder.builder(String.class) - .newValue(teamName) - .currentValue(this.teamName) - .validate(); + .newValue(teamName) + .currentValue(this.teamName) + .validate(); this.description = DomainFieldUtils.DomainValidateBuilder.builder(String.class) - .newValue(description) - .currentValue(this.description) - .validate(); + .newValue(description) + .currentValue(this.description) + .validate(); this.imageUrl = DomainFieldUtils.DomainValidateBuilder.builder(String.class) - .newValue(imageUrl) - .currentValue(this.imageUrl) - .validate(); + .newValue(imageUrl) + .currentValue(this.imageUrl) + .validate(); } } 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 acee8485..d9a2ede9 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 @@ -14,7 +14,8 @@ public enum TodoError implements Error { CATEGORY_NOT_FOUND("카테고리를 찾을 수 없습니다.", 3006), CANNOT_PRESIDENT_UNREGISTRABLE("팀장 탈퇴시 탈퇴 후 팀장이 1명 이상 있어야 합니다.", 3007), CANNOT_CHANGE_AUTHORITY("권한을 변경할 수 없습니다", 3008), - ASSIGN_NOT_FOUND("할당된 Todo를 찾을 수 없습니다.", 3009) + ASSIGN_NOT_FOUND("할당된 Todo를 찾을 수 없습니다.", 3009), + TODO_MODIFICATION_NOT_ALLOWED("Todo 수정 및 삭제가 허용되지 않습니다.", 3010), ; private final String message; diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/exception/TodoModificationNotAllowedException.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/exception/TodoModificationNotAllowedException.java new file mode 100644 index 00000000..6833cbe6 --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/exception/TodoModificationNotAllowedException.java @@ -0,0 +1,8 @@ +package com.pawith.tododomain.exception; +import com.pawith.commonmodule.exception.Error; + +public class TodoModificationNotAllowedException extends TodoException { + public TodoModificationNotAllowedException(Error error) { + super(error); + } +} 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 new file mode 100644 index 00000000..1133179e --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/AssignQueryRepository.java @@ -0,0 +1,18 @@ +package com.pawith.tododomain.repository; + +import com.pawith.tododomain.entity.Assign; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +public interface AssignQueryRepository { + List findAllByCategoryIdAndScheduledDateQuery(Long categoryId, LocalDate scheduledDate); + List findAllByUserIdAndTodoTeamIdAndScheduledDateQuery(Long userId, Long todoTeamId, LocalDate scheduledDate); + void deleteByRegisterIdsQuery(final List registerIds); + void deleteAllByTodoIdQuery(final Long todoId); + Optional findByTodoIdAndUserIdQuery(Long todoId, Long userId); + List findAllByTodoIdQuery(Long todoId); + List findAllByTodoIdWithRegisterFetchQuery(Long todoId); + void deleteAllByCategoryIdQuery(final Long categoryId); +} diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/AssignRepository.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/AssignRepository.java index cb6460f3..a706e531 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/AssignRepository.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/AssignRepository.java @@ -4,51 +4,12 @@ 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.List; import java.util.Optional; -import org.springframework.data.repository.query.Param; - -public interface AssignRepository extends JpaRepository { - - @Query("select a " + - "from Assign a " + - "join fetch a.register " + - "join fetch a.todo t " + - "where t.category.id=:categoryId and t.scheduledDate=:scheduledDate order by t.completionStatus desc") - List findAllByCategoryIdAndScheduledDate(Long categoryId, LocalDate scheduledDate); - - - @Query("select a " + - "from Assign a " + - "join a.register r " + - "join fetch a.todo t " + - "join fetch t.category " + - "where r.userId=:userId and r.todoTeam.id=:todoTeamId and t.scheduledDate=:scheduledDate and t.category.categoryStatus = 'ON'") - List findAllByUserIdAndTodoTeamIdAndScheduledDate(Long userId, Long todoTeamId, LocalDate scheduledDate); - - @Modifying - @Query("update Assign a set a.isDeleted = true where a.register.id in (:registerIds)") - void deleteByRegisterIds(final List registerIds); - - @Modifying - @Query("update Assign a set a.isDeleted = true where a.todo.id=:todoId") - void deleteAllByTodoId(final Long todoId); - - @Query("select a " + - "from Assign a " + - "join Register r on r.id = a.register.id and r.userId=:userId " + - "join Todo t on t.id = a.todo.id and t.id = :todoId") - Optional findByTodoIdAndUserId(Long todoId, Long userId); - @Query("select a " + - "from Assign a " + - "join fetch a.todo t " + - "where t.id=:todoId") - List findAllByTodoId(Long todoId); +public interface AssignRepository extends JpaRepository, AssignQueryRepository { - @Modifying - @Query("update Assign a set a.isDeleted = true where a.todo.id in (select t.id from Todo t where t.category.id = :categoryId)") - void deleteAllByCategoryId(@Param("categoryId") Long categoryId); } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/CategoryQueryRepository.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/CategoryQueryRepository.java new file mode 100644 index 00000000..86f9ea84 --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/CategoryQueryRepository.java @@ -0,0 +1,11 @@ +package com.pawith.tododomain.repository; + +import com.pawith.tododomain.entity.Category; +import java.time.LocalDate; +import java.util.List; + +public interface CategoryQueryRepository { + + List findAllByTodoTeamIdAndCategoryStatusQuery(Long todoTeamId, LocalDate moveDate); + List findAllByTodoTeamIdQuery(Long todoTeamId); +} diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/CategoryRepository.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/CategoryRepository.java index a14d11c5..e1e3295a 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/CategoryRepository.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/CategoryRepository.java @@ -1,15 +1,12 @@ package com.pawith.tododomain.repository; import com.pawith.tododomain.entity.Category; +import java.time.LocalDate; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import java.util.List; -public interface CategoryRepository extends JpaRepository { - @Query("select c from Category c where c.todoTeam.id = :todoTeamId and c.categoryStatus = 'ON' order by c.createdAt desc") - List findAllByTodoTeamIdAndCategoryStatus(Long todoTeamId); +public interface CategoryRepository extends JpaRepository, CategoryQueryRepository { - @Query("select c from Category c where c.todoTeam.id = :todoTeamId order by c.createdAt desc") - List findAllByTodoTeamId(Long todoTeamId); } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/RegisterQueryRepository.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/RegisterQueryRepository.java new file mode 100644 index 00000000..40037496 --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/RegisterQueryRepository.java @@ -0,0 +1,19 @@ +package com.pawith.tododomain.repository; + +import com.pawith.tododomain.entity.Register; +import com.pawith.tododomain.repository.dao.IncompleteTodoCountInfoDao; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; + +import java.util.List; +import java.util.Optional; + +public interface RegisterQueryRepository { + Slice findAllByUserIdQuery(Long userId, Pageable pageable); + List findAllByIdsQuery(List ids); + List findAllByUserIdWithTodoTeamFetchQuery(Long userId); + Integer countByTodoTeamIdQuery(Long todoTeamId); + Optional findLatestRegisterByUserIdQuery(Long userId); + void deleteByRegisterIdsQuery(List registerIds); + List findAllIncompleteTodoCountInfoQuery(Pageable pageable); +} diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/RegisterRepository.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/RegisterRepository.java index a3c715c5..173d2c92 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/RegisterRepository.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/RegisterRepository.java @@ -12,44 +12,16 @@ import java.util.List; import java.util.Optional; -public interface RegisterRepository extends JpaRepository { - - @Query("select r from Register r join fetch r.todoTeam where r.userId = :userId and r.isRegistered = true") - Slice findAllByUserId(Long userId, Pageable pageable); +public interface RegisterRepository extends JpaRepository, RegisterQueryRepository { List findAllByTodoTeamId(Long todoTeamId); - @Query("select r from Register r where r.id in :ids") - List findAllByIds(@Param("ids") List ids); - - @Query("select r from Register r join Assign a on a.id=:todoId where a.register.id = r.id") - List findByTodoId(Long todoId); - - @Query("select r from Register r join Category c on c.id = :categoryId where c.todoTeam.id= r.todoTeam.id") - List findAllByCategoryId(Long categoryId); - - @Query("select r from Register r join fetch r.todoTeam where r.userId = :userId and r.isRegistered = true order by r.registerAt desc") - List findAllByUserIdWithTodoTeamFetch(Long userId); - - List findAllByUserId(Long userId); Optional findByTodoTeamIdAndUserId(Long todoTeamId, Long userId); Optional findByTodoTeamIdAndAuthority(Long todoTeamId, Authority authority); - @Query("select count(r) from Register r where r.todoTeam.id = :todoTeamId and r.isRegistered = true") - Integer countByTodoTeamId(Long todoTeamId); - - @Query("select count(r) from Register r where r.todoTeam.id = :todoTeamId and r.authority = :authority and r.isRegistered = true") - Integer countByTodoTeamIdAndAuthority(Long todoTeamId, Authority authority); - Boolean existsByTodoTeamIdAndUserIdAndIsRegistered(Long todoTeamId, Long userId, boolean isRegistered); - @Query("select r from Register r join fetch r.todoTeam where r.id = (select max(r.id) from Register r where r.userId = :userId and r.isRegistered = true)") - Optional findLatestRegisterByUserId(Long userId); - - @Modifying - @Query("update Register r set r.isDeleted=true , r.isRegistered=false where r.id in (:registerIds)") - void deleteByRegisterIds(List registerIds); } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoNotificationQueryRepository.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoNotificationQueryRepository.java new file mode 100644 index 00000000..bc2be304 --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoNotificationQueryRepository.java @@ -0,0 +1,15 @@ +package com.pawith.tododomain.repository; + +import com.pawith.tododomain.entity.TodoNotification; +import com.pawith.tododomain.repository.dao.NotificationDao; +import org.springframework.data.domain.Pageable; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; + +public interface TodoNotificationQueryRepository { + List findAllWithNotCompletedAssignAndAlarmTimeQuery(Duration criterionTime, LocalDateTime alarmTime, Pageable pageable); + + List findAllByTodoIdsAndUserIdWithInCompleteAssignQuery(List todoId, Long userId); +} diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoNotificationRepository.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoNotificationRepository.java index 04d625df..1ebd1b81 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoNotificationRepository.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoNotificationRepository.java @@ -1,42 +1,11 @@ package com.pawith.tododomain.repository; import com.pawith.tododomain.entity.TodoNotification; -import com.pawith.tododomain.repository.dao.NotificationDao; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import java.time.Duration; -import java.time.LocalTime; -import java.util.List; import java.util.Optional; -public interface TodoNotificationRepository extends JpaRepository { - - @Query(value = - """ - select c.todoTeam.id as todoTeamId ,r.userId as userId, c.name as categoryName, t.description as todoDescription, tn.notificationTime as notificationTime - from TodoNotification tn - join Assign a on tn.assign = a and a.completionStatus='INCOMPLETE' - join Todo t on a.todo = t and t.scheduledDate = date(now()) - join Category c on t.category = c - join Register r on a.register = r and r.isRegistered = true - where timediff(tn.notificationTime, :alarmTime) <= :criterionTime - and timediff(tn.notificationTime , :alarmTime) > 0 -""" - ) - Slice findAllWithNotCompletedAssignAndTodayScheduledTodo(Duration criterionTime, LocalTime alarmTime, Pageable pageable); - - @Query(value = """ - select tn - from TodoNotification tn - join Assign a on tn.assign = a and a.completionStatus='INCOMPLETE' - join Register r on a.register = r and r.isRegistered = true and r.userId = :userId - where a.todo.id in :todoId - """) - List findAllByTodoIdWithIncompleteAssign(List todoId, Long userId); - +public interface TodoNotificationRepository extends JpaRepository, TodoNotificationQueryRepository { Optional findByAssignId(Long assignId); } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoQueryRepository.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoQueryRepository.java new file mode 100644 index 00000000..5fa6db67 --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoQueryRepository.java @@ -0,0 +1,23 @@ +package com.pawith.tododomain.repository; + +import com.pawith.tododomain.entity.Todo; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; + +import java.time.LocalDate; +import java.util.List; + +public interface TodoQueryRepository { + List findTodoListByCategoryIdAndScheduledDateQuery(Long categoryId, LocalDate moveDate); + Long countTodoByDateQuery(Long userId, Long todoTeamId, LocalDate scheduledDate); + Long countCompleteTodoByDateQuery(Long userId, Long todoTeamId, LocalDate scheduledDate); + Long countTodoByBetweenDateQuery(Long userId, Long todoTeamId, LocalDate startDate, LocalDate endDate); + Long countCompleteTodoByBetweenDateQuery(Long userId, Long todoTeamId, LocalDate startDate, LocalDate endDate); + void deleteAllByCategoryIdQuery(Long categoryId); + Slice findTodoSliceByUserIdAndTodoTeamIdQuery(Long userId, Long todoTeamId, Pageable pageable); + Slice findTodoSliceByUserIdQuery(Long userId, Pageable pageable); + Integer countTodoByUserIdAndTodoTeamIdQuery(Long userId, Long todoTeamId); + Integer countTodoByUserIdQuery(Long userId); + + List findTodoListByCreatorIdAndTodoTeamIdQuery(Long creatorId, Long todoTeamId); +} diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoRepository.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoRepository.java index 3f3e2410..d2d45540 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoRepository.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoRepository.java @@ -9,84 +9,7 @@ import org.springframework.data.repository.query.Param; import java.time.LocalDate; -import java.util.List; -public interface TodoRepository extends JpaRepository { +public interface TodoRepository extends JpaRepository, TodoQueryRepository { - @Query(value = - """ - select count(t) - from Todo t - join Register r on r.userId=:userId and r.todoTeam.id=:todoTeamId - join Assign a on a.register.id=r.id and a.todo=t and a.isDeleted=false - join Category c on c=t.category and c.categoryStatus='ON' - where t.scheduledDate = :scheduledDate - """ - ) - Long countTodoByDate(@Param("userId") Long userId, @Param("todoTeamId") Long todoTeamId, @Param("scheduledDate") LocalDate scheduledDate); - - @Query(value = - """ - select count(t) - from Todo t - join Register r on r.userId=:userId and r.todoTeam.id=:todoTeamId - join Assign a on a.register.id=r.id and a.completionStatus='COMPLETE' and a.todo=t and a.isDeleted=false - join Category c on c=t.category and c.categoryStatus='ON' - where t.scheduledDate = :scheduledDate - """ - ) - Long countCompleteTodoByDate(@Param("userId") Long userId, @Param("todoTeamId") Long todoTeamId, @Param("scheduledDate") LocalDate scheduledDate); - - @Query(value = "select count(t) " + - "from Todo t " + - "join Register r on r.userId=:userId and r.todoTeam.id=:todoTeamId " + - "join Assign a on a.register.id=r.id " + - "where a.todo.id=t.id and t.scheduledDate between :startDate and :endDate" - ) - Long countTodoByBetweenDate(@Param("userId") Long userId, @Param("todoTeamId") Long todoTeamId, @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate); - - @Query(value = "select count(t) " + - "from Todo t " + - "join Register r on r.userId=:userId and r.todoTeam.id=:todoTeamId " + - "join Assign a on a.register.id=r.id " + - "where a.todo.id=t.id and t.scheduledDate between :startDate and :endDate and t.completionStatus='COMPLETE'" - ) - Long countCompleteTodoByBetweenDate(@Param("userId") Long userId, @Param("todoTeamId") Long todoTeamId, @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate); - - @Query("select t from Todo t " + - "join Category c on c.id=:categoryId " + - "where t.category.id = c.id and t.scheduledDate = :moveDate") - List findTodoListByCategoryIdAndscheduledDate(Long categoryId, LocalDate moveDate); - - @Modifying - @Query("update Todo t set t.isDeleted = true where t.category.id=:categoryId") - void deleteAllByCategoryId(@Param("categoryId") Long categoryId); - - - @Query("select t from Todo t " + - "join fetch t.category " + - "join Register r on r.userId=:userId and r.todoTeam.id=:todoTeamId " + - "join Assign a on a.register.id=r.id " + - "where a.todo.id=t.id") - Slice findTodoSliceByUserIdAndTodoTeamId(Long userId, Long todoTeamId, Pageable pageable); - - @Query("select t from Todo t " + - "join fetch t.category c " + - "join fetch c.todoTeam " + - "join Register r on r.userId=:userId " + - "join Assign a on a.register.id=r.id " + - "where a.todo.id=t.id") - Slice findTodoSliceByUserId(Long userId, Pageable pageable); - - @Query("select count(t) from Todo t " + - "join Register r on r.userId=:userId and r.todoTeam.id=:todoTeamId " + - "join Assign a on a.register.id=r.id " + - "where a.todo.id=t.id") - Integer countTodoByUserIdAndTodoTeamId(Long userId, Long todoTeamId); - - @Query("select count(t) from Todo t " + - "join Register r on r.userId=:userId " + - "join Assign a on a.register.id=r.id " + - "where a.todo.id=t.id") - Integer countTodoByUserId(Long userId); } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoTeamQueryRepository.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoTeamQueryRepository.java new file mode 100644 index 00000000..48b2b5d9 --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoTeamQueryRepository.java @@ -0,0 +1,9 @@ +package com.pawith.tododomain.repository; + +import com.pawith.tododomain.entity.TodoTeam; +import java.util.List; + +public interface TodoTeamQueryRepository { + List findAllByUserIdQuery(Long userId); + TodoTeam findByTodoId(Long todoId); +} diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoTeamRepository.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoTeamRepository.java index 52b14afb..69be0f9d 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoTeamRepository.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/TodoTeamRepository.java @@ -8,11 +8,8 @@ import java.util.List; import java.util.Optional; -public interface TodoTeamRepository extends JpaRepository { +public interface TodoTeamRepository extends JpaRepository, TodoTeamQueryRepository { Optional findByTeamCode(String teamCode); Boolean existsByTeamCode(String teamCode); - - @Query("select t from TodoTeam t join Register r on r.userId = :userId where r.todoTeam.id = t.id and r.isRegistered = true") - List findAllByUserId(@Param("userId") Long userId); } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/dao/IncompleteTodoCountInfoDao.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/dao/IncompleteTodoCountInfoDao.java new file mode 100644 index 00000000..cd7d0342 --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/dao/IncompleteTodoCountInfoDao.java @@ -0,0 +1,8 @@ +package com.pawith.tododomain.repository.dao; + +public interface IncompleteTodoCountInfoDao { + Long getTodoTeamId(); + Long getUserId(); + String getTodoTeamName(); + Long getIncompleteTodoCount(); +} diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/dao/NotificationDao.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/dao/NotificationDao.java index 622fad65..3ef2af46 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/dao/NotificationDao.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/repository/dao/NotificationDao.java @@ -1,5 +1,6 @@ package com.pawith.tododomain.repository.dao; +import java.time.LocalDate; import java.time.LocalTime; public interface NotificationDao { @@ -8,4 +9,7 @@ public interface NotificationDao { String getCategoryName(); String getTodoDescription(); LocalTime getNotificationTime(); + LocalDate getScheduledDate(); + + String getTodoTeamName(); } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/AssignDeleteService.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/AssignDeleteService.java index 15bd503f..438ebdaf 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/AssignDeleteService.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/AssignDeleteService.java @@ -14,14 +14,14 @@ public class AssignDeleteService { private final AssignRepository assignRepository; public void deleteAssignByRegisterId(final List registerId){ - assignRepository.deleteByRegisterIds(registerId); + assignRepository.deleteByRegisterIdsQuery(registerId); } public void deleteAllByTodoId(final Long todoId){ - assignRepository.deleteAllByTodoId(todoId); + assignRepository.deleteAllByTodoIdQuery(todoId); } public void deleteAssignByCategoryId(final Long categoryId){ - assignRepository.deleteAllByCategoryId(categoryId); + assignRepository.deleteAllByCategoryIdQuery(categoryId); } } 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 95668292..db9aaec4 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 @@ -19,22 +19,26 @@ public class AssignQueryService { private final AssignRepository assignRepository; public List findAllAssignByCategoryIdAndScheduledDate(Long categoryId, LocalDate scheduledDate) { - return assignRepository.findAllByCategoryIdAndScheduledDate(categoryId, scheduledDate); + return assignRepository.findAllByCategoryIdAndScheduledDateQuery(categoryId, scheduledDate); } public List findAllByUserIdAndTodoTeamIdAndScheduledDate(Long userId, Long todoTeamId) { final LocalDate now = LocalDate.now(); - return assignRepository.findAllByUserIdAndTodoTeamIdAndScheduledDate(userId, todoTeamId, now); + return assignRepository.findAllByUserIdAndTodoTeamIdAndScheduledDateQuery(userId, todoTeamId, now); } public Assign findAssignByTodoIdAndUserId(Long todoId, Long userId) { - return assignRepository.findByTodoIdAndUserId(todoId, userId) + return assignRepository.findByTodoIdAndUserIdQuery(todoId, userId) .orElseThrow(() -> new AssignNotFoundException(TodoError.ASSIGN_NOT_FOUND)); } public List findAllAssignByTodoId(Long todoId) { - return assignRepository.findAllByTodoId(todoId); + return assignRepository.findAllByTodoIdQuery(todoId); + } + + public List findAllAssignWithRegisterByTodoId(Long todoId) { + return assignRepository.findAllByTodoIdWithRegisterFetchQuery(todoId); } } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/CategoryQueryService.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/CategoryQueryService.java index c6477568..8470e0e7 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/CategoryQueryService.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/CategoryQueryService.java @@ -5,6 +5,7 @@ import com.pawith.tododomain.exception.CategoryNotFoundException; import com.pawith.tododomain.exception.TodoError; import com.pawith.tododomain.repository.CategoryRepository; +import java.time.LocalDate; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; @@ -17,12 +18,12 @@ public class CategoryQueryService { private final CategoryRepository categoryRepository; - public List findCategoryListByTodoTeamIdAndStatus(Long todoTeamId) { - return categoryRepository.findAllByTodoTeamIdAndCategoryStatus(todoTeamId); + public List findCategoryListByTodoTeamIdAndStatus(Long todoTeamId, LocalDate moveDate) { + return categoryRepository.findAllByTodoTeamIdAndCategoryStatusQuery(todoTeamId, moveDate); } public List findCategoryListByTodoTeamId(Long todoTeamId) { - return categoryRepository.findAllByTodoTeamId(todoTeamId); + return categoryRepository.findAllByTodoTeamIdQuery(todoTeamId); } public Category findCategoryByCategoryId(Long categoryId) { diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/RegisterDeleteService.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/RegisterDeleteService.java index 313e64af..25171f3d 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/RegisterDeleteService.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/RegisterDeleteService.java @@ -20,6 +20,6 @@ public void deleteRegister(Register requestRegister){ } public void deleteRegisterByRegisterIds(List registerIds){ - registerRepository.deleteByRegisterIds(registerIds); + registerRepository.deleteByRegisterIdsQuery(registerIds); } } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/RegisterQueryService.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/RegisterQueryService.java index 77e95bfa..57a0381e 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/RegisterQueryService.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/RegisterQueryService.java @@ -25,11 +25,11 @@ public class RegisterQueryService { private final RegisterRepository registerRepository; public Slice findRegisterSliceByUserId(Long userId, Pageable pageable) { - return registerRepository.findAllByUserId(userId, pageable); + return registerRepository.findAllByUserIdQuery(userId, pageable); } public List findRegisterListByUserIdWithTodoTeam(Long userId) { - return registerRepository.findAllByUserIdWithTodoTeamFetch(userId); + return findRegisterList(() -> registerRepository.findAllByUserIdWithTodoTeamFetchQuery(userId)); } public Register findRegisterByTodoTeamIdAndUserId(Long todoTeamId, Long userId) { @@ -49,29 +49,15 @@ public List findAllRegistersByTodoTeamId(Long todoTeamId){ } public List findAllRegistersByIds(List registerIds){ - return findRegisterList(() -> registerRepository.findAllByIds(registerIds)); - } - - public List findAllRegistersByTodoId(Long todoId) { - return findRegisterList(() -> registerRepository.findByTodoId(todoId)); - } - - public List findAllRegistersByCategoryId(Long categoryId) { - return registerRepository.findAllByCategoryId(categoryId); + return findRegisterList(() -> registerRepository.findAllByIdsQuery(registerIds)); } public List findAllRegistersByUserId(Long userId){ return findRegisterList(() -> registerRepository.findAllByUserId(userId)); } - public List findUserIdsByCategoryId(Long categoryId){ - return findRegisterList(() -> registerRepository.findAllByCategoryId(categoryId)).stream() - .map(Register::getUserId) - .collect(Collectors.toList()); - } - public Integer countRegisterByTodoTeamId(Long todoTeamId){ - return registerRepository.countByTodoTeamId(todoTeamId); + return registerRepository.countByTodoTeamIdQuery(todoTeamId); } public Integer findUserRegisterTerm (Long todoTeamId, Long userId){ @@ -80,13 +66,13 @@ public Integer findUserRegisterTerm (Long todoTeamId, Long userId){ } public Register findLatestTodoTeam(Long userId){ - return findRegister(() -> registerRepository.findLatestRegisterByUserId(userId)); + return findRegister(() -> registerRepository.findLatestRegisterByUserIdQuery(userId)); } private Register findRegister(Supplier> optionalSupplier){ return registerOptionalHandle(optionalSupplier); } - private List findRegisterList(Supplier> listSupplier) { + private List findRegisterList(Supplier> listSupplier) { return filterUnregister(listSupplier.get()); } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/RegisterSaveService.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/RegisterSaveService.java index 923d0d99..bca472ce 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/RegisterSaveService.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/RegisterSaveService.java @@ -1,6 +1,7 @@ package com.pawith.tododomain.service; import com.pawith.commonmodule.annotation.DomainService; +import com.pawith.commonmodule.event.ChristmasEvent; import com.pawith.tododomain.entity.Authority; import com.pawith.tododomain.entity.Register; import com.pawith.tododomain.entity.TodoTeam; @@ -8,6 +9,7 @@ import com.pawith.tododomain.exception.TodoError; import com.pawith.tododomain.repository.RegisterRepository; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.transaction.annotation.Transactional; @DomainService @@ -15,6 +17,7 @@ @Transactional public class RegisterSaveService { private final RegisterRepository registerRepository; + private final ApplicationEventPublisher applicationEventPublisher; public void saveRegisterAboutMember(TodoTeam todoTeam, Long userId) { saveRegisterEntity(todoTeam, userId, Authority.MEMBER); @@ -37,6 +40,7 @@ private void saveRegisterEntity(TodoTeam todoTeam, Long userId, Authority author .authority(authority) .build()); }); + applicationEventPublisher.publishEvent(new ChristmasEvent.ChristmasEventCreateNewRegister(todoTeam.getId(), userId)); } private void checkAlreadyRegisterTodoTeam(TodoTeam todoTeam, Long userId) { diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/RegisterValidateService.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/RegisterValidateService.java index d2744f87..e30aa8d9 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/RegisterValidateService.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/RegisterValidateService.java @@ -8,12 +8,10 @@ import com.pawith.tododomain.exception.UnchangeableException; import com.pawith.tododomain.exception.UnregistrableException; import com.pawith.tododomain.repository.RegisterRepository; -import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; import java.util.List; -import java.util.stream.Collectors; @DomainService @RequiredArgsConstructor @@ -25,23 +23,21 @@ public class RegisterValidateService { public void validatePresidentRegisterDeletable(final Register register) { if (register.isPresident()) { final TodoTeam todoTeam = register.getTodoTeam(); - final Integer presidentRegisterCount = registerRepository.countByTodoTeamIdAndAuthority(todoTeam.getId(), Authority.PRESIDENT); - final Integer registerCount = registerRepository.countByTodoTeamId(todoTeam.getId()); - if (presidentRegisterCount <= 1 && registerCount > 1) { + final Integer registerCount = registerRepository.countByTodoTeamIdQuery(todoTeam.getId()); + if (registerCount > 1) { throw new UnregistrableException(TodoError.CANNOT_PRESIDENT_UNREGISTRABLE); } } } public void validateRegisterDeletable(final List registerList) { - final Map registerCountMap = registerList.stream() - .collect(Collectors.toMap(register -> register, register -> registerRepository.countByTodoTeamId(register.getTodoTeam().getId()))); - final boolean isPresidentExist = !registerList.isEmpty() && - registerList.stream() - .anyMatch(register -> register.isPresident() && registerCountMap.get(register) > 1); - if (isPresidentExist) { - throw new UnregistrableException(TodoError.CANNOT_PRESIDENT_UNREGISTRABLE); - } + registerList.forEach(register -> { + final Long todoTeamId = register.getTodoTeam().getId(); + final Integer registerCount = registerRepository.countByTodoTeamIdQuery(todoTeamId); + if (register.isPresident() && registerCount > 1) { + throw new UnregistrableException(TodoError.CANNOT_PRESIDENT_UNREGISTRABLE); + } + }); } public void validateAuthorityChangeable(final Register userRegister, final Authority authority) { diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoDeleteService.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoDeleteService.java index 46729c47..3ba4d79c 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoDeleteService.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoDeleteService.java @@ -17,6 +17,6 @@ public void deleteTodoByTodoId(Long todoId) { } public void deleteTodoByCategoryId(Long categoryId) { - todoRepository.deleteAllByCategoryId(categoryId); + todoRepository.deleteAllByCategoryIdQuery(categoryId); } } 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 48c3cab1..a18acb53 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 @@ -16,6 +16,6 @@ public class TodoNotificationQueryService { private final TodoNotificationRepository todoNotificationRepository; public List findAllByTodoIdsWithIncompleteAssign(List todoIds, Long userId) { - return todoNotificationRepository.findAllByTodoIdWithIncompleteAssign(todoIds, userId); + return todoNotificationRepository.findAllByTodoIdsAndUserIdWithInCompleteAssignQuery(todoIds, userId); } } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoQueryService.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoQueryService.java index 54932ecf..0d1fde10 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoQueryService.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoQueryService.java @@ -28,13 +28,13 @@ public Todo findTodoByTodoId(Long todoId) { public Integer findTodoCompleteRate(Long userId, Long todoTeamId) { final LocalDate now = LocalDate.now(); - final Long countTodayTodo = todoRepository.countTodoByDate(userId, todoTeamId, now); - final Long countCompleteTodayTodo = todoRepository.countCompleteTodoByDate(userId, todoTeamId, now); + final Long countTodayTodo = todoRepository.countTodoByDateQuery(userId, todoTeamId, now); + final Long countCompleteTodayTodo = todoRepository.countCompleteTodoByDateQuery(userId, todoTeamId, now); return (int) ((countCompleteTodayTodo / (double) countTodayTodo) * 100); } - public List findTodoListByCategoryIdAndscheduledDate(Long categoryId, LocalDate moveDate){ - return todoRepository.findTodoListByCategoryIdAndscheduledDate(categoryId, moveDate); + public List findTodoListByCategoryIdAndScheduledDate(Long categoryId, LocalDate moveDate){ + return todoRepository.findTodoListByCategoryIdAndScheduledDateQuery(categoryId, moveDate); } public Integer findThisWeekTodoCompleteRate(Long userId, Long todoTeamId) { @@ -50,24 +50,24 @@ public Integer findLastWeekTodoCompleteRate(Long userId, Long todoTeamId) { } public Integer getWeekProgress(Long userId, Long todoTeamId, LocalDate now, LocalDate firstDayOfWeek) { - final Long countWeekTodo = todoRepository.countTodoByBetweenDate(userId, todoTeamId, now, firstDayOfWeek); - final Long countCompleteWeekTodo = todoRepository.countCompleteTodoByBetweenDate(userId, todoTeamId, now, firstDayOfWeek); + final Long countWeekTodo = todoRepository.countTodoByBetweenDateQuery(userId, todoTeamId, now, firstDayOfWeek); + final Long countCompleteWeekTodo = todoRepository.countCompleteTodoByBetweenDateQuery(userId, todoTeamId, now, firstDayOfWeek); return (int) ((countCompleteWeekTodo / (double) countWeekTodo) * 100); } public Slice findAllTodoListByTodoTeamId(Long userId, Long todoTeamId, Pageable pageable) { - return todoRepository.findTodoSliceByUserIdAndTodoTeamId(userId, todoTeamId, pageable); + return todoRepository.findTodoSliceByUserIdAndTodoTeamIdQuery(userId, todoTeamId, pageable); } public Slice findAllTodoListByUserId(Long userId, Pageable pageable) { - return todoRepository.findTodoSliceByUserId(userId, pageable); + return todoRepository.findTodoSliceByUserIdQuery(userId, pageable); } public Integer countTodoByTodoTeamId(Long userId, Long todoTeamId) { - return todoRepository.countTodoByUserIdAndTodoTeamId(userId, todoTeamId); + return todoRepository.countTodoByUserIdAndTodoTeamIdQuery(userId, todoTeamId); } public Integer countTodoByUserId(Long userId) { - return todoRepository.countTodoByUserId(userId); + return todoRepository.countTodoByUserIdQuery(userId); } } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoTeamCodeGenerateService.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoTeamCodeManageService.java similarity index 50% rename from Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoTeamCodeGenerateService.java rename to Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoTeamCodeManageService.java index 405a7ad3..e4925c29 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoTeamCodeGenerateService.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoTeamCodeManageService.java @@ -1,22 +1,28 @@ package com.pawith.tododomain.service; import com.pawith.commonmodule.annotation.DomainService; +import com.pawith.commonmodule.cache.operators.SetOperator; import com.pawith.tododomain.repository.TodoTeamRepository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import java.util.UUID; +import java.util.concurrent.TimeUnit; +@Slf4j @DomainService @RequiredArgsConstructor -public class TodoTeamCodeGenerateService { +public class TodoTeamCodeManageService { private final TodoTeamRepository todoTeamRepository; + private final SetOperator occupiedTodoTeamCodeSet; - // TODO: 2023/09/20 랜덤 코드 생성이 너무 오래걸리면 어떻게할지 고민해보기 - public String generateRandomCode(){ - while(true){ + + public String generateRandomCode() { + while (true) { final String randomUUID = UUID.randomUUID().toString(); final String[] split = randomUUID.split("-"); - if(!todoTeamRepository.existsByTeamCode(split[0])){ + if (!todoTeamRepository.existsByTeamCode(split[0]) && !occupiedTodoTeamCodeSet.contains(split[0])) { + occupiedTodoTeamCodeSet.addWithExpire(split[0], 1, TimeUnit.HOURS); return split[0]; } } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoTeamQueryService.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoTeamQueryService.java index 3f4796b3..acf21948 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoTeamQueryService.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoTeamQueryService.java @@ -20,7 +20,7 @@ public class TodoTeamQueryService { private final TodoTeamRepository todoTeamRepository; public List findAllTodoTeamByUserId(Long userId){ - return todoTeamRepository.findAllByUserId(userId); + return todoTeamRepository.findAllByUserIdQuery(userId); } public TodoTeam findTodoTeamById(Long todoTeamId) { @@ -31,6 +31,10 @@ public TodoTeam findTodoTeamByCode(String todoTeamCode) { return findTodo(todoTeamRepository::findByTeamCode, todoTeamCode); } + public TodoTeam findTodoTeamByTodoId(Long todoId) { + return todoTeamRepository.findByTodoId(todoId); + } + private TodoTeam findTodo(Function> findMethod, T specificationData){ return findMethod.apply(specificationData) .orElseThrow(() -> new TodoTeamNotFoundException(TodoError.TODO_TEAM_NOT_FOUND)); diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoTeamSaveService.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoTeamSaveService.java index 890c5dbc..7c394cee 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoTeamSaveService.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoTeamSaveService.java @@ -1,9 +1,11 @@ package com.pawith.tododomain.service; import com.pawith.commonmodule.annotation.DomainService; +import com.pawith.commonmodule.event.ChristmasEvent; import com.pawith.tododomain.entity.TodoTeam; import com.pawith.tododomain.repository.TodoTeamRepository; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.transaction.annotation.Transactional; @DomainService @@ -12,8 +14,10 @@ public class TodoTeamSaveService { private final TodoTeamRepository todoTeamRepository; + private final ApplicationEventPublisher applicationEventPublisher; public void saveTodoTeamEntity(TodoTeam todoTeam){ - todoTeamRepository.save(todoTeam); + final TodoTeam savedTodoTeam = todoTeamRepository.save(todoTeam); + applicationEventPublisher.publishEvent(new ChristmasEvent.ChristmasEventCreateNewTodoTeam(savedTodoTeam.getId())); } } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoValidateService.java b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoValidateService.java new file mode 100644 index 00000000..73680758 --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Domain/src/main/java/com/pawith/tododomain/service/TodoValidateService.java @@ -0,0 +1,25 @@ +package com.pawith.tododomain.service; + +import com.pawith.commonmodule.annotation.DomainService; +import com.pawith.tododomain.entity.Register; +import com.pawith.tododomain.entity.Todo; +import com.pawith.tododomain.exception.TodoError; +import com.pawith.tododomain.exception.TodoModificationNotAllowedException; +import lombok.RequiredArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +@DomainService +@RequiredArgsConstructor +@Transactional +public class TodoValidateService { + + public boolean validateDeleteAndUpdate(Todo todo, Register register) { + return !todo.isTodoCreator(register.getId()) && register.isMember(); + } + + public void validateTodoDeletable(Todo todo, Register register) { + if (validateDeleteAndUpdate(todo, register)) { + throw new TodoModificationNotAllowedException(TodoError.TODO_MODIFICATION_NOT_ALLOWED); + } + } +} diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/AssignQueryServiceTest.java b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/AssignQueryServiceTest.java index 5f808114..ef1e78ba 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/AssignQueryServiceTest.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/AssignQueryServiceTest.java @@ -1,6 +1,5 @@ package com.pawith.tododomain.service; -import com.navercorp.fixturemonkey.FixtureMonkey; import com.pawith.commonmodule.UnitTestConfig; import com.pawith.commonmodule.utils.FixtureMonkeyUtils; import com.pawith.tododomain.entity.Assign; @@ -10,15 +9,12 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mock; -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 java.time.LocalDate; -import java.time.LocalTime; import java.util.List; +import java.util.Optional; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; @UnitTestConfig @@ -28,27 +24,66 @@ class AssignQueryServiceTest { @Mock private AssignRepository assignRepository; -// private AssignQueryService assignQueryService; - -// @BeforeEach -// void init() { -// assignQueryService = new AssignQueryService(assignRepository); -// } - -// @Test -// @DisplayName("registerId와 날짜를 받아 해당 날짜의 할당 목록을 조회한다.") -// void findAssignByRegisterIdAndCreatedAtBetween() { -// //given -// final Pageable pageable = PageRequest.of(0,10); -// final Long mockRegisterId = FixtureMonkey.create().giveMeOne(Long.class); -// final List mockAssign = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMe(Assign.class,10); -// final Slice mockSlice = new SliceImpl<>(mockAssign, pageable, true); -// final LocalDate now = LocalDate.now(); -// given(assignRepository.findAllByRegisterIdAndCreatedAtBetween(mockRegisterId, now.atStartOfDay(),now.atTime(LocalTime.MAX), pageable)).willReturn(mockSlice); -// //when -// Slice result = assignQueryService.findTodayAssignSliceByRegisterId(mockRegisterId, pageable); -// //then -// Assertions.assertThat(result.getContent().size()).isEqualTo(pageable.getPageSize()); -// } + private AssignQueryService assignQueryService; + + @BeforeEach + void init() { + assignQueryService = new AssignQueryService(assignRepository); + } + + @Test + @DisplayName("카테고리 id와 날짜 기준으로 Assign 목록 조회 테스트") + void findAllAssignByCategoryIdAndScheduledDate() { + // given + final Long categoryId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final LocalDate scheduledDate = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(LocalDate.class); + final List assigns = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMe(Assign.class, 10); + given(assignRepository.findAllByCategoryIdAndScheduledDateQuery(categoryId, scheduledDate)).willReturn(assigns); + // when + final List result = assignQueryService.findAllAssignByCategoryIdAndScheduledDate(categoryId, scheduledDate); + // then + Assertions.assertThat(result).usingRecursiveComparison().isEqualTo(assigns); + } + + @Test + @DisplayName("userId와 todoTeamId, 날짜 기준으로 Assign 목록 조회 테스트") + void findAllByUserIdAndTodoTeamIdAndScheduledDate() { + // given + final Long userId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final Long todoTeamId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final List assigns = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMe(Assign.class, 10); + given(assignRepository.findAllByUserIdAndTodoTeamIdAndScheduledDateQuery(any(), any(), any(LocalDate.class))).willReturn(assigns); + // when + final List result = assignQueryService.findAllByUserIdAndTodoTeamIdAndScheduledDate(userId, todoTeamId); + // then + Assertions.assertThat(result).usingRecursiveComparison().isEqualTo(assigns); + } + + @Test + @DisplayName("todoId와 userId로 Assign 조회 테스트") + void findAssignByTodoIdAndUserId() { + // given + final Long todoId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final Long userId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final Assign assign = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeOne(Assign.class); + given(assignRepository.findByTodoIdAndUserIdQuery(todoId, userId)).willReturn(Optional.of(assign)); + // when + final Assign result = assignQueryService.findAssignByTodoIdAndUserId(todoId, userId); + // then + Assertions.assertThat(result).usingRecursiveComparison().isEqualTo(assign); + } + + @Test + @DisplayName("todoId로 Assign 목록 조회 테스트") + void findAllAssignByTodoId() { + // given + final Long todoId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final List assigns = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMe(Assign.class, 10); + given(assignRepository.findAllByTodoIdQuery(todoId)).willReturn(assigns); + // when + final List result = assignQueryService.findAllAssignByTodoId(todoId); + // then + Assertions.assertThat(result).usingRecursiveComparison().isEqualTo(assigns); + } } \ No newline at end of file diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/CategoryQueryServiceTest.java b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/CategoryQueryServiceTest.java index ecf72974..f0790d55 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/CategoryQueryServiceTest.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/CategoryQueryServiceTest.java @@ -5,6 +5,7 @@ import com.pawith.commonmodule.utils.FixtureMonkeyUtils; import com.pawith.tododomain.entity.Category; import com.pawith.tododomain.repository.CategoryRepository; +import java.time.LocalDate; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -34,10 +35,11 @@ void init() { void findCategoryListByTodoTeamId() { //given final Long mockTodoTeamId = FixtureMonkey.create().giveMeOne(Long.class); + final LocalDate mockMoveDate = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeOne(LocalDate.class); final List mockCategoryList = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMe(Category.class, 10); - given(categoryRepository.findAllByTodoTeamIdAndCategoryStatus(mockTodoTeamId)).willReturn(mockCategoryList); + given(categoryRepository.findAllByTodoTeamIdAndCategoryStatusQuery(mockTodoTeamId, mockMoveDate)).willReturn(mockCategoryList); //when - List result = categoryQueryService.findCategoryListByTodoTeamIdAndStatus(mockTodoTeamId); + List result = categoryQueryService.findCategoryListByTodoTeamIdAndStatus(mockTodoTeamId, mockMoveDate); //then Assertions.assertThat(result).usingRecursiveComparison().isEqualTo(mockCategoryList); } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/RegisterQueryServiceTest.java b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/RegisterQueryServiceTest.java index 34c515aa..3851a957 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/RegisterQueryServiceTest.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/RegisterQueryServiceTest.java @@ -2,11 +2,13 @@ import com.navercorp.fixturemonkey.FixtureMonkey; import com.pawith.commonmodule.UnitTestConfig; +import com.pawith.commonmodule.utils.FixtureMonkeyUtils; import com.pawith.tododomain.entity.Authority; import com.pawith.tododomain.entity.Register; import com.pawith.tododomain.exception.NotRegisterUserException; import com.pawith.tododomain.repository.RegisterRepository; import com.pawith.tododomain.utils.RegisterTestFixtureEntityUtils; +import lombok.extern.slf4j.Slf4j; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -20,10 +22,10 @@ import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import static org.mockito.BDDMockito.given; +@Slf4j @UnitTestConfig @DisplayName("RegisterQueryService 테스트") class RegisterQueryServiceTest { @@ -42,8 +44,8 @@ void init(){ @DisplayName("todoTeamId와 userId로 Register 엔티티를 조회한다.") void findByTodoTeamIdAndUserId() { //given - final Long todoTeamId = FixtureMonkey.create().giveMeOne(Long.class); - final Long userId = FixtureMonkey.create().giveMeOne(Long.class); + final Long todoTeamId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final Long userId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); final Register mockRegister = RegisterTestFixtureEntityUtils.getRegisterEntity(); given(registerRepository.findByTodoTeamIdAndUserId(todoTeamId, userId)).willReturn(Optional.of(mockRegister)); //when @@ -56,8 +58,8 @@ void findByTodoTeamIdAndUserId() { @DisplayName("todoTeamId와 userId로 Register 엔티티를 조회하지 못하면 NotRegisterUserException이 발생한다.") void findByTodoTeamIdAndUserIdFail() { //given - final Long todoTeamId = FixtureMonkey.create().giveMeOne(Long.class); - final Long userId = FixtureMonkey.create().giveMeOne(Long.class); + final Long todoTeamId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final Long userId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); given(registerRepository.findByTodoTeamIdAndUserId(todoTeamId, userId)).willReturn(Optional.empty()); //when //then @@ -69,22 +71,36 @@ void findByTodoTeamIdAndUserIdFail() { @DisplayName("userId와 Pageable로 Register 엔티티를 조회한다.") void findRegisterSliceByUserId() { //given - final Long userId = FixtureMonkey.create().giveMeOne(Long.class); + final Long userId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); final PageRequest pageRequest = PageRequest.of(0, 10); final List mockRegister = RegisterTestFixtureEntityUtils.getRegisterEntityList(pageRequest.getPageSize()); SliceImpl mockSlice = new SliceImpl<>(mockRegister, pageRequest, true); - given(registerRepository.findAllByUserId(userId,pageRequest)).willReturn(mockSlice); + given(registerRepository.findAllByUserIdQuery(userId,pageRequest)).willReturn(mockSlice); //when Slice result = registerQueryService.findRegisterSliceByUserId(userId,pageRequest); //then Assertions.assertThat(result).usingRecursiveComparison().isEqualTo(mockSlice); } + @Test + @DisplayName("userId와 Pageable로 Register 엔티티를 조회한다. 탈퇴한 todo team은 조회되지 않는다.") + void findRegisterSliceByUserIdWithUnregistered() { + //given + final Long userId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final PageRequest pageRequest = PageRequest.of(0, 10); + SliceImpl mockSlice = new SliceImpl<>(List.of(), pageRequest, false); + given(registerRepository.findAllByUserIdQuery(userId,pageRequest)).willReturn(mockSlice); + //when + Slice result = registerQueryService.findRegisterSliceByUserId(userId,pageRequest); + //then + Assertions.assertThat(result).isEmpty(); + } + @Test @DisplayName("todoTeamId와 authority로 Register 엔티티를 조회한다.") void findByTodoTeamIdAndAuthority() { //given - final Long todoTeamId = FixtureMonkey.create().giveMeOne(Long.class); + final Long todoTeamId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); final Register mockRegister = RegisterTestFixtureEntityUtils.getRegisterEntity(); given(registerRepository.findByTodoTeamIdAndAuthority(todoTeamId, Authority.PRESIDENT)).willReturn(Optional.of(mockRegister)); //when @@ -97,7 +113,7 @@ void findByTodoTeamIdAndAuthority() { @DisplayName("todoTeamId와 authority로 Register 엔티티를 조회하지 못하면 NotRegisterUserException이 발생한다.") void findByTodoTeamIdAndAuthorityFail() { //given - final Long todoTeamId = FixtureMonkey.create().giveMeOne(Long.class); + final Long todoTeamId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); given(registerRepository.findByTodoTeamIdAndAuthority(todoTeamId, Authority.PRESIDENT)).willReturn(Optional.empty()); //when //then @@ -109,15 +125,28 @@ void findByTodoTeamIdAndAuthorityFail() { @DisplayName("registerIds로 Register 엔티티를 조회한다.") void findAllRegisterByIds() { //given - final List registerIds = FixtureMonkey.create().giveMe(Long.class, 10); + final List registerIds = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMe(Long.class,10); final List mockRegister = RegisterTestFixtureEntityUtils.getRegisterEntityList(registerIds.size()); - given(registerRepository.findAllByIds(registerIds)).willReturn(mockRegister); + given(registerRepository.findAllByIdsQuery(registerIds)).willReturn(mockRegister); //when List result = registerQueryService.findAllRegistersByIds(registerIds); //then Assertions.assertThat(result).usingRecursiveComparison().isEqualTo(mockRegister); } + @Test + @DisplayName("registerIds로 Register 엔티티를 조회한다. 탈퇴한 todo team은 조회되지 않는다.") + void findAllRegisterByIdsWithUnregistered() { + //given + final List registerIds = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMe(Long.class,10); + final List mockRegister = RegisterTestFixtureEntityUtils.getRegisterEntityListWithUnregistered(registerIds.size()); + given(registerRepository.findAllByIdsQuery(registerIds)).willReturn(mockRegister); + //when + List result = registerQueryService.findAllRegistersByIds(registerIds); + //then + Assertions.assertThat(result.size()).isEqualTo(0); + } + @Test @DisplayName("UserId와 todoTeamId로 Register 엔티티를 조회한다") void findAllRegisters() { @@ -136,8 +165,7 @@ void findAllRegisters() { @DisplayName("UserId와 todoTeamId로 Register 엔티티를 조회한다. 존재하지 않는다면 빈 리스트를 반환한다.") void findAllRegistersWhenDoesNotExist() { //given - final Long userId = FixtureMonkey.create().giveMeOne(Long.class); - final Long todoTeamId = FixtureMonkey.create().giveMeOne(Long.class); + final Long todoTeamId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); //when List result = registerQueryService.findAllRegistersByTodoTeamId(todoTeamId); //then @@ -145,57 +173,137 @@ void findAllRegistersWhenDoesNotExist() { } @Test - @DisplayName("todoId로 Register 엔티티를 조회한다.") - void findAllRegisterByTodoId() { + @DisplayName("todoTeamId로 Register 엔티티의 수를 조회한다.") + void countRegisterByTodoTeamId() { + //given + final Long todoTeamId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final Integer mockCount = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Integer.class); + given(registerRepository.countByTodoTeamIdQuery(todoTeamId)).willReturn(mockCount); + //when + Integer result = registerQueryService.countRegisterByTodoTeamId(todoTeamId); + //then + Assertions.assertThat(result).isEqualTo(mockCount); + } + + + @Test + @DisplayName("todoTeam 가입 기간을 조회한다") + void findUserRegisterTerm() { + //given + final Long todoTeamId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final Long userId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final Register mockRegister = RegisterTestFixtureEntityUtils.getRegisterEntity(); + final LocalDate mockDate = LocalDate.now(); + given(registerRepository.findByTodoTeamIdAndUserId(todoTeamId, userId)).willReturn(Optional.of(mockRegister)); + //when + Integer result = registerQueryService.findUserRegisterTerm(todoTeamId, userId); + //then + Assertions.assertThat(result).isEqualTo((int) ChronoUnit.DAYS.between(mockRegister.getCreatedAt().toLocalDate(), mockDate)); + } + + @Test + @DisplayName("userId로 가장 최근에 가입한 todoteam의 register를 조회한다.") + void findLatestTodoTeam() { + //given + final Long userId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final Register mockRegister = RegisterTestFixtureEntityUtils.getRegisterEntity(); + given(registerRepository.findLatestRegisterByUserIdQuery(userId)).willReturn(Optional.of(mockRegister)); + //when + Register result = registerQueryService.findLatestTodoTeam(userId); + //then + Assertions.assertThat(result).usingRecursiveComparison().isEqualTo(mockRegister); + } + + @Test + @DisplayName("userId로 가장 최근에 가입한 todoteam의 register를 조회하지 못하면 NotRegisterUserException이 발생한다.") + void findLatestTodoTeamFail() { + //given + final Long userId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + given(registerRepository.findLatestRegisterByUserIdQuery(userId)).willReturn(Optional.empty()); + //when + //then + Assertions.assertThatCode(() -> registerQueryService.findLatestTodoTeam(userId)) + .isInstanceOf(NotRegisterUserException.class); + } + + @Test + @DisplayName("userId로 가입한 todo team의 register 리스트를 반환한다.") + void findRegisterListByUserIdWithTodoTeam() { //given - final Long todoId = FixtureMonkey.create().giveMeOne(Long.class); + final Long userId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); final List mockRegister = RegisterTestFixtureEntityUtils.getRegisterEntityList(5); - given(registerRepository.findByTodoId(todoId)).willReturn(mockRegister); + given(registerRepository.findAllByUserId(userId)).willReturn(mockRegister); //when - List result = registerQueryService.findAllRegistersByTodoId(todoId); + List result = registerQueryService.findAllRegistersByUserId(userId); //then Assertions.assertThat(result).usingRecursiveComparison().isEqualTo(mockRegister); } @Test - @DisplayName("todoTeamId로 Register 엔티티의 수를 조회한다.") - void countRegisterByTodoTeamId() { + @DisplayName("userId로 가입한 todo team의 register 리스트를 반환한다. 탈퇴할 경우 조회되지 않는다.") + void findRegisterListByUserIdWithTodoTeamWithUnregistered() { //given - final Long todoTeamId = FixtureMonkey.create().giveMeOne(Long.class); - final Integer mockCount = FixtureMonkey.create().giveMeOne(Integer.class); - given(registerRepository.countByTodoTeamId(todoTeamId)).willReturn(mockCount); + final Long userId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final List mockRegister = RegisterTestFixtureEntityUtils.getRegisterEntityListWithUnregistered(5); + given(registerRepository.findAllByUserId(userId)).willReturn(mockRegister); //when - Integer result = registerQueryService.countRegisterByTodoTeamId(todoTeamId); + List result = registerQueryService.findAllRegistersByUserId(userId); //then - Assertions.assertThat(result).isEqualTo(mockCount); + Assertions.assertThat(result.size()).isEqualTo(0); } @Test - @DisplayName("CategoryId로 UserId를 조회한다.") - void findUserIdsByCategoryId() { + @DisplayName("userId로 가입한 todo team의 register 리스트를 반환한다. 해당 메소드는 todoteam과 fetch join된 상태이다.") + void findRegisterListByUserIdWithTodoTeamFetch() { //given - final Long categoryId = FixtureMonkey.create().giveMeOne(Long.class); + final Long userId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); final List mockRegister = RegisterTestFixtureEntityUtils.getRegisterEntityList(5); - given(registerRepository.findAllByCategoryId(categoryId)).willReturn(mockRegister); + given(registerRepository.findAllByUserIdWithTodoTeamFetchQuery(userId)).willReturn(mockRegister); //when - List result = registerQueryService.findUserIdsByCategoryId(categoryId); + List result = registerQueryService.findRegisterListByUserIdWithTodoTeam(userId); //then - Assertions.assertThat(result).isEqualTo(mockRegister.stream().map(Register::getUserId).collect(Collectors.toList())); + Assertions.assertThat(result).usingRecursiveComparison().isEqualTo(mockRegister); } @Test - @DisplayName("todoTeam 가입 기간을 조회한다") - void findUserRegisterTerm() { + @DisplayName("userId로 가입한 todo team의 register 리스트를 반환한다. 해당 메소드는 todoteam과 fetch join된 상태이다. 탈퇴할 경우 조회되지 않는다.") + void findRegisterListByUserIdWithTodoTeamFetchWithUnregistered() { //given - final Long todoTeamId = FixtureMonkey.create().giveMeOne(Long.class); - final Long userId = FixtureMonkey.create().giveMeOne(Long.class); + final Long userId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final List mockRegister = RegisterTestFixtureEntityUtils.getRegisterEntityListWithUnregistered(5); + given(registerRepository.findAllByUserIdWithTodoTeamFetchQuery(userId)).willReturn(mockRegister); + //when + List result = registerQueryService.findRegisterListByUserIdWithTodoTeam(userId); + //then + Assertions.assertThat(result.size()).isEqualTo(0); + } + + @Test + @DisplayName("registerId로 register를 조회한다.") + void findRegisterById() { + //given + final Long registerId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); final Register mockRegister = RegisterTestFixtureEntityUtils.getRegisterEntity(); - final LocalDate mockDate = LocalDate.now(); - given(registerRepository.findByTodoTeamIdAndUserId(todoTeamId, userId)).willReturn(Optional.of(mockRegister)); + given(registerRepository.findById(registerId)).willReturn(Optional.of(mockRegister)); //when - Integer result = registerQueryService.findUserRegisterTerm(todoTeamId, userId); + Register result = registerQueryService.findRegisterById(registerId); //then - Assertions.assertThat(result).isEqualTo((int) ChronoUnit.DAYS.between(mockRegister.getCreatedAt().toLocalDate(), mockDate)); + Assertions.assertThat(result).usingRecursiveComparison().isEqualTo(mockRegister); } + @Test + @DisplayName("registerId로 register를 조회한다. 탈퇴할 경우 조회되지 않는다.") + void findRegisterByIdWithUnregistered() { + //given + final Long registerId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final Register mockRegister = RegisterTestFixtureEntityUtils.getRegisterEntityWithUnregistered(); + given(registerRepository.findById(registerId)).willReturn(Optional.of(mockRegister)); + //when + //then + Assertions.assertThatCode(() -> registerQueryService.findRegisterById(registerId)) + .isInstanceOf(NotRegisterUserException.class); + } + + + } \ No newline at end of file diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/RegisterSaveServiceTest.java b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/RegisterSaveServiceTest.java index c05e292a..5606223d 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/RegisterSaveServiceTest.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/RegisterSaveServiceTest.java @@ -1,6 +1,5 @@ package com.pawith.tododomain.service; -import com.navercorp.fixturemonkey.FixtureMonkey; import com.pawith.commonmodule.UnitTestConfig; import com.pawith.commonmodule.utils.FixtureMonkeyUtils; import com.pawith.tododomain.entity.TodoTeam; @@ -11,6 +10,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mock; +import org.springframework.context.ApplicationEventPublisher; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; @@ -22,12 +22,14 @@ class RegisterSaveServiceTest { @Mock private RegisterRepository registerRepository; + @Mock + private ApplicationEventPublisher applicationEventPublisher; private RegisterSaveService registerSaveService; @BeforeEach void init() { - registerSaveService = new RegisterSaveService(registerRepository); + registerSaveService = new RegisterSaveService(registerRepository, applicationEventPublisher); } @Test diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/RegisterValidateServiceTest.java b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/RegisterValidateServiceTest.java new file mode 100644 index 00000000..9bb856ae --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/RegisterValidateServiceTest.java @@ -0,0 +1,140 @@ +package com.pawith.tododomain.service; + +import com.pawith.commonmodule.UnitTestConfig; +import com.pawith.commonmodule.utils.FixtureMonkeyUtils; +import com.pawith.tododomain.entity.Authority; +import com.pawith.tododomain.entity.Register; +import com.pawith.tododomain.exception.UnchangeableException; +import com.pawith.tododomain.exception.UnregistrableException; +import com.pawith.tododomain.repository.RegisterRepository; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.BDDMockito; +import org.mockito.Mock; + +import java.util.List; + +import static org.mockito.BDDMockito.given; + + +@UnitTestConfig +@DisplayName("RegisterValidateService 테스트") +class RegisterValidateServiceTest { + + @Mock + private RegisterRepository registerRepository; + + private RegisterValidateService registerValidateService; + + @BeforeEach + void init() { + registerValidateService = new RegisterValidateService(registerRepository); + } + + @Test + @DisplayName("운영진 register 탈퇴 가능 여부 테스트 - 운영진이 1명이고, register가 2명 이상일 때 UnregisterableException 발생") + void validatePresidentRegisterDeletable_one_president_two_register_throw_unregisterableException() { + // given + final Register mockRegister = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeBuilder(Register.class) + .set("isRegistered", true) + .set("authority", Authority.PRESIDENT) + .sample(); + final Integer registerCount = 2; + given(registerRepository.countByTodoTeamIdQuery(mockRegister.getTodoTeam().getId())).willReturn(registerCount); + // when + // then + Assertions.assertThatCode(() -> registerValidateService.validatePresidentRegisterDeletable(mockRegister)).isInstanceOf(UnregistrableException.class); + } + + @Test + @DisplayName("register 탈퇴 가능 여부 테스트 - 운영진이 1명 이상이고, register가 1명일 때 UnregisterableException 발생하지 않음") + void validateRegisterDeletable_one_over_president_one_register() { + // given + final Register mockRegister = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeBuilder(Register.class) + .set("isRegistered", true) + .set("authority", Authority.MEMBER) + .sample(); + final Integer registerCount = 1; + BDDMockito.lenient().when(registerRepository.countByTodoTeamIdQuery(mockRegister.getTodoTeam().getId())).thenReturn(registerCount); + // when + // then + Assertions.assertThatCode(() -> registerValidateService.validatePresidentRegisterDeletable(mockRegister)).doesNotThrowAnyException(); + } + + @Test + @DisplayName("사용자 탈퇴 가능 여부 테스트 - 참여중인 todo team 중 운영진으로 포함된 todo team이 1개 이상일 때 UnregisterableException 발생") + void validateRegisterDeletable_with_president_register() { + // given + final List registerList = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeBuilder(Register.class) + .set("isRegistered", true) + .set("authority", Authority.PRESIDENT) + .sampleList(5); + registerList.forEach(register -> { + final Integer registerCount = 2; + BDDMockito.lenient().when(registerRepository.countByTodoTeamIdQuery(register.getTodoTeam().getId())).thenReturn(registerCount); + }); + // when + // then + Assertions.assertThatCode(() -> registerValidateService.validateRegisterDeletable(registerList)).isInstanceOf(UnregistrableException.class); + } + + @Test + @DisplayName("사용자 탈퇴 가능 여부 테스트 - 참여중인 todo team 중 운영진으로 포함된 todo team이 1개 이하일 때 UnregisterableException 발생하지 않음") + void validateRegisterDeletable_without_president_register() { + // given + final List registerList = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeBuilder(Register.class) + .set("isRegistered", true) + .set("authority", Authority.MEMBER) + .sampleList(5); + registerList.forEach(register -> { + final Integer registerCount = 1; + BDDMockito.lenient().when(registerRepository.countByTodoTeamIdQuery(register.getTodoTeam().getId())).thenReturn(registerCount); + }); + // when + // then + Assertions.assertThatCode(() -> registerValidateService.validateRegisterDeletable(registerList)).doesNotThrowAnyException(); + } + + @Test + @DisplayName("사용자 권한 변경 가능 여부 테스트 - 사용자가 운영진이 아니고 권한 파라미터가 운영진이 아닌경우 UnchangeableException 발생하지 않음") + void validateAuthorityChangeable_with_member() { + // given + final Register mockRegister = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeBuilder(Register.class) + .set("isRegistered", true) + .set("authority", Authority.MEMBER) + .sample(); + // when + // then + Assertions.assertThatCode(() -> registerValidateService.validateAuthorityChangeable(mockRegister, Authority.MEMBER)).doesNotThrowAnyException(); + } + + @Test + @DisplayName("사용자 권한 변경 가능 여부 테스트 - 사용자가 운영진이고 권한 파라미터가 운영진인 경우 UnchangeableException 발생하지 않음") + void validateAuthorityChangeable_with_president() { + // given + final Register mockRegister = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeBuilder(Register.class) + .set("isRegistered", true) + .set("authority", Authority.PRESIDENT) + .sample(); + // when + // then + Assertions.assertThatCode(() -> registerValidateService.validateAuthorityChangeable(mockRegister, Authority.PRESIDENT)).doesNotThrowAnyException(); + } + + @Test + @DisplayName("사용자 권한 변경 가능 여부 테스트 - 사용자가 운영진이 아니고 권한 파라미터가 운영진인 경우 UnchangeableException 발생") + void validateAuthorityChangeable_with_member_and_president() { + // given + final Register mockRegister = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeBuilder(Register.class) + .set("isRegistered", true) + .set("authority", Authority.MEMBER) + .sample(); + // when + // then + Assertions.assertThatCode(() -> registerValidateService.validateAuthorityChangeable(mockRegister, Authority.PRESIDENT)).isInstanceOf(UnchangeableException.class); + } + + +} \ No newline at end of file diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoQueryServiceTest.java b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoQueryServiceTest.java index 142605d5..ff864d4c 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoQueryServiceTest.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoQueryServiceTest.java @@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.SliceImpl; @@ -69,12 +70,12 @@ void findTodoCompleteRate() { final LocalDate now = LocalDate.now(); final Long mockCountTodayTodo = FixtureMonkey.create().giveMeOne(Long.class); final Long mockCountCompleteTodayTodo = FixtureMonkey.create().giveMeOne(Long.class); - given(todoRepository.countTodoByDate(userId, todoTeamId, now)).willReturn(mockCountTodayTodo); - given(todoRepository.countCompleteTodoByDate(userId, todoTeamId, now)).willReturn(mockCountCompleteTodayTodo); + given(todoRepository.countTodoByDateQuery(userId, todoTeamId, now)).willReturn(mockCountTodayTodo); + given(todoRepository.countCompleteTodoByDateQuery(userId, todoTeamId, now)).willReturn(mockCountCompleteTodayTodo); //when Integer result = todoQueryService.findTodoCompleteRate(userId, todoTeamId); //then - Assertions.assertThat(result).isEqualTo((int)(mockCountCompleteTodayTodo / (double) mockCountTodayTodo * 100)); + Assertions.assertThat(result).isEqualTo((int) (mockCountCompleteTodayTodo / (double) mockCountTodayTodo * 100)); } @Test @@ -84,9 +85,9 @@ void findTodoListByCategoryIdAndscheduledDate() { final Long categoryId = FixtureMonkey.create().giveMeOne(Long.class); final LocalDate moveDate = FixtureMonkey.create().giveMeOne(LocalDate.class); final List mockTodoList = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMe(Todo.class, 10); - given(todoRepository.findTodoListByCategoryIdAndscheduledDate(categoryId, moveDate)).willReturn(mockTodoList); + given(todoRepository.findTodoListByCategoryIdAndScheduledDateQuery(categoryId, moveDate)).willReturn(mockTodoList); //when - List result = todoQueryService.findTodoListByCategoryIdAndscheduledDate(categoryId, moveDate); + List result = todoQueryService.findTodoListByCategoryIdAndScheduledDate(categoryId, moveDate); //then Assertions.assertThat(result).usingRecursiveComparison().isEqualTo(mockTodoList); } @@ -101,12 +102,12 @@ void findThisWeekTodoCompleteRate() { final LocalDate firstDayOfWeek = now.with(java.time.DayOfWeek.SUNDAY); final Long mockCountWeekTodo = FixtureMonkey.create().giveMeOne(Long.class); final Long mockCountCompleteWeekTodo = FixtureMonkey.create().giveMeOne(Long.class); - given(todoRepository.countTodoByBetweenDate(userId, todoTeamId, now, firstDayOfWeek)).willReturn(mockCountWeekTodo); - given(todoRepository.countCompleteTodoByBetweenDate(userId, todoTeamId, now, firstDayOfWeek)).willReturn(mockCountCompleteWeekTodo); + given(todoRepository.countTodoByBetweenDateQuery(userId, todoTeamId, now, firstDayOfWeek)).willReturn(mockCountWeekTodo); + given(todoRepository.countCompleteTodoByBetweenDateQuery(userId, todoTeamId, now, firstDayOfWeek)).willReturn(mockCountCompleteWeekTodo); //when Integer result = todoQueryService.findThisWeekTodoCompleteRate(userId, todoTeamId); //then - Assertions.assertThat(result).isEqualTo((int)(mockCountCompleteWeekTodo / (double) mockCountWeekTodo * 100)); + Assertions.assertThat(result).isEqualTo((int) (mockCountCompleteWeekTodo / (double) mockCountWeekTodo * 100)); } @Test @@ -119,13 +120,72 @@ void findLastWeekTodoCompleteRate() { final LocalDate firstDayOfLastWeek = now.with(java.time.DayOfWeek.SUNDAY).minusWeeks(1); final Long mockCountWeekTodo = FixtureMonkey.create().giveMeOne(Long.class); final Long mockCountCompleteWeekTodo = FixtureMonkey.create().giveMeOne(Long.class); - given(todoRepository.countTodoByBetweenDate(userId, todoTeamId, now, firstDayOfLastWeek)).willReturn(mockCountWeekTodo); - given(todoRepository.countCompleteTodoByBetweenDate(userId, todoTeamId, now, firstDayOfLastWeek)).willReturn(mockCountCompleteWeekTodo); + given(todoRepository.countTodoByBetweenDateQuery(userId, todoTeamId, now, firstDayOfLastWeek)).willReturn(mockCountWeekTodo); + given(todoRepository.countCompleteTodoByBetweenDateQuery(userId, todoTeamId, now, firstDayOfLastWeek)).willReturn(mockCountCompleteWeekTodo); //when Integer result = todoQueryService.findLastWeekTodoCompleteRate(userId, todoTeamId); //then - Assertions.assertThat(result).isEqualTo((int)(mockCountCompleteWeekTodo / (double) mockCountWeekTodo * 100)); + Assertions.assertThat(result).isEqualTo((int) (mockCountCompleteWeekTodo / (double) mockCountWeekTodo * 100)); } + @Test + @DisplayName("UserId와 TodoTeamId로 Todo 목록을 조회한다.") + void findAllTodoListByTodoTeamId() { + //given + final Long userId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final Long todoTeamId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final Pageable pageable = PageRequest.of(0, 10); + final List mockTodoList = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMe(Todo.class, pageable.getPageSize()); + final SliceImpl mockTodoSlice = new SliceImpl<>(mockTodoList, pageable, true); + given(todoRepository.findTodoSliceByUserIdAndTodoTeamIdQuery(userId, todoTeamId, pageable)).willReturn(mockTodoSlice); + //when + Slice result = todoQueryService.findAllTodoListByTodoTeamId(userId, todoTeamId, pageable); + //then + Assertions.assertThat(result).usingRecursiveComparison().isEqualTo(mockTodoSlice); + } + + @Test + @DisplayName("UserId로 Todo 목록을 조회한다.") + void findAllTodoListByUserId() { + //given + final Long userId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final Pageable pageable = PageRequest.of(0, 10); + final List mockTodoList = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMe(Todo.class, pageable.getPageSize()); + final SliceImpl mockTodoSlice = new SliceImpl<>(mockTodoList, pageable, true); + given(todoRepository.findTodoSliceByUserIdQuery(userId, pageable)).willReturn(mockTodoSlice); + //when + Slice result = todoQueryService.findAllTodoListByUserId(userId, pageable); + //then + Assertions.assertThat(result).usingRecursiveComparison().isEqualTo(mockTodoSlice); + } + + @Test + @DisplayName("UserId와 TodoTeamId로 Todo의 개수를 조회한다.") + void countTodoByTodoTeamId() { + //given + final Long userId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final Long todoTeamId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final Integer mockCount = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Integer.class); + given(todoRepository.countTodoByUserIdAndTodoTeamIdQuery(userId, todoTeamId)).willReturn(mockCount); + //when + Integer result = todoQueryService.countTodoByTodoTeamId(userId, todoTeamId); + //then + Assertions.assertThat(result).isEqualTo(mockCount); + } + + @Test + @DisplayName("UserId로 Todo의 개수를 조회한다.") + void countTodoByUserId() { + //given + final Long userId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final Integer mockCount = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Integer.class); + given(todoRepository.countTodoByUserIdQuery(userId)).willReturn(mockCount); + //when + Integer result = todoQueryService.countTodoByUserId(userId); + //then + Assertions.assertThat(result).isEqualTo(mockCount); + } + + } \ No newline at end of file diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoTeamCodeGenerateServiceTest.java b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoTeamCodeManageServiceTest.java similarity index 69% rename from Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoTeamCodeGenerateServiceTest.java rename to Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoTeamCodeManageServiceTest.java index cf6c3f6c..08f04bef 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoTeamCodeGenerateServiceTest.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoTeamCodeManageServiceTest.java @@ -1,6 +1,7 @@ package com.pawith.tododomain.service; import com.pawith.commonmodule.UnitTestConfig; +import com.pawith.commonmodule.cache.operators.SetOperator; import com.pawith.tododomain.repository.TodoTeamRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -13,16 +14,18 @@ @UnitTestConfig @DisplayName("TodoTeamCodeGenerateService 테스트") -class TodoTeamCodeGenerateServiceTest { +class TodoTeamCodeManageServiceTest { @Mock private TodoTeamRepository todoTeamRepository; + @Mock + private SetOperator setOperator; - private TodoTeamCodeGenerateService todoTeamCodeGenerateService; + private TodoTeamCodeManageService todoTeamCodeManageService; @BeforeEach void init() { - todoTeamCodeGenerateService = new TodoTeamCodeGenerateService(todoTeamRepository); + todoTeamCodeManageService = new TodoTeamCodeManageService(todoTeamRepository, setOperator); } @Test @@ -31,7 +34,7 @@ void generateTodoTeamCode() { //given given(todoTeamRepository.existsByTeamCode(anyString())).willReturn(false); //when - String result = todoTeamCodeGenerateService.generateRandomCode(); + String result = todoTeamCodeManageService.generateRandomCode(); //then assertEquals(8, result.length()); } diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoTeamQueryServiceTest.java b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoTeamQueryServiceTest.java index d49c67d0..1f90f58f 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoTeamQueryServiceTest.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoTeamQueryServiceTest.java @@ -90,7 +90,7 @@ void findAllTodoTeamByUserId() { //given final Long userId = FixtureMonkey.create().giveMeOne(Long.class); final List mockTodoTeam = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMe(TodoTeam.class, 10); - given(todoTeamRepository.findAllByUserId(userId)).willReturn(mockTodoTeam); + given(todoTeamRepository.findAllByUserIdQuery(userId)).willReturn(mockTodoTeam); //when List result = todoTeamQueryService.findAllTodoTeamByUserId(userId); //then diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoTeamSaveServiceTest.java b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoTeamSaveServiceTest.java index 103f63f6..41688054 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoTeamSaveServiceTest.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoTeamSaveServiceTest.java @@ -9,7 +9,9 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mock; +import org.springframework.context.ApplicationEventPublisher; +import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; @Slf4j @@ -19,12 +21,14 @@ class TodoTeamSaveServiceTest { @Mock private TodoTeamRepository todoTeamRepository; + @Mock + private ApplicationEventPublisher applicationEventPublisher; private TodoTeamSaveService todoTeamSaveService; @BeforeEach void init() { - todoTeamSaveService = new TodoTeamSaveService(todoTeamRepository); + todoTeamSaveService = new TodoTeamSaveService(todoTeamRepository, applicationEventPublisher); } @Test @@ -33,7 +37,7 @@ void saveTodoTeamEntity() { //given final TodoTeam mockTodoTeam = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey() .giveMeOne(TodoTeam.class); - log.info("mockTodoTeam: {}", mockTodoTeam); + given(todoTeamRepository.save(mockTodoTeam)).willReturn(mockTodoTeam); //when todoTeamSaveService.saveTodoTeamEntity(mockTodoTeam); //then diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoValidateServiceTest.java b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoValidateServiceTest.java new file mode 100644 index 00000000..556da0fa --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/service/TodoValidateServiceTest.java @@ -0,0 +1,74 @@ +package com.pawith.tododomain.service; + +import com.pawith.commonmodule.UnitTestConfig; +import com.pawith.commonmodule.utils.FixtureMonkeyUtils; +import com.pawith.tododomain.entity.Authority; +import com.pawith.tododomain.entity.Register; +import com.pawith.tododomain.entity.Todo; +import com.pawith.tododomain.exception.TodoModificationNotAllowedException; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@UnitTestConfig +@DisplayName("TodoValidateService 테스트") +class TodoValidateServiceTest { + + private TodoValidateService todoValidateService; + + @BeforeEach + void init() { + todoValidateService = new TodoValidateService(); + } + + @Test + @DisplayName("todo 생성자가 아니면서 멤버이면 true를 반환한다.") + void validateDeleteAndUpdate_throw_ToDoModificationNotAllowedException() { + // given + final Boolean isNotValidate = true; + final Register register = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeBuilder(Register.class) + .set("authority", Authority.MEMBER) + .sample(); + final Todo todo = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeBuilder(Todo.class) + .set("creatorId",register.getId()-1) + .sample(); + // when + // then + Assertions.assertThat(todoValidateService.validateDeleteAndUpdate(todo, register)).isEqualTo(isNotValidate); + } + + + @Test + @DisplayName("todo 생성자이면 false를 반환한다.") + void validateDeleteAndUpdate_throw_NoException() { + // given + final Boolean isNotValidate = false; + final Register register = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeBuilder(Register.class) + .set("authority", Authority.MEMBER) + .sample(); + final Todo todo = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeBuilder(Todo.class) + .set("creatorId",register.getId()) + .sample(); + // when + // then + Assertions.assertThat(todoValidateService.validateDeleteAndUpdate(todo, register)).isEqualTo(isNotValidate); + } + + @Test + @DisplayName("todo 생성자가 아니면서 운영진이면 false를 반환한다.") + void validateDeleteAndUpdate_throw_NoException2() { + // given + final Boolean isNotValidate = false; + final Register register = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeBuilder(Register.class) + .set("authority", Authority.PRESIDENT) + .sample(); + final Todo todo = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeBuilder(Todo.class) + .set("creatorId",register.getId()-1) + .sample(); + // when + // then + Assertions.assertThat(todoValidateService.validateDeleteAndUpdate(todo, register)).isEqualTo(isNotValidate); + } + +} \ No newline at end of file diff --git a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/utils/RegisterTestFixtureEntityUtils.java b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/utils/RegisterTestFixtureEntityUtils.java index 97465933..6916a5d6 100644 --- a/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/utils/RegisterTestFixtureEntityUtils.java +++ b/Domain-Module/Todo-Module/Todo-Domain/src/test/java/com/pawith/tododomain/utils/RegisterTestFixtureEntityUtils.java @@ -23,13 +23,13 @@ public static Register getRegisterEntityWithUnregistered(){ } public static List getRegisterEntityList(int size){ - return FixtureMonkeyUtils.getConstructBasedFixtureMonkey().giveMeBuilder(Register.class) + return FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeBuilder(Register.class) .set("isRegistered", true) .sampleList(size); } public static List getRegisterEntityListWithUnregistered(int size){ - return FixtureMonkeyUtils.getConstructBasedFixtureMonkey().giveMeBuilder(Register.class) + return FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeBuilder(Register.class) .set("isRegistered", false) .sampleList(size); } diff --git a/Domain-Module/Todo-Module/Todo-Infrastructure/build.gradle b/Domain-Module/Todo-Module/Todo-Infrastructure/build.gradle new file mode 100644 index 00000000..def1ee25 --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Infrastructure/build.gradle @@ -0,0 +1,3 @@ +dependencies { + implementation project(":Domain-Module:Todo-Module:Todo-Domain") +} \ No newline at end of file diff --git a/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/dao/IncompleteTodoCountInfoDaoImpl.java b/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/dao/IncompleteTodoCountInfoDaoImpl.java new file mode 100644 index 00000000..905b7e84 --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/dao/IncompleteTodoCountInfoDaoImpl.java @@ -0,0 +1,31 @@ +package com.pawith.todoinfrastructure.dao; + +import com.pawith.tododomain.repository.dao.IncompleteTodoCountInfoDao; +import com.querydsl.core.annotations.QueryProjection; + +public record IncompleteTodoCountInfoDaoImpl(Long todoTeamId, Long userId, String todoTeamName, Long incompleteTodoCount) implements IncompleteTodoCountInfoDao { + + @QueryProjection + public IncompleteTodoCountInfoDaoImpl { + } + + @Override + public Long getTodoTeamId() { + return this.todoTeamId; + } + + @Override + public Long getUserId() { + return this.userId; + } + + @Override + public String getTodoTeamName() { + return this.todoTeamName; + } + + @Override + public Long getIncompleteTodoCount() { + return this.incompleteTodoCount; + } +} diff --git a/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/dao/NotificationDaoImpl.java b/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/dao/NotificationDaoImpl.java new file mode 100644 index 00000000..e11a9692 --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/dao/NotificationDaoImpl.java @@ -0,0 +1,51 @@ +package com.pawith.todoinfrastructure.dao; + +import com.pawith.tododomain.repository.dao.NotificationDao; +import com.querydsl.core.annotations.QueryProjection; + +import java.time.LocalDate; +import java.time.LocalTime; + +public record NotificationDaoImpl( + Long todoTeamId, Long userId, String categoryName, String todoDescription, + LocalTime notificationTime, LocalDate scheduledDate,String todoTeamName ) implements NotificationDao { + + @QueryProjection + public NotificationDaoImpl { + } + + @Override + public Long getTodoTeamId() { + return this.todoTeamId; + } + + @Override + public Long getUserId() { + return this.userId; + } + + @Override + public String getCategoryName() { + return this.categoryName; + } + + @Override + public String getTodoDescription() { + return this.todoDescription; + } + + @Override + public LocalTime getNotificationTime() { + return this.notificationTime; + } + + @Override + public LocalDate getScheduledDate() { + return this.scheduledDate; + } + + @Override + public String getTodoTeamName() { + return this.todoTeamName; + } +} 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 new file mode 100644 index 00000000..a48fb4fb --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/AssignRepositoryImpl.java @@ -0,0 +1,115 @@ +package com.pawith.todoinfrastructure.repository; + +import com.pawith.tododomain.entity.*; +import com.pawith.tododomain.repository.AssignQueryRepository; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.impl.JPAQueryFactory; +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.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; + final QTodo qTodo = qAssign.todo; + final QRegister qRegister = qAssign.register; + return queryFactory.select(qAssign) + .from(qAssign) + .join(qTodo).fetchJoin() + .join(qRegister).fetchJoin() + .where(qTodo.category.id.eq(categoryId) + .and(qTodo.scheduledDate.eq(scheduledDate))) + .orderBy(qTodo.completionStatus.desc()) + .fetch(); + } + + @Override + public List findAllByUserIdAndTodoTeamIdAndScheduledDateQuery(Long userId, Long todoTeamId, LocalDate scheduledDate) { + final QAssign qAssign = QAssign.assign; + final QTodo qTodo = qAssign.todo; + final QRegister qRegister = qAssign.register; + final QCategory qCategory = qTodo.category; + return queryFactory.select(qAssign) + .from(qAssign) + .join(qTodo).fetchJoin() + .join(qCategory).fetchJoin() + .where(qRegister.userId.eq(userId) + .and(qRegister.todoTeam.id.eq(todoTeamId)) + .and(qTodo.scheduledDate.eq(scheduledDate)) + .and(qTodo.category.categoryStatus.eq(CategoryStatus.ON))) + .fetch(); + } + + @Override + public void deleteByRegisterIdsQuery(final List registerIds) { + final QAssign qAssign = QAssign.assign; + queryFactory.update(qAssign) + .set(qAssign.isDeleted, true) + .where(qAssign.register.id.in(registerIds)) + .execute(); + } + + @Override + public void deleteAllByTodoIdQuery(final Long todoId) { + final QAssign qAssign = QAssign.assign; + queryFactory.update(qAssign) + .set(qAssign.isDeleted, true) + .where(qAssign.todo.id.eq(todoId)) + .execute(); + } + + @Override + public Optional findByTodoIdAndUserIdQuery(Long todoId, Long userId) { + final QAssign qAssign = QAssign.assign; + final QTodo qTodo = QTodo.todo; + final QRegister qRegister = QRegister.register; + return Optional.ofNullable(queryFactory.select(qAssign) + .from(qAssign) + .join(qRegister).on(qRegister.id.eq(qAssign.register.id).and(qRegister.userId.eq(userId))) + .join(qTodo).on(qTodo.id.eq(qAssign.todo.id).and(qTodo.id.eq(todoId))) + .fetchOne()); + } + + @Override + public List findAllByTodoIdQuery(Long todoId) { + final QAssign qAssign = QAssign.assign; + final QTodo qTodo = qAssign.todo; + return queryFactory.select(qAssign) + .from(qAssign) + .join(qTodo).fetchJoin() + .where(qTodo.id.eq(todoId)) + .fetch(); + } + + @Override + public List findAllByTodoIdWithRegisterFetchQuery(Long todoId) { + final QAssign qAssign = QAssign.assign; + final QRegister qRegister = qAssign.register; + return queryFactory.select(qAssign) + .from(qAssign) + .join(qRegister).fetchJoin() + .where(qAssign.todo.id.eq(todoId)) + .fetch(); + } + + @Override + @Transactional + public void deleteAllByCategoryIdQuery(final Long categoryId) { + final QAssign qAssign = QAssign.assign; + final QTodo qTodo = QTodo.todo; + queryFactory.update(qAssign) + .set(qAssign.isDeleted, true) + .where(qAssign.todo.id.in(JPAExpressions.select(qTodo.id) + .from(qTodo) + .where(qTodo.category.id.eq(categoryId)))) + .execute(); + } +} diff --git a/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/CategoryRepositoryImpl.java b/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/CategoryRepositoryImpl.java new file mode 100644 index 00000000..7417455e --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/CategoryRepositoryImpl.java @@ -0,0 +1,48 @@ +package com.pawith.todoinfrastructure.repository; + +import com.pawith.tododomain.entity.Category; +import com.pawith.tododomain.entity.CategoryStatus; +import com.pawith.tododomain.entity.QCategory; +import com.pawith.tododomain.repository.CategoryQueryRepository; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.time.LocalDate; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class CategoryRepositoryImpl implements CategoryQueryRepository { + private final JPAQueryFactory queryFactory; + + @Override + public List findAllByTodoTeamIdAndCategoryStatusQuery(Long todoTeamId, LocalDate moveDate) { + final QCategory qCategory = QCategory.category; + return queryFactory.select(qCategory) + .from(qCategory) + .where( + qCategory.todoTeam.id.eq(todoTeamId) + .and( + qCategory.categoryStatus.eq(CategoryStatus.ON) + .or( + moveDate != null + ? qCategory.categoryStatus.eq(CategoryStatus.OFF) + .and(qCategory.disabledAt.gt(moveDate)) + : null + ) + ) + ) + .orderBy(qCategory.createdAt.desc()) + .fetch(); + } + + @Override + public List findAllByTodoTeamIdQuery(Long todoTeamId) { + final QCategory qCategory = QCategory.category; + return queryFactory.select(qCategory) + .from(qCategory) + .where(qCategory.todoTeam.id.eq(todoTeamId)) + .orderBy(qCategory.createdAt.desc()) + .fetch(); + } +} diff --git a/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/RegisterRepositoryImpl.java b/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/RegisterRepositoryImpl.java new file mode 100644 index 00000000..553791a6 --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/RegisterRepositoryImpl.java @@ -0,0 +1,110 @@ +package com.pawith.todoinfrastructure.repository; + +import com.pawith.commonmodule.util.SliceUtils; +import com.pawith.tododomain.entity.*; +import com.pawith.tododomain.repository.RegisterQueryRepository; +import com.pawith.todoinfrastructure.dao.IncompleteTodoCountInfoDaoImpl; +import com.pawith.todoinfrastructure.dao.QIncompleteTodoCountInfoDaoImpl; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Repository; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +@Repository +@RequiredArgsConstructor +public class RegisterRepositoryImpl implements RegisterQueryRepository { + private final JPAQueryFactory queryFactory; + + @Override + public Slice findAllByUserIdQuery(Long userId, Pageable pageable) { + QRegister qRegister = QRegister.register; + QTodoTeam qTodoTeam = qRegister.todoTeam; + List registers = queryFactory.select(qRegister) + .from(qRegister) + .join(qTodoTeam).fetchJoin() + .where(qRegister.userId.eq(userId).and(qRegister.isRegistered.isTrue())) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize() + 1) + .fetch(); + return SliceUtils.getSliceImpl(registers, pageable); + } + + @Override + public List findAllByIdsQuery(List ids) { + QRegister qRegister = QRegister.register; + return queryFactory.select(qRegister) + .from(qRegister) + .where(qRegister.id.in(ids)) + .fetch(); + } + + @Override + public List findAllByUserIdWithTodoTeamFetchQuery(Long userId) { + QRegister qRegister = QRegister.register; + QTodoTeam qTodoTeam = qRegister.todoTeam; + return queryFactory.select(qRegister) + .from(qRegister) + .join(qTodoTeam).fetchJoin() + .where(qRegister.userId.eq(userId)) + .orderBy(qRegister.registerAt.desc()) + .fetch(); + } + + @Override + public Integer countByTodoTeamIdQuery(Long todoTeamId) { + QRegister qRegister = QRegister.register; + return queryFactory.select(qRegister.count()) + .from(qRegister) + .where(qRegister.todoTeam.id.eq(todoTeamId) + .and(qRegister.isRegistered.eq(true))) + .fetchOne().intValue(); + } + + @Override + public Optional findLatestRegisterByUserIdQuery(Long userId) { + QRegister qRegister = QRegister.register; + QTodoTeam qTodoTeam = qRegister.todoTeam; + return Optional.ofNullable(queryFactory.select(qRegister) + .from(qRegister) + .join(qTodoTeam).fetchJoin() + .where(qRegister.userId.eq(userId)) + .orderBy(qRegister.id.desc()) + .limit(1) + .fetchOne()); + } + + @Override + public void deleteByRegisterIdsQuery(List registerIds) { + QRegister qRegister = QRegister.register; + queryFactory.update(qRegister) + .set(qRegister.isDeleted, true) + .set(qRegister.isRegistered, false) + .where(qRegister.id.in(registerIds)) + .execute(); + } + + @Override + @SuppressWarnings("unchecked") + public List findAllIncompleteTodoCountInfoQuery(Pageable pageable) { + final QRegister qRegister = QRegister.register; + final QTodoTeam qTodoTeam = qRegister.todoTeam; + final QAssign qAssign = QAssign.assign; + final QTodo qTodo = qAssign.todo; + return queryFactory.select(new QIncompleteTodoCountInfoDaoImpl(qTodoTeam.id, qRegister.userId, qTodoTeam.teamName, qAssign.count())) + .from(qRegister) + .join(qTodoTeam) + .join(qAssign).on(qAssign.register.eq(qRegister)) + .join(qTodo).on(qTodo.scheduledDate.eq(LocalDate.now())) + .where(qAssign.completionStatus.eq(CompletionStatus.INCOMPLETE).and(qRegister.isRegistered.isTrue())) + .groupBy(qRegister.userId, qTodoTeam.teamName) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + } + +} diff --git a/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/TodoNotificationRepositoryImpl.java b/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/TodoNotificationRepositoryImpl.java new file mode 100644 index 00000000..85f5bd1a --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/TodoNotificationRepositoryImpl.java @@ -0,0 +1,64 @@ +package com.pawith.todoinfrastructure.repository; + +import com.pawith.tododomain.entity.*; +import com.pawith.tododomain.repository.TodoNotificationQueryRepository; +import com.pawith.todoinfrastructure.dao.NotificationDaoImpl; +import com.pawith.todoinfrastructure.dao.QNotificationDaoImpl; +import com.querydsl.core.types.dsl.DateTimeTemplate; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; + +@Slf4j +@Repository +@RequiredArgsConstructor +public class TodoNotificationRepositoryImpl implements TodoNotificationQueryRepository { + + private final JPAQueryFactory jpaQueryFactory; + + @Override + @SuppressWarnings("unchecked") + public List findAllWithNotCompletedAssignAndAlarmTimeQuery(final Duration criterionTime, final LocalDateTime alarmTime, final Pageable pageable) { + final QTodoNotification todoNotification = QTodoNotification.todoNotification; + final QAssign assign = QAssign.assign; + final QTodo todo = assign.todo; + final QRegister register = assign.register; + final QCategory category = todo.category; + final QTodoTeam todoTeam = register.todoTeam; + final DateTimeTemplate localDateTimeTemplate = + Expressions.dateTimeTemplate(LocalDateTime.class, "timestamp({0},{1})", todo.scheduledDate,todoNotification.notificationTime); + return jpaQueryFactory.select(new QNotificationDaoImpl(register.todoTeam.id, register.userId, category.name, todo.description, todoNotification.notificationTime, todo.scheduledDate,todoTeam.teamName )) + .from(todoNotification) + .join(todoNotification.assign, assign) + .join(todo) + .join(category) + .join(register) + .where(localDateTimeTemplate.between(alarmTime, alarmTime.plus(criterionTime)) + .and(assign.completionStatus.eq(CompletionStatus.INCOMPLETE)) + .and(register.isRegistered.eq(true))) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + } + + @Override + public List findAllByTodoIdsAndUserIdWithInCompleteAssignQuery(List todoId, Long userId) { + final QTodoNotification todoNotification = QTodoNotification.todoNotification; + final QAssign assign = todoNotification.assign; + final QRegister register = assign.register; + return jpaQueryFactory.selectFrom(todoNotification) + .join(assign) + .join(register).on(register.userId.eq(userId)) + .where(assign.completionStatus.eq(CompletionStatus.INCOMPLETE) + .and(assign.todo.id.in(todoId)) + .and(register.isRegistered.eq(true))) + .fetch(); + } +} diff --git a/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/TodoRepositoryImpl.java b/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/TodoRepositoryImpl.java new file mode 100644 index 00000000..d3c338d9 --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/TodoRepositoryImpl.java @@ -0,0 +1,177 @@ +package com.pawith.todoinfrastructure.repository; + +import com.pawith.commonmodule.util.SliceUtils; +import com.pawith.tododomain.entity.*; +import com.pawith.tododomain.repository.TodoQueryRepository; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class TodoRepositoryImpl implements TodoQueryRepository { + + private final JPAQueryFactory jpaQueryFactory; + + @Override + public List findTodoListByCategoryIdAndScheduledDateQuery(Long categoryId, LocalDate moveDate) { + final QTodo todo = QTodo.todo; + return jpaQueryFactory.selectFrom(todo) + .where(todo.category.id.eq(categoryId) + .and(todo.scheduledDate.eq(moveDate))) + .fetch(); + } + + @Override + public Long countTodoByDateQuery(Long userId, Long todoTeamId, LocalDate scheduledDate) { + final QTodo todo = QTodo.todo; + final QRegister register = QRegister.register; + final QAssign assign = QAssign.assign; + final QCategory category = QCategory.category; + return jpaQueryFactory.select(todo.count()) + .from(todo) + .join(register).on(register.userId.eq(userId).and(register.todoTeam.id.eq(todoTeamId))) + .join(assign).on(assign.register.id.eq(register.id).and(assign.todo.eq(todo) + .and(assign.isDeleted.eq(false)))) + .join(category).on(category.eq(todo.category).and(category.categoryStatus.eq(CategoryStatus.ON))) + .where(todo.scheduledDate.eq(scheduledDate)) + .fetchOne(); + } + + @Override + public Long countCompleteTodoByDateQuery(Long userId, Long todoTeamId, LocalDate scheduledDate) { + final QTodo todo = QTodo.todo; + final QRegister register = QRegister.register; + final QAssign assign = QAssign.assign; + final QCategory category = QCategory.category; + return jpaQueryFactory.select(todo.count()) + .from(todo) + .join(register).on(register.userId.eq(userId).and(register.todoTeam.id.eq(todoTeamId))) + .join(assign).on(assign.register.id.eq(register.id).and(assign.completionStatus.eq(CompletionStatus.COMPLETE)) + .and(assign.todo.eq(todo).and(assign.isDeleted.eq(false)))) + .join(category).on(category.eq(todo.category).and(category.categoryStatus.eq(CategoryStatus.ON))) + .where(todo.scheduledDate.eq(scheduledDate)) + .fetchOne(); + } + + @Override + public Long countTodoByBetweenDateQuery(Long userId, Long todoTeamId, LocalDate startDate, LocalDate endDate) { + final QTodo todo = QTodo.todo; + final QRegister register = QRegister.register; + final QAssign assign = QAssign.assign; + return jpaQueryFactory.select(todo.count()) + .from(todo) + .join(register).on(register.userId.eq(userId).and(register.todoTeam.id.eq(todoTeamId))) + .join(assign).on(assign.register.id.eq(register.id)) + .where(todo.scheduledDate.between(startDate, endDate).and(assign.todo.eq(todo))) + .fetchOne(); + } + + @Override + public Long countCompleteTodoByBetweenDateQuery(Long userId, Long todoTeamId, LocalDate startDate, LocalDate endDate) { + final QTodo todo = QTodo.todo; + final QRegister register = QRegister.register; + final QAssign assign = QAssign.assign; + return jpaQueryFactory.select(todo.count()) + .from(todo) + .join(register).on(register.userId.eq(userId).and(register.todoTeam.id.eq(todoTeamId))) + .join(assign).on(assign.register.id.eq(register.id)) + .where(todo.scheduledDate.between(startDate, endDate).and(assign.todo.eq(todo)).and(todo.completionStatus.eq(CompletionStatus.COMPLETE))) + .fetchOne(); + } + + @Override + @Transactional + public void deleteAllByCategoryIdQuery(Long categoryId) { + final QTodo todo = QTodo.todo; + jpaQueryFactory + .update(todo) + .set(todo.isDeleted, true) + .where(todo.category.id.eq(categoryId)) + .execute(); + } + + @Override + public Slice findTodoSliceByUserIdAndTodoTeamIdQuery(Long userId, Long todoTeamId, Pageable pageable) { + final QTodo todo = QTodo.todo; + final QCategory category = todo.category; + final QRegister register = QRegister.register; + final QAssign assign = QAssign.assign; + List todos = jpaQueryFactory.select(todo) + .from(todo) + .join(category).fetchJoin() + .join(register).on(register.userId.eq(userId).and(register.todoTeam.id.eq(todoTeamId))) + .join(assign).on(assign.register.id.eq(register.id)) + .where(assign.todo.eq(todo)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize() + 1) + .fetch(); + return SliceUtils.getSliceImpl(todos, pageable); + } + + @Override + public Slice findTodoSliceByUserIdQuery(Long userId, Pageable pageable) { + final QTodo todo = QTodo.todo; + final QCategory category = todo.category; + final QTodoTeam todoTeam = category.todoTeam; + final QRegister register = QRegister.register; + final QAssign assign = QAssign.assign; + List todos = jpaQueryFactory.select(todo) + .from(todo) + .join(category).fetchJoin() + .join(todoTeam).fetchJoin() + .join(register).on(register.userId.eq(userId)) + .join(assign).on(assign.register.id.eq(register.id)) + .where(assign.todo.eq(todo)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize() + 1) + .fetch(); + return SliceUtils.getSliceImpl(todos, pageable); + } + + @Override + public Integer countTodoByUserIdAndTodoTeamIdQuery(Long userId, Long todoTeamId) { + final QTodo todo = QTodo.todo; + final QRegister register = QRegister.register; + final QAssign assign = QAssign.assign; + return jpaQueryFactory.select(todo.count()) + .from(todo) + .join(register).on(register.userId.eq(userId).and(register.todoTeam.id.eq(todoTeamId))) + .join(assign).on(assign.register.id.eq(register.id)) + .where(assign.todo.eq(todo)) + .fetchOne().intValue(); + } + + @Override + public Integer countTodoByUserIdQuery(Long userId) { + final QTodo todo = QTodo.todo; + final QRegister register = QRegister.register; + final QAssign assign = QAssign.assign; + return jpaQueryFactory.select(todo.count()) + .from(todo) + .join(register).on(register.userId.eq(userId)) + .join(assign).on(assign.register.id.eq(register.id)) + .where(assign.todo.eq(todo)) + .fetchOne().intValue(); + } + + @Override + public List findTodoListByCreatorIdAndTodoTeamIdQuery(Long creatorId, Long todoTeamId) { + final QTodo todo = QTodo.todo; + final QCategory category = todo.category; + final QTodoTeam todoTeam = category.todoTeam; + return jpaQueryFactory.select(todo) + .from(todo) + .join(category).fetchJoin() + .join(todoTeam).fetchJoin() + .where(todo.creatorId.eq(creatorId).and(todoTeam.id.eq(todoTeamId))) + .fetch(); + } + +} diff --git a/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/TodoTeamRepositoryImpl.java b/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/TodoTeamRepositoryImpl.java new file mode 100644 index 00000000..d7e4a10c --- /dev/null +++ b/Domain-Module/Todo-Module/Todo-Infrastructure/src/main/java/com/pawith/todoinfrastructure/repository/TodoTeamRepositoryImpl.java @@ -0,0 +1,43 @@ +package com.pawith.todoinfrastructure.repository; + +import com.pawith.tododomain.entity.QAssign; +import com.pawith.tododomain.entity.QRegister; +import com.pawith.tododomain.entity.QTodoTeam; +import com.pawith.tododomain.entity.TodoTeam; +import com.pawith.tododomain.repository.TodoTeamQueryRepository; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class TodoTeamRepositoryImpl implements TodoTeamQueryRepository { + private final JPAQueryFactory jpaQueryFactory; + + @Override + public List findAllByUserIdQuery(Long userId) { + final QTodoTeam todoTeam = QTodoTeam.todoTeam; + final QRegister register = QRegister.register; + return jpaQueryFactory.select(todoTeam) + .from(todoTeam) + .join(register).on(register.userId.eq(userId)) + .where(register.todoTeam.eq(todoTeam) + .and(register.isRegistered.eq(true))) + .fetch(); + } + + @Override + public TodoTeam findByTodoId(Long todoId) { + final QTodoTeam todoTeam = QTodoTeam.todoTeam; + final QRegister register = QRegister.register; + final QAssign assign = QAssign.assign; + return jpaQueryFactory.select(todoTeam) + .from(todoTeam) + .join(register).on(register.todoTeam.eq(todoTeam)) + .join(assign).on(assign.register.eq(register)) + .where(assign.todo.id.eq(todoId)) + .fetchOne(); + } + +} diff --git a/Domain-Module/Todo-Module/Todo-Presentation/src/docs/asciidoc/Todo-API.adoc b/Domain-Module/Todo-Module/Todo-Presentation/src/docs/asciidoc/Todo-API.adoc index 553b9990..f9a3d5f7 100644 --- a/Domain-Module/Todo-Module/Todo-Presentation/src/docs/asciidoc/Todo-API.adoc +++ b/Domain-Module/Todo-Module/Todo-Presentation/src/docs/asciidoc/Todo-API.adoc @@ -108,6 +108,11 @@ operation::todo-controller-test/get-todo-completion[snippets='http-request,path- operation::todo-controller-test/delete-todo[snippets='http-request,path-parameters,request-headers,http-response'] +[[Todo-삭제-및-수정-검증]] +=== Todo 삭제 및 수정 검증 + +operation::todo-controller-test/validate-delete-and-update-todo[snippets='http-request,path-parameters,request-headers,http-response,response-fields'] + [[Todo-알림-설정]] === Todo 알림 설정 @@ -134,6 +139,11 @@ operation::todo-controller-test/get-withdraw-todo-count[snippets='http-request,p === 서비스 탈퇴시 담당했던 Todo 개수 조회 operation::todo-controller-test/get-all-withdraw-todo-count[snippets='http-request,request-headers,http-response,response-fields'] + +[[Todo-담당자-수정]] +=== Todo 담당자 수정 + +operation::todo-controller-test/change-assign[snippets='http-request,path-parameters,request-headers,request-fields,http-response'] [[Category-API]] == Category API diff --git a/Domain-Module/Todo-Module/Todo-Presentation/src/main/java/com/pawith/todopresentation/CategoryController.java b/Domain-Module/Todo-Module/Todo-Presentation/src/main/java/com/pawith/todopresentation/CategoryController.java index 0a3b1686..8764ffe2 100644 --- a/Domain-Module/Todo-Module/Todo-Presentation/src/main/java/com/pawith/todopresentation/CategoryController.java +++ b/Domain-Module/Todo-Module/Todo-Presentation/src/main/java/com/pawith/todopresentation/CategoryController.java @@ -9,13 +9,16 @@ import com.pawith.todoapplication.service.CategoryCreateUseCase; import com.pawith.todoapplication.service.CategoryDeleteUseCase; import com.pawith.todoapplication.service.CategoryGetUseCase; +import java.time.LocalDate; import lombok.RequiredArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -28,8 +31,8 @@ public class CategoryController { private final CategoryCreateUseCase categoryCreateUseCase; @GetMapping("/teams/{todoTeamId}/category") - public ListResponse getCategoryList(@PathVariable Long todoTeamId){ - return categoryGetUseCase.getCategoryList(todoTeamId); + public ListResponse getCategoryList(@PathVariable Long todoTeamId, @RequestParam(value = "moveDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate moveDate){ + return categoryGetUseCase.getCategoryList(todoTeamId, moveDate); } @GetMapping("/teams/{todoTeamId}/category/manage") diff --git a/Domain-Module/Todo-Module/Todo-Presentation/src/main/java/com/pawith/todopresentation/TodoController.java b/Domain-Module/Todo-Module/Todo-Presentation/src/main/java/com/pawith/todopresentation/TodoController.java index 46aac7d1..48f4fa32 100644 --- a/Domain-Module/Todo-Module/Todo-Presentation/src/main/java/com/pawith/todopresentation/TodoController.java +++ b/Domain-Module/Todo-Module/Todo-Presentation/src/main/java/com/pawith/todopresentation/TodoController.java @@ -2,6 +2,7 @@ import com.pawith.commonmodule.response.ListResponse; import com.pawith.commonmodule.response.SliceResponse; +import com.pawith.todoapplication.dto.request.AssignChangeRequest; import com.pawith.todoapplication.dto.request.ScheduledDateChangeRequest; import com.pawith.todoapplication.dto.request.TodoCreateRequest; import com.pawith.todoapplication.dto.request.TodoDescriptionChangeRequest; @@ -27,6 +28,7 @@ public class TodoController { private final TodoChangeUseCase todoChangeUseCase; private final AssignChangeUseCase assignChangeUseCase; private final TodoDeleteUseCase todoDeleteUseCase; + private final TodoValidationUseCase todoValidationUseCase; private final TodoNotificationCreateUseCase todoNotificationCreateUseCase; private final TodoWithdrawGetUseCase todoWithdrawGetUseCase; @@ -71,6 +73,11 @@ public void putAssignStatus(@PathVariable Long todoId){ assignChangeUseCase.changeAssignStatus(todoId); } + @PutMapping("/todos/{todoId}/assign") + public void putAssign(@PathVariable Long todoId, @RequestBody AssignChangeRequest assignChangeRequest){ + assignChangeUseCase.changeAssign(todoId, assignChangeRequest); + } + @GetMapping("/todos/{todoId}/completion") public TodoCompletionResponse getTodoCompletion(@PathVariable Long todoId){ return todoGetUseCase.getTodoCompletion(todoId); @@ -81,6 +88,11 @@ public void deleteTodoById(@PathVariable Long todoId){ todoDeleteUseCase.deleteTodoByTodoId(todoId); } + @GetMapping("/{todoTeamId}/todos/{todoId}/validate") + public TodoValidateResponse validateDeleteAndUpdateTodo(@PathVariable Long todoTeamId, @PathVariable Long todoId){ + return todoValidationUseCase.validateDeleteAndUpdateTodoByTodoId(todoTeamId, todoId); + } + @PostMapping("/todos/{todoId}/assign/notification") public void postTodoAssignAlarm(@PathVariable Long todoId, @RequestParam("notificationTime") @DateTimeFormat(iso = DateTimeFormat.ISO.TIME) LocalTime notificationTime){ todoNotificationCreateUseCase.createNotification(todoId, notificationTime); diff --git a/Domain-Module/Todo-Module/Todo-Presentation/src/test/java/com/pawith/todopresentation/CategoryControllerTest.java b/Domain-Module/Todo-Module/Todo-Presentation/src/test/java/com/pawith/todopresentation/CategoryControllerTest.java index cfbab387..da59a9a9 100644 --- a/Domain-Module/Todo-Module/Todo-Presentation/src/test/java/com/pawith/todopresentation/CategoryControllerTest.java +++ b/Domain-Module/Todo-Module/Todo-Presentation/src/test/java/com/pawith/todopresentation/CategoryControllerTest.java @@ -11,6 +11,7 @@ import com.pawith.todoapplication.service.CategoryCreateUseCase; import com.pawith.todoapplication.service.CategoryDeleteUseCase; import com.pawith.todoapplication.service.CategoryGetUseCase; +import java.time.LocalDate; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -58,8 +59,10 @@ public void getCategoryList() throws Exception { // given final Long testTeamId = FixtureMonkeyUtils.getConstructBasedFixtureMonkey().giveMeOne(Long.class); final List categoryListResponses = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMe(CategoryInfoResponse.class, 2); - given(categoryGetUseCase.getCategoryList(testTeamId)).willReturn(ListResponse.from(categoryListResponses)); + final LocalDate mockMoveDate = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(LocalDate.class); + given(categoryGetUseCase.getCategoryList(testTeamId, mockMoveDate)).willReturn(ListResponse.from(categoryListResponses)); MockHttpServletRequestBuilder request = get(CATEGORY_REQUEST_URL + "/{teamId}/category", testTeamId) + .queryParam("moveDate", mockMoveDate.toString()) .header("Authorization", "Bearer accessToken"); // when ResultActions result = mvc.perform(request); @@ -72,6 +75,9 @@ public void getCategoryList() throws Exception { pathParameters( parameterWithName("teamId").description("TodoTeam의 Id") ), + queryParameters( + parameterWithName("moveDate").description("달력에서 이동하는 날짜(LocalDate)") + ), responseFields( fieldWithPath("content[].categoryId").description("Category의 Id"), fieldWithPath("content[].categoryName").description("Category의 이름") diff --git a/Domain-Module/Todo-Module/Todo-Presentation/src/test/java/com/pawith/todopresentation/TodoControllerTest.java b/Domain-Module/Todo-Module/Todo-Presentation/src/test/java/com/pawith/todopresentation/TodoControllerTest.java index 60717f9a..bb117fdc 100644 --- a/Domain-Module/Todo-Module/Todo-Presentation/src/test/java/com/pawith/todopresentation/TodoControllerTest.java +++ b/Domain-Module/Todo-Module/Todo-Presentation/src/test/java/com/pawith/todopresentation/TodoControllerTest.java @@ -4,6 +4,7 @@ import com.pawith.commonmodule.response.ListResponse; import com.pawith.commonmodule.response.SliceResponse; import com.pawith.commonmodule.utils.FixtureMonkeyUtils; +import com.pawith.todoapplication.dto.request.AssignChangeRequest; import com.pawith.todoapplication.dto.request.ScheduledDateChangeRequest; import com.pawith.todoapplication.dto.request.TodoCreateRequest; import com.pawith.todoapplication.dto.request.TodoDescriptionChangeRequest; @@ -51,6 +52,8 @@ public class TodoControllerTest extends BaseRestDocsTest { @MockBean private TodoDeleteUseCase todoDeleteUseCase; @MockBean + private TodoValidationUseCase todoValidationUseCase; + @MockBean private TodoNotificationCreateUseCase todoNotificationCreateUseCase; @MockBean private TodoWithdrawGetUseCase todoWithdrawGetUseCase; @@ -122,7 +125,7 @@ void getTodos() throws Exception{ @DisplayName("todo 등록 API 테스트") void postTodo() throws Exception { //given - final TodoCreateRequest todoCreateRequest = FixtureMonkeyUtils.getConstructBasedFixtureMonkey().giveMeOne(TodoCreateRequest.class); + final TodoCreateRequest todoCreateRequest = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeOne(TodoCreateRequest.class); MockHttpServletRequestBuilder request = post(TODO_REQUEST_URL+"/todos") .content(objectMapper.writeValueAsString(todoCreateRequest)) .contentType(MediaType.APPLICATION_JSON) @@ -137,6 +140,7 @@ void postTodo() throws Exception { ), requestFields( fieldWithPath("categoryId").description("todo 등록 카테고리 id"), + fieldWithPath("todoTeamId").description("todo 등록 팀 id"), fieldWithPath("description").description("todo 설명"), fieldWithPath("scheduledDate").description("todo 완료 기한 날짜"), fieldWithPath("registerIds[]").description("todo를 할당할 사용자 registerId들") @@ -339,6 +343,35 @@ void deleteTodo() throws Exception { )); } + @Test + @DisplayName("투두 삭제 및 수정 검증 API 테스트") + void validateDeleteAndUpdateTodo() throws Exception { + //given + final Long testTodoTeamId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final Long testTodoId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final TodoValidateResponse todoValidateResponse = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeOne(TodoValidateResponse.class); + + given(todoValidationUseCase.validateDeleteAndUpdateTodoByTodoId(any(), any())).willReturn(todoValidateResponse); + MockHttpServletRequestBuilder request = get(TODO_REQUEST_URL + "/{todoTeamId}/todos/{todoId}/validate", testTodoTeamId, testTodoId) + .header("Authorization", "Bearer accessToken"); + //when + ResultActions result = mvc.perform(request); + //then + result.andExpect(status().isOk()) + .andDo(resultHandler.document( + requestHeaders( + headerWithName("Authorization").description("access 토큰") + ), + pathParameters( + parameterWithName("todoTeamId").description("투두 팀 Id"), + parameterWithName("todoId").description("투두 항목 Id") + ), + responseFields( + fieldWithPath("isNotValidate").description("투두 삭제 및 수정 검증 여부 true면 삭제 및 수정 불가능") + ) + )); + } + @Test @DisplayName("투두 알림 생성 API 테스트") void postNotification() throws Exception{ @@ -493,4 +526,30 @@ void getAllWithdrawTodoCount() throws Exception { )); } + @Test + @DisplayName("투두 담당자 변경 API 테스트") + void changeAssign() throws Exception { + //given + final Long testTodoId = FixtureMonkeyUtils.getJavaTypeBasedFixtureMonkey().giveMeOne(Long.class); + final AssignChangeRequest assignChangeRequest = FixtureMonkeyUtils.getReflectionbasedFixtureMonkey().giveMeOne(AssignChangeRequest.class); + MockHttpServletRequestBuilder request = put(TODO_REQUEST_URL + "/todos/{todoId}/assign", testTodoId) + .content(objectMapper.writeValueAsString(assignChangeRequest)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", "Bearer accessToken"); + //when + ResultActions result = mvc.perform(request); + //then + result.andExpect(status().isOk()) + .andDo(resultHandler.document( + requestHeaders( + headerWithName("Authorization").description("access 토큰") + ), + pathParameters( + parameterWithName("todoId").description("투두 항목 Id") + ), + requestFields( + fieldWithPath("registerIds[]").description("변경할 담당자들의 registerId") + ) + )); + } } 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 38fcf401..7bf9a38e 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 @@ -30,6 +30,7 @@ public class User extends BaseEntity { private Long id; private String nickname; + @Column(name="email", nullable = false) private String email; private String imageUrl; @Enumerated(EnumType.STRING) diff --git a/Event/build.gradle b/Event/build.gradle new file mode 100644 index 00000000..70d4a048 --- /dev/null +++ b/Event/build.gradle @@ -0,0 +1,4 @@ +dependencies { + implementation project(":Common-Module") + implementation project(":Domain-Module:Todo-Module:Todo-Domain") +} \ No newline at end of file diff --git a/Event/src/main/java/com/pawith/event/christmas/ChristmasEventCloseHandler.java b/Event/src/main/java/com/pawith/event/christmas/ChristmasEventCloseHandler.java new file mode 100644 index 00000000..e56a9dfa --- /dev/null +++ b/Event/src/main/java/com/pawith/event/christmas/ChristmasEventCloseHandler.java @@ -0,0 +1,51 @@ +package com.pawith.event.christmas; + +import com.pawith.commonmodule.schedule.AbstractBatchSchedulingHandler; +import com.pawith.event.christmas.entity.ChristmasCategory; +import com.pawith.event.christmas.repository.ChristmasCategoryRepository; +import com.pawith.tododomain.repository.AssignRepository; +import com.pawith.tododomain.repository.CategoryRepository; +import com.pawith.tododomain.repository.TodoRepository; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Component +public class ChristmasEventCloseHandler extends AbstractBatchSchedulingHandler { + private static final Integer BATCH_SIZE = 100; + private static final String CRON_EXPRESSION = "0 0 0 26 12 *"; + + private final ChristmasCategoryRepository christmasCategoryRepository; + private final CategoryRepository categoryRepository; + private final TodoRepository todoRepository; + private final AssignRepository assignRepository; + + public ChristmasEventCloseHandler(ChristmasCategoryRepository christmasCategoryRepository, CategoryRepository categoryRepository, TodoRepository todoRepository, AssignRepository assignRepository) { + super(BATCH_SIZE, CRON_EXPRESSION); + this.christmasCategoryRepository = christmasCategoryRepository; + this.categoryRepository = categoryRepository; + this.todoRepository = todoRepository; + this.assignRepository = assignRepository; + } + + + @Override + protected List extractBatchData(Pageable pageable) { + return christmasCategoryRepository.findAll(pageable).getContent(); + } + + @Override + @Transactional + protected void processBatch(List executionResult) { + executionResult + .forEach(christmasCategory -> { + final Long categoryId = christmasCategory.getCategoryId(); + christmasCategoryRepository.deleteById(christmasCategory.getId()); + assignRepository.deleteAllByCategoryIdQuery(categoryId); + todoRepository.deleteAllByCategoryIdQuery(categoryId); + categoryRepository.deleteById(categoryId); + }); + } +} diff --git a/Event/src/main/java/com/pawith/event/christmas/ChristmasEventListener.java b/Event/src/main/java/com/pawith/event/christmas/ChristmasEventListener.java new file mode 100644 index 00000000..0e6eebc6 --- /dev/null +++ b/Event/src/main/java/com/pawith/event/christmas/ChristmasEventListener.java @@ -0,0 +1,27 @@ +package com.pawith.event.christmas; + +import com.pawith.commonmodule.event.ChristmasEvent; +import com.pawith.tododomain.entity.TodoTeam; +import com.pawith.tododomain.service.TodoTeamQueryService; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ChristmasEventListener { + + private final ChristmasEventService christmasEventService; + private final TodoTeamQueryService todoTeamQueryService; + + @EventListener + public void handleEventCreateNewTodoTeam(ChristmasEvent.ChristmasEventCreateNewTodoTeam event) { + final TodoTeam todoTeam = todoTeamQueryService.findTodoTeamById(event.todoTeamId()); + christmasEventService.publishEvent(todoTeam); + } + + @EventListener + public void handleEventCreateNewRegister(ChristmasEvent.ChristmasEventCreateNewRegister event) { + christmasEventService.addNewRegisterAtChristmasEventTodo(event.todoTeamId(), event.userId()); + } +} diff --git a/Event/src/main/java/com/pawith/event/christmas/ChristmasEventPublishHandler.java b/Event/src/main/java/com/pawith/event/christmas/ChristmasEventPublishHandler.java new file mode 100644 index 00000000..2449b6ce --- /dev/null +++ b/Event/src/main/java/com/pawith/event/christmas/ChristmasEventPublishHandler.java @@ -0,0 +1,37 @@ +package com.pawith.event.christmas; + +import com.pawith.commonmodule.schedule.AbstractBatchSchedulingHandler; +import com.pawith.tododomain.entity.TodoTeam; +import com.pawith.tododomain.repository.TodoTeamRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Slf4j +@Component +public class ChristmasEventPublishHandler extends AbstractBatchSchedulingHandler { + private static final Integer BATCH_SIZE = 100; + private static final String CRON_EXPRESSION = "0 0 0 23 12 *"; + private final TodoTeamRepository todoTeamRepository; + private final ChristmasEventService christmasEventService; + public ChristmasEventPublishHandler(TodoTeamRepository todoTeamRepository, ChristmasEventService christmasEventService) { + super(BATCH_SIZE, CRON_EXPRESSION); + this.todoTeamRepository = todoTeamRepository; + this.christmasEventService = christmasEventService; + } + + @Override + protected List extractBatchData(Pageable pageable) { + return todoTeamRepository.findAll(pageable).getContent(); + } + + @Override + @Transactional + protected void processBatch(List executionResult) { + executionResult.forEach(christmasEventService::publishEvent); + } + +} diff --git a/Event/src/main/java/com/pawith/event/christmas/ChristmasEventService.java b/Event/src/main/java/com/pawith/event/christmas/ChristmasEventService.java new file mode 100644 index 00000000..b71a5e06 --- /dev/null +++ b/Event/src/main/java/com/pawith/event/christmas/ChristmasEventService.java @@ -0,0 +1,86 @@ +package com.pawith.event.christmas; + +import com.pawith.event.christmas.entity.ChristmasCategory; +import com.pawith.event.christmas.repository.ChristmasCategoryRepository; +import com.pawith.tododomain.entity.*; +import com.pawith.tododomain.repository.*; +import com.pawith.tododomain.service.RegisterQueryService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +@Component +@RequiredArgsConstructor +public class ChristmasEventService { + private static final String EVENT_CATEGORY_NAME = "크리스마스"; + private static final String EVENT_TODO_DESCRIPTION = "포잇스마스 이벤트 참여하기"; + private static final List EVENT_TODO_SCHEDULED_DATE_LIST = List.of( + LocalDate.of(2023, 12, 23), + LocalDate.of(2023, 12, 24), + LocalDate.of(2023, 12, 25) + ); + + + private static final Long EVENT_CREATOR_ID = -1L; + private final CategoryRepository categoryRepository; + private final ChristmasCategoryRepository christmasCategoryRepository; + private final RegisterRepository registerRepository; + private final TodoRepository todoRepository; + private final AssignRepository assignRepository; + private final RegisterQueryService registerQueryService; + + @Transactional + public void publishEvent(TodoTeam todoTeam){ + final Category category = Category.builder() + .todoTeam(todoTeam) + .name(EVENT_CATEGORY_NAME) + .categoryStatus(CategoryStatus.ON) + .build(); + final Category saved = categoryRepository.save(category); + final ChristmasCategory christmasCategory = new ChristmasCategory(saved.getId()); + christmasCategoryRepository.save(christmasCategory); + + final List allRegisters = registerRepository.findAllByTodoTeamId(todoTeam.getId()); + final List eventTodoList = new ArrayList<>(); + final List eventAssignList = new ArrayList<>(); + EVENT_TODO_SCHEDULED_DATE_LIST + .forEach(eventDate -> { + final Todo todo = Todo.builder() + .category(saved) + .creatorId(EVENT_CREATOR_ID) + .description(EVENT_TODO_DESCRIPTION) + .scheduledDate(eventDate) + .build(); + eventTodoList.add(todo); + final List assignList = allRegisters.stream() + .filter(Register::isRegistered) + .map(register -> Assign.builder() + .todo(todo) + .register(register) + .build()).toList(); + eventAssignList.addAll(assignList); + } + ); + todoRepository.saveAll(eventTodoList); + assignRepository.saveAll(eventAssignList); + } + + @Transactional + public void addNewRegisterAtChristmasEventTodo(Long todoTeamId, Long userId){ + final Register register = registerQueryService.findRegisterByTodoTeamIdAndUserId(todoTeamId, userId); + final TodoTeam todoTeam = register.getTodoTeam(); + todoRepository.findTodoListByCreatorIdAndTodoTeamIdQuery(EVENT_CREATOR_ID, todoTeam.getId()) + .forEach(todo -> { + final Assign assign = Assign.builder() + .todo(todo) + .register(register) + .build(); + assignRepository.save(assign); + }); + } + +} diff --git a/Event/src/main/java/com/pawith/event/christmas/entity/ChristmasCategory.java b/Event/src/main/java/com/pawith/event/christmas/entity/ChristmasCategory.java new file mode 100644 index 00000000..7be92164 --- /dev/null +++ b/Event/src/main/java/com/pawith/event/christmas/entity/ChristmasCategory.java @@ -0,0 +1,24 @@ +package com.pawith.event.christmas.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ChristmasCategory { + @Id@GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private Long categoryId; + + + public ChristmasCategory(Long categoryId) { + this.categoryId = categoryId; + } +} diff --git a/Event/src/main/java/com/pawith/event/christmas/repository/ChristmasCategoryRepository.java b/Event/src/main/java/com/pawith/event/christmas/repository/ChristmasCategoryRepository.java new file mode 100644 index 00000000..5774f6b2 --- /dev/null +++ b/Event/src/main/java/com/pawith/event/christmas/repository/ChristmasCategoryRepository.java @@ -0,0 +1,7 @@ +package com.pawith.event.christmas.repository; + +import com.pawith.event.christmas.entity.ChristmasCategory; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ChristmasCategoryRepository extends JpaRepository { +} diff --git a/Log-Module/Log-Aop/build.gradle b/Log-Module/Log-Aop/build.gradle deleted file mode 100644 index 543792c3..00000000 --- a/Log-Module/Log-Aop/build.gradle +++ /dev/null @@ -1,3 +0,0 @@ -dependencies { - implementation 'org.springframework.boot:spring-boot-starter-aop' -} diff --git a/Log-Module/Log-Aop/src/main/java/com/pawith/log/aop/LogTrace.java b/Log-Module/Log-Aop/src/main/java/com/pawith/log/aop/LogTrace.java deleted file mode 100644 index 705a59d3..00000000 --- a/Log-Module/Log-Aop/src/main/java/com/pawith/log/aop/LogTrace.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.pawith.log.aop; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.pawith.commonmodule.exception.BusinessException; -import lombok.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; - -import java.util.UUID; - -@Component -@RequiredArgsConstructor -public class LogTrace { - - private ThreadLocal threadId = new ThreadLocal<>(); - private static final Logger logger = LoggerFactory.getLogger(LogTrace.class); - - private final ObjectMapper objectMapper; - - private static final String THREAD_ID_REMOVE_END_POINT = "Controller"; - - - public TraceStatus start(String fullClassName, String method) { - syncTrace(); - String id = threadId.get(); - long startTime = System.currentTimeMillis(); - int lastDotIndex = fullClassName.lastIndexOf("."); - String className = fullClassName.substring(lastDotIndex + 1); - return new TraceStatus(id, startTime, className, method, className.contains(THREAD_ID_REMOVE_END_POINT)); - } - - @SneakyThrows - public void end(TraceStatus traceStatus) { - final LogFormat logFormat = LogFormat.createLogFormat(traceStatus); - final String log = objectMapper.writeValueAsString(logFormat); - if (logFormat.getExecuteTime() > 1000) { - logger.warn(log); - } else { - logger.info(log); - } - clearTheadId(traceStatus); - } - - @SneakyThrows - public void exception(Exception exception, TraceStatus traceStatus){ - final LogFormat errorLogFormat = LogFormat.createErrorLogFormat(traceStatus, exception); - final String errorLog = objectMapper.writeValueAsString(errorLogFormat); - logger.error(errorLog); - clearTheadId(traceStatus); - } - - private void syncTrace() { - String id = threadId.get(); - if (id == null) { - threadId.set(createThreadId()); - } - } - - private void clearTheadId(TraceStatus traceStatus) { - if(traceStatus.getIsEndPoint()){ - threadId.remove(); - } - } - - private String createThreadId() { - return UUID.randomUUID().toString().substring(0, 8); - } - - @Getter - @Builder - @JsonInclude(JsonInclude.Include.NON_NULL) - @AllArgsConstructor(access = AccessLevel.PRIVATE) - private static class LogFormat{ - private final String threadId; - private final String className; - private final String methodName; - private final Long executeTime; - private final Integer errorCode; - private final String errorMessage; - private final Class errorClass; - private final StackTraceElement[] errorStackTrace; - - public static LogFormat createLogFormat(TraceStatus traceStatus){ - return LogFormat.builder() - .threadId(traceStatus.getThreadId()) - .className(traceStatus.getClassName()) - .methodName(traceStatus.getMethodName()) - .executeTime(System.currentTimeMillis()-traceStatus.getStartTime()) - .build(); - } - - public static LogFormat createErrorLogFormat(TraceStatus traceStatus, Exception exception){ - LogFormatBuilder logFormatBuilder = LogFormat.builder() - .threadId(traceStatus.getThreadId()) - .className(traceStatus.getClassName()) - .methodName(traceStatus.getMethodName()); - if(exception instanceof BusinessException){ - return logFormatBuilder - .errorCode(((BusinessException) exception).getErrorCode()) - .errorMessage(((BusinessException) exception).getError().getMessage()) - .build(); - }else{ - return logFormatBuilder - .errorClass(exception.getClass()) - .errorMessage(exception.getMessage()) - .errorStackTrace(exception.getStackTrace()) - .build(); - } - } - } -} \ No newline at end of file diff --git a/Log-Module/build.gradle b/Log-Module/build.gradle index dda24ca9..1d9365e4 100644 --- a/Log-Module/build.gradle +++ b/Log-Module/build.gradle @@ -1,5 +1,4 @@ -subprojects { - dependencies { - implementation project(":Common-Module") - } -} \ No newline at end of file +dependencies { + implementation project(":Common-Module") + implementation 'org.springframework.boot:spring-boot-starter-aop' +} diff --git a/Log-Module/Log-Aop/src/main/java/com/pawith/log/aop/LogAspect.java b/Log-Module/src/main/java/com/pawith/log/aop/LogAspect.java similarity index 100% rename from Log-Module/Log-Aop/src/main/java/com/pawith/log/aop/LogAspect.java rename to Log-Module/src/main/java/com/pawith/log/aop/LogAspect.java diff --git a/Log-Module/src/main/java/com/pawith/log/aop/LogFormat.java b/Log-Module/src/main/java/com/pawith/log/aop/LogFormat.java new file mode 100644 index 00000000..3d875bb8 --- /dev/null +++ b/Log-Module/src/main/java/com/pawith/log/aop/LogFormat.java @@ -0,0 +1,51 @@ +package com.pawith.log.aop; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.pawith.commonmodule.exception.BusinessException; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class LogFormat { + private final String threadId; + private final String className; + private final String methodName; + private final Long executeTime; + private final Integer errorCode; + private final String errorMessage; + private final Class errorClass; + private final StackTraceElement[] errorStackTrace; + + public static LogFormat createLogFormat(TraceStatus traceStatus) { + return LogFormat.builder() + .threadId(traceStatus.getThreadId()) + .className(traceStatus.getClassName()) + .methodName(traceStatus.getMethodName()) + .executeTime(System.currentTimeMillis() - traceStatus.getStartTime()) + .build(); + } + + public static LogFormat createErrorLogFormat(TraceStatus traceStatus, Exception exception) { + LogFormat.LogFormatBuilder logFormatBuilder = LogFormat.builder() + .threadId(traceStatus.getThreadId()) + .className(traceStatus.getClassName()) + .methodName(traceStatus.getMethodName()); + if (exception instanceof BusinessException be) { + return logFormatBuilder + .errorCode(be.getErrorCode()) + .errorMessage(be.getError().getMessage()) + .build(); + } else { + return logFormatBuilder + .errorClass(exception.getClass()) + .errorMessage(exception.getMessage()) + .errorStackTrace(exception.getStackTrace()) + .build(); + } + } +} diff --git a/Log-Module/src/main/java/com/pawith/log/aop/LogTrace.java b/Log-Module/src/main/java/com/pawith/log/aop/LogTrace.java new file mode 100644 index 00000000..79a342c7 --- /dev/null +++ b/Log-Module/src/main/java/com/pawith/log/aop/LogTrace.java @@ -0,0 +1,64 @@ +package com.pawith.log.aop; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.UUID; + +@Slf4j +@Component +@RequiredArgsConstructor +public class LogTrace { + + private static final ThreadLocal threadId = new ThreadLocal<>(); + private static final Integer WARN_REQUEST_TIME = 1000; + + private final ObjectMapper objectMapper; + + + public TraceStatus start(String fullClassName, String method) { + String id = threadId.get(); + long startTime = System.currentTimeMillis(); + int lastDotIndex = fullClassName.lastIndexOf("."); + String className = fullClassName.substring(lastDotIndex + 1); + return new TraceStatus(id, startTime, className, method); + } + + @SneakyThrows + public void end(TraceStatus traceStatus) { + final LogFormat logFormat = LogFormat.createLogFormat(traceStatus); + final String logMessage = objectMapper.writeValueAsString(logFormat); + if (logFormat.getExecuteTime() >= WARN_REQUEST_TIME) { + log.warn(logMessage); + } else { + log.info(logMessage); + } + } + + @SneakyThrows + public void exception(Exception exception, TraceStatus traceStatus){ + final LogFormat errorLogFormat = LogFormat.createErrorLogFormat(traceStatus, exception); + final String errorLog = objectMapper.writeValueAsString(errorLogFormat); + log.error(errorLog); + } + + public void configThreadId() { + threadId.set(createThreadId()); + } + + public void clearTheadId() { + threadId.remove(); + } + + public String getThreadId() { + return threadId.get(); + } + + private String createThreadId() { + return UUID.randomUUID().toString().substring(0, 8); + } + +} \ No newline at end of file diff --git a/Log-Module/Log-Aop/src/main/java/com/pawith/log/aop/Pointcuts.java b/Log-Module/src/main/java/com/pawith/log/aop/Pointcuts.java similarity index 100% rename from Log-Module/Log-Aop/src/main/java/com/pawith/log/aop/Pointcuts.java rename to Log-Module/src/main/java/com/pawith/log/aop/Pointcuts.java diff --git a/Log-Module/Log-Aop/src/main/java/com/pawith/log/aop/TraceStatus.java b/Log-Module/src/main/java/com/pawith/log/aop/TraceStatus.java similarity index 91% rename from Log-Module/Log-Aop/src/main/java/com/pawith/log/aop/TraceStatus.java rename to Log-Module/src/main/java/com/pawith/log/aop/TraceStatus.java index 6818782b..2e48e1ab 100644 --- a/Log-Module/Log-Aop/src/main/java/com/pawith/log/aop/TraceStatus.java +++ b/Log-Module/src/main/java/com/pawith/log/aop/TraceStatus.java @@ -12,5 +12,4 @@ public class TraceStatus { private Long startTime; private String className; private String methodName; - private Boolean isEndPoint; } diff --git a/Log-Module/src/main/java/com/pawith/log/filter/LogThreadIdHandleFilter.java b/Log-Module/src/main/java/com/pawith/log/filter/LogThreadIdHandleFilter.java new file mode 100644 index 00000000..68721117 --- /dev/null +++ b/Log-Module/src/main/java/com/pawith/log/filter/LogThreadIdHandleFilter.java @@ -0,0 +1,51 @@ +package com.pawith.log.filter; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.pawith.log.aop.LogTrace; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Slf4j +@Order(Integer.MIN_VALUE) +@Component +@RequiredArgsConstructor +public class LogThreadIdHandleFilter implements Filter { + + private final ObjectMapper objectMapper; + private final LogTrace logTrace; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + Filter.super.init(filterConfig); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + logTrace.configThreadId(); + log.info(buildRequestInfoMessage((HttpServletRequest) request)); + chain.doFilter(request, response); + logTrace.clearTheadId(); + } + + private String buildRequestInfoMessage(HttpServletRequest request) throws JsonProcessingException { + final RequestInfoFormat requestInfoFormat = RequestInfoFormat.builder() + .threadId(logTrace.getThreadId()) + .url(request.getRequestURI()) + .method(request.getMethod()) +// .ip(httpServletRequest.getRemoteAddr()) + .build(); + return objectMapper.writeValueAsString(requestInfoFormat); + } + + @Override + public void destroy() { + Filter.super.destroy(); + } +} diff --git a/Log-Module/src/main/java/com/pawith/log/filter/RequestInfoFormat.java b/Log-Module/src/main/java/com/pawith/log/filter/RequestInfoFormat.java new file mode 100644 index 00000000..bfdcbb14 --- /dev/null +++ b/Log-Module/src/main/java/com/pawith/log/filter/RequestInfoFormat.java @@ -0,0 +1,18 @@ +package com.pawith.log.filter; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class RequestInfoFormat { + private final String threadId; + private final String url; + private final String method; + private final String ip; +} diff --git a/build.gradle b/build.gradle index bbc774d1..3998b20c 100644 --- a/build.gradle +++ b/build.gradle @@ -22,4 +22,6 @@ apply from: 'gradle/lombok.gradle' apply from: 'gradle/test.gradle' apply from: 'gradle/restdocs.gradle' //apply from: 'gradle/jacoco.gradle' -apply from: 'gradle/fcm.gradle' \ No newline at end of file +apply from: 'gradle/fcm.gradle' +apply from: 'gradle/externallib.gradle' +apply from: 'gradle/querydsl.gradle' \ No newline at end of file diff --git a/gradle/externallib.gradle b/gradle/externallib.gradle new file mode 100644 index 00000000..3345f792 --- /dev/null +++ b/gradle/externallib.gradle @@ -0,0 +1,5 @@ +subprojects { + dependencies { + implementation 'net.jodah:expiringmap:0.5.11' + } +} \ No newline at end of file diff --git a/gradle/querydsl.gradle b/gradle/querydsl.gradle new file mode 100644 index 00000000..759a9088 --- /dev/null +++ b/gradle/querydsl.gradle @@ -0,0 +1,60 @@ +subprojects { + buildscript { + ext { + queryDslVersion = "5.0.0" + } + } + + configurations { + compileOnly { + extendsFrom annotationProcessor + } +// querydsl.extendsFrom compileClasspath + } + + repositories { + mavenCentral() + } + + dependencies { + //querydsl 설정 추가 + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + implementation "com.querydsl:querydsl-core" + implementation "com.querydsl:querydsl-collections" + annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + } + + // Querydsl 설정부 + def generated = 'src/main/generated' + + + tasks.withType(JavaCompile) { + options.getGeneratedSourceOutputDirectory().set(file(generated)) + } + + sourceSets { + main{ + java{ + srcDirs 'src/main/java' + srcDirs generated + } + } + test{ + java{ + srcDirs 'src/test/java' + } + } + } + + + + +// gradle clean 시에 QClass 디렉토리 삭제 + clean { + delete file(generated) + } + + +} \ No newline at end of file diff --git a/gradle/spring.gradle b/gradle/spring.gradle index 9125430a..0579c9d9 100644 --- a/gradle/spring.gradle +++ b/gradle/spring.gradle @@ -42,6 +42,9 @@ subprojects { implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' // redis // implementation 'org.springframework.boot:spring-boot-starter-data-redis' + + // spring jdbc + implementation 'org.springframework.boot:spring-boot-starter-jdbc' } dependencyManagement { diff --git a/settings.gradle b/settings.gradle index 8df28c47..b1639245 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,7 +3,6 @@ rootProject.name = 'pawith' include 'Api-Module' include 'Log-Module' -include 'Log-Module:Log-Aop' include 'Domain-Module' @@ -16,6 +15,8 @@ include 'Domain-Module:Todo-Module' include 'Domain-Module:Todo-Module:Todo-Domain' include 'Domain-Module:Todo-Module:Todo-Application' include 'Domain-Module:Todo-Module:Todo-Presentation' +include 'Domain-Module:Todo-Module:Todo-Infrastructure' + include 'Domain-Module:Auth-Module' include 'Domain-Module:Auth-Module:Auth-Presentation' @@ -26,4 +27,6 @@ include 'Domain-Module:Auth-Module:Auth-Domain' include 'Common-Module' include 'Image-Module' -include 'Alarm-Module' \ No newline at end of file +include 'Alarm-Module' + +include 'Event' \ No newline at end of file