diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index eea3851..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index 34b294e..47855af 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,9 @@ out/ ### VS Code ### .vscode/ +### Mac OS ### +.DS_Store + ### 환경 변수 .env /src/main/resources/env.properties diff --git a/src/main/java/com/Alchive/backend/controller/BoardController.java b/src/main/java/com/Alchive/backend/controller/BoardController.java index f18abb0..edddf71 100644 --- a/src/main/java/com/Alchive/backend/controller/BoardController.java +++ b/src/main/java/com/Alchive/backend/controller/BoardController.java @@ -31,7 +31,6 @@ @RequestMapping("/api/v2/boards") public class BoardController { private final BoardService boardService; - private final SlackService slackService; @Operation(summary = "게시물 저장 여부 조회", description = "게시물의 저장 여부를 조회하는 메서드입니다. ") @PostMapping("/saved") diff --git a/src/main/java/com/Alchive/backend/controller/DiscordController.java b/src/main/java/com/Alchive/backend/controller/DiscordController.java index 2e849ec..dd1d8ae 100644 --- a/src/main/java/com/Alchive/backend/controller/DiscordController.java +++ b/src/main/java/com/Alchive/backend/controller/DiscordController.java @@ -1,11 +1,7 @@ package com.Alchive.backend.controller; import com.Alchive.backend.config.result.ResultResponse; -import com.Alchive.backend.domain.sns.Sns; -import com.Alchive.backend.domain.sns.SnsCategory; import com.Alchive.backend.domain.user.User; -import com.Alchive.backend.dto.request.SnsCreateRequest; -import com.Alchive.backend.service.SnsService; import com.Alchive.backend.service.DiscordService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -24,45 +20,20 @@ @Slf4j @RequestMapping("/api/v2/discord") public class DiscordController { - private final SnsService snsService; private final DiscordService discordService; @Operation(summary = "디스코드 봇 연결", description = "디스코드 액세스 토큰을 요청하고 DM 채널을 연결하는 api입니다. ") @GetMapping("/dm/open") - public ResponseEntity openDiscordDm(@AuthenticationPrincipal User user, @RequestParam String code) { - // Access Token 요청 - String accessToken = discordService.getAccessToken(code); - log.info("Access Token 반환 완료: " + accessToken); - - // 사용자 DISCORD USER ID 요청 - String discordUserId = discordService.getDiscordUserIdFromAccessToken(accessToken); - log.info("Discord User Id 반환 완료: " + discordUserId); - - // DM 채널 생성 요청 - String channelId = discordService.getDmChannel(discordUserId); - log.info("DM 채널 생성 완료"); - - // Discord SNS 정보 저장 - SnsCreateRequest snsCreateRequest = SnsCreateRequest.builder() - .category(SnsCategory.DISCORD) - .sns_id(discordUserId) // Discord User Id - .channel_id(channelId) // Discord Channel Id - .time("0 0 18 ? * MON") - .build(); - snsService.createSns(user, snsCreateRequest); - log.info("SNS 정보 저장 완료"); - - // DM 전송 요청 - discordService.sendDm(channelId, "안녕하세요! 이제부터 풀지 못한 문제들을 정해진 시간에 알려드릴게요. "); - + public ResponseEntity initializeDiscordChannelAndSendWelcomeDM(@AuthenticationPrincipal User user, @RequestParam String code) { + discordService.initializeDiscordChannelAndSaveSnsInfo(user, code); + discordService.sendDiscordDm(user, "안녕하세요! 이제부터 풀지 못한 문제들을 정해진 시간에 알려드릴게요. "); return ResponseEntity.ok(ResultResponse.of(DISCORD_DM_SEND_SUCCESS)); } @Operation(summary = "디스코드 DM 전송", description = "디스코드 DM으로 메시지를 전송하는 api입니다. ") @PostMapping("dm/send") public ResponseEntity sendDiscordDm(@AuthenticationPrincipal User user, @RequestParam String message) { - Sns discordInfo = discordService.getDiscordInfo(user); - discordService.sendDmJda(discordInfo.getSns_id(), message); + discordService.sendJdaDm(user, message); return ResponseEntity.ok(ResultResponse.of(DISCORD_DM_SEND_SUCCESS)); } } diff --git a/src/main/java/com/Alchive/backend/controller/SlackController.java b/src/main/java/com/Alchive/backend/controller/SlackController.java index d3d9083..3bf0b02 100644 --- a/src/main/java/com/Alchive/backend/controller/SlackController.java +++ b/src/main/java/com/Alchive/backend/controller/SlackController.java @@ -1,9 +1,7 @@ package com.Alchive.backend.controller; import com.Alchive.backend.config.result.ResultResponse; -import com.Alchive.backend.domain.sns.Sns; import com.Alchive.backend.domain.user.User; -import com.Alchive.backend.dto.request.SnsCreateRequest; import com.Alchive.backend.service.SnsService; import com.Alchive.backend.service.SlackService; import io.swagger.v3.oas.annotations.Operation; @@ -23,33 +21,19 @@ @RequiredArgsConstructor public class SlackController { private final SlackService slackService; - private final SnsService snsService; @Operation(summary = "슬랙 봇 연결", description = "슬랙 액세스 토큰을 요청하고 DM 채널을 연결하는 api입니다. ") @GetMapping("/dm/open") public ResponseEntity openSlackDm(@AuthenticationPrincipal User user, @RequestParam String code) { - // Bot Access Token, User Access Token, Slack User Id 요청 - SnsCreateRequest snsCreateRequest = slackService.getSlackInfo(code); - log.info("사용자 slack 정보를 불러왔습니다. "); - - // Slack SNS 정보 저장 - snsService.createSns(user, snsCreateRequest); - log.info("사용자 slack 정보를 저장했습니다. "); - - // DM 전송 요청 - String slackUserId = snsCreateRequest.getSns_id(); - String botAccessToken = snsCreateRequest.getBot_token(); - - slackService.sendDm(slackUserId, botAccessToken, "안녕하세요! 이제부터 풀지 못한 문제들을 정해진 시간에 알려드릴게요. "); - log.info("봇을 사용자 slack 채널에 추가했습니다. "); + slackService.initializeSlackChannelAndSaveSnsInfo(user, code); + slackService.sendSlackDm(user, "안녕하세요! 이제부터 풀지 못한 문제들을 정해진 시간에 알려드릴게요. "); return ResponseEntity.ok(ResultResponse.of(SLACK_DM_SEND_SUCCESS)); } @Operation(summary = "슬랙 DM 전송", description = "슬랙 DM으로 메시지를 전송하는 api입니다. ") @PostMapping("dm/send") public ResponseEntity sendSlackDm(@AuthenticationPrincipal User user, @RequestParam String message) { - Sns sns = slackService.getSlackInfo(user); - slackService.sendDm(sns.getSns_id(), sns.getBot_token(), message); + slackService.sendSlackDm(user, message); return ResponseEntity.ok(ResultResponse.of(SLACK_DM_SEND_SUCCESS)); } } \ No newline at end of file diff --git a/src/main/java/com/Alchive/backend/controller/SnsController.java b/src/main/java/com/Alchive/backend/controller/SnsController.java index c11ed3b..b685509 100644 --- a/src/main/java/com/Alchive/backend/controller/SnsController.java +++ b/src/main/java/com/Alchive/backend/controller/SnsController.java @@ -1,6 +1,7 @@ package com.Alchive.backend.controller; import com.Alchive.backend.config.result.ResultResponse; +import com.Alchive.backend.domain.sns.Sns; import com.Alchive.backend.domain.user.User; import com.Alchive.backend.dto.request.SnsCreateRequest; import com.Alchive.backend.dto.response.SnsResponseDTO; @@ -31,7 +32,7 @@ public ResponseEntity getSns(@PathVariable Long snsId) { @Operation(summary = "소셜 정보 생성", description = "소셜 정보를 생성하는 메서드입니다. ") @PostMapping("") - public ResponseEntity createSns(@AuthenticationPrincipal User user, SnsCreateRequest request) { + public ResponseEntity createSns(@AuthenticationPrincipal User user, @RequestBody SnsCreateRequest request) { snsService.createSns(user, request); return ResponseEntity.ok(ResultResponse.of(SNS_CREATE_SUCCESS)); } diff --git a/src/main/java/com/Alchive/backend/service/DiscordService.java b/src/main/java/com/Alchive/backend/service/DiscordService.java index a3a0ce9..f77bec1 100644 --- a/src/main/java/com/Alchive/backend/service/DiscordService.java +++ b/src/main/java/com/Alchive/backend/service/DiscordService.java @@ -1,11 +1,11 @@ package com.Alchive.backend.service; -import com.Alchive.backend.config.error.exception.sns.InvalidGrantException; import com.Alchive.backend.config.error.exception.sns.NoSuchDiscordUserException; import com.Alchive.backend.config.error.exception.sns.NoSuchSnsIdException; import com.Alchive.backend.domain.board.Board; import com.Alchive.backend.domain.sns.Sns; import com.Alchive.backend.domain.sns.SnsCategory; +import com.Alchive.backend.dto.request.SnsCreateRequest; import com.Alchive.backend.repository.BoardRepository; import com.Alchive.backend.repository.SnsReporitory; import lombok.RequiredArgsConstructor; @@ -18,7 +18,6 @@ import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; @@ -30,10 +29,13 @@ @EnableScheduling @Slf4j public class DiscordService { + private final static String DISCORD_USER_INFO_REQUEST_URL = "https://discord.com/api/v10/users/@me"; + private final static String DISCORD_ACCESS_TOKEN_REQUEST_URL = "https://discord.com/api/oauth2/token"; + private final static String DISCORD_DM_CHANNEL_REQUEST_URL = "https://discord.com/api/v10/users/@me/channels"; private final JDA jda; private final BoardRepository boardRepository; private final SnsReporitory snsReporitory; - + private final SnsService snsService; @Value("${DISCORD_CLIENT_ID}") private String clientId; @@ -45,110 +47,113 @@ public class DiscordService { @Value("${DISCORD_BOT_TOKEN}") private String discordBotToken; - private RestTemplate restTemplate = new RestTemplate(); - - public String getAccessToken(String code) { - String getTokenUrl = "https://discord.com/api/oauth2/token"; - - HttpHeaders getTokenHeaders = new HttpHeaders(); - getTokenHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - - MultiValueMap getTokenParams = new LinkedMultiValueMap<>(); - getTokenParams.add("client_id", clientId); - getTokenParams.add("client_secret", clientSecret); - getTokenParams.add("code", code); - getTokenParams.add("grant_type", "authorization_code"); - getTokenParams.add("redirect_uri", redirectUri); - - HttpEntity> request = new HttpEntity<>(getTokenParams, getTokenHeaders); - ResponseEntity response = restTemplate.postForEntity(getTokenUrl, request, Map.class); - Map responseBody = response.getBody(); + public void initializeDiscordChannelAndSaveSnsInfo(com.Alchive.backend.domain.user.User user, String code) { + String accessToken = getAccessToken(code); + String discordUserId = getDiscordUserIdFromAccessToken(accessToken); + String channelId = getDmChannel(discordUserId); + + SnsCreateRequest snsCreateRequest = SnsCreateRequest.builder() + .category(SnsCategory.DISCORD) + .sns_id(discordUserId) // Discord User Id + .channel_id(channelId) // Discord Channel Id + .time("0 0 18 ? * MON") + .build(); + snsService.createSns(user, snsCreateRequest); + } - if (responseBody.containsKey("error") && responseBody.get("error") == "invalid_grant" ) { - throw new InvalidGrantException(); - } - String accessToken = (String) responseBody.get("access_token"); + private String getAccessToken(String code) { + HttpHeaders httpHeader = new HttpHeaders(); + httpHeader.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + MultiValueMap httpBody = new LinkedMultiValueMap<>(); + httpBody.add("client_id", clientId); + httpBody.add("client_secret", clientSecret); + httpBody.add("grant_type", "authorization_code"); + httpBody.add("redirect_uri", redirectUri); + httpBody.add("code", code); + + HttpEntity> request = new HttpEntity<>(httpBody, httpHeader); + ResponseEntity response = snsService.sendRestTemplateExchange(DISCORD_ACCESS_TOKEN_REQUEST_URL, HttpMethod.POST, request); + String accessToken = (String) snsService.parseResponse(response, "access_token"); return accessToken; } - public String getDiscordUserIdFromAccessToken(String accessToken) { - String getUserInfoUrl = "https://discord.com/api/v10/users/@me"; + private String getDiscordUserIdFromAccessToken(String accessToken) { HttpHeaders accessTokenHeaders = new HttpHeaders(); accessTokenHeaders.setBearerAuth(accessToken); + HttpEntity request = new HttpEntity<>(accessTokenHeaders); + ResponseEntity response = snsService.sendRestTemplateExchange(DISCORD_USER_INFO_REQUEST_URL, HttpMethod.GET, request); + String discordUserId = (String) snsService.parseResponse(response, "id"); + return discordUserId; + } - HttpEntity authRequest = new HttpEntity<>(accessTokenHeaders); - ResponseEntity userInfoResponse = restTemplate.exchange(getUserInfoUrl, HttpMethod.GET, authRequest, Map.class); - Map userInfo = userInfoResponse.getBody(); + private String getDmChannel(String discordUserId) { + Map httpBody = new HashMap<>(); + httpBody.put("recipient_id", discordUserId); + HttpHeaders httpHeader = new HttpHeaders(); + httpHeader.setContentType(MediaType.APPLICATION_JSON); + httpHeader.set("Authorization", "Bot " + discordBotToken); + HttpEntity> request = new HttpEntity<>(httpBody, httpHeader); - String discordUserId = (String) userInfo.get("id"); - return discordUserId; + ResponseEntity response = snsService.sendRestTemplateExchange(DISCORD_DM_CHANNEL_REQUEST_URL, HttpMethod.POST, request); + String channelId = (String) snsService.parseResponse(response, "id"); + return channelId; } - public Sns getDiscordInfo(com.Alchive.backend.domain.user.User user) { - Long userId = user.getId(); - Sns discordInfo = snsReporitory.findByUser_IdAndCategory(userId, SnsCategory.DISCORD) - .orElseThrow(NoSuchSnsIdException::new); - return discordInfo; + // @Scheduled(cron = "0 */1 * * * *") // todo: Quartz로 동적 스케줄링 작성하기 + private void sendMessageReminderBoard(com.Alchive.backend.domain.user.User user) { + LocalDateTime threeDaysAgo = LocalDateTime.now().minusDays(1); + + Board unSolvedBoard = boardRepository.findUnsolvedBoardAddedBefore(threeDaysAgo, user.getId()); + if (unSolvedBoard != null) { + String message = String.format(":star-struck: %d일 전 도전했던 %d. %s 문제를 아직 풀지 못했어요. \n \n다시 도전해보세요! :facepunch: \n \n<%s|:link: 문제 풀러가기>", + ChronoUnit.DAYS.between(unSolvedBoard.getCreatedAt(), LocalDateTime.now()), + unSolvedBoard.getProblem().getNumber(), + unSolvedBoard.getProblem().getTitle(), + unSolvedBoard.getProblem().getUrl()); + + sendJdaDm(user, message); + } else { + log.info("풀지 못한 문제가 존재하지 않습니다. "); + } } - public void sendDm(String channelId, String message) { + public void sendDiscordDm(com.Alchive.backend.domain.user.User user, String message) { + String channelId = getUserChannelId(user); String sendMessageUrl = "https://discord.com/api/v10/channels/" + channelId + "/messages"; + HttpHeaders sendDmHeaders = new HttpHeaders(); sendDmHeaders.set("Authorization", "Bot " + discordBotToken); sendDmHeaders.setContentType(MediaType.APPLICATION_JSON); - Map sendDmParams = new HashMap<>(); sendDmParams.put("content", message); + HttpEntity> request = new HttpEntity<>(sendDmParams, sendDmHeaders); - HttpEntity> sendMessageRequest = new HttpEntity<>(sendDmParams, sendDmHeaders); - restTemplate.postForEntity(sendMessageUrl, sendMessageRequest, Map.class); + snsService.sendRestTemplateExchange(sendMessageUrl, HttpMethod.POST, request); } - public String getDmChannel(String discordUserId) { - String getDMChannelUrl = "https://discord.com/api/v10/users/@me/channels"; - Map getDmParams = new HashMap<>(); - getDmParams.put("recipient_id", discordUserId); - - HttpHeaders botTokenHeader = new HttpHeaders(); - botTokenHeader.setContentType(MediaType.APPLICATION_JSON); - botTokenHeader.set("Authorization", "Bot " + discordBotToken); - - HttpEntity> createDmRequest = new HttpEntity<>(getDmParams, botTokenHeader); - ResponseEntity dmResponse = restTemplate.postForEntity(getDMChannelUrl, createDmRequest, Map.class); - Map dmResponseBody = dmResponse.getBody(); + private String getUserChannelId(com.Alchive.backend.domain.user.User user) { + return getDiscordInfo(user).getChannel_id(); + } - String channelId = (String) dmResponseBody.get("id"); - return channelId; + private Sns getDiscordInfo(com.Alchive.backend.domain.user.User user) { + Long userId = user.getId(); + Sns discordInfo = snsReporitory.findByUser_IdAndCategory(userId, SnsCategory.DISCORD) + .orElseThrow(NoSuchSnsIdException::new); + return discordInfo; } // JDA 사용 메서드 - public void sendDmJda(String discordUserId, String message) { - User user = jda.retrieveUserById(discordUserId).complete(); - if (user != null) { - user.openPrivateChannel().queue(channel -> - channel.sendMessage(message).queue() - ); - } else { - throw new NoSuchDiscordUserException(); - } + public void sendJdaDm(com.Alchive.backend.domain.user.User user, String message) { + String discordUserId = getDiscordInfo(user).getSns_id(); + User jdaUser = jda.retrieveUserById(discordUserId).complete(); + checkUserNull(jdaUser); + jdaUser.openPrivateChannel().queue(channel -> + channel.sendMessage(message).queue()); } -// @Scheduled(cron = "0 */1 * * * *") // todo: Quartz로 동적 스케줄링 작성하기 - public void sendMessageReminderBoard(com.Alchive.backend.domain.user.User user) { - LocalDateTime threeDaysAgo = LocalDateTime.now().minusDays(1); - - Board unSolvedBoard = boardRepository.findUnsolvedBoardAddedBefore(threeDaysAgo, user.getId()); - if (unSolvedBoard != null) { - String message = String.format(":star-struck: %d일 전 도전했던 %d. %s 문제를 아직 풀지 못했어요. \n \n다시 도전해보세요! :facepunch: \n \n<%s|:link: 문제 풀러가기>", - ChronoUnit.DAYS.between(unSolvedBoard.getCreatedAt(), LocalDateTime.now()), - unSolvedBoard.getProblem().getNumber(), - unSolvedBoard.getProblem().getTitle(), - unSolvedBoard.getProblem().getUrl()); - - Sns discordInfo = getDiscordInfo(user); - sendDmJda(discordInfo.getSns_id(), message); - } else { - log.info("풀지 못한 문제가 존재하지 않습니다. "); + private void checkUserNull(User user) { + if (user == null) { + throw new NoSuchDiscordUserException(); } } } diff --git a/src/main/java/com/Alchive/backend/service/SlackService.java b/src/main/java/com/Alchive/backend/service/SlackService.java index 88e9239..e909e20 100644 --- a/src/main/java/com/Alchive/backend/service/SlackService.java +++ b/src/main/java/com/Alchive/backend/service/SlackService.java @@ -1,6 +1,5 @@ package com.Alchive.backend.service; -import com.Alchive.backend.config.error.exception.sns.InvalidGrantException; import com.Alchive.backend.config.error.exception.sns.NoSuchSnsIdException; import com.Alchive.backend.domain.board.Board; import com.Alchive.backend.domain.sns.Sns; @@ -15,16 +14,13 @@ import org.springframework.context.annotation.Configuration; import org.springframework.http.*; import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.HashMap; -import java.util.List; import java.util.Map; @@ -34,6 +30,8 @@ @EnableScheduling @Configuration public class SlackService { + private final static String SLACK_TOKEN_REQUEST_URL = "https://slack.com/api/oauth.v2.access"; + private final static String SLACK_SEND_DM_REQUEST_URL = "https://slack.com/api/chat.postMessage"; @Value("${SLACK_CLIENT_ID}") private String clientId; @@ -46,34 +44,16 @@ public class SlackService { @Value("${SLACK_BOT_TOKEN}") private String slackBotToken; - private RestTemplate restTemplate = new RestTemplate(); - private BoardRepository boardRepository; - private SnsReporitory snsReporitory; + private final BoardRepository boardRepository; + private final SnsReporitory snsReporitory; + private final SnsService snsService; - public SnsCreateRequest getSlackInfo(String code) { - String getTokenUrl = "https://slack.com/api/oauth.v2.access"; - - HttpHeaders getTokenHeaders = new HttpHeaders(); - getTokenHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - - MultiValueMap getTokenParams = new LinkedMultiValueMap<>(); - getTokenParams.add("client_id", clientId); - getTokenParams.add("client_secret", clientSecret); - getTokenParams.add("code", code); - getTokenParams.add("redirect_uri", redirectUri); - - HttpEntity> request = new HttpEntity<>(getTokenParams, getTokenHeaders); - ResponseEntity reponse = restTemplate.postForEntity(getTokenUrl, request, Map.class); - Map responseBody = reponse.getBody(); - - if (responseBody.get("ok") == "false") { - throw new InvalidGrantException(); - } - - Map authedUser = (Map) responseBody.get("authed_user"); + public void initializeSlackChannelAndSaveSnsInfo(User user, String code) { + ResponseEntity response = getSlackAuthUserInfo(code); + Map authedUser = (Map) snsService.parseResponse(response, "authed_user"); String slackUserId = (String) authedUser.get("id"); - String botAccessToken = (String) responseBody.get("access_token"); String userAccessToken = (String) authedUser.get("access_token"); + String botAccessToken = (String) snsService.parseResponse(response, "access_token"); SnsCreateRequest snsCreateRequest = SnsCreateRequest.builder() .category(SnsCategory.SLACK) @@ -82,29 +62,22 @@ public SnsCreateRequest getSlackInfo(String code) { .user_token(userAccessToken) .time("0 0 18 ? * MON") .build(); - - return snsCreateRequest; + snsService.createSns(user, snsCreateRequest); } - public Sns getSlackInfo(User user) { - Long userId = user.getId(); - Sns slackInfo = snsReporitory.findByUser_IdAndCategory(userId, SnsCategory.SLACK) - .orElseThrow(NoSuchSnsIdException::new); - return slackInfo; - } - - public void sendDm(String slackUserId, String botAccessToken, String message) { - String sendDmUrl = "https://slack.com/api/chat.postMessage"; - HttpHeaders sendDmHeaders = new HttpHeaders(); - sendDmHeaders.setContentType(MediaType.APPLICATION_JSON); - sendDmHeaders.setBearerAuth(botAccessToken); + private ResponseEntity getSlackAuthUserInfo (String code) { + HttpHeaders getTokenHeaders = new HttpHeaders(); + getTokenHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - Map sendDmParams = new HashMap<>(); - sendDmParams.put("channel", slackUserId); - sendDmParams.put("text", message); + MultiValueMap getTokenParams = new LinkedMultiValueMap<>(); + getTokenParams.add("client_id", clientId); + getTokenParams.add("client_secret", clientSecret); + getTokenParams.add("redirect_uri", redirectUri); + getTokenParams.add("code", code); - HttpEntity> request = new HttpEntity<>(sendDmParams, sendDmHeaders); - restTemplate.postForEntity(sendDmUrl, request, Map.class); + HttpEntity> request = new HttpEntity<>(getTokenParams, getTokenHeaders); + ResponseEntity response = snsService.sendRestTemplateExchange(SLACK_TOKEN_REQUEST_URL, HttpMethod.POST, request); + return response; } // @Scheduled(cron = "0 0 * * * *") // todo: Quartz로 동적 스케줄링 작성하기 @@ -119,10 +92,32 @@ public void sendMessageReminderBoard(User user) { unSolvedBoard.getProblem().getTitle(), unSolvedBoard.getProblem().getUrl()); - Sns slackInfo = getSlackInfo(user); - sendDm(slackInfo.getSns_id(), slackInfo.getBot_token(), message); + sendSlackDm(user, message); } else { log.info("풀지 못한 문제가 존재하지 않습니다. "); } } + + public void sendSlackDm(User user, String message) { + Sns slackInfo = getSlackInfo(user); + String slackUserId = slackInfo.getSns_id(); + String botAccessToken = slackInfo.getBot_token(); + HttpHeaders httpHeader = new HttpHeaders(); + httpHeader.setContentType(MediaType.APPLICATION_JSON); + httpHeader.setBearerAuth(botAccessToken); + + Map httpBody = new HashMap<>(); + httpBody.put("channel", slackUserId); + httpBody.put("text", message); + + HttpEntity> request = new HttpEntity<>(httpBody, httpHeader); + snsService.sendRestTemplateExchange(SLACK_SEND_DM_REQUEST_URL, HttpMethod.POST, request); + } + + private Sns getSlackInfo(User user) { + Long userId = user.getId(); + Sns slackInfo = snsReporitory.findByUser_IdAndCategory(userId, SnsCategory.SLACK) + .orElseThrow(NoSuchSnsIdException::new); + return slackInfo; + } } diff --git a/src/main/java/com/Alchive/backend/service/SnsService.java b/src/main/java/com/Alchive/backend/service/SnsService.java index 8dce77d..0e4b032 100644 --- a/src/main/java/com/Alchive/backend/service/SnsService.java +++ b/src/main/java/com/Alchive/backend/service/SnsService.java @@ -1,5 +1,6 @@ package com.Alchive.backend.service; +import com.Alchive.backend.config.error.exception.sns.InvalidGrantException; import com.Alchive.backend.config.error.exception.sns.NoSuchSnsIdException; import com.Alchive.backend.domain.sns.Sns; import com.Alchive.backend.domain.user.User; @@ -7,9 +8,17 @@ import com.Alchive.backend.dto.response.SnsResponseDTO; import com.Alchive.backend.repository.SnsReporitory; import jakarta.transaction.Transactional; +import lombok.AllArgsConstructor; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.client.RestTemplate; + +import java.util.Map; @RequiredArgsConstructor @Service @@ -27,4 +36,23 @@ public void createSns(User user, SnsCreateRequest request) { Sns sns = Sns.of(user, request); snsReporitory.save(sns); } + + private RestTemplate restTemplate = new RestTemplate(); + protected ResponseEntity sendRestTemplateExchange(String requestUrl, HttpMethod method, HttpEntity request) { + ResponseEntity response = restTemplate.exchange(requestUrl, method, request, Map.class); + return response; + } + + protected Object parseResponse(ResponseEntity response, String targetParam) { + Map responseBody = response.getBody(); + checkInvalidGrant(responseBody); + Object targetResult = responseBody.get(targetParam); + return targetResult; + } + + private void checkInvalidGrant(Map responseBody) { + if ((responseBody.containsKey("error") && responseBody.get("error").equals("invalid_grant")) || (responseBody.containsKey("ok") && responseBody.get("ok").equals("false"))) { + throw new InvalidGrantException(); + } + } }