Skip to content

Commit

Permalink
운영진 슬랙 알림 기능 추가 완료 (#466)
Browse files Browse the repository at this point in the history
  • Loading branch information
limehee authored Aug 16, 2024
1 parent 4a5b7f0 commit 8937ce0
Show file tree
Hide file tree
Showing 12 changed files with 121 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import page.clab.api.domain.community.board.application.port.in.RegisterBoardUseCase;
import page.clab.api.domain.community.board.application.port.out.RegisterBoardPort;
import page.clab.api.domain.community.board.domain.Board;
import page.clab.api.domain.community.board.domain.SlackBoardInfo;
import page.clab.api.global.common.slack.domain.SlackBoardInfo;
import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto;
import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase;
import page.clab.api.external.memberManagement.notification.application.port.ExternalSendNotificationUseCase;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberBasicInfoDto;
import page.clab.api.domain.members.membershipFee.application.dto.request.MembershipFeeRequestDto;
import page.clab.api.domain.members.membershipFee.application.port.in.RegisterMembershipFeeUseCase;
import page.clab.api.domain.members.membershipFee.application.port.out.RegisterMembershipFeePort;
import page.clab.api.domain.members.membershipFee.domain.MembershipFee;
import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase;
import page.clab.api.external.memberManagement.notification.application.port.ExternalSendNotificationUseCase;
import page.clab.api.global.common.slack.application.SlackService;
import page.clab.api.global.common.slack.domain.SlackMembershipFeeInfo;

@Service
@RequiredArgsConstructor
Expand All @@ -17,13 +20,16 @@ public class MembershipFeeRegisterService implements RegisterMembershipFeeUseCas
private final RegisterMembershipFeePort registerMembershipFeePort;
private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase;
private final ExternalSendNotificationUseCase externalSendNotificationUseCase;
private final SlackService slackService;

@Transactional
@Override
public Long registerMembershipFee(MembershipFeeRequestDto requestDto) {
String currentMemberId = externalRetrieveMemberUseCase.getCurrentMemberId();
MembershipFee membershipFee = MembershipFeeRequestDto.toEntity(requestDto, currentMemberId);
MemberBasicInfoDto memberInfo = externalRetrieveMemberUseCase.getCurrentMemberBasicInfo();
MembershipFee membershipFee = MembershipFeeRequestDto.toEntity(requestDto, memberInfo.getMemberId());
externalSendNotificationUseCase.sendNotificationToAdmins("새로운 회비 내역이 등록되었습니다.");
SlackMembershipFeeInfo membershipFeeInfo = SlackMembershipFeeInfo.create(membershipFee, memberInfo);
slackService.sendNewMembershipFeeNotification(membershipFeeInfo);
return registerMembershipFeePort.save(membershipFee).getId();
}
}
Original file line number Diff line number Diff line change
@@ -1,48 +1,59 @@
package page.clab.api.global.common.slack.application;

import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import page.clab.api.domain.community.board.domain.SlackBoardInfo;
import page.clab.api.domain.hiring.application.application.dto.request.ApplicationRequestDto;
import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberLoginInfoDto;
import page.clab.api.global.common.slack.domain.ExecutivesAlertType;
import page.clab.api.global.common.slack.domain.GeneralAlertType;
import page.clab.api.global.common.slack.domain.SecurityAlertType;
import page.clab.api.global.common.slack.domain.SlackBoardInfo;
import page.clab.api.global.common.slack.domain.SlackMembershipFeeInfo;
import page.clab.api.global.common.slack.event.NotificationEvent;
import page.clab.api.global.config.SlackConfig;

@Service
@RequiredArgsConstructor
@Slf4j
public class SlackService {

private final ApplicationEventPublisher eventPublisher;
private final String coreTeamWebhookUrl;
private final String executivesWebhookUrl;

public SlackService(ApplicationEventPublisher eventPublisher, SlackConfig slackConfig) {
this.eventPublisher = eventPublisher;
this.coreTeamWebhookUrl = slackConfig.getCoreTeamWebhookUrl();
this.executivesWebhookUrl = slackConfig.getExecutivesWebhookUrl();
}

public void sendServerErrorNotification(HttpServletRequest request, Exception e) {
eventPublisher.publishEvent(new NotificationEvent(this, GeneralAlertType.SERVER_ERROR, request, e));
eventPublisher.publishEvent(new NotificationEvent(this, coreTeamWebhookUrl, GeneralAlertType.SERVER_ERROR, request, e));
}

public void sendSecurityAlertNotification(HttpServletRequest request, SecurityAlertType alertType, String additionalMessage) {
eventPublisher.publishEvent(new NotificationEvent(this, alertType, request, additionalMessage));
eventPublisher.publishEvent(new NotificationEvent(this, coreTeamWebhookUrl, alertType, request, additionalMessage));
}

public void sendAdminLoginNotification(HttpServletRequest request, MemberLoginInfoDto loginMember) {
eventPublisher.publishEvent(new NotificationEvent(this, GeneralAlertType.ADMIN_LOGIN, request, loginMember));
eventPublisher.publishEvent(new NotificationEvent(this, coreTeamWebhookUrl, GeneralAlertType.ADMIN_LOGIN, request, loginMember));
}

public void sendNewApplicationNotification(ApplicationRequestDto applicationRequestDto) {
eventPublisher.publishEvent(new NotificationEvent(this, GeneralAlertType.APPLICATION_CREATED, null, applicationRequestDto));
eventPublisher.publishEvent(new NotificationEvent(this, executivesWebhookUrl, ExecutivesAlertType.NEW_APPLICATION, null, applicationRequestDto));
}

public void sendNewBoardNotification(SlackBoardInfo board) {
eventPublisher.publishEvent(new NotificationEvent(this, GeneralAlertType.BOARD_CREATED, null, board));
eventPublisher.publishEvent(new NotificationEvent(this, executivesWebhookUrl, ExecutivesAlertType.NEW_BOARD, null, board));
}

public void sendNewMembershipFeeNotification(SlackMembershipFeeInfo membershipFee) {
eventPublisher.publishEvent(new NotificationEvent(this, executivesWebhookUrl, ExecutivesAlertType.NEW_MEMBERSHIP_FEE, null, membershipFee));
}

@EventListener(ContextRefreshedEvent.class)
public void sendServerStartNotification() {
eventPublisher.publishEvent(new NotificationEvent(this, GeneralAlertType.SERVER_START, null, null));
eventPublisher.publishEvent(new NotificationEvent(this, coreTeamWebhookUrl, GeneralAlertType.SERVER_START, null, null));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import page.clab.api.domain.community.board.domain.SlackBoardInfo;
import page.clab.api.domain.hiring.application.application.dto.request.ApplicationRequestDto;
import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberLoginInfoDto;
import page.clab.api.global.common.slack.domain.AlertType;
import page.clab.api.global.common.slack.domain.ExecutivesAlertType;
import page.clab.api.global.common.slack.domain.GeneralAlertType;
import page.clab.api.global.common.slack.domain.SecurityAlertType;
import page.clab.api.global.common.slack.domain.SlackBoardInfo;
import page.clab.api.global.common.slack.domain.SlackMembershipFeeInfo;
import page.clab.api.global.config.SlackConfig;
import page.clab.api.global.util.HttpReqResUtil;

Expand All @@ -47,7 +49,6 @@
public class SlackServiceHelper {

private final Slack slack;
private final String webhookUrl;
private final String webUrl;
private final String apiUrl;
private final String color;
Expand All @@ -56,7 +57,6 @@ public class SlackServiceHelper {

public SlackServiceHelper(SlackConfig slackConfig, Environment environment, AttributeStrategy attributeStrategy) {
this.slack = slackConfig.slack();
this.webhookUrl = slackConfig.getWebhookUrl();
this.webUrl = slackConfig.getWebUrl();
this.apiUrl = slackConfig.getApiUrl();
this.color = slackConfig.getColor();
Expand All @@ -69,9 +69,8 @@ private static String getUsername() {
return (authentication == null || authentication.getName() == null) ? "anonymous" : authentication.getName();
}

public CompletableFuture<Boolean> sendSlackMessage(AlertType alertType, HttpServletRequest request, Object additionalData) {
public CompletableFuture<Boolean> sendSlackMessage(String webhookUrl, AlertType alertType, HttpServletRequest request, Object additionalData) {
List<LayoutBlock> blocks = createBlocks(alertType, request, additionalData);

return CompletableFuture.supplyAsync(() -> {
Payload payload = Payload.builder()
.blocks(List.of(blocks.getFirst()))
Expand Down Expand Up @@ -106,23 +105,33 @@ public List<LayoutBlock> createBlocks(AlertType alertType, HttpServletRequest re
return createAdminLoginBlocks(request, (MemberLoginInfoDto) additionalData);
}
break;
case APPLICATION_CREATED:
case SERVER_START:
return createServerStartBlocks();
case SERVER_ERROR:
if (additionalData instanceof Exception) {
return createErrorBlocks(request, (Exception) additionalData);
}
break;
default:
log.error("Unknown alert type: {}", alertType);
return List.of();
}
} else if (alertType instanceof ExecutivesAlertType) {
switch ((ExecutivesAlertType) alertType) {
case NEW_APPLICATION:
if (additionalData instanceof ApplicationRequestDto) {
return createApplicationBlocks((ApplicationRequestDto) additionalData);
}
break;
case BOARD_CREATED:
case NEW_BOARD:
if (additionalData instanceof SlackBoardInfo) {
return createBoardBlocks((SlackBoardInfo) additionalData);
}
break;
case SERVER_START:
return createServerStartBlocks();
case SERVER_ERROR:
if (additionalData instanceof Exception) {
return createErrorBlocks(request, (Exception) additionalData);
case NEW_MEMBERSHIP_FEE:
if (additionalData instanceof SlackMembershipFeeInfo) {
return createMembershipFeeBlocks((SlackMembershipFeeInfo) additionalData);
}
break;
default:
log.error("Unknown alert type: {}", alertType);
return List.of();
Expand Down Expand Up @@ -216,6 +225,20 @@ private List<LayoutBlock> createBoardBlocks(SlackBoardInfo board) {
return blocks;
}

private List<LayoutBlock> createMembershipFeeBlocks(SlackMembershipFeeInfo additionalData) {
String username = additionalData.getMemberId() + " " + additionalData.getMemberName();

return Arrays.asList(
section(section -> section.text(markdownText(":dollar: *New Membership Fee*"))),
section(section -> section.fields(Arrays.asList(
markdownText("*User:*\n" + username),
markdownText("*Category:*\n" + additionalData.getCategory()),
markdownText("*Amount:*\n" + additionalData.getAmount() + "원")
))),
section(section -> section.text(markdownText("*Content:*\n" + additionalData.getContent())))
);
}

private List<LayoutBlock> createServerStartBlocks() {
String osInfo = System.getProperty("os.name") + " " + System.getProperty("os.version");
String jdkVersion = System.getProperty("java.version");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public class AlertTypeConverter implements AttributeConverter<AlertType, String>
for (SecurityAlertType type : SecurityAlertType.values()) {
CACHE.put(type.getTitle(), type);
}
for (ExecutivesAlertType type : ExecutivesAlertType.values()) {
CACHE.put(type.getTitle(), type);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package page.clab.api.global.common.slack.domain;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum ExecutivesAlertType implements AlertType {

NEW_APPLICATION("새 지원서", "New application has been submitted."),
NEW_BOARD("새 게시글", "New board has been posted."),
NEW_MEMBERSHIP_FEE("새 회비 신청", "New membership fee has been submitted.");

private final String title;
private final String defaultMessage;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
public enum GeneralAlertType implements AlertType {

ADMIN_LOGIN("관리자 로그인", "Admin login."),
APPLICATION_CREATED("새 지원서", "New application has been submitted."),
BOARD_CREATED("새 게시글", "New board has been created."),
SERVER_START("서버 시작", "Server has been started."),
SERVER_ERROR("서버 에러", "Server error occurred.");

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
package page.clab.api.domain.community.board.domain;
package page.clab.api.global.common.slack.domain;

import lombok.Builder;
import lombok.Getter;
import page.clab.api.domain.community.board.domain.Board;
import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto;

@Getter
@Builder
public class SlackBoardInfo {

private String title;

private String category;

private String username;

public static SlackBoardInfo create(Board board, MemberDetailedInfoDto memberInfo) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package page.clab.api.global.common.slack.domain;

import lombok.Builder;
import lombok.Getter;
import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberBasicInfoDto;
import page.clab.api.domain.members.membershipFee.domain.MembershipFee;

@Getter
@Builder
public class SlackMembershipFeeInfo {

private String memberId;
private String memberName;
private String category;
private Long amount;
private String content;

public static SlackMembershipFeeInfo create(MembershipFee membershipFee, MemberBasicInfoDto memberInfo) {
return SlackMembershipFeeInfo.builder()
.memberId(memberInfo.getMemberId())
.memberName(memberInfo.getMemberName())
.category(membershipFee.getCategory())
.content(membershipFee.getContent())
.amount(membershipFee.getAmount())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
@Getter
public class NotificationEvent extends ApplicationEvent {

private final String webhookUrl;
private final AlertType alertType;
private final HttpServletRequest request;
private final Object additionalData;

public NotificationEvent(Object source, AlertType alertType, HttpServletRequest request, Object additionalData) {
public NotificationEvent(Object source, String webhookUrl, AlertType alertType, HttpServletRequest request, Object additionalData) {
super(source);
this.webhookUrl = webhookUrl;
this.alertType = alertType;
this.request = request;
this.additionalData = additionalData;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void handleNotificationEvent(NotificationEvent event) {
NotificationSetting setting = settingService.getOrCreateDefaultSetting(alertType);

if (setting.isEnabled()) {
slackServiceHelper.sendSlackMessage(alertType, event.getRequest(), event.getAdditionalData());
slackServiceHelper.sendSlackMessage(event.getWebhookUrl(), alertType, event.getRequest(), event.getAdditionalData());
}
}
}
3 changes: 2 additions & 1 deletion src/main/java/page/clab/api/global/config/SlackConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
@ConfigurationProperties(prefix = "slack")
public class SlackConfig {

private String webhookUrl;
private String coreTeamWebhookUrl;
private String executivesWebhookUrl;
private String webUrl;
private String apiUrl;
private String color;
Expand Down

0 comments on commit 8937ce0

Please sign in to comment.