diff --git a/src/main/java/com/server/capple/domain/answer/dto/AnswerResponse.java b/src/main/java/com/server/capple/domain/answer/dto/AnswerResponse.java index 9c4594e4..7f35b5d3 100644 --- a/src/main/java/com/server/capple/domain/answer/dto/AnswerResponse.java +++ b/src/main/java/com/server/capple/domain/answer/dto/AnswerResponse.java @@ -16,11 +16,14 @@ public static class AnswerId { @Builder public static class AnswerInfo { private Long answerId; + private Long writerId; private String profileImage; private String nickname; private String content; - private Boolean isMyAnswer; + private Boolean isMine; private Boolean isReported; + private Boolean isLiked; + private String writeAt; } @Getter @@ -44,11 +47,13 @@ public static class AnswerLike { public static class MemberAnswerInfo { private Long questionId; private Long answerId; + private Long writerId; private String nickname; private String profileImage; private String content; private int heartCount; private String writeAt; + private Boolean isLiked; } @Getter diff --git a/src/main/java/com/server/capple/domain/answer/mapper/AnswerMapper.java b/src/main/java/com/server/capple/domain/answer/mapper/AnswerMapper.java index 295adb23..10264ec0 100644 --- a/src/main/java/com/server/capple/domain/answer/mapper/AnswerMapper.java +++ b/src/main/java/com/server/capple/domain/answer/mapper/AnswerMapper.java @@ -29,26 +29,31 @@ public AnswerList toAnswerList(List answerInfoList) { .build(); } - public AnswerInfo toAnswerInfo(Answer answer, Long memberId, Boolean isReported) { + public AnswerInfo toAnswerInfo(Answer answer, Long memberId, Boolean isReported, Boolean isLiked, Boolean isMine) { return AnswerInfo.builder() .answerId(answer.getId()) + .writerId(answer.getMember().getId()) .profileImage(answer.getMember().getProfileImage()) .nickname(answer.getMember().getNickname()) .content(answer.getContent()) - .isMyAnswer(answer.getMember().getId() == memberId) + .isMine(isMine) .isReported(isReported) + .isLiked(isLiked) + .writeAt(answer.getCreatedAt().toString()) .build(); } - public MemberAnswerInfo toMemberAnswerInfo(Answer answer, int heartCount) { + public MemberAnswerInfo toMemberAnswerInfo(Answer answer, int heartCount, Boolean isLiked) { return MemberAnswerInfo.builder() .questionId(answer.getQuestion().getId()) .answerId(answer.getId()) + .writerId(answer.getMember().getId()) .nickname(answer.getMember().getNickname()) .profileImage(answer.getMember().getProfileImage()) .content(answer.getContent()) .heartCount(heartCount) - .writeAt(answer.getCreatedAt().format(DateTimeFormatter.ofPattern("yyyy.MM.dd"))) + .writeAt(answer.getCreatedAt().toString()) + .isLiked(isLiked) .build(); } diff --git a/src/main/java/com/server/capple/domain/answer/repository/AnswerHeartRedisRepository.java b/src/main/java/com/server/capple/domain/answer/repository/AnswerHeartRedisRepository.java index 81bc8814..1b5bab35 100644 --- a/src/main/java/com/server/capple/domain/answer/repository/AnswerHeartRedisRepository.java +++ b/src/main/java/com/server/capple/domain/answer/repository/AnswerHeartRedisRepository.java @@ -54,4 +54,10 @@ public Set getMemberHeartsAnswer(Long memberId) { } return answerIds; } + + public boolean isMemberLikedAnswer(Long memberId, Long answerId) { + String key = ANSWER_HEART_KEY_PREFIX + answerId; + String memberKey = MEMBER_KEY_PREFIX + memberId; + return redisTemplate.opsForSet().isMember(key, memberKey); + } } diff --git a/src/main/java/com/server/capple/domain/answer/service/AnswerServiceImpl.java b/src/main/java/com/server/capple/domain/answer/service/AnswerServiceImpl.java index 027bbb24..a134d04c 100644 --- a/src/main/java/com/server/capple/domain/answer/service/AnswerServiceImpl.java +++ b/src/main/java/com/server/capple/domain/answer/service/AnswerServiceImpl.java @@ -40,8 +40,13 @@ public AnswerResponse.AnswerId createAnswer(Member loginMember, Long questionId, Member member = memberService.findMember(loginMember.getId()); Question question = questionService.findQuestion(questionId); + if (answerRepository.existsByQuestionAndMember(question, loginMember)) { + throw new RestApiException(AnswerErrorCode.ANSWER_ALREADY_EXIST); + } + //답변 저장 Answer answer = answerRepository.save(answerMapper.toAnswerEntity(request, member, question)); +// answer.getQuestion().increaseCommentCount(); return new AnswerResponse.AnswerId(answer.getId()); } @@ -66,6 +71,7 @@ public AnswerResponse.AnswerId deleteAnswer(Member loginMember, Long answerId) { Answer answer = findAnswer(answerId); checkPermission(loginMember, answer); +// answer.getQuestion().decreaseCommentCount(); answer.delete(); @@ -90,14 +96,14 @@ public AnswerList getAnswerList(Long memberId, Long questionId, String keyword, answerRepository.findByQuestion(questionId, pageable).orElseThrow(() -> new RestApiException(AnswerErrorCode.ANSWER_NOT_FOUND)) .stream() - .map(answer -> answerMapper.toAnswerInfo(answer, memberId, reportRepository.existsReportByAnswer(answer))) + .map(answer -> answerMapper.toAnswerInfo(answer, memberId, reportRepository.existsReportByAnswer(answer), answerHeartRedisRepository.isMemberLikedAnswer(memberId, answer.getId()), answer.getMember().getId().equals(memberId))) .toList()); } else { return answerMapper.toAnswerList( answerRepository.findByQuestionAndKeyword(questionId, keyword, pageable).orElseThrow(() -> new RestApiException(AnswerErrorCode.ANSWER_NOT_FOUND)) .stream() - .map(answer -> answerMapper.toAnswerInfo(answer, memberId, reportRepository.existsReportByAnswer(answer))) + .map(answer -> answerMapper.toAnswerInfo(answer, memberId, reportRepository.existsReportByAnswer(answer), answerHeartRedisRepository.isMemberLikedAnswer(memberId, answer.getId()), answer.getMember().getId().equals(memberId))) .toList()); } @@ -109,7 +115,7 @@ public MemberAnswerList getMemberAnswer(Member member) { List answers = answerRepository.findByMember(member).orElse(null); return answerMapper.toMemberAnswerList( answers.stream() - .map(answer -> answerMapper.toMemberAnswerInfo(answer, answerHeartRedisRepository.getAnswerHeartsCount(answer.getId()))) + .map(answer -> answerMapper.toMemberAnswerInfo(answer, answerHeartRedisRepository.getAnswerHeartsCount(answer.getId()), answerHeartRedisRepository.isMemberLikedAnswer(member.getId(), answer.getId()))) .toList() ); } @@ -120,7 +126,7 @@ public MemberAnswerList getMemberHeartAnswer(Member member) { return answerMapper.toMemberAnswerList( answerHeartRedisRepository.getMemberHeartsAnswer(member.getId()) .stream() - .map(answerId -> answerMapper.toMemberAnswerInfo(findAnswer((answerId)), answerHeartRedisRepository.getAnswerHeartsCount(answerId))) + .map(answerId -> answerMapper.toMemberAnswerInfo(findAnswer((answerId)), answerHeartRedisRepository.getAnswerHeartsCount(answerId), answerHeartRedisRepository.isMemberLikedAnswer(member.getId(), answerId))) .toList() ); } diff --git a/src/main/java/com/server/capple/domain/answerComment/dto/AnswerCommentResponse.java b/src/main/java/com/server/capple/domain/answerComment/dto/AnswerCommentResponse.java index 6f77d4a1..f877645b 100644 --- a/src/main/java/com/server/capple/domain/answerComment/dto/AnswerCommentResponse.java +++ b/src/main/java/com/server/capple/domain/answerComment/dto/AnswerCommentResponse.java @@ -25,7 +25,7 @@ public static class AnswerCommentHeart { @Builder public static class AnswerCommentInfo { private Long answerCommentId; - private String writer; + private Long writerId; private String content; private Long heartCount; private LocalDateTime createdAt; diff --git a/src/main/java/com/server/capple/domain/answerComment/mapper/AnswerCommentMapper.java b/src/main/java/com/server/capple/domain/answerComment/mapper/AnswerCommentMapper.java index d8b8816f..698cf40a 100644 --- a/src/main/java/com/server/capple/domain/answerComment/mapper/AnswerCommentMapper.java +++ b/src/main/java/com/server/capple/domain/answerComment/mapper/AnswerCommentMapper.java @@ -20,7 +20,7 @@ public AnswerComment toAnswerCommentEntity(Member member, Answer answer, String public AnswerCommentInfo toAnswerCommentInfo(AnswerComment comment, Long heartCount) { return AnswerCommentInfo.builder() .answerCommentId(comment.getId()) - .writer(comment.getMember().getNickname()) + .writerId(comment.getMember().getId()) .content(comment.getContent()) .heartCount(heartCount) .createdAt(comment.getCreatedAt()) diff --git a/src/main/java/com/server/capple/domain/board/controller/BoardController.java b/src/main/java/com/server/capple/domain/board/controller/BoardController.java index 82035305..d5ede657 100644 --- a/src/main/java/com/server/capple/domain/board/controller/BoardController.java +++ b/src/main/java/com/server/capple/domain/board/controller/BoardController.java @@ -46,11 +46,12 @@ private BaseResponse createBoard( }) @GetMapping() private BaseResponse getBoardsByBoardType( + @AuthMember Member member, @RequestParam(name = "boardType", required = false) BoardType boardType // TODO: 페이징 프론트 이슈로 추후 구현 // @PageableDefault(sort = "created_at", direction = Sort.Direction.DESC) @Parameter(hidden = true) Pageable pageable ) { - return BaseResponse.onSuccess(boardService.getBoardsByBoardType(boardType)); + return BaseResponse.onSuccess(boardService.getBoardsByBoardType(member, boardType)); } @Operation(summary = "게시글 삭제 API", description = "게시글을 삭제합니다.") diff --git a/src/main/java/com/server/capple/domain/board/dto/BoardResponse.java b/src/main/java/com/server/capple/domain/board/dto/BoardResponse.java index 5627f505..2ab95bc9 100644 --- a/src/main/java/com/server/capple/domain/board/dto/BoardResponse.java +++ b/src/main/java/com/server/capple/domain/board/dto/BoardResponse.java @@ -25,7 +25,7 @@ public static class BoardCreate { @AllArgsConstructor @NoArgsConstructor public static class BoardsGetByBoardType { - private List boards = new ArrayList<>(); + private List boards; } @Getter @@ -39,6 +39,10 @@ public static class BoardsGetByBoardTypeBoardInfo { private Integer heartCount; private Integer commentCount; private LocalDateTime createAt; + private Boolean liLiked; + private Boolean isMine; + private Boolean isReported; + private Boolean isLiked; } @Getter @@ -54,7 +58,7 @@ public static class BoardDelete { @AllArgsConstructor @NoArgsConstructor public static class BoardsSearchByKeyword { - private List boards = new ArrayList<>(); + private List boards; } @Getter diff --git a/src/main/java/com/server/capple/domain/board/entity/Board.java b/src/main/java/com/server/capple/domain/board/entity/Board.java index af811828..a6ea3e33 100644 --- a/src/main/java/com/server/capple/domain/board/entity/Board.java +++ b/src/main/java/com/server/capple/domain/board/entity/Board.java @@ -1,5 +1,6 @@ package com.server.capple.domain.board.entity; +import com.server.capple.domain.answer.dto.AnswerRequest; import com.server.capple.domain.member.entity.Member; import com.server.capple.global.common.BaseEntity; import jakarta.persistence.*; @@ -31,4 +32,12 @@ public class Board extends BaseEntity { @Column(nullable = false) private Integer commentCount; + + public void increaseCommentCount() { + this.commentCount += 1; + } + + public void decreaseCommentCount() { + this.commentCount -= 1; + } } diff --git a/src/main/java/com/server/capple/domain/board/mapper/BoardMapper.java b/src/main/java/com/server/capple/domain/board/mapper/BoardMapper.java index 337ffe5a..e53377a1 100644 --- a/src/main/java/com/server/capple/domain/board/mapper/BoardMapper.java +++ b/src/main/java/com/server/capple/domain/board/mapper/BoardMapper.java @@ -44,15 +44,21 @@ public BoardResponse.BoardsGetByBoardType toBoardsGetByBoardType( public BoardResponse.BoardsGetByBoardTypeBoardInfo toBoardsGetByBoardTypeBoardInfo( Board board, - Integer boardHeartsCount) { + Integer boardHeartsCount, + Boolean isLiked, + Boolean isMine, + Boolean isReported) { return BoardResponse.BoardsGetByBoardTypeBoardInfo.builder() .boardId(board.getId()) .writerId(board.getWriter().getId()) .content(board.getContent()) .heartCount(boardHeartsCount) - // TODO : 댓글 작성 API 나오면 추후 구현 - .commentCount(0) + .commentCount(board.getCommentCount()) .createAt(board.getCreatedAt()) + .isLiked(isLiked) + .isMine(isMine) + // TODO: BoardReport 관련 테이블 구현 후 수정 요망 + .isReported(isReported) .build(); } diff --git a/src/main/java/com/server/capple/domain/board/repository/BoardHeartRedisRepository.java b/src/main/java/com/server/capple/domain/board/repository/BoardHeartRedisRepository.java index 7f1432fa..1477a93d 100644 --- a/src/main/java/com/server/capple/domain/board/repository/BoardHeartRedisRepository.java +++ b/src/main/java/com/server/capple/domain/board/repository/BoardHeartRedisRepository.java @@ -23,10 +23,10 @@ public class BoardHeartRedisRepository implements Serializable { private final RedisTemplate redisTemplate; // 게시판 좋아요 토글 - public Boolean toggleBoardHeart(Long boardId, Long memberId) { + public Boolean toggleBoardHeart(Long memberId, Long boardId) { String key = BOARD_HEART_KEY_PREFIX + boardId.toString(); String member = MEMBER_KEY_PREFIX + memberId.toString(); - String createAtKey = key + ":" + member + ":createAt"; // member ID를 포함한 createAtKeyㄱ + String createAtKey = key + ":" + member + ":createAt"; // member ID를 포함한 createAtKey SetOperations setOperations = redisTemplate.opsForSet(); ValueOperations valueOperations = redisTemplate.opsForValue(); @@ -44,7 +44,7 @@ public Boolean toggleBoardHeart(Long boardId, Long memberId) { } } - // + public String getBoardHeartCreateAt(Long boardId, Long memberId) { String createAtKey = BOARD_HEART_KEY_PREFIX + boardId.toString() + ":" + MEMBER_KEY_PREFIX + memberId.toString() + ":createAt"; return redisTemplate.opsForValue().get(createAtKey); @@ -71,4 +71,10 @@ public Set getMemberHeartsBoard(Long memberId) { } return boardIds; } + + public boolean isMemberLikedBoard(Long memberId, Long boardId) { + String key = BOARD_HEART_KEY_PREFIX + boardId; + String memberKey = MEMBER_KEY_PREFIX + memberId; + return redisTemplate.opsForSet().isMember(key, memberKey); + } } \ No newline at end of file diff --git a/src/main/java/com/server/capple/domain/board/service/BoardService.java b/src/main/java/com/server/capple/domain/board/service/BoardService.java index 92c5a005..b8c6ee45 100644 --- a/src/main/java/com/server/capple/domain/board/service/BoardService.java +++ b/src/main/java/com/server/capple/domain/board/service/BoardService.java @@ -10,7 +10,7 @@ public interface BoardService { BoardCreate createBoard(Member member, BoardType boardType, String content); - BoardsGetByBoardType getBoardsByBoardType(BoardType boardType); + BoardsGetByBoardType getBoardsByBoardType(Member member, BoardType boardType); BoardDelete deleteBoard(Member member, Long boardId); diff --git a/src/main/java/com/server/capple/domain/board/service/BoardServiceImpl.java b/src/main/java/com/server/capple/domain/board/service/BoardServiceImpl.java index ad3bd6c4..b9289921 100644 --- a/src/main/java/com/server/capple/domain/board/service/BoardServiceImpl.java +++ b/src/main/java/com/server/capple/domain/board/service/BoardServiceImpl.java @@ -6,6 +6,7 @@ import com.server.capple.domain.board.mapper.BoardMapper; import com.server.capple.domain.board.repository.BoardHeartRedisRepository; import com.server.capple.domain.board.repository.BoardRepository; +import com.server.capple.domain.boardComment.repository.BoardCommentHeartRedisRepository; import com.server.capple.domain.member.entity.Member; import com.server.capple.global.exception.RestApiException; import com.server.capple.global.exception.errorCode.BoardErrorCode; @@ -37,7 +38,7 @@ public BoardResponse.BoardCreate createBoard(Member member, BoardType boardType, } @Override - public BoardResponse.BoardsGetByBoardType getBoardsByBoardType(BoardType boardType) { + public BoardResponse.BoardsGetByBoardType getBoardsByBoardType(Member member, BoardType boardType) { List boards = new ArrayList<>(); if (boardType == null) { boards = boardRepository.findAll(); @@ -49,7 +50,8 @@ public BoardResponse.BoardsGetByBoardType getBoardsByBoardType(BoardType boardTy throw new RestApiException(BoardErrorCode.BOARD_BAD_REQUEST); } return boardMapper.toBoardsGetByBoardType(boards.stream() - .map(board -> boardMapper.toBoardsGetByBoardTypeBoardInfo(board, boardHeartRedisRepository.getBoardHeartsCount(board.getId()))) + // TODO: BoardReport 관련 테이블 구현 후 수정 요망 + .map(board -> boardMapper.toBoardsGetByBoardTypeBoardInfo(board, boardHeartRedisRepository.getBoardHeartsCount(board.getId()), boardHeartRedisRepository.isMemberLikedBoard(member.getId(), board.getId()), board.getWriter().getId().equals(member.getId()), false)) .toList() ); } @@ -76,7 +78,7 @@ public BoardResponse.BoardsSearchByKeyword searchBoardsByKeyword(String keyword) @Override public BoardResponse.BoardToggleHeart toggleBoardHeart(Member member, Long boardId) { Board board = findBoard(boardId); - System.out.println(boardHeartRedisRepository.getBoardHeartCreateAt(board.getId(), member.getId())); +// System.out.println(boardHeartRedisRepository.getBoardHeartCreateAt(board.getId(), member.getId())); Boolean isLiked = boardHeartRedisRepository.toggleBoardHeart(member.getId(), board.getId()); return new BoardResponse.BoardToggleHeart(boardId, isLiked); diff --git a/src/main/java/com/server/capple/domain/boardComment/dto/BoardCommentResponse.java b/src/main/java/com/server/capple/domain/boardComment/dto/BoardCommentResponse.java index b87ed7cd..c006c4e6 100644 --- a/src/main/java/com/server/capple/domain/boardComment/dto/BoardCommentResponse.java +++ b/src/main/java/com/server/capple/domain/boardComment/dto/BoardCommentResponse.java @@ -26,7 +26,7 @@ public static class BoardCommentHeart { @Builder public static class BoardCommentInfo { private Long boardCommentId; - private String writer; + private Long writerId; private String content; private Long heartCount; private Boolean isLiked; diff --git a/src/main/java/com/server/capple/domain/boardComment/mapper/BoardCommentMapper.java b/src/main/java/com/server/capple/domain/boardComment/mapper/BoardCommentMapper.java index dbc38ff7..7e693444 100644 --- a/src/main/java/com/server/capple/domain/boardComment/mapper/BoardCommentMapper.java +++ b/src/main/java/com/server/capple/domain/boardComment/mapper/BoardCommentMapper.java @@ -19,7 +19,7 @@ public BoardComment toBoardCommentEntity(Member member, Board board, String comm public BoardCommentInfo toBoardCommentInfo(BoardComment comment, Long heartCount, Boolean isLiked) { return BoardCommentInfo.builder() .boardCommentId(comment.getId()) - .writer(comment.getMember().getNickname()) + .writerId(comment.getMember().getId()) .content(comment.getContent()) .heartCount(heartCount) .isLiked(isLiked) diff --git a/src/main/java/com/server/capple/domain/boardComment/service/BoardCommentServiceImpl.java b/src/main/java/com/server/capple/domain/boardComment/service/BoardCommentServiceImpl.java index 1890632b..d4564ede 100644 --- a/src/main/java/com/server/capple/domain/boardComment/service/BoardCommentServiceImpl.java +++ b/src/main/java/com/server/capple/domain/boardComment/service/BoardCommentServiceImpl.java @@ -39,6 +39,7 @@ public BoardCommentId createBoardComment(Member member, Long boardId, BoardComme BoardComment boardComment = boardCommentRepository.save( boardCommentMapper.toBoardCommentEntity(loginMember, board, request.getComment())); + board.increaseCommentCount(); return new BoardCommentId(boardComment.getId()); } @@ -59,6 +60,7 @@ public BoardCommentId deleteBoardComment(Member member, Long commentId) { BoardComment boardComment = findBoardComment(commentId); checkPermission(member, boardComment); + boardComment.getBoard().decreaseCommentCount();; boardComment.delete(); return new BoardCommentId(boardComment.getId()); diff --git a/src/main/java/com/server/capple/domain/question/controller/QuestionController.java b/src/main/java/com/server/capple/domain/question/controller/QuestionController.java index 2720784e..95c59553 100644 --- a/src/main/java/com/server/capple/domain/question/controller/QuestionController.java +++ b/src/main/java/com/server/capple/domain/question/controller/QuestionController.java @@ -1,7 +1,9 @@ package com.server.capple.domain.question.controller; import com.server.capple.config.security.AuthMember; +import com.server.capple.domain.board.dto.BoardResponse; import com.server.capple.domain.member.entity.Member; +import com.server.capple.domain.question.dto.response.QuestionResponse; import com.server.capple.domain.question.dto.response.QuestionResponse.QuestionSummary; import com.server.capple.domain.question.dto.response.QuestionResponse.QuestionInfos; import com.server.capple.domain.question.service.QuestionService; @@ -11,9 +13,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @Tag(name = "질문 API", description = "질문 관련 API") @RestController @@ -41,6 +41,13 @@ private BaseResponse getQuestions(@AuthMember Member member) { return BaseResponse.onSuccess(questionService.getQuestions(member)); } + @Operation(summary = "질문 좋아요/취소 API", description = " 질문 좋아요/취소 API 입니다." + + "pathvariable 으로 questionId를 주세요.") + @PostMapping("/{questionId}/heart") + public BaseResponse toggleBoardHeart(@AuthMember Member member, @PathVariable(value = "questionId") Long questionId) { + return BaseResponse.onSuccess(questionService.toggleQuestionHeart(member, questionId)); + } + // @Operation(summary = "최근 지난 질문 조회 API", description = "최근 지난 질문을 조회합니다.") // @ApiResponses(value = { diff --git a/src/main/java/com/server/capple/domain/question/dto/response/QuestionResponse.java b/src/main/java/com/server/capple/domain/question/dto/response/QuestionResponse.java index 157ac337..d792f632 100644 --- a/src/main/java/com/server/capple/domain/question/dto/response/QuestionResponse.java +++ b/src/main/java/com/server/capple/domain/question/dto/response/QuestionResponse.java @@ -5,6 +5,7 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import java.time.LocalDateTime; import java.util.List; @@ -18,6 +19,8 @@ public static class QuestionSummary { private Long questionId; private QuestionStatus questionStatus; private String content; +// private Integer likeCount; +// private Integer commentCount; private Boolean isAnswered; } @@ -31,9 +34,8 @@ public static class QuestionInfo { @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") private LocalDateTime livedAt; private String content; - // 추후 추가 예정 -// private Long likeCount; -// private Long commentCount; +// private Integer likeCount; +// private Integer commentCount; private Boolean isAnswered; } @@ -50,4 +52,13 @@ public static class QuestionId { public static class QuestionInfos { private List questionInfos; } + + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class QuestionToggleHeart { + private Long questionId; + private Boolean isLiked; + } } diff --git a/src/main/java/com/server/capple/domain/question/entity/Question.java b/src/main/java/com/server/capple/domain/question/entity/Question.java index f1d77239..27a82495 100644 --- a/src/main/java/com/server/capple/domain/question/entity/Question.java +++ b/src/main/java/com/server/capple/domain/question/entity/Question.java @@ -36,6 +36,9 @@ public class Question extends BaseEntity { private LocalDateTime livedAt; +// @Column(nullable = false) +// private Integer commentCount; + //question Status를 바꾸는 함수 public void setQuestionStatus(QuestionStatus questionStatus) { this.questionStatus = questionStatus; @@ -44,4 +47,12 @@ public void setQuestionStatus(QuestionStatus questionStatus) { this.livedAt = LocalDateTime.now(); } +// public void increaseCommentCount() { +// this.commentCount += 1; +// } +// +// public void decreaseCommentCount() { +// this.commentCount -= 1; +// } + } diff --git a/src/main/java/com/server/capple/domain/question/mapper/QuestionMapper.java b/src/main/java/com/server/capple/domain/question/mapper/QuestionMapper.java index e404bd07..ee9e4360 100644 --- a/src/main/java/com/server/capple/domain/question/mapper/QuestionMapper.java +++ b/src/main/java/com/server/capple/domain/question/mapper/QuestionMapper.java @@ -16,28 +16,30 @@ public Question toQuestion(QuestionCreate request) { return Question.builder() .questionStatus(request.getQuestionStatus()) .content(request.getContent()) +// .commentCount(0) .build(); } - public QuestionSummary toQuestionSummary(Question question, boolean isAnswered) { + public QuestionSummary toQuestionSummary(Question question, boolean isAnswered/*, Integer likeCount*/) { return QuestionSummary.builder() .questionId(question.getId()) .questionStatus(question.getQuestionStatus()) .content(question.getContent()) .isAnswered(isAnswered) +// .likeCount(likeCount) +// .commentCount(question.getCommentCount()) .build(); } - public QuestionInfo toQuestionInfo(Question question, boolean isAnswered) { + public QuestionInfo toQuestionInfo(Question question, boolean isAnswered/*, Integer likeCount*/) { return QuestionInfo.builder() .questionId(question.getId()) .questionStatus(question.getQuestionStatus()) .livedAt(question.getLivedAt()) .content(question.getContent()) - // Count는 추후 수정 예정(필드 수정해야함...) -// .likeCount(100L) -// .commentCount(0L) +// .likeCount(likeCount) +// .commentCount(question.getCommentCount()) .isAnswered(isAnswered) .build(); } diff --git a/src/main/java/com/server/capple/domain/question/repository/QuestionHeartRedisRepository.java b/src/main/java/com/server/capple/domain/question/repository/QuestionHeartRedisRepository.java new file mode 100644 index 00000000..f647e948 --- /dev/null +++ b/src/main/java/com/server/capple/domain/question/repository/QuestionHeartRedisRepository.java @@ -0,0 +1,75 @@ +package com.server.capple.domain.question.repository; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.SetOperations; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Repository; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.Set; + +import static java.lang.Boolean.FALSE; +import static java.lang.Boolean.TRUE; + +@Repository +@RequiredArgsConstructor +public class QuestionHeartRedisRepository implements Serializable { + public static final String QUESTION_HEART_KEY_PREFIX = "questionHeart-"; + public static final String MEMBER_KEY_PREFIX = "member-"; + + private final RedisTemplate redisTemplate; + + // 질문 좋아요 토글 + public Boolean toggleBoardHeart(Long memberId, Long boardId) { + String key = QUESTION_HEART_KEY_PREFIX + boardId.toString(); + String member = MEMBER_KEY_PREFIX + memberId.toString(); + SetOperations setOperations = redisTemplate.opsForSet(); + + //해당 key에 member가 존재하지 않으면 추가, 존재하면 삭제 + if (FALSE.equals(setOperations.isMember(key, member))) { + setOperations.add(key, member); + return TRUE; + } else { + setOperations.remove(key, member); + // 좋아요 취소 시 생성 시간도 삭제할 수 있음 + return FALSE; + } + } + + + public String getQuestionHeartCreateAt(Long questionId, Long memberId) { + String createAtKey = QUESTION_HEART_KEY_PREFIX + questionId.toString() + ":" + MEMBER_KEY_PREFIX + memberId.toString() + ":createAt"; + return redisTemplate.opsForValue().get(createAtKey); + } + + // 질문 좋아요 수 조회 + public Integer getQuestionHeartsCount(Long questionId) { + String key = QUESTION_HEART_KEY_PREFIX + questionId.toString(); + Set members = redisTemplate.opsForSet().members(key); + return members != null ? members.size() : 0; + } + + // 좋아요 누른 질문 조회 + public Set getMemberHeartsQuestion(Long memberId) { + String member = MEMBER_KEY_PREFIX + memberId.toString(); + Set keys = redisTemplate.keys(QUESTION_HEART_KEY_PREFIX + "*"); // 모든 키 조회 + Set questionIds = new HashSet<>(); + + for (String key : keys) { + if (redisTemplate.opsForSet().isMember(key, member)) { + String questionId = key.substring(QUESTION_HEART_KEY_PREFIX.length()); + questionIds.add(Long.parseLong(questionId)); + } + } + return questionIds; + } + + public boolean isMemberLikedQuestion(Long memberId, Long questionId) { + String key = QUESTION_HEART_KEY_PREFIX + questionId; + String memberKey = MEMBER_KEY_PREFIX + memberId; + return redisTemplate.opsForSet().isMember(key, memberKey); + } +} \ No newline at end of file diff --git a/src/main/java/com/server/capple/domain/question/service/QuestionService.java b/src/main/java/com/server/capple/domain/question/service/QuestionService.java index 5b4e8415..1fefb3ec 100644 --- a/src/main/java/com/server/capple/domain/question/service/QuestionService.java +++ b/src/main/java/com/server/capple/domain/question/service/QuestionService.java @@ -1,6 +1,8 @@ package com.server.capple.domain.question.service; import com.server.capple.config.security.AuthMember; +import com.server.capple.domain.board.dto.BoardResponse; import com.server.capple.domain.member.entity.Member; +import com.server.capple.domain.question.dto.response.QuestionResponse; import com.server.capple.domain.question.dto.response.QuestionResponse.QuestionSummary; import com.server.capple.domain.question.dto.response.QuestionResponse.QuestionInfos; import com.server.capple.domain.question.entity.Question; @@ -10,4 +12,6 @@ public interface QuestionService { QuestionSummary getMainQuestion(Member member); QuestionInfos getQuestions(Member member); + + QuestionResponse.QuestionToggleHeart toggleQuestionHeart(Member member, Long questionId); } diff --git a/src/main/java/com/server/capple/domain/question/service/QuestionServiceImpl.java b/src/main/java/com/server/capple/domain/question/service/QuestionServiceImpl.java index 03fc1a83..aeb5af35 100644 --- a/src/main/java/com/server/capple/domain/question/service/QuestionServiceImpl.java +++ b/src/main/java/com/server/capple/domain/question/service/QuestionServiceImpl.java @@ -1,12 +1,17 @@ package com.server.capple.domain.question.service; import com.server.capple.domain.answer.repository.AnswerRepository; +import com.server.capple.domain.answerComment.repository.AnswerCommentHeartRedisRepository; +import com.server.capple.domain.board.dto.BoardResponse; +import com.server.capple.domain.board.entity.Board; import com.server.capple.domain.member.entity.Member; import com.server.capple.domain.question.dao.QuestionInfoInterface; +import com.server.capple.domain.question.dto.response.QuestionResponse; import com.server.capple.domain.question.dto.response.QuestionResponse.QuestionInfos; import com.server.capple.domain.question.dto.response.QuestionResponse.QuestionSummary; import com.server.capple.domain.question.entity.Question; import com.server.capple.domain.question.mapper.QuestionMapper; +import com.server.capple.domain.question.repository.QuestionHeartRedisRepository; import com.server.capple.domain.question.repository.QuestionRepository; import com.server.capple.global.exception.RestApiException; import com.server.capple.global.exception.errorCode.QuestionErrorCode; @@ -23,6 +28,8 @@ public class QuestionServiceImpl implements QuestionService { private final QuestionRepository questionRepository; private final AnswerRepository answerRepository; private final QuestionMapper questionMapper; + private final QuestionHeartRedisRepository questionHeartRepository; + private final AnswerCommentHeartRedisRepository answerCommentHeartRepository; @Override public Question findQuestion(Long questionId) { @@ -37,7 +44,7 @@ public QuestionSummary getMainQuestion(Member member) { boolean isAnswered = answerRepository.existsByQuestionAndMember(mainQuestion, member); - return questionMapper.toQuestionSummary(mainQuestion, isAnswered); + return questionMapper.toQuestionSummary(mainQuestion, isAnswered/*, questionHeartRepository.getQuestionHeartsCount(mainQuestion.getId())*/); } @Override @@ -47,7 +54,16 @@ public QuestionInfos getQuestions(Member member) { return questionMapper.toQuestionInfos(questions.stream() .map(questionInfo -> questionMapper.toQuestionInfo(questionInfo.getQuestion(), - questionInfo.getIsAnsweredByMember()) + questionInfo.getIsAnsweredByMember()/*, + questionHeartRepository.getQuestionHeartsCount(questionInfo.getQuestion().getId())*/) ).toList()); } + + @Override + public QuestionResponse.QuestionToggleHeart toggleQuestionHeart(Member member, Long questionId) { + Question question = findQuestion(questionId); + + Boolean isLiked = questionHeartRepository.toggleBoardHeart(member.getId(), question.getId()); + return new QuestionResponse.QuestionToggleHeart(questionId, isLiked); + } } diff --git a/src/main/java/com/server/capple/global/exception/errorCode/AnswerErrorCode.java b/src/main/java/com/server/capple/global/exception/errorCode/AnswerErrorCode.java index 56b0da4d..f42150ac 100644 --- a/src/main/java/com/server/capple/global/exception/errorCode/AnswerErrorCode.java +++ b/src/main/java/com/server/capple/global/exception/errorCode/AnswerErrorCode.java @@ -10,7 +10,9 @@ @AllArgsConstructor public enum AnswerErrorCode implements ErrorCodeInterface { ANSWER_NOT_FOUND("ANSWER001", "답변을 찾을 수 없습니다.", HttpStatus.NOT_FOUND), - ANSWER_UNAUTHORIZED("ANSWER002", "답변에 대한 권한이 업습니다.", HttpStatus.FORBIDDEN); + ANSWER_UNAUTHORIZED("ANSWER002", "답변에 대한 권한이 업습니다.", HttpStatus.FORBIDDEN), + ANSWER_ALREADY_EXIST("ANSWER003", "이미 답변한 질문입니다.", HttpStatus.BAD_REQUEST), + ; private final String code; private final String message; diff --git a/src/test/java/com/server/capple/domain/answer/controller/AnswerControllerTest.java b/src/test/java/com/server/capple/domain/answer/controller/AnswerControllerTest.java index 7bb8c0b1..347f2f74 100644 --- a/src/test/java/com/server/capple/domain/answer/controller/AnswerControllerTest.java +++ b/src/test/java/com/server/capple/domain/answer/controller/AnswerControllerTest.java @@ -149,7 +149,7 @@ public void getMyPageMemberAnswerTest() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.message").value("요청에 성공하였습니다.")) - .andExpect(jsonPath("$.result.memberAnswerInfos[0].nickname").value("루시")) +// .andExpect(jsonPath("$.result.memberAnswerInfos[0].nickname").value("루시")) .andExpect(jsonPath("$.result.memberAnswerInfos[0].content").value("나는 무자비한 사람이 좋아")); } diff --git a/src/test/java/com/server/capple/domain/answer/service/AnswerServiceTest.java b/src/test/java/com/server/capple/domain/answer/service/AnswerServiceTest.java index 78ae87ba..564c01d5 100644 --- a/src/test/java/com/server/capple/domain/answer/service/AnswerServiceTest.java +++ b/src/test/java/com/server/capple/domain/answer/service/AnswerServiceTest.java @@ -4,6 +4,7 @@ import com.server.capple.domain.answer.dto.AnswerResponse; import com.server.capple.domain.answer.entity.Answer; import com.server.capple.domain.tag.service.TagService; +import com.server.capple.global.exception.RestApiException; import com.server.capple.support.ServiceTestConfig; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -13,8 +14,7 @@ import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; @DisplayName("Answer 서비스의 ") @SpringBootTest @@ -25,9 +25,25 @@ public class AnswerServiceTest extends ServiceTestConfig { private TagService tagService; @Test - @DisplayName("Answer 생성 테스트") + @DisplayName("Answer 중복 생성 시 예외 발생 테스트") + @Transactional + public void createDuplicateAnswerTest() { + // given + AnswerRequest request = getAnswerRequest(); + + // 이미 `setUp()`에서 답변이 생성된 상태이므로, 다시 답변을 생성하면 예외 발생 + // then + assertThrows(RestApiException.class, () -> { + answerService.createAnswer(member, liveQuestion.getId(), request); + }); + } + + @Test + @DisplayName("Answer 중복 생성 방지 후 새로운 답변 생성 테스트") @Transactional public void createAnswerTest() { + // 기존 답변 삭제 + answerService.deleteAnswer(member, answer.getId()); //given AnswerRequest request = getAnswerRequest(); @@ -41,9 +57,11 @@ public void createAnswerTest() { } @Test - @DisplayName("Answer 수정 테스트") + @DisplayName("Answer 중복 생성 방지 후 새로운 답변 수정 테스트") @Transactional public void updateAnswerTest() { + // 기존 답변 삭제 + answerService.deleteAnswer(member, answer.getId()); //given AnswerRequest request = getAnswerRequest(); Long answerId = answerService.createAnswer(member, liveQuestion.getId(), request).getAnswerId(); @@ -61,9 +79,11 @@ public void updateAnswerTest() { } @Test - @DisplayName("Answer 삭제 테스트") + @DisplayName("Answer 중복 생성 방지 후 새로운 답변 삭제 테스트") @Transactional public void deleteAnswerTest() { + // 기존 답변 삭제 + answerService.deleteAnswer(member, answer.getId()); //given AnswerRequest request = getAnswerRequest(); Long answerId = answerService.createAnswer(member, liveQuestion.getId(), request).getAnswerId(); diff --git a/src/test/java/com/server/capple/domain/answerComment/controller/AnswerCommentControllerTest.java b/src/test/java/com/server/capple/domain/answerComment/controller/AnswerCommentControllerTest.java index e4838564..4cfd0898 100644 --- a/src/test/java/com/server/capple/domain/answerComment/controller/AnswerCommentControllerTest.java +++ b/src/test/java/com/server/capple/domain/answerComment/controller/AnswerCommentControllerTest.java @@ -149,7 +149,7 @@ public void getAnswerCommentInfosTest() throws Exception { .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.message").value("요청에 성공하였습니다.")) .andExpect(jsonPath("$.result.answerCommentInfos[0].answerCommentId").value(1L)) - .andExpect(jsonPath("$.result.answerCommentInfos[0].writer").value("루시")) +// .andExpect(jsonPath("$.result.answerCommentInfos[0].writer").value("루시")) .andExpect(jsonPath("$.result.answerCommentInfos[0].content").value("댓글 1")) .andExpect(jsonPath("$.result.answerCommentInfos[0].heartCount").value(3L)) .andExpect(jsonPath("$.result.answerCommentInfos[0].createdAt").value("2022-11-01T12:02:00")); diff --git a/src/test/java/com/server/capple/domain/answerComment/service/AnswerCommentServiceTest.java b/src/test/java/com/server/capple/domain/answerComment/service/AnswerCommentServiceTest.java index df077628..87962724 100644 --- a/src/test/java/com/server/capple/domain/answerComment/service/AnswerCommentServiceTest.java +++ b/src/test/java/com/server/capple/domain/answerComment/service/AnswerCommentServiceTest.java @@ -101,7 +101,7 @@ public void getAnswerCommentsTest() { AnswerCommentInfos response = answerCommentService.getAnswerCommentInfos(answer.getId()); //then - assertEquals("루시", response.getAnswerCommentInfos().get(0).getWriter()); + assertEquals(member.getId(), response.getAnswerCommentInfos().get(0).getWriterId()); assertEquals("답변에 대한 댓글이어유", response.getAnswerCommentInfos().get(0).getContent()); assertEquals(0L, response.getAnswerCommentInfos().get(0).getHeartCount()); } diff --git a/src/test/java/com/server/capple/domain/boardComment/controller/BoardCommentControllerTest.java b/src/test/java/com/server/capple/domain/boardComment/controller/BoardCommentControllerTest.java index f1af70b0..b526fedf 100644 --- a/src/test/java/com/server/capple/domain/boardComment/controller/BoardCommentControllerTest.java +++ b/src/test/java/com/server/capple/domain/boardComment/controller/BoardCommentControllerTest.java @@ -149,7 +149,7 @@ public void getBoardCommentInfosTest() throws Exception { .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.message").value("요청에 성공하였습니다.")) .andExpect(jsonPath("$.result.boardCommentInfos[0].boardCommentId").value(1L)) - .andExpect(jsonPath("$.result.boardCommentInfos[0].writer").value("루시")) +// .andExpect(jsonPath("$.result.boardCommentInfos[0].writer").value("루시")) .andExpect(jsonPath("$.result.boardCommentInfos[0].content").value("댓글")) .andExpect(jsonPath("$.result.boardCommentInfos[0].heartCount").value(2L)) .andExpect(jsonPath("$.result.boardCommentInfos[0].isLiked").value(true)); diff --git a/src/test/java/com/server/capple/domain/boardComment/service/BoardCommentServiceTest.java b/src/test/java/com/server/capple/domain/boardComment/service/BoardCommentServiceTest.java index d1accde2..21286fd8 100644 --- a/src/test/java/com/server/capple/domain/boardComment/service/BoardCommentServiceTest.java +++ b/src/test/java/com/server/capple/domain/boardComment/service/BoardCommentServiceTest.java @@ -103,7 +103,7 @@ public void getBoardCommentsTest() { BoardCommentInfos response = boardCommentService.getBoardCommentInfos(member, board.getId()); //then - assertEquals("루시", response.getBoardCommentInfos().get(0).getWriter()); + assertEquals(member.getId(), response.getBoardCommentInfos().get(0).getWriterId()); assertEquals("게시글 댓글", response.getBoardCommentInfos().get(0).getContent()); assertEquals(0L, response.getBoardCommentInfos().get(0).getHeartCount()); assertEquals(false, response.getBoardCommentInfos().get(0).getIsLiked()); diff --git a/src/test/java/com/server/capple/support/ControllerTestConfig.java b/src/test/java/com/server/capple/support/ControllerTestConfig.java index 5144dca7..aafdc453 100644 --- a/src/test/java/com/server/capple/support/ControllerTestConfig.java +++ b/src/test/java/com/server/capple/support/ControllerTestConfig.java @@ -100,7 +100,7 @@ protected MemberAnswerList getMemberAnswerList () { List memberAnswerInfos = List.of(AnswerResponse.MemberAnswerInfo.builder() .questionId(answer.getQuestion().getId()) .answerId(answer.getId()) - .nickname(answer.getMember().getNickname()) + .writerId(member.getId()) .profileImage(answer.getMember().getProfileImage()) .content(answer.getContent()) .heartCount(1) @@ -117,7 +117,7 @@ protected BoardCommentInfos getBoardCommentInfos() { List commentInfos = List.of(BoardCommentInfo.builder() .boardCommentId(1L) - .writer(member.getNickname()) + .writerId(member.getId()) .content("댓글") .createdAt(LocalDateTime.now()) .heartCount(2L) @@ -136,7 +136,7 @@ protected AnswerCommentRequest getAnswerCommentRequest() { protected AnswerCommentInfos getAnswerCommentInfos () { List answerCommentInfos = List.of(AnswerCommentInfo.builder() .answerCommentId(1L) - .writer(member.getNickname()) + .writerId(member.getId()) .content("댓글 1") .createdAt(LocalDateTime.of(2022, 11, 1, 12, 02)) .heartCount(3L) diff --git a/src/test/java/com/server/capple/support/ServiceTestConfig.java b/src/test/java/com/server/capple/support/ServiceTestConfig.java index 61943193..bc99047a 100644 --- a/src/test/java/com/server/capple/support/ServiceTestConfig.java +++ b/src/test/java/com/server/capple/support/ServiceTestConfig.java @@ -129,7 +129,7 @@ protected Board createBoard() { .boardType(BoardType.FREEBOARD) .writer(member) .content("오늘 밥먹을 사람!") - .commentCount(2) + .commentCount(0) .build()); } protected BoardComment createBoardComment() {