From c7c304fedbe05f0dd09b39703b5a00abf4be63ae Mon Sep 17 00:00:00 2001 From: whqtker Date: Wed, 5 Aug 2026 22:57:50 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20Discord=20=EA=B2=80=EC=88=98=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EC=A0=84=EC=86=A1=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20DiscordNotifier=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../discord/DiscordNotificationType.java | 19 +++++++ .../common/discord/DiscordNotifier.java | 50 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/main/java/com/example/solidconnection/common/discord/DiscordNotificationType.java create mode 100644 src/main/java/com/example/solidconnection/common/discord/DiscordNotifier.java diff --git a/src/main/java/com/example/solidconnection/common/discord/DiscordNotificationType.java b/src/main/java/com/example/solidconnection/common/discord/DiscordNotificationType.java new file mode 100644 index 000000000..a49cb0fac --- /dev/null +++ b/src/main/java/com/example/solidconnection/common/discord/DiscordNotificationType.java @@ -0,0 +1,19 @@ +package com.example.solidconnection.common.discord; + +import lombok.Getter; + +@Getter +public enum DiscordNotificationType { + + GPA_SCORE("학점 성적"), + LANGUAGE_TEST_SCORE("어학 성적"), + MENTOR_APPLICATION("멘토 신청"), + REPORT("신고"), + ; + + private final String displayName; + + DiscordNotificationType(String displayName) { + this.displayName = displayName; + } +} diff --git a/src/main/java/com/example/solidconnection/common/discord/DiscordNotifier.java b/src/main/java/com/example/solidconnection/common/discord/DiscordNotifier.java new file mode 100644 index 000000000..c8fe306f6 --- /dev/null +++ b/src/main/java/com/example/solidconnection/common/discord/DiscordNotifier.java @@ -0,0 +1,50 @@ +package com.example.solidconnection.common.discord; + +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@Component +@RequiredArgsConstructor +@EnableAsync +@Slf4j +public class DiscordNotifier { + + private static final String ADMIN_PAGE_URL = "https://admins.solid-connection.com"; + + private final RestTemplate restTemplate; + + @Value("${discord.webhook-url:}") + private String webhookUrl; + + @Value("${spring.profiles.active:local}") + private String environment; + + @Async + public void notifyReviewRequested(DiscordNotificationType type, String applicantInfo) { + if (webhookUrl.isBlank()) { + return; + } + try { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity> request = new HttpEntity<>(Map.of("content", buildMessage(type, applicantInfo)), headers); + restTemplate.postForEntity(webhookUrl, request, Void.class); + } catch (Exception e) { + log.error("Discord 검수 알림 전송 실패. type={}, applicantInfo={}", type, applicantInfo, e); + } + } + + private String buildMessage(DiscordNotificationType type, String applicantInfo) { + return "[%s] %s 검수 요청이 등록되었습니다.\n신청자: %s\n관리자 페이지: %s" + .formatted(environment.toUpperCase(), type.getDisplayName(), applicantInfo, ADMIN_PAGE_URL); + } +} From cfb0b50135102f9d475b74676f43b1ef123cc022 Mon Sep 17 00:00:00 2001 From: whqtker Date: Wed, 5 Aug 2026 22:57:54 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=ED=95=99=EC=A0=90/=EC=96=B4?= =?UTF-8?q?=ED=95=99=20=EC=84=B1=EC=A0=81=20=EB=93=B1=EB=A1=9D=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20=EC=8B=9C=20Discord=20=EA=B2=80=EC=88=98=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EC=A0=84=EC=86=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/solidconnection/score/service/ScoreService.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/com/example/solidconnection/score/service/ScoreService.java b/src/main/java/com/example/solidconnection/score/service/ScoreService.java index 871c955f4..f8174cf6d 100644 --- a/src/main/java/com/example/solidconnection/score/service/ScoreService.java +++ b/src/main/java/com/example/solidconnection/score/service/ScoreService.java @@ -4,6 +4,8 @@ import com.example.solidconnection.application.domain.Gpa; import com.example.solidconnection.application.domain.LanguageTest; +import com.example.solidconnection.common.discord.DiscordNotificationType; +import com.example.solidconnection.common.discord.DiscordNotifier; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.s3.domain.UploadPath; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; @@ -37,6 +39,7 @@ public class ScoreService { private final LanguageTestScoreRepository languageTestScoreRepository; private final SiteUserRepository siteUserRepository; private final HomeUniversityQueryService homeUniversityQueryService; + private final DiscordNotifier discordNotifier; @Transactional public Long submitGpaScore(long siteUserId, GpaScoreRequest gpaScoreRequest, MultipartFile file) { @@ -46,6 +49,7 @@ public Long submitGpaScore(long siteUserId, GpaScoreRequest gpaScoreRequest, Mul Gpa gpa = new Gpa(gpaScoreRequest.gpa(), gpaScoreRequest.gpaCriteria(), uploadedFile.fileUrl()); GpaScore newGpaScore = new GpaScore(gpa, siteUser); GpaScore savedNewGpaScore = gpaScoreRepository.save(newGpaScore); + discordNotifier.notifyReviewRequested(DiscordNotificationType.GPA_SCORE, siteUser.getNickname()); return savedNewGpaScore.getId(); } @@ -58,6 +62,7 @@ public Long submitLanguageTestScore(long siteUserId, LanguageTestScoreRequest la languageTestScoreRequest.languageTestScore(), uploadedFile.fileUrl()); LanguageTestScore newScore = new LanguageTestScore(languageTest, siteUser); LanguageTestScore savedNewScore = languageTestScoreRepository.save(newScore); + discordNotifier.notifyReviewRequested(DiscordNotificationType.LANGUAGE_TEST_SCORE, siteUser.getNickname()); return savedNewScore.getId(); } From 711fee120ab953549f276af75a5fef58a6e42799 Mon Sep 17 00:00:00 2001 From: whqtker Date: Wed, 5 Aug 2026 22:58:00 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=EB=A9=98=ED=86=A0=20=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EB=93=B1=EB=A1=9D=20=EC=99=84=EB=A3=8C=20=EC=8B=9C?= =?UTF-8?q?=20Discord=20=EA=B2=80=EC=88=98=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mentor/service/MentorApplicationService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentorApplicationService.java b/src/main/java/com/example/solidconnection/mentor/service/MentorApplicationService.java index 9a6f4c1bc..ecfcf3a61 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentorApplicationService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentorApplicationService.java @@ -5,6 +5,8 @@ import static com.example.solidconnection.common.exception.ErrorCode.TERM_NOT_FOUND; import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; +import com.example.solidconnection.common.discord.DiscordNotificationType; +import com.example.solidconnection.common.discord.DiscordNotifier; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.mentor.domain.MentorApplication; import com.example.solidconnection.mentor.domain.MentorApplicationStatus; @@ -35,6 +37,7 @@ public class MentorApplicationService { private final SiteUserRepository siteUserRepository; private final S3Service s3Service; private final TermRepository termRepository; + private final DiscordNotifier discordNotifier; @Transactional public void submitMentorApplication( @@ -60,6 +63,7 @@ public void submitMentorApplication( mentorApplicationRequest.exchangeStatus() ); mentorApplicationRepository.save(mentorApplication); + discordNotifier.notifyReviewRequested(DiscordNotificationType.MENTOR_APPLICATION, siteUser.getNickname()); } private void ensureNoPendingOrApprovedMentorApplication(long siteUserId) { From bd5ef55b4e51d3fd447fa447f53a3b3b28303196 Mon Sep 17 00:00:00 2001 From: whqtker Date: Wed, 5 Aug 2026 22:58:04 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=EC=8B=A0=EA=B3=A0=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EC=99=84=EB=A3=8C=20=EC=8B=9C=20Discord=20?= =?UTF-8?q?=EA=B2=80=EC=88=98=20=EC=95=8C=EB=A6=BC=20=EC=A0=84=EC=86=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/solidconnection/report/service/ReportService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/example/solidconnection/report/service/ReportService.java b/src/main/java/com/example/solidconnection/report/service/ReportService.java index 9cfa1e389..d0521b6a9 100644 --- a/src/main/java/com/example/solidconnection/report/service/ReportService.java +++ b/src/main/java/com/example/solidconnection/report/service/ReportService.java @@ -4,6 +4,8 @@ import com.example.solidconnection.chat.domain.ChatParticipant; import com.example.solidconnection.chat.repository.ChatMessageRepository; import com.example.solidconnection.chat.repository.ChatParticipantRepository; +import com.example.solidconnection.common.discord.DiscordNotificationType; +import com.example.solidconnection.common.discord.DiscordNotifier; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.community.post.domain.Post; @@ -28,6 +30,7 @@ public class ReportService { private final PostRepository postRepository; private final ChatMessageRepository chatMessageRepository; private final ChatParticipantRepository chatParticipantRepository; + private final DiscordNotifier discordNotifier; @Transactional public void createReport(long reporterId, ReportRequest request) { @@ -39,6 +42,7 @@ public void createReport(long reporterId, ReportRequest request) { Report report = new Report(reporterId, reportedId, request.reportType(), request.targetType(), request.targetId()); reportRepository.save(report); + discordNotifier.notifyReviewRequested(DiscordNotificationType.REPORT, "신고자 ID: " + reporterId); } private void validateReporterAndReportedExists(long reporterId, long reportedId) { From 282496c2189a83c7b09ceeaac18b3dc99656dcf2 Mon Sep 17 00:00:00 2001 From: whqtker Date: Wed, 5 Aug 2026 23:04:17 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20=ED=99=98=EA=B2=BD=20=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=EC=A0=95=20=EC=8B=9C=20=EA=B8=B0=EB=B3=B8=EA=B0=92?= =?UTF-8?q?=EC=9D=84=20local=20=EB=8C=80=EC=8B=A0=20=EB=B9=88=20=EB=AC=B8?= =?UTF-8?q?=EC=9E=90=EC=97=B4=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/solidconnection/common/discord/DiscordNotifier.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/example/solidconnection/common/discord/DiscordNotifier.java b/src/main/java/com/example/solidconnection/common/discord/DiscordNotifier.java index c8fe306f6..d42ecda76 100644 --- a/src/main/java/com/example/solidconnection/common/discord/DiscordNotifier.java +++ b/src/main/java/com/example/solidconnection/common/discord/DiscordNotifier.java @@ -25,7 +25,7 @@ public class DiscordNotifier { @Value("${discord.webhook-url:}") private String webhookUrl; - @Value("${spring.profiles.active:local}") + @Value("${spring.profiles.active:}") private String environment; @Async From 8729a79937962398f11c4b80783780d62129e618 Mon Sep 17 00:00:00 2001 From: whqtker Date: Wed, 5 Aug 2026 23:07:51 +0900 Subject: [PATCH 6/6] =?UTF-8?q?refactor:=20notifyReviewRequested=EB=A5=BC?= =?UTF-8?q?=20notify=EB=A1=9C=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solidconnection/common/discord/DiscordNotifier.java | 2 +- .../mentor/service/MentorApplicationService.java | 2 +- .../example/solidconnection/report/service/ReportService.java | 2 +- .../example/solidconnection/score/service/ScoreService.java | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/example/solidconnection/common/discord/DiscordNotifier.java b/src/main/java/com/example/solidconnection/common/discord/DiscordNotifier.java index d42ecda76..8f4926f1d 100644 --- a/src/main/java/com/example/solidconnection/common/discord/DiscordNotifier.java +++ b/src/main/java/com/example/solidconnection/common/discord/DiscordNotifier.java @@ -29,7 +29,7 @@ public class DiscordNotifier { private String environment; @Async - public void notifyReviewRequested(DiscordNotificationType type, String applicantInfo) { + public void notify(DiscordNotificationType type, String applicantInfo) { if (webhookUrl.isBlank()) { return; } diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentorApplicationService.java b/src/main/java/com/example/solidconnection/mentor/service/MentorApplicationService.java index ecfcf3a61..8dd6e9075 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentorApplicationService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentorApplicationService.java @@ -63,7 +63,7 @@ public void submitMentorApplication( mentorApplicationRequest.exchangeStatus() ); mentorApplicationRepository.save(mentorApplication); - discordNotifier.notifyReviewRequested(DiscordNotificationType.MENTOR_APPLICATION, siteUser.getNickname()); + discordNotifier.notify(DiscordNotificationType.MENTOR_APPLICATION, siteUser.getNickname()); } private void ensureNoPendingOrApprovedMentorApplication(long siteUserId) { diff --git a/src/main/java/com/example/solidconnection/report/service/ReportService.java b/src/main/java/com/example/solidconnection/report/service/ReportService.java index d0521b6a9..06015568e 100644 --- a/src/main/java/com/example/solidconnection/report/service/ReportService.java +++ b/src/main/java/com/example/solidconnection/report/service/ReportService.java @@ -42,7 +42,7 @@ public void createReport(long reporterId, ReportRequest request) { Report report = new Report(reporterId, reportedId, request.reportType(), request.targetType(), request.targetId()); reportRepository.save(report); - discordNotifier.notifyReviewRequested(DiscordNotificationType.REPORT, "신고자 ID: " + reporterId); + discordNotifier.notify(DiscordNotificationType.REPORT, "신고자 ID: " + reporterId); } private void validateReporterAndReportedExists(long reporterId, long reportedId) { diff --git a/src/main/java/com/example/solidconnection/score/service/ScoreService.java b/src/main/java/com/example/solidconnection/score/service/ScoreService.java index f8174cf6d..2ea786c38 100644 --- a/src/main/java/com/example/solidconnection/score/service/ScoreService.java +++ b/src/main/java/com/example/solidconnection/score/service/ScoreService.java @@ -49,7 +49,7 @@ public Long submitGpaScore(long siteUserId, GpaScoreRequest gpaScoreRequest, Mul Gpa gpa = new Gpa(gpaScoreRequest.gpa(), gpaScoreRequest.gpaCriteria(), uploadedFile.fileUrl()); GpaScore newGpaScore = new GpaScore(gpa, siteUser); GpaScore savedNewGpaScore = gpaScoreRepository.save(newGpaScore); - discordNotifier.notifyReviewRequested(DiscordNotificationType.GPA_SCORE, siteUser.getNickname()); + discordNotifier.notify(DiscordNotificationType.GPA_SCORE, siteUser.getNickname()); return savedNewGpaScore.getId(); } @@ -62,7 +62,7 @@ public Long submitLanguageTestScore(long siteUserId, LanguageTestScoreRequest la languageTestScoreRequest.languageTestScore(), uploadedFile.fileUrl()); LanguageTestScore newScore = new LanguageTestScore(languageTest, siteUser); LanguageTestScore savedNewScore = languageTestScoreRepository.save(newScore); - discordNotifier.notifyReviewRequested(DiscordNotificationType.LANGUAGE_TEST_SCORE, siteUser.getNickname()); + discordNotifier.notify(DiscordNotificationType.LANGUAGE_TEST_SCORE, siteUser.getNickname()); return savedNewScore.getId(); }