From 0def2b98dfba9a6526b3c35a322c339f084349c0 Mon Sep 17 00:00:00 2001 From: LEE SO JUNG <90453158+xxoznge@users.noreply.github.com> Date: Mon, 23 Sep 2024 12:13:29 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=83=80=EC=9E=85=20=EB=B3=84=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=20=EB=A7=88=EC=A7=80=EB=A7=89=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EC=8B=9C=EA=B0=84=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?(#78)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../card/controller/CardController.java | 31 ++++++++++---- .../card/dto/request/CardCreateRequest.java | 3 ++ .../domain/card/entity/Card.java | 4 ++ .../domain/card/exception/CardErrorCode.java | 3 +- .../card/repository/CardRepository.java | 10 +++++ .../domain/card/service/CardQueryService.java | 42 +++++++++++++------ .../domain/card/service/CardService.java | 4 +- .../user/dto/request/UserJoinRequest.java | 1 - 8 files changed, 76 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/controller/CardController.java b/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/controller/CardController.java index c240d15..2f5c220 100644 --- a/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/controller/CardController.java +++ b/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/controller/CardController.java @@ -38,6 +38,7 @@ public class CardController { private final CardService cardService; private final CardQueryService cardQueryService; + /* 카드 작성 */ @PostMapping(value = "", consumes = "multipart/form-data") public ApiResponse createCard( @UserResolver User authUser, @@ -47,45 +48,61 @@ public ApiResponse createCard( return ApiResponse.onSuccess(cardService.createCard(authUser, request, file)); } - @GetMapping("/{cardId}") - public ApiResponse getCardDetail( - @PathVariable Long cardId - ) { - return ApiResponse.onSuccess(cardQueryService.getCardDetail(cardId)); - } - + /* 오늘의 따봉도치 랭킹 조회 */ @GetMapping("/top") public ApiResponse> getTopCardToday() { return ApiResponse.onSuccess(cardQueryService.getTopCardToday()); } + /* 전체 카드 목록 조회 */ @GetMapping("") public ApiResponse> getCard( @RequestParam(name = "sort") CardStatus sortStatus ) { + // 최신순 조회 if (sortStatus == CardStatus.RECENT) { return ApiResponse.onSuccess(cardQueryService.getRecentCard()); } + // 인기순 조회 if (sortStatus == CardStatus.POPULAR) { return ApiResponse.onSuccess(cardQueryService.getPopularCard()); } return ApiResponse.onSuccess(Collections.emptyList()); } + /* 테마 별 카드 목록 조회 */ @GetMapping("/type") public ApiResponse> getTypeCard( @RequestParam(name = "type") FortuneType type, @RequestParam(name = "sort") CardStatus sortStatus ) { + // 최신순 조회 if (sortStatus == CardStatus.RECENT) { return ApiResponse.onSuccess(cardQueryService.getRecentTypeCard(type)); } + // 인기순 조회 if (sortStatus == CardStatus.POPULAR) { return ApiResponse.onSuccess(cardQueryService.getPopularTypeCard(type)); } return ApiResponse.onSuccess(Collections.emptyList()); } + /* 카드 상세 조회 */ + @GetMapping("/{cardId}") + public ApiResponse getCardDetail( + @PathVariable Long cardId + ) { + return ApiResponse.onSuccess(cardQueryService.getCardDetail(cardId)); + } + + /* 타입 별 카드 마지막 업로드 시간 조회 */ + @GetMapping("/last") + public ApiResponse getLastUploadTime(@RequestParam(name = "type") FortuneType type) { + String lastUploadTime = cardQueryService.getLastUploadTime(type); + return ApiResponse.onSuccess(lastUploadTime); + } + + /* 카드 삭제 */ @DeleteMapping("/{cardId}") public ApiResponse deleteCard( @UserResolver User authUser, diff --git a/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/dto/request/CardCreateRequest.java b/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/dto/request/CardCreateRequest.java index 50c787d..930f44a 100644 --- a/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/dto/request/CardCreateRequest.java +++ b/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/dto/request/CardCreateRequest.java @@ -1,5 +1,7 @@ package com.ddabong.ddabongdotchiBE.domain.card.dto.request; +import java.time.LocalDateTime; + import com.ddabong.ddabongdotchiBE.domain.card.entity.Card; import com.ddabong.ddabongdotchiBE.domain.card.entity.FortuneType; import com.ddabong.ddabongdotchiBE.domain.user.entity.User; @@ -30,6 +32,7 @@ public Card toEntity( .content(content) .type(FortuneType.valueOf(type)) .user(cardUser) + .lastUploadTime(LocalDateTime.now()) .build(); } } diff --git a/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/entity/Card.java b/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/entity/Card.java index 887ca5c..3e694f9 100644 --- a/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/entity/Card.java +++ b/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/entity/Card.java @@ -1,5 +1,6 @@ package com.ddabong.ddabongdotchiBE.domain.card.entity; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -60,6 +61,9 @@ public class Card extends BaseEntity { @Column(name = "comment_count", nullable = false) private long commentCount; + @Column(name = "last_upload_time") + private LocalDateTime lastUploadTime; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) private User user; diff --git a/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/exception/CardErrorCode.java b/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/exception/CardErrorCode.java index b1a0181..d702f5d 100644 --- a/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/exception/CardErrorCode.java +++ b/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/exception/CardErrorCode.java @@ -13,7 +13,8 @@ public enum CardErrorCode implements BaseErrorCode { USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USR4000", "존재하지 않는 사용자입니다."), - CARD_NOT_FOUND(HttpStatus.NOT_FOUND, "CARD4040", "해당 카드가 존재하지 않습니다."); + CARD_NOT_FOUND(HttpStatus.NOT_FOUND, "CARD4040", "해당 카드가 존재하지 않습니다."), + UNAUTHORIZED_ACCESS(HttpStatus.FORBIDDEN, "CARD4030", "권한이 없습니다."); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/repository/CardRepository.java b/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/repository/CardRepository.java index cbab80f..fec615d 100644 --- a/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/repository/CardRepository.java +++ b/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/repository/CardRepository.java @@ -16,17 +16,27 @@ public interface CardRepository extends JpaRepository { Optional findById(Long Id); + // 전체 카드 최신순 List findAllByOrderByCreatedAtDesc(); + // 전체 카드 인기순 List findAllByOrderByCommentCountDescCreatedAtDesc(); + // 타입 별 카드 최신순 List findAllByTypeOrderByCreatedAtDesc(FortuneType type); + // 타입 별 카드 인기순 List findAllByTypeOrderByCommentCountDescCreatedAtDesc(FortuneType type); + // 오늘의 따봉도치 랭킹 @Query("SELECT c FROM Card c WHERE c.createdAt >= :today ORDER BY c.commentCount DESC LIMIT 3") List findTop3CommentedCardsToday(@Param("today") LocalDateTime today); + // 카드 없을 시 오늘의 따봉도치 랜덤 조회 @Query("SELECT c FROM Card c ORDER BY RAND() LIMIT 3") List findRandom3Cards(); + + // 타입 별 카드 마지막 업로드 시간 조회 + @Query("SELECT c.lastUploadTime FROM Card c WHERE c.type = :fortuneType ORDER BY c.lastUploadTime DESC") + LocalDateTime findLastUploadTimeByType(@Param("fortuneType") FortuneType fortuneType); } diff --git a/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/service/CardQueryService.java b/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/service/CardQueryService.java index ef8aefd..4579917 100644 --- a/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/service/CardQueryService.java +++ b/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/service/CardQueryService.java @@ -1,5 +1,6 @@ package com.ddabong.ddabongdotchiBE.domain.card.service; +import java.time.Duration; import java.time.LocalDateTime; import java.util.List; @@ -25,6 +26,7 @@ public class CardQueryService { private final CardRepository cardRepository; + /* 카드 상세 조회 */ public CardDetailGetResponse getCardDetail(Long cardId) { final Card card = cardRepository.findById(cardId) .orElseThrow(() -> new CardExceptionHandler(CardErrorCode.CARD_NOT_FOUND)); @@ -32,6 +34,22 @@ public CardDetailGetResponse getCardDetail(Long cardId) { return CardDetailGetResponse.from(card); } + /* 오늘의 따봉도치 랭킹 조회 */ + public List getTopCardToday() { + LocalDateTime today = LocalDateTime.now().toLocalDate().atStartOfDay(); + List top3CommentedCards = cardRepository.findTop3CommentedCardsToday(today); + + // 댓글 많은 카드가 없을 경우 바로 랜덤 3개 카드 반환 + return top3CommentedCards.isEmpty() + ? cardRepository.findRandom3Cards().stream() + .map(CardSummaryGetResponse::from) + .toList() + : top3CommentedCards.stream() + .map(CardSummaryGetResponse::from) + .toList(); + } + + /* 전체 카드 최신순 조회 */ public List getRecentCard() { return cardRepository.findAllByOrderByCreatedAtDesc() .stream() @@ -39,6 +57,7 @@ public List getRecentCard() { .toList(); } + /* 전체 카드 인기순 조회 */ public List getPopularCard() { return cardRepository.findAllByOrderByCommentCountDescCreatedAtDesc() .stream() @@ -46,6 +65,7 @@ public List getPopularCard() { .toList(); } + /* 타입 별 카드 최신순 조회 */ public List getRecentTypeCard(FortuneType fortuneType) { return cardRepository.findAllByTypeOrderByCreatedAtDesc(fortuneType) .stream() @@ -53,6 +73,7 @@ public List getRecentTypeCard(FortuneType fortuneType) { .toList(); } + /* 타입 별 카드 인기순 조회 */ public List getPopularTypeCard(FortuneType fortuneType) { return cardRepository.findAllByTypeOrderByCommentCountDescCreatedAtDesc(fortuneType) .stream() @@ -60,17 +81,14 @@ public List getPopularTypeCard(FortuneType fortuneType) .toList(); } - public List getTopCardToday() { - LocalDateTime today = LocalDateTime.now().toLocalDate().atStartOfDay(); - List top3CommentedCards = cardRepository.findTop3CommentedCardsToday(today); - - // 댓글 많은 카드가 없을 경우 바로 랜덤 3개 카드 반환 - return top3CommentedCards.isEmpty() - ? cardRepository.findRandom3Cards().stream() - .map(CardSummaryGetResponse::from) - .toList() - : top3CommentedCards.stream() - .map(CardSummaryGetResponse::from) - .toList(); + /* 타입 별 카드 마지막 업로드 시간 조회 */ + public String getLastUploadTime(FortuneType type) { + LocalDateTime lastUploadTime = cardRepository.findLastUploadTimeByType(type); + if (lastUploadTime == null) { + return "마지막 업로드 시간이 없습니다."; + } + Duration duration = Duration.between(lastUploadTime, LocalDateTime.now()); + long minutes = duration.toMinutes(); + return minutes + "분 전"; } } diff --git a/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/service/CardService.java b/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/service/CardService.java index 9275a9d..7fae483 100644 --- a/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/service/CardService.java +++ b/src/main/java/com/ddabong/ddabongdotchiBE/domain/card/service/CardService.java @@ -25,6 +25,7 @@ public class CardService { private final CardRepository cardRepository; private final S3Service s3Service; + /* 카드 작성 */ public CardCreateResponse createCard( User authUser, CardCreateRequest request, @@ -37,11 +38,12 @@ public CardCreateResponse createCard( return CardCreateResponse.from(card); } + /* 카드 삭제 */ public void deleteCard(User user, Long cardId) { final Card card = cardRepository.findById(cardId) .orElseThrow(() -> new CardExceptionHandler(CardErrorCode.CARD_NOT_FOUND)); if (!card.getUser().getUsername().equals(user.getUsername())) { - throw new CardExceptionHandler(CardErrorCode.CARD_NOT_FOUND); + throw new CardExceptionHandler(CardErrorCode.UNAUTHORIZED_ACCESS); } cardRepository.delete(card); } diff --git a/src/main/java/com/ddabong/ddabongdotchiBE/domain/user/dto/request/UserJoinRequest.java b/src/main/java/com/ddabong/ddabongdotchiBE/domain/user/dto/request/UserJoinRequest.java index 96c55f5..e542a96 100644 --- a/src/main/java/com/ddabong/ddabongdotchiBE/domain/user/dto/request/UserJoinRequest.java +++ b/src/main/java/com/ddabong/ddabongdotchiBE/domain/user/dto/request/UserJoinRequest.java @@ -19,7 +19,6 @@ public record UserJoinRequest( @NotBlank(message = "[ERROR] 닉네임 입력은 필수입니다.") @Pattern(regexp = "^[가-힣]{2,7}$", message = "[ERROR] 닉네임은 한글로 2~7글자여야 합니다.") String nickname, - String description ) {