From 75ff9434b5389be3590f7cacf389a31e138a0776 Mon Sep 17 00:00:00 2001 From: JunRain Date: Fri, 14 Jun 2024 15:45:12 +0900 Subject: [PATCH 01/13] =?UTF-8?q?[#59]=20=EC=9E=90=EC=9B=90=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EC=A0=91=EA=B7=BC=20=EA=B6=8C=ED=95=9C=20?= =?UTF-8?q?=EC=B1=85=EC=9E=84=EC=9D=84=20=20MemberService=EB=A1=9C=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 --- .../dto/request/GenerateCommentRequest.java | 23 ----- .../exception/AccessDeniedException.java | 10 +++ .../domain/member/service/MemberService.java | 7 ++ .../exception/AccessDeniedMemeException.java | 10 --- .../domain/meme/service/MemeService.java | 9 +- .../domain/memes/controller/MemesLikeApi.java | 87 ------------------- .../memes/controller/MemesLikeController.java | 70 --------------- .../exception/AccessDinedMemesException.java | 11 --- .../domain/memes/service/MemesService.java | 14 ++- .../memetory/global/response/ErrorCode.java | 6 +- .../member/service/MemberServiceTest.java | 12 +++ .../domain/meme/service/MemeServiceTest.java | 6 +- .../domain/memes/MemesIntegrationTest.java | 2 +- .../memes/service/MemesServiceTest.java | 11 ++- 14 files changed, 47 insertions(+), 231 deletions(-) delete mode 100644 backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/request/GenerateCommentRequest.java create mode 100644 backend/memetory/src/main/java/com/example/memetory/domain/member/exception/AccessDeniedException.java delete mode 100644 backend/memetory/src/main/java/com/example/memetory/domain/meme/exception/AccessDeniedMemeException.java delete mode 100644 backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/MemesLikeApi.java delete mode 100644 backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/MemesLikeController.java delete mode 100644 backend/memetory/src/main/java/com/example/memetory/domain/memes/exception/AccessDinedMemesException.java diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/request/GenerateCommentRequest.java b/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/request/GenerateCommentRequest.java deleted file mode 100644 index f369bade..00000000 --- a/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/request/GenerateCommentRequest.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.example.memetory.domain.comment.dto.request; - -import com.example.memetory.domain.comment.dto.CommentServiceDto; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor -public class GenerateCommentRequest { - - private Long memesId; - - private String content; - - public CommentServiceDto toServiceDto(String email) { - return CommentServiceDto.builder() - .memesId(memesId) - .content(content) - .email(email) - .build(); - } -} diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/member/exception/AccessDeniedException.java b/backend/memetory/src/main/java/com/example/memetory/domain/member/exception/AccessDeniedException.java new file mode 100644 index 00000000..b46b0abf --- /dev/null +++ b/backend/memetory/src/main/java/com/example/memetory/domain/member/exception/AccessDeniedException.java @@ -0,0 +1,10 @@ +package com.example.memetory.domain.member.exception; + +import com.example.memetory.global.exception.BusinessException; +import com.example.memetory.global.response.ErrorCode; + +public class AccessDeniedException extends BusinessException { + public AccessDeniedException() { + super(ErrorCode.MEMBER_ACCESS_DENY); + } +} diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/member/service/MemberService.java b/backend/memetory/src/main/java/com/example/memetory/domain/member/service/MemberService.java index 2389ed6c..01a7697a 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/member/service/MemberService.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/member/service/MemberService.java @@ -6,6 +6,7 @@ import com.example.memetory.domain.member.dto.MemberServiceDto; import com.example.memetory.domain.member.dto.response.MemberResponse; import com.example.memetory.domain.member.entity.Member; +import com.example.memetory.domain.member.exception.AccessDeniedException; import com.example.memetory.domain.member.exception.DuplicatedMemberException; import com.example.memetory.domain.member.exception.NotFoundMemberException; import com.example.memetory.domain.member.repository.MemberRepository; @@ -48,4 +49,10 @@ public MemberResponse findMemberResponse(MemberServiceDto memberServiceDto) { public Member findMemberFromId(Long id) { return memberRepository.findById(id).orElseThrow(NotFoundMemberException::new); } + + public void certifyMember(Member m1, Member m2) { + if (!m1.equals(m2)) { + throw new AccessDeniedException(); + } + } } diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/meme/exception/AccessDeniedMemeException.java b/backend/memetory/src/main/java/com/example/memetory/domain/meme/exception/AccessDeniedMemeException.java deleted file mode 100644 index 29af4e10..00000000 --- a/backend/memetory/src/main/java/com/example/memetory/domain/meme/exception/AccessDeniedMemeException.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.example.memetory.domain.meme.exception; - -import com.example.memetory.global.exception.BusinessException; -import com.example.memetory.global.response.ErrorCode; - -public class AccessDeniedMemeException extends BusinessException { - public AccessDeniedMemeException() { - super(ErrorCode.MEME_ACCESS_DENY); - } -} diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/meme/service/MemeService.java b/backend/memetory/src/main/java/com/example/memetory/domain/meme/service/MemeService.java index 46fda8b4..2923ceed 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/meme/service/MemeService.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/meme/service/MemeService.java @@ -18,7 +18,6 @@ import com.example.memetory.domain.meme.dto.MemeResponse; import com.example.memetory.domain.meme.dto.MemeServiceDto; import com.example.memetory.domain.meme.entity.Meme; -import com.example.memetory.domain.meme.exception.AccessDeniedMemeException; import com.example.memetory.domain.meme.exception.NotFoundMemeException; import com.example.memetory.domain.meme.repository.MemeRepository; import com.example.memetory.global.firebase.service.FirebaseService; @@ -80,17 +79,11 @@ public MemeResponse findMemberMemeResponse(MemeServiceDto memeServiceDto) { Member loginMember = memberService.findMemberFromEmail(memeServiceDto.getEmail()); Member memeMember = meme.getMember(); - certifyMemeMember(memeMember, loginMember); + memberService.certifyMember(memeMember, loginMember); return MemeResponse.of(meme); } - public void certifyMemeMember(Member m1, Member m2) { - if (!m1.equals(m2)) { - throw new AccessDeniedMemeException(); - } - } - @Transactional public MemePageResponse findMemberMemePageResponse(MemeServiceDto memeServiceDto, Pageable pageable) { Member member = memberService.findMemberFromEmail(memeServiceDto.getEmail()); diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/MemesLikeApi.java b/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/MemesLikeApi.java deleted file mode 100644 index cf15945a..00000000 --- a/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/MemesLikeApi.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.example.memetory.domain.memes.controller; - -import org.springframework.http.ResponseEntity; - -import com.example.memetory.global.response.ResultResponse; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.enums.ParameterIn; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; - -public interface MemesLikeApi { - @Operation( - summary = "meme`s 인기차트 조회", - description = "좋아요 수 많은 순으로 10개 조회", - security = {@SecurityRequirement(name = "access_token")} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "인기차트 조회" - ) - }) - ResponseEntity findTopMemesByLike(); - - @Operation( - summary = "meme`s 이달의 인기차트 조회", - description = "최근 한 달 동안 좋아요 수 많은 순으로 10개 조회", - security = {@SecurityRequirement(name = "access_token")} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "이달의 인기차트 조회" - ) - }) - ResponseEntity findTopMemesByLikeForMonth(); - - @Operation( - summary = "meme`s 이주의 인기차트 조회", - description = "최근 한 주 동안 좋아요 수 많은 순으로 10개 조회", - security = {@SecurityRequirement(name = "access_token")} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "이주의 인기차트 조회" - ) - }) - ResponseEntity findTopMemesByLikeForWeek(); - - @Operation( - summary = "meme`s 좋아요 등록", - description = "공유된 meme`s 게시물에 좋아요를 등록한다.", - security = {@SecurityRequirement(name = "access_token")} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "201", - description = "좋아요 등록!" - ) - }) - ResponseEntity registerLike( - @Parameter(hidden = true) String email, - @Parameter(in = ParameterIn.PATH, description = "밈스 아이디", required = true) - Long memesId - ); - - @Operation( - summary = "meme`s 좋아요 취소", - description = "공유된 meme`s 게시물에 등록된 좋아요를 취소한다.", - security = {@SecurityRequirement(name = "access_token")} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "좋아요 취소!" - ) - }) - ResponseEntity cancelLike( - @Parameter(hidden = true) String email, - @Parameter(in = ParameterIn.PATH, description = "밈스 아이디", required = true) - Long memesId - ); -} diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/MemesLikeController.java b/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/MemesLikeController.java deleted file mode 100644 index 0ba82224..00000000 --- a/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/MemesLikeController.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.example.memetory.domain.memes.controller; - -import static com.example.memetory.global.response.ResultCode.*; - -import java.util.List; - -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import com.example.memetory.domain.like.dto.LikeServiceDto; -import com.example.memetory.domain.like.service.LikeService; -import com.example.memetory.domain.memes.dto.response.MemesInfoResponse; -import com.example.memetory.domain.memes.service.MemesService; -import com.example.memetory.global.annotation.LoginMemberEmail; -import com.example.memetory.global.response.ResultResponse; - -import lombok.RequiredArgsConstructor; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/memes") -public class MemesLikeController implements MemesLikeApi { - private final MemesService memesService; - private final LikeService likeService; - - @GetMapping("/like/all") - @Override - public ResponseEntity findTopMemesByLike() { - List response = memesService.findTopMemesByLike(); - return ResponseEntity.ok(ResultResponse.of(GET_TOP_TEN_MEMES_SUCCESS, response)); - } - - @GetMapping("/like/month") - @Override - public ResponseEntity findTopMemesByLikeForMonth() { - List response = memesService.findTopMemesByLikeForMonth(); - return ResponseEntity.ok(ResultResponse.of(GET_MONTH_TOP_TEN_MEMES_SUCCESS, response)); - } - - @GetMapping("/like/week") - @Override - public ResponseEntity findTopMemesByLikeForWeek() { - List response = memesService.findTopMemesByLikeForWeek(); - return ResponseEntity.ok(ResultResponse.of(GET_WEEK_TOP_TEN_MEMES_SUCCESS, response)); - } - - @PostMapping("/{memesId}/like") - @Override - public ResponseEntity registerLike(@LoginMemberEmail String email, @PathVariable Long memesId) { - LikeServiceDto likeServiceDto = LikeServiceDto.fromEmailAndMemesId(email, memesId); - likeService.registerLike(likeServiceDto); - - return ResponseEntity.status(HttpStatus.CREATED).body(ResultResponse.of(CREATE_LIKE_SUCCESS)); - } - - @DeleteMapping("/{memesId}/like") - @Override - public ResponseEntity cancelLike(@LoginMemberEmail String email, @PathVariable Long memesId) { - LikeServiceDto likeServiceDto = LikeServiceDto.fromEmailAndMemesId(email, memesId); - likeService.cancelLike(likeServiceDto); - - return ResponseEntity.ok(ResultResponse.of(DELETE_LIKE_SUCCESS)); - } -} diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/memes/exception/AccessDinedMemesException.java b/backend/memetory/src/main/java/com/example/memetory/domain/memes/exception/AccessDinedMemesException.java deleted file mode 100644 index 3224f4a6..00000000 --- a/backend/memetory/src/main/java/com/example/memetory/domain/memes/exception/AccessDinedMemesException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.example.memetory.domain.memes.exception; - -import static com.example.memetory.global.response.ErrorCode.*; - -import com.example.memetory.global.exception.BusinessException; - -public class AccessDinedMemesException extends BusinessException { - public AccessDinedMemesException() { - super(MEMES_ACCESS_DENY); - } -} diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/memes/service/MemesService.java b/backend/memetory/src/main/java/com/example/memetory/domain/memes/service/MemesService.java index 1dc480c2..ffc727b3 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/memes/service/MemesService.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/memes/service/MemesService.java @@ -1,5 +1,7 @@ package com.example.memetory.domain.memes.service; +import static com.example.memetory.global.response.ErrorCode.*; + import java.util.List; import org.springframework.data.domain.Pageable; @@ -17,7 +19,6 @@ import com.example.memetory.domain.memes.dto.response.MemesInfoSliceResponse; import com.example.memetory.domain.memes.dto.response.MemesResponse; import com.example.memetory.domain.memes.entity.Memes; -import com.example.memetory.domain.memes.exception.AccessDinedMemesException; import com.example.memetory.domain.memes.exception.NotFoundMemesException; import com.example.memetory.domain.memes.repository.MemesRepository; @@ -37,7 +38,8 @@ public MemesResponse registerMemes(MemesServiceDto memesServiceDto) { Member member = memberService.findMemberFromEmail(memesServiceDto.getEmail()); Member memeMember = meme.getMember(); - memeService.certifyMemeMember(member, memeMember); + + memberService.certifyMember(member, memeMember); Memes memes = memesServiceDto.toEntityFromMemberAndMeme(member, meme); Memes savedMemes = memesRepository.save(memes); @@ -57,17 +59,11 @@ private Memes findMemberMemes(MemesServiceDto memesServiceDto) { Member loginMember = memberService.findMemberFromEmail(memesServiceDto.getEmail()); Member memesMember = memes.getMember(); - certifyMemesMember(loginMember, memesMember); + memberService.certifyMember(loginMember, memesMember); return memes; } - private void certifyMemesMember(Member m1, Member m2) { - if (!m1.equals(m2)) { - throw new AccessDinedMemesException(); - } - } - @Transactional(readOnly = true) public MemesResponse findMemesResponse(MemesServiceDto memesServiceDto) { Memes memes = findMemesFromMemesId(memesServiceDto.getMemesId()); diff --git a/backend/memetory/src/main/java/com/example/memetory/global/response/ErrorCode.java b/backend/memetory/src/main/java/com/example/memetory/global/response/ErrorCode.java index 0eb28ca4..cc2d1b92 100644 --- a/backend/memetory/src/main/java/com/example/memetory/global/response/ErrorCode.java +++ b/backend/memetory/src/main/java/com/example/memetory/global/response/ErrorCode.java @@ -19,14 +19,13 @@ public enum ErrorCode { // Member MEMBER_NOT_FOUND(404, "멤버를 찾기 실패"), NICKNAME_IS_DUPLICATED(409, "닉네임 중복"), + MEMBER_ACCESS_DENY(403, "자원 접근 실패"), // Meme MEME_NOT_FOUND(404, "밈을 찾기 실패"), - MEME_ACCESS_DENY(403, "밈 접근 실패"), // Memes MEMES_NOT_FOUND(404, "밈스를 찾기 실패"), - MEMES_ACCESS_DENY(403, "밈스 접근 실패"), // Like LIKE_NOT_FOUND(404, "좋아요 찾기 실패"), @@ -39,9 +38,10 @@ public enum ErrorCode { COMMENT_NOT_FOUND(404, "댓글 찾기 실패"), // Voice - VOICE_NOT_FOUND(404, "보이스 찾기 실패") + VOICE_NOT_FOUND(404, "보이스 찾기 실패"), ; + private final int status; private final String message; } \ No newline at end of file diff --git a/backend/memetory/src/test/java/com/example/memetory/domain/member/service/MemberServiceTest.java b/backend/memetory/src/test/java/com/example/memetory/domain/member/service/MemberServiceTest.java index 8e66cb2a..bf72ca70 100644 --- a/backend/memetory/src/test/java/com/example/memetory/domain/member/service/MemberServiceTest.java +++ b/backend/memetory/src/test/java/com/example/memetory/domain/member/service/MemberServiceTest.java @@ -18,6 +18,7 @@ import com.example.memetory.domain.member.dto.MemberServiceDto; import com.example.memetory.domain.member.dto.response.MemberResponse; import com.example.memetory.domain.member.entity.Member; +import com.example.memetory.domain.member.exception.AccessDeniedException; import com.example.memetory.domain.member.exception.DuplicatedMemberException; import com.example.memetory.domain.member.exception.NotFoundMemberException; import com.example.memetory.domain.member.repository.MemberRepository; @@ -134,4 +135,15 @@ void Given_MemberServiceDto_When_findMemberResponse_Then_MemberResponse() { // then assertThat(expectedResult).usingRecursiveComparison().isEqualTo(result); } + + @Test + @DisplayName("다른 두 멤버로 인한 AccessDeniedException 반환") + void Given_differentMember_When_certifyMember_Throw_AccessDeniedException() { + // given + Member m1 = MEMBER(); + Member m2 = OTHER_MEMBER(); + + // then + assertThrows(AccessDeniedException.class, () -> memberService.certifyMember(m1, m2)); + } } diff --git a/backend/memetory/src/test/java/com/example/memetory/domain/meme/service/MemeServiceTest.java b/backend/memetory/src/test/java/com/example/memetory/domain/meme/service/MemeServiceTest.java index 2fa31879..29f93c78 100644 --- a/backend/memetory/src/test/java/com/example/memetory/domain/meme/service/MemeServiceTest.java +++ b/backend/memetory/src/test/java/com/example/memetory/domain/meme/service/MemeServiceTest.java @@ -23,12 +23,12 @@ import org.springframework.data.domain.Pageable; import com.example.memetory.domain.member.entity.Member; +import com.example.memetory.domain.member.exception.AccessDeniedException; import com.example.memetory.domain.member.service.MemberService; import com.example.memetory.domain.meme.dto.MemePageResponse; import com.example.memetory.domain.meme.dto.MemeResponse; import com.example.memetory.domain.meme.dto.MemeServiceDto; import com.example.memetory.domain.meme.entity.Meme; -import com.example.memetory.domain.meme.exception.AccessDeniedMemeException; import com.example.memetory.domain.meme.exception.NotFoundMemeException; import com.example.memetory.domain.meme.repository.MemeRepository; import com.example.memetory.global.firebase.service.FirebaseService; @@ -44,7 +44,7 @@ public class MemeServiceTest { @Mock private MemeRepository memeRepository; @Mock - private FirebaseService firebaseService; + private FirebaseService firebaseService; private Member member; private Meme meme; @@ -148,7 +148,7 @@ void Given_differentMember_When_certifyMemeMember_Throw_AccessDeniedMemeExceptio Member differentMember = OTHER_MEMBER(); // then - assertThrows(AccessDeniedMemeException.class, () -> memeService.certifyMemeMember(member, differentMember)); + assertThrows(AccessDeniedException.class, () -> memberService.certifyMember(member, differentMember)); } } diff --git a/backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesIntegrationTest.java b/backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesIntegrationTest.java index 6f40120b..52cf72f1 100644 --- a/backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesIntegrationTest.java +++ b/backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesIntegrationTest.java @@ -142,7 +142,7 @@ public void Given_unAuthorizedMember_When_deleteMemes_Then_MEMES_ACCESS_DENY() { String result = response.jsonPath().get(ERROR_MESSAGE); // then - assertThat(result).isEqualTo(MEMES_ACCESS_DENY.getMessage()); + assertThat(result).isEqualTo(MEMBER_ACCESS_DENY.getMessage()); } @Test diff --git a/backend/memetory/src/test/java/com/example/memetory/domain/memes/service/MemesServiceTest.java b/backend/memetory/src/test/java/com/example/memetory/domain/memes/service/MemesServiceTest.java index d6fe1370..8283581a 100644 --- a/backend/memetory/src/test/java/com/example/memetory/domain/memes/service/MemesServiceTest.java +++ b/backend/memetory/src/test/java/com/example/memetory/domain/memes/service/MemesServiceTest.java @@ -20,16 +20,15 @@ import org.mockito.junit.jupiter.MockitoExtension; import com.example.memetory.domain.member.entity.Member; +import com.example.memetory.domain.member.exception.AccessDeniedException; import com.example.memetory.domain.member.service.MemberService; import com.example.memetory.domain.meme.entity.Meme; -import com.example.memetory.domain.meme.exception.AccessDeniedMemeException; import com.example.memetory.domain.meme.service.MemeService; import com.example.memetory.domain.memes.dto.MemesRankDto; import com.example.memetory.domain.memes.dto.MemesServiceDto; import com.example.memetory.domain.memes.dto.response.MemesInfoResponse; import com.example.memetory.domain.memes.dto.response.MemesResponse; import com.example.memetory.domain.memes.entity.Memes; -import com.example.memetory.domain.memes.exception.AccessDinedMemesException; import com.example.memetory.domain.memes.exception.NotFoundMemesException; import com.example.memetory.domain.memes.repository.MemesRepository; @@ -86,11 +85,11 @@ void Given_unAuthorizedMember_When_registerMemes_Throw_NotAccessMemeException() given(memeService.findMemeFromId(memesServiceDto.getMemeId())).willReturn(meme); given(memberService.findMemberFromEmail(any(String.class))).willReturn(unAuthorizedMember); - doThrow(new AccessDeniedMemeException()).when(memeService) - .certifyMemeMember(any(Member.class), any(Member.class)); + doThrow(new AccessDeniedException()).when(memberService) + .certifyMember(any(Member.class), any(Member.class)); // then - assertThrows(AccessDeniedMemeException.class, () -> memesService.registerMemes(memesServiceDto)); + assertThrows(AccessDeniedException.class, () -> memesService.registerMemes(memesServiceDto)); } @Test @@ -117,7 +116,7 @@ void Given_unAuthorizedMember_When_deleteMemes_Throw_NotAccessMemesException() { given(memberService.findMemberFromEmail(any())).willReturn(unAuthorizedMember); // then - assertThrows(AccessDinedMemesException.class, () -> memesService.deleteMemes(memesServiceDto)); + assertThrows(AccessDeniedException.class, () -> memesService.deleteMemes(memesServiceDto)); } @Test From 49dccc2832afb4a420443e7d3ae873323692e26b Mon Sep 17 00:00:00 2001 From: JunRain Date: Fri, 14 Jun 2024 17:49:37 +0900 Subject: [PATCH 02/13] =?UTF-8?q?[#59]=20DeniedAccessException=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=98=88=EC=99=B8=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 --- ...eption.java => DeniedAccessException.java} | 4 +- .../domain/member/service/MemberService.java | 4 +- .../memes/controller/like/MemesLikeApi.java | 87 +++++++++++++++++++ .../controller/like/MemesLikeController.java | 70 +++++++++++++++ .../member/service/MemberServiceTest.java | 4 +- .../domain/meme/service/MemeServiceTest.java | 13 +-- .../domain/memes/MemesIntegrationTest.java | 2 +- .../memes/service/MemesServiceTest.java | 11 +-- 8 files changed, 171 insertions(+), 24 deletions(-) rename backend/memetory/src/main/java/com/example/memetory/domain/member/exception/{AccessDeniedException.java => DeniedAccessException.java} (69%) create mode 100644 backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/like/MemesLikeApi.java create mode 100644 backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/like/MemesLikeController.java diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/member/exception/AccessDeniedException.java b/backend/memetory/src/main/java/com/example/memetory/domain/member/exception/DeniedAccessException.java similarity index 69% rename from backend/memetory/src/main/java/com/example/memetory/domain/member/exception/AccessDeniedException.java rename to backend/memetory/src/main/java/com/example/memetory/domain/member/exception/DeniedAccessException.java index b46b0abf..3b076d05 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/member/exception/AccessDeniedException.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/member/exception/DeniedAccessException.java @@ -3,8 +3,8 @@ import com.example.memetory.global.exception.BusinessException; import com.example.memetory.global.response.ErrorCode; -public class AccessDeniedException extends BusinessException { - public AccessDeniedException() { +public class DeniedAccessException extends BusinessException { + public DeniedAccessException() { super(ErrorCode.MEMBER_ACCESS_DENY); } } diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/member/service/MemberService.java b/backend/memetory/src/main/java/com/example/memetory/domain/member/service/MemberService.java index 01a7697a..d67772ef 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/member/service/MemberService.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/member/service/MemberService.java @@ -6,7 +6,7 @@ import com.example.memetory.domain.member.dto.MemberServiceDto; import com.example.memetory.domain.member.dto.response.MemberResponse; import com.example.memetory.domain.member.entity.Member; -import com.example.memetory.domain.member.exception.AccessDeniedException; +import com.example.memetory.domain.member.exception.DeniedAccessException; import com.example.memetory.domain.member.exception.DuplicatedMemberException; import com.example.memetory.domain.member.exception.NotFoundMemberException; import com.example.memetory.domain.member.repository.MemberRepository; @@ -52,7 +52,7 @@ public Member findMemberFromId(Long id) { public void certifyMember(Member m1, Member m2) { if (!m1.equals(m2)) { - throw new AccessDeniedException(); + throw new DeniedAccessException(); } } } diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/like/MemesLikeApi.java b/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/like/MemesLikeApi.java new file mode 100644 index 00000000..31877c85 --- /dev/null +++ b/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/like/MemesLikeApi.java @@ -0,0 +1,87 @@ +package com.example.memetory.domain.memes.controller.like; + +import org.springframework.http.ResponseEntity; + +import com.example.memetory.global.response.ResultResponse; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; + +public interface MemesLikeApi { + @Operation( + summary = "meme`s 인기차트 조회", + description = "좋아요 수 많은 순으로 10개 조회", + security = {@SecurityRequirement(name = "access_token")} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "인기차트 조회" + ) + }) + ResponseEntity findTopMemesByLike(); + + @Operation( + summary = "meme`s 이달의 인기차트 조회", + description = "최근 한 달 동안 좋아요 수 많은 순으로 10개 조회", + security = {@SecurityRequirement(name = "access_token")} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "이달의 인기차트 조회" + ) + }) + ResponseEntity findTopMemesByLikeForMonth(); + + @Operation( + summary = "meme`s 이주의 인기차트 조회", + description = "최근 한 주 동안 좋아요 수 많은 순으로 10개 조회", + security = {@SecurityRequirement(name = "access_token")} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "이주의 인기차트 조회" + ) + }) + ResponseEntity findTopMemesByLikeForWeek(); + + @Operation( + summary = "meme`s 좋아요 등록", + description = "공유된 meme`s 게시물에 좋아요를 등록한다.", + security = {@SecurityRequirement(name = "access_token")} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "201", + description = "좋아요 등록!" + ) + }) + ResponseEntity registerLike( + @Parameter(hidden = true) String email, + @Parameter(in = ParameterIn.PATH, description = "밈스 아이디", required = true) + Long memesId + ); + + @Operation( + summary = "meme`s 좋아요 취소", + description = "공유된 meme`s 게시물에 등록된 좋아요를 취소한다.", + security = {@SecurityRequirement(name = "access_token")} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "좋아요 취소!" + ) + }) + ResponseEntity cancelLike( + @Parameter(hidden = true) String email, + @Parameter(in = ParameterIn.PATH, description = "밈스 아이디", required = true) + Long memesId + ); +} diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/like/MemesLikeController.java b/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/like/MemesLikeController.java new file mode 100644 index 00000000..13ffe3fe --- /dev/null +++ b/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/like/MemesLikeController.java @@ -0,0 +1,70 @@ +package com.example.memetory.domain.memes.controller.like; + +import static com.example.memetory.global.response.ResultCode.*; + +import java.util.List; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.example.memetory.domain.like.dto.LikeServiceDto; +import com.example.memetory.domain.like.service.LikeService; +import com.example.memetory.domain.memes.dto.response.MemesInfoResponse; +import com.example.memetory.domain.memes.service.MemesService; +import com.example.memetory.global.annotation.LoginMemberEmail; +import com.example.memetory.global.response.ResultResponse; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/memes") +public class MemesLikeController implements MemesLikeApi { + private final MemesService memesService; + private final LikeService likeService; + + @GetMapping("/like/all") + @Override + public ResponseEntity findTopMemesByLike() { + List response = memesService.findTopMemesByLike(); + return ResponseEntity.ok(ResultResponse.of(GET_TOP_TEN_MEMES_SUCCESS, response)); + } + + @GetMapping("/like/month") + @Override + public ResponseEntity findTopMemesByLikeForMonth() { + List response = memesService.findTopMemesByLikeForMonth(); + return ResponseEntity.ok(ResultResponse.of(GET_MONTH_TOP_TEN_MEMES_SUCCESS, response)); + } + + @GetMapping("/like/week") + @Override + public ResponseEntity findTopMemesByLikeForWeek() { + List response = memesService.findTopMemesByLikeForWeek(); + return ResponseEntity.ok(ResultResponse.of(GET_WEEK_TOP_TEN_MEMES_SUCCESS, response)); + } + + @PostMapping("/{memesId}/like") + @Override + public ResponseEntity registerLike(@LoginMemberEmail String email, @PathVariable Long memesId) { + LikeServiceDto likeServiceDto = LikeServiceDto.fromEmailAndMemesId(email, memesId); + likeService.registerLike(likeServiceDto); + + return ResponseEntity.status(HttpStatus.CREATED).body(ResultResponse.of(CREATE_LIKE_SUCCESS)); + } + + @DeleteMapping("/{memesId}/like") + @Override + public ResponseEntity cancelLike(@LoginMemberEmail String email, @PathVariable Long memesId) { + LikeServiceDto likeServiceDto = LikeServiceDto.fromEmailAndMemesId(email, memesId); + likeService.cancelLike(likeServiceDto); + + return ResponseEntity.ok(ResultResponse.of(DELETE_LIKE_SUCCESS)); + } +} diff --git a/backend/memetory/src/test/java/com/example/memetory/domain/member/service/MemberServiceTest.java b/backend/memetory/src/test/java/com/example/memetory/domain/member/service/MemberServiceTest.java index bf72ca70..98d88c30 100644 --- a/backend/memetory/src/test/java/com/example/memetory/domain/member/service/MemberServiceTest.java +++ b/backend/memetory/src/test/java/com/example/memetory/domain/member/service/MemberServiceTest.java @@ -18,7 +18,7 @@ import com.example.memetory.domain.member.dto.MemberServiceDto; import com.example.memetory.domain.member.dto.response.MemberResponse; import com.example.memetory.domain.member.entity.Member; -import com.example.memetory.domain.member.exception.AccessDeniedException; +import com.example.memetory.domain.member.exception.DeniedAccessException; import com.example.memetory.domain.member.exception.DuplicatedMemberException; import com.example.memetory.domain.member.exception.NotFoundMemberException; import com.example.memetory.domain.member.repository.MemberRepository; @@ -144,6 +144,6 @@ void Given_differentMember_When_certifyMember_Throw_AccessDeniedException() { Member m2 = OTHER_MEMBER(); // then - assertThrows(AccessDeniedException.class, () -> memberService.certifyMember(m1, m2)); + assertThrows(DeniedAccessException.class, () -> memberService.certifyMember(m1, m2)); } } diff --git a/backend/memetory/src/test/java/com/example/memetory/domain/meme/service/MemeServiceTest.java b/backend/memetory/src/test/java/com/example/memetory/domain/meme/service/MemeServiceTest.java index 29f93c78..20056e2c 100644 --- a/backend/memetory/src/test/java/com/example/memetory/domain/meme/service/MemeServiceTest.java +++ b/backend/memetory/src/test/java/com/example/memetory/domain/meme/service/MemeServiceTest.java @@ -23,7 +23,7 @@ import org.springframework.data.domain.Pageable; import com.example.memetory.domain.member.entity.Member; -import com.example.memetory.domain.member.exception.AccessDeniedException; +import com.example.memetory.domain.member.exception.DeniedAccessException; import com.example.memetory.domain.member.service.MemberService; import com.example.memetory.domain.meme.dto.MemePageResponse; import com.example.memetory.domain.meme.dto.MemeResponse; @@ -140,15 +140,4 @@ private List createMemeResponseList(int pageSize) { return memeResponseList; } - - @Test - @DisplayName("밈 멤버와 다른 멤버로 인한 AccessDeniedMemeException 반환") - void Given_differentMember_When_certifyMemeMember_Throw_AccessDeniedMemeException() { - // given - Member differentMember = OTHER_MEMBER(); - - // then - assertThrows(AccessDeniedException.class, () -> memberService.certifyMember(member, differentMember)); - } - } diff --git a/backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesIntegrationTest.java b/backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesIntegrationTest.java index 52cf72f1..46ebcf9e 100644 --- a/backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesIntegrationTest.java +++ b/backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesIntegrationTest.java @@ -118,7 +118,7 @@ public void Given_memesId_When_deleteMemes_Then_DELETE_MEMES_SUCCESS() { assertThat(result).isEqualTo(DELETE_MEMES_SUCCESS.getMessage()); } - @DisplayName("권한 없는 멤버로 인한 MEMES_ACCESS_DENY 반환") + @DisplayName("권한 없는 멤버로 인한 MEMBER_ACCESS_DENY 반환") @Test public void Given_unAuthorizedMember_When_deleteMemes_Then_MEMES_ACCESS_DENY() { // given diff --git a/backend/memetory/src/test/java/com/example/memetory/domain/memes/service/MemesServiceTest.java b/backend/memetory/src/test/java/com/example/memetory/domain/memes/service/MemesServiceTest.java index 8283581a..d6ed3a68 100644 --- a/backend/memetory/src/test/java/com/example/memetory/domain/memes/service/MemesServiceTest.java +++ b/backend/memetory/src/test/java/com/example/memetory/domain/memes/service/MemesServiceTest.java @@ -20,7 +20,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import com.example.memetory.domain.member.entity.Member; -import com.example.memetory.domain.member.exception.AccessDeniedException; +import com.example.memetory.domain.member.exception.DeniedAccessException; import com.example.memetory.domain.member.service.MemberService; import com.example.memetory.domain.meme.entity.Meme; import com.example.memetory.domain.meme.service.MemeService; @@ -85,11 +85,11 @@ void Given_unAuthorizedMember_When_registerMemes_Throw_NotAccessMemeException() given(memeService.findMemeFromId(memesServiceDto.getMemeId())).willReturn(meme); given(memberService.findMemberFromEmail(any(String.class))).willReturn(unAuthorizedMember); - doThrow(new AccessDeniedException()).when(memberService) + doThrow(new DeniedAccessException()).when(memberService) .certifyMember(any(Member.class), any(Member.class)); // then - assertThrows(AccessDeniedException.class, () -> memesService.registerMemes(memesServiceDto)); + assertThrows(DeniedAccessException.class, () -> memesService.registerMemes(memesServiceDto)); } @Test @@ -107,16 +107,17 @@ void Given_MemesServiceDto_When_deleteMemes_Execute_MemesRepository_delete() { } @Test - @DisplayName("권한 없는 멤버로 인한 NotAccessMemesException 반환") + @DisplayName("권한 없는 멤버로 인한 DeniedAccessException 반환") void Given_unAuthorizedMember_When_deleteMemes_Throw_NotAccessMemesException() { // given Member unAuthorizedMember = OTHER_MEMBER(); given(memesRepository.findByMemesId(any())).willReturn(Optional.ofNullable(memes)); given(memberService.findMemberFromEmail(any())).willReturn(unAuthorizedMember); + doThrow(new DeniedAccessException()).when(memberService).certifyMember(any(), any()); // then - assertThrows(AccessDeniedException.class, () -> memesService.deleteMemes(memesServiceDto)); + assertThrows(DeniedAccessException.class, () -> memesService.deleteMemes(memesServiceDto)); } @Test From 037a0747fa30e21ce785c83ee75f25952ddc2163 Mon Sep 17 00:00:00 2001 From: JunRain Date: Fri, 14 Jun 2024 17:50:33 +0900 Subject: [PATCH 03/13] =?UTF-8?q?[#59]=20CommentService=20delete=EC=97=90?= =?UTF-8?q?=20certifyMember=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/service/CommentService.java | 65 +++++++++++-------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/comment/service/CommentService.java b/backend/memetory/src/main/java/com/example/memetory/domain/comment/service/CommentService.java index fe33e725..8390b819 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/comment/service/CommentService.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/comment/service/CommentService.java @@ -1,6 +1,10 @@ package com.example.memetory.domain.comment.service; -import com.example.memetory.domain.comment.dto.CommentServiceDto; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.example.memetory.domain.comment.dto.CommentInfo; +import com.example.memetory.domain.comment.dto.request.CommentRequest; import com.example.memetory.domain.comment.entity.Comment; import com.example.memetory.domain.comment.exception.NotFoundCommentException; import com.example.memetory.domain.comment.repository.CommentRepository; @@ -8,35 +12,42 @@ import com.example.memetory.domain.member.service.MemberService; import com.example.memetory.domain.memes.entity.Memes; import com.example.memetory.domain.memes.service.MemesService; + import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor public class CommentService { - - private final CommentRepository commentRepository; - private final MemberService memberService; - private final MemesService memesService; - - @Transactional - public void register(CommentServiceDto commentServiceDto) { - Member foundMember = memberService.findMemberFromEmail(commentServiceDto.getEmail()); - Memes foundMemes = memesService.findMemesFromMemesId(commentServiceDto.getMemesId()); - foundMemes.addCommentCount(); - - Comment newComment = commentServiceDto.toEntity(foundMember, foundMemes); - - commentRepository.save(newComment); - } - - @Transactional - public void delete(CommentServiceDto commentServiceDto) { - Comment foundComment = commentRepository.findById(commentServiceDto.getCommentId()).orElseThrow(NotFoundCommentException::new); - Memes foundMemes = memesService.findMemesFromMemesId(foundComment.getMemes().getId()); - foundMemes.cancelCommentCount(); - - commentRepository.delete(foundComment); - } + private final CommentRepository commentRepository; + private final MemberService memberService; + private final MemesService memesService; + + @Transactional + public CommentInfo saveComment(CommentRequest commentRequest) { + Member member = memberService.findMemberFromEmail(commentRequest.getEmail()); + Memes memes = memesService.findMemesFromMemesId(commentRequest.getMemesId()); + + Comment comment = commentRepository.save( + Comment.builder() + .member(member) + .memes(memes) + .content(commentRequest.getContent()) + .build() + ); + + memes.addCommentCount(); + + return CommentInfo.of(comment); + } + + @Transactional + public void deleteComment(String email, Long commentId) { + Comment comment = commentRepository.findById(commentId).orElseThrow(NotFoundCommentException::new); + Member loginMember = memberService.findMemberFromEmail(email); + + memberService.certifyMember(loginMember, comment.getMember()); + + comment.getMemes().cancelCommentCount(); + commentRepository.delete(comment); + } } From 5133f4869d51ae0f589e53c095e86f7da42fad6f Mon Sep 17 00:00:00 2001 From: JunRain Date: Fri, 14 Jun 2024 17:51:06 +0900 Subject: [PATCH 04/13] =?UTF-8?q?[#59]=20CommentController=EB=A5=BC=20Meme?= =?UTF-8?q?sCommentController=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 --- .../domain/comment/controller/CommentApi.java | 49 -------------- .../domain/comment/dto/CommentInfo.java | 67 +++++++++---------- .../comment/dto/request/CommentRequest.java | 21 ++++++ .../controller/comment/MemesCommentApi.java | 51 ++++++++++++++ .../comment/MemesCommentController.java} | 34 +++++----- 5 files changed, 123 insertions(+), 99 deletions(-) delete mode 100644 backend/memetory/src/main/java/com/example/memetory/domain/comment/controller/CommentApi.java create mode 100644 backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/request/CommentRequest.java create mode 100644 backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/comment/MemesCommentApi.java rename backend/memetory/src/main/java/com/example/memetory/domain/{comment/controller/CommentController.java => memes/controller/comment/MemesCommentController.java} (55%) diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/comment/controller/CommentApi.java b/backend/memetory/src/main/java/com/example/memetory/domain/comment/controller/CommentApi.java deleted file mode 100644 index 487ea5e8..00000000 --- a/backend/memetory/src/main/java/com/example/memetory/domain/comment/controller/CommentApi.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.example.memetory.domain.comment.controller; - -import com.example.memetory.domain.comment.dto.request.GenerateCommentRequest; -import com.example.memetory.global.response.ResultResponse; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.enums.ParameterIn; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.tags.Tag; - -import org.springframework.http.ResponseEntity; - -@Tag(name = "Comment") -public interface CommentApi { - - @Operation( - summary = "밈스 댓글 생성", - description = "밈스에 댓글을 달 수 있다.", - security = {@SecurityRequirement(name = "access_token")} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "201", - description = "댓글 생성!" - ) - }) - ResponseEntity register( - @Parameter(hidden = true) String email, - GenerateCommentRequest generateCommentRequest - ); - - @Operation( - summary = "밈스 댓글 삭제", - description = "댓글을 삭제 한다.", - security = {@SecurityRequirement(name = "access_token")} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "댓글 삭제!" - ) - }) - ResponseEntity delete( - @Parameter(in = ParameterIn.PATH, description = "댓글 아이디", required = true) Long commentId - ); -} diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentInfo.java b/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentInfo.java index 735ad24f..afb706b2 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentInfo.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentInfo.java @@ -1,49 +1,46 @@ package com.example.memetory.domain.comment.dto; +import java.time.LocalDateTime; + import com.example.memetory.domain.comment.entity.Comment; +import com.example.memetory.domain.member.dto.response.MemberResponse; + import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - @Getter @NoArgsConstructor @Schema(description = "댓글 정보") public class CommentInfo { - @Schema(description = "댓글 아이디") - private Long commentId; - - @Schema(description = "댓글 쓴 멤버 아이디") - private Long memberId; - - @Schema(description = "댓글 쓴 멤버 이름") - private String memberName; - - @Schema(description = "댓글 내용") - private String content; - - @Schema(description = "댓글 생성 시각") - private LocalDateTime createdAt; - - @Builder - public CommentInfo(Long commentId, Long memberId, String memberName, String content, LocalDateTime createdAt) { - this.commentId = commentId; - this.memberId = memberId; - this.memberName = memberName; - this.content = content; - this.createdAt = createdAt; - } - - public static CommentInfo of(Comment comment) { - return CommentInfo.builder() - .commentId(comment.getId()) - .memberId(comment.getMember().getId()) - .memberName(comment.getMember().getName()) - .content(comment.getContent()) - .createdAt(comment.getCreatedAt()) - .build(); - } + @Schema(description = "댓글 아이디") + private Long commentId; + + @Schema(description = "댓글 쓴 멤버 정보") + private MemberResponse member; + + @Schema(description = "댓글 내용") + private String content; + + @Schema(description = "댓글 생성 시각") + private LocalDateTime createdAt; + + @Builder + public CommentInfo(Long commentId, MemberResponse member, String content, LocalDateTime createdAt) { + this.commentId = commentId; + this.member = member; + this.content = content; + this.createdAt = createdAt; + } + + public static CommentInfo of(Comment comment) { + return CommentInfo.builder() + .commentId(comment.getId()) + .member(MemberResponse.of(comment.getMember())) + .content(comment.getContent()) + .createdAt(comment.getCreatedAt()) + .build(); + } } diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/request/CommentRequest.java b/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/request/CommentRequest.java new file mode 100644 index 00000000..eaa85d17 --- /dev/null +++ b/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/request/CommentRequest.java @@ -0,0 +1,21 @@ +package com.example.memetory.domain.comment.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Builder +@AllArgsConstructor +@Getter +public class CommentRequest { + private String content; + private Long memesId; + private String email; + + public void setMemesIdAndEmail(Long memesId, String email) { + this.memesId = memesId; + this.email = email; + } +} diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/comment/MemesCommentApi.java b/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/comment/MemesCommentApi.java new file mode 100644 index 00000000..b926a880 --- /dev/null +++ b/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/comment/MemesCommentApi.java @@ -0,0 +1,51 @@ +package com.example.memetory.domain.memes.controller.comment; + +import org.springframework.http.ResponseEntity; + +import com.example.memetory.domain.comment.dto.request.CommentRequest; +import com.example.memetory.global.response.ResultResponse; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "Comment") +public interface MemesCommentApi { + + @Operation( + summary = "밈스 댓글 생성", + description = "밈스에 댓글을 달 수 있다.", + security = {@SecurityRequirement(name = "access_token")} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "201", + description = "댓글 생성!" + ) + }) + ResponseEntity registerComment( + @Parameter(hidden = true) String email, + @Parameter(in = ParameterIn.PATH, description = "밈스 아이디", required = true) Long memesId, + CommentRequest commentRequest + ); + + @Operation( + summary = "밈스 댓글 삭제", + description = "댓글을 삭제 한다.", + security = {@SecurityRequirement(name = "access_token")} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "댓글 삭제!" + ) + }) + ResponseEntity deleteComment( + @Parameter(hidden = true) String email, + @Parameter(in = ParameterIn.PATH, description = "댓글 아이디", required = true) Long commentId + ); +} \ No newline at end of file diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/comment/controller/CommentController.java b/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/comment/MemesCommentController.java similarity index 55% rename from backend/memetory/src/main/java/com/example/memetory/domain/comment/controller/CommentController.java rename to backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/comment/MemesCommentController.java index a14e1bdf..265adb65 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/comment/controller/CommentController.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/comment/MemesCommentController.java @@ -1,4 +1,4 @@ -package com.example.memetory.domain.comment.controller; +package com.example.memetory.domain.memes.controller.comment; import static com.example.memetory.global.response.ResultCode.*; @@ -11,8 +11,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.example.memetory.domain.comment.dto.CommentServiceDto; -import com.example.memetory.domain.comment.dto.request.GenerateCommentRequest; +import com.example.memetory.domain.comment.dto.CommentInfo; +import com.example.memetory.domain.comment.dto.request.CommentRequest; import com.example.memetory.domain.comment.service.CommentService; import com.example.memetory.global.annotation.LoginMemberEmail; import com.example.memetory.global.response.ResultResponse; @@ -20,28 +20,32 @@ import lombok.RequiredArgsConstructor; @RestController -@RequestMapping("/comments") @RequiredArgsConstructor -public class CommentController implements CommentApi { - +@RequestMapping("/memes/{memesId}/comments") +public class MemesCommentController implements MemesCommentApi { private final CommentService commentService; @PostMapping @Override - public ResponseEntity register(@LoginMemberEmail String email, - @RequestBody GenerateCommentRequest generateCommentRequest) { - CommentServiceDto newCommentServiceDto = generateCommentRequest.toServiceDto(email); - commentService.register(newCommentServiceDto); - - return ResponseEntity.status(HttpStatus.CREATED).body(ResultResponse.of(CREATE_COMMENT_SUCCESS)); + public ResponseEntity registerComment( + @LoginMemberEmail String email, + @PathVariable Long memesId, + @RequestBody CommentRequest commentRequest + ) { + commentRequest.setMemesIdAndEmail(memesId, email); + CommentInfo response = commentService.saveComment(commentRequest); + + return ResponseEntity.status(HttpStatus.CREATED).body(ResultResponse.of(CREATE_COMMENT_SUCCESS, response)); } @DeleteMapping("/{commentId}") @Override - public ResponseEntity delete(@PathVariable Long commentId) { - CommentServiceDto newCommentServiceDto = CommentServiceDto.create(commentId); - commentService.delete(newCommentServiceDto); + public ResponseEntity deleteComment(@LoginMemberEmail String email, @PathVariable Long commentId) { + commentService.deleteComment(email, commentId); return ResponseEntity.ok(ResultResponse.of(DELETE_COMMENT_SUCCESS)); } + + //TODO 조회(슬라이스 형식) + } From 25f40b9b7ee6bc753be6388ef6668846efe5dc27 Mon Sep 17 00:00:00 2001 From: JunRain Date: Fri, 14 Jun 2024 17:51:33 +0900 Subject: [PATCH 05/13] =?UTF-8?q?[#59]=20CommentServiceTest=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/CommentFixture.java | 20 ++++ .../repository/CommentRepositoryTest.java | 10 ++ .../comment/service/CommentServiceTest.java | 106 ++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 backend/memetory/src/test/java/com/example/memetory/domain/comment/CommentFixture.java create mode 100644 backend/memetory/src/test/java/com/example/memetory/domain/comment/repository/CommentRepositoryTest.java create mode 100644 backend/memetory/src/test/java/com/example/memetory/domain/comment/service/CommentServiceTest.java diff --git a/backend/memetory/src/test/java/com/example/memetory/domain/comment/CommentFixture.java b/backend/memetory/src/test/java/com/example/memetory/domain/comment/CommentFixture.java new file mode 100644 index 00000000..818a7aca --- /dev/null +++ b/backend/memetory/src/test/java/com/example/memetory/domain/comment/CommentFixture.java @@ -0,0 +1,20 @@ +package com.example.memetory.domain.comment; + +import com.example.memetory.domain.comment.dto.request.CommentRequest; +import com.example.memetory.domain.comment.entity.Comment; +import com.example.memetory.domain.member.entity.Member; +import com.example.memetory.domain.memes.entity.Memes; + +public class CommentFixture { + public static Comment COMMENT(Member member, Memes memes) { + return Comment.builder() + .member(member) + .memes(memes) + .content("댓글") + .build(); + } + + public static CommentRequest COMMENT_REQUEST() { + return new CommentRequest("댓글", -1L, "junRain@ourservce.com"); + } +} diff --git a/backend/memetory/src/test/java/com/example/memetory/domain/comment/repository/CommentRepositoryTest.java b/backend/memetory/src/test/java/com/example/memetory/domain/comment/repository/CommentRepositoryTest.java new file mode 100644 index 00000000..05e80834 --- /dev/null +++ b/backend/memetory/src/test/java/com/example/memetory/domain/comment/repository/CommentRepositoryTest.java @@ -0,0 +1,10 @@ +package com.example.memetory.domain.comment.repository; + +import com.example.memetory.global.RepositoryTest; + +@RepositoryTest +public class CommentRepositoryTest { + // id를 통한 조회 + // 생성 + // 삭제 +} diff --git a/backend/memetory/src/test/java/com/example/memetory/domain/comment/service/CommentServiceTest.java b/backend/memetory/src/test/java/com/example/memetory/domain/comment/service/CommentServiceTest.java new file mode 100644 index 00000000..27682298 --- /dev/null +++ b/backend/memetory/src/test/java/com/example/memetory/domain/comment/service/CommentServiceTest.java @@ -0,0 +1,106 @@ +package com.example.memetory.domain.comment.service; + +import static com.example.memetory.domain.comment.CommentFixture.*; +import static com.example.memetory.domain.member.MemberFixture.*; +import static com.example.memetory.domain.meme.MemeFixture.*; +import static com.example.memetory.domain.memes.MemesFixture.*; +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.example.memetory.domain.comment.dto.CommentInfo; +import com.example.memetory.domain.comment.dto.request.CommentRequest; +import com.example.memetory.domain.comment.entity.Comment; +import com.example.memetory.domain.comment.repository.CommentRepository; +import com.example.memetory.domain.member.entity.Member; +import com.example.memetory.domain.member.exception.DeniedAccessException; +import com.example.memetory.domain.member.service.MemberService; +import com.example.memetory.domain.meme.entity.Meme; +import com.example.memetory.domain.memes.entity.Memes; +import com.example.memetory.domain.memes.service.MemesService; + +@DisplayName("Comment 서비스 테스트의 ") +@ExtendWith(MockitoExtension.class) +public class CommentServiceTest { + @InjectMocks + private CommentService commentService; + @Mock + private CommentRepository commentRepository; + @Mock + private MemberService memberService; + @Mock + private MemesService memesService; + + private Member member; + private Memes memes; + private Comment comment; + + @BeforeEach + void setUp() { + member = MEMBER(); + Meme meme = MEME(member); + memes = MEMES(member, meme); + comment = COMMENT(member, memes); + } + + @Test + @DisplayName("CommentRequest를 통한 댓글 생성 성공") + void Given_CommentRequest_When_saveComment_Then_CommentInfo() { + // given + CommentRequest request = COMMENT_REQUEST(); + + given(memberService.findMemberFromEmail(request.getEmail())).willReturn(member); + given(memesService.findMemesFromMemesId(request.getMemesId())).willReturn(memes); + given(commentRepository.save(any(Comment.class))).willReturn(comment); + + // when + CommentInfo result = commentService.saveComment(request); + + // then + assertThat(result.getContent()).isEqualTo(request.getContent()); + } + + @Test + @DisplayName("접근 권한이 없는 유저로 인한 DeniedAccessException 반환") + void Given_AccessDeniedMember_When_deleteComment_Throw_AccessDeniedException() { + // given + String email = "junrain@ourservice.com"; + Long commentId = -1L; + + given(commentRepository.findById(commentId)).willReturn(Optional.ofNullable(comment)); + given(memberService.findMemberFromEmail(email)).willReturn(OTHER_MEMBER()); + doThrow(new DeniedAccessException()).when(memberService).certifyMember(any(Member.class), any(Member + .class)); + + // then + assertThrows(DeniedAccessException.class, () -> commentService.deleteComment(email, commentId)); + } + + @Test + @DisplayName("email과 commentId를 통한 삭제 성공") + void Given_emailAndCommentId_When_deleteComment_Verify_deleteComment() { + // given + String email = "junrain@ourservice.com"; + Long commentId = -1L; + + given(commentRepository.findById(commentId)).willReturn(Optional.ofNullable(comment)); + given(memberService.findMemberFromEmail(email)).willReturn(member); + doNothing().when(memberService).certifyMember(any(Member.class), any(Member.class)); + + // when + commentService.deleteComment(email, commentId); + + // then + verify(commentRepository).delete(comment); + } +} From 2bd3b67d8142f360a3a251d6d86e27c8d5806f10 Mon Sep 17 00:00:00 2001 From: JunRain Date: Fri, 14 Jun 2024 17:51:57 +0900 Subject: [PATCH 06/13] =?UTF-8?q?[#59]=20MemesCommentIntegrationTest=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 --- .../memes/MemesCommentIntegrationTest.java | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesCommentIntegrationTest.java diff --git a/backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesCommentIntegrationTest.java b/backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesCommentIntegrationTest.java new file mode 100644 index 00000000..25cfe0b9 --- /dev/null +++ b/backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesCommentIntegrationTest.java @@ -0,0 +1,132 @@ +package com.example.memetory.domain.memes; + +import static com.example.memetory.domain.comment.CommentFixture.*; +import static com.example.memetory.domain.member.MemberFixture.*; +import static com.example.memetory.domain.meme.MemeFixture.*; +import static com.example.memetory.domain.memes.MemesFixture.*; +import static com.example.memetory.global.response.ErrorCode.*; +import static com.example.memetory.global.response.ResultCode.*; +import static io.restassured.RestAssured.*; +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; + +import com.example.memetory.domain.comment.dto.CommentInfo; +import com.example.memetory.domain.comment.dto.request.CommentRequest; +import com.example.memetory.domain.comment.entity.Comment; +import com.example.memetory.domain.comment.repository.CommentRepository; +import com.example.memetory.domain.member.entity.Member; +import com.example.memetory.domain.meme.entity.Meme; +import com.example.memetory.domain.meme.repository.MemeRepository; +import com.example.memetory.domain.memes.entity.Memes; +import com.example.memetory.domain.memes.repository.MemesRepository; +import com.example.memetory.global.integration.BaseIntegrationTest; + +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; + +@DisplayName("Comment 통합 테스트의 ") +public class MemesCommentIntegrationTest extends BaseIntegrationTest { + @Autowired + MemeRepository memeRepository; + @Autowired + MemesRepository memesRepository; + @Autowired + CommentRepository commentRepository; + + private Meme meme; + private Memes memes; + + @Override + @BeforeEach + public void setUp() { + super.setUp(); + + meme = memeRepository.save(MEME(member)); + memes = memesRepository.save(MEMES(member, meme)); + } + + @Test + @DisplayName("memesId와 CommentRequest를 통한 댓글 생성 성공") + void Given_memesIdAndCommentRequest_When_registerComment_() { + // given + CommentRequest request = CommentRequest.builder() + .content("댓글 생성 완료") + .build(); + + // when + ExtractableResponse response = + given() + .log() + .all() + .auth().oauth2(accessToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(request) + .when() + .post("/memes/{memesId}/comments", memes.getId()) + .then() + .log() + .all() + .extract(); + + CommentInfo result = response.jsonPath().getObject("data", CommentInfo.class); + + // then + assertThat(result.getContent()).isEqualTo(request.getContent()); + } + + @Test + @DisplayName("접근 불가능한 멤버로 인한 MEMBER_NOT_DENY 반환") + void Given_DeniedMember_When_deleteComment_Then_MEMBER_NOT_DENY() { + // then + Member deninedMember = memberRepository.save(OTHER_MEMBER()); + Comment comment = commentRepository.save(COMMENT(deninedMember, memes)); + + // when + ExtractableResponse response = + given() + .log() + .all() + .auth().oauth2(accessToken) + .when() + .delete("/memes/{memesId}/comments/{commentId}", memes.getId(), comment.getId()) + .then() + .log() + .all() + .extract(); + + String result = response.jsonPath().get(ERROR_MESSAGE); + + //then + assertThat(result).isEqualTo(MEMBER_ACCESS_DENY.getMessage()); + } + + @Test + @DisplayName("commentId 통한 댓글 삭제 성공") + void Given_commentId_When_deleteComment_Then_DELETE_COMMENT_SUCCESS() { + // then + Comment comment = commentRepository.save(COMMENT(member, memes)); + + // when + ExtractableResponse response = + given() + .log() + .all() + .auth().oauth2(accessToken) + .when() + .delete("/memes/{memesId}/comments/{commentId}", memes.getId(), comment.getId()) + .then() + .log() + .all() + .extract(); + + String result = response.jsonPath().get(MESSAGE); + + //then + assertThat(result).isEqualTo(DELETE_COMMENT_SUCCESS.getMessage()); + } +} From 8ca49bc8dca4dff84a7526d76a63921d86ec3386 Mon Sep 17 00:00:00 2001 From: JunRain Date: Fri, 14 Jun 2024 18:02:28 +0900 Subject: [PATCH 07/13] =?UTF-8?q?[#59]=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../memetory/domain/memes/MemesCommentIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesCommentIntegrationTest.java b/backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesCommentIntegrationTest.java index 25cfe0b9..bcd5be97 100644 --- a/backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesCommentIntegrationTest.java +++ b/backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesCommentIntegrationTest.java @@ -80,7 +80,7 @@ void Given_memesIdAndCommentRequest_When_registerComment_() { } @Test - @DisplayName("접근 불가능한 멤버로 인한 MEMBER_NOT_DENY 반환") + @DisplayName("접근 불가능한 멤버로 인한 MEMBER_ACCESS_DENY 반환") void Given_DeniedMember_When_deleteComment_Then_MEMBER_NOT_DENY() { // then Member deninedMember = memberRepository.save(OTHER_MEMBER()); From eadc2a7dbc2fe90bc85ece07c409fd0b29d08430 Mon Sep 17 00:00:00 2001 From: JunRain Date: Fri, 14 Jun 2024 20:52:29 +0900 Subject: [PATCH 08/13] =?UTF-8?q?[#59]=20CommentRepository=EC=97=90=20Quer?= =?UTF-8?q?yDsl=20=EC=A0=81=EC=9A=A9=20=EB=B0=8F=20=EB=B0=88=EC=8A=A4=20Id?= =?UTF-8?q?=EB=A5=BC=20=ED=86=B5=ED=95=B4=20=EB=B0=88=EC=8A=A4=20=EC=8A=AC?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EC=8A=A4=20=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=EA=B8=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/dto/CommentInfo.java | 2 + ...entInfoList.java => CommentInfoSlice.java} | 9 +++- .../domain/comment/dto/CommentServiceDto.java | 33 -------------- .../repository/CommentQDtoFactory.java | 20 +++++++++ .../repository/CommentQueryRepository.java | 10 +++++ .../CommentQueryRepositoryImpl.java | 45 +++++++++++++++++++ .../comment/repository/CommentRepository.java | 2 +- .../member/dto/response/MemberResponse.java | 2 + 8 files changed, 87 insertions(+), 36 deletions(-) rename backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/{CommentInfoList.java => CommentInfoSlice.java} (60%) delete mode 100644 backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentServiceDto.java create mode 100644 backend/memetory/src/main/java/com/example/memetory/domain/comment/repository/CommentQDtoFactory.java create mode 100644 backend/memetory/src/main/java/com/example/memetory/domain/comment/repository/CommentQueryRepository.java create mode 100644 backend/memetory/src/main/java/com/example/memetory/domain/comment/repository/CommentQueryRepositoryImpl.java diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentInfo.java b/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentInfo.java index afb706b2..8795ec7d 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentInfo.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentInfo.java @@ -4,6 +4,7 @@ import com.example.memetory.domain.comment.entity.Comment; import com.example.memetory.domain.member.dto.response.MemberResponse; +import com.querydsl.core.annotations.QueryProjection; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; @@ -27,6 +28,7 @@ public class CommentInfo { @Schema(description = "댓글 생성 시각") private LocalDateTime createdAt; + @QueryProjection @Builder public CommentInfo(Long commentId, MemberResponse member, String content, LocalDateTime createdAt) { this.commentId = commentId; diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentInfoList.java b/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentInfoSlice.java similarity index 60% rename from backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentInfoList.java rename to backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentInfoSlice.java index b4917c94..7fa7c314 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentInfoList.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentInfoSlice.java @@ -10,11 +10,16 @@ @Getter @NoArgsConstructor @Schema(description = "댓글 리스트") -public class CommentInfoList { +public class CommentInfoSlice { + @Schema(description = "현재 페이지") + private int currentPage; + @Schema(description = "다음 페이지 여부") + private boolean hasNext; + private List commentInfoList; @Builder - public CommentInfoList(List commentInfoList) { + public CommentInfoSlice(List commentInfoList) { this.commentInfoList = commentInfoList; } } diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentServiceDto.java b/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentServiceDto.java deleted file mode 100644 index 0934709b..00000000 --- a/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentServiceDto.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.example.memetory.domain.comment.dto; - -import com.example.memetory.domain.comment.entity.Comment; -import com.example.memetory.domain.member.entity.Member; -import com.example.memetory.domain.meme.dto.MemeServiceDto; -import com.example.memetory.domain.memes.entity.Memes; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; - -@Builder -@Getter -@AllArgsConstructor -public class CommentServiceDto { - private Long memesId; - private Long commentId; - private String content; - private String email; - - public static CommentServiceDto create(Long commentId) { - return CommentServiceDto.builder() - .commentId(commentId) - .build(); - } - - public Comment toEntity(Member member, Memes memes) { - return Comment.builder() - .content(content) - .member(member) - .memes(memes) - .build(); - } -} diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/comment/repository/CommentQDtoFactory.java b/backend/memetory/src/main/java/com/example/memetory/domain/comment/repository/CommentQDtoFactory.java new file mode 100644 index 00000000..9c4dd78f --- /dev/null +++ b/backend/memetory/src/main/java/com/example/memetory/domain/comment/repository/CommentQDtoFactory.java @@ -0,0 +1,20 @@ +package com.example.memetory.domain.comment.repository; + +import static com.example.memetory.domain.comment.entity.QComment.*; + +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import com.example.memetory.domain.comment.dto.QCommentInfo; +import com.example.memetory.domain.member.dto.response.QMemberResponse; + +@Component +public class CommentQDtoFactory { + + @Bean + public QCommentInfo qCommentInfo() { + return new QCommentInfo(comment.id, + new QMemberResponse(comment.member.id, comment.member.nickname, comment.member.imageUrl, comment.member.createdAt), + comment.content, comment.createdAt); + } +} diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/comment/repository/CommentQueryRepository.java b/backend/memetory/src/main/java/com/example/memetory/domain/comment/repository/CommentQueryRepository.java new file mode 100644 index 00000000..dba52ddf --- /dev/null +++ b/backend/memetory/src/main/java/com/example/memetory/domain/comment/repository/CommentQueryRepository.java @@ -0,0 +1,10 @@ +package com.example.memetory.domain.comment.repository; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; + +import com.example.memetory.domain.comment.dto.CommentInfo; + +public interface CommentQueryRepository { + Slice findCommentsSliceByMemesId(Long memesId, Pageable pageable); +} diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/comment/repository/CommentQueryRepositoryImpl.java b/backend/memetory/src/main/java/com/example/memetory/domain/comment/repository/CommentQueryRepositoryImpl.java new file mode 100644 index 00000000..b1bfb9d9 --- /dev/null +++ b/backend/memetory/src/main/java/com/example/memetory/domain/comment/repository/CommentQueryRepositoryImpl.java @@ -0,0 +1,45 @@ +package com.example.memetory.domain.comment.repository; + +import static com.example.memetory.domain.comment.entity.QComment.*; +import static com.example.memetory.domain.member.entity.QMember.*; +import static com.example.memetory.domain.memes.entity.QMemes.*; + +import java.util.List; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import org.springframework.stereotype.Repository; + +import com.example.memetory.domain.comment.dto.CommentInfo; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class CommentQueryRepositoryImpl implements CommentQueryRepository { + private final JPAQueryFactory jpaQueryFactory; + private final CommentQDtoFactory commentQDtoFactory; + + @Override + public Slice findCommentsSliceByMemesId(Long memesId, Pageable pageable) { + int pageSize = pageable.getPageSize(); + List memesList = jpaQueryFactory.select(commentQDtoFactory.qCommentInfo()) + .from(comment) + .where(comment.memes.id.eq(memesId)) + .join(comment.member, member) + .join(comment.memes, memes) + .offset(pageable.getOffset()) + .limit(pageSize + 1) + .fetch(); + + boolean hasNext = false; + if (memesList.size() > pageSize) { + memesList.remove(pageSize); + hasNext = true; + } + + return new SliceImpl<>(memesList, pageable, hasNext); + } +} diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/comment/repository/CommentRepository.java b/backend/memetory/src/main/java/com/example/memetory/domain/comment/repository/CommentRepository.java index 7bd34b52..7359d2d4 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/comment/repository/CommentRepository.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/comment/repository/CommentRepository.java @@ -3,5 +3,5 @@ import com.example.memetory.domain.comment.entity.Comment; import org.springframework.data.jpa.repository.JpaRepository; -public interface CommentRepository extends JpaRepository { +public interface CommentRepository extends JpaRepository, CommentQueryRepository { } diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/member/dto/response/MemberResponse.java b/backend/memetory/src/main/java/com/example/memetory/domain/member/dto/response/MemberResponse.java index 4753ab14..5e9d22fc 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/member/dto/response/MemberResponse.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/member/dto/response/MemberResponse.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import com.example.memetory.domain.member.entity.Member; +import com.querydsl.core.annotations.QueryProjection; import lombok.AccessLevel; import lombok.Builder; @@ -17,6 +18,7 @@ public class MemberResponse { private String imageUrl; private LocalDateTime createdAt; + @QueryProjection @Builder public MemberResponse(Long memberId, String nickName, String imageUrl, LocalDateTime createdAt) { this.memberId = memberId; From afc81733f814c79c708beb927b1c986c102fc039 Mon Sep 17 00:00:00 2001 From: JunRain Date: Fri, 14 Jun 2024 20:53:23 +0900 Subject: [PATCH 09/13] =?UTF-8?q?[#59]=20CommentQDtoFactory=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/java/com/example/memetory/global/RepositoryTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/memetory/src/test/java/com/example/memetory/global/RepositoryTest.java b/backend/memetory/src/test/java/com/example/memetory/global/RepositoryTest.java index 7b003b18..b61a0558 100644 --- a/backend/memetory/src/test/java/com/example/memetory/global/RepositoryTest.java +++ b/backend/memetory/src/test/java/com/example/memetory/global/RepositoryTest.java @@ -9,6 +9,7 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; +import com.example.memetory.domain.comment.repository.CommentQDtoFactory; import com.example.memetory.domain.meme.repository.MemeQDtoFactory; import com.example.memetory.domain.memes.repository.MemesQDtoFactory; import com.example.memetory.global.config.JpaAuditingConfig; @@ -17,7 +18,8 @@ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @DataJpaTest -@Import({JpaAuditingConfig.class, QueryDslConfig.class, MemeQDtoFactory.class, MemesQDtoFactory.class}) +@Import({JpaAuditingConfig.class, QueryDslConfig.class, MemeQDtoFactory.class, MemesQDtoFactory.class, + CommentQDtoFactory.class}) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) public @interface RepositoryTest { } From 3ac6af99feddf5ff246271b00b0bd454d0a36c19 Mon Sep 17 00:00:00 2001 From: JunRain Date: Fri, 14 Jun 2024 20:53:55 +0900 Subject: [PATCH 10/13] =?UTF-8?q?[#59]=20CommentRepositoryTest=20=EB=B0=88?= =?UTF-8?q?=EC=8A=A4=20Id=EB=A5=BC=20=ED=86=B5=ED=95=9C=20=EB=8C=93?= =?UTF-8?q?=EA=B8=80=20=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0=20=EC=84=B1?= =?UTF-8?q?=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/CommentRepositoryTest.java | 57 ++++++++++++++++++- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/backend/memetory/src/test/java/com/example/memetory/domain/comment/repository/CommentRepositoryTest.java b/backend/memetory/src/test/java/com/example/memetory/domain/comment/repository/CommentRepositoryTest.java index 05e80834..2d918149 100644 --- a/backend/memetory/src/test/java/com/example/memetory/domain/comment/repository/CommentRepositoryTest.java +++ b/backend/memetory/src/test/java/com/example/memetory/domain/comment/repository/CommentRepositoryTest.java @@ -1,10 +1,61 @@ package com.example.memetory.domain.comment.repository; +import static com.example.memetory.domain.member.MemberFixture.*; +import static com.example.memetory.domain.meme.MemeFixture.*; +import static com.example.memetory.domain.memes.MemesFixture.*; +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; + +import com.example.memetory.domain.comment.dto.CommentInfo; +import com.example.memetory.domain.comment.entity.Comment; +import com.example.memetory.domain.member.entity.Member; +import com.example.memetory.domain.member.repository.MemberRepository; +import com.example.memetory.domain.meme.entity.Meme; +import com.example.memetory.domain.meme.repository.MemeRepository; +import com.example.memetory.domain.memes.entity.Memes; +import com.example.memetory.domain.memes.repository.MemesRepository; import com.example.memetory.global.RepositoryTest; +@DisplayName("Comment 레포지토리 테스트의 ") @RepositoryTest public class CommentRepositoryTest { - // id를 통한 조회 - // 생성 - // 삭제 + @Autowired + private CommentRepository commentRepository; + @Autowired + private MemberRepository memberRepository; + @Autowired + private MemesRepository memesRepository; + @Autowired + private MemeRepository memeRepository; + + @Test + @DisplayName("memesId를 통한 Slice 반환") + void Given_memesId_When_findCommentsSliceByMemesId_Then_Slice_CommentInfo() { + // given + Member member = memberRepository.save(MEMBER()); + Meme meme = memeRepository.save(MEME(member)); + Memes memes = memesRepository.save(MEMES(member, meme)); + for (int i = 0; i < 30; i++) { + commentRepository.save(new Comment("댓글" + i, member, memes)); + } + + Pageable pageable = PageRequest.of(0, 10); + + // when + Slice result = commentRepository.findCommentsSliceByMemesId(memes.getId(), pageable); + + // then + assertThat(result).hasSize(10); + assertTrue(result.hasNext()); + + Long memberResponseId = result.getContent().get(0).getMember().getMemberId(); + assertThat(memberResponseId).isEqualTo(member.getId()); + } } From dcecb80b9297985b76e4526025bebc9ef9b8a166 Mon Sep 17 00:00:00 2001 From: JunRain Date: Fri, 14 Jun 2024 21:28:50 +0900 Subject: [PATCH 11/13] =?UTF-8?q?[#59]=20CommentService=EC=97=90=20memesId?= =?UTF-8?q?=EB=A5=BC=20=ED=86=B5=ED=95=9C=20CommentInfoSlice=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/dto/CommentInfoSlice.java | 6 +++++- .../domain/comment/service/CommentService.java | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentInfoSlice.java b/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentInfoSlice.java index 7fa7c314..278d432a 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentInfoSlice.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/comment/dto/CommentInfoSlice.java @@ -7,6 +7,8 @@ import java.util.List; +import org.springframework.data.domain.Slice; + @Getter @NoArgsConstructor @Schema(description = "댓글 리스트") @@ -19,7 +21,9 @@ public class CommentInfoSlice { private List commentInfoList; @Builder - public CommentInfoSlice(List commentInfoList) { + public CommentInfoSlice(int currentPage, boolean hasNext, List commentInfoList) { + this.currentPage = currentPage; + this.hasNext = hasNext; this.commentInfoList = commentInfoList; } } diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/comment/service/CommentService.java b/backend/memetory/src/main/java/com/example/memetory/domain/comment/service/CommentService.java index 8390b819..782016ec 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/comment/service/CommentService.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/comment/service/CommentService.java @@ -1,9 +1,12 @@ package com.example.memetory.domain.comment.service; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.memetory.domain.comment.dto.CommentInfo; +import com.example.memetory.domain.comment.dto.CommentInfoSlice; import com.example.memetory.domain.comment.dto.request.CommentRequest; import com.example.memetory.domain.comment.entity.Comment; import com.example.memetory.domain.comment.exception.NotFoundCommentException; @@ -50,4 +53,15 @@ public void deleteComment(String email, Long commentId) { comment.getMemes().cancelCommentCount(); commentRepository.delete(comment); } + + @Transactional(readOnly = true) + public CommentInfoSlice findCommentFromMemesId(Long memesId, Pageable pageable) { + Slice commentInfos = commentRepository.findCommentsSliceByMemesId(memesId, pageable); + + return CommentInfoSlice.builder() + .commentInfoList(commentInfos.getContent()) + .currentPage(pageable.getPageNumber()) + .hasNext(commentInfos.hasNext()) + .build(); + } } From 5401f0e392127b9f60f7d346c4c08f1429b380a6 Mon Sep 17 00:00:00 2001 From: JunRain Date: Fri, 14 Jun 2024 21:39:40 +0900 Subject: [PATCH 12/13] =?UTF-8?q?[#59]=20MemesCommentController=20commentI?= =?UTF-8?q?d=EB=A5=BC=20=ED=86=B5=ED=95=9C=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=EB=B0=88=EC=8A=A4=20=EB=8C=93=EA=B8=80=20=EC=A1=B0=ED=9A=8C=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 --- .../memes/controller/comment/MemesCommentController.java | 9 ++++++++- .../com/example/memetory/global/response/ResultCode.java | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/comment/MemesCommentController.java b/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/comment/MemesCommentController.java index 265adb65..c688e75d 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/comment/MemesCommentController.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/memes/controller/comment/MemesCommentController.java @@ -2,9 +2,11 @@ import static com.example.memetory.global.response.ResultCode.*; +import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -12,6 +14,7 @@ import org.springframework.web.bind.annotation.RestController; import com.example.memetory.domain.comment.dto.CommentInfo; +import com.example.memetory.domain.comment.dto.CommentInfoSlice; import com.example.memetory.domain.comment.dto.request.CommentRequest; import com.example.memetory.domain.comment.service.CommentService; import com.example.memetory.global.annotation.LoginMemberEmail; @@ -46,6 +49,10 @@ public ResponseEntity deleteComment(@LoginMemberEmail String ema return ResponseEntity.ok(ResultResponse.of(DELETE_COMMENT_SUCCESS)); } - //TODO 조회(슬라이스 형식) + @GetMapping() + public ResponseEntity findCommentInfoSlice(@PathVariable Long memesId, Pageable pageable) { + CommentInfoSlice response = commentService.findCommentFromMemesId(memesId, pageable); + return ResponseEntity.ok(ResultResponse.of(GET_ALL_MEMES_COMMENT_SUCCESS, response)); + } } diff --git a/backend/memetory/src/main/java/com/example/memetory/global/response/ResultCode.java b/backend/memetory/src/main/java/com/example/memetory/global/response/ResultCode.java index aa4341b5..70a6b3d2 100644 --- a/backend/memetory/src/main/java/com/example/memetory/global/response/ResultCode.java +++ b/backend/memetory/src/main/java/com/example/memetory/global/response/ResultCode.java @@ -32,6 +32,7 @@ public enum ResultCode { // comment CREATE_COMMENT_SUCCESS(201, "댓글 생성 성공"), DELETE_COMMENT_SUCCESS(200, "댓글 삭제 성공"), + GET_ALL_MEMES_COMMENT_SUCCESS(200, "전체 밈스 댓글 조회 성공"), //complain CREATE_COMPLAIN_SUCCESS(201, "신고 생성 성공"), From bc6716f56ff4d1e32cad1f6c9ab574d7c3772e7c Mon Sep 17 00:00:00 2001 From: JunRain Date: Fri, 14 Jun 2024 21:40:19 +0900 Subject: [PATCH 13/13] =?UTF-8?q?[#59]=20MemesCommentIntegrationTest=20mem?= =?UTF-8?q?esId=EB=A5=BC=20=ED=86=B5=ED=95=9C=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=EB=B0=88=EC=8A=A4=20=EB=8C=93=EA=B8=80=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../memes/MemesCommentIntegrationTest.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesCommentIntegrationTest.java b/backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesCommentIntegrationTest.java index bcd5be97..8a9e62fa 100644 --- a/backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesCommentIntegrationTest.java +++ b/backend/memetory/src/test/java/com/example/memetory/domain/memes/MemesCommentIntegrationTest.java @@ -8,6 +8,7 @@ import static com.example.memetory.global.response.ResultCode.*; import static io.restassured.RestAssured.*; import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -16,6 +17,7 @@ import org.springframework.http.MediaType; import com.example.memetory.domain.comment.dto.CommentInfo; +import com.example.memetory.domain.comment.dto.CommentInfoSlice; import com.example.memetory.domain.comment.dto.request.CommentRequest; import com.example.memetory.domain.comment.entity.Comment; import com.example.memetory.domain.comment.repository.CommentRepository; @@ -129,4 +131,32 @@ void Given_commentId_When_deleteComment_Then_DELETE_COMMENT_SUCCESS() { //then assertThat(result).isEqualTo(DELETE_COMMENT_SUCCESS.getMessage()); } + + @Test + @DisplayName("memesId를 통한 전체 밈스 댓글 조회 성공") + void Given_memesId_When_deleteComment_Then_DELETE_COMMENT_SUCCESS() { + // given + for (int i = 0; i < 30; i++) { + commentRepository.save(new Comment("댓글" + i, member, memes)); + } + + // when + ExtractableResponse response = + given() + .log() + .all() + .auth().oauth2(accessToken) + .when() + .get("/memes/{memesId}/comments?page={page}&size={size}", memes.getId(), 0, 10) + .then() + .log() + .all() + .extract(); + + CommentInfoSlice result = response.jsonPath().getObject("data", CommentInfoSlice.class); + + //then + assertThat(result.getCommentInfoList()).hasSize(10); + assertTrue(result.isHasNext()); + } }