From 87a42b78c790740fa7c68ccb60067a417c80671f Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Sat, 9 Nov 2024 02:19:18 +0900 Subject: [PATCH 01/28] =?UTF-8?q?feat(HotBoard):=20=ED=95=AB=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/HotBoardsRetrievalController.java | 35 ++++++ .../persistence/BoardPersistenceAdapter.java | 19 +++ .../out/persistence/BoardRepository.java | 4 + .../port/in/RetrieveHotBoardsUseCase.java | 9 ++ .../port/out/RetrieveBoardPort.java | 7 ++ .../service/HotBoardsRetrievalService.java | 111 ++++++++++++++++++ 6 files changed, 185 insertions(+) create mode 100644 src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java create mode 100644 src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java create mode 100644 src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java new file mode 100644 index 000000000..ff2fcbe42 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java @@ -0,0 +1,35 @@ +package page.clab.api.domain.community.board.adapter.in.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; +import page.clab.api.domain.community.board.application.port.in.RetrieveHotBoardsUseCase; +import page.clab.api.global.common.dto.ApiResponse; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/boards") +@RequiredArgsConstructor +@Tag(name = "Community - Board", description = "커뮤니티 게시판") +public class HotBoardsRetrievalController { + + private final RetrieveHotBoardsUseCase retrieveHotBoardsUseCase; + + @Operation(summary = "[G] 커뮤니티 핫 게시글 목록 조회", description = "ROLE_GUEST 이상의 권한이 필요함
" + + "반응(이모지), 댓글 수를 합친 결과가 높은 순으로 size만큼 조회 가능") + @PreAuthorize("hasRole('GUEST')") + @GetMapping("/hot") + public ApiResponse> retrieveHotBoards( + @RequestParam(defaultValue = "5") int size + ) { + List boards = retrieveHotBoardsUseCase.retrieveHotBoards(size); + return ApiResponse.success(boards); + } +} diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardPersistenceAdapter.java b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardPersistenceAdapter.java index 6752552c3..e0fe93a8c 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardPersistenceAdapter.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardPersistenceAdapter.java @@ -10,6 +10,11 @@ import page.clab.api.domain.community.board.domain.BoardCategory; import page.clab.api.global.exception.NotFoundException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + @Component @RequiredArgsConstructor public class BoardPersistenceAdapter implements @@ -40,6 +45,20 @@ public Board findByIdRegardlessOfDeletion(Long boardId) { .orElseThrow(() -> new NotFoundException("[Board] id: " + boardId + "에 해당하는 게시글이 존재하지 않습니다.")); } + @Override + public List findAllWithinDateRange(LocalDateTime start, LocalDateTime end) { + return boardRepository.findAllWithinDateRange(start, end).stream() + .map(boardMapper::toDomain) + .collect(Collectors.toList()); + } + + @Override + public List findAll() { + return boardRepository.findAll().stream() + .map(boardMapper::toDomain) + .collect(Collectors.toList()); + } + @Override public Page findAllByCategory(BoardCategory category, Pageable pageable) { return boardRepository.findAllByCategory(category, pageable) diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardRepository.java b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardRepository.java index bbb0dc8ed..31d51db74 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardRepository.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardRepository.java @@ -1,5 +1,6 @@ package page.clab.api.domain.community.board.adapter.out.persistence; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import org.springframework.data.domain.Page; @@ -16,6 +17,9 @@ public interface BoardRepository extends JpaRepository { @Query("SELECT b FROM BoardJpaEntity b WHERE b.memberId = ?1 AND b.isDeleted = false") Page findAllByMemberIdAndIsDeletedFalse(String memberId, Pageable pageable); + @Query("SELECT b FROM BoardJpaEntity b WHERE b.createdAt BETWEEN :start AND :end AND b.isDeleted = false") + List findAllWithinDateRange(LocalDateTime start, LocalDateTime end); + Page findAllByCategory(BoardCategory category, Pageable pageable); @Query(value = "SELECT b.* FROM board b WHERE b.is_deleted = true", nativeQuery = true) diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java b/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java new file mode 100644 index 000000000..a77c0479b --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java @@ -0,0 +1,9 @@ +package page.clab.api.domain.community.board.application.port.in; + +import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; + +import java.util.List; + +public interface RetrieveHotBoardsUseCase { + List retrieveHotBoards(int size); +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardPort.java b/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardPort.java index 54aabd972..2cddcde45 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardPort.java +++ b/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardPort.java @@ -5,12 +5,19 @@ import page.clab.api.domain.community.board.domain.Board; import page.clab.api.domain.community.board.domain.BoardCategory; +import java.time.LocalDateTime; +import java.util.List; + public interface RetrieveBoardPort { Board getById(Long boardId); Board findByIdRegardlessOfDeletion(Long boardId); + List findAllWithinDateRange(LocalDateTime from, LocalDateTime to); + + List findAll(); + Page findAll(Pageable pageable); Page findAllByCategory(BoardCategory category, Pageable pageable); diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java new file mode 100644 index 000000000..2d856e18d --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java @@ -0,0 +1,111 @@ +package page.clab.api.domain.community.board.application.service; + +import com.drew.lang.annotations.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import page.clab.api.domain.community.board.application.dto.mapper.BoardDtoMapper; +import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; +import page.clab.api.domain.community.board.application.port.in.RetrieveHotBoardsUseCase; +import page.clab.api.domain.community.board.application.port.out.RetrieveBoardEmojiPort; +import page.clab.api.domain.community.board.application.port.out.RetrieveBoardPort; +import page.clab.api.domain.community.board.domain.Board; +import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto; +import page.clab.api.external.community.comment.application.port.ExternalRetrieveCommentUseCase; +import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class HotBoardsRetrievalService implements RetrieveHotBoardsUseCase { + + private final RetrieveBoardPort retrieveBoardPort; + private final RetrieveBoardEmojiPort retrieveBoardEmojiPort; + private final ExternalRetrieveCommentUseCase externalRetrieveCommentUseCase; + private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; + private final BoardDtoMapper mapper; + + @Transactional + @Override + public List retrieveHotBoards(int size) { + List hotBoards = getHotBoards(size); + + return hotBoards.stream() + .map(board -> mapToBoardListResponseDto(board, getMemberDetailedInfoByBoard(board))) + .toList(); + } + + private List getHotBoards(int size) { + List hotBoards = getHotBoardsForWeek(1, size); + + // 만약 게시글의 총 개수가 size보다 적다면 모든 게시글 반환 + List allBoards = retrieveBoardPort.findAll(); + if (allBoards.size() < size) { + return sortAndLimit(allBoards.size(), allBoards); + } + + int weeksAgo = 2; + // 필요한 수량을 확보할 때까지 반복해서 이전 주로 이동하여 Hot 게시글 보충 + while (hotBoards.size() < size) { + Board additionalBoard = getMostRecentHotBoardForWeek(weeksAgo++, size); + if (additionalBoard != null) { + hotBoards.add(additionalBoard); + } + } + return hotBoards; + } + + private List getHotBoardsForWeek(int weeksAgo, int size) { + LocalDateTime startOfWeek = LocalDate.now().minusWeeks(weeksAgo).with(java.time.DayOfWeek.MONDAY).atStartOfDay(); + LocalDateTime endOfWeek = LocalDate.now().minusWeeks(weeksAgo).with(java.time.DayOfWeek.SUNDAY).atTime(23, 59, 59); + + List boardsForWeek = retrieveBoardPort.findAllWithinDateRange(startOfWeek, endOfWeek); + + return sortAndLimit(size, boardsForWeek); + } + + private List sortAndLimit(int size, List boardsForWeek) { + if (boardsForWeek == null) { + return null; + } + + return boardsForWeek.stream() + .sorted(Comparator + .comparingInt(this::getTotalReactionCount).reversed() + .thenComparing(Board::getCreatedAt, Comparator.reverseOrder())) + .limit(size) + .collect(Collectors.toList()); + } + + private Board getMostRecentHotBoardForWeek(int weeksAgo, int size) { + // 특정 주의 핫 게시글을 가져오고, 그 중 가장 최근에 작성된 게시글 선택 + List topHotBoardsForWeek = getHotBoardsForWeek(weeksAgo, size); + + return topHotBoardsForWeek.stream() + .max(Comparator.comparing(Board::getCreatedAt)) + .orElse(null); + } + + private int getTotalReactionCount(Board board) { + Long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId()); + int emojiCount = retrieveBoardEmojiPort.findEmojiClickCountsByBoardId(board.getId(), null).size(); + + return commentCount.intValue() + emojiCount; + } + + private MemberDetailedInfoDto getMemberDetailedInfoByBoard(Board board) { + return externalRetrieveMemberUseCase.getMemberDetailedInfoById(board.getMemberId()); + } + + @NotNull + private BoardListResponseDto mapToBoardListResponseDto(Board board, MemberDetailedInfoDto memberInfo) { + Long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId()); + + return mapper.toListDto(board, memberInfo, commentCount); + } +} From 422d3bde0c8b7e15ce393b742b69359c7cfd62be Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Thu, 14 Nov 2024 08:37:53 +0900 Subject: [PATCH 02/28] =?UTF-8?q?refactor(HotBoardsRetrievalService):=20si?= =?UTF-8?q?ze=EB=B3=B4=EB=8B=A4=20=EC=9E=91=EC=9D=80=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EC=A0=84=EC=B2=B4=20=EB=B0=98=ED=99=98=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=9C=84=EC=B9=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/application/service/HotBoardsRetrievalService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java index 2d856e18d..b242ed309 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java @@ -41,14 +41,14 @@ public List retrieveHotBoards(int size) { } private List getHotBoards(int size) { - List hotBoards = getHotBoardsForWeek(1, size); - // 만약 게시글의 총 개수가 size보다 적다면 모든 게시글 반환 List allBoards = retrieveBoardPort.findAll(); if (allBoards.size() < size) { return sortAndLimit(allBoards.size(), allBoards); } + List hotBoards = getHotBoardsForWeek(1, size); + int weeksAgo = 2; // 필요한 수량을 확보할 때까지 반복해서 이전 주로 이동하여 Hot 게시글 보충 while (hotBoards.size() < size) { From 0f158ebd4bdc5a7977df95b7f60c387ebfe5ef6c Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Thu, 14 Nov 2024 08:44:20 +0900 Subject: [PATCH 03/28] =?UTF-8?q?refactor(HotBoards):=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=EB=AA=85=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/out/persistence/BoardPersistenceAdapter.java | 5 ++--- .../board/application/port/out/RetrieveBoardPort.java | 2 +- .../application/service/HotBoardsRetrievalService.java | 6 +++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardPersistenceAdapter.java b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardPersistenceAdapter.java index e0fe93a8c..281a2acdb 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardPersistenceAdapter.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardPersistenceAdapter.java @@ -10,7 +10,6 @@ import page.clab.api.domain.community.board.domain.BoardCategory; import page.clab.api.global.exception.NotFoundException; -import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @@ -46,8 +45,8 @@ public Board findByIdRegardlessOfDeletion(Long boardId) { } @Override - public List findAllWithinDateRange(LocalDateTime start, LocalDateTime end) { - return boardRepository.findAllWithinDateRange(start, end).stream() + public List findAllWithinDateRange(LocalDateTime startDate, LocalDateTime endDate) { + return boardRepository.findAllWithinDateRange(startDate, endDate).stream() .map(boardMapper::toDomain) .collect(Collectors.toList()); } diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardPort.java b/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardPort.java index 2cddcde45..5e42bcc25 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardPort.java +++ b/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardPort.java @@ -14,7 +14,7 @@ public interface RetrieveBoardPort { Board findByIdRegardlessOfDeletion(Long boardId); - List findAllWithinDateRange(LocalDateTime from, LocalDateTime to); + List findAllWithinDateRange(LocalDateTime startDate, LocalDateTime endDate); List findAll(); diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java index b242ed309..325e2bece 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java @@ -61,10 +61,10 @@ private List getHotBoards(int size) { } private List getHotBoardsForWeek(int weeksAgo, int size) { - LocalDateTime startOfWeek = LocalDate.now().minusWeeks(weeksAgo).with(java.time.DayOfWeek.MONDAY).atStartOfDay(); - LocalDateTime endOfWeek = LocalDate.now().minusWeeks(weeksAgo).with(java.time.DayOfWeek.SUNDAY).atTime(23, 59, 59); + LocalDateTime startDate = LocalDate.now().minusWeeks(weeksAgo).with(java.time.DayOfWeek.MONDAY).atStartOfDay(); + LocalDateTime endDate = LocalDate.now().minusWeeks(weeksAgo).with(java.time.DayOfWeek.SUNDAY).atTime(23, 59, 59); - List boardsForWeek = retrieveBoardPort.findAllWithinDateRange(startOfWeek, endOfWeek); + List boardsForWeek = retrieveBoardPort.findAllWithinDateRange(startDate, endDate); return sortAndLimit(size, boardsForWeek); } From 15ba5a17da550d7cec46e8440de76eb2c3cdeedf Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Thu, 14 Nov 2024 12:59:47 +0900 Subject: [PATCH 04/28] =?UTF-8?q?refactor(HotBoards):=20size=EA=B0=80=20?= =?UTF-8?q?=EC=9D=8C=EC=88=98=EC=9D=B8=EC=A7=80=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/adapter/in/web/HotBoardsRetrievalController.java | 2 ++ src/main/resources/messages.properties | 1 + 2 files changed, 3 insertions(+) diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java index ff2fcbe42..c4658b894 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.Min; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; @@ -27,6 +28,7 @@ public class HotBoardsRetrievalController { @PreAuthorize("hasRole('GUEST')") @GetMapping("/hot") public ApiResponse> retrieveHotBoards( + @Min(message = "min.board.size", value = 0) @RequestParam(defaultValue = "5") int size ) { List boards = retrieveHotBoardsUseCase.retrieveHotBoards(size); diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 6e7db6fb5..d8f546c4a 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -61,6 +61,7 @@ email.member.email=올바른 이메일 형식으로 입력하세요. min.application.grade=학년은 {value}학년 이상이어야 합니다. min.donation.amount=금액은 {value}원 이상이어야 합니다. min.member.grade=최소값은 {value} 이상이어야 합니다. +min.board.size=최소값은 {value} 이상이어야 합니다. max.application.grade=학년은 {value}학년 이하여야 합니다. max.member.grade=최대값은 {value} 이하여야 합니다. url.application.githubUrl=올바른 URL 형식으로 입력하세요. From 5cd56f0be429171e07c49a082c86d0d669804215 Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Thu, 14 Nov 2024 13:49:25 +0900 Subject: [PATCH 05/28] =?UTF-8?q?refactor(HotBoards):=20=EB=B6=80=EC=A1=B1?= =?UTF-8?q?=ED=95=9C=20=EC=9D=B8=EA=B8=B0=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=88=98=EB=9F=89=20=EC=B6=94=EA=B0=80=20=EC=A0=84=EB=9E=B5=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/HotBoardsRetrievalService.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java index 325e2bece..e9532d237 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java @@ -52,9 +52,9 @@ private List getHotBoards(int size) { int weeksAgo = 2; // 필요한 수량을 확보할 때까지 반복해서 이전 주로 이동하여 Hot 게시글 보충 while (hotBoards.size() < size) { - Board additionalBoard = getMostRecentHotBoardForWeek(weeksAgo++, size); - if (additionalBoard != null) { - hotBoards.add(additionalBoard); + List additionalBoards = getLatestHotBoardForWeek(weeksAgo++, size - hotBoards.size()); + if (additionalBoards != null && !additionalBoards.isEmpty()) { + hotBoards.addAll(additionalBoards); } } return hotBoards; @@ -82,13 +82,13 @@ private List sortAndLimit(int size, List boardsForWeek) { .collect(Collectors.toList()); } - private Board getMostRecentHotBoardForWeek(int weeksAgo, int size) { - // 특정 주의 핫 게시글을 가져오고, 그 중 가장 최근에 작성된 게시글 선택 + private List getLatestHotBoardForWeek(int weeksAgo, int size) { + List topHotBoardsForWeek = getHotBoardsForWeek(weeksAgo, size); return topHotBoardsForWeek.stream() - .max(Comparator.comparing(Board::getCreatedAt)) - .orElse(null); + .sorted(Comparator.comparing(Board::getCreatedAt).reversed()) + .toList(); } private int getTotalReactionCount(Board board) { From d943ffeecea279793f1d4820d229ef91ccbdfcdf Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Thu, 14 Nov 2024 13:51:40 +0900 Subject: [PATCH 06/28] refactor(HotBoards): import DayOfWeek --- .../board/application/service/HotBoardsRetrievalService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java index e9532d237..7be845fe4 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java @@ -14,6 +14,7 @@ import page.clab.api.external.community.comment.application.port.ExternalRetrieveCommentUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; +import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Comparator; @@ -61,8 +62,8 @@ private List getHotBoards(int size) { } private List getHotBoardsForWeek(int weeksAgo, int size) { - LocalDateTime startDate = LocalDate.now().minusWeeks(weeksAgo).with(java.time.DayOfWeek.MONDAY).atStartOfDay(); - LocalDateTime endDate = LocalDate.now().minusWeeks(weeksAgo).with(java.time.DayOfWeek.SUNDAY).atTime(23, 59, 59); + LocalDateTime startDate = LocalDate.now().minusWeeks(weeksAgo).with(DayOfWeek.MONDAY).atStartOfDay(); + LocalDateTime endDate = LocalDate.now().minusWeeks(weeksAgo).with(DayOfWeek.SUNDAY).atTime(23, 59, 59); List boardsForWeek = retrieveBoardPort.findAllWithinDateRange(startDate, endDate); From bab622701f8e78230a1368e6c6f23695c67a35c3 Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Thu, 14 Nov 2024 14:03:04 +0900 Subject: [PATCH 07/28] =?UTF-8?q?refactor(HotBoards):=20sortAndLimit=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/HotBoardsRetrievalService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java index 7be845fe4..73cd12033 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java @@ -45,7 +45,7 @@ private List getHotBoards(int size) { // 만약 게시글의 총 개수가 size보다 적다면 모든 게시글 반환 List allBoards = retrieveBoardPort.findAll(); if (allBoards.size() < size) { - return sortAndLimit(allBoards.size(), allBoards); + return sortBoardsByReactionAndDateWithLimit(allBoards.size(), allBoards); } List hotBoards = getHotBoardsForWeek(1, size); @@ -67,10 +67,10 @@ private List getHotBoardsForWeek(int weeksAgo, int size) { List boardsForWeek = retrieveBoardPort.findAllWithinDateRange(startDate, endDate); - return sortAndLimit(size, boardsForWeek); + return sortBoardsByReactionAndDateWithLimit(size, boardsForWeek); } - private List sortAndLimit(int size, List boardsForWeek) { + private List sortBoardsByReactionAndDateWithLimit(int size, List boardsForWeek) { if (boardsForWeek == null) { return null; } From 499a16ef7c80f4dd64e82daf765ddae896b6754f Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Thu, 14 Nov 2024 14:14:23 +0900 Subject: [PATCH 08/28] =?UTF-8?q?refactor(HotBoards):=20=ED=95=AB=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=EC=9D=84=20=EC=9D=B8=EA=B8=B0=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=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 --- .../board/adapter/in/web/HotBoardsRetrievalController.java | 2 +- .../board/application/service/HotBoardsRetrievalService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java index c4658b894..a5a85652f 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java @@ -23,7 +23,7 @@ public class HotBoardsRetrievalController { private final RetrieveHotBoardsUseCase retrieveHotBoardsUseCase; - @Operation(summary = "[G] 커뮤니티 핫 게시글 목록 조회", description = "ROLE_GUEST 이상의 권한이 필요함
" + + @Operation(summary = "[G] 커뮤니티 인기 게시글 목록 조회", description = "ROLE_GUEST 이상의 권한이 필요함
" + "반응(이모지), 댓글 수를 합친 결과가 높은 순으로 size만큼 조회 가능") @PreAuthorize("hasRole('GUEST')") @GetMapping("/hot") diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java index 73cd12033..f9ae2a18d 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java @@ -51,7 +51,7 @@ private List getHotBoards(int size) { List hotBoards = getHotBoardsForWeek(1, size); int weeksAgo = 2; - // 필요한 수량을 확보할 때까지 반복해서 이전 주로 이동하여 Hot 게시글 보충 + // 필요한 수량을 확보할 때까지 반복해서 이전 주로 이동하여 인기 게시글 보충 while (hotBoards.size() < size) { List additionalBoards = getLatestHotBoardForWeek(weeksAgo++, size - hotBoards.size()); if (additionalBoards != null && !additionalBoards.isEmpty()) { From 2560a09c6e7f38c1610efd6d49795d2a1eba92c4 Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Fri, 15 Nov 2024 17:14:54 +0900 Subject: [PATCH 09/28] =?UTF-8?q?refactor(HotBoards):=20=EC=9D=B8=EA=B8=B0?= =?UTF-8?q?=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=84=A0=EC=A0=95=EC=97=90=20?= =?UTF-8?q?Strategy=20=ED=8C=A8=ED=84=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/HotBoardsRetrievalController.java | 9 +- .../port/in/RetrieveHotBoardsUseCase.java | 3 +- .../DefaultHotBoardsRetrievalService.java | 111 ++++++++++++++++++ .../service/HotBoardsRetrievalService.java | 101 +--------------- .../service/HotBoardsStrategy.java | 9 ++ .../board/domain/HotBoardStrategyType.java | 14 +++ 6 files changed, 148 insertions(+), 99 deletions(-) create mode 100644 src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardsRetrievalService.java create mode 100644 src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsStrategy.java create mode 100644 src/main/java/page/clab/api/domain/community/board/domain/HotBoardStrategyType.java diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java index a5a85652f..2d69d45fb 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java @@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RestController; import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; import page.clab.api.domain.community.board.application.port.in.RetrieveHotBoardsUseCase; +import page.clab.api.domain.community.board.domain.HotBoardStrategyType; import page.clab.api.global.common.dto.ApiResponse; import java.util.List; @@ -24,14 +25,16 @@ public class HotBoardsRetrievalController { private final RetrieveHotBoardsUseCase retrieveHotBoardsUseCase; @Operation(summary = "[G] 커뮤니티 인기 게시글 목록 조회", description = "ROLE_GUEST 이상의 권한이 필요함
" + - "반응(이모지), 댓글 수를 합친 결과가 높은 순으로 size만큼 조회 가능") + "반응(이모지), 댓글 수를 합친 결과가 높은 순으로 size만큼 조회 가능
" + + "인기 게시글 선정 타입 설정 가능") @PreAuthorize("hasRole('GUEST')") @GetMapping("/hot") public ApiResponse> retrieveHotBoards( @Min(message = "min.board.size", value = 0) - @RequestParam(defaultValue = "5") int size + @RequestParam(name = "size", defaultValue = "5") int size, + @RequestParam(name = "type") HotBoardStrategyType type ) { - List boards = retrieveHotBoardsUseCase.retrieveHotBoards(size); + List boards = retrieveHotBoardsUseCase.retrieveHotBoards(size, type); return ApiResponse.success(boards); } } diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java b/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java index a77c0479b..59b4f0a79 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java +++ b/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java @@ -1,9 +1,10 @@ package page.clab.api.domain.community.board.application.port.in; import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; +import page.clab.api.domain.community.board.domain.HotBoardStrategyType; import java.util.List; public interface RetrieveHotBoardsUseCase { - List retrieveHotBoards(int size); + List retrieveHotBoards(int size, HotBoardStrategyType type); } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardsRetrievalService.java new file mode 100644 index 000000000..f0370345b --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardsRetrievalService.java @@ -0,0 +1,111 @@ +package page.clab.api.domain.community.board.application.service; + +import com.drew.lang.annotations.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import page.clab.api.domain.community.board.application.dto.mapper.BoardDtoMapper; +import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; +import page.clab.api.domain.community.board.application.port.out.RetrieveBoardEmojiPort; +import page.clab.api.domain.community.board.application.port.out.RetrieveBoardPort; +import page.clab.api.domain.community.board.domain.Board; +import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto; +import page.clab.api.external.community.comment.application.port.ExternalRetrieveCommentUseCase; +import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +@Service("default") +@RequiredArgsConstructor +public class DefaultHotBoardsRetrievalService implements HotBoardsStrategy { + + private final RetrieveBoardPort retrieveBoardPort; + private final RetrieveBoardEmojiPort retrieveBoardEmojiPort; + private final ExternalRetrieveCommentUseCase externalRetrieveCommentUseCase; + private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; + private final BoardDtoMapper mapper; + + @Transactional + @Override + public List retrieveHotBoards(int size) { + List hotBoards = getHotBoards(size); + + return hotBoards.stream() + .map(board -> mapToBoardListResponseDto(board, getMemberDetailedInfoByBoard(board))) + .toList(); + } + + private List getHotBoards(int size) { + // 만약 게시글의 총 개수가 size보다 적다면 모든 게시글 반환 + List allBoards = retrieveBoardPort.findAll(); + if (allBoards.size() < size) { + return sortBoardsByReactionAndDateWithLimit(allBoards.size(), allBoards); + } + + List hotBoards = getHotBoardsForWeek(1, size); + + int weeksAgo = 2; + // 필요한 수량을 확보할 때까지 반복해서 이전 주로 이동하여 인기 게시글 보충 + while (hotBoards.size() < size) { + List additionalBoards = getLatestHotBoardForWeek(weeksAgo++, size - hotBoards.size()); + if (additionalBoards != null && !additionalBoards.isEmpty()) { + hotBoards.addAll(additionalBoards); + } + } + return hotBoards; + } + + private List getHotBoardsForWeek(int weeksAgo, int size) { + LocalDateTime startDate = LocalDate.now().minusWeeks(weeksAgo).with(DayOfWeek.MONDAY).atStartOfDay(); + LocalDateTime endDate = LocalDate.now().minusWeeks(weeksAgo).with(DayOfWeek.SUNDAY).atTime(23, 59, 59); + + List boardsForWeek = retrieveBoardPort.findAllWithinDateRange(startDate, endDate); + + return sortBoardsByReactionAndDateWithLimit(size, boardsForWeek); + } + + private List sortBoardsByReactionAndDateWithLimit(int size, List boardsForWeek) { + if (boardsForWeek == null) { + return null; + } + + return boardsForWeek.stream() + .sorted(Comparator + .comparingInt(this::getTotalReactionCount).reversed() + .thenComparing(Board::getCreatedAt, Comparator.reverseOrder())) + .limit(size) + .collect(Collectors.toList()); + } + + private List getLatestHotBoardForWeek(int weeksAgo, int size) { + + List topHotBoardsForWeek = getHotBoardsForWeek(weeksAgo, size); + + return topHotBoardsForWeek.stream() + .sorted(Comparator.comparing(Board::getCreatedAt).reversed()) + .toList(); + } + + private int getTotalReactionCount(Board board) { + Long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId()); + int emojiCount = retrieveBoardEmojiPort.findEmojiClickCountsByBoardId(board.getId(), null).size(); + + return commentCount.intValue() + emojiCount; + } + + private MemberDetailedInfoDto getMemberDetailedInfoByBoard(Board board) { + return externalRetrieveMemberUseCase.getMemberDetailedInfoById(board.getMemberId()); + } + + @NotNull + private BoardListResponseDto mapToBoardListResponseDto(Board board, MemberDetailedInfoDto memberInfo) { + Long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId()); + + return mapper.toListDto(board, memberInfo, commentCount); + } +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java index f9ae2a18d..90ec3c9ad 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java @@ -1,112 +1,23 @@ package page.clab.api.domain.community.board.application.service; -import com.drew.lang.annotations.NotNull; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import page.clab.api.domain.community.board.application.dto.mapper.BoardDtoMapper; import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; import page.clab.api.domain.community.board.application.port.in.RetrieveHotBoardsUseCase; -import page.clab.api.domain.community.board.application.port.out.RetrieveBoardEmojiPort; -import page.clab.api.domain.community.board.application.port.out.RetrieveBoardPort; -import page.clab.api.domain.community.board.domain.Board; -import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto; -import page.clab.api.external.community.comment.application.port.ExternalRetrieveCommentUseCase; -import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; +import page.clab.api.domain.community.board.domain.HotBoardStrategyType; -import java.time.DayOfWeek; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.Comparator; import java.util.List; -import java.util.stream.Collectors; +import java.util.Map; @Service @RequiredArgsConstructor public class HotBoardsRetrievalService implements RetrieveHotBoardsUseCase { - private final RetrieveBoardPort retrieveBoardPort; - private final RetrieveBoardEmojiPort retrieveBoardEmojiPort; - private final ExternalRetrieveCommentUseCase externalRetrieveCommentUseCase; - private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; - private final BoardDtoMapper mapper; + private final Map strategies; - @Transactional @Override - public List retrieveHotBoards(int size) { - List hotBoards = getHotBoards(size); - - return hotBoards.stream() - .map(board -> mapToBoardListResponseDto(board, getMemberDetailedInfoByBoard(board))) - .toList(); - } - - private List getHotBoards(int size) { - // 만약 게시글의 총 개수가 size보다 적다면 모든 게시글 반환 - List allBoards = retrieveBoardPort.findAll(); - if (allBoards.size() < size) { - return sortBoardsByReactionAndDateWithLimit(allBoards.size(), allBoards); - } - - List hotBoards = getHotBoardsForWeek(1, size); - - int weeksAgo = 2; - // 필요한 수량을 확보할 때까지 반복해서 이전 주로 이동하여 인기 게시글 보충 - while (hotBoards.size() < size) { - List additionalBoards = getLatestHotBoardForWeek(weeksAgo++, size - hotBoards.size()); - if (additionalBoards != null && !additionalBoards.isEmpty()) { - hotBoards.addAll(additionalBoards); - } - } - return hotBoards; - } - - private List getHotBoardsForWeek(int weeksAgo, int size) { - LocalDateTime startDate = LocalDate.now().minusWeeks(weeksAgo).with(DayOfWeek.MONDAY).atStartOfDay(); - LocalDateTime endDate = LocalDate.now().minusWeeks(weeksAgo).with(DayOfWeek.SUNDAY).atTime(23, 59, 59); - - List boardsForWeek = retrieveBoardPort.findAllWithinDateRange(startDate, endDate); - - return sortBoardsByReactionAndDateWithLimit(size, boardsForWeek); - } - - private List sortBoardsByReactionAndDateWithLimit(int size, List boardsForWeek) { - if (boardsForWeek == null) { - return null; - } - - return boardsForWeek.stream() - .sorted(Comparator - .comparingInt(this::getTotalReactionCount).reversed() - .thenComparing(Board::getCreatedAt, Comparator.reverseOrder())) - .limit(size) - .collect(Collectors.toList()); - } - - private List getLatestHotBoardForWeek(int weeksAgo, int size) { - - List topHotBoardsForWeek = getHotBoardsForWeek(weeksAgo, size); - - return topHotBoardsForWeek.stream() - .sorted(Comparator.comparing(Board::getCreatedAt).reversed()) - .toList(); - } - - private int getTotalReactionCount(Board board) { - Long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId()); - int emojiCount = retrieveBoardEmojiPort.findEmojiClickCountsByBoardId(board.getId(), null).size(); - - return commentCount.intValue() + emojiCount; - } - - private MemberDetailedInfoDto getMemberDetailedInfoByBoard(Board board) { - return externalRetrieveMemberUseCase.getMemberDetailedInfoById(board.getMemberId()); - } - - @NotNull - private BoardListResponseDto mapToBoardListResponseDto(Board board, MemberDetailedInfoDto memberInfo) { - Long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId()); - - return mapper.toListDto(board, memberInfo, commentCount); + public List retrieveHotBoards(int size, HotBoardStrategyType type) { + HotBoardsStrategy hotBoardsStrategy = strategies.get(type.getKey()); + return hotBoardsStrategy.retrieveHotBoards(size); } } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsStrategy.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsStrategy.java new file mode 100644 index 000000000..7a9f0311d --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsStrategy.java @@ -0,0 +1,9 @@ +package page.clab.api.domain.community.board.application.service; + +import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; + +import java.util.List; + +public interface HotBoardsStrategy { + List retrieveHotBoards(int size); +} diff --git a/src/main/java/page/clab/api/domain/community/board/domain/HotBoardStrategyType.java b/src/main/java/page/clab/api/domain/community/board/domain/HotBoardStrategyType.java new file mode 100644 index 000000000..5b053951f --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/domain/HotBoardStrategyType.java @@ -0,0 +1,14 @@ +package page.clab.api.domain.community.board.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum HotBoardStrategyType { + + DEFAULT("default", "댓글, 반응순 정렬"); + + private final String key; + private final String description; +} From 901ee99ec9728b636c32f3fa14cbc69130937e44 Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Wed, 27 Nov 2024 09:04:15 +0900 Subject: [PATCH 10/28] =?UTF-8?q?refactor(HotBoards):=20=EC=9D=B8=EA=B8=B0?= =?UTF-8?q?=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=A0=80=EC=9E=A5=EC=97=90=20?= =?UTF-8?q?Redis=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/HotBoardsRetrievalController.java | 6 +-- .../RedisHotBoardPersistenceAdapter.java | 38 +++++++++++++++ .../port/in/RetrieveHotBoardsUseCase.java | 2 +- .../port/out/RegisterHotBoardPort.java | 5 ++ .../port/out/RemoveHotBoardPort.java | 5 ++ .../port/out/RetrieveHotBoardPort.java | 7 +++ ...rvice.java => DefaultHotBoardService.java} | 46 +++++++++++++++---- .../service/HotBoardsRetrievalService.java | 4 +- .../service/HotBoardsStrategy.java | 2 +- 9 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/RedisHotBoardPersistenceAdapter.java create mode 100644 src/main/java/page/clab/api/domain/community/board/application/port/out/RegisterHotBoardPort.java create mode 100644 src/main/java/page/clab/api/domain/community/board/application/port/out/RemoveHotBoardPort.java create mode 100644 src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveHotBoardPort.java rename src/main/java/page/clab/api/domain/community/board/application/service/{DefaultHotBoardsRetrievalService.java => DefaultHotBoardService.java} (72%) diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java index 2d69d45fb..bd7151a5e 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java @@ -2,7 +2,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.constraints.Min; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; @@ -25,16 +24,13 @@ public class HotBoardsRetrievalController { private final RetrieveHotBoardsUseCase retrieveHotBoardsUseCase; @Operation(summary = "[G] 커뮤니티 인기 게시글 목록 조회", description = "ROLE_GUEST 이상의 권한이 필요함
" + - "반응(이모지), 댓글 수를 합친 결과가 높은 순으로 size만큼 조회 가능
" + "인기 게시글 선정 타입 설정 가능") @PreAuthorize("hasRole('GUEST')") @GetMapping("/hot") public ApiResponse> retrieveHotBoards( - @Min(message = "min.board.size", value = 0) - @RequestParam(name = "size", defaultValue = "5") int size, @RequestParam(name = "type") HotBoardStrategyType type ) { - List boards = retrieveHotBoardsUseCase.retrieveHotBoards(size, type); + List boards = retrieveHotBoardsUseCase.retrieveHotBoards(type); return ApiResponse.success(boards); } } diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/RedisHotBoardPersistenceAdapter.java b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/RedisHotBoardPersistenceAdapter.java new file mode 100644 index 000000000..8ebd20644 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/RedisHotBoardPersistenceAdapter.java @@ -0,0 +1,38 @@ +package page.clab.api.domain.community.board.adapter.out.persistence; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; +import page.clab.api.domain.community.board.application.port.out.RegisterHotBoardPort; +import page.clab.api.domain.community.board.application.port.out.RemoveHotBoardPort; +import page.clab.api.domain.community.board.application.port.out.RetrieveHotBoardPort; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class RedisHotBoardPersistenceAdapter implements + RegisterHotBoardPort, + RetrieveHotBoardPort, + RemoveHotBoardPort { + + private static final String HOT_BOARDS_KEY = "hotBoards"; + + private final RedisTemplate redisTemplate; + + @Override + public void save(String boardId) { + redisTemplate.opsForList().rightPush(HOT_BOARDS_KEY, boardId); + } + + @Override + public List findAll() { + List hotBoards = redisTemplate.opsForList().range(HOT_BOARDS_KEY, 0, -1); + return (hotBoards != null) ? hotBoards : List.of(); + } + + @Override + public void clearHotBoard() { + redisTemplate.delete(HOT_BOARDS_KEY); + } +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java b/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java index 59b4f0a79..2278e44ab 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java +++ b/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java @@ -6,5 +6,5 @@ import java.util.List; public interface RetrieveHotBoardsUseCase { - List retrieveHotBoards(int size, HotBoardStrategyType type); + List retrieveHotBoards(HotBoardStrategyType type); } diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/out/RegisterHotBoardPort.java b/src/main/java/page/clab/api/domain/community/board/application/port/out/RegisterHotBoardPort.java new file mode 100644 index 000000000..20e66b609 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/port/out/RegisterHotBoardPort.java @@ -0,0 +1,5 @@ +package page.clab.api.domain.community.board.application.port.out; + +public interface RegisterHotBoardPort { + void save(String boardId); +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/out/RemoveHotBoardPort.java b/src/main/java/page/clab/api/domain/community/board/application/port/out/RemoveHotBoardPort.java new file mode 100644 index 000000000..c0fbe82c6 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/port/out/RemoveHotBoardPort.java @@ -0,0 +1,5 @@ +package page.clab.api.domain.community.board.application.port.out; + +public interface RemoveHotBoardPort { + void clearHotBoard(); +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveHotBoardPort.java b/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveHotBoardPort.java new file mode 100644 index 000000000..c356f8d6e --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveHotBoardPort.java @@ -0,0 +1,7 @@ +package page.clab.api.domain.community.board.application.port.out; + +import java.util.List; + +public interface RetrieveHotBoardPort { + List findAll(); +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardService.java similarity index 72% rename from src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardsRetrievalService.java rename to src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardService.java index f0370345b..d668537df 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardsRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardService.java @@ -2,12 +2,16 @@ import com.drew.lang.annotations.NotNull; import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.community.board.application.dto.mapper.BoardDtoMapper; import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; +import page.clab.api.domain.community.board.application.port.out.RegisterHotBoardPort; +import page.clab.api.domain.community.board.application.port.out.RemoveHotBoardPort; import page.clab.api.domain.community.board.application.port.out.RetrieveBoardEmojiPort; import page.clab.api.domain.community.board.application.port.out.RetrieveBoardPort; +import page.clab.api.domain.community.board.application.port.out.RetrieveHotBoardPort; import page.clab.api.domain.community.board.domain.Board; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto; import page.clab.api.external.community.comment.application.port.ExternalRetrieveCommentUseCase; @@ -22,9 +26,12 @@ @Service("default") @RequiredArgsConstructor -public class DefaultHotBoardsRetrievalService implements HotBoardsStrategy { +public class DefaultHotBoardService implements HotBoardsStrategy { private final RetrieveBoardPort retrieveBoardPort; + private final RetrieveHotBoardPort retrieveHotBoardPort; + private final RegisterHotBoardPort registerHotBoardPort; + private final RemoveHotBoardPort removeHotBoardPort; private final RetrieveBoardEmojiPort retrieveBoardEmojiPort; private final ExternalRetrieveCommentUseCase externalRetrieveCommentUseCase; private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; @@ -32,31 +39,46 @@ public class DefaultHotBoardsRetrievalService implements HotBoardsStrategy { @Transactional @Override - public List retrieveHotBoards(int size) { - List hotBoards = getHotBoards(size); + public List retrieveHotBoards() { + List hotBoardIds = retrieveHotBoardPort.findAll(); - return hotBoards.stream() + return hotBoardIds.stream() + .map(hotBoardId -> retrieveBoardPort.getById(Long.parseLong(hotBoardId))) .map(board -> mapToBoardListResponseDto(board, getMemberDetailedInfoByBoard(board))) .toList(); } - private List getHotBoards(int size) { - // 만약 게시글의 총 개수가 size보다 적다면 모든 게시글 반환 + @Transactional + @Scheduled(cron = "0 0 0 * * MON") // 매주 월요일 00:00 실행 + public void saveHotBoards() { + clearHotBoards(); // 저장된 지난 인기 게시글 초기화 + + List hotBoardIds = getHotBoards().stream() + .map(Board::getId) + .map(String::valueOf) + .toList(); + + hotBoardIds.forEach(registerHotBoardPort::save); + } + + private List getHotBoards() { + // 만약 게시글의 총 개수가 5개보다 적다면 모든 게시글 반환 List allBoards = retrieveBoardPort.findAll(); - if (allBoards.size() < size) { + if (allBoards.size() < 5) { return sortBoardsByReactionAndDateWithLimit(allBoards.size(), allBoards); } - List hotBoards = getHotBoardsForWeek(1, size); + List hotBoards = getHotBoardsForWeek(1, 5); int weeksAgo = 2; // 필요한 수량을 확보할 때까지 반복해서 이전 주로 이동하여 인기 게시글 보충 - while (hotBoards.size() < size) { - List additionalBoards = getLatestHotBoardForWeek(weeksAgo++, size - hotBoards.size()); + while (hotBoards.size() < 5) { + List additionalBoards = getLatestHotBoardForWeek(weeksAgo++, 5 - hotBoards.size()); if (additionalBoards != null && !additionalBoards.isEmpty()) { hotBoards.addAll(additionalBoards); } } + return hotBoards; } @@ -108,4 +130,8 @@ private BoardListResponseDto mapToBoardListResponseDto(Board board, MemberDetail return mapper.toListDto(board, memberInfo, commentCount); } + + private void clearHotBoards() { + removeHotBoardPort.clearHotBoard(); + } } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java index 90ec3c9ad..b358965fc 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java @@ -16,8 +16,8 @@ public class HotBoardsRetrievalService implements RetrieveHotBoardsUseCase { private final Map strategies; @Override - public List retrieveHotBoards(int size, HotBoardStrategyType type) { + public List retrieveHotBoards(HotBoardStrategyType type) { HotBoardsStrategy hotBoardsStrategy = strategies.get(type.getKey()); - return hotBoardsStrategy.retrieveHotBoards(size); + return hotBoardsStrategy.retrieveHotBoards(); } } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsStrategy.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsStrategy.java index 7a9f0311d..f438cfe60 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsStrategy.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsStrategy.java @@ -5,5 +5,5 @@ import java.util.List; public interface HotBoardsStrategy { - List retrieveHotBoards(int size); + List retrieveHotBoards(); } From bfc564b1fdf9a22f21ce3b1e2a06aefe442edc19 Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Thu, 28 Nov 2024 09:21:41 +0900 Subject: [PATCH 11/28] =?UTF-8?q?refactor(messages):=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=ED=81=AC=EA=B8=B0=20=EA=B4=80=EB=A0=A8=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/messages.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index d8f546c4a..6e7db6fb5 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -61,7 +61,6 @@ email.member.email=올바른 이메일 형식으로 입력하세요. min.application.grade=학년은 {value}학년 이상이어야 합니다. min.donation.amount=금액은 {value}원 이상이어야 합니다. min.member.grade=최소값은 {value} 이상이어야 합니다. -min.board.size=최소값은 {value} 이상이어야 합니다. max.application.grade=학년은 {value}학년 이하여야 합니다. max.member.grade=최대값은 {value} 이하여야 합니다. url.application.githubUrl=올바른 URL 형식으로 입력하세요. From b7fcbe749b0cb6b33cae7c96d3b1340fbafb5492 Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Thu, 28 Nov 2024 09:30:08 +0900 Subject: [PATCH 12/28] =?UTF-8?q?refactor(DefaultHotBoardService):=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/application/service/DefaultHotBoardService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardService.java b/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardService.java index d668537df..f9ebd084e 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardService.java @@ -83,10 +83,10 @@ private List getHotBoards() { } private List getHotBoardsForWeek(int weeksAgo, int size) { - LocalDateTime startDate = LocalDate.now().minusWeeks(weeksAgo).with(DayOfWeek.MONDAY).atStartOfDay(); - LocalDateTime endDate = LocalDate.now().minusWeeks(weeksAgo).with(DayOfWeek.SUNDAY).atTime(23, 59, 59); + LocalDateTime startOfWeek = LocalDate.now().minusWeeks(weeksAgo).with(DayOfWeek.MONDAY).atStartOfDay(); + LocalDateTime endOfWeek = LocalDate.now().minusWeeks(weeksAgo).with(DayOfWeek.SUNDAY).atTime(23, 59, 59); - List boardsForWeek = retrieveBoardPort.findAllWithinDateRange(startDate, endDate); + List boardsForWeek = retrieveBoardPort.findAllWithinDateRange(startOfWeek, endOfWeek); return sortBoardsByReactionAndDateWithLimit(size, boardsForWeek); } From 1b9a11d375d2033bdbb2cedeffd9a5ea388a9d4b Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Thu, 28 Nov 2024 20:18:59 +0900 Subject: [PATCH 13/28] =?UTF-8?q?refactor(HotBoardSelectionStrategyType):?= =?UTF-8?q?=20enum=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 --- ...oardStrategyType.java => HotBoardSelectionStrategyType.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/java/page/clab/api/domain/community/board/domain/{HotBoardStrategyType.java => HotBoardSelectionStrategyType.java} (85%) diff --git a/src/main/java/page/clab/api/domain/community/board/domain/HotBoardStrategyType.java b/src/main/java/page/clab/api/domain/community/board/domain/HotBoardSelectionStrategyType.java similarity index 85% rename from src/main/java/page/clab/api/domain/community/board/domain/HotBoardStrategyType.java rename to src/main/java/page/clab/api/domain/community/board/domain/HotBoardSelectionStrategyType.java index 5b053951f..f9dcfbd81 100644 --- a/src/main/java/page/clab/api/domain/community/board/domain/HotBoardStrategyType.java +++ b/src/main/java/page/clab/api/domain/community/board/domain/HotBoardSelectionStrategyType.java @@ -5,7 +5,7 @@ @Getter @AllArgsConstructor -public enum HotBoardStrategyType { +public enum HotBoardSelectionStrategyType { DEFAULT("default", "댓글, 반응순 정렬"); From 365a2ea33c7a0401fbf59e2352112cee3d99fb2c Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Thu, 28 Nov 2024 20:20:03 +0900 Subject: [PATCH 14/28] =?UTF-8?q?refactor(HotBoardSelectionStrategy):=20?= =?UTF-8?q?=EC=A0=84=EB=9E=B5=20=ED=8C=A8=ED=84=B4=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=EA=B3=BC=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=9D=84=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/HotBoardsRetrievalController.java | 11 +--- .../port/in/RetrieveHotBoardsUseCase.java | 3 +- ... => DefaultHotBoardSelectionStrategy.java} | 55 +------------------ .../service/HotBoardRegisterService.java | 38 +++++++++++++ .../service/HotBoardRetrievalService.java | 50 +++++++++++++++++ .../service/HotBoardSelectionStrategy.java | 9 +++ .../service/HotBoardsRetrievalService.java | 23 -------- .../service/HotBoardsStrategy.java | 9 --- 8 files changed, 103 insertions(+), 95 deletions(-) rename src/main/java/page/clab/api/domain/community/board/application/service/{DefaultHotBoardService.java => DefaultHotBoardSelectionStrategy.java} (57%) create mode 100644 src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java create mode 100644 src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRetrievalService.java create mode 100644 src/main/java/page/clab/api/domain/community/board/application/service/HotBoardSelectionStrategy.java delete mode 100644 src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java delete mode 100644 src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsStrategy.java diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java index bd7151a5e..189ec6ab1 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java @@ -6,11 +6,9 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; import page.clab.api.domain.community.board.application.port.in.RetrieveHotBoardsUseCase; -import page.clab.api.domain.community.board.domain.HotBoardStrategyType; import page.clab.api.global.common.dto.ApiResponse; import java.util.List; @@ -23,14 +21,11 @@ public class HotBoardsRetrievalController { private final RetrieveHotBoardsUseCase retrieveHotBoardsUseCase; - @Operation(summary = "[G] 커뮤니티 인기 게시글 목록 조회", description = "ROLE_GUEST 이상의 권한이 필요함
" + - "인기 게시글 선정 타입 설정 가능") + @Operation(summary = "[G] 커뮤니티 인기 게시글 목록 조회", description = "ROLE_GUEST 이상의 권한이 필요함
") @PreAuthorize("hasRole('GUEST')") @GetMapping("/hot") - public ApiResponse> retrieveHotBoards( - @RequestParam(name = "type") HotBoardStrategyType type - ) { - List boards = retrieveHotBoardsUseCase.retrieveHotBoards(type); + public ApiResponse> retrieveHotBoards() { + List boards = retrieveHotBoardsUseCase.retrieveHotBoards(); return ApiResponse.success(boards); } } diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java b/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java index 2278e44ab..417bb7318 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java +++ b/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java @@ -1,10 +1,9 @@ package page.clab.api.domain.community.board.application.port.in; import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; -import page.clab.api.domain.community.board.domain.HotBoardStrategyType; import java.util.List; public interface RetrieveHotBoardsUseCase { - List retrieveHotBoards(HotBoardStrategyType type); + List retrieveHotBoards(); } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardService.java b/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardSelectionStrategy.java similarity index 57% rename from src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardService.java rename to src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardSelectionStrategy.java index f9ebd084e..6672c93da 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardSelectionStrategy.java @@ -1,21 +1,12 @@ package page.clab.api.domain.community.board.application.service; -import com.drew.lang.annotations.NotNull; import lombok.RequiredArgsConstructor; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import page.clab.api.domain.community.board.application.dto.mapper.BoardDtoMapper; -import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; -import page.clab.api.domain.community.board.application.port.out.RegisterHotBoardPort; -import page.clab.api.domain.community.board.application.port.out.RemoveHotBoardPort; import page.clab.api.domain.community.board.application.port.out.RetrieveBoardEmojiPort; import page.clab.api.domain.community.board.application.port.out.RetrieveBoardPort; -import page.clab.api.domain.community.board.application.port.out.RetrieveHotBoardPort; import page.clab.api.domain.community.board.domain.Board; -import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto; import page.clab.api.external.community.comment.application.port.ExternalRetrieveCommentUseCase; -import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import java.time.DayOfWeek; import java.time.LocalDate; @@ -26,42 +17,15 @@ @Service("default") @RequiredArgsConstructor -public class DefaultHotBoardService implements HotBoardsStrategy { +public class DefaultHotBoardSelectionStrategy implements HotBoardSelectionStrategy { private final RetrieveBoardPort retrieveBoardPort; - private final RetrieveHotBoardPort retrieveHotBoardPort; - private final RegisterHotBoardPort registerHotBoardPort; - private final RemoveHotBoardPort removeHotBoardPort; private final RetrieveBoardEmojiPort retrieveBoardEmojiPort; private final ExternalRetrieveCommentUseCase externalRetrieveCommentUseCase; - private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; - private final BoardDtoMapper mapper; @Transactional @Override - public List retrieveHotBoards() { - List hotBoardIds = retrieveHotBoardPort.findAll(); - - return hotBoardIds.stream() - .map(hotBoardId -> retrieveBoardPort.getById(Long.parseLong(hotBoardId))) - .map(board -> mapToBoardListResponseDto(board, getMemberDetailedInfoByBoard(board))) - .toList(); - } - - @Transactional - @Scheduled(cron = "0 0 0 * * MON") // 매주 월요일 00:00 실행 - public void saveHotBoards() { - clearHotBoards(); // 저장된 지난 인기 게시글 초기화 - - List hotBoardIds = getHotBoards().stream() - .map(Board::getId) - .map(String::valueOf) - .toList(); - - hotBoardIds.forEach(registerHotBoardPort::save); - } - - private List getHotBoards() { + public List getHotBoards() { // 만약 게시글의 총 개수가 5개보다 적다면 모든 게시글 반환 List allBoards = retrieveBoardPort.findAll(); if (allBoards.size() < 5) { @@ -119,19 +83,4 @@ private int getTotalReactionCount(Board board) { return commentCount.intValue() + emojiCount; } - - private MemberDetailedInfoDto getMemberDetailedInfoByBoard(Board board) { - return externalRetrieveMemberUseCase.getMemberDetailedInfoById(board.getMemberId()); - } - - @NotNull - private BoardListResponseDto mapToBoardListResponseDto(Board board, MemberDetailedInfoDto memberInfo) { - Long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId()); - - return mapper.toListDto(board, memberInfo, commentCount); - } - - private void clearHotBoards() { - removeHotBoardPort.clearHotBoard(); - } } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java new file mode 100644 index 000000000..f0c230375 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java @@ -0,0 +1,38 @@ +package page.clab.api.domain.community.board.application.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import page.clab.api.domain.community.board.application.port.out.RegisterHotBoardPort; +import page.clab.api.domain.community.board.application.port.out.RemoveHotBoardPort; +import page.clab.api.domain.community.board.domain.Board; +import page.clab.api.domain.community.board.domain.HotBoardSelectionStrategyType; + +import java.util.List; +import java.util.Map; + +@Service +@RequiredArgsConstructor +public class HotBoardRegisterService { + + private final Map strategies; + private final RegisterHotBoardPort registerHotBoardPort; + private final RemoveHotBoardPort removeHotBoardPort; + + @Transactional + //@Scheduled(cron = "0 0 0 * * MON") // 매주 월요일 00:00 실행 + @Scheduled(cron = "0 18 20 * * *") + public void registerHotBoards() { + HotBoardSelectionStrategy strategy = strategies.get(HotBoardSelectionStrategyType.DEFAULT.getKey()); + + removeHotBoardPort.clearHotBoard(); // 저장된 지난 인기 게시글 초기화 + + List hotBoardIds = strategy.getHotBoards().stream() + .map(Board::getId) + .map(String::valueOf) + .toList(); + + hotBoardIds.forEach(registerHotBoardPort::save); + } +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRetrievalService.java new file mode 100644 index 000000000..a14cfb5f5 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRetrievalService.java @@ -0,0 +1,50 @@ +package page.clab.api.domain.community.board.application.service; + +import com.drew.lang.annotations.NotNull; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import page.clab.api.domain.community.board.application.dto.mapper.BoardDtoMapper; +import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; +import page.clab.api.domain.community.board.application.port.in.RetrieveHotBoardsUseCase; +import page.clab.api.domain.community.board.application.port.out.RetrieveBoardPort; +import page.clab.api.domain.community.board.application.port.out.RetrieveHotBoardPort; +import page.clab.api.domain.community.board.domain.Board; +import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto; +import page.clab.api.external.community.comment.application.port.ExternalRetrieveCommentUseCase; +import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class HotBoardRetrievalService implements RetrieveHotBoardsUseCase { + + private final RetrieveHotBoardPort retrieveHotBoardPort; + private final RetrieveBoardPort retrieveBoardPort; + private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; + private final ExternalRetrieveCommentUseCase externalRetrieveCommentUseCase; + private final BoardDtoMapper mapper; + + @Transactional + @Override + public List retrieveHotBoards() { + List hotBoardIds = retrieveHotBoardPort.findAll(); + + return hotBoardIds.stream() + .map(hotBoardId -> retrieveBoardPort.getById(Long.parseLong(hotBoardId))) + .map(board -> mapToBoardListResponseDto(board, getMemberDetailedInfoByBoard(board))) + .toList(); + } + + private MemberDetailedInfoDto getMemberDetailedInfoByBoard(Board board) { + return externalRetrieveMemberUseCase.getMemberDetailedInfoById(board.getMemberId()); + } + + @NotNull + private BoardListResponseDto mapToBoardListResponseDto(Board board, MemberDetailedInfoDto memberInfo) { + Long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId()); + + return mapper.toListDto(board, memberInfo, commentCount); + } +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardSelectionStrategy.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardSelectionStrategy.java new file mode 100644 index 000000000..6a2d4a5c9 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardSelectionStrategy.java @@ -0,0 +1,9 @@ +package page.clab.api.domain.community.board.application.service; + +import page.clab.api.domain.community.board.domain.Board; + +import java.util.List; + +public interface HotBoardSelectionStrategy { + List getHotBoards(); +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java deleted file mode 100644 index b358965fc..000000000 --- a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java +++ /dev/null @@ -1,23 +0,0 @@ -package page.clab.api.domain.community.board.application.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; -import page.clab.api.domain.community.board.application.port.in.RetrieveHotBoardsUseCase; -import page.clab.api.domain.community.board.domain.HotBoardStrategyType; - -import java.util.List; -import java.util.Map; - -@Service -@RequiredArgsConstructor -public class HotBoardsRetrievalService implements RetrieveHotBoardsUseCase { - - private final Map strategies; - - @Override - public List retrieveHotBoards(HotBoardStrategyType type) { - HotBoardsStrategy hotBoardsStrategy = strategies.get(type.getKey()); - return hotBoardsStrategy.retrieveHotBoards(); - } -} diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsStrategy.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsStrategy.java deleted file mode 100644 index f438cfe60..000000000 --- a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsStrategy.java +++ /dev/null @@ -1,9 +0,0 @@ -package page.clab.api.domain.community.board.application.service; - -import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; - -import java.util.List; - -public interface HotBoardsStrategy { - List retrieveHotBoards(); -} From 5492a00d0aff993a7984a6abe750fb7e5f62a538 Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Thu, 28 Nov 2024 20:58:24 +0900 Subject: [PATCH 15/28] =?UTF-8?q?refactor(HotBoardRegisterService):=20?= =?UTF-8?q?=EC=8A=A4=EC=BC=80=EC=A4=84=EB=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/application/service/HotBoardRegisterService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java index f0c230375..89e1e9c1a 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java @@ -21,8 +21,7 @@ public class HotBoardRegisterService { private final RemoveHotBoardPort removeHotBoardPort; @Transactional - //@Scheduled(cron = "0 0 0 * * MON") // 매주 월요일 00:00 실행 - @Scheduled(cron = "0 18 20 * * *") + @Scheduled(cron = "0 0 0 * * MON") // 매주 월요일 00:00 실행 public void registerHotBoards() { HotBoardSelectionStrategy strategy = strategies.get(HotBoardSelectionStrategyType.DEFAULT.getKey()); From 97c4ef53fe864f00524ff638373c4b538acc3ac6 Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Fri, 6 Dec 2024 20:31:39 +0900 Subject: [PATCH 16/28] =?UTF-8?q?refactor(HotBoardSelectionStrategies):=20?= =?UTF-8?q?=EC=A0=84=EB=9E=B5=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/DefaultHotBoardSelectionStrategy.java | 3 ++- .../service/HotBoardRegisterService.java | 4 ++-- .../board/domain/HotBoardSelectionStrategies.java | 10 ++++++++++ .../domain/HotBoardSelectionStrategyType.java | 14 -------------- 4 files changed, 14 insertions(+), 17 deletions(-) create mode 100644 src/main/java/page/clab/api/domain/community/board/domain/HotBoardSelectionStrategies.java delete mode 100644 src/main/java/page/clab/api/domain/community/board/domain/HotBoardSelectionStrategyType.java diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardSelectionStrategy.java b/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardSelectionStrategy.java index 6672c93da..993cca181 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardSelectionStrategy.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardSelectionStrategy.java @@ -6,6 +6,7 @@ import page.clab.api.domain.community.board.application.port.out.RetrieveBoardEmojiPort; import page.clab.api.domain.community.board.application.port.out.RetrieveBoardPort; import page.clab.api.domain.community.board.domain.Board; +import page.clab.api.domain.community.board.domain.HotBoardSelectionStrategies; import page.clab.api.external.community.comment.application.port.ExternalRetrieveCommentUseCase; import java.time.DayOfWeek; @@ -15,7 +16,7 @@ import java.util.List; import java.util.stream.Collectors; -@Service("default") +@Service(HotBoardSelectionStrategies.DEFAULT) @RequiredArgsConstructor public class DefaultHotBoardSelectionStrategy implements HotBoardSelectionStrategy { diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java index 89e1e9c1a..b8c68f54c 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java @@ -7,7 +7,7 @@ import page.clab.api.domain.community.board.application.port.out.RegisterHotBoardPort; import page.clab.api.domain.community.board.application.port.out.RemoveHotBoardPort; import page.clab.api.domain.community.board.domain.Board; -import page.clab.api.domain.community.board.domain.HotBoardSelectionStrategyType; +import page.clab.api.domain.community.board.domain.HotBoardSelectionStrategies; import java.util.List; import java.util.Map; @@ -23,7 +23,7 @@ public class HotBoardRegisterService { @Transactional @Scheduled(cron = "0 0 0 * * MON") // 매주 월요일 00:00 실행 public void registerHotBoards() { - HotBoardSelectionStrategy strategy = strategies.get(HotBoardSelectionStrategyType.DEFAULT.getKey()); + HotBoardSelectionStrategy strategy = strategies.get(HotBoardSelectionStrategies.DEFAULT); removeHotBoardPort.clearHotBoard(); // 저장된 지난 인기 게시글 초기화 diff --git a/src/main/java/page/clab/api/domain/community/board/domain/HotBoardSelectionStrategies.java b/src/main/java/page/clab/api/domain/community/board/domain/HotBoardSelectionStrategies.java new file mode 100644 index 000000000..29fa6c97d --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/domain/HotBoardSelectionStrategies.java @@ -0,0 +1,10 @@ +package page.clab.api.domain.community.board.domain; + +public class HotBoardSelectionStrategies { + + public static final String DEFAULT = "default"; + + private HotBoardSelectionStrategies() { + // 인스턴스화 방지 + } +} diff --git a/src/main/java/page/clab/api/domain/community/board/domain/HotBoardSelectionStrategyType.java b/src/main/java/page/clab/api/domain/community/board/domain/HotBoardSelectionStrategyType.java deleted file mode 100644 index f9dcfbd81..000000000 --- a/src/main/java/page/clab/api/domain/community/board/domain/HotBoardSelectionStrategyType.java +++ /dev/null @@ -1,14 +0,0 @@ -package page.clab.api.domain.community.board.domain; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public enum HotBoardSelectionStrategyType { - - DEFAULT("default", "댓글, 반응순 정렬"); - - private final String key; - private final String description; -} From e2a864469a79fa3796bbccb99cfd231918257325 Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Fri, 13 Dec 2024 13:48:01 +0900 Subject: [PATCH 17/28] =?UTF-8?q?refactor(HotBoardRetrievalController):=20?= =?UTF-8?q?=EC=9D=B8=EA=B8=B0=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EC=97=90=20=EC=A0=84=EB=9E=B5=EC=9D=84=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/HotBoardsRetrievalController.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java index 189ec6ab1..5236643cd 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java @@ -6,6 +6,7 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; import page.clab.api.domain.community.board.application.port.in.RetrieveHotBoardsUseCase; @@ -21,11 +22,15 @@ public class HotBoardsRetrievalController { private final RetrieveHotBoardsUseCase retrieveHotBoardsUseCase; - @Operation(summary = "[G] 커뮤니티 인기 게시글 목록 조회", description = "ROLE_GUEST 이상의 권한이 필요함
") + @Operation(summary = "[G] 커뮤니티 인기 게시글 목록 조회", description = "ROLE_GUEST 이상의 권한이 필요함
" + + "인기게시글 선정 전략별 조회가 가능함
" + + "- DEFAULT : 반응 순 기본 전략
") @PreAuthorize("hasRole('GUEST')") @GetMapping("/hot") - public ApiResponse> retrieveHotBoards() { - List boards = retrieveHotBoardsUseCase.retrieveHotBoards(); + public ApiResponse> retrieveHotBoards( + @RequestParam(name = "strategyName", defaultValue = "DEFAULT") String strategyName + ) { + List boards = retrieveHotBoardsUseCase.retrieveHotBoards(strategyName); return ApiResponse.success(boards); } } From dd12766a4892aff49e4c2fc83ec9178f2d5ba07a Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Fri, 13 Dec 2024 13:51:09 +0900 Subject: [PATCH 18/28] =?UTF-8?q?refactor(RedisHotBoardPersistenceAdapter)?= =?UTF-8?q?:=20=EC=9D=B8=EA=B8=B0=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EB=B0=8F=20=EC=A1=B0=ED=9A=8C=EC=8B=9C=20=EC=A0=84?= =?UTF-8?q?=EB=9E=B5=20=EC=9D=B4=EB=A6=84=EC=9D=84=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RedisHotBoardPersistenceAdapter.java | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/RedisHotBoardPersistenceAdapter.java b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/RedisHotBoardPersistenceAdapter.java index 8ebd20644..3ff92cf92 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/RedisHotBoardPersistenceAdapter.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/RedisHotBoardPersistenceAdapter.java @@ -8,6 +8,8 @@ import page.clab.api.domain.community.board.application.port.out.RetrieveHotBoardPort; import java.util.List; +import java.util.Objects; +import java.util.Set; @Component @RequiredArgsConstructor @@ -16,23 +18,41 @@ public class RedisHotBoardPersistenceAdapter implements RetrieveHotBoardPort, RemoveHotBoardPort { - private static final String HOT_BOARDS_KEY = "hotBoards"; + private static final String HOT_BOARDS_PREFIX = "hotBoards"; private final RedisTemplate redisTemplate; @Override - public void save(String boardId) { - redisTemplate.opsForList().rightPush(HOT_BOARDS_KEY, boardId); + public void save(String boardId, String strategyName) { + String key = getRedisKey(strategyName); + redisTemplate.opsForList().rightPush(key, boardId); } @Override - public List findAll() { - List hotBoards = redisTemplate.opsForList().range(HOT_BOARDS_KEY, 0, -1); + public List findByHotBoardStrategy(String strategyName) { + String key = getRedisKey(strategyName); + List hotBoards = redisTemplate.opsForList().range(key, 0, -1); return (hotBoards != null) ? hotBoards : List.of(); } @Override public void clearHotBoard() { - redisTemplate.delete(HOT_BOARDS_KEY); + String pattern = HOT_BOARDS_PREFIX + ":*"; + Set keys = redisTemplate.keys(pattern); + if (keys == null) { + return; + } + keys.stream() + .filter(Objects::nonNull) + .forEach(key -> { + Long size = redisTemplate.opsForList().size(key); + if (size != null && size > 0) { + redisTemplate.delete(key); + } + }); + } + + private String getRedisKey(String strategyName) { + return String.format("%s:%s", HOT_BOARDS_PREFIX, strategyName); } } From 405bde62b6406b39b433638d1dfe5d5f3dca6e75 Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Fri, 13 Dec 2024 13:51:48 +0900 Subject: [PATCH 19/28] =?UTF-8?q?refactor(RetrieveHotBoardsUseCase):=20?= =?UTF-8?q?=EC=9D=B8=EA=B8=B0=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EC=8B=9C=20=EC=A0=84=EB=9E=B5=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/application/port/in/RetrieveHotBoardsUseCase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java b/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java index 417bb7318..11da513c5 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java +++ b/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java @@ -5,5 +5,5 @@ import java.util.List; public interface RetrieveHotBoardsUseCase { - List retrieveHotBoards(); + List retrieveHotBoards(String strategyName); } From fe8ed1c524ce7c498ffa9a2098763d249f07914b Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Fri, 13 Dec 2024 13:52:09 +0900 Subject: [PATCH 20/28] =?UTF-8?q?refactor(RetrieveHotBoardPort):=20?= =?UTF-8?q?=EC=9D=B8=EA=B8=B0=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EC=8B=9C=20=EC=A0=84=EB=9E=B5=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/application/port/out/RetrieveHotBoardPort.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveHotBoardPort.java b/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveHotBoardPort.java index c356f8d6e..a1042709f 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveHotBoardPort.java +++ b/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveHotBoardPort.java @@ -3,5 +3,5 @@ import java.util.List; public interface RetrieveHotBoardPort { - List findAll(); + List findByHotBoardStrategy(String strategyName); } From fda1d297c898ef8ca067aa3ce5f5c5ff71ae2d11 Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Fri, 13 Dec 2024 13:52:28 +0900 Subject: [PATCH 21/28] =?UTF-8?q?refactor(RegisterHotBoardPort):=20?= =?UTF-8?q?=EC=9D=B8=EA=B8=B0=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EC=8B=9C=20=EC=A0=84=EB=9E=B5=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/application/port/out/RegisterHotBoardPort.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/out/RegisterHotBoardPort.java b/src/main/java/page/clab/api/domain/community/board/application/port/out/RegisterHotBoardPort.java index 20e66b609..914b10dc0 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/port/out/RegisterHotBoardPort.java +++ b/src/main/java/page/clab/api/domain/community/board/application/port/out/RegisterHotBoardPort.java @@ -1,5 +1,5 @@ package page.clab.api.domain.community.board.application.port.out; public interface RegisterHotBoardPort { - void save(String boardId); + void save(String boardId, String strategyName); } From e80719ff6e9d27c8dace56309d7f5e85948fec81 Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Fri, 13 Dec 2024 13:53:18 +0900 Subject: [PATCH 22/28] =?UTF-8?q?refactor(HotBoardRegisterService):=20?= =?UTF-8?q?=EC=A0=84=EB=9E=B5=20=ED=8C=A8=ED=84=B4=EB=B3=84=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/HotBoardRegisterService.java | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java index b8c68f54c..2e34bfc7e 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java @@ -16,22 +16,48 @@ @RequiredArgsConstructor public class HotBoardRegisterService { - private final Map strategies; + private final Map strategyMap; private final RegisterHotBoardPort registerHotBoardPort; private final RemoveHotBoardPort removeHotBoardPort; + /** + * 주어진 전략을 기반으로 인기 게시글을 등록합니다. + *

+ * 지정된 전략을 기반으로 인기 게시글을 선정하고, 이를 Redis에 저장합니다. + * + * @param strategyName 인기 게시글 선정 전략 이름 + * @return 인기 게시글 ID 리스트 + */ + @Transactional + public List registerHotBoards(String strategyName) { + HotBoardSelectionStrategy strategy = strategyMap.get(strategyName); + List hotBoardIds = getHotBoardIds(strategy); + hotBoardIds.forEach(id -> registerHotBoardPort.save(id, strategyName)); + return hotBoardIds; + } + + /** + * 기본 전략을 이용하여 인기 게시글을 등록합니다. + *

+ * 매주 월요일 자정에 실행되도록 스케줄링되어 있습니다. 먼저 기존 인기 게시글을 초기화한 뒤, + * 기본 전략을 이용하여 선정된 인기 게시글을 Redis에 저장합니다. + */ @Transactional @Scheduled(cron = "0 0 0 * * MON") // 매주 월요일 00:00 실행 - public void registerHotBoards() { - HotBoardSelectionStrategy strategy = strategies.get(HotBoardSelectionStrategies.DEFAULT); + public void registerDefaultHotBoards() { + HotBoardSelectionStrategy strategy = strategyMap.get(HotBoardSelectionStrategies.DEFAULT); - removeHotBoardPort.clearHotBoard(); // 저장된 지난 인기 게시글 초기화 + removeHotBoardPort.clearHotBoard(); // 저장된 지난 모든 인기 게시글 초기화 - List hotBoardIds = strategy.getHotBoards().stream() + List hotBoardIds = getHotBoardIds(strategy); + hotBoardIds.forEach(id -> + registerHotBoardPort.save(id, HotBoardSelectionStrategies.DEFAULT)); + } + + private static List getHotBoardIds(HotBoardSelectionStrategy strategy) { + return strategy.getHotBoards().stream() .map(Board::getId) .map(String::valueOf) .toList(); - - hotBoardIds.forEach(registerHotBoardPort::save); } } From ffea8ab7f10bfa6b2fa48fbf289527ae04bcd4c4 Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Fri, 13 Dec 2024 13:53:43 +0900 Subject: [PATCH 23/28] =?UTF-8?q?refactor(HotBoardRegisterService):=20?= =?UTF-8?q?=EC=9D=B8=EA=B8=B0=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EC=8B=9C=20=EC=A0=84=EB=9E=B5=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/HotBoardRetrievalService.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRetrievalService.java index a14cfb5f5..e332d7e14 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRetrievalService.java @@ -10,11 +10,13 @@ import page.clab.api.domain.community.board.application.port.out.RetrieveBoardPort; import page.clab.api.domain.community.board.application.port.out.RetrieveHotBoardPort; import page.clab.api.domain.community.board.domain.Board; +import page.clab.api.domain.community.board.domain.HotBoardSelectionStrategies; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto; import page.clab.api.external.community.comment.application.port.ExternalRetrieveCommentUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import java.util.List; +import java.util.Map; @Service @RequiredArgsConstructor @@ -24,12 +26,18 @@ public class HotBoardRetrievalService implements RetrieveHotBoardsUseCase { private final RetrieveBoardPort retrieveBoardPort; private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; private final ExternalRetrieveCommentUseCase externalRetrieveCommentUseCase; + private final HotBoardRegisterService hotBoardRegisterService; + private final Map strategyMap; private final BoardDtoMapper mapper; @Transactional @Override - public List retrieveHotBoards() { - List hotBoardIds = retrieveHotBoardPort.findAll(); + public List retrieveHotBoards(String strategyName) { + String validatedStrategyName = validateStrategyName(strategyName); + List hotBoardIds = retrieveHotBoardPort.findByHotBoardStrategy(validatedStrategyName); + if (hotBoardIds.isEmpty()) { + hotBoardIds = hotBoardRegisterService.registerHotBoards(validatedStrategyName); + } return hotBoardIds.stream() .map(hotBoardId -> retrieveBoardPort.getById(Long.parseLong(hotBoardId))) @@ -47,4 +55,11 @@ private BoardListResponseDto mapToBoardListResponseDto(Board board, MemberDetail return mapper.toListDto(board, memberInfo, commentCount); } + + private String validateStrategyName(String strategyName) { + if (strategyName == null || !strategyMap.containsKey(strategyName)) { + strategyName = HotBoardSelectionStrategies.DEFAULT; + } + return strategyName; + } } From b19ebdffb1f7d839dae4d750ebe45633aacbc980 Mon Sep 17 00:00:00 2001 From: SongJaeHoonn Date: Sun, 15 Dec 2024 05:50:35 +0900 Subject: [PATCH 24/28] =?UTF-8?q?refactor(HotBoardSelectionStrategies):=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/DefaultHotBoardSelectionStrategy.java | 1 - .../board/application/service/HotBoardRegisterService.java | 1 - .../board/application/service/HotBoardRetrievalService.java | 1 - .../service}/HotBoardSelectionStrategies.java | 2 +- 4 files changed, 1 insertion(+), 4 deletions(-) rename src/main/java/page/clab/api/domain/community/board/{domain => application/service}/HotBoardSelectionStrategies.java (73%) diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardSelectionStrategy.java b/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardSelectionStrategy.java index 993cca181..b31a4f1c3 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardSelectionStrategy.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardSelectionStrategy.java @@ -6,7 +6,6 @@ import page.clab.api.domain.community.board.application.port.out.RetrieveBoardEmojiPort; import page.clab.api.domain.community.board.application.port.out.RetrieveBoardPort; import page.clab.api.domain.community.board.domain.Board; -import page.clab.api.domain.community.board.domain.HotBoardSelectionStrategies; import page.clab.api.external.community.comment.application.port.ExternalRetrieveCommentUseCase; import java.time.DayOfWeek; diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java index 2e34bfc7e..462ef38dc 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRegisterService.java @@ -7,7 +7,6 @@ import page.clab.api.domain.community.board.application.port.out.RegisterHotBoardPort; import page.clab.api.domain.community.board.application.port.out.RemoveHotBoardPort; import page.clab.api.domain.community.board.domain.Board; -import page.clab.api.domain.community.board.domain.HotBoardSelectionStrategies; import java.util.List; import java.util.Map; diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRetrievalService.java index e332d7e14..aed58c011 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardRetrievalService.java @@ -10,7 +10,6 @@ import page.clab.api.domain.community.board.application.port.out.RetrieveBoardPort; import page.clab.api.domain.community.board.application.port.out.RetrieveHotBoardPort; import page.clab.api.domain.community.board.domain.Board; -import page.clab.api.domain.community.board.domain.HotBoardSelectionStrategies; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto; import page.clab.api.external.community.comment.application.port.ExternalRetrieveCommentUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; diff --git a/src/main/java/page/clab/api/domain/community/board/domain/HotBoardSelectionStrategies.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardSelectionStrategies.java similarity index 73% rename from src/main/java/page/clab/api/domain/community/board/domain/HotBoardSelectionStrategies.java rename to src/main/java/page/clab/api/domain/community/board/application/service/HotBoardSelectionStrategies.java index 29fa6c97d..1b37ef5c5 100644 --- a/src/main/java/page/clab/api/domain/community/board/domain/HotBoardSelectionStrategies.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardSelectionStrategies.java @@ -1,4 +1,4 @@ -package page.clab.api.domain.community.board.domain; +package page.clab.api.domain.community.board.application.service; public class HotBoardSelectionStrategies { From ba1903882d39067c726d0674d5c426e8c6b10701 Mon Sep 17 00:00:00 2001 From: JEON MIN JU <96719969+mingmingmon@users.noreply.github.com> Date: Fri, 20 Dec 2024 19:11:10 +0900 Subject: [PATCH 25/28] =?UTF-8?q?refactor:=20Jenkins=20Config=20YAML=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EC=95=8C=EB=A6=BC=20=ED=94=8C=EB=9E=AB?= =?UTF-8?q?=ED=8F=BC=20=EC=9C=A0=EB=8F=99=EC=A0=81=20=EC=A7=80=EC=9B=90=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EC=99=84=EB=A3=8C=20(#637)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 한관희 --- jenkins/prod/Jenkinsfile | 68 ++++++++++++++++++++++------- jenkins/prod/config.yml | 92 ++++++++++++++++++++++----------------- jenkins/stage/Jenkinsfile | 68 ++++++++++++++++++++++------- jenkins/stage/config.yml | 80 ++++++++++++++++++++-------------- 4 files changed, 207 insertions(+), 101 deletions(-) diff --git a/jenkins/prod/Jenkinsfile b/jenkins/prod/Jenkinsfile index 9f0513946..8d273d9e1 100644 --- a/jenkins/prod/Jenkinsfile +++ b/jenkins/prod/Jenkinsfile @@ -140,31 +140,52 @@ pipeline { } post { - failure { + success { script { - sendSlackBuildNotification(":scream_cat: Stage *${FAILED_STAGE}* failed.", env.SLACK_COLOR_FAILURE) + sendBuildNotification(":rocket: Deployment completed successfully", env.NOTIFICATION_COLOR_SUCCESS) } } - - success { + failure { script { - sendSlackBuildNotification(":rocket: Deployment completed successfully", env.SLACK_COLOR_SUCCESS) + sendBuildNotification(":scream_cat: Deployment failed in stage *${FAILED_STAGE}*", env.NOTIFICATION_COLOR_FAILURE) } } } } -def sendSlackBuildNotification(String message, String color) { +def sendBuildNotification(String message, String color) { def jobUrl = "${env.JENKINS_DOMAIN}/job/${env.JOB_NAME}" def consoleOutputUrl = "${jobUrl}/${env.BUILD_NUMBER}/console" + def changelog = env.GIT_CHANGELOG + + def notificationPlatforms = readJSON text: env.NOTIFICATION_PLATFORMS_JSON.trim() + + notificationPlatforms.each { notification -> + if (notification.enabled.toBoolean()) { + def platform = notification.platform ? notification.platform.trim().toLowerCase() : "" + echo "Processing notification for platform: '${platform}'" + + def payload = null + switch (platform) { + case 'slack': + payload = createSlackPayload(message, color, jobUrl, consoleOutputUrl, changelog) + break + case 'discord': + payload = createDiscordPayload(message, color, jobUrl, consoleOutputUrl, changelog) + break + default: + echo "Unsupported or undefined notification platform: '${platform}'" + } - def payload = createSlackPayload(message, color, jobUrl, consoleOutputUrl) - def payloadJson = groovy.json.JsonOutput.toJson(payload) - - sendHttpPostRequest(env.SLACK_WEBHOOK_URL, payloadJson) + if (payload != null) { + def payloadJson = groovy.json.JsonOutput.toJson(payload) + sendHttpPostRequest(notification.'webhook-url', payloadJson) + } + } + } } -def createSlackPayload(String message, String color, String jobUrl, String consoleOutputUrl) { +def createSlackPayload(String message, String color, String jobUrl, String consoleOutputUrl, String changelog) { return [ blocks: [ [ @@ -183,7 +204,7 @@ def createSlackPayload(String message, String color, String jobUrl, String conso type: "section", text: [ type: "mrkdwn", - text: "*Change Log:*\n${env.GIT_CHANGELOG}" + text: "*Change Log:*\n${changelog}" ] ], [ @@ -217,6 +238,23 @@ def createSlackPayload(String message, String color, String jobUrl, String conso ] } +def createDiscordPayload(String message, String color, String jobUrl, String consoleOutputUrl, String changelog) { + return [ + embeds: [ + [ + title: message, + color: parseColor(color), + description: "*Change Log:*\n${changelog}\n\n[Job](${jobUrl}) | [Console Output](${consoleOutputUrl})", + ] + ] + ] +} + +def parseColor(String hexColor) { + // Discord requires the color in decimal format + return Integer.parseInt(hexColor.replace("#", ""), 16) +} + def sendHttpPostRequest(String url, String payload) { def CONTENT_TYPE_JSON = 'application/json' def HTTP_POST = 'POST' @@ -233,9 +271,9 @@ def loadEnvironmentVariables(String configFile) { def config = readYaml(file: configFile) env.JENKINS_DOMAIN = config.'jenkins-domain' - env.SLACK_WEBHOOK_URL = config.slack.'webhook-url' - env.SLACK_COLOR_SUCCESS = config.slack.'color-success' - env.SLACK_COLOR_FAILURE = config.slack.'color-failure' + env.NOTIFICATION_COLOR_SUCCESS = config.notifications.common.'color-success' + env.NOTIFICATION_COLOR_FAILURE = config.notifications.common.'color-failure' + env.NOTIFICATION_PLATFORMS_JSON = groovy.json.JsonOutput.toJson(config.notifications.platforms) env.PG_USER = config.postgresql.user env.PG_PASSWORD = config.postgresql.password diff --git a/jenkins/prod/config.yml b/jenkins/prod/config.yml index c476127f5..ca0530808 100644 --- a/jenkins/prod/config.yml +++ b/jenkins/prod/config.yml @@ -1,60 +1,74 @@ -jenkins-domain: "https://jenkins.example.com" # Jenkins domain +jenkins-domain: "https://jenkins.example.com" # Base URL of the Jenkins instance -slack: - webhook-url: "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX" # Slack webhook URL - color-success: "#F2C744" # Slack message color for success - color-failure: "#8D1E0E" # Slack message color for failure +notifications: + common: + color-success: "#F2C744" # Common hex color code for success messages across all notification platforms + color-failure: "#8D1E0E" # Common hex color code for failure messages across all notification platforms + + platforms: + - platform: "slack" # Name of the notification platform (e.g., Slack) + enabled: true # Enable (true) or disable (false) notifications for this platform + webhook-url: "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX" # Webhook URL for sending messages to Slack + + - platform: "discord" # Name of the notification platform (e.g., Discord) + enabled: true # Enable (true) or disable (false) notifications for this platform + webhook-url: "https://discord.com/api/webhooks/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" # Webhook URL for sending messages to Discord + + # Add additional notification platforms below as needed + # Example: + # - platform: "microsoft-teams" + # enabled: true + # webhook-url: "https://outlook.office.com/webhook/..." postgresql: - user: "postgres_user" # PostgreSQL username - password: "postgres_password" # PostgreSQL password - backup-dir: "/var/backups/postgresql" # Directory for PostgreSQL backups + user: "postgres_user" # Username for PostgreSQL + password: "postgres_password" # Password for PostgreSQL + backup-dir: "/var/backups/postgresql" # Directory where PostgreSQL backups are stored dockerhub: - repo: "yourdockerhub/repository" # Docker Hub repository name - user: "dockerhub_user" # Docker Hub username - password: "dockerhub_password" # Docker Hub password + repo: "yourdockerhub/repository" # Docker Hub repository name + user: "dockerhub_user" # Docker Hub username + password: "dockerhub_password" # Docker Hub password external-server: - config-path: "/path/to/external/config" # Path for external server configuration - cloud-path: "/path/to/external/cloud" # Path for external server cloud storage - logs-path: "/path/to/external/logs" # Path for external server logs + config-path: "/path/to/external/config" # Path to external server configuration files + cloud-path: "/path/to/external/cloud" # Path to external server cloud storage + logs-path: "/path/to/external/logs" # Path to external server log files internal-server: - config-path: "/path/to/internal/config" # Path for internal server configuration - cloud-path: "/path/to/internal/cloud" # Path for internal server cloud storage - logs-path: "/path/to/internal/logs" # Path for internal server logs + config-path: "/path/to/internal/config" # Path to internal server configuration files + cloud-path: "/path/to/internal/cloud" # Path to internal server cloud storage + logs-path: "/path/to/internal/logs" # Path to internal server log files containers: - blue: "blue-container" # Blue-Green deployment: Blue container name - green: "green-container" # Blue-Green deployment: Green container name - blue-url: "http://blue-container:8080" # URL for the Blue container environment - green-url: "http://green-container:8080" # URL for the Green container environment - image-name: "application-image" # Docker image name for the application + blue: "blue-container" # Name of the Blue container in Blue-Green deployment + green: "green-container" # Name of the Green container in Blue-Green deployment + blue-url: "http://blue-container:8080" # URL for accessing the Blue container environment + green-url: "http://green-container:8080" # URL for accessing the Green container environment + image-name: "application-image" # Docker image name for the application networks: - application: "application-network" # Docker network for the application - monitoring: "monitoring-network" # Docker network for monitoring + application: "application-network" # Docker network used by the application + monitoring: "monitoring-network" # Docker network used for monitoring services spring: - profile: "default" # Spring profile setting - port-a: 8080 # Application port A - port-b: 8081 # Application port B + profile: "default" # Spring profile setting + port-a: 8080 # Application port A + port-b: 8081 # Application port B admin: - username: "admin" # Backend admin username - password: "admin_password" # Backend admin password + username: "admin" # Username for the backend admin + password: "admin_password" # Password for the backend admin docker: - dockerfile-path: "/jenkins/prod/Dockerfile" # Path to the Dockerfile - nginx-container-name: "nginx" # Nginx container name - postgresql-container-name: "postgresql" # PostgreSQL container name + dockerfile-path: "/jenkins/prod/Dockerfile" # Path to the Dockerfile used for building the application image + nginx-container-name: "nginx" # Name of the Nginx container + postgresql-container-name: "postgresql" # Name of the PostgreSQL container staging: - user: "user" # Staging user name - host: "host" # Staging host name or IP address - backup-dir-path: "/var/backups/postgresql" # Staging Directory Path for PostgreSQL backups - restore-backup-script-path: "/var/restore.sh" # Staging Restore Backup Script Path - ssh-port: 22 # Staging SSH Port - postgresql-user: "postgres_user" # Staging Postgresql User - + user: "user" # Username for accessing the staging environment + host: "host" # Hostname or IP address of the staging server + backup-dir-path: "/var/backups/postgresql" # Directory path on the staging server for PostgreSQL backups + restore-backup-script-path: "/var/restore.sh" # Path to the script on the staging server for restoring backups + ssh-port: 22 # SSH port for accessing the staging server + postgresql-user: "postgres_user" # PostgreSQL username on the staging server diff --git a/jenkins/stage/Jenkinsfile b/jenkins/stage/Jenkinsfile index d22b26d02..77ee2d304 100644 --- a/jenkins/stage/Jenkinsfile +++ b/jenkins/stage/Jenkinsfile @@ -118,31 +118,52 @@ pipeline { } post { - failure { + success { script { - sendSlackBuildNotification(":scream_cat: Stage *${FAILED_STAGE}* failed.", env.SLACK_COLOR_FAILURE) + sendBuildNotification(":rocket: Deployment completed successfully", env.NOTIFICATION_COLOR_SUCCESS) } } - - success { + failure { script { - sendSlackBuildNotification(":rocket: Deployment completed successfully", env.SLACK_COLOR_SUCCESS) + sendBuildNotification(":scream_cat: Deployment failed in stage *${FAILED_STAGE}*", env.NOTIFICATION_COLOR_FAILURE) } } } } -def sendSlackBuildNotification(String message, String color) { +def sendBuildNotification(String message, String color) { def jobUrl = "${env.JENKINS_DOMAIN}/job/${env.JOB_NAME}" def consoleOutputUrl = "${jobUrl}/${env.BUILD_NUMBER}/console" + def changelog = env.GIT_CHANGELOG + + def notificationPlatforms = readJSON text: env.NOTIFICATION_PLATFORMS_JSON.trim() + + notificationPlatforms.each { notification -> + if (notification.enabled.toBoolean()) { + def platform = notification.platform ? notification.platform.trim().toLowerCase() : "" + echo "Processing notification for platform: '${platform}'" + + def payload = null + switch (platform) { + case 'slack': + payload = createSlackPayload(message, color, jobUrl, consoleOutputUrl, changelog) + break + case 'discord': + payload = createDiscordPayload(message, color, jobUrl, consoleOutputUrl, changelog) + break + default: + echo "Unsupported or undefined notification platform: '${platform}'" + } - def payload = createSlackPayload(message, color, jobUrl, consoleOutputUrl) - def payloadJson = groovy.json.JsonOutput.toJson(payload) - - sendHttpPostRequest(env.SLACK_WEBHOOK_URL, payloadJson) + if (payload != null) { + def payloadJson = groovy.json.JsonOutput.toJson(payload) + sendHttpPostRequest(notification.'webhook-url', payloadJson) + } + } + } } -def createSlackPayload(String message, String color, String jobUrl, String consoleOutputUrl) { +def createSlackPayload(String message, String color, String jobUrl, String consoleOutputUrl, String changelog) { return [ blocks: [ [ @@ -161,7 +182,7 @@ def createSlackPayload(String message, String color, String jobUrl, String conso type: "section", text: [ type: "mrkdwn", - text: "*Change Log:*\n${env.GIT_CHANGELOG}" + text: "*Change Log:*\n${changelog}" ] ], [ @@ -195,6 +216,23 @@ def createSlackPayload(String message, String color, String jobUrl, String conso ] } +def createDiscordPayload(String message, String color, String jobUrl, String consoleOutputUrl, String changelog) { + return [ + embeds: [ + [ + title: message, + color: parseColor(color), + description: "*Change Log:*\n${changelog}\n\n[Job](${jobUrl}) | [Console Output](${consoleOutputUrl})", + ] + ] + ] +} + +def parseColor(String hexColor) { + // Discord requires the color in decimal format + return Integer.parseInt(hexColor.replace("#", ""), 16) +} + def sendHttpPostRequest(String url, String payload) { def CONTENT_TYPE_JSON = 'application/json' def HTTP_POST = 'POST' @@ -211,9 +249,9 @@ def loadEnvironmentVariables(String configFile) { def config = readYaml(file: configFile) env.JENKINS_DOMAIN = config.'jenkins-domain' - env.SLACK_WEBHOOK_URL = config.slack.'webhook-url' - env.SLACK_COLOR_SUCCESS = config.slack.'color-success' - env.SLACK_COLOR_FAILURE = config.slack.'color-failure' + env.NOTIFICATION_COLOR_SUCCESS = config.notifications.common.'color-success' + env.NOTIFICATION_COLOR_FAILURE = config.notifications.common.'color-failure' + env.NOTIFICATION_PLATFORMS_JSON = groovy.json.JsonOutput.toJson(config.notifications.platforms) env.PG_USER = config.postgresql.user env.PG_PASSWORD = config.postgresql.password diff --git a/jenkins/stage/config.yml b/jenkins/stage/config.yml index 4ba799d8c..7adf0faf3 100644 --- a/jenkins/stage/config.yml +++ b/jenkins/stage/config.yml @@ -1,50 +1,66 @@ -jenkins-domain: "https://jenkins.example.com" # Jenkins domain +jenkins-domain: "https://jenkins.example.com" # Base URL of the Jenkins instance -slack: - webhook-url: "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX" # Slack webhook URL - color-success: "#F2C744" # Slack message color for success - color-failure: "#8D1E0E" # Slack message color for failure +notifications: + common: + color-success: "#F2C744" # Common hex color code for success messages across all notification platforms + color-failure: "#8D1E0E" # Common hex color code for failure messages across all notification platforms + + platforms: + - platform: "slack" # Name of the notification platform (e.g., Slack) + enabled: true # Enable (true) or disable (false) notifications for this platform + webhook-url: "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX" # Webhook URL for sending messages to Slack + + - platform: "discord" # Name of the notification platform (e.g., Discord) + enabled: true # Enable (true) or disable (false) notifications for this platform + webhook-url: "https://discord.com/api/webhooks/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" # Webhook URL for sending messages to Discord + + # Add additional notification platforms below as needed + # Example: + # - platform: "microsoft-teams" + # enabled: true + # webhook-url: "https://outlook.office.com/webhook/..." postgresql: - user: "postgres_user" # PostgreSQL username - password: "postgres_password" # PostgreSQL password - backup-dir: "/var/backups/postgresql" # Directory for PostgreSQL backups + user: "postgres_user" # Username for PostgreSQL + password: "postgres_password" # Password for PostgreSQL + backup-dir: "/var/backups/postgresql" # Directory where PostgreSQL backups are stored dockerhub: - repo: "yourdockerhub/repository" # Docker Hub repository name - user: "dockerhub_user" # Docker Hub username - password: "dockerhub_password" # Docker Hub password + repo: "yourdockerhub/repository" # Docker Hub repository name + user: "dockerhub_user" # Docker Hub username + password: "dockerhub_password" # Docker Hub password external-server: - config-path: "/path/to/external/config" # Path for external server configuration - cloud-path: "/path/to/external/cloud" # Path for external server cloud storage - logs-path: "/path/to/external/logs" # Path for external server logs + config-path: "/path/to/external/config" # Path to external server configuration files + cloud-path: "/path/to/external/cloud" # Path to external server cloud storage + logs-path: "/path/to/external/logs" # Path to external server log files internal-server: - config-path: "/path/to/internal/config" # Path for internal server configuration - cloud-path: "/path/to/internal/cloud" # Path for internal server cloud storage - logs-path: "/path/to/internal/logs" # Path for internal server logs + config-path: "/path/to/internal/config" # Path to internal server configuration files + cloud-path: "/path/to/internal/cloud" # Path to internal server cloud storage + logs-path: "/path/to/internal/logs" # Path to internal server log files containers: - blue: "blue-container" # Blue-Green deployment: Blue container name - green: "green-container" # Blue-Green deployment: Green container name - blue-url: "http://blue-container:8080" # URL for the Blue container environment - green-url: "http://green-container:8080" # URL for the Green container environment - image-name: "application-image" # Docker image name for the application + blue: "blue-container" # Name of the Blue container in Blue-Green deployment + green: "green-container" # Name of the Green container in Blue-Green deployment + blue-url: "http://blue-container:8080" # URL for accessing the Blue container environment + green-url: "http://green-container:8080" # URL for accessing the Green container environment + image-name: "application-image" # Docker image name for the application networks: - application: "application-network" # Docker network for the application - monitoring: "monitoring-network" # Docker network for monitoring + application: "application-network" # Docker network used by the application + monitoring: "monitoring-network" # Docker network used for monitoring services spring: - profile: "default" # Spring profile setting - port-a: 8080 # Application port A - port-b: 8081 # Application port B + profile: "default" # Spring profile setting + port-a: 8080 # Application port A + port-b: 8081 # Application port B admin: - username: "admin" # Backend admin username - password: "admin_password" # Backend admin password + username: "admin" # Username for the backend admin + password: "admin_password" # Password for the backend admin + docker: - dockerfile-path: "/jenkins/stage/Dockerfile" # Path to the Dockerfile - nginx-container-name: "nginx" # Nginx container name - postgresql-container-name: "postgresql" # PostgreSQL container name + dockerfile-path: "/jenkins/stage/Dockerfile" # Path to the Dockerfile used for building the application image + nginx-container-name: "nginx" # Name of the Nginx container + postgresql-container-name: "postgresql" # Name of the PostgreSQL container From 3dd26a83c3c21ee590e876927b95cf3e4e258deb Mon Sep 17 00:00:00 2001 From: JEON MIN JU <96719969+mingmingmon@users.noreply.github.com> Date: Fri, 20 Dec 2024 22:19:04 +0900 Subject: [PATCH 26/28] =?UTF-8?q?refactor=20:=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EC=9D=B4=EB=AA=A8=EC=A7=80=EC=99=80=20=EB=8C=93?= =?UTF-8?q?=EA=B8=80=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EB=88=84=EB=A5=B4?= =?UTF-8?q?=EA=B8=B0/=EC=B7=A8=EC=86=8C=ED=95=98=EA=B8=B0=20API=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20DTO=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95=20(#635)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/BoardEmojiToggleController.java | 7 ++++--- .../application/dto/mapper/BoardDtoMapper.java | 1 + .../dto/mapper/BoardEmojiDtoMapper.java | 17 +++++++++++++++++ .../response/BoardEmojiToggleResponseDto.java | 13 +++++++++++++ .../port/in/ToggleBoardEmojiUseCase.java | 4 +++- .../service/BoardEmojiToggleService.java | 7 +++++-- .../in/web/CommentLikeToggleController.java | 7 ++++--- .../dto/mapper/CommentLikeDtoMapper.java | 16 ++++++++++++++++ .../response/CommentLikeToggleResponseDto.java | 15 +++++++++++++++ .../port/in/ToggleCommentLikeUseCase.java | 4 +++- .../service/CommentLikeToggleService.java | 12 +++++++++--- 11 files changed, 90 insertions(+), 13 deletions(-) create mode 100644 src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardEmojiDtoMapper.java create mode 100644 src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardEmojiToggleResponseDto.java create mode 100644 src/main/java/page/clab/api/domain/community/comment/application/dto/mapper/CommentLikeDtoMapper.java create mode 100644 src/main/java/page/clab/api/domain/community/comment/application/dto/response/CommentLikeToggleResponseDto.java diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/BoardEmojiToggleController.java b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/BoardEmojiToggleController.java index 3d77c9ba6..4b402ffd1 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/BoardEmojiToggleController.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/BoardEmojiToggleController.java @@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import page.clab.api.domain.community.board.application.dto.response.BoardEmojiToggleResponseDto; import page.clab.api.domain.community.board.application.port.in.ToggleBoardEmojiUseCase; import page.clab.api.global.common.dto.ApiResponse; @@ -22,11 +23,11 @@ public class BoardEmojiToggleController { @Operation(summary = "[U] 커뮤니티 게시글 이모지 누르기/취소하기", description = "ROLE_USER 이상의 권한이 필요함") @PreAuthorize("hasRole('USER')") @PostMapping("/{boardId}/react/{emoji}") - public ApiResponse toggleEmojiStatus( + public ApiResponse toggleEmojiStatus( @PathVariable(name = "boardId") Long boardId, @PathVariable(name = "emoji") String emoji ) { - String id = toggleBoardEmojiUseCase.toggleEmojiStatus(boardId, emoji); - return ApiResponse.success(id); + BoardEmojiToggleResponseDto responseDto = toggleBoardEmojiUseCase.toggleEmojiStatus(boardId, emoji); + return ApiResponse.success(responseDto); } } diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardDtoMapper.java b/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardDtoMapper.java index 8ba304cd7..67f37ce0a 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardDtoMapper.java +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardDtoMapper.java @@ -6,6 +6,7 @@ import page.clab.api.domain.community.board.application.dto.response.BoardCategoryResponseDto; import page.clab.api.domain.community.board.application.dto.response.BoardDetailsResponseDto; import page.clab.api.domain.community.board.application.dto.response.BoardEmojiCountResponseDto; +import page.clab.api.domain.community.board.application.dto.response.BoardEmojiToggleResponseDto; import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; import page.clab.api.domain.community.board.application.dto.response.BoardMyResponseDto; import page.clab.api.domain.community.board.application.dto.response.WriterInfo; diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardEmojiDtoMapper.java b/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardEmojiDtoMapper.java new file mode 100644 index 000000000..be2fb81dd --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardEmojiDtoMapper.java @@ -0,0 +1,17 @@ +package page.clab.api.domain.community.board.application.dto.mapper; + +import org.springframework.stereotype.Component; +import page.clab.api.domain.community.board.application.dto.response.BoardEmojiToggleResponseDto; +import page.clab.api.domain.community.board.domain.BoardEmoji; + +@Component +public class BoardEmojiDtoMapper { + + public BoardEmojiToggleResponseDto toDto(BoardEmoji boardEmoji) { + return BoardEmojiToggleResponseDto.builder() + .boardId(boardEmoji.getBoardId()) + .emoji(boardEmoji.getEmoji()) + .isDeleted(boardEmoji.getIsDeleted()) + .build(); + } +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardEmojiToggleResponseDto.java b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardEmojiToggleResponseDto.java new file mode 100644 index 000000000..5ac044ac8 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardEmojiToggleResponseDto.java @@ -0,0 +1,13 @@ +package page.clab.api.domain.community.board.application.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class BoardEmojiToggleResponseDto { + + private Long boardId; + private String emoji; + private Boolean isDeleted; +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/in/ToggleBoardEmojiUseCase.java b/src/main/java/page/clab/api/domain/community/board/application/port/in/ToggleBoardEmojiUseCase.java index 84ad15d47..a082738e2 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/port/in/ToggleBoardEmojiUseCase.java +++ b/src/main/java/page/clab/api/domain/community/board/application/port/in/ToggleBoardEmojiUseCase.java @@ -1,5 +1,7 @@ package page.clab.api.domain.community.board.application.port.in; +import page.clab.api.domain.community.board.application.dto.response.BoardEmojiToggleResponseDto; + public interface ToggleBoardEmojiUseCase { - String toggleEmojiStatus(Long boardId, String emoji); + BoardEmojiToggleResponseDto toggleEmojiStatus(Long boardId, String emoji); } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardEmojiToggleService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardEmojiToggleService.java index fbd38c661..59fcae1a7 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/BoardEmojiToggleService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardEmojiToggleService.java @@ -3,6 +3,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import page.clab.api.domain.community.board.application.dto.mapper.BoardEmojiDtoMapper; +import page.clab.api.domain.community.board.application.dto.response.BoardEmojiToggleResponseDto; import page.clab.api.domain.community.board.application.port.in.ToggleBoardEmojiUseCase; import page.clab.api.domain.community.board.application.port.out.RegisterBoardEmojiPort; import page.clab.api.domain.community.board.application.port.out.RetrieveBoardEmojiPort; @@ -22,6 +24,7 @@ public class BoardEmojiToggleService implements ToggleBoardEmojiUseCase { private final RetrieveBoardEmojiPort retrieveBoardEmojiPort; private final RegisterBoardEmojiPort registerBoardEmojiPort; private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; + private final BoardEmojiDtoMapper mapper; /** * 게시글의 이모지 상태를 토글합니다. @@ -36,7 +39,7 @@ public class BoardEmojiToggleService implements ToggleBoardEmojiUseCase { */ @Transactional @Override - public String toggleEmojiStatus(Long boardId, String emoji) { + public BoardEmojiToggleResponseDto toggleEmojiStatus(Long boardId, String emoji) { if (!EmojiUtils.isEmoji(emoji)) { throw new InvalidEmojiException("지원하지 않는 이모지입니다."); } @@ -50,6 +53,6 @@ public String toggleEmojiStatus(Long boardId, String emoji) { }) .orElseGet(() -> BoardEmoji.create(memberId, boardId, emoji)); registerBoardEmojiPort.save(boardEmoji); - return board.getCategory().getKey(); + return mapper.toDto(boardEmoji); } } diff --git a/src/main/java/page/clab/api/domain/community/comment/adapter/in/web/CommentLikeToggleController.java b/src/main/java/page/clab/api/domain/community/comment/adapter/in/web/CommentLikeToggleController.java index a6ac318c9..0f2f47597 100644 --- a/src/main/java/page/clab/api/domain/community/comment/adapter/in/web/CommentLikeToggleController.java +++ b/src/main/java/page/clab/api/domain/community/comment/adapter/in/web/CommentLikeToggleController.java @@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import page.clab.api.domain.community.comment.application.dto.response.CommentLikeToggleResponseDto; import page.clab.api.domain.community.comment.application.port.in.ToggleCommentLikeUseCase; import page.clab.api.global.common.dto.ApiResponse; @@ -22,10 +23,10 @@ public class CommentLikeToggleController { @Operation(summary = "[U] 댓글 좋아요 누르기/취소하기", description = "ROLE_USER 이상의 권한이 필요함") @PreAuthorize("hasRole('USER')") @PostMapping("/likes/{commentId}") - public ApiResponse toggleLikeStatus( + public ApiResponse toggleLikeStatus( @PathVariable(name = "commentId") Long commentId ) { - Long id = toggleCommentLikeUseCase.toggleLikeStatus(commentId); - return ApiResponse.success(id); + CommentLikeToggleResponseDto responseDto = toggleCommentLikeUseCase.toggleLikeStatus(commentId); + return ApiResponse.success(responseDto); } } diff --git a/src/main/java/page/clab/api/domain/community/comment/application/dto/mapper/CommentLikeDtoMapper.java b/src/main/java/page/clab/api/domain/community/comment/application/dto/mapper/CommentLikeDtoMapper.java new file mode 100644 index 000000000..872568c56 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/comment/application/dto/mapper/CommentLikeDtoMapper.java @@ -0,0 +1,16 @@ +package page.clab.api.domain.community.comment.application.dto.mapper; + +import org.springframework.stereotype.Component; +import page.clab.api.domain.community.comment.application.dto.response.CommentLikeToggleResponseDto; + +@Component +public class CommentLikeDtoMapper { + + public CommentLikeToggleResponseDto of(Long boardId, Long commentLikes, Boolean isDeleted) { + return CommentLikeToggleResponseDto.builder() + .boardId(boardId) + .likes(commentLikes) + .isDeleted(isDeleted) + .build(); + } +} diff --git a/src/main/java/page/clab/api/domain/community/comment/application/dto/response/CommentLikeToggleResponseDto.java b/src/main/java/page/clab/api/domain/community/comment/application/dto/response/CommentLikeToggleResponseDto.java new file mode 100644 index 000000000..a7317773f --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/comment/application/dto/response/CommentLikeToggleResponseDto.java @@ -0,0 +1,15 @@ +package page.clab.api.domain.community.comment.application.dto.response; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class CommentLikeToggleResponseDto { + + private Long boardId; + private Long likes; + private Boolean isDeleted; +} diff --git a/src/main/java/page/clab/api/domain/community/comment/application/port/in/ToggleCommentLikeUseCase.java b/src/main/java/page/clab/api/domain/community/comment/application/port/in/ToggleCommentLikeUseCase.java index 387c2e94b..0a5f3792e 100644 --- a/src/main/java/page/clab/api/domain/community/comment/application/port/in/ToggleCommentLikeUseCase.java +++ b/src/main/java/page/clab/api/domain/community/comment/application/port/in/ToggleCommentLikeUseCase.java @@ -1,5 +1,7 @@ package page.clab.api.domain.community.comment.application.port.in; +import page.clab.api.domain.community.comment.application.dto.response.CommentLikeToggleResponseDto; + public interface ToggleCommentLikeUseCase { - Long toggleLikeStatus(Long commentId); + CommentLikeToggleResponseDto toggleLikeStatus(Long commentId); } diff --git a/src/main/java/page/clab/api/domain/community/comment/application/service/CommentLikeToggleService.java b/src/main/java/page/clab/api/domain/community/comment/application/service/CommentLikeToggleService.java index 58f1877ee..07280ad5c 100644 --- a/src/main/java/page/clab/api/domain/community/comment/application/service/CommentLikeToggleService.java +++ b/src/main/java/page/clab/api/domain/community/comment/application/service/CommentLikeToggleService.java @@ -3,6 +3,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import page.clab.api.domain.community.comment.application.dto.mapper.CommentLikeDtoMapper; +import page.clab.api.domain.community.comment.application.dto.response.CommentLikeToggleResponseDto; import page.clab.api.domain.community.comment.application.port.in.ToggleCommentLikeUseCase; import page.clab.api.domain.community.comment.application.port.out.RegisterCommentLikePort; import page.clab.api.domain.community.comment.application.port.out.RemoveCommentLikePort; @@ -23,6 +25,7 @@ public class CommentLikeToggleService implements ToggleCommentLikeUseCase { private final ExternalRetrieveCommentUseCase externalRetrieveCommentUseCase; private final ExternalRegisterCommentUseCase externalRegisterCommentUseCase; private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; + private final CommentLikeDtoMapper mapper; /** * 댓글의 좋아요 상태를 토글합니다. @@ -36,20 +39,23 @@ public class CommentLikeToggleService implements ToggleCommentLikeUseCase { */ @Transactional @Override - public Long toggleLikeStatus(Long commentId) { + public CommentLikeToggleResponseDto toggleLikeStatus(Long commentId) { String currentMemberId = externalRetrieveMemberUseCase.getCurrentMemberId(); Comment comment = externalRetrieveCommentUseCase.getById(commentId); + Long boardId = comment.getBoardId(); return retrieveCommentLikePort.findByCommentIdAndMemberId(comment.getId(), currentMemberId) .map(commentLike -> { removeCommentLikePort.delete(commentLike); comment.decrementLikes(); - return externalRegisterCommentUseCase.save(comment).getLikes(); + externalRegisterCommentUseCase.save(comment); + return mapper.of(boardId, comment.getLikes(), true); }) .orElseGet(() -> { CommentLike newLike = CommentLike.create(currentMemberId, comment.getId()); registerCommentLikePort.save(newLike); comment.incrementLikes(); - return externalRegisterCommentUseCase.save(comment).getLikes(); + externalRegisterCommentUseCase.save(comment); + return mapper.of(boardId, comment.getLikes(), false); }); } } From ec1ecda5afcacc266bc153bf648953fca3f9d6fa Mon Sep 17 00:00:00 2001 From: JEON MIN JU <96719969+mingmingmon@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:08:33 +0900 Subject: [PATCH 27/28] =?UTF-8?q?refactor=20:=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EC=9D=B4=EB=AA=A8=EC=A7=80=20=EB=88=84=EB=A5=B4?= =?UTF-8?q?=EA=B8=B0/=EC=B7=A8=EC=86=8C=ED=95=98=EA=B8=B0=20api=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=EC=97=90=EC=84=9C=20board=20category=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#639)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/application/dto/mapper/BoardEmojiDtoMapper.java | 3 ++- .../application/dto/response/BoardEmojiToggleResponseDto.java | 1 + .../board/application/service/BoardEmojiToggleService.java | 2 +- .../clab/api/domain/community/board/domain/BoardCategory.java | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardEmojiDtoMapper.java b/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardEmojiDtoMapper.java index be2fb81dd..be063ce1c 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardEmojiDtoMapper.java +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardEmojiDtoMapper.java @@ -7,9 +7,10 @@ @Component public class BoardEmojiDtoMapper { - public BoardEmojiToggleResponseDto toDto(BoardEmoji boardEmoji) { + public BoardEmojiToggleResponseDto toDto(BoardEmoji boardEmoji, String category) { return BoardEmojiToggleResponseDto.builder() .boardId(boardEmoji.getBoardId()) + .category(category) .emoji(boardEmoji.getEmoji()) .isDeleted(boardEmoji.getIsDeleted()) .build(); diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardEmojiToggleResponseDto.java b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardEmojiToggleResponseDto.java index 5ac044ac8..4dbf1b331 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardEmojiToggleResponseDto.java +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardEmojiToggleResponseDto.java @@ -8,6 +8,7 @@ public class BoardEmojiToggleResponseDto { private Long boardId; + private String category; private String emoji; private Boolean isDeleted; } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardEmojiToggleService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardEmojiToggleService.java index 59fcae1a7..57b03418f 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/BoardEmojiToggleService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardEmojiToggleService.java @@ -53,6 +53,6 @@ public BoardEmojiToggleResponseDto toggleEmojiStatus(Long boardId, String emoji) }) .orElseGet(() -> BoardEmoji.create(memberId, boardId, emoji)); registerBoardEmojiPort.save(boardEmoji); - return mapper.toDto(boardEmoji); + return mapper.toDto(boardEmoji, board.getCategory().getKey()); } } diff --git a/src/main/java/page/clab/api/domain/community/board/domain/BoardCategory.java b/src/main/java/page/clab/api/domain/community/board/domain/BoardCategory.java index 831bad896..757a599ae 100644 --- a/src/main/java/page/clab/api/domain/community/board/domain/BoardCategory.java +++ b/src/main/java/page/clab/api/domain/community/board/domain/BoardCategory.java @@ -9,8 +9,8 @@ public enum BoardCategory { NOTICE("notice", "공지사항"), FREE("free", "자유 게시판"), - DEVELOPMENT_QNA("development_qna", "개발 질문 게시판"), - INFORMATION_REVIEWS("information_reviews", "정보 및 후기 게시판"), + DEVELOPMENT_QNA("developmentQna", "개발 질문 게시판"), + INFORMATION_REVIEWS("informationReviews", "정보 및 후기 게시판"), ORGANIZATION("organization", "동아리 소식"); private final String key; From d77c3ce4766712ac54fa484baeb3255c0f0b86b0 Mon Sep 17 00:00:00 2001 From: JEON MIN JU <96719969+mingmingmon@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:59:48 +0900 Subject: [PATCH 28/28] =?UTF-8?q?refactor=20:=20BoardCategory=EC=9D=98=20k?= =?UTF-8?q?ey=20=ED=91=9C=ED=98=84=20=EB=B0=A9=EC=8B=9D=20=EC=86=8C?= =?UTF-8?q?=EB=AC=B8=EC=9E=90=20+=20=EC=8A=A4=EB=84=A4=EC=9D=B4=ED=81=AC?= =?UTF-8?q?=EB=A1=9C=20=EB=A1=A4=EB=B0=B1=20(#641)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../clab/api/domain/community/board/domain/BoardCategory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/page/clab/api/domain/community/board/domain/BoardCategory.java b/src/main/java/page/clab/api/domain/community/board/domain/BoardCategory.java index 757a599ae..831bad896 100644 --- a/src/main/java/page/clab/api/domain/community/board/domain/BoardCategory.java +++ b/src/main/java/page/clab/api/domain/community/board/domain/BoardCategory.java @@ -9,8 +9,8 @@ public enum BoardCategory { NOTICE("notice", "공지사항"), FREE("free", "자유 게시판"), - DEVELOPMENT_QNA("developmentQna", "개발 질문 게시판"), - INFORMATION_REVIEWS("informationReviews", "정보 및 후기 게시판"), + DEVELOPMENT_QNA("development_qna", "개발 질문 게시판"), + INFORMATION_REVIEWS("information_reviews", "정보 및 후기 게시판"), ORGANIZATION("organization", "동아리 소식"); private final String key;