From 843a41aaaed8417248d12c9164417f0d1d331127 Mon Sep 17 00:00:00 2001 From: soundbar91 Date: Fri, 2 Jan 2026 15:57:12 +0900 Subject: [PATCH 01/15] =?UTF-8?q?build:=20Slack=20API=20Client=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 8b278865..9fdff6a4 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,7 @@ dependencies { // notification implementation 'com.github.maricn:logback-slack-appender:1.4.0' implementation 'net.logstash.logback:logstash-logback-encoder:8.0' + implementation 'com.slack.api:slack-api-client:1.44.2' // querydsl implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' From 059abb3ddcafadca980dd51c21cfd885fc90141f Mon Sep 17 00:00:00 2001 From: soundbar91 Date: Fri, 2 Jan 2026 16:27:12 +0900 Subject: [PATCH 02/15] =?UTF-8?q?feat:=20UserWithdrawEvent=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agit/konect/domain/user/event/UserWithdrawEvent.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/gg/agit/konect/domain/user/event/UserWithdrawEvent.java diff --git a/src/main/java/gg/agit/konect/domain/user/event/UserWithdrawEvent.java b/src/main/java/gg/agit/konect/domain/user/event/UserWithdrawEvent.java new file mode 100644 index 00000000..334a870c --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/user/event/UserWithdrawEvent.java @@ -0,0 +1,7 @@ +package gg.agit.konect.domain.user.event; + +public record UserWithdrawEvent( + String email +) { + +} From 4ec9194ee0965a2eb4f90d67102d69b5f66e0026 Mon Sep 17 00:00:00 2001 From: soundbar91 Date: Fri, 2 Jan 2026 16:33:26 +0900 Subject: [PATCH 03/15] =?UTF-8?q?feat:=20SlackProperties=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../slack/config/SlackProperties.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/main/java/gg/agit/konect/infrastructure/slack/config/SlackProperties.java diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/config/SlackProperties.java b/src/main/java/gg/agit/konect/infrastructure/slack/config/SlackProperties.java new file mode 100644 index 00000000..e66b97cc --- /dev/null +++ b/src/main/java/gg/agit/konect/infrastructure/slack/config/SlackProperties.java @@ -0,0 +1,15 @@ +package gg.agit.konect.infrastructure.slack.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "slack") +public record SlackProperties( + Webhooks webhooks +) { + public record Webhooks( + String error, + String event + ) { + + } +} From a3146bb40598542e140460aaca6a2a8857e163ba Mon Sep 17 00:00:00 2001 From: soundbar91 Date: Fri, 2 Jan 2026 16:44:14 +0900 Subject: [PATCH 04/15] =?UTF-8?q?feat:=20SlackClient=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../slack/client/SlackClient.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/main/java/gg/agit/konect/infrastructure/slack/client/SlackClient.java diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/client/SlackClient.java b/src/main/java/gg/agit/konect/infrastructure/slack/client/SlackClient.java new file mode 100644 index 00000000..07a97a92 --- /dev/null +++ b/src/main/java/gg/agit/konect/infrastructure/slack/client/SlackClient.java @@ -0,0 +1,41 @@ +package gg.agit.konect.infrastructure.slack.client; + +import static org.springframework.http.MediaType.APPLICATION_JSON; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +@RequiredArgsConstructor +public class SlackClient { + + private final RestTemplate restTemplate; + + public void sendMessage(String message, String url) { + try { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(APPLICATION_JSON); + + Map payload = new HashMap<>(); + payload.put("text", message); + + HttpEntity> request = new HttpEntity<>(payload, headers); + restTemplate.postForEntity( + url, + request, + String.class + ); + } catch (Exception e) { + log.error("Slack 메시지 전송 중 오류 발생", e); + } + } +} From 3c53e4879b469e3efa1fa1c7e199b398afe8ffdd Mon Sep 17 00:00:00 2001 From: soundbar91 Date: Fri, 2 Jan 2026 16:50:33 +0900 Subject: [PATCH 05/15] =?UTF-8?q?feat:=20SlackMessageTemplate=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../slack/enums/SlackMessageTemplate.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java b/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java new file mode 100644 index 00000000..f18e2447 --- /dev/null +++ b/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java @@ -0,0 +1,22 @@ +package gg.agit.konect.infrastructure.slack.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum SlackMessageTemplate { + + USER_WITHDRAWAL( + """ + `%s님이 탈퇴하셨습니다.` + """ + ), + ; + + private final String template; + + public String format(Object... args) { + return String.format(template, args); + } +} From d2a9ef73bec1fa5993be7744e55bfb9e071bf797 Mon Sep 17 00:00:00 2001 From: soundbar91 Date: Fri, 2 Jan 2026 16:51:48 +0900 Subject: [PATCH 06/15] =?UTF-8?q?feat:=20SlackNotificationService=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/SlackNotificationService.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java b/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java new file mode 100644 index 00000000..9c4cd3b8 --- /dev/null +++ b/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java @@ -0,0 +1,22 @@ +package gg.agit.konect.infrastructure.slack.service; + +import static gg.agit.konect.infrastructure.slack.enums.SlackMessageTemplate.USER_WITHDRAWAL; + +import org.springframework.stereotype.Service; + +import gg.agit.konect.infrastructure.slack.client.SlackClient; +import gg.agit.konect.infrastructure.slack.config.SlackProperties; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class SlackNotificationService { + + private final SlackProperties slackProperties; + private final SlackClient slackClient; + + public void notifyUserWithdrawal(String email) { + String message = USER_WITHDRAWAL.format(email); + slackClient.sendMessage(message, slackProperties.webhooks().event()); + } +} From bd0b4e3a1159e6f9247d21f48b5ff352c5d0e5d0 Mon Sep 17 00:00:00 2001 From: soundbar91 Date: Fri, 2 Jan 2026 16:54:43 +0900 Subject: [PATCH 07/15] =?UTF-8?q?feat:=20UserSlackListener=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../slack/listener/UserSlackListener.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/main/java/gg/agit/konect/infrastructure/slack/listener/UserSlackListener.java diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/listener/UserSlackListener.java b/src/main/java/gg/agit/konect/infrastructure/slack/listener/UserSlackListener.java new file mode 100644 index 00000000..b2e51b5a --- /dev/null +++ b/src/main/java/gg/agit/konect/infrastructure/slack/listener/UserSlackListener.java @@ -0,0 +1,24 @@ +package gg.agit.konect.infrastructure.slack.listener; + +import static org.springframework.transaction.event.TransactionPhase.AFTER_COMMIT; + +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionalEventListener; + +import gg.agit.konect.domain.user.event.UserWithdrawEvent; +import gg.agit.konect.infrastructure.slack.service.SlackNotificationService; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class UserSlackListener { + + private final SlackNotificationService slackNotificationService; + + @Async + @TransactionalEventListener(phase = AFTER_COMMIT) + public void handleUserWithdrawn(UserWithdrawEvent event) { + slackNotificationService.notifyUserWithdrawal(event.email()); + } +} From 946afadcc72ccf1e7de9f951f9988a64f53086a2 Mon Sep 17 00:00:00 2001 From: soundbar91 Date: Fri, 2 Jan 2026 17:00:20 +0900 Subject: [PATCH 08/15] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=B0=9C=ED=96=89=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gg/agit/konect/domain/user/event/UserWithdrawEvent.java | 4 +++- .../java/gg/agit/konect/domain/user/service/UserService.java | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/gg/agit/konect/domain/user/event/UserWithdrawEvent.java b/src/main/java/gg/agit/konect/domain/user/event/UserWithdrawEvent.java index 334a870c..32e158b5 100644 --- a/src/main/java/gg/agit/konect/domain/user/event/UserWithdrawEvent.java +++ b/src/main/java/gg/agit/konect/domain/user/event/UserWithdrawEvent.java @@ -3,5 +3,7 @@ public record UserWithdrawEvent( String email ) { - + public static UserWithdrawEvent from(String email) { + return new UserWithdrawEvent(email); + } } diff --git a/src/main/java/gg/agit/konect/domain/user/service/UserService.java b/src/main/java/gg/agit/konect/domain/user/service/UserService.java index 8d295f40..576ca178 100644 --- a/src/main/java/gg/agit/konect/domain/user/service/UserService.java +++ b/src/main/java/gg/agit/konect/domain/user/service/UserService.java @@ -4,6 +4,7 @@ import java.util.List; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; @@ -20,6 +21,7 @@ import gg.agit.konect.domain.user.dto.UserInfoResponse; import gg.agit.konect.domain.user.dto.UserUpdateRequest; import gg.agit.konect.domain.user.enums.Provider; +import gg.agit.konect.domain.user.event.UserWithdrawEvent; import gg.agit.konect.domain.user.model.UnRegisteredUser; import gg.agit.konect.domain.user.model.User; import gg.agit.konect.domain.user.repository.UnRegisteredUserRepository; @@ -41,6 +43,7 @@ public class UserService { private final ClubApplyRepository clubApplyRepository; private final ChatMessageRepository chatMessageRepository; private final ChatRoomRepository chatRoomRepository; + private final ApplicationEventPublisher applicationEventPublisher; @Transactional public Integer signup(String email, Provider provider, SignupRequest request) { @@ -146,5 +149,7 @@ public void deleteUser(Integer userId) { clubApplyRepository.deleteByUserId(userId); clubMemberRepository.deleteByUserId(userId); userRepository.delete(user); + + applicationEventPublisher.publishEvent(UserWithdrawEvent.from(user.getEmail())); } } From 32f823c3f0bdea8c4b9f9e399758c9f86a207e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Fri, 2 Jan 2026 22:07:08 +0900 Subject: [PATCH 09/15] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=B0=9C=ED=96=89=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agit/konect/domain/user/event/UserRegisterEvent.java | 9 +++++++++ .../gg/agit/konect/domain/user/service/UserService.java | 2 ++ 2 files changed, 11 insertions(+) create mode 100644 src/main/java/gg/agit/konect/domain/user/event/UserRegisterEvent.java diff --git a/src/main/java/gg/agit/konect/domain/user/event/UserRegisterEvent.java b/src/main/java/gg/agit/konect/domain/user/event/UserRegisterEvent.java new file mode 100644 index 00000000..3901ad19 --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/user/event/UserRegisterEvent.java @@ -0,0 +1,9 @@ +package gg.agit.konect.domain.user.event; + +public record UserRegisterEvent( + String email +) { + public static UserRegisterEvent from(String email) { + return new UserRegisterEvent(email); + } +} diff --git a/src/main/java/gg/agit/konect/domain/user/service/UserService.java b/src/main/java/gg/agit/konect/domain/user/service/UserService.java index 576ca178..868263b7 100644 --- a/src/main/java/gg/agit/konect/domain/user/service/UserService.java +++ b/src/main/java/gg/agit/konect/domain/user/service/UserService.java @@ -21,6 +21,7 @@ import gg.agit.konect.domain.user.dto.UserInfoResponse; import gg.agit.konect.domain.user.dto.UserUpdateRequest; import gg.agit.konect.domain.user.enums.Provider; +import gg.agit.konect.domain.user.event.UserRegisterEvent; import gg.agit.konect.domain.user.event.UserWithdrawEvent; import gg.agit.konect.domain.user.model.UnRegisteredUser; import gg.agit.konect.domain.user.model.User; @@ -75,6 +76,7 @@ public Integer signup(String email, Provider provider, SignupRequest request) { unRegisteredUserRepository.delete(tempUser); + applicationEventPublisher.publishEvent(UserRegisterEvent.from(savedUser.getEmail())); return savedUser.getId(); } From 1842ec493929139aea48913fe1b13265c08e66f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Fri, 2 Jan 2026 22:07:23 +0900 Subject: [PATCH 10/15] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=8A=AC=EB=9E=99=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infrastructure/slack/enums/SlackMessageTemplate.java | 5 +++++ .../infrastructure/slack/listener/UserSlackListener.java | 7 +++++++ .../slack/service/SlackNotificationService.java | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java b/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java index f18e2447..4e0167f5 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java @@ -7,6 +7,11 @@ @RequiredArgsConstructor public enum SlackMessageTemplate { + USER_REGISTER( + """ + `%s님이 가입하셨습니다.` + """ + ), USER_WITHDRAWAL( """ `%s님이 탈퇴하셨습니다.` diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/listener/UserSlackListener.java b/src/main/java/gg/agit/konect/infrastructure/slack/listener/UserSlackListener.java index b2e51b5a..8a5329bf 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/listener/UserSlackListener.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/listener/UserSlackListener.java @@ -6,6 +6,7 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionalEventListener; +import gg.agit.konect.domain.user.event.UserRegisterEvent; import gg.agit.konect.domain.user.event.UserWithdrawEvent; import gg.agit.konect.infrastructure.slack.service.SlackNotificationService; import lombok.RequiredArgsConstructor; @@ -21,4 +22,10 @@ public class UserSlackListener { public void handleUserWithdrawn(UserWithdrawEvent event) { slackNotificationService.notifyUserWithdrawal(event.email()); } + + @Async + @TransactionalEventListener(phase = AFTER_COMMIT) + public void handleUserRegistered(UserRegisterEvent event) { + slackNotificationService.notifyUserRegister(event.email()); + } } diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java b/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java index 9c4cd3b8..800e1558 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java @@ -1,5 +1,6 @@ package gg.agit.konect.infrastructure.slack.service; +import static gg.agit.konect.infrastructure.slack.enums.SlackMessageTemplate.USER_REGISTER; import static gg.agit.konect.infrastructure.slack.enums.SlackMessageTemplate.USER_WITHDRAWAL; import org.springframework.stereotype.Service; @@ -19,4 +20,9 @@ public void notifyUserWithdrawal(String email) { String message = USER_WITHDRAWAL.format(email); slackClient.sendMessage(message, slackProperties.webhooks().event()); } + + public void notifyUserRegister(String email) { + String message = USER_REGISTER.format(email); + slackClient.sendMessage(message, slackProperties.webhooks().event()); + } } From 5b55d23019628ab2ac6ed04c5bdd7652b93d92a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Fri, 2 Jan 2026 22:11:06 +0900 Subject: [PATCH 11/15] =?UTF-8?q?chore:=20=EB=A9=94=EC=86=8C=EB=93=9C=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infrastructure/slack/listener/UserSlackListener.java | 6 +++--- .../slack/service/SlackNotificationService.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/listener/UserSlackListener.java b/src/main/java/gg/agit/konect/infrastructure/slack/listener/UserSlackListener.java index 8a5329bf..9c3f0142 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/listener/UserSlackListener.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/listener/UserSlackListener.java @@ -19,13 +19,13 @@ public class UserSlackListener { @Async @TransactionalEventListener(phase = AFTER_COMMIT) - public void handleUserWithdrawn(UserWithdrawEvent event) { - slackNotificationService.notifyUserWithdrawal(event.email()); + public void handleUserWithdraw(UserWithdrawEvent event) { + slackNotificationService.notifyUserWithdraw(event.email()); } @Async @TransactionalEventListener(phase = AFTER_COMMIT) - public void handleUserRegistered(UserRegisterEvent event) { + public void handleUserRegister(UserRegisterEvent event) { slackNotificationService.notifyUserRegister(event.email()); } } diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java b/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java index 800e1558..af0b1d6d 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java @@ -16,7 +16,7 @@ public class SlackNotificationService { private final SlackProperties slackProperties; private final SlackClient slackClient; - public void notifyUserWithdrawal(String email) { + public void notifyUserWithdraw(String email) { String message = USER_WITHDRAWAL.format(email); slackClient.sendMessage(message, slackProperties.webhooks().event()); } From 5f3b6c69493b4d5944ecd3cad08ac6b66a006cf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Sat, 3 Jan 2026 20:50:34 +0900 Subject: [PATCH 12/15] =?UTF-8?q?chore:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=ED=95=B8=EB=93=A4?= =?UTF-8?q?=EB=9F=AC=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EA=B3=BC=EA=B1=B0=ED=98=95=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agit/konect/domain/user/event/UserRegisterEvent.java | 9 --------- .../konect/domain/user/event/UserRegisteredEvent.java | 9 +++++++++ .../agit/konect/domain/user/event/UserWithdrawEvent.java | 9 --------- .../konect/domain/user/event/UserWithdrawnEvent.java | 9 +++++++++ .../gg/agit/konect/domain/user/service/UserService.java | 8 ++++---- .../infrastructure/slack/listener/UserSlackListener.java | 8 ++++---- 6 files changed, 26 insertions(+), 26 deletions(-) delete mode 100644 src/main/java/gg/agit/konect/domain/user/event/UserRegisterEvent.java create mode 100644 src/main/java/gg/agit/konect/domain/user/event/UserRegisteredEvent.java delete mode 100644 src/main/java/gg/agit/konect/domain/user/event/UserWithdrawEvent.java create mode 100644 src/main/java/gg/agit/konect/domain/user/event/UserWithdrawnEvent.java diff --git a/src/main/java/gg/agit/konect/domain/user/event/UserRegisterEvent.java b/src/main/java/gg/agit/konect/domain/user/event/UserRegisterEvent.java deleted file mode 100644 index 3901ad19..00000000 --- a/src/main/java/gg/agit/konect/domain/user/event/UserRegisterEvent.java +++ /dev/null @@ -1,9 +0,0 @@ -package gg.agit.konect.domain.user.event; - -public record UserRegisterEvent( - String email -) { - public static UserRegisterEvent from(String email) { - return new UserRegisterEvent(email); - } -} diff --git a/src/main/java/gg/agit/konect/domain/user/event/UserRegisteredEvent.java b/src/main/java/gg/agit/konect/domain/user/event/UserRegisteredEvent.java new file mode 100644 index 00000000..7f6f584c --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/user/event/UserRegisteredEvent.java @@ -0,0 +1,9 @@ +package gg.agit.konect.domain.user.event; + +public record UserRegisteredEvent( + String email +) { + public static UserRegisteredEvent from(String email) { + return new UserRegisteredEvent(email); + } +} diff --git a/src/main/java/gg/agit/konect/domain/user/event/UserWithdrawEvent.java b/src/main/java/gg/agit/konect/domain/user/event/UserWithdrawEvent.java deleted file mode 100644 index 32e158b5..00000000 --- a/src/main/java/gg/agit/konect/domain/user/event/UserWithdrawEvent.java +++ /dev/null @@ -1,9 +0,0 @@ -package gg.agit.konect.domain.user.event; - -public record UserWithdrawEvent( - String email -) { - public static UserWithdrawEvent from(String email) { - return new UserWithdrawEvent(email); - } -} diff --git a/src/main/java/gg/agit/konect/domain/user/event/UserWithdrawnEvent.java b/src/main/java/gg/agit/konect/domain/user/event/UserWithdrawnEvent.java new file mode 100644 index 00000000..847f7f20 --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/user/event/UserWithdrawnEvent.java @@ -0,0 +1,9 @@ +package gg.agit.konect.domain.user.event; + +public record UserWithdrawnEvent( + String email +) { + public static UserWithdrawnEvent from(String email) { + return new UserWithdrawnEvent(email); + } +} diff --git a/src/main/java/gg/agit/konect/domain/user/service/UserService.java b/src/main/java/gg/agit/konect/domain/user/service/UserService.java index 6a1774a3..e8167800 100644 --- a/src/main/java/gg/agit/konect/domain/user/service/UserService.java +++ b/src/main/java/gg/agit/konect/domain/user/service/UserService.java @@ -22,8 +22,8 @@ import gg.agit.konect.domain.user.dto.UserInfoResponse; import gg.agit.konect.domain.user.dto.UserUpdateRequest; import gg.agit.konect.domain.user.enums.Provider; -import gg.agit.konect.domain.user.event.UserRegisterEvent; -import gg.agit.konect.domain.user.event.UserWithdrawEvent; +import gg.agit.konect.domain.user.event.UserRegisteredEvent; +import gg.agit.konect.domain.user.event.UserWithdrawnEvent; import gg.agit.konect.domain.user.model.UnRegisteredUser; import gg.agit.konect.domain.user.model.User; import gg.agit.konect.domain.user.repository.UnRegisteredUserRepository; @@ -78,7 +78,7 @@ public Integer signup(String email, Provider provider, SignupRequest request) { unRegisteredUserRepository.delete(tempUser); - applicationEventPublisher.publishEvent(UserRegisterEvent.from(savedUser.getEmail())); + applicationEventPublisher.publishEvent(UserRegisteredEvent.from(savedUser.getEmail())); return savedUser.getId(); } @@ -155,6 +155,6 @@ public void deleteUser(Integer userId) { clubMemberRepository.deleteByUserId(userId); userRepository.delete(user); - applicationEventPublisher.publishEvent(UserWithdrawEvent.from(user.getEmail())); + applicationEventPublisher.publishEvent(UserWithdrawnEvent.from(user.getEmail())); } } diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/listener/UserSlackListener.java b/src/main/java/gg/agit/konect/infrastructure/slack/listener/UserSlackListener.java index 9c3f0142..d5bd5f77 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/listener/UserSlackListener.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/listener/UserSlackListener.java @@ -6,8 +6,8 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionalEventListener; -import gg.agit.konect.domain.user.event.UserRegisterEvent; -import gg.agit.konect.domain.user.event.UserWithdrawEvent; +import gg.agit.konect.domain.user.event.UserRegisteredEvent; +import gg.agit.konect.domain.user.event.UserWithdrawnEvent; import gg.agit.konect.infrastructure.slack.service.SlackNotificationService; import lombok.RequiredArgsConstructor; @@ -19,13 +19,13 @@ public class UserSlackListener { @Async @TransactionalEventListener(phase = AFTER_COMMIT) - public void handleUserWithdraw(UserWithdrawEvent event) { + public void handleUserWithdrawn(UserWithdrawnEvent event) { slackNotificationService.notifyUserWithdraw(event.email()); } @Async @TransactionalEventListener(phase = AFTER_COMMIT) - public void handleUserRegister(UserRegisterEvent event) { + public void handleUserRegistered(UserRegisteredEvent event) { slackNotificationService.notifyUserRegister(event.email()); } } From d68c2aa7b6e9b8777655fa45ad3a051190d81186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Sat, 3 Jan 2026 21:35:29 +0900 Subject: [PATCH 13/15] =?UTF-8?q?feat:=20=EC=9E=AC=EC=8B=9C=EB=8F=84=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../gg/agit/konect/KonectApplication.java | 2 ++ .../slack/client/SlackClient.java | 36 ++++++++++--------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/build.gradle b/build.gradle index 9fdff6a4..a207b72c 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.retry:spring-retry' // db implementation 'com.mysql:mysql-connector-j' diff --git a/src/main/java/gg/agit/konect/KonectApplication.java b/src/main/java/gg/agit/konect/KonectApplication.java index e171797e..e5f3a2f7 100644 --- a/src/main/java/gg/agit/konect/KonectApplication.java +++ b/src/main/java/gg/agit/konect/KonectApplication.java @@ -3,7 +3,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.retry.annotation.EnableRetry; +@EnableRetry @SpringBootApplication @ConfigurationPropertiesScan public class KonectApplication { diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/client/SlackClient.java b/src/main/java/gg/agit/konect/infrastructure/slack/client/SlackClient.java index 07a97a92..25fc4c85 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/client/SlackClient.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/client/SlackClient.java @@ -7,6 +7,8 @@ import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; +import org.springframework.retry.annotation.Recover; +import org.springframework.retry.annotation.Retryable; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; @@ -20,22 +22,24 @@ public class SlackClient { private final RestTemplate restTemplate; + @Retryable public void sendMessage(String message, String url) { - try { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(APPLICATION_JSON); - - Map payload = new HashMap<>(); - payload.put("text", message); - - HttpEntity> request = new HttpEntity<>(payload, headers); - restTemplate.postForEntity( - url, - request, - String.class - ); - } catch (Exception e) { - log.error("Slack 메시지 전송 중 오류 발생", e); - } + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(APPLICATION_JSON); + + Map payload = new HashMap<>(); + payload.put("text", message); + + HttpEntity> request = new HttpEntity<>(payload, headers); + restTemplate.postForEntity( + url, + request, + String.class + ); + } + + @Recover + public void sendMessageRecover(Exception e, String message, String url) { + log.error("Slack 메시지 전송 실패 : message={}, url={}", message, url, e); } } From 6bef2c9f70feb8c5a5e346d56f2f64f7e274ae05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Mon, 5 Jan 2026 22:37:22 +0900 Subject: [PATCH 14/15] =?UTF-8?q?fix:=20=ED=9A=8C=EC=9B=90=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 탈퇴 회원 보관을 위한 WithdrawnUser 엔티티 및 테이블 추가 * feat: 미납 회비 탈퇴 차단 예외 코드 추가 * refactor: 회원 탈퇴 로직 개선 (미납 회비 검증 및 탈퇴 회원 아카이빙) * feat: 탈퇴 회원 1년 후 자동 삭제 배치 스케줄러 구현 * refactor: 미납 회비 검증 로직을 ClubMember 도메인 메소드로 이동 * refactor: 불필요한 WithdrawnUser 아카이브 기능 제거 * refactor: DB CASCADE 활용으로 회원 탈퇴 로직 단순화 * chore: 주석 삭제 * refactor: CASCADE로 대체된 불필요한 deleteByUserId 메소드 제거 * fix: 미납 회비 유효성 검증 메소드 네이밍 수정 --- .../repository/ChatMessageRepository.java | 7 ----- .../chat/repository/ChatRoomRepository.java | 7 ----- .../konect/domain/club/model/ClubMember.java | 4 +++ .../club/repository/ClubApplyRepository.java | 2 -- .../club/repository/ClubMemberRepository.java | 2 -- .../CouncilNoticeReadRepository.java | 2 -- .../domain/user/service/UserService.java | 31 ++++++++++--------- .../konect/global/code/ApiResponseCode.java | 1 + 8 files changed, 22 insertions(+), 34 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/chat/repository/ChatMessageRepository.java b/src/main/java/gg/agit/konect/domain/chat/repository/ChatMessageRepository.java index b8116ccb..fa7a7b40 100644 --- a/src/main/java/gg/agit/konect/domain/chat/repository/ChatMessageRepository.java +++ b/src/main/java/gg/agit/konect/domain/chat/repository/ChatMessageRepository.java @@ -52,11 +52,4 @@ List findUnreadMessagesByChatRoomIdAndUserId( @Param("chatRoomId") Integer chatRoomId, @Param("receiverId") Integer receiverId ); - - @Modifying - @Query(""" - DELETE FROM ChatMessage cm - WHERE cm.sender.id = :userId OR cm.receiver.id = :userId - """) - void deleteByUserId(@Param("userId") Integer userId); } diff --git a/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java b/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java index 5bec2da4..12fbf8d7 100644 --- a/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java +++ b/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java @@ -40,11 +40,4 @@ public interface ChatRoomRepository extends Repository { OR (cr.sender.id = :userId2 AND cr.receiver.id = :userId1) """) Optional findByTwoUsers(@Param("userId1") Integer userId1, @Param("userId2") Integer userId2); - - @Modifying - @Query(""" - DELETE FROM ChatRoom cr - WHERE cr.sender.id = :userId OR cr.receiver.id = :userId - """) - void deleteByUserId(@Param("userId") Integer userId); } diff --git a/src/main/java/gg/agit/konect/domain/club/model/ClubMember.java b/src/main/java/gg/agit/konect/domain/club/model/ClubMember.java index 1c12ce7c..cd748285 100644 --- a/src/main/java/gg/agit/konect/domain/club/model/ClubMember.java +++ b/src/main/java/gg/agit/konect/domain/club/model/ClubMember.java @@ -58,4 +58,8 @@ public boolean isPresident() { public boolean isSameUser(Integer userId) { return this.user.getId().equals(userId); } + + public boolean hasUnpaidFee() { + return Boolean.FALSE.equals(this.isFeePaid); + } } diff --git a/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.java b/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.java index 11cba5c1..87debe1f 100644 --- a/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.java +++ b/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.java @@ -9,6 +9,4 @@ public interface ClubApplyRepository extends Repository { boolean existsByClubIdAndUserId(Integer clubId, Integer userId); ClubApply save(ClubApply clubApply); - - void deleteByUserId(Integer userId); } diff --git a/src/main/java/gg/agit/konect/domain/club/repository/ClubMemberRepository.java b/src/main/java/gg/agit/konect/domain/club/repository/ClubMemberRepository.java index 10a46254..5c1a78b2 100644 --- a/src/main/java/gg/agit/konect/domain/club/repository/ClubMemberRepository.java +++ b/src/main/java/gg/agit/konect/domain/club/repository/ClubMemberRepository.java @@ -43,6 +43,4 @@ public interface ClubMemberRepository extends Repository findByUserId(Integer userId); - - void deleteByUserId(Integer userId); } diff --git a/src/main/java/gg/agit/konect/domain/notice/repository/CouncilNoticeReadRepository.java b/src/main/java/gg/agit/konect/domain/notice/repository/CouncilNoticeReadRepository.java index 2ff8e81c..4554903c 100644 --- a/src/main/java/gg/agit/konect/domain/notice/repository/CouncilNoticeReadRepository.java +++ b/src/main/java/gg/agit/konect/domain/notice/repository/CouncilNoticeReadRepository.java @@ -26,6 +26,4 @@ WHERE NOT EXISTS ( ) """) Long countUnreadNoticesByUserId(@Param("userId") Integer userId); - - void deleteByUserId(Integer userId); } diff --git a/src/main/java/gg/agit/konect/domain/user/service/UserService.java b/src/main/java/gg/agit/konect/domain/user/service/UserService.java index e8167800..693acd17 100644 --- a/src/main/java/gg/agit/konect/domain/user/service/UserService.java +++ b/src/main/java/gg/agit/konect/domain/user/service/UserService.java @@ -1,6 +1,7 @@ package gg.agit.konect.domain.user.service; import static gg.agit.konect.global.code.ApiResponseCode.CANNOT_DELETE_CLUB_PRESIDENT; +import static gg.agit.konect.global.code.ApiResponseCode.CANNOT_DELETE_USER_WITH_UNPAID_FEE; import java.util.List; @@ -9,10 +10,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; -import gg.agit.konect.domain.chat.repository.ChatMessageRepository; -import gg.agit.konect.domain.chat.repository.ChatRoomRepository; import gg.agit.konect.domain.club.model.ClubMember; -import gg.agit.konect.domain.club.repository.ClubApplyRepository; import gg.agit.konect.domain.club.repository.ClubMemberRepository; import gg.agit.konect.domain.notice.repository.CouncilNoticeReadRepository; import gg.agit.konect.domain.studytime.service.StudyTimeQueryService; @@ -42,9 +40,6 @@ public class UserService { private final UniversityRepository universityRepository; private final ClubMemberRepository clubMemberRepository; private final CouncilNoticeReadRepository councilNoticeReadRepository; - private final ClubApplyRepository clubApplyRepository; - private final ChatMessageRepository chatMessageRepository; - private final ChatRoomRepository chatRoomRepository; private final StudyTimeQueryService studyTimeQueryService; private final ApplicationEventPublisher applicationEventPublisher; @@ -141,20 +136,28 @@ private void validatePhoneNumberDuplication(User user, UserUpdateRequest request public void deleteUser(Integer userId) { User user = userRepository.getById(userId); + validateNotClubPresident(userId); + validatePaidFees(userId); + userRepository.delete(user); + + applicationEventPublisher.publishEvent(UserWithdrawnEvent.from(user.getEmail())); + } + + private void validateNotClubPresident(Integer userId) { List clubMembers = clubMemberRepository.findByUserId(userId); boolean isPresident = clubMembers.stream().anyMatch(ClubMember::isPresident); if (isPresident) { throw CustomException.of(CANNOT_DELETE_CLUB_PRESIDENT); } + } - // TODO. 메시지 데이터 히스토리 테이블로 이관 로직 추가 - chatMessageRepository.deleteByUserId(userId); - chatRoomRepository.deleteByUserId(userId); - councilNoticeReadRepository.deleteByUserId(userId); - clubApplyRepository.deleteByUserId(userId); - clubMemberRepository.deleteByUserId(userId); - userRepository.delete(user); + private void validatePaidFees(Integer userId) { + List clubMembers = clubMemberRepository.findByUserId(userId); + boolean hasUnpaidFee = clubMembers.stream() + .anyMatch(ClubMember::hasUnpaidFee); - applicationEventPublisher.publishEvent(UserWithdrawnEvent.from(user.getEmail())); + if (hasUnpaidFee) { + throw CustomException.of(CANNOT_DELETE_USER_WITH_UNPAID_FEE); + } } } diff --git a/src/main/java/gg/agit/konect/global/code/ApiResponseCode.java b/src/main/java/gg/agit/konect/global/code/ApiResponseCode.java index 74f4b096..919edbfb 100644 --- a/src/main/java/gg/agit/konect/global/code/ApiResponseCode.java +++ b/src/main/java/gg/agit/konect/global/code/ApiResponseCode.java @@ -22,6 +22,7 @@ public enum ApiResponseCode { CANNOT_CREATE_CHAT_ROOM_WITH_SELF(HttpStatus.BAD_REQUEST, "자기 자신과는 채팅방을 만들 수 없습니다."), REQUIRED_CLUB_APPLY_ANSWER_MISSING(HttpStatus.BAD_REQUEST, "필수 가입 답변이 누락되었습니다."), CANNOT_DELETE_CLUB_PRESIDENT(HttpStatus.BAD_REQUEST, "동아리 회장인 경우 회장을 양도하고 탈퇴해야 합니다."), + CANNOT_DELETE_USER_WITH_UNPAID_FEE(HttpStatus.BAD_REQUEST, "미납 회비가 있는 경우 탈퇴할 수 없습니다."), STUDY_TIMER_NOT_RUNNING(HttpStatus.BAD_REQUEST, "실행 중인 스터디 타이머가 없습니다."), STUDY_TIMER_TIME_MISMATCH(HttpStatus.BAD_REQUEST, "스터디 타이머 시간이 유효하지 않습니다."), From cea36346228c489519e808676154f0638e91ee37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Mon, 5 Jan 2026 22:39:05 +0900 Subject: [PATCH 15/15] =?UTF-8?q?chore:=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20im?= =?UTF-8?q?port=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konect/domain/chat/repository/ChatMessageRepository.java | 1 - .../agit/konect/domain/chat/repository/ChatRoomRepository.java | 1 - 2 files changed, 2 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/chat/repository/ChatMessageRepository.java b/src/main/java/gg/agit/konect/domain/chat/repository/ChatMessageRepository.java index fa7a7b40..3f6039da 100644 --- a/src/main/java/gg/agit/konect/domain/chat/repository/ChatMessageRepository.java +++ b/src/main/java/gg/agit/konect/domain/chat/repository/ChatMessageRepository.java @@ -4,7 +4,6 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; import org.springframework.data.repository.query.Param; diff --git a/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java b/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java index 12fbf8d7..92b86323 100644 --- a/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java +++ b/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java @@ -3,7 +3,6 @@ import java.util.List; import java.util.Optional; -import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; import org.springframework.data.repository.query.Param;