From 667114bd49182110c69d6da00c8bc1958455d64d Mon Sep 17 00:00:00 2001 From: nahowo Date: Wed, 30 Oct 2024 15:54:22 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20Slack=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 ++ .../backend/slack/SlackController.java | 23 ++++++++ .../Alchive/backend/slack/SlackService.java | 53 +++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 src/main/java/com/Alchive/backend/slack/SlackController.java create mode 100644 src/main/java/com/Alchive/backend/slack/SlackService.java diff --git a/build.gradle b/build.gradle index d45c799..821ab7e 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,11 @@ dependencies { // Validation - @NotBlank implementation 'org.springframework.boot:spring-boot-starter-validation' + + // Slack API + implementation("com.slack.api:bolt:1.18.0") + implementation("com.slack.api:bolt-servlet:1.18.0") + implementation("com.slack.api:bolt-jetty:1.18.0") } tasks.named('test') { diff --git a/src/main/java/com/Alchive/backend/slack/SlackController.java b/src/main/java/com/Alchive/backend/slack/SlackController.java new file mode 100644 index 0000000..39ef966 --- /dev/null +++ b/src/main/java/com/Alchive/backend/slack/SlackController.java @@ -0,0 +1,23 @@ +package com.Alchive.backend.slack; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping("/slack") +@RequiredArgsConstructor +public class SlackController { + @Autowired + private final SlackService slackService; + + @GetMapping("/test") + public void test() { + slackService.sendMessage(":wave: Hi from a bot written in Alchive!"); + log.info("Slack Test"); + } +} \ No newline at end of file diff --git a/src/main/java/com/Alchive/backend/slack/SlackService.java b/src/main/java/com/Alchive/backend/slack/SlackService.java new file mode 100644 index 0000000..0a65356 --- /dev/null +++ b/src/main/java/com/Alchive/backend/slack/SlackService.java @@ -0,0 +1,53 @@ +package com.Alchive.backend.slack; + +import com.Alchive.backend.domain.board.BoardStatus; +import com.Alchive.backend.dto.request.BoardCreateRequest; +import com.Alchive.backend.dto.response.BoardResponseDTO; +import com.slack.api.Slack; +import com.slack.api.methods.MethodsClient; +import com.slack.api.methods.request.chat.ChatPostMessageRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + + +@Slf4j +@Service +public class SlackService { + @Value("${SLACK_TOKEN}") + private String token; + + private String channel = "#alchive-bot"; + + public void sendMessage(String message) { + try { + MethodsClient methods = Slack.getInstance().methods(token); + + ChatPostMessageRequest request = ChatPostMessageRequest.builder() + .channel(channel) + .text(message) + .build(); + + methods.chatPostMessage(request); + log.info("Slack - Message 전송 완료 : {}", message); + } catch (Exception e) { + log.warn("Slack Error - {}", e.getMessage()); + } + } + + public void sendMessageCreateBoard(BoardCreateRequest boardCreateRequest, BoardResponseDTO board) { + String message = ""; + if (boardCreateRequest.getStatus() != BoardStatus.NOT_SUBMITTED) { + message = String.format(":partying_face: %d. %s 문제를 해결했습니다! \n \n", + boardCreateRequest.getProblemCreateRequest().getNumber(), + boardCreateRequest.getProblemCreateRequest().getTitle(), + board.getId(), board.getId()); + } else { + message = String.format(":round_pushpin: %d. %s 문제를 저장했습니다. \n \n3일 뒤 리마인드 알림을 보내드릴게요! :saluting_face: \n \n<%s|:link: 문제 보러가기>", + boardCreateRequest.getProblemCreateRequest().getNumber(), + boardCreateRequest.getProblemCreateRequest().getTitle(), + boardCreateRequest.getProblemCreateRequest().getUrl()); + } + sendMessage(message); + } +} From 6379cb8349270e800f7a299bbb2daa981644aade Mon Sep 17 00:00:00 2001 From: nahowo Date: Wed, 30 Oct 2024 15:56:31 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=8B=9C=20Slack=20=EC=95=8C=EB=A6=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/Alchive/backend/controller/BoardController.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/Alchive/backend/controller/BoardController.java b/src/main/java/com/Alchive/backend/controller/BoardController.java index bbc8259..583c5a9 100644 --- a/src/main/java/com/Alchive/backend/controller/BoardController.java +++ b/src/main/java/com/Alchive/backend/controller/BoardController.java @@ -8,6 +8,7 @@ import com.Alchive.backend.dto.response.BoardDetailResponseDTO; import com.Alchive.backend.dto.response.BoardResponseDTO; import com.Alchive.backend.service.BoardService; +import com.Alchive.backend.slack.SlackService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; @@ -29,6 +30,7 @@ @RequestMapping("/api/v1/boards") public class BoardController { private final BoardService boardService; + private final SlackService slackService; @Operation(summary = "게시물 저장 여부 조회", description = "게시물의 저장 여부를 조회하는 메서드입니다. ") @PostMapping("/saved") @@ -53,6 +55,7 @@ public ResponseEntity getBoardList(@ModelAttribute PaginationReq @PostMapping("") public ResponseEntity createBoard(HttpServletRequest tokenRequest, @RequestBody @Valid BoardCreateRequest boardCreateRequest) { BoardResponseDTO board = boardService.createBoard(tokenRequest, boardCreateRequest); + slackService.sendMessageCreateBoard(boardCreateRequest, board); return ResponseEntity.ok(ResultResponse.of(BOARD_CREATE_SUCCESS, board)); } From 1d72b519a856118e2b14fa15c9a0464feeec2592 Mon Sep 17 00:00:00 2001 From: nahowo Date: Wed, 30 Oct 2024 18:07:26 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20cron=20=EC=8A=A4=EC=BC=80=EC=A4=84?= =?UTF-8?q?=EB=A7=81=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/repository/BoardRepository.java | 7 +++++ .../Alchive/backend/slack/SlackService.java | 30 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/main/java/com/Alchive/backend/repository/BoardRepository.java b/src/main/java/com/Alchive/backend/repository/BoardRepository.java index 45d00e0..664c8d6 100644 --- a/src/main/java/com/Alchive/backend/repository/BoardRepository.java +++ b/src/main/java/com/Alchive/backend/repository/BoardRepository.java @@ -3,11 +3,18 @@ import com.Alchive.backend.domain.board.Board; import com.Alchive.backend.domain.problem.ProblemPlatform; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.time.LocalDateTime; import java.util.Optional; @Repository public interface BoardRepository extends JpaRepository { Optional findByProblem_PlatformAndProblem_NumberAndUser_Id(ProblemPlatform platform, int problemNumber, Long userId); + + + @Query(value = "SELECT * FROM Board WHERE createdAt <= :threeDaysAgo AND status = 'NOT_SUBMITTED' ORDER BY RAND() LIMIT 1", nativeQuery = true) + Board findUnsolvedBoardAddedBefore(@Param("threeDaysAgo") LocalDateTime threeDaysAgo); } diff --git a/src/main/java/com/Alchive/backend/slack/SlackService.java b/src/main/java/com/Alchive/backend/slack/SlackService.java index 0a65356..46546a8 100644 --- a/src/main/java/com/Alchive/backend/slack/SlackService.java +++ b/src/main/java/com/Alchive/backend/slack/SlackService.java @@ -1,22 +1,35 @@ package com.Alchive.backend.slack; +import com.Alchive.backend.domain.board.Board; import com.Alchive.backend.domain.board.BoardStatus; import com.Alchive.backend.dto.request.BoardCreateRequest; import com.Alchive.backend.dto.response.BoardResponseDTO; +import com.Alchive.backend.repository.BoardRepository; import com.slack.api.Slack; import com.slack.api.methods.MethodsClient; import com.slack.api.methods.request.chat.ChatPostMessageRequest; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; + @Slf4j @Service +@RequiredArgsConstructor +@EnableScheduling +@Configuration public class SlackService { @Value("${SLACK_TOKEN}") private String token; + private final BoardRepository boardRepository; + private String channel = "#alchive-bot"; public void sendMessage(String message) { @@ -50,4 +63,21 @@ public void sendMessageCreateBoard(BoardCreateRequest boardCreateRequest, BoardR } sendMessage(message); } + + @Scheduled(cron = "0 0 18 * * *") + public void sendMessageReminderBoard() { + LocalDateTime threeDaysAgo = LocalDateTime.now().minusDays(3); + + Board unSolvedBoard = boardRepository.findUnsolvedBoardAddedBefore(threeDaysAgo); + + if (unSolvedBoard != null) { + String message = String.format(":star-struck: %d. %s 문제를 아직 풀지 못했어요. \n \n다시 도전해보세요! :facepunch: \n \n<%s|:link: 문제 풀러가기>", + unSolvedBoard.getProblem().getNumber(), + unSolvedBoard.getProblem().getTitle(), + unSolvedBoard.getProblem().getUrl()); + sendMessage(message); + } else { + log.info("풀지 못한 문제가 존재하지 않습니다. "); + } + } } From 9068c14a505e83a7023b288da19b07fe7d78f446 Mon Sep 17 00:00:00 2001 From: nahowo Date: Wed, 30 Oct 2024 19:49:18 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20=EC=98=A4=EB=8B=B5=EC=9D=B8=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EC=95=8C=EB=A6=BC=20=EC=A0=9C=EA=B1=B0,?= =?UTF-8?q?=20=EC=95=8C=EB=A6=BC=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/Alchive/backend/slack/SlackService.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/Alchive/backend/slack/SlackService.java b/src/main/java/com/Alchive/backend/slack/SlackService.java index 46546a8..9a3cb27 100644 --- a/src/main/java/com/Alchive/backend/slack/SlackService.java +++ b/src/main/java/com/Alchive/backend/slack/SlackService.java @@ -17,6 +17,7 @@ import org.springframework.stereotype.Service; import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; @Slf4j @@ -50,30 +51,33 @@ public void sendMessage(String message) { public void sendMessageCreateBoard(BoardCreateRequest boardCreateRequest, BoardResponseDTO board) { String message = ""; - if (boardCreateRequest.getStatus() != BoardStatus.NOT_SUBMITTED) { + if (boardCreateRequest.getStatus() == BoardStatus.CORRECT) { message = String.format(":partying_face: %d. %s 문제를 해결했습니다! \n \n", boardCreateRequest.getProblemCreateRequest().getNumber(), boardCreateRequest.getProblemCreateRequest().getTitle(), board.getId(), board.getId()); - } else { - message = String.format(":round_pushpin: %d. %s 문제를 저장했습니다. \n \n3일 뒤 리마인드 알림을 보내드릴게요! :saluting_face: \n \n<%s|:link: 문제 보러가기>", + } else if (boardCreateRequest.getStatus() == BoardStatus.NOT_SUBMITTED) { + message = String.format(":round_pushpin: %d. %s 문제를 저장했습니다. \n \n리마인드 알림을 보내드릴게요! :saluting_face: \n \n<%s|:link: 문제 보러가기>", boardCreateRequest.getProblemCreateRequest().getNumber(), boardCreateRequest.getProblemCreateRequest().getTitle(), boardCreateRequest.getProblemCreateRequest().getUrl()); + } else { + return; } sendMessage(message); } - @Scheduled(cron = "0 0 18 * * *") + @Scheduled(cron = "0 0 18 * * *") // 매일 오후 6시마다 public void sendMessageReminderBoard() { LocalDateTime threeDaysAgo = LocalDateTime.now().minusDays(3); Board unSolvedBoard = boardRepository.findUnsolvedBoardAddedBefore(threeDaysAgo); if (unSolvedBoard != null) { - String message = String.format(":star-struck: %d. %s 문제를 아직 풀지 못했어요. \n \n다시 도전해보세요! :facepunch: \n \n<%s|:link: 문제 풀러가기>", + String message = String.format(":star-struck: %d. %s 문제를 아직 풀지 못했어요. (%d일 전)\n \n다시 도전해보세요! :facepunch: \n \n<%s|:link: 문제 풀러가기>", unSolvedBoard.getProblem().getNumber(), unSolvedBoard.getProblem().getTitle(), + ChronoUnit.DAYS.between(unSolvedBoard.getCreatedAt(), LocalDateTime.now()), unSolvedBoard.getProblem().getUrl()); sendMessage(message); } else { From 1c3eb4c86cf9f49119643c8383c66a54167d1e86 Mon Sep 17 00:00:00 2001 From: nahowo Date: Thu, 31 Oct 2024 11:58:21 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20swagger=20=EC=84=A4=EC=A0=95,=20sla?= =?UTF-8?q?ck=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/Alchive/backend/slack/SlackController.java | 10 +++++++--- .../java/com/Alchive/backend/slack/SlackService.java | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/Alchive/backend/slack/SlackController.java b/src/main/java/com/Alchive/backend/slack/SlackController.java index 39ef966..68edfab 100644 --- a/src/main/java/com/Alchive/backend/slack/SlackController.java +++ b/src/main/java/com/Alchive/backend/slack/SlackController.java @@ -1,5 +1,7 @@ package com.Alchive.backend.slack; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -8,15 +10,17 @@ import org.springframework.web.bind.annotation.RestController; @Slf4j +@Tag(name = "슬랙", description = "슬랙 관련 API입니다. ") @RestController -@RequestMapping("/slack") +@RequestMapping("/api/v1/slack") @RequiredArgsConstructor public class SlackController { @Autowired private final SlackService slackService; - @GetMapping("/test") - public void test() { + @Operation(summary = "슬랙 봇 설정", description = "슬랙 봇 추가 시 메시지를 전송하는 메서드입니다. ") + @GetMapping("/added") + public void addedSlackBot() { slackService.sendMessage(":wave: Hi from a bot written in Alchive!"); log.info("Slack Test"); } diff --git a/src/main/java/com/Alchive/backend/slack/SlackService.java b/src/main/java/com/Alchive/backend/slack/SlackService.java index 9a3cb27..8efa5b5 100644 --- a/src/main/java/com/Alchive/backend/slack/SlackService.java +++ b/src/main/java/com/Alchive/backend/slack/SlackService.java @@ -74,10 +74,10 @@ public void sendMessageReminderBoard() { Board unSolvedBoard = boardRepository.findUnsolvedBoardAddedBefore(threeDaysAgo); if (unSolvedBoard != null) { - String message = String.format(":star-struck: %d. %s 문제를 아직 풀지 못했어요. (%d일 전)\n \n다시 도전해보세요! :facepunch: \n \n<%s|:link: 문제 풀러가기>", + 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(), - ChronoUnit.DAYS.between(unSolvedBoard.getCreatedAt(), LocalDateTime.now()), unSolvedBoard.getProblem().getUrl()); sendMessage(message); } else {