From 2adae28791a911812702af09fb8697e5c923110b Mon Sep 17 00:00:00 2001 From: jihhyeong Date: Sun, 2 Feb 2025 17:49:12 +0900 Subject: [PATCH 1/2] =?UTF-8?q?:sparkles:=20=EB=81=8C=EC=98=AC=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/controller/BoardController.java | 13 ++++++++ .../content/board/domain/Board.java | 15 ++++++++++ .../board/dto/response/BoardBumpResponse.java | 20 +++++++++++++ .../board/service/BoardFacadeService.java | 12 ++++++++ .../content/board/service/BoardService.java | 30 ++++++++++++++++++- .../core/exception/common/ErrorCode.java | 3 +- 6 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/gamegoo/gamegoo_v2/content/board/dto/response/BoardBumpResponse.java diff --git a/src/main/java/com/gamegoo/gamegoo_v2/content/board/controller/BoardController.java b/src/main/java/com/gamegoo/gamegoo_v2/content/board/controller/BoardController.java index 7edab7fe..60bce88d 100644 --- a/src/main/java/com/gamegoo/gamegoo_v2/content/board/controller/BoardController.java +++ b/src/main/java/com/gamegoo/gamegoo_v2/content/board/controller/BoardController.java @@ -7,6 +7,7 @@ import com.gamegoo.gamegoo_v2.account.member.domain.Tier; import com.gamegoo.gamegoo_v2.content.board.dto.request.BoardInsertRequest; import com.gamegoo.gamegoo_v2.content.board.dto.request.BoardUpdateRequest; +import com.gamegoo.gamegoo_v2.content.board.dto.response.BoardBumpResponse; import com.gamegoo.gamegoo_v2.content.board.dto.response.BoardByIdResponse; import com.gamegoo.gamegoo_v2.content.board.dto.response.BoardByIdResponseForMember; import com.gamegoo.gamegoo_v2.content.board.dto.response.BoardInsertResponse; @@ -124,4 +125,16 @@ public ApiResponse getMyBoardList(@ValidPage @RequestParam(name return ApiResponse.ok(boardFacadeService.getMyBoardList(member, page)); } + /** + * 게시글 끌올(bump) API + * 사용자가 "끌올" 버튼을 누르면 해당 게시글의 bumpTime이 업데이트되어 상단으로 노출됩니다. + */ + @PostMapping("/{boardId}/bump") + @Operation(summary = "게시글 끌올 API", description = "게시글을 끌올하여 상단 노출시키는 API 입니다. 마지막 끌올 후 1시간 제한이 적용됩니다.") + @Parameter(name = "boardId", description = "끌올할 게시판 글 id 입니다.") + public ApiResponse bumpBoard(@PathVariable Long boardId, + @AuthMember Member member) { + return ApiResponse.ok(boardFacadeService.bumpBoard(boardId, member)); + } + } diff --git a/src/main/java/com/gamegoo/gamegoo_v2/content/board/domain/Board.java b/src/main/java/com/gamegoo/gamegoo_v2/content/board/domain/Board.java index 79ac8330..8dc0af4a 100644 --- a/src/main/java/com/gamegoo/gamegoo_v2/content/board/domain/Board.java +++ b/src/main/java/com/gamegoo/gamegoo_v2/content/board/domain/Board.java @@ -21,7 +21,9 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.Formula; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -71,6 +73,11 @@ public class Board extends BaseDateTimeEntity { @OneToMany(mappedBy = "board", cascade = CascadeType.PERSIST, orphanRemoval = true) private List boardGameStyles = new ArrayList<>(); + private LocalDateTime bumpTime; + + @Formula("GREATEST(COALESCE(bump_time, created_at), created_at)") + private LocalDateTime activityTime; + public static Board create(Member member, GameMode gameMode, Position mainP, Position subP, Position wantP, @@ -143,5 +150,13 @@ public void setDeleted(boolean deleted) { this.deleted = deleted; } + public void bump(LocalDateTime bumpTime) { + this.bumpTime = bumpTime; + } + + public LocalDateTime getActivityTime() { + return activityTime; + } + } diff --git a/src/main/java/com/gamegoo/gamegoo_v2/content/board/dto/response/BoardBumpResponse.java b/src/main/java/com/gamegoo/gamegoo_v2/content/board/dto/response/BoardBumpResponse.java new file mode 100644 index 00000000..79b7694c --- /dev/null +++ b/src/main/java/com/gamegoo/gamegoo_v2/content/board/dto/response/BoardBumpResponse.java @@ -0,0 +1,20 @@ +package com.gamegoo.gamegoo_v2.content.board.dto.response; + +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +public class BoardBumpResponse { + + private Long boardId; + private LocalDateTime bumpTime; + + public static BoardBumpResponse of(Long boardId, LocalDateTime bumpTime) { + BoardBumpResponse response = new BoardBumpResponse(); + response.boardId = boardId; + response.bumpTime = bumpTime; + return response; + } + +} diff --git a/src/main/java/com/gamegoo/gamegoo_v2/content/board/service/BoardFacadeService.java b/src/main/java/com/gamegoo/gamegoo_v2/content/board/service/BoardFacadeService.java index e335a16b..f97af0b7 100644 --- a/src/main/java/com/gamegoo/gamegoo_v2/content/board/service/BoardFacadeService.java +++ b/src/main/java/com/gamegoo/gamegoo_v2/content/board/service/BoardFacadeService.java @@ -8,6 +8,7 @@ import com.gamegoo.gamegoo_v2.content.board.domain.Board; import com.gamegoo.gamegoo_v2.content.board.dto.request.BoardInsertRequest; import com.gamegoo.gamegoo_v2.content.board.dto.request.BoardUpdateRequest; +import com.gamegoo.gamegoo_v2.content.board.dto.response.BoardBumpResponse; import com.gamegoo.gamegoo_v2.content.board.dto.response.BoardByIdResponse; import com.gamegoo.gamegoo_v2.content.board.dto.response.BoardByIdResponseForMember; import com.gamegoo.gamegoo_v2.content.board.dto.response.BoardInsertResponse; @@ -131,5 +132,16 @@ public MyBoardResponse getMyBoardList(Member member, int pageIdx) { return MyBoardResponse.of(boardPage); } + /** + * 게시글 끌올(bump) 기능 (파사드) + * 사용자가 "끌올" 버튼을 누르면 해당 게시글의 bumpTime을 업데이트합니다. + * 단, 마지막 끌올 후 1시간이 지나지 않았다면 예외를 발생시킵니다. + */ + + @Transactional + public BoardBumpResponse bumpBoard(Long boardId, Member member) { + Board board = boardService.bumpBoard(boardId, member.getId()); + return BoardBumpResponse.of(board.getId(), board.getBumpTime()); + } } diff --git a/src/main/java/com/gamegoo/gamegoo_v2/content/board/service/BoardService.java b/src/main/java/com/gamegoo/gamegoo_v2/content/board/service/BoardService.java index 49ef0e81..a7abd052 100644 --- a/src/main/java/com/gamegoo/gamegoo_v2/content/board/service/BoardService.java +++ b/src/main/java/com/gamegoo/gamegoo_v2/content/board/service/BoardService.java @@ -19,6 +19,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.Duration; +import java.time.LocalDateTime; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -27,6 +30,7 @@ public class BoardService { private final BoardRepository boardRepository; public static final int PAGE_SIZE = 20; public static final int MY_PAGE_SIZE = 10; + private static final Duration BUMP_INTERVAL = Duration.ofHours(1); /** * 게시글 엔티티 생성 및 저장 @@ -63,7 +67,7 @@ public Page findBoards(GameMode gameMode, Tier tier, Position mainP, Mike public Page getBoardsWithPagination(GameMode gameMode, Tier tier, Position mainP, Mike mike, int pageIdx) { - Pageable pageable = PageRequest.of(pageIdx - 1, PAGE_SIZE, Sort.by(Sort.Direction.DESC, "createdAt")); + Pageable pageable = PageRequest.of(pageIdx - 1, PAGE_SIZE, Sort.by(Sort.Direction.DESC, "activityTime")); return findBoards(gameMode, tier, mainP, mike, pageable); } @@ -136,4 +140,28 @@ public Board saveBoard(Board board) { return boardRepository.save(board); } + /** + * 끌올 기능: 사용자가 게시글을 끌올하면 bumpTime을 현재 시간으로 업데이트 + */ + @Transactional + public Board bumpBoard(Long boardId, Long memberId) { + Board board = boardRepository.findByIdAndDeleted(boardId, false) + .orElseThrow(() -> new BoardException(ErrorCode.BOARD_NOT_FOUND)); + + if (!board.getMember().getId().equals(memberId)) { + throw new BoardException(ErrorCode.BUMP_ACCESS_DENIED); + } + + LocalDateTime now = LocalDateTime.now(); + if (board.getBumpTime() != null) { + Duration diff = Duration.between(board.getBumpTime(), now); + if (diff.compareTo(BUMP_INTERVAL) < 0) { + throw new BoardException(ErrorCode.BUMP_TIME_LIMIT); + } + } + + board.bump(now); + return boardRepository.save(board); + } + } diff --git a/src/main/java/com/gamegoo/gamegoo_v2/core/exception/common/ErrorCode.java b/src/main/java/com/gamegoo/gamegoo_v2/core/exception/common/ErrorCode.java index 51a46827..039a625d 100644 --- a/src/main/java/com/gamegoo/gamegoo_v2/core/exception/common/ErrorCode.java +++ b/src/main/java/com/gamegoo/gamegoo_v2/core/exception/common/ErrorCode.java @@ -130,7 +130,8 @@ public enum ErrorCode { BOARD_PAGE_BAD_REQUEST(BAD_REQUEST, "BOARD_407", "페이지 값은 0 이상만 가능합니다."), BOARD_FORBIDDEN_WORD(BAD_REQUEST, "BOARD_408", "금지어가 포함되어 있습니다."), BOARD_FORBIDDEN_WORD_LOAD_FAILED(INTERNAL_SERVER_ERROR, "BOARD_409", "금지어 파일을 읽어오는데 실패했습니다."), - + BUMP_ACCESS_DENIED(FORBIDDEN, "BOARD_410", "게시글 끌어올리기 권한이 없습니다."), + BUMP_TIME_LIMIT(BAD_REQUEST, "BOARD_411", "게시글 끌어올리기는 24시간에 1회만 가능합니다."), /** * 매너평가 관련 에러 */ From ec38e4f76a3335624bc99b44acb87fc7064bc4be Mon Sep 17 00:00:00 2001 From: jihhyeong Date: Sun, 2 Feb 2025 21:00:58 +0900 Subject: [PATCH 2/2] =?UTF-8?q?:bug:=20[Fix]=20=EB=81=8C=EC=98=AC=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=BF=A8=ED=83=80=EC=9E=84=201=EB=B6=84?= =?UTF-8?q?=EC=9C=BC=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 --- .../gamegoo/gamegoo_v2/content/board/service/BoardService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/gamegoo/gamegoo_v2/content/board/service/BoardService.java b/src/main/java/com/gamegoo/gamegoo_v2/content/board/service/BoardService.java index a7abd052..edde1771 100644 --- a/src/main/java/com/gamegoo/gamegoo_v2/content/board/service/BoardService.java +++ b/src/main/java/com/gamegoo/gamegoo_v2/content/board/service/BoardService.java @@ -30,7 +30,7 @@ public class BoardService { private final BoardRepository boardRepository; public static final int PAGE_SIZE = 20; public static final int MY_PAGE_SIZE = 10; - private static final Duration BUMP_INTERVAL = Duration.ofHours(1); + private static final Duration BUMP_INTERVAL = Duration.ofMinutes(1); /** * 게시글 엔티티 생성 및 저장