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..8f4926f1d --- /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:}") + private String environment; + + @Async + public void notify(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); + } +} 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..8dd6e9075 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.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 9cfa1e389..06015568e 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.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 871c955f4..2ea786c38 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.notify(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.notify(DiscordNotificationType.LANGUAGE_TEST_SCORE, siteUser.getNickname()); return savedNewScore.getId(); }