From df68bc96a5e980fa0c508723b31c6f526ff66476 Mon Sep 17 00:00:00 2001 From: ybchar Date: Sun, 7 Apr 2024 00:04:27 +0900 Subject: [PATCH] feat: Create Reaction --- .../daybook/domain/board/entity/Board.java | 5 ++ .../daybook/domain/member/domain/Member.java | 1 + .../controller/ReactionController.java | 35 +++++++++++++ .../dto/request/ReactionCreateRequest.java | 9 ++++ .../dto/response/ReactionCreateResponse.java | 21 ++++++++ .../domain/reaction/entity/Reaction.java | 38 ++++++++++++-- .../domain/reaction/entity/ReactionType.java | 13 +++++ .../repository/ReactionRepository.java | 8 +++ .../repository/ReactionRepositoryCustom.java | 10 ++++ .../repository/ReactionRepositoryImpl.java | 28 +++++++++++ .../reaction/service/ReactionService.java | 50 +++++++++++++++++++ .../global/error/exception/ErrorCode.java | 8 ++- 12 files changed, 222 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/unit/daybook/domain/reaction/controller/ReactionController.java create mode 100644 src/main/java/com/unit/daybook/domain/reaction/dto/request/ReactionCreateRequest.java create mode 100644 src/main/java/com/unit/daybook/domain/reaction/dto/response/ReactionCreateResponse.java create mode 100644 src/main/java/com/unit/daybook/domain/reaction/entity/ReactionType.java create mode 100644 src/main/java/com/unit/daybook/domain/reaction/repository/ReactionRepository.java create mode 100644 src/main/java/com/unit/daybook/domain/reaction/repository/ReactionRepositoryCustom.java create mode 100644 src/main/java/com/unit/daybook/domain/reaction/repository/ReactionRepositoryImpl.java create mode 100644 src/main/java/com/unit/daybook/domain/reaction/service/ReactionService.java diff --git a/src/main/java/com/unit/daybook/domain/board/entity/Board.java b/src/main/java/com/unit/daybook/domain/board/entity/Board.java index 4a4691e..1c39b54 100644 --- a/src/main/java/com/unit/daybook/domain/board/entity/Board.java +++ b/src/main/java/com/unit/daybook/domain/board/entity/Board.java @@ -3,6 +3,8 @@ import com.unit.daybook.domain.board.dto.request.AddBoardRequestDto; import com.unit.daybook.domain.common.model.BaseTimeEntity; import com.unit.daybook.domain.member.domain.Member; +import com.unit.daybook.domain.reaction.entity.Reaction; + import jakarta.persistence.*; import lombok.*; @@ -43,6 +45,9 @@ public class Board extends BaseTimeEntity { @OneToMany(mappedBy = "board") private List hashtags = new ArrayList<>(); + @OneToMany(mappedBy = "board", cascade = CascadeType.REMOVE) + private List reactions = new ArrayList<>(); + @Builder(access = AccessLevel.PRIVATE) public Board(Long boardId, String content, Long respectBoardId, Member member, String category, Long hearts, String paperType) { this.boardId = boardId; diff --git a/src/main/java/com/unit/daybook/domain/member/domain/Member.java b/src/main/java/com/unit/daybook/domain/member/domain/Member.java index 53d9234..fe139c1 100644 --- a/src/main/java/com/unit/daybook/domain/member/domain/Member.java +++ b/src/main/java/com/unit/daybook/domain/member/domain/Member.java @@ -6,6 +6,7 @@ import com.unit.daybook.domain.board.entity.Board; import com.unit.daybook.domain.common.model.BaseTimeEntity; +import com.unit.daybook.domain.reaction.entity.Reaction; import jakarta.persistence.*; import lombok.AccessLevel; diff --git a/src/main/java/com/unit/daybook/domain/reaction/controller/ReactionController.java b/src/main/java/com/unit/daybook/domain/reaction/controller/ReactionController.java new file mode 100644 index 0000000..4fd1348 --- /dev/null +++ b/src/main/java/com/unit/daybook/domain/reaction/controller/ReactionController.java @@ -0,0 +1,35 @@ +package com.unit.daybook.domain.reaction.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.unit.daybook.domain.common.annotation.LoginUsers; +import com.unit.daybook.domain.reaction.dto.request.ReactionCreateRequest; +import com.unit.daybook.domain.reaction.dto.response.ReactionCreateResponse; +import com.unit.daybook.domain.reaction.service.ReactionService; +import com.unit.daybook.global.config.security.CustomUserDetails; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/reactions") +@RequiredArgsConstructor +public class ReactionController { + + private final ReactionService reactionService; + + @PostMapping + public ResponseEntity reactionCreate( + @Valid @RequestBody ReactionCreateRequest request, + @LoginUsers CustomUserDetails userDetails + ) { + ReactionCreateResponse reaction = reactionService.createReaction(request, userDetails.getMemberId()); + return ResponseEntity.status(HttpStatus.CREATED).body(reaction); + } +} diff --git a/src/main/java/com/unit/daybook/domain/reaction/dto/request/ReactionCreateRequest.java b/src/main/java/com/unit/daybook/domain/reaction/dto/request/ReactionCreateRequest.java new file mode 100644 index 0000000..8b1885d --- /dev/null +++ b/src/main/java/com/unit/daybook/domain/reaction/dto/request/ReactionCreateRequest.java @@ -0,0 +1,9 @@ +package com.unit.daybook.domain.reaction.dto.request; + +import com.unit.daybook.domain.reaction.entity.ReactionType; + +public record ReactionCreateRequest( + Long boardId, + ReactionType reactionType +) { +} diff --git a/src/main/java/com/unit/daybook/domain/reaction/dto/response/ReactionCreateResponse.java b/src/main/java/com/unit/daybook/domain/reaction/dto/response/ReactionCreateResponse.java new file mode 100644 index 0000000..aa1d2ba --- /dev/null +++ b/src/main/java/com/unit/daybook/domain/reaction/dto/response/ReactionCreateResponse.java @@ -0,0 +1,21 @@ +package com.unit.daybook.domain.reaction.dto.response; + +import com.unit.daybook.domain.reaction.entity.Reaction; +import com.unit.daybook.domain.reaction.entity.ReactionType; + +public record ReactionCreateResponse( + Long reactionId, + ReactionType reactionType, + Long memberId, + Long boardId +) { + public static ReactionCreateResponse from(Reaction reaction) { + return new ReactionCreateResponse( + reaction.getId(), + reaction.getReactionType(), + reaction.getMember().getId(), + reaction.getBoard().getBoardId() + ); + } + +} \ No newline at end of file diff --git a/src/main/java/com/unit/daybook/domain/reaction/entity/Reaction.java b/src/main/java/com/unit/daybook/domain/reaction/entity/Reaction.java index 9a8076d..6c78e71 100644 --- a/src/main/java/com/unit/daybook/domain/reaction/entity/Reaction.java +++ b/src/main/java/com/unit/daybook/domain/reaction/entity/Reaction.java @@ -1,7 +1,11 @@ package com.unit.daybook.domain.reaction.entity; +import com.unit.daybook.domain.board.entity.Board; +import com.unit.daybook.domain.member.domain.Member; + import jakarta.persistence.*; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -15,9 +19,37 @@ public class Reaction { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "reaction_id") - private Long reactionId; + private Long id; + + @Enumerated(EnumType.STRING) + private ReactionType reactionType; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "board_id") + private Board board; + + @Builder(access = AccessLevel.PRIVATE) + private Reaction(ReactionType reactionType, Member member, Board board) { + this.reactionType = reactionType; + this.member = member; + this.board = board; + } - @Column - private String content; + public static Reaction createReaction( + ReactionType reactionType, Member member, Board board + ) { + return Reaction.builder() + .reactionType(reactionType) + .member(member) + .board(board) + .build(); + } + public void updateReaction(ReactionType reactionType) { + this.reactionType = reactionType; + } } \ No newline at end of file diff --git a/src/main/java/com/unit/daybook/domain/reaction/entity/ReactionType.java b/src/main/java/com/unit/daybook/domain/reaction/entity/ReactionType.java new file mode 100644 index 0000000..03ecf9b --- /dev/null +++ b/src/main/java/com/unit/daybook/domain/reaction/entity/ReactionType.java @@ -0,0 +1,13 @@ +package com.unit.daybook.domain.reaction.entity; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ReactionType { + MOVING, + ADMIRE, + GREAT, + ; +} diff --git a/src/main/java/com/unit/daybook/domain/reaction/repository/ReactionRepository.java b/src/main/java/com/unit/daybook/domain/reaction/repository/ReactionRepository.java new file mode 100644 index 0000000..969e4e4 --- /dev/null +++ b/src/main/java/com/unit/daybook/domain/reaction/repository/ReactionRepository.java @@ -0,0 +1,8 @@ +package com.unit.daybook.domain.reaction.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.unit.daybook.domain.reaction.entity.Reaction; + +public interface ReactionRepository extends JpaRepository, ReactionRepositoryCustom { +} diff --git a/src/main/java/com/unit/daybook/domain/reaction/repository/ReactionRepositoryCustom.java b/src/main/java/com/unit/daybook/domain/reaction/repository/ReactionRepositoryCustom.java new file mode 100644 index 0000000..2900955 --- /dev/null +++ b/src/main/java/com/unit/daybook/domain/reaction/repository/ReactionRepositoryCustom.java @@ -0,0 +1,10 @@ +package com.unit.daybook.domain.reaction.repository; + +import com.unit.daybook.domain.board.entity.Board; +import com.unit.daybook.domain.member.domain.Member; +import com.unit.daybook.domain.reaction.entity.Reaction; + +public interface ReactionRepositoryCustom { + + Reaction findReactionByMemberAndBoard(Member member, Board board); +} diff --git a/src/main/java/com/unit/daybook/domain/reaction/repository/ReactionRepositoryImpl.java b/src/main/java/com/unit/daybook/domain/reaction/repository/ReactionRepositoryImpl.java new file mode 100644 index 0000000..d1d8d0b --- /dev/null +++ b/src/main/java/com/unit/daybook/domain/reaction/repository/ReactionRepositoryImpl.java @@ -0,0 +1,28 @@ +package com.unit.daybook.domain.reaction.repository; + +import static com.unit.daybook.domain.reaction.entity.QReaction.*; + +import org.springframework.stereotype.Repository; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.unit.daybook.domain.board.entity.Board; +import com.unit.daybook.domain.member.domain.Member; +import com.unit.daybook.domain.reaction.entity.Reaction; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class ReactionRepositoryImpl implements ReactionRepositoryCustom { + + private final JPAQueryFactory jpaQueryFactory; + + @Override + public Reaction findReactionByMemberAndBoard(Member member, Board board) { + return jpaQueryFactory.selectFrom(reaction) + .where( + reaction.board.boardId.eq(board.getBoardId()), + reaction.member.id.eq(member.getId())) + .fetchOne(); + } +} diff --git a/src/main/java/com/unit/daybook/domain/reaction/service/ReactionService.java b/src/main/java/com/unit/daybook/domain/reaction/service/ReactionService.java new file mode 100644 index 0000000..f707711 --- /dev/null +++ b/src/main/java/com/unit/daybook/domain/reaction/service/ReactionService.java @@ -0,0 +1,50 @@ +package com.unit.daybook.domain.reaction.service; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.unit.daybook.domain.board.entity.Board; +import com.unit.daybook.domain.board.repository.BoardRepository; +import com.unit.daybook.domain.member.domain.Member; +import com.unit.daybook.domain.member.repository.MemberRepository; +import com.unit.daybook.domain.reaction.dto.request.ReactionCreateRequest; +import com.unit.daybook.domain.reaction.dto.response.ReactionCreateResponse; +import com.unit.daybook.domain.reaction.entity.Reaction; +import com.unit.daybook.domain.reaction.repository.ReactionRepository; +import com.unit.daybook.global.error.exception.CustomException; +import com.unit.daybook.global.error.exception.ErrorCode; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional +public class ReactionService { + + private final ReactionRepository reactionRepository; + private final MemberRepository memberRepository; + private final BoardRepository boardRepository; + + public ReactionCreateResponse createReaction(ReactionCreateRequest request, Long memberId) { + final Member member = findMemberById(memberId); + Board board = boardRepository.findById(request.boardId()) + .orElseThrow(() -> new CustomException(ErrorCode.BOARD_NOT_FOUND)); + + validateReactionMemberMatching(member, board); + + Reaction reaction = Reaction.createReaction(request.reactionType(), member, board); + return ReactionCreateResponse.from(reactionRepository.save(reaction)); + } + + private void validateReactionMemberMatching(Member member, Board board) { + Reaction reaction = reactionRepository.findReactionByMemberAndBoard(member, board); + if (reaction != null) { + throw new CustomException(ErrorCode.REACTION_EXISTS); + } + } + + private Member findMemberById(Long memberId) { + return memberRepository.findById(memberId) + .orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND)); + } +} diff --git a/src/main/java/com/unit/daybook/global/error/exception/ErrorCode.java b/src/main/java/com/unit/daybook/global/error/exception/ErrorCode.java index 295d7a8..cf3432e 100644 --- a/src/main/java/com/unit/daybook/global/error/exception/ErrorCode.java +++ b/src/main/java/com/unit/daybook/global/error/exception/ErrorCode.java @@ -22,7 +22,13 @@ public enum ErrorCode { EXPIRED_REFRESH_TOKEN(HttpStatus.FORBIDDEN, "리프레시 토큰이 만료되었습니다."), // Member - MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 회원입니다."); + MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 회원입니다."), + + // Board + BOARD_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 일지입니다."), + + // Reaction + REACTION_EXISTS(HttpStatus.CONFLICT, "이미 리액션을 하였습니다."); private final HttpStatus status; private final String message; }