From b7652ec66a9670dad782d1c84ff827f013657a5c Mon Sep 17 00:00:00 2001 From: ArcticFoox Date: Tue, 6 May 2025 21:25:42 +0900 Subject: [PATCH 01/66] feat: add logback xml --- build.gradle | 8 ++++++++ security_setting | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f9edae1..a856e84 100644 --- a/build.gradle +++ b/build.gradle @@ -68,3 +68,11 @@ tasks.register('copyYml', Copy) { into 'src/main/resources' } } + +tasks.register('copyLog', Copy) { + copy { + from 'security_setting' + include "logback-spring.xml" + into 'src/main/resources' + } +} diff --git a/security_setting b/security_setting index 9785410..3a87dd5 160000 --- a/security_setting +++ b/security_setting @@ -1 +1 @@ -Subproject commit 978541071dd3e00273a3bb03a06feeccb4b8b060 +Subproject commit 3a87dd52cc27d79a02574700277cb0b49363a54c From c2e11591576dc11862c34efc905f0e55847f2659 Mon Sep 17 00:00:00 2001 From: ArcticFoox Date: Wed, 7 May 2025 22:50:53 +0900 Subject: [PATCH 02/66] feat: add console log in dev --- .gitignore | 1 + security_setting | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 785de1e..8a87770 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ out/ ### YAML Settings ### /src/main/resources/*.yml /src/test/resources/application-test.yml +/src/main/resources/logback-spring.xml ### VS Code ### .vscode/ diff --git a/security_setting b/security_setting index 3a87dd5..926fbf3 160000 --- a/security_setting +++ b/security_setting @@ -1 +1 @@ -Subproject commit 3a87dd52cc27d79a02574700277cb0b49363a54c +Subproject commit 926fbf3b591cbf44470e27b95d0508dccbed0d87 From a93647a4ff9f32d39840f168e1e74ed7fc3a1712 Mon Sep 17 00:00:00 2001 From: ehgur Date: Thu, 8 May 2025 09:33:03 +0900 Subject: [PATCH 03/66] =?UTF-8?q?feat:=20code=20post=20=EA=B3=B5=EA=B0=9C?= =?UTF-8?q?=20=EC=97=AC=EB=B6=80=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 작성자가 아닐 경우 비공개 게시글을 볼 수 없음 - 작성자여도 전체 조회에서는 비공개 게시글을 볼 수 없음 - 작성자는 myCode 에서만 비공개 글을 볼 수 있음 --- .../domain/codepost/controller/CodePostController.java | 9 +++++---- .../codepost/dto/request/CodePostSaveRequestDto.java | 4 ++++ .../codepost/dto/request/CodePostUpdateRequestDto.java | 3 +++ .../dto/response/CodePostThumbnailResponseDto.java | 2 ++ .../autoreview/domain/codepost/entity/CodePost.java | 10 +++++++++- .../domain/codepost/entity/CodePostRepository.java | 8 ++++++-- .../domain/codepost/service/CodePostDtoService.java | 4 ++-- .../domain/codepost/service/CodePostService.java | 7 ++++--- .../V5.1__add_is_public_column_to_code_post.sql | 2 ++ .../V5.2__if_ispublic_column_is_null_then_update.sql | 1 + 10 files changed, 38 insertions(+), 12 deletions(-) create mode 100644 src/main/resources/db/migration/V5.1__add_is_public_column_to_code_post.sql create mode 100644 src/main/resources/db/migration/V5.2__if_ispublic_column_is_null_then_update.sql diff --git a/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java b/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java index 157d6a2..3806f8e 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java +++ b/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java @@ -45,13 +45,14 @@ public ResponseEntity search(@RequestParam String keywo return ResponseEntity.ok().body(codePostMemberService.postSearch(keyword, pageable)); } - @Operation(summary = "코드 포스트 단일 조회", description = "코드 포스트 단일 조회") + @Operation(summary = "코드 포스트 단일 조회", description = "공개된 포스트 or 작성자일 경우만 조회됨") @GetMapping("/detail/{id}") - public ResponseEntity view(@PathVariable("id") Long codePostId) { - return ResponseEntity.ok().body(codePostMemberService.findPostById(codePostId)); + public ResponseEntity view(@PathVariable("id") Long codePostId, + @AuthenticationPrincipal UserDetails userDetails) { + return ResponseEntity.ok().body(codePostMemberService.findPostById(codePostId,userDetails.getUsername())); } - @Operation(summary = "코드 포스트 전체 조회", description = "코드 포스트 전체 조회") + @Operation(summary = "코드 포스트 전체 조회", description = "코드 포스트 전체 조회(비공개 포스트는 제외)") @GetMapping("/list") public ResponseEntity viewAll(@PageableDefault(page = 0, size = 9) Pageable pageable) { return ResponseEntity.ok().body(codePostMemberService.findPostByPage(pageable)); diff --git a/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostSaveRequestDto.java b/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostSaveRequestDto.java index b31578b..d051341 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostSaveRequestDto.java +++ b/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostSaveRequestDto.java @@ -15,6 +15,9 @@ public record CodePostSaveRequestDto( @Schema(description = "난이도", example = "4") int level, + @Schema(description = "공개여부", example = "true") + boolean isPublic, + @Schema(description = "복습일 설정", example = "2024-10-11") LocalDate reviewDay, @@ -33,6 +36,7 @@ public CodePost toEntity(Member member){ .writerId(member.getId()) .title(title) .level(level) + .isPublic(isPublic) .reviewDay(reviewDay) .description(description) .language(Language.of(language)) diff --git a/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostUpdateRequestDto.java b/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostUpdateRequestDto.java index 9eaa89a..e5b98fb 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostUpdateRequestDto.java +++ b/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostUpdateRequestDto.java @@ -21,6 +21,9 @@ public class CodePostUpdateRequestDto { @Schema(description = "난이도", example = "4") private final int level; + @Schema(description = "공개여부", example = "true") + private final boolean isPublic; + @Schema(description = "복습일 설정", example = "2024-10-11") private final LocalDate reviewDay; diff --git a/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostThumbnailResponseDto.java b/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostThumbnailResponseDto.java index d6229c4..ce16b8b 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostThumbnailResponseDto.java +++ b/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostThumbnailResponseDto.java @@ -14,6 +14,7 @@ public class CodePostThumbnailResponseDto { private final String writerNickName; private final String title; private final int level; + private final boolean isPublic; private final String description; private final LocalDateTime createdDate; @@ -24,6 +25,7 @@ public CodePostThumbnailResponseDto(CodePost entity, Member writer) { this.writerNickName = writer.getNickname(); this.title = entity.getTitle(); this.level = entity.getLevel(); + this.isPublic = entity.isPublic(); this.description = entity.getDescription(); this.createdDate = entity.getCreateDate(); } diff --git a/src/main/java/org/example/autoreview/domain/codepost/entity/CodePost.java b/src/main/java/org/example/autoreview/domain/codepost/entity/CodePost.java index 7ee168f..d4ed452 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/entity/CodePost.java +++ b/src/main/java/org/example/autoreview/domain/codepost/entity/CodePost.java @@ -19,6 +19,7 @@ import org.example.autoreview.domain.comment.codepost.entity.CodePostComment; import org.example.autoreview.domain.review.entity.Review; import org.example.autoreview.global.common.basetime.BaseEntity; +import org.hibernate.annotations.ColumnDefault; @Getter @NoArgsConstructor @@ -42,6 +43,10 @@ public class CodePost extends BaseEntity { private int level; + @Column(nullable = false) + @ColumnDefault("true") + private boolean isPublic; + private LocalDate reviewDay; @Column(length = 4000) @@ -54,7 +59,8 @@ public class CodePost extends BaseEntity { private Language language; @Builder - public CodePost(String title, int level, LocalDate reviewDay, String description, String code, Language language, Long writerId) { + public CodePost(String title, int level, LocalDate reviewDay, String description, + String code, Language language, Long writerId, boolean isPublic) { this.writerId = writerId; this.title = title; this.level = level; @@ -62,11 +68,13 @@ public CodePost(String title, int level, LocalDate reviewDay, String description this.description = description; this.code = code; this.language = language; + this.isPublic = isPublic; } public void update(CodePostUpdateRequestDto requestDto){ this.title = requestDto.getTitle(); this.level = requestDto.getLevel(); + this.isPublic = requestDto.isPublic(); this.reviewDay = requestDto.getReviewDay(); this.description = requestDto.getDescription(); this.language = Language.of(requestDto.getLanguage()); diff --git a/src/main/java/org/example/autoreview/domain/codepost/entity/CodePostRepository.java b/src/main/java/org/example/autoreview/domain/codepost/entity/CodePostRepository.java index 89b4867..1148928 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/entity/CodePostRepository.java +++ b/src/main/java/org/example/autoreview/domain/codepost/entity/CodePostRepository.java @@ -1,6 +1,7 @@ package org.example.autoreview.domain.codepost.entity; import io.lettuce.core.dynamic.annotation.Param; +import java.util.Optional; import org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -9,11 +10,14 @@ public interface CodePostRepository extends JpaRepository { + @Query("SELECT c FROM CodePost c WHERE c.id = :id AND c.writerId = :memberId OR c.isPublic = TRUE") + Optional findByIdIsPublic(@Param("id") Long id, @Param("memberId") Long memberId); + @Query("SELECT new org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto(c,m) " + - "FROM CodePost c INNER JOIN Member m ON c.writerId = m.id ORDER BY c.id DESC") + "FROM CodePost c INNER JOIN Member m ON c.writerId = m.id AND c.isPublic = TRUE ORDER BY c.id DESC") Page findByPage(Pageable pageable); - @Query(value = "SELECT * FROM code_post c WHERE MATCH(c.title) AGAINST(:keyword IN BOOLEAN MODE)", + @Query(value = "SELECT * FROM code_post c WHERE c.is_public = TRUE AND MATCH(c.title) AGAINST(:keyword IN BOOLEAN MODE)", countQuery = "SELECT COUNT(*) FROM code_post c WHERE MATCH(c.title) AGAINST(:keyword IN BOOLEAN MODE)", nativeQuery = true) Page search(@Param("keyword") String keyword, Pageable pageable); diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java index f6d0fba..b0fd9d1 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java +++ b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java @@ -34,8 +34,8 @@ public Long postSave(CodePostSaveRequestDto requestDto, String email){ return codePost.getId(); } - public CodePostResponseDto findPostById(Long id){ - return codePostService.findById(id); + public CodePostResponseDto findPostById(Long id, String email){ + return codePostService.findById(id,email); } public CodePostListResponseDto postSearch(String keyword, Pageable pageable){ diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java index 4ca0ed3..6b0db54 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java +++ b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java @@ -44,11 +44,12 @@ public CodePost findEntityById(Long id) { ); } - public CodePostResponseDto findById(Long id) { - CodePost codePost = codePostRepository.findById(id).orElseThrow( + public CodePostResponseDto findById(Long id,String email) { + Member member = memberCommand.findByEmail(email); + CodePost codePost = codePostRepository.findByIdIsPublic(id,member.getId()).orElseThrow( () -> new NotFoundException(ErrorCode.NOT_FOUND_POST) ); - Member member = memberCommand.findById(codePost.getWriterId()); + List reviews = codePost.getReviewList(); List dtoList = reviews.stream() .map(ReviewResponseDto::new) diff --git a/src/main/resources/db/migration/V5.1__add_is_public_column_to_code_post.sql b/src/main/resources/db/migration/V5.1__add_is_public_column_to_code_post.sql new file mode 100644 index 0000000..47eb43d --- /dev/null +++ b/src/main/resources/db/migration/V5.1__add_is_public_column_to_code_post.sql @@ -0,0 +1,2 @@ +ALTER TABLE code_post +ADD COLUMN is_public TINYINT(1) NOT NULL DEFAULT 1; diff --git a/src/main/resources/db/migration/V5.2__if_ispublic_column_is_null_then_update.sql b/src/main/resources/db/migration/V5.2__if_ispublic_column_is_null_then_update.sql new file mode 100644 index 0000000..bbf854f --- /dev/null +++ b/src/main/resources/db/migration/V5.2__if_ispublic_column_is_null_then_update.sql @@ -0,0 +1 @@ +UPDATE code_post SET is_public = 1 WHERE is_public IS NULL; \ No newline at end of file From a39e9aff2309fcd932eef922fba8378d6dbf0de7 Mon Sep 17 00:00:00 2001 From: ehgur Date: Thu, 8 May 2025 22:40:24 +0900 Subject: [PATCH 04/66] =?UTF-8?q?refactor:=20code=20post=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=8B=9C=20=EC=9D=91=EB=8B=B5=20=EA=B0=92=EC=97=90?= =?UTF-8?q?=20=EB=8C=93=EA=B8=80=20=EB=B0=8F=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EA=B0=9C=EC=88=98=20=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 전체 조회 시에는 댓글 개수만 추가 - 나의 코드 게시글 조회 시 댓글 및 리뷰 개수 추가 --- .../dto/response/CodePostListResponseDto.java | 6 ++-- .../CodePostThumbnailResponseDto.java | 2 ++ .../MyCodePostThumbnailResponseDto.java | 32 +++++++++++++++++++ .../codepost/service/CodePostDtoService.java | 10 +++--- .../codepost/service/CodePostService.java | 25 ++++++++------- 5 files changed, 56 insertions(+), 19 deletions(-) create mode 100644 src/main/java/org/example/autoreview/domain/codepost/dto/response/MyCodePostThumbnailResponseDto.java diff --git a/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostListResponseDto.java b/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostListResponseDto.java index afecb30..1280729 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostListResponseDto.java +++ b/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostListResponseDto.java @@ -4,12 +4,12 @@ import lombok.Getter; @Getter -public class CodePostListResponseDto { +public class CodePostListResponseDto { - private final List dtoList; + private final List dtoList; private final int totalPage; - public CodePostListResponseDto(List dtoList, int totalPage) { + public CodePostListResponseDto(List dtoList, int totalPage) { this.dtoList = dtoList; this.totalPage = totalPage; } diff --git a/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostThumbnailResponseDto.java b/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostThumbnailResponseDto.java index ce16b8b..18afcd3 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostThumbnailResponseDto.java +++ b/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostThumbnailResponseDto.java @@ -14,6 +14,7 @@ public class CodePostThumbnailResponseDto { private final String writerNickName; private final String title; private final int level; + private final int commentCount; private final boolean isPublic; private final String description; private final LocalDateTime createdDate; @@ -25,6 +26,7 @@ public CodePostThumbnailResponseDto(CodePost entity, Member writer) { this.writerNickName = writer.getNickname(); this.title = entity.getTitle(); this.level = entity.getLevel(); + this.commentCount = entity.getCodePostCommentList().size(); this.isPublic = entity.isPublic(); this.description = entity.getDescription(); this.createdDate = entity.getCreateDate(); diff --git a/src/main/java/org/example/autoreview/domain/codepost/dto/response/MyCodePostThumbnailResponseDto.java b/src/main/java/org/example/autoreview/domain/codepost/dto/response/MyCodePostThumbnailResponseDto.java new file mode 100644 index 0000000..a106d1f --- /dev/null +++ b/src/main/java/org/example/autoreview/domain/codepost/dto/response/MyCodePostThumbnailResponseDto.java @@ -0,0 +1,32 @@ +package org.example.autoreview.domain.codepost.dto.response; + +import java.time.LocalDateTime; +import lombok.Getter; +import org.example.autoreview.domain.codepost.entity.CodePost; +import org.example.autoreview.domain.member.entity.Member; + +@Getter +public class MyCodePostThumbnailResponseDto { + + private final Long id; + private final Long writerId; + private final String writerEmail; + private final String writerNickName; + private final String title; + private final int level; + private final int commentCount; + private final int reviewCount; + private final LocalDateTime createdDate; + + public MyCodePostThumbnailResponseDto(CodePost entity, Member writer) { + this.id = entity.getId(); + this.writerId = entity.getWriterId(); + this.writerEmail = writer.getEmail(); + this.writerNickName = writer.getNickname(); + this.title = entity.getTitle(); + this.level = entity.getLevel(); + this.commentCount = entity.getCodePostCommentList().size(); + this.reviewCount = entity.getReviewList().size(); + this.createdDate = entity.getCreateDate(); + } +} diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java index b0fd9d1..ccc1268 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java +++ b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java @@ -6,6 +6,8 @@ import org.example.autoreview.domain.codepost.dto.request.CodePostUpdateRequestDto; import org.example.autoreview.domain.codepost.dto.response.CodePostListResponseDto; import org.example.autoreview.domain.codepost.dto.response.CodePostResponseDto; +import org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto; +import org.example.autoreview.domain.codepost.dto.response.MyCodePostThumbnailResponseDto; import org.example.autoreview.domain.codepost.entity.CodePost; import org.example.autoreview.domain.member.entity.Member; import org.example.autoreview.domain.member.service.MemberService; @@ -38,21 +40,21 @@ public CodePostResponseDto findPostById(Long id, String email){ return codePostService.findById(id,email); } - public CodePostListResponseDto postSearch(String keyword, Pageable pageable){ + public CodePostListResponseDto postSearch(String keyword, Pageable pageable){ return codePostService.search(keyword, pageable); } - public CodePostListResponseDto postMySearch(String keyword, Pageable pageable, String email){ + public CodePostListResponseDto postMySearch(String keyword, Pageable pageable, String email){ Member member = memberService.findByEmail(email); return codePostService.mySearch(keyword, pageable, member); } - public CodePostListResponseDto findPostByMemberId(Pageable pageable, String email){ + public CodePostListResponseDto findPostByMemberId(Pageable pageable, String email){ Member member = memberService.findByEmail(email); return codePostService.findByMemberId(pageable, member); } - public CodePostListResponseDto findPostByPage(Pageable pageable){ + public CodePostListResponseDto findPostByPage(Pageable pageable){ return codePostService.findByPage(pageable); } diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java index 6b0db54..3527446 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java +++ b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java @@ -9,6 +9,7 @@ import org.example.autoreview.domain.codepost.dto.response.CodePostListResponseDto; import org.example.autoreview.domain.codepost.dto.response.CodePostResponseDto; import org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto; +import org.example.autoreview.domain.codepost.dto.response.MyCodePostThumbnailResponseDto; import org.example.autoreview.domain.codepost.entity.CodePost; import org.example.autoreview.domain.codepost.entity.CodePostRepository; import org.example.autoreview.domain.member.entity.Member; @@ -58,7 +59,7 @@ public CodePostResponseDto findById(Long id,String email) { return new CodePostResponseDto(codePost, dtoList, member); } - public CodePostListResponseDto search(String keyword, Pageable pageable) { + public CodePostListResponseDto search(String keyword, Pageable pageable) { keywordValidator(keyword); String wildcardKeyword = keyword + "*"; Page codePostPage = codePostRepository.search(wildcardKeyword, pageable) @@ -67,18 +68,18 @@ public CodePostListResponseDto search(String keyword, Pageable pageable) { return new CodePostThumbnailResponseDto(post, member); }); - return new CodePostListResponseDto(codePostPage.getContent(), codePostPage.getTotalPages()); + return new CodePostListResponseDto<>(codePostPage.getContent(), codePostPage.getTotalPages()); } - public CodePostListResponseDto findByMemberId(Pageable pageable, Member member) { + public CodePostListResponseDto findByMemberId(Pageable pageable, Member member) { Page codePostPage = codePostRepository.findByMemberId(member.getId(), pageable); - return new CodePostListResponseDto(convertListDto(codePostPage,member), codePostPage.getTotalPages()); + return new CodePostListResponseDto<>(convertMyListDto(codePostPage,member), codePostPage.getTotalPages()); } - public CodePostListResponseDto mySearch(String keyword, Pageable pageable, Member member) { + public CodePostListResponseDto mySearch(String keyword, Pageable pageable, Member member) { keywordValidator(keyword); Page codePostPage = codePostRepository.mySearch(keyword, pageable, member.getId()); - return new CodePostListResponseDto(convertListDto(codePostPage,member), codePostPage.getTotalPages()); + return new CodePostListResponseDto<>(convertMyListDto(codePostPage,member), codePostPage.getTotalPages()); } private static void keywordValidator(String keyword) { @@ -87,20 +88,20 @@ private static void keywordValidator(String keyword) { } } - private List convertListDto(Page page, Member member) { + private List convertMyListDto(Page page, Member member) { return page.stream() - .map(post -> getCodePostThumbnailResponseDto(post,member)) + .map(post -> getMyCodePostThumbnailResponseDto(post,member)) .collect(Collectors.toList()); } - private CodePostThumbnailResponseDto getCodePostThumbnailResponseDto(CodePost codePost, Member member) { - return new CodePostThumbnailResponseDto(codePost, member); + private MyCodePostThumbnailResponseDto getMyCodePostThumbnailResponseDto(CodePost codePost, Member member) { + return new MyCodePostThumbnailResponseDto(codePost, member); } - public CodePostListResponseDto findByPage(Pageable pageable) { + public CodePostListResponseDto findByPage(Pageable pageable) { Page page = codePostRepository.findByPage(pageable); - return new CodePostListResponseDto(page.getContent(), page.getTotalPages()); + return new CodePostListResponseDto<>(page.getContent(), page.getTotalPages()); } @Transactional From 62d59b17f75e53a2d5aacf8638c52751d17d861e Mon Sep 17 00:00:00 2001 From: ehgur Date: Fri, 9 May 2025 15:46:33 +0900 Subject: [PATCH 05/66] =?UTF-8?q?fix:=20=EB=8B=A8=EC=9D=BC=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=8B=9C=20=EA=B2=B0=EA=B3=BC=EA=B0=92=EC=9D=B4=20?= =?UTF-8?q?1=EA=B0=9C=20=EC=9D=B4=EC=83=81=EC=9D=B8=20=EB=AC=B8=EC=A0=9C?= =?UTF-8?q?=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 쿼리문에서 and와 or의 순서가 잘못됨 괄호를 통해 해결 --- .../domain/codepost/dto/request/CodePostUpdateRequestDto.java | 2 +- .../autoreview/domain/codepost/entity/CodePostRepository.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostUpdateRequestDto.java b/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostUpdateRequestDto.java index e5b98fb..d79a126 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostUpdateRequestDto.java +++ b/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostUpdateRequestDto.java @@ -27,7 +27,7 @@ public class CodePostUpdateRequestDto { @Schema(description = "복습일 설정", example = "2024-10-11") private final LocalDate reviewDay; - @Schema(description = "사용 언어", example = "c++") + @Schema(description = "사용 언어", example = "cpp") private final String language; @Schema(description = "코드", example = "import test") diff --git a/src/main/java/org/example/autoreview/domain/codepost/entity/CodePostRepository.java b/src/main/java/org/example/autoreview/domain/codepost/entity/CodePostRepository.java index 1148928..edfd8b6 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/entity/CodePostRepository.java +++ b/src/main/java/org/example/autoreview/domain/codepost/entity/CodePostRepository.java @@ -10,7 +10,7 @@ public interface CodePostRepository extends JpaRepository { - @Query("SELECT c FROM CodePost c WHERE c.id = :id AND c.writerId = :memberId OR c.isPublic = TRUE") + @Query("SELECT c FROM CodePost c WHERE c.id = :id AND (c.writerId = :memberId OR c.isPublic = TRUE)") Optional findByIdIsPublic(@Param("id") Long id, @Param("memberId") Long memberId); @Query("SELECT new org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto(c,m) " + From 7ace3917b48d6a6b63396fbbfa32fbe390d8608e Mon Sep 17 00:00:00 2001 From: ArcticFoox Date: Fri, 9 May 2025 22:14:44 +0900 Subject: [PATCH 06/66] fix: change file path --- security_setting | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security_setting b/security_setting index 926fbf3..6d21618 160000 --- a/security_setting +++ b/security_setting @@ -1 +1 @@ -Subproject commit 926fbf3b591cbf44470e27b95d0508dccbed0d87 +Subproject commit 6d2161852cede6700a8209b656ecf77e530249b4 From 54caf53334946d9e124b34df469ee92365c35821 Mon Sep 17 00:00:00 2001 From: ehgur Date: Sat, 10 May 2025 02:36:46 +0900 Subject: [PATCH 07/66] =?UTF-8?q?feat:=20code=20post=20=EB=B6=81=EB=A7=88?= =?UTF-8?q?=ED=81=AC=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CodePostBookmarkController.java | 50 +++++++++++++++++++ .../CodePostBookmarkSaveRequestDto.java | 19 +++++++ .../CodePostBookmarkListResponseDto.java | 9 ++++ .../response/CodePostBookmarkResponseDto.java | 25 ++++++++++ .../entity/CodePostBookmark.java | 49 ++++++++++++++++++ .../entity/CodePostBookmarkRepository.java | 24 +++++++++ .../service/CodePostBookmarkService.java | 48 ++++++++++++++++++ .../domain/member/entity/Member.java | 4 ++ .../V6.1__create_code_post_bookmark_table.sql | 11 ++++ 9 files changed, 239 insertions(+) create mode 100644 src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/controller/CodePostBookmarkController.java create mode 100644 src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/request/CodePostBookmarkSaveRequestDto.java create mode 100644 src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/response/CodePostBookmarkListResponseDto.java create mode 100644 src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/response/CodePostBookmarkResponseDto.java create mode 100644 src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmark.java create mode 100644 src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java create mode 100644 src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java create mode 100644 src/main/resources/db/migration/V6.1__create_code_post_bookmark_table.sql diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/controller/CodePostBookmarkController.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/controller/CodePostBookmarkController.java new file mode 100644 index 0000000..366f312 --- /dev/null +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/controller/CodePostBookmarkController.java @@ -0,0 +1,50 @@ +package org.example.autoreview.domain.bookmark.CodePostBookmark.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.request.CodePostBookmarkSaveRequestDto; +import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkListResponseDto; +import org.example.autoreview.domain.bookmark.CodePostBookmark.service.CodePostBookmarkService; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "Code Post Bookmark API", description = "Code Post Bookmark API") +@RequiredArgsConstructor +@RestController +@RequestMapping("/v1/api/code-post/bookmark") +public class CodePostBookmarkController { + + private final CodePostBookmarkService codePostBookmarkService; + + @Operation(summary = "북마크 저장 또는 수정", description = "soft delete 되었다면 수정, 존재하지 않는다면 저장") + @PutMapping + public ResponseEntity saveOrUpdate(@RequestBody CodePostBookmarkSaveRequestDto requestDto, + @AuthenticationPrincipal UserDetails userDetails) { + return ResponseEntity.ok().body(codePostBookmarkService.saveOrUpdate(requestDto, userDetails.getUsername())); + } + + @Operation(summary = "북마크 전체 조회") + @GetMapping("/list") + public ResponseEntity findAll(@AuthenticationPrincipal UserDetails userDetails, + @PageableDefault(page = 0, size = 10) Pageable pageable) { + return ResponseEntity.ok().body(codePostBookmarkService.findAllByMemberId(userDetails.getUsername(), pageable)); + } + + @Operation(summary = "북마크 삭제") + @DeleteMapping + public ResponseEntity deleteExpiredSoftDeletedBookmarks() { + codePostBookmarkService.deleteExpiredSoftDeletedBookmarks(); + return ResponseEntity.ok().body("Success Hard Delete"); + } + +} diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/request/CodePostBookmarkSaveRequestDto.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/request/CodePostBookmarkSaveRequestDto.java new file mode 100644 index 0000000..379fb6e --- /dev/null +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/request/CodePostBookmarkSaveRequestDto.java @@ -0,0 +1,19 @@ +package org.example.autoreview.domain.bookmark.CodePostBookmark.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmark; +import org.example.autoreview.domain.member.entity.Member; + +public record CodePostBookmarkSaveRequestDto( + + @Schema(description = "코드 포스트 아이디", example = "1") + Long codePostId +) { + public CodePostBookmark toEntity(Member member){ + return CodePostBookmark.builder() + .codePostId(codePostId) + .member(member) + .isDeleted(false) + .build(); + } +} diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/response/CodePostBookmarkListResponseDto.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/response/CodePostBookmarkListResponseDto.java new file mode 100644 index 0000000..8764cf0 --- /dev/null +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/response/CodePostBookmarkListResponseDto.java @@ -0,0 +1,9 @@ +package org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response; + +import java.util.List; + +public record CodePostBookmarkListResponseDto( + List listDto, + int totalPage +) { +} diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/response/CodePostBookmarkResponseDto.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/response/CodePostBookmarkResponseDto.java new file mode 100644 index 0000000..bbdf2fc --- /dev/null +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/response/CodePostBookmarkResponseDto.java @@ -0,0 +1,25 @@ +package org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response; + +import java.time.LocalDateTime; +import lombok.Getter; +import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmark; +import org.example.autoreview.domain.codepost.entity.CodePost; +import org.example.autoreview.domain.member.entity.Member; + +@Getter +public class CodePostBookmarkResponseDto { + + private final Long id; + private final String codePostTitle; + private final int commentCount; + private final String writer; + private final LocalDateTime updateAt; + + public CodePostBookmarkResponseDto(CodePostBookmark bookmark, CodePost codePost, Member member) { + this.id = bookmark.getId(); + this.codePostTitle = codePost.getTitle(); + this.commentCount = codePost.getCodePostCommentList().size(); + this.writer = member.getNickname(); + this.updateAt = bookmark.getUpdateDate(); + } +} diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmark.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmark.java new file mode 100644 index 0000000..bec4553 --- /dev/null +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmark.java @@ -0,0 +1,49 @@ +package org.example.autoreview.domain.bookmark.CodePostBookmark.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.example.autoreview.domain.member.entity.Member; +import org.example.autoreview.global.common.basetime.BaseEntity; +import org.hibernate.annotations.ColumnDefault; + +@Getter +@NoArgsConstructor +@Entity +public class CodePostBookmark extends BaseEntity { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn + private Member member; + + @Column(nullable = false) + private Long codePostId; + + @ColumnDefault("false") + @Column(nullable = false) + private boolean isDeleted; + + @Builder + public CodePostBookmark(Long codePostId, Member member, boolean isDeleted) { + this.codePostId = codePostId; + this.member = member; + this.isDeleted = isDeleted; + } + + public Long update() { + this.isDeleted = !this.isDeleted; + return this.id; + } + +} diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java new file mode 100644 index 0000000..543c555 --- /dev/null +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java @@ -0,0 +1,24 @@ +package org.example.autoreview.domain.bookmark.CodePostBookmark.entity; + +import io.lettuce.core.dynamic.annotation.Param; +import java.util.Optional; +import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkResponseDto; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; + +public interface CodePostBookmarkRepository extends JpaRepository { + + @Query("SELECT b FROM CodePostBookmark b WHERE b.member.id = :memberId AND b.codePostId = :codePostId") + Optional findById(@Param("memberId") Long memberId, @Param("codePostId") Long codePostId); + + @Query("SELECT new org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkResponseDto(b,c,m) from CodePostBookmark b " + + "JOIN CodePost c ON b.codePostId = c.id JOIN Member m ON m.id = :memberId WHERE b.isDeleted = FALSE") + Page findAllByMemberId(@Param("memberId") Long memberId, Pageable pageable); + + @Modifying + @Query("DELETE FROM CodePostBookmark b WHERE b.isDeleted = TRUE") + void deleteExpiredSoftDeletedBookmarks(); +} diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java new file mode 100644 index 0000000..8096348 --- /dev/null +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java @@ -0,0 +1,48 @@ +package org.example.autoreview.domain.bookmark.CodePostBookmark.service; + +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.request.CodePostBookmarkSaveRequestDto; +import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkListResponseDto; +import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkResponseDto; +import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmark; +import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmarkRepository; +import org.example.autoreview.domain.member.entity.Member; +import org.example.autoreview.domain.member.service.MemberCommand; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@RequiredArgsConstructor +@Service +@Transactional(readOnly = false) +public class CodePostBookmarkService { + + private final CodePostBookmarkRepository codePostBookmarkRepository; + private final MemberCommand memberCommand; + + public Long saveOrUpdate(CodePostBookmarkSaveRequestDto requestDto, String email) { + Member member = memberCommand.findByEmail(email); + Optional existingBookmark = + codePostBookmarkRepository.findById(requestDto.codePostId(), member.getId()); + + return existingBookmark + .map(CodePostBookmark::update) + .orElseGet(() -> codePostBookmarkRepository.save(requestDto.toEntity(member)).getId()); + } + + @Transactional(readOnly = true) + public CodePostBookmarkListResponseDto findAllByMemberId(String email, Pageable pageable) { + Long memberId = memberCommand.findByEmail(email).getId(); + Page pageDto = codePostBookmarkRepository.findAllByMemberId(memberId,pageable); + + return new CodePostBookmarkListResponseDto(pageDto.getContent(),pageDto.getTotalPages()); + } + + public void deleteExpiredSoftDeletedBookmarks() { + codePostBookmarkRepository.deleteExpiredSoftDeletedBookmarks(); + } +} diff --git a/src/main/java/org/example/autoreview/domain/member/entity/Member.java b/src/main/java/org/example/autoreview/domain/member/entity/Member.java index 72a2edc..4ad4212 100644 --- a/src/main/java/org/example/autoreview/domain/member/entity/Member.java +++ b/src/main/java/org/example/autoreview/domain/member/entity/Member.java @@ -13,6 +13,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmark; import org.example.autoreview.domain.fcm.entity.FcmToken; import org.example.autoreview.domain.notification.entity.Notification; import org.example.autoreview.global.common.basetime.BaseEntity; @@ -29,6 +30,9 @@ public class Member extends BaseEntity { @Column(nullable = false, unique = true) private String email; + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) + private List codePostBookmarks = new ArrayList<>(); + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) private List fcmTokens = new ArrayList<>(); diff --git a/src/main/resources/db/migration/V6.1__create_code_post_bookmark_table.sql b/src/main/resources/db/migration/V6.1__create_code_post_bookmark_table.sql new file mode 100644 index 0000000..f6b0d4c --- /dev/null +++ b/src/main/resources/db/migration/V6.1__create_code_post_bookmark_table.sql @@ -0,0 +1,11 @@ +create table code_post_bookmark ( + id bigint not null auto_increment, + code_post_id bigint not null, + is_deleted bit not null default false, + member_id bigint, + create_date datetime(6) not null, + update_date datetime(6) not null, + primary key (id), + constraint fk_code_post_bookmark_member + foreign key (member_id) references member (id) +); From 8cf061fcbfe17bfd68d0f9232ab7d2bb016533b3 Mon Sep 17 00:00:00 2001 From: ehgur Date: Sat, 10 May 2025 03:01:52 +0900 Subject: [PATCH 08/66] =?UTF-8?q?refactor:=20=EC=9D=91=EB=8B=B5=EA=B0=92?= =?UTF-8?q?=EC=97=90=20code=20post=20id=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/CodePostBookmarkResponseDto.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/response/CodePostBookmarkResponseDto.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/response/CodePostBookmarkResponseDto.java index bbdf2fc..3f1fb52 100644 --- a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/response/CodePostBookmarkResponseDto.java +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/response/CodePostBookmarkResponseDto.java @@ -10,6 +10,7 @@ public class CodePostBookmarkResponseDto { private final Long id; + private final Long codePostId; private final String codePostTitle; private final int commentCount; private final String writer; @@ -17,6 +18,7 @@ public class CodePostBookmarkResponseDto { public CodePostBookmarkResponseDto(CodePostBookmark bookmark, CodePost codePost, Member member) { this.id = bookmark.getId(); + this.codePostId = codePost.getId(); this.codePostTitle = codePost.getTitle(); this.commentCount = codePost.getCodePostCommentList().size(); this.writer = member.getNickname(); From 23af3c364d1ce6324efdff49cd11ee2aeea05c14 Mon Sep 17 00:00:00 2001 From: ehgur Date: Sun, 11 May 2025 19:38:12 +0900 Subject: [PATCH 09/66] =?UTF-8?q?refactor:=20code=20post=20=EC=84=AC?= =?UTF-8?q?=EB=84=A4=EC=9D=BC=20response=20dto=20=ED=86=B5=EC=9D=BC=20?= =?UTF-8?q?=EB=B0=8F=20description=20=EC=9A=94=EC=95=BD=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/CodePostListResponseDto.java | 6 ++-- .../dto/response/CodePostResponseDto.java | 6 ++-- .../CodePostThumbnailResponseDto.java | 11 ++++++- .../MyCodePostThumbnailResponseDto.java | 32 ------------------- .../codepost/service/CodePostDtoService.java | 10 +++--- .../codepost/service/CodePostService.java | 28 ++++++++-------- 6 files changed, 34 insertions(+), 59 deletions(-) delete mode 100644 src/main/java/org/example/autoreview/domain/codepost/dto/response/MyCodePostThumbnailResponseDto.java diff --git a/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostListResponseDto.java b/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostListResponseDto.java index 1280729..afecb30 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostListResponseDto.java +++ b/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostListResponseDto.java @@ -4,12 +4,12 @@ import lombok.Getter; @Getter -public class CodePostListResponseDto { +public class CodePostListResponseDto { - private final List dtoList; + private final List dtoList; private final int totalPage; - public CodePostListResponseDto(List dtoList, int totalPage) { + public CodePostListResponseDto(List dtoList, int totalPage) { this.dtoList = dtoList; this.totalPage = totalPage; } diff --git a/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostResponseDto.java b/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostResponseDto.java index 5b0cb7d..496861a 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostResponseDto.java +++ b/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostResponseDto.java @@ -24,11 +24,11 @@ public class CodePostResponseDto { private final List dtoList; private final LocalDateTime createDate; - public CodePostResponseDto(CodePost entity, List dtoList, Member member) { + public CodePostResponseDto(CodePost entity, List dtoList, Member writer) { this.id = entity.getId(); this.writerId = entity.getWriterId(); - this.writerEmail = member.getEmail(); - this.writerNickName = member.getNickname(); + this.writerEmail = writer.getEmail(); + this.writerNickName = writer.getNickname(); this.title = entity.getTitle(); this.level = entity.getLevel(); this.reviewDay = entity.getReviewDay(); diff --git a/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostThumbnailResponseDto.java b/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostThumbnailResponseDto.java index 18afcd3..ef5e4f1 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostThumbnailResponseDto.java +++ b/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostThumbnailResponseDto.java @@ -15,6 +15,7 @@ public class CodePostThumbnailResponseDto { private final String title; private final int level; private final int commentCount; + private final int reviewCount; private final boolean isPublic; private final String description; private final LocalDateTime createdDate; @@ -27,8 +28,16 @@ public CodePostThumbnailResponseDto(CodePost entity, Member writer) { this.title = entity.getTitle(); this.level = entity.getLevel(); this.commentCount = entity.getCodePostCommentList().size(); + this.reviewCount = entity.getReviewList().size(); this.isPublic = entity.isPublic(); - this.description = entity.getDescription(); + this.description = summarize(entity.getDescription(),100); this.createdDate = entity.getCreateDate(); } + + private String summarize(String text, int maxLength) { + if (text == null) { + return ""; + } + return text.length() <= maxLength ? text : text.substring(0, maxLength) + "..."; + } } diff --git a/src/main/java/org/example/autoreview/domain/codepost/dto/response/MyCodePostThumbnailResponseDto.java b/src/main/java/org/example/autoreview/domain/codepost/dto/response/MyCodePostThumbnailResponseDto.java deleted file mode 100644 index a106d1f..0000000 --- a/src/main/java/org/example/autoreview/domain/codepost/dto/response/MyCodePostThumbnailResponseDto.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.example.autoreview.domain.codepost.dto.response; - -import java.time.LocalDateTime; -import lombok.Getter; -import org.example.autoreview.domain.codepost.entity.CodePost; -import org.example.autoreview.domain.member.entity.Member; - -@Getter -public class MyCodePostThumbnailResponseDto { - - private final Long id; - private final Long writerId; - private final String writerEmail; - private final String writerNickName; - private final String title; - private final int level; - private final int commentCount; - private final int reviewCount; - private final LocalDateTime createdDate; - - public MyCodePostThumbnailResponseDto(CodePost entity, Member writer) { - this.id = entity.getId(); - this.writerId = entity.getWriterId(); - this.writerEmail = writer.getEmail(); - this.writerNickName = writer.getNickname(); - this.title = entity.getTitle(); - this.level = entity.getLevel(); - this.commentCount = entity.getCodePostCommentList().size(); - this.reviewCount = entity.getReviewList().size(); - this.createdDate = entity.getCreateDate(); - } -} diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java index ccc1268..b0fd9d1 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java +++ b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java @@ -6,8 +6,6 @@ import org.example.autoreview.domain.codepost.dto.request.CodePostUpdateRequestDto; import org.example.autoreview.domain.codepost.dto.response.CodePostListResponseDto; import org.example.autoreview.domain.codepost.dto.response.CodePostResponseDto; -import org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto; -import org.example.autoreview.domain.codepost.dto.response.MyCodePostThumbnailResponseDto; import org.example.autoreview.domain.codepost.entity.CodePost; import org.example.autoreview.domain.member.entity.Member; import org.example.autoreview.domain.member.service.MemberService; @@ -40,21 +38,21 @@ public CodePostResponseDto findPostById(Long id, String email){ return codePostService.findById(id,email); } - public CodePostListResponseDto postSearch(String keyword, Pageable pageable){ + public CodePostListResponseDto postSearch(String keyword, Pageable pageable){ return codePostService.search(keyword, pageable); } - public CodePostListResponseDto postMySearch(String keyword, Pageable pageable, String email){ + public CodePostListResponseDto postMySearch(String keyword, Pageable pageable, String email){ Member member = memberService.findByEmail(email); return codePostService.mySearch(keyword, pageable, member); } - public CodePostListResponseDto findPostByMemberId(Pageable pageable, String email){ + public CodePostListResponseDto findPostByMemberId(Pageable pageable, String email){ Member member = memberService.findByEmail(email); return codePostService.findByMemberId(pageable, member); } - public CodePostListResponseDto findPostByPage(Pageable pageable){ + public CodePostListResponseDto findPostByPage(Pageable pageable){ return codePostService.findByPage(pageable); } diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java index 3527446..32d4764 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java +++ b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java @@ -9,7 +9,6 @@ import org.example.autoreview.domain.codepost.dto.response.CodePostListResponseDto; import org.example.autoreview.domain.codepost.dto.response.CodePostResponseDto; import org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto; -import org.example.autoreview.domain.codepost.dto.response.MyCodePostThumbnailResponseDto; import org.example.autoreview.domain.codepost.entity.CodePost; import org.example.autoreview.domain.codepost.entity.CodePostRepository; import org.example.autoreview.domain.member.entity.Member; @@ -50,16 +49,17 @@ public CodePostResponseDto findById(Long id,String email) { CodePost codePost = codePostRepository.findByIdIsPublic(id,member.getId()).orElseThrow( () -> new NotFoundException(ErrorCode.NOT_FOUND_POST) ); + Member writer = memberCommand.findById(codePost.getWriterId()); List reviews = codePost.getReviewList(); List dtoList = reviews.stream() .map(ReviewResponseDto::new) .toList(); - return new CodePostResponseDto(codePost, dtoList, member); + return new CodePostResponseDto(codePost, dtoList, writer); } - public CodePostListResponseDto search(String keyword, Pageable pageable) { + public CodePostListResponseDto search(String keyword, Pageable pageable) { keywordValidator(keyword); String wildcardKeyword = keyword + "*"; Page codePostPage = codePostRepository.search(wildcardKeyword, pageable) @@ -68,18 +68,18 @@ public CodePostListResponseDto search(String keywo return new CodePostThumbnailResponseDto(post, member); }); - return new CodePostListResponseDto<>(codePostPage.getContent(), codePostPage.getTotalPages()); + return new CodePostListResponseDto(codePostPage.getContent(), codePostPage.getTotalPages()); } - public CodePostListResponseDto findByMemberId(Pageable pageable, Member member) { + public CodePostListResponseDto findByMemberId(Pageable pageable, Member member) { Page codePostPage = codePostRepository.findByMemberId(member.getId(), pageable); - return new CodePostListResponseDto<>(convertMyListDto(codePostPage,member), codePostPage.getTotalPages()); + return new CodePostListResponseDto(convertListDto(codePostPage,member), codePostPage.getTotalPages()); } - public CodePostListResponseDto mySearch(String keyword, Pageable pageable, Member member) { + public CodePostListResponseDto mySearch(String keyword, Pageable pageable, Member member) { keywordValidator(keyword); Page codePostPage = codePostRepository.mySearch(keyword, pageable, member.getId()); - return new CodePostListResponseDto<>(convertMyListDto(codePostPage,member), codePostPage.getTotalPages()); + return new CodePostListResponseDto(convertListDto(codePostPage,member), codePostPage.getTotalPages()); } private static void keywordValidator(String keyword) { @@ -88,20 +88,20 @@ private static void keywordValidator(String keyword) { } } - private List convertMyListDto(Page page, Member member) { + private List convertListDto(Page page, Member member) { return page.stream() - .map(post -> getMyCodePostThumbnailResponseDto(post,member)) + .map(post -> getCodePostThumbnailResponseDto(post,member)) .collect(Collectors.toList()); } - private MyCodePostThumbnailResponseDto getMyCodePostThumbnailResponseDto(CodePost codePost, Member member) { - return new MyCodePostThumbnailResponseDto(codePost, member); + private CodePostThumbnailResponseDto getCodePostThumbnailResponseDto(CodePost codePost, Member member) { + return new CodePostThumbnailResponseDto(codePost, member); } - public CodePostListResponseDto findByPage(Pageable pageable) { + public CodePostListResponseDto findByPage(Pageable pageable) { Page page = codePostRepository.findByPage(pageable); - return new CodePostListResponseDto<>(page.getContent(), page.getTotalPages()); + return new CodePostListResponseDto(page.getContent(), page.getTotalPages()); } @Transactional From 8be236f7ed59e753418b1f8280087b717e465220 Mon Sep 17 00:00:00 2001 From: ehgur Date: Thu, 15 May 2025 01:03:47 +0900 Subject: [PATCH 10/66] =?UTF-8?q?feat:=20github=20=EC=97=B0=EB=8F=99=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../github/controller/GithubController.java | 37 ++++++++++ .../dto/request/GithubCodeRequestDto.java | 10 +++ .../dto/request/GithubTokenRequestDto.java | 20 +++++ .../domain/github/entity/GithubToken.java | 37 ++++++++++ .../github/entity/GithubTokenRepository.java | 12 +++ .../domain/github/service/GithubService.java | 73 +++++++++++++++++++ .../global/config/WebClientConfig.java | 14 ++++ .../global/exception/errorcode/ErrorCode.java | 5 +- 9 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/example/autoreview/domain/github/controller/GithubController.java create mode 100644 src/main/java/org/example/autoreview/domain/github/dto/request/GithubCodeRequestDto.java create mode 100644 src/main/java/org/example/autoreview/domain/github/dto/request/GithubTokenRequestDto.java create mode 100644 src/main/java/org/example/autoreview/domain/github/entity/GithubToken.java create mode 100644 src/main/java/org/example/autoreview/domain/github/entity/GithubTokenRepository.java create mode 100644 src/main/java/org/example/autoreview/domain/github/service/GithubService.java create mode 100644 src/main/java/org/example/autoreview/global/config/WebClientConfig.java diff --git a/build.gradle b/build.gradle index a856e84..3d92b50 100644 --- a/build.gradle +++ b/build.gradle @@ -29,6 +29,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.springframework.boot:spring-boot-starter-data-redis' diff --git a/src/main/java/org/example/autoreview/domain/github/controller/GithubController.java b/src/main/java/org/example/autoreview/domain/github/controller/GithubController.java new file mode 100644 index 0000000..a6d19bc --- /dev/null +++ b/src/main/java/org/example/autoreview/domain/github/controller/GithubController.java @@ -0,0 +1,37 @@ +package org.example.autoreview.domain.github.controller; + +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.example.autoreview.domain.github.dto.request.GithubCodeRequestDto; +import org.example.autoreview.domain.github.service.GithubService; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.GetMapping; +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; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/v1/api/github") +public class GithubController { + + private final GithubService githubService; + + @Operation(description = "깃헙 인증 토큰이 있는지 확인") + @GetMapping("/token/check") + public ResponseEntity checkToken(@AuthenticationPrincipal UserDetails userDetails) { + return ResponseEntity.ok().body(githubService.tokenCheck(userDetails.getUsername())); + } + + @Operation(description = "깃헙 리다이렉트 된 일회성 인증 코드로 인증 토큰 발급 후 저장") + @PostMapping("/callback") + public ResponseEntity callbackAndSave(@RequestBody GithubCodeRequestDto requestDto, + @AuthenticationPrincipal UserDetails userDetails) { + String accessToken = githubService.getAccessToken(requestDto); + return ResponseEntity.ok().body(githubService.save(accessToken, userDetails.getUsername())); + } + +} diff --git a/src/main/java/org/example/autoreview/domain/github/dto/request/GithubCodeRequestDto.java b/src/main/java/org/example/autoreview/domain/github/dto/request/GithubCodeRequestDto.java new file mode 100644 index 0000000..e147aed --- /dev/null +++ b/src/main/java/org/example/autoreview/domain/github/dto/request/GithubCodeRequestDto.java @@ -0,0 +1,10 @@ +package org.example.autoreview.domain.github.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record GithubCodeRequestDto( + + @Schema(description = "리다이렉트로 온 일회성 코드") + String code +) { +} diff --git a/src/main/java/org/example/autoreview/domain/github/dto/request/GithubTokenRequestDto.java b/src/main/java/org/example/autoreview/domain/github/dto/request/GithubTokenRequestDto.java new file mode 100644 index 0000000..4fe5236 --- /dev/null +++ b/src/main/java/org/example/autoreview/domain/github/dto/request/GithubTokenRequestDto.java @@ -0,0 +1,20 @@ +package org.example.autoreview.domain.github.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import org.example.autoreview.domain.github.entity.GithubToken; + +public record GithubTokenRequestDto( + + @Schema(description = "인증 코드") + String githubToken, + + @Schema(description = "사용자 이메일", example = "abc@gmail.com") + String email +) { + public GithubToken toEntity(){ + return GithubToken.builder() + .githubToken(githubToken) + .email(email) + .build(); + } +} diff --git a/src/main/java/org/example/autoreview/domain/github/entity/GithubToken.java b/src/main/java/org/example/autoreview/domain/github/entity/GithubToken.java new file mode 100644 index 0000000..eeec429 --- /dev/null +++ b/src/main/java/org/example/autoreview/domain/github/entity/GithubToken.java @@ -0,0 +1,37 @@ +package org.example.autoreview.domain.github.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.example.autoreview.global.common.basetime.BaseEntity; + +@Getter +@NoArgsConstructor +@Entity +public class GithubToken extends BaseEntity { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String email; + + private String githubToken; + + @Builder + public GithubToken(String email, String githubToken) { + this.email = email; + this.githubToken = githubToken; + } + + public Long update(String githubToken) { + this.githubToken = githubToken; + return this.id; + } + +} diff --git a/src/main/java/org/example/autoreview/domain/github/entity/GithubTokenRepository.java b/src/main/java/org/example/autoreview/domain/github/entity/GithubTokenRepository.java new file mode 100644 index 0000000..cfeb29d --- /dev/null +++ b/src/main/java/org/example/autoreview/domain/github/entity/GithubTokenRepository.java @@ -0,0 +1,12 @@ +package org.example.autoreview.domain.github.entity; + +import io.lettuce.core.dynamic.annotation.Param; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GithubTokenRepository extends JpaRepository { + + Optional findByEmail(@Param("email") String email); + + boolean existsByEmail(@Param("email") String email); +} diff --git a/src/main/java/org/example/autoreview/domain/github/service/GithubService.java b/src/main/java/org/example/autoreview/domain/github/service/GithubService.java new file mode 100644 index 0000000..1884e54 --- /dev/null +++ b/src/main/java/org/example/autoreview/domain/github/service/GithubService.java @@ -0,0 +1,73 @@ +package org.example.autoreview.domain.github.service; + +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.example.autoreview.domain.github.dto.request.GithubCodeRequestDto; +import org.example.autoreview.domain.github.dto.request.GithubTokenRequestDto; +import org.example.autoreview.domain.github.entity.GithubToken; +import org.example.autoreview.domain.github.entity.GithubTokenRepository; +import org.example.autoreview.global.exception.errorcode.ErrorCode; +import org.example.autoreview.global.exception.sub_exceptions.NotFoundException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.reactive.function.client.WebClient; + +@Slf4j +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class GithubService { + + @Value("${github.client-id}") + private String clientId; + + @Value("${github.client-secret}") + private String clientSecret; + + private final GithubTokenRepository githubTokenRepository; + private final WebClient webClient; + + @Transactional + public Long save(String accessToken, String email) { + GithubToken githubToken = GithubToken.builder() + .githubToken(accessToken) + .email(email) + .build(); + + return githubTokenRepository.save(githubToken).getId(); + } + + @Transactional + public Long update(GithubTokenRequestDto requestDto) { + GithubToken githubToken = githubTokenRepository.findByEmail(requestDto.email()).orElseThrow( + () -> new NotFoundException(ErrorCode.NOT_FOUND_GITHUB_TOKEN) + ); + return githubToken.update(requestDto.githubToken()); + } + + public boolean tokenCheck(String email) { + return githubTokenRepository.existsByEmail(email); + } + + public String getAccessToken(GithubCodeRequestDto requestDto) { + Map body = Map.of( + "client_id", clientId, + "client_secret", clientSecret, + "code", requestDto.code() + ); + + Map response = webClient.post() + .uri("https://github.com/login/oauth/access_token") + .header(HttpHeaders.ACCEPT, "application/json") + .bodyValue(body) + .retrieve() + .bodyToMono(Map.class) + .block(); + + return (String) response.get("access_token"); + + } +} diff --git a/src/main/java/org/example/autoreview/global/config/WebClientConfig.java b/src/main/java/org/example/autoreview/global/config/WebClientConfig.java new file mode 100644 index 0000000..67bede0 --- /dev/null +++ b/src/main/java/org/example/autoreview/global/config/WebClientConfig.java @@ -0,0 +1,14 @@ +package org.example.autoreview.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.client.WebClient; + +@Configuration +public class WebClientConfig { + + @Bean + public WebClient webClient(WebClient.Builder builder) { + return builder.build(); + } +} diff --git a/src/main/java/org/example/autoreview/global/exception/errorcode/ErrorCode.java b/src/main/java/org/example/autoreview/global/exception/errorcode/ErrorCode.java index faa6ed6..154c759 100644 --- a/src/main/java/org/example/autoreview/global/exception/errorcode/ErrorCode.java +++ b/src/main/java/org/example/autoreview/global/exception/errorcode/ErrorCode.java @@ -48,7 +48,10 @@ public enum ErrorCode { NOT_FOUND_BOOKMARK(404, HttpStatus.NOT_FOUND, "해당 북마크를 찾을 수 없습니다."), // Comment - NOT_FOUND_COMMENT(404, HttpStatus.NOT_FOUND, "해당 댓글을 찾을 수 없습니다.") + NOT_FOUND_COMMENT(404, HttpStatus.NOT_FOUND, "해당 댓글을 찾을 수 없습니다."), + + // GitHub Token + NOT_FOUND_GITHUB_TOKEN(404, HttpStatus.NOT_FOUND, "해당 토큰을 찾을 수 없습니다.") ; From db30b342574f1cf1d4ae6f946f7e10512b3ab028 Mon Sep 17 00:00:00 2001 From: ehgur Date: Thu, 15 May 2025 01:30:01 +0900 Subject: [PATCH 11/66] chore: update submodule pointer for github config --- security_setting | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security_setting b/security_setting index 6d21618..c96f0b3 160000 --- a/security_setting +++ b/security_setting @@ -1 +1 @@ -Subproject commit 6d2161852cede6700a8209b656ecf77e530249b4 +Subproject commit c96f0b30f51ad929493e3061f9d401087801a5e3 From 265b56801ae3988c099be4c57309605b0f0841c2 Mon Sep 17 00:00:00 2001 From: ehgur Date: Thu, 15 May 2025 01:38:54 +0900 Subject: [PATCH 12/66] =?UTF-8?q?feat:=20github=20=EC=97=B0=EB=8F=99=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- security_setting | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security_setting b/security_setting index 6d21618..c96f0b3 160000 --- a/security_setting +++ b/security_setting @@ -1 +1 @@ -Subproject commit 6d2161852cede6700a8209b656ecf77e530249b4 +Subproject commit c96f0b30f51ad929493e3061f9d401087801a5e3 From 2f357ed38cf84e0b6a82d7b381235a99de277b52 Mon Sep 17 00:00:00 2001 From: ehgur Date: Thu, 15 May 2025 22:49:58 +0900 Subject: [PATCH 13/66] =?UTF-8?q?feat:=20github=20token=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db/migration/V7.1_create_github_token_table.sql | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/resources/db/migration/V7.1_create_github_token_table.sql diff --git a/src/main/resources/db/migration/V7.1_create_github_token_table.sql b/src/main/resources/db/migration/V7.1_create_github_token_table.sql new file mode 100644 index 0000000..6d6996e --- /dev/null +++ b/src/main/resources/db/migration/V7.1_create_github_token_table.sql @@ -0,0 +1,9 @@ +CREATE TABLE `github_token` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `email` varchar(255) NOT NULL, + `github_token` varchar(255) DEFAULT NULL, + `create_date` datetime(6) DEFAULT NULL, + `update_date` datetime(6) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UK4b3exbfoct7fo6vl8upp3qgo3` (`email`) +) \ No newline at end of file From c139ad5de4d9e6d180561679a8db0d7c11a90785 Mon Sep 17 00:00:00 2001 From: ehgur Date: Thu, 15 May 2025 22:57:42 +0900 Subject: [PATCH 14/66] =?UTF-8?q?style:=20=EC=8A=A4=ED=81=AC=EB=A6=BD?= =?UTF-8?q?=ED=8A=B8=20=ED=8C=8C=EC=9D=BC=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 --- ...github_token_table.sql => V7.1__create_github_token_table.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/resources/db/migration/{V7.1_create_github_token_table.sql => V7.1__create_github_token_table.sql} (100%) diff --git a/src/main/resources/db/migration/V7.1_create_github_token_table.sql b/src/main/resources/db/migration/V7.1__create_github_token_table.sql similarity index 100% rename from src/main/resources/db/migration/V7.1_create_github_token_table.sql rename to src/main/resources/db/migration/V7.1__create_github_token_table.sql From ccc264ed475910a56d747e78a0b8b0928cc8cf07 Mon Sep 17 00:00:00 2001 From: ehgur Date: Fri, 16 May 2025 23:03:14 +0900 Subject: [PATCH 15/66] =?UTF-8?q?feat:=20github=20push=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TODO: code post의 language와 github에서 인식하는 파일 형식 이름이 달라서 매핑해야함 --- build.gradle | 3 ++ .../github/controller/GithubController.java | 10 ++++ .../dto/request/GithubCodePushRequestDto.java | 24 +++++++++ .../domain/github/service/GithubService.java | 54 +++++++++++++++++++ 4 files changed, 91 insertions(+) create mode 100644 src/main/java/org/example/autoreview/domain/github/dto/request/GithubCodePushRequestDto.java diff --git a/build.gradle b/build.gradle index 3d92b50..b461c84 100644 --- a/build.gradle +++ b/build.gradle @@ -50,6 +50,9 @@ dependencies { // MySQL 을 사용해야한다면 추가 implementation 'org.flywaydb:flyway-mysql' + // Github API 사용을 위해 추가 + implementation 'org.kohsuke:github-api:1.327' + compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' diff --git a/src/main/java/org/example/autoreview/domain/github/controller/GithubController.java b/src/main/java/org/example/autoreview/domain/github/controller/GithubController.java index a6d19bc..8695980 100644 --- a/src/main/java/org/example/autoreview/domain/github/controller/GithubController.java +++ b/src/main/java/org/example/autoreview/domain/github/controller/GithubController.java @@ -1,7 +1,9 @@ package org.example.autoreview.domain.github.controller; import io.swagger.v3.oas.annotations.Operation; +import java.io.IOException; import lombok.RequiredArgsConstructor; +import org.example.autoreview.domain.github.dto.request.GithubCodePushRequestDto; import org.example.autoreview.domain.github.dto.request.GithubCodeRequestDto; import org.example.autoreview.domain.github.service.GithubService; import org.springframework.http.ResponseEntity; @@ -34,4 +36,12 @@ public ResponseEntity callbackAndSave(@RequestBody GithubCodeRequestDto re return ResponseEntity.ok().body(githubService.save(accessToken, userDetails.getUsername())); } + @Operation + @PostMapping("/push/post/code") + public ResponseEntity push(@AuthenticationPrincipal UserDetails userDetails, + @RequestBody GithubCodePushRequestDto requestDto) throws IOException { + githubService.pushToGithub(userDetails.getUsername(), requestDto); + return ResponseEntity.ok().body("Push to GitHub completed successfully."); + } + } diff --git a/src/main/java/org/example/autoreview/domain/github/dto/request/GithubCodePushRequestDto.java b/src/main/java/org/example/autoreview/domain/github/dto/request/GithubCodePushRequestDto.java new file mode 100644 index 0000000..8ff846d --- /dev/null +++ b/src/main/java/org/example/autoreview/domain/github/dto/request/GithubCodePushRequestDto.java @@ -0,0 +1,24 @@ +package org.example.autoreview.domain.github.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +public record GithubCodePushRequestDto( + + @Schema(description = "코드 포스트 제목", example = "[BOJ] 0000: test 해보기") + String title, + + @Schema(description = "난이도", example = "4") + int level, + + @Schema(description = "해설", example = "test 기법을 사용해서 구현") + String description, + + @NotNull + @Schema(description = "사용 언어", example = "java") + String language, + + @Schema(description = "코드", example = "import test") + String code +) { +} diff --git a/src/main/java/org/example/autoreview/domain/github/service/GithubService.java b/src/main/java/org/example/autoreview/domain/github/service/GithubService.java index 1884e54..10b96a7 100644 --- a/src/main/java/org/example/autoreview/domain/github/service/GithubService.java +++ b/src/main/java/org/example/autoreview/domain/github/service/GithubService.java @@ -1,14 +1,21 @@ package org.example.autoreview.domain.github.service; +import java.io.IOException; import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.example.autoreview.domain.github.dto.request.GithubCodePushRequestDto; import org.example.autoreview.domain.github.dto.request.GithubCodeRequestDto; import org.example.autoreview.domain.github.dto.request.GithubTokenRequestDto; import org.example.autoreview.domain.github.entity.GithubToken; import org.example.autoreview.domain.github.entity.GithubTokenRepository; import org.example.autoreview.global.exception.errorcode.ErrorCode; import org.example.autoreview.global.exception.sub_exceptions.NotFoundException; +import org.kohsuke.github.GHContent; +import org.kohsuke.github.GHFileNotFoundException; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GitHub; +import org.kohsuke.github.GitHubBuilder; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Service; @@ -29,6 +36,7 @@ public class GithubService { private final GithubTokenRepository githubTokenRepository; private final WebClient webClient; + private final String REPO_NAME = "Ori"; @Transactional public Long save(String accessToken, String email) { @@ -68,6 +76,52 @@ public String getAccessToken(GithubCodeRequestDto requestDto) { .block(); return (String) response.get("access_token"); + } + + //TODO: language와 github에서 인식하는 파일 형식이 달라서 매핑해야함 + @Transactional + public void pushToGithub(String email, GithubCodePushRequestDto requestDto) throws IOException { + String codePath = requestDto.title() + "[" + requestDto.language() + "]" + "/code." + requestDto.language(); + String readmePath = requestDto.title() + "[" + requestDto.language() + "]" + "/description.md"; + + String githubToken = githubTokenRepository.findByEmail(email).orElseThrow( + () -> new NotFoundException(ErrorCode.NOT_FOUND_GITHUB_TOKEN) + ).getGithubToken(); + + GitHub github = new GitHubBuilder().withOAuthToken(githubToken).build(); + GHRepository repo = getOrCreateRepository(github, getGithubName(github)); + + pushFile(repo, codePath, requestDto.code(), "Initial commit: code", "Updated code"); + pushFile(repo, readmePath, requestDto.description(), "Initial commit: description", "Updated README"); } + + private GHRepository getOrCreateRepository(GitHub github, String username) throws IOException { + try { + return github.getRepository(username + "/" + REPO_NAME); + } catch (GHFileNotFoundException e) { + return github.createRepository(REPO_NAME) + .description("Auto-created ori repo") + .private_(false) + .create(); + } + } + + private void pushFile(GHRepository repo, String path, String content, String initialMsg, String updateMsg) throws IOException { + try { + GHContent existing = repo.getFileContent(path, "main"); + existing.update(content, updateMsg, "main"); + } catch (GHFileNotFoundException e) { + repo.createContent() + .path(path) + .message(initialMsg) + .content(content) + .commit(); + } + } + + private String getGithubName(GitHub github) throws IOException { + return github.getMyself().getLogin(); + } + } From 3d298dfa5685d63931d6d196cd84e127eb61dc39 Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Tue, 20 May 2025 16:26:44 +0900 Subject: [PATCH 16/66] =?UTF-8?q?feat:=20=EC=A0=84=EC=B2=B4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=A0=95=EB=A0=AC=20=EB=B0=8F=20=ED=95=84=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 응답 값에 공개여부 필드 추가 - 프로그래밍 언어별 필터링 추가 - 댓글 개수 순 정렬 추가 Closes#125 --- .../controller/CodePostController.java | 19 ++++----- .../dto/response/CodePostResponseDto.java | 2 + .../codepost/entity/CodePostRepository.java | 40 +++++++++++++++++-- .../domain/codepost/entity/Language.java | 1 + .../codepost/service/CodePostDtoService.java | 13 +++++- .../codepost/service/CodePostService.java | 14 ++++++- 6 files changed, 70 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java b/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java index 3806f8e..d4c8f76 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java +++ b/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java @@ -7,21 +7,14 @@ import org.example.autoreview.domain.codepost.dto.request.CodePostUpdateRequestDto; import org.example.autoreview.domain.codepost.dto.response.CodePostListResponseDto; import org.example.autoreview.domain.codepost.dto.response.CodePostResponseDto; +import org.example.autoreview.domain.codepost.entity.Language; import org.example.autoreview.domain.codepost.service.CodePostDtoService; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; -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.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @Tag(name = "코드 포스트 API", description = "코드 포스트 API") @RequiredArgsConstructor @@ -54,8 +47,12 @@ public ResponseEntity view(@PathVariable("id") Long codePos @Operation(summary = "코드 포스트 전체 조회", description = "코드 포스트 전체 조회(비공개 포스트는 제외)") @GetMapping("/list") - public ResponseEntity viewAll(@PageableDefault(page = 0, size = 9) Pageable pageable) { - return ResponseEntity.ok().body(codePostMemberService.findPostByPage(pageable)); + public ResponseEntity viewAll(@RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "9") int size, + @RequestParam(defaultValue = "desc") String direction, + @RequestParam(defaultValue = "id") String sortBy, + @RequestParam(defaultValue = "all") Language language) { + return ResponseEntity.ok().body(codePostMemberService.findPostByPage(page,size,direction,sortBy,language)); } @Operation(summary = "내가 쓴 코드 포스트 조회", description = "내가 쓴 코드 포스트 조회") diff --git a/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostResponseDto.java b/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostResponseDto.java index 496861a..ddb3b10 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostResponseDto.java +++ b/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostResponseDto.java @@ -17,6 +17,7 @@ public class CodePostResponseDto { private final String writerNickName; private final String title; private final int level; + private final boolean isPublic; private final LocalDate reviewDay; private final String description; private final String language; @@ -31,6 +32,7 @@ public CodePostResponseDto(CodePost entity, List dtoList, Mem this.writerNickName = writer.getNickname(); this.title = entity.getTitle(); this.level = entity.getLevel(); + this.isPublic = entity.isPublic(); this.reviewDay = entity.getReviewDay(); this.description = entity.getDescription(); this.language = entity.getLanguage().getType(); diff --git a/src/main/java/org/example/autoreview/domain/codepost/entity/CodePostRepository.java b/src/main/java/org/example/autoreview/domain/codepost/entity/CodePostRepository.java index edfd8b6..d15e471 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/entity/CodePostRepository.java +++ b/src/main/java/org/example/autoreview/domain/codepost/entity/CodePostRepository.java @@ -13,9 +13,14 @@ public interface CodePostRepository extends JpaRepository { @Query("SELECT c FROM CodePost c WHERE c.id = :id AND (c.writerId = :memberId OR c.isPublic = TRUE)") Optional findByIdIsPublic(@Param("id") Long id, @Param("memberId") Long memberId); - @Query("SELECT new org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto(c,m) " + - "FROM CodePost c INNER JOIN Member m ON c.writerId = m.id AND c.isPublic = TRUE ORDER BY c.id DESC") - Page findByPage(Pageable pageable); + @Query(""" + SELECT new org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto(c,m) + FROM CodePost c + INNER JOIN Member m ON c.writerId = m.id + AND c.isPublic = TRUE + AND (:language = 'all' OR c.language = :language) + """) + Page findByPage(Pageable pageable, @Param("language") Language language); @Query(value = "SELECT * FROM code_post c WHERE c.is_public = TRUE AND MATCH(c.title) AGAINST(:keyword IN BOOLEAN MODE)", countQuery = "SELECT COUNT(*) FROM code_post c WHERE MATCH(c.title) AGAINST(:keyword IN BOOLEAN MODE)", @@ -26,7 +31,34 @@ public interface CodePostRepository extends JpaRepository { @Query("SELECT c FROM CodePost c WHERE c.writerId =:id AND c.title LIKE %:keyword% ORDER BY c.id DESC") Page mySearch(@Param("keyword") String keyword, Pageable pageable, @Param("id") Long id); - @Query("SELECT c FROM CodePost c WHERE c.writerId =:id ORDER BY c.id DESC") + @Query("SELECT c FROM CodePost c WHERE c.writerId =:id") Page findByMemberId(@Param("id") Long id, Pageable pageable); + + @Query(""" + SELECT new org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto(c, m) + FROM CodePost c + LEFT JOIN c.codePostCommentList cm + INNER JOIN Member m ON c.writerId = m.id + AND c.isPublic = TRUE + AND (:language = 'all' OR c.language = :language) + GROUP BY c, m + ORDER BY COUNT(cm) DESC + """) + Page findByPageSortByCommentCountDesc(Pageable pageable, @Param("language") Language language); + + @Query(""" + SELECT new org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto(c, m) + FROM CodePost c + LEFT JOIN c.codePostCommentList cm + INNER JOIN Member m ON c.writerId = m.id + AND c.isPublic = TRUE + AND (:language = 'all' OR c.language = :language) + GROUP BY c, m + ORDER BY COUNT(cm) ASC + """) + Page findByPageSortByCommentCountAsc(Pageable pageable, @Param("language") Language language); + + + } diff --git a/src/main/java/org/example/autoreview/domain/codepost/entity/Language.java b/src/main/java/org/example/autoreview/domain/codepost/entity/Language.java index 485f868..7df141c 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/entity/Language.java +++ b/src/main/java/org/example/autoreview/domain/codepost/entity/Language.java @@ -5,6 +5,7 @@ @Getter public enum Language { + ALL("all"), JAVASCRIPT("javascript"), PYTHON("python"), JAVA("java"), diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java index b0fd9d1..75699f7 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java +++ b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java @@ -7,11 +7,14 @@ import org.example.autoreview.domain.codepost.dto.response.CodePostListResponseDto; import org.example.autoreview.domain.codepost.dto.response.CodePostResponseDto; import org.example.autoreview.domain.codepost.entity.CodePost; +import org.example.autoreview.domain.codepost.entity.Language; import org.example.autoreview.domain.member.entity.Member; import org.example.autoreview.domain.member.service.MemberService; import org.example.autoreview.domain.notification.enums.NotificationStatus; import org.example.autoreview.domain.notification.service.NotificationService; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; @Slf4j @@ -52,8 +55,14 @@ public CodePostListResponseDto findPostByMemberId(Pageable pageable, String emai return codePostService.findByMemberId(pageable, member); } - public CodePostListResponseDto findPostByPage(Pageable pageable){ - return codePostService.findByPage(pageable); + public CodePostListResponseDto findPostByPage(int page, int size, String direction, String sortBy, Language language){ + if(sortBy.equals("commentCount")) { + Pageable pageable = PageRequest.of(page, size); + return codePostService.findByPageSortByCommentCount(pageable,direction,language); + } + Sort.Direction sortDirection = Sort.Direction.fromString(direction); + Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection,sortBy)); + return codePostService.findByPage(pageable,language); } public Long postUpdate(CodePostUpdateRequestDto requestDto, String email) { diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java index 32d4764..278f4fb 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java +++ b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java @@ -11,6 +11,7 @@ import org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto; import org.example.autoreview.domain.codepost.entity.CodePost; import org.example.autoreview.domain.codepost.entity.CodePostRepository; +import org.example.autoreview.domain.codepost.entity.Language; import org.example.autoreview.domain.member.entity.Member; import org.example.autoreview.domain.member.service.MemberCommand; import org.example.autoreview.domain.review.dto.response.ReviewResponseDto; @@ -98,12 +99,21 @@ private CodePostThumbnailResponseDto getCodePostThumbnailResponseDto(CodePost co return new CodePostThumbnailResponseDto(codePost, member); } - public CodePostListResponseDto findByPage(Pageable pageable) { - Page page = codePostRepository.findByPage(pageable); + public CodePostListResponseDto findByPage(Pageable pageable, Language language) { + Page page = codePostRepository.findByPage(pageable,language); return new CodePostListResponseDto(page.getContent(), page.getTotalPages()); } + public CodePostListResponseDto findByPageSortByCommentCount(Pageable pageable, String direction, Language language) { + if (direction.equals("desc")) { + Page page = codePostRepository.findByPageSortByCommentCountDesc(pageable,language); + return new CodePostListResponseDto(page.getContent(), page.getTotalPages()); + } + Page page = codePostRepository.findByPageSortByCommentCountAsc(pageable,language); + return new CodePostListResponseDto(page.getContent(), page.getTotalPages()); + } + @Transactional public CodePost update(CodePostUpdateRequestDto requestDto, String email) { CodePost codePost = codePostRepository.findById(requestDto.getId()).orElseThrow( From 2c723f96209c6d4e1266513bf9d1b067ef273f45 Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Wed, 21 May 2025 00:29:45 +0900 Subject: [PATCH 17/66] =?UTF-8?q?refactor:=20github=20push=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 - 프로그래밍 언어에 맞는 파일 형식자 매핑 - md 파일에 줄바꿈 적용 Closes #122 --- .../controller/CodePostController.java | 3 +- .../dto/request/CodePostSaveRequestDto.java | 6 ++- .../domain/codepost/entity/CodePost.java | 6 ++- .../domain/codepost/entity/Language.java | 37 ++++++++++--------- .../codepost/service/CodePostDtoService.java | 11 ++++-- .../domain/github/service/GithubService.java | 24 +++++++----- .../global/exception/errorcode/ErrorCode.java | 1 + 7 files changed, 54 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java b/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java index d4c8f76..39ac93d 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java +++ b/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java @@ -7,7 +7,6 @@ import org.example.autoreview.domain.codepost.dto.request.CodePostUpdateRequestDto; import org.example.autoreview.domain.codepost.dto.response.CodePostListResponseDto; import org.example.autoreview.domain.codepost.dto.response.CodePostResponseDto; -import org.example.autoreview.domain.codepost.entity.Language; import org.example.autoreview.domain.codepost.service.CodePostDtoService; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; @@ -51,7 +50,7 @@ public ResponseEntity viewAll(@RequestParam(defaultValu @RequestParam(defaultValue = "9") int size, @RequestParam(defaultValue = "desc") String direction, @RequestParam(defaultValue = "id") String sortBy, - @RequestParam(defaultValue = "all") Language language) { + @RequestParam(defaultValue = "all") String language) { return ResponseEntity.ok().body(codePostMemberService.findPostByPage(page,size,direction,sortBy,language)); } diff --git a/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostSaveRequestDto.java b/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostSaveRequestDto.java index d051341..3f5751a 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostSaveRequestDto.java +++ b/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostSaveRequestDto.java @@ -6,6 +6,8 @@ import org.example.autoreview.domain.codepost.entity.CodePost; import org.example.autoreview.domain.codepost.entity.Language; import org.example.autoreview.domain.member.entity.Member; +import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; +import org.example.autoreview.global.exception.errorcode.ErrorCode; public record CodePostSaveRequestDto( @@ -39,7 +41,9 @@ public CodePost toEntity(Member member){ .isPublic(isPublic) .reviewDay(reviewDay) .description(description) - .language(Language.of(language)) + .language(Language.of(language).orElseThrow( + () -> new CustomRuntimeException(ErrorCode.NOT_FOUND_LANGUAGE) + )) .code(code) .build(); } diff --git a/src/main/java/org/example/autoreview/domain/codepost/entity/CodePost.java b/src/main/java/org/example/autoreview/domain/codepost/entity/CodePost.java index d4ed452..80c76ab 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/entity/CodePost.java +++ b/src/main/java/org/example/autoreview/domain/codepost/entity/CodePost.java @@ -19,6 +19,8 @@ import org.example.autoreview.domain.comment.codepost.entity.CodePostComment; import org.example.autoreview.domain.review.entity.Review; import org.example.autoreview.global.common.basetime.BaseEntity; +import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; +import org.example.autoreview.global.exception.errorcode.ErrorCode; import org.hibernate.annotations.ColumnDefault; @Getter @@ -77,7 +79,9 @@ public void update(CodePostUpdateRequestDto requestDto){ this.isPublic = requestDto.isPublic(); this.reviewDay = requestDto.getReviewDay(); this.description = requestDto.getDescription(); - this.language = Language.of(requestDto.getLanguage()); + this.language = Language.of(requestDto.getLanguage()).orElseThrow( + () -> new CustomRuntimeException(ErrorCode.NOT_FOUND_LANGUAGE) + ); this.code = requestDto.getCode(); } } diff --git a/src/main/java/org/example/autoreview/domain/codepost/entity/Language.java b/src/main/java/org/example/autoreview/domain/codepost/entity/Language.java index 7df141c..c06fc22 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/entity/Language.java +++ b/src/main/java/org/example/autoreview/domain/codepost/entity/Language.java @@ -2,31 +2,34 @@ import lombok.Getter; +import java.util.Arrays; +import java.util.Optional; + @Getter public enum Language { - ALL("all"), - JAVASCRIPT("javascript"), - PYTHON("python"), - JAVA("java"), - CSHARP("csharp"), - CPP("cpp"), - C("c"), - RUBY("ruby"), - GO("go"); + ALL("all","txt"), + KOTLIN("kotlin","kt"), + JAVASCRIPT("javascript", "js"), + PYTHON("python", "py"), + JAVA("java", "java"), + CSHARP("csharp", "cs"), + CPP("cpp", "cpp"), + C("c", "c"), + RUBY("ruby", "rb"), + GO("go", "go"); private final String type; + private final String fileExtension; - Language(String type) { + Language(String type, String fileExtension) { this.type = type; + this.fileExtension = fileExtension; } - public static Language of(String type) { - for (Language lt : Language.values()) { - if (lt.type.equals(type)) { - return lt; - } - } - return null; + public static Optional of(String name) { + return Arrays.stream(values()) + .filter(lang -> lang.type.equalsIgnoreCase(name)) + .findFirst(); } } diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java index 75699f7..9b21ef3 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java +++ b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java @@ -12,6 +12,8 @@ import org.example.autoreview.domain.member.service.MemberService; import org.example.autoreview.domain.notification.enums.NotificationStatus; import org.example.autoreview.domain.notification.service.NotificationService; +import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; +import org.example.autoreview.global.exception.errorcode.ErrorCode; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -55,14 +57,17 @@ public CodePostListResponseDto findPostByMemberId(Pageable pageable, String emai return codePostService.findByMemberId(pageable, member); } - public CodePostListResponseDto findPostByPage(int page, int size, String direction, String sortBy, Language language){ + public CodePostListResponseDto findPostByPage(int page, int size, String direction, String sortBy, String language){ + Language lang = Language.of(language).orElseThrow( + () -> new CustomRuntimeException(ErrorCode.NOT_FOUND_LANGUAGE) + ); if(sortBy.equals("commentCount")) { Pageable pageable = PageRequest.of(page, size); - return codePostService.findByPageSortByCommentCount(pageable,direction,language); + return codePostService.findByPageSortByCommentCount(pageable,direction,lang); } Sort.Direction sortDirection = Sort.Direction.fromString(direction); Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection,sortBy)); - return codePostService.findByPage(pageable,language); + return codePostService.findByPage(pageable,lang); } public Long postUpdate(CodePostUpdateRequestDto requestDto, String email) { diff --git a/src/main/java/org/example/autoreview/domain/github/service/GithubService.java b/src/main/java/org/example/autoreview/domain/github/service/GithubService.java index 10b96a7..e81d8fb 100644 --- a/src/main/java/org/example/autoreview/domain/github/service/GithubService.java +++ b/src/main/java/org/example/autoreview/domain/github/service/GithubService.java @@ -1,27 +1,26 @@ package org.example.autoreview.domain.github.service; -import java.io.IOException; -import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.example.autoreview.domain.codepost.entity.Language; import org.example.autoreview.domain.github.dto.request.GithubCodePushRequestDto; import org.example.autoreview.domain.github.dto.request.GithubCodeRequestDto; import org.example.autoreview.domain.github.dto.request.GithubTokenRequestDto; import org.example.autoreview.domain.github.entity.GithubToken; import org.example.autoreview.domain.github.entity.GithubTokenRepository; +import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; import org.example.autoreview.global.exception.errorcode.ErrorCode; import org.example.autoreview.global.exception.sub_exceptions.NotFoundException; -import org.kohsuke.github.GHContent; -import org.kohsuke.github.GHFileNotFoundException; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GitHub; -import org.kohsuke.github.GitHubBuilder; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.reactive.function.client.WebClient; +import java.io.IOException; +import java.util.Map; +import java.util.Objects; + @Slf4j @RequiredArgsConstructor @Transactional(readOnly = true) @@ -78,10 +77,13 @@ public String getAccessToken(GithubCodeRequestDto requestDto) { return (String) response.get("access_token"); } - //TODO: language와 github에서 인식하는 파일 형식이 달라서 매핑해야함 @Transactional public void pushToGithub(String email, GithubCodePushRequestDto requestDto) throws IOException { - String codePath = requestDto.title() + "[" + requestDto.language() + "]" + "/code." + requestDto.language(); + String fileExtension = Objects.requireNonNull(Language.of(requestDto.language()) + .orElseThrow(() -> new CustomRuntimeException(ErrorCode.NOT_FOUND_LANGUAGE)) + ).getFileExtension(); + + String codePath = requestDto.title() + "[" + requestDto.language() + "]" + "/code." + fileExtension; String readmePath = requestDto.title() + "[" + requestDto.language() + "]" + "/description.md"; String githubToken = githubTokenRepository.findByEmail(email).orElseThrow( @@ -92,7 +94,9 @@ public void pushToGithub(String email, GithubCodePushRequestDto requestDto) thro GHRepository repo = getOrCreateRepository(github, getGithubName(github)); pushFile(repo, codePath, requestDto.code(), "Initial commit: code", "Updated code"); - pushFile(repo, readmePath, requestDto.description(), "Initial commit: description", "Updated README"); + + String newDescription = requestDto.description().replaceAll("\n", " \n"); + pushFile(repo, readmePath, newDescription, "Initial commit: description", "Updated README"); } diff --git a/src/main/java/org/example/autoreview/global/exception/errorcode/ErrorCode.java b/src/main/java/org/example/autoreview/global/exception/errorcode/ErrorCode.java index 154c759..ed2fa40 100644 --- a/src/main/java/org/example/autoreview/global/exception/errorcode/ErrorCode.java +++ b/src/main/java/org/example/autoreview/global/exception/errorcode/ErrorCode.java @@ -34,6 +34,7 @@ public enum ErrorCode { // POST NOT_FOUND_POST(404, HttpStatus.NOT_FOUND, "해당 포스트를 찾을 수 없습니다."), + NOT_FOUND_LANGUAGE(404, HttpStatus.NOT_FOUND, "해당 언어를 찾을 수 없습니다."), // NOTIFICATION NOT_FOUND_NOTIFICATION(404, HttpStatus.NOT_FOUND, "해당 알림을 찾을 수 없습니다."), From 3442ee45d15b449b0d83e3ab3f2a6dfa7e126dbf Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Wed, 21 May 2025 22:49:08 +0900 Subject: [PATCH 18/66] =?UTF-8?q?refactor:=20=EB=A0=88=EC=9D=B4=EC=96=B4?= =?UTF-8?q?=20=EA=B0=84=20=EC=B1=85=EC=9E=84=20=EB=B6=84=EB=A6=AC=20?= =?UTF-8?q?=EC=9B=90=EC=B9=99=EC=97=90=EC=84=9C=20=EB=B2=97=EC=96=B4?= =?UTF-8?q?=EB=82=A0=20=EC=97=AC=EC=A7=80=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CodePostController.java | 2 +- .../dto/request/CodePostSaveRequestDto.java | 9 +++----- .../domain/codepost/entity/CodePost.java | 23 +++++-------------- .../domain/codepost/entity/Language.java | 6 ++--- .../codepost/service/CodePostDtoService.java | 6 +---- .../codepost/service/CodePostService.java | 5 ++-- .../domain/github/service/GithubService.java | 12 +++++----- 7 files changed, 23 insertions(+), 40 deletions(-) diff --git a/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java b/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java index 39ac93d..d306304 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java +++ b/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java @@ -33,7 +33,7 @@ public ResponseEntity save(@RequestBody CodePostSaveRequestDto requestDto, @Operation(summary = "제목으로 코드 포스트 검색", description = "공백 또는 null 입력 시 에러 반환") @GetMapping("/search") public ResponseEntity search(@RequestParam String keyword, - @PageableDefault(page = 0, size = 9) Pageable pageable) { + @PageableDefault(page = 0, size = 9) Pageable pageable) { return ResponseEntity.ok().body(codePostMemberService.postSearch(keyword, pageable)); } diff --git a/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostSaveRequestDto.java b/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostSaveRequestDto.java index 3f5751a..e0a71d4 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostSaveRequestDto.java +++ b/src/main/java/org/example/autoreview/domain/codepost/dto/request/CodePostSaveRequestDto.java @@ -2,12 +2,11 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; -import java.time.LocalDate; import org.example.autoreview.domain.codepost.entity.CodePost; import org.example.autoreview.domain.codepost.entity.Language; import org.example.autoreview.domain.member.entity.Member; -import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; -import org.example.autoreview.global.exception.errorcode.ErrorCode; + +import java.time.LocalDate; public record CodePostSaveRequestDto( @@ -41,9 +40,7 @@ public CodePost toEntity(Member member){ .isPublic(isPublic) .reviewDay(reviewDay) .description(description) - .language(Language.of(language).orElseThrow( - () -> new CustomRuntimeException(ErrorCode.NOT_FOUND_LANGUAGE) - )) + .language(Language.of(language)) .code(code) .build(); } diff --git a/src/main/java/org/example/autoreview/domain/codepost/entity/CodePost.java b/src/main/java/org/example/autoreview/domain/codepost/entity/CodePost.java index 80c76ab..56954bf 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/entity/CodePost.java +++ b/src/main/java/org/example/autoreview/domain/codepost/entity/CodePost.java @@ -1,17 +1,6 @@ package org.example.autoreview.domain.codepost.entity; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; +import jakarta.persistence.*; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -19,10 +8,12 @@ import org.example.autoreview.domain.comment.codepost.entity.CodePostComment; import org.example.autoreview.domain.review.entity.Review; import org.example.autoreview.global.common.basetime.BaseEntity; -import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; -import org.example.autoreview.global.exception.errorcode.ErrorCode; import org.hibernate.annotations.ColumnDefault; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + @Getter @NoArgsConstructor @Entity @@ -79,9 +70,7 @@ public void update(CodePostUpdateRequestDto requestDto){ this.isPublic = requestDto.isPublic(); this.reviewDay = requestDto.getReviewDay(); this.description = requestDto.getDescription(); - this.language = Language.of(requestDto.getLanguage()).orElseThrow( - () -> new CustomRuntimeException(ErrorCode.NOT_FOUND_LANGUAGE) - ); + this.language = Language.of(requestDto.getLanguage()); this.code = requestDto.getCode(); } } diff --git a/src/main/java/org/example/autoreview/domain/codepost/entity/Language.java b/src/main/java/org/example/autoreview/domain/codepost/entity/Language.java index c06fc22..237812f 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/entity/Language.java +++ b/src/main/java/org/example/autoreview/domain/codepost/entity/Language.java @@ -3,7 +3,6 @@ import lombok.Getter; import java.util.Arrays; -import java.util.Optional; @Getter public enum Language { @@ -27,9 +26,10 @@ public enum Language { this.fileExtension = fileExtension; } - public static Optional of(String name) { + public static Language of(String name) { return Arrays.stream(values()) .filter(lang -> lang.type.equalsIgnoreCase(name)) - .findFirst(); + .findFirst() + .get(); } } diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java index 9b21ef3..ce33233 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java +++ b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java @@ -12,8 +12,6 @@ import org.example.autoreview.domain.member.service.MemberService; import org.example.autoreview.domain.notification.enums.NotificationStatus; import org.example.autoreview.domain.notification.service.NotificationService; -import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; -import org.example.autoreview.global.exception.errorcode.ErrorCode; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -58,9 +56,7 @@ public CodePostListResponseDto findPostByMemberId(Pageable pageable, String emai } public CodePostListResponseDto findPostByPage(int page, int size, String direction, String sortBy, String language){ - Language lang = Language.of(language).orElseThrow( - () -> new CustomRuntimeException(ErrorCode.NOT_FOUND_LANGUAGE) - ); + Language lang = Language.of(language); if(sortBy.equals("commentCount")) { Pageable pageable = PageRequest.of(page, size); return codePostService.findByPageSortByCommentCount(pageable,direction,lang); diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java index 278f4fb..54bacb7 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java +++ b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java @@ -1,7 +1,5 @@ package org.example.autoreview.domain.codepost.service; -import java.util.List; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.example.autoreview.domain.codepost.dto.request.CodePostSaveRequestDto; @@ -24,6 +22,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; +import java.util.stream.Collectors; + @Slf4j @Transactional(readOnly = true) @RequiredArgsConstructor diff --git a/src/main/java/org/example/autoreview/domain/github/service/GithubService.java b/src/main/java/org/example/autoreview/domain/github/service/GithubService.java index e81d8fb..fd9ba74 100644 --- a/src/main/java/org/example/autoreview/domain/github/service/GithubService.java +++ b/src/main/java/org/example/autoreview/domain/github/service/GithubService.java @@ -8,9 +8,12 @@ import org.example.autoreview.domain.github.dto.request.GithubTokenRequestDto; import org.example.autoreview.domain.github.entity.GithubToken; import org.example.autoreview.domain.github.entity.GithubTokenRepository; -import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; import org.example.autoreview.global.exception.errorcode.ErrorCode; import org.example.autoreview.global.exception.sub_exceptions.NotFoundException; +import org.kohsuke.github.GHFileNotFoundException; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GitHub; +import org.kohsuke.github.GitHubBuilder; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Service; @@ -19,7 +22,6 @@ import java.io.IOException; import java.util.Map; -import java.util.Objects; @Slf4j @RequiredArgsConstructor @@ -79,9 +81,7 @@ public String getAccessToken(GithubCodeRequestDto requestDto) { @Transactional public void pushToGithub(String email, GithubCodePushRequestDto requestDto) throws IOException { - String fileExtension = Objects.requireNonNull(Language.of(requestDto.language()) - .orElseThrow(() -> new CustomRuntimeException(ErrorCode.NOT_FOUND_LANGUAGE)) - ).getFileExtension(); + String fileExtension = Language.of(requestDto.language()).getFileExtension(); String codePath = requestDto.title() + "[" + requestDto.language() + "]" + "/code." + fileExtension; String readmePath = requestDto.title() + "[" + requestDto.language() + "]" + "/description.md"; @@ -113,7 +113,7 @@ private GHRepository getOrCreateRepository(GitHub github, String username) throw private void pushFile(GHRepository repo, String path, String content, String initialMsg, String updateMsg) throws IOException { try { - GHContent existing = repo.getFileContent(path, "main"); + org.kohsuke.github.GHContent existing = repo.getFileContent(path, "main"); existing.update(content, updateMsg, "main"); } catch (GHFileNotFoundException e) { repo.createContent() From 4c38529b876dd89e46e98e2e5438e81cf262eb76 Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Sun, 1 Jun 2025 17:42:35 +0900 Subject: [PATCH 19/66] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=ED=8F=AC=EC=8A=A4=ED=8A=B8=20=EB=8B=A8=EC=9D=BC=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=8B=9C=20=EB=B6=81=EB=A7=88=ED=81=AC=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CodePostBookmarkListResponseDto.java | 2 +- .../service/CodePostBookmarkCommand.java | 21 +++++++++++++++++++ .../controller/CodePostController.java | 2 +- .../dto/response/CodePostResponseDto.java | 4 +++- .../codepost/service/CodePostService.java | 11 +++++++++- 5 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkCommand.java diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/response/CodePostBookmarkListResponseDto.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/response/CodePostBookmarkListResponseDto.java index 8764cf0..cc3f32a 100644 --- a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/response/CodePostBookmarkListResponseDto.java +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/response/CodePostBookmarkListResponseDto.java @@ -3,7 +3,7 @@ import java.util.List; public record CodePostBookmarkListResponseDto( - List listDto, + List dtoList, int totalPage ) { } diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkCommand.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkCommand.java new file mode 100644 index 0000000..f082fad --- /dev/null +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkCommand.java @@ -0,0 +1,21 @@ +package org.example.autoreview.domain.bookmark.CodePostBookmark.service; + +import lombok.RequiredArgsConstructor; +import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmark; +import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmarkRepository; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +@RequiredArgsConstructor +@Component +public class CodePostBookmarkCommand { + + private final CodePostBookmarkRepository codePostBookmarkRepository; + + @Transactional(readOnly = true) + public Optional findByCodePostBookmark(Long memberId, Long codePostId) { + return codePostBookmarkRepository.findById(memberId,codePostId); + } +} diff --git a/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java b/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java index d306304..1fae416 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java +++ b/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java @@ -37,7 +37,7 @@ public ResponseEntity search(@RequestParam String keywo return ResponseEntity.ok().body(codePostMemberService.postSearch(keyword, pageable)); } - @Operation(summary = "코드 포스트 단일 조회", description = "공개된 포스트 or 작성자일 경우만 조회됨") + @Operation(summary = "코드 포스트 단일 조회", description = "공개된 포스트 or 작성자일 경우만 조회됨 + 북마크 여부 포함") @GetMapping("/detail/{id}") public ResponseEntity view(@PathVariable("id") Long codePostId, @AuthenticationPrincipal UserDetails userDetails) { diff --git a/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostResponseDto.java b/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostResponseDto.java index ddb3b10..374df89 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostResponseDto.java +++ b/src/main/java/org/example/autoreview/domain/codepost/dto/response/CodePostResponseDto.java @@ -22,10 +22,11 @@ public class CodePostResponseDto { private final String description; private final String language; private final String code; + private final boolean isBookmarked; private final List dtoList; private final LocalDateTime createDate; - public CodePostResponseDto(CodePost entity, List dtoList, Member writer) { + public CodePostResponseDto(CodePost entity, List dtoList, Member writer, boolean isBookmarked) { this.id = entity.getId(); this.writerId = entity.getWriterId(); this.writerEmail = writer.getEmail(); @@ -37,6 +38,7 @@ public CodePostResponseDto(CodePost entity, List dtoList, Mem this.description = entity.getDescription(); this.language = entity.getLanguage().getType(); this.code = entity.getCode(); + this.isBookmarked = isBookmarked; this.dtoList = dtoList; this.createDate = entity.getCreateDate(); } diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java index 54bacb7..346e928 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java +++ b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java @@ -2,6 +2,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmark; +import org.example.autoreview.domain.bookmark.CodePostBookmark.service.CodePostBookmarkCommand; import org.example.autoreview.domain.codepost.dto.request.CodePostSaveRequestDto; import org.example.autoreview.domain.codepost.dto.request.CodePostUpdateRequestDto; import org.example.autoreview.domain.codepost.dto.response.CodePostListResponseDto; @@ -23,6 +25,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; @Slf4j @@ -33,6 +36,7 @@ public class CodePostService { private final CodePostRepository codePostRepository; private final MemberCommand memberCommand; + private final CodePostBookmarkCommand codePostBookmarkCommand; @Transactional public CodePost save(CodePostSaveRequestDto requestDto, Member member) { @@ -52,13 +56,18 @@ public CodePostResponseDto findById(Long id,String email) { () -> new NotFoundException(ErrorCode.NOT_FOUND_POST) ); Member writer = memberCommand.findById(codePost.getWriterId()); + Optional codePostBookmark = codePostBookmarkCommand.findByCodePostBookmark(member.getId(),codePost.getId()); + boolean isBookmarked = false; + if (codePostBookmark.isPresent()) { + isBookmarked = !codePostBookmark.get().isDeleted(); + } List reviews = codePost.getReviewList(); List dtoList = reviews.stream() .map(ReviewResponseDto::new) .toList(); - return new CodePostResponseDto(codePost, dtoList, writer); + return new CodePostResponseDto(codePost, dtoList, writer, isBookmarked); } public CodePostListResponseDto search(String keyword, Pageable pageable) { From a573b35c113773c4344122859dd88f333c11aac3 Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Mon, 2 Jun 2025 01:13:19 +0900 Subject: [PATCH 20/66] =?UTF-8?q?refactor:=20=EB=B6=81=EB=A7=88=ED=81=AC?= =?UTF-8?q?=20save=20or=20update=20=EB=8F=99=EC=8B=9C=EC=84=B1=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bookmark/BookmarkHelper.java | 28 +++++++++++++++++++ .../CodePostBookmarkController.java | 2 +- .../entity/CodePostBookmark.java | 12 +++----- .../service/CodePostBookmarkService.java | 17 +++++------ ...8.1__add_unique_constraint_on_bookmark.sql | 7 +++++ 5 files changed, 49 insertions(+), 17 deletions(-) create mode 100644 src/main/java/org/example/autoreview/domain/bookmark/BookmarkHelper.java create mode 100644 src/main/resources/db/migration/V8.1__add_unique_constraint_on_bookmark.sql diff --git a/src/main/java/org/example/autoreview/domain/bookmark/BookmarkHelper.java b/src/main/java/org/example/autoreview/domain/bookmark/BookmarkHelper.java new file mode 100644 index 0000000..c791fe6 --- /dev/null +++ b/src/main/java/org/example/autoreview/domain/bookmark/BookmarkHelper.java @@ -0,0 +1,28 @@ +package org.example.autoreview.domain.bookmark; + +import lombok.RequiredArgsConstructor; +import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.request.CodePostBookmarkSaveRequestDto; +import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmark; +import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmarkRepository; +import org.example.autoreview.domain.member.entity.Member; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Service +public class BookmarkHelper { + + private final CodePostBookmarkRepository codePostBookmarkRepository; + + @Transactional + public Long trySave(CodePostBookmarkSaveRequestDto requestDto, Member member) { + return codePostBookmarkRepository.save(requestDto.toEntity(member)).getId(); + } + + @Transactional + public Long fallbackToUpdate(Long codePostId, Long memberId) { + return codePostBookmarkRepository.findById(codePostId, memberId) + .map(CodePostBookmark::update) + .orElseThrow(() -> new IllegalStateException("Bookmark should exist but was not found")); + } +} diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/controller/CodePostBookmarkController.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/controller/CodePostBookmarkController.java index 366f312..63a3eb6 100644 --- a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/controller/CodePostBookmarkController.java +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/controller/CodePostBookmarkController.java @@ -21,7 +21,7 @@ @Tag(name = "Code Post Bookmark API", description = "Code Post Bookmark API") @RequiredArgsConstructor @RestController -@RequestMapping("/v1/api/code-post/bookmark") +@RequestMapping("/v1/api/post/code/bookmark") public class CodePostBookmarkController { private final CodePostBookmarkService codePostBookmarkService; diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmark.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmark.java index bec4553..9a76396 100644 --- a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmark.java +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmark.java @@ -1,13 +1,6 @@ package org.example.autoreview.domain.bookmark.CodePostBookmark.entity; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; +import jakarta.persistence.*; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -17,6 +10,9 @@ @Getter @NoArgsConstructor +@Table(uniqueConstraints = {@UniqueConstraint(name = "uq_codepost_member", + columnNames = {"code_post_id", "member_id"})} +) @Entity public class CodePostBookmark extends BaseEntity { diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java index 8096348..62160a7 100644 --- a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java @@ -1,15 +1,15 @@ package org.example.autoreview.domain.bookmark.CodePostBookmark.service; -import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.example.autoreview.domain.bookmark.BookmarkHelper; import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.request.CodePostBookmarkSaveRequestDto; import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkListResponseDto; import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkResponseDto; -import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmark; import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmarkRepository; import org.example.autoreview.domain.member.entity.Member; import org.example.autoreview.domain.member.service.MemberCommand; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -18,20 +18,20 @@ @Slf4j @RequiredArgsConstructor @Service -@Transactional(readOnly = false) public class CodePostBookmarkService { private final CodePostBookmarkRepository codePostBookmarkRepository; + private final BookmarkHelper bookmarkHelper; private final MemberCommand memberCommand; public Long saveOrUpdate(CodePostBookmarkSaveRequestDto requestDto, String email) { Member member = memberCommand.findByEmail(email); - Optional existingBookmark = - codePostBookmarkRepository.findById(requestDto.codePostId(), member.getId()); - return existingBookmark - .map(CodePostBookmark::update) - .orElseGet(() -> codePostBookmarkRepository.save(requestDto.toEntity(member)).getId()); + try { + return bookmarkHelper.trySave(requestDto, member); + } catch (DataIntegrityViolationException e) { + return bookmarkHelper.fallbackToUpdate(requestDto.codePostId(), member.getId()); + } } @Transactional(readOnly = true) @@ -42,6 +42,7 @@ public CodePostBookmarkListResponseDto findAllByMemberId(String email, Pageable return new CodePostBookmarkListResponseDto(pageDto.getContent(),pageDto.getTotalPages()); } + @Transactional public void deleteExpiredSoftDeletedBookmarks() { codePostBookmarkRepository.deleteExpiredSoftDeletedBookmarks(); } diff --git a/src/main/resources/db/migration/V8.1__add_unique_constraint_on_bookmark.sql b/src/main/resources/db/migration/V8.1__add_unique_constraint_on_bookmark.sql new file mode 100644 index 0000000..4891b2b --- /dev/null +++ b/src/main/resources/db/migration/V8.1__add_unique_constraint_on_bookmark.sql @@ -0,0 +1,7 @@ +ALTER TABLE code_post_bookmark + ADD CONSTRAINT uq_codepost_member + UNIQUE (code_post_id, member_id); + +ALTER TABLE tilbookmark + ADD CONSTRAINT uq_tilpost_email + UNIQUE (tilpost_id, member_email); From f493c5bbb9f699d82c0d2e47db904861dd518dfa Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Mon, 2 Jun 2025 01:26:15 +0900 Subject: [PATCH 21/66] =?UTF-8?q?style:=20=EB=A7=A4=EA=B0=9C=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EC=88=9C=EC=84=9C=20=EC=98=A4=EB=A5=98=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 --- .../org/example/autoreview/domain/bookmark/BookmarkHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/example/autoreview/domain/bookmark/BookmarkHelper.java b/src/main/java/org/example/autoreview/domain/bookmark/BookmarkHelper.java index c791fe6..06cbef9 100644 --- a/src/main/java/org/example/autoreview/domain/bookmark/BookmarkHelper.java +++ b/src/main/java/org/example/autoreview/domain/bookmark/BookmarkHelper.java @@ -21,7 +21,7 @@ public Long trySave(CodePostBookmarkSaveRequestDto requestDto, Member member) { @Transactional public Long fallbackToUpdate(Long codePostId, Long memberId) { - return codePostBookmarkRepository.findById(codePostId, memberId) + return codePostBookmarkRepository.findById(memberId, codePostId) .map(CodePostBookmark::update) .orElseThrow(() -> new IllegalStateException("Bookmark should exist but was not found")); } From 0166889cf2392034555a0b2ea62f856743232b5f Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Thu, 5 Jun 2025 14:08:55 +0900 Subject: [PATCH 22/66] =?UTF-8?q?refactor:=20package=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bookmark/BookmarkHelper.java | 28 --- .../service/CodePostBookmarkCommand.java | 29 +++ .../service/CodePostBookmarkService.java | 28 ++- .../controller/CodePostController.java | 20 +- .../codepost/service/CodePostCommand.java | 59 ++++++ .../codepost/service/CodePostDtoService.java | 93 --------- .../codepost/service/CodePostService.java | 196 ++++++++++++------ .../domain/comment/base/CommentCommand.java | 32 +++ .../domain/comment/base/CommentService.java | 43 ++-- .../service/CodePostCommentCommand.java | 1 + .../service/CodePostCommentService.java | 17 +- .../fcm/controller/FcmTokenController.java | 6 +- .../domain/fcm/entity/FcmToken.java | 4 +- .../domain/fcm/service/FcmTokenCommand.java | 29 +++ .../fcm/service/FcmTokenMemberService.java | 20 -- .../domain/fcm/service/FcmTokenService.java | 43 +--- .../domain/github/entity/GithubToken.java | 4 +- .../domain/github/service/GithubCommand.java | 39 ++++ .../domain/github/service/GithubService.java | 62 ++---- .../domain/member/service/MemberCommand.java | 3 +- .../controller/NotificationController.java | 27 ++- .../service/NotificationCommand.java | 70 +++++++ .../service/NotificationDtoService.java | 81 -------- .../service/NotificationService.java | 167 ++++++++------- .../review/service/ReviewCodePostService.java | 11 +- .../scheduler/NotificationScheduler.java | 8 +- .../service/CodePostBookmarkServiceTest.java | 101 +++++++++ 27 files changed, 708 insertions(+), 513 deletions(-) delete mode 100644 src/main/java/org/example/autoreview/domain/bookmark/BookmarkHelper.java delete mode 100644 src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java create mode 100644 src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenCommand.java delete mode 100644 src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenMemberService.java create mode 100644 src/main/java/org/example/autoreview/domain/github/service/GithubCommand.java delete mode 100644 src/main/java/org/example/autoreview/domain/notification/service/NotificationDtoService.java create mode 100644 src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceTest.java diff --git a/src/main/java/org/example/autoreview/domain/bookmark/BookmarkHelper.java b/src/main/java/org/example/autoreview/domain/bookmark/BookmarkHelper.java deleted file mode 100644 index 06cbef9..0000000 --- a/src/main/java/org/example/autoreview/domain/bookmark/BookmarkHelper.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.example.autoreview.domain.bookmark; - -import lombok.RequiredArgsConstructor; -import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.request.CodePostBookmarkSaveRequestDto; -import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmark; -import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmarkRepository; -import org.example.autoreview.domain.member.entity.Member; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@RequiredArgsConstructor -@Service -public class BookmarkHelper { - - private final CodePostBookmarkRepository codePostBookmarkRepository; - - @Transactional - public Long trySave(CodePostBookmarkSaveRequestDto requestDto, Member member) { - return codePostBookmarkRepository.save(requestDto.toEntity(member)).getId(); - } - - @Transactional - public Long fallbackToUpdate(Long codePostId, Long memberId) { - return codePostBookmarkRepository.findById(memberId, codePostId) - .map(CodePostBookmark::update) - .orElseThrow(() -> new IllegalStateException("Bookmark should exist but was not found")); - } -} diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkCommand.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkCommand.java index f082fad..6f3852c 100644 --- a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkCommand.java +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkCommand.java @@ -1,8 +1,15 @@ package org.example.autoreview.domain.bookmark.CodePostBookmark.service; import lombok.RequiredArgsConstructor; +import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.request.CodePostBookmarkSaveRequestDto; +import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkResponseDto; import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmark; import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmarkRepository; +import org.example.autoreview.domain.member.entity.Member; +import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; +import org.example.autoreview.global.exception.errorcode.ErrorCode; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -14,8 +21,30 @@ public class CodePostBookmarkCommand { private final CodePostBookmarkRepository codePostBookmarkRepository; + @Transactional + public Long trySave(CodePostBookmarkSaveRequestDto requestDto, Member member) { + return codePostBookmarkRepository.save(requestDto.toEntity(member)).getId(); + } + + @Transactional + public Long fallbackToUpdate(Long codePostId, Long memberId) { + return codePostBookmarkRepository.findById(memberId, codePostId) + .map(CodePostBookmark::update) + .orElseThrow(() -> new CustomRuntimeException(ErrorCode.NOT_FOUND_BOOKMARK)); + } + + @Transactional + public void deleteExpiredSoftDeletedBookmarks() { + codePostBookmarkRepository.deleteExpiredSoftDeletedBookmarks(); + } + @Transactional(readOnly = true) public Optional findByCodePostBookmark(Long memberId, Long codePostId) { return codePostBookmarkRepository.findById(memberId,codePostId); } + + @Transactional(readOnly = true) + public Page findAllByMemberId(Long memberId, Pageable pageable) { + return codePostBookmarkRepository.findAllByMemberId(memberId, pageable); + } } diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java index 62160a7..64e59fb 100644 --- a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java @@ -2,48 +2,54 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.example.autoreview.domain.bookmark.BookmarkHelper; import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.request.CodePostBookmarkSaveRequestDto; import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkListResponseDto; import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkResponseDto; -import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmarkRepository; import org.example.autoreview.domain.member.entity.Member; import org.example.autoreview.domain.member.service.MemberCommand; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Slf4j @RequiredArgsConstructor @Service public class CodePostBookmarkService { - private final CodePostBookmarkRepository codePostBookmarkRepository; - private final BookmarkHelper bookmarkHelper; + private final CodePostBookmarkCommand codePostBookmarkCommand; private final MemberCommand memberCommand; + /** + * 북마크가 없을 시 생성, 있으면 상태 변경하는 메서드이다. + * 1. DB에 code_post_id, member_id로 UNIQUE 제약 조건 설정 + * 2. UNIQUE 제약 조건 위반 시 catch를 통해 update() 로직 실행 + */ public Long saveOrUpdate(CodePostBookmarkSaveRequestDto requestDto, String email) { Member member = memberCommand.findByEmail(email); try { - return bookmarkHelper.trySave(requestDto, member); + return codePostBookmarkCommand.trySave(requestDto, member); } catch (DataIntegrityViolationException e) { - return bookmarkHelper.fallbackToUpdate(requestDto.codePostId(), member.getId()); + return codePostBookmarkCommand.fallbackToUpdate(requestDto.codePostId(), member.getId()); } } - @Transactional(readOnly = true) + /** + * 회원 아이디를 통해 북마크한 포스트를 조회하는 메서드이다. + */ public CodePostBookmarkListResponseDto findAllByMemberId(String email, Pageable pageable) { Long memberId = memberCommand.findByEmail(email).getId(); - Page pageDto = codePostBookmarkRepository.findAllByMemberId(memberId,pageable); + Page pageDto = codePostBookmarkCommand.findAllByMemberId(memberId,pageable); return new CodePostBookmarkListResponseDto(pageDto.getContent(),pageDto.getTotalPages()); } - @Transactional + /** + * 북마크 Table에서 soft delete 되어있는 컬럼을 전부 삭제하는 메서드이다. + * Scheduler에서 쓰일 예정 + */ public void deleteExpiredSoftDeletedBookmarks() { - codePostBookmarkRepository.deleteExpiredSoftDeletedBookmarks(); + codePostBookmarkCommand.deleteExpiredSoftDeletedBookmarks(); } } diff --git a/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java b/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java index 1fae416..013ba9f 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java +++ b/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java @@ -7,7 +7,7 @@ import org.example.autoreview.domain.codepost.dto.request.CodePostUpdateRequestDto; import org.example.autoreview.domain.codepost.dto.response.CodePostListResponseDto; import org.example.autoreview.domain.codepost.dto.response.CodePostResponseDto; -import org.example.autoreview.domain.codepost.service.CodePostDtoService; +import org.example.autoreview.domain.codepost.service.CodePostService; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; @@ -21,27 +21,27 @@ @RequestMapping("/v1/api/post/code") public class CodePostController { - private final CodePostDtoService codePostMemberService; + private final CodePostService codePostService; @Operation(summary = "코드 포스트 생성", description = "코드 포스트 생성") @PostMapping public ResponseEntity save(@RequestBody CodePostSaveRequestDto requestDto, @AuthenticationPrincipal UserDetails userDetails) { - return ResponseEntity.ok().body(codePostMemberService.postSave(requestDto, userDetails.getUsername())); + return ResponseEntity.ok().body(codePostService.save(requestDto, userDetails.getUsername())); } @Operation(summary = "제목으로 코드 포스트 검색", description = "공백 또는 null 입력 시 에러 반환") @GetMapping("/search") public ResponseEntity search(@RequestParam String keyword, @PageableDefault(page = 0, size = 9) Pageable pageable) { - return ResponseEntity.ok().body(codePostMemberService.postSearch(keyword, pageable)); + return ResponseEntity.ok().body(codePostService.search(keyword, pageable)); } @Operation(summary = "코드 포스트 단일 조회", description = "공개된 포스트 or 작성자일 경우만 조회됨 + 북마크 여부 포함") @GetMapping("/detail/{id}") public ResponseEntity view(@PathVariable("id") Long codePostId, @AuthenticationPrincipal UserDetails userDetails) { - return ResponseEntity.ok().body(codePostMemberService.findPostById(codePostId,userDetails.getUsername())); + return ResponseEntity.ok().body(codePostService.findById(codePostId,userDetails.getUsername())); } @Operation(summary = "코드 포스트 전체 조회", description = "코드 포스트 전체 조회(비공개 포스트는 제외)") @@ -51,14 +51,14 @@ public ResponseEntity viewAll(@RequestParam(defaultValu @RequestParam(defaultValue = "desc") String direction, @RequestParam(defaultValue = "id") String sortBy, @RequestParam(defaultValue = "all") String language) { - return ResponseEntity.ok().body(codePostMemberService.findPostByPage(page,size,direction,sortBy,language)); + return ResponseEntity.ok().body(codePostService.findPostByPage(page,size,direction,sortBy,language)); } @Operation(summary = "내가 쓴 코드 포스트 조회", description = "내가 쓴 코드 포스트 조회") @GetMapping("/own") public ResponseEntity myCodePostPage(@PageableDefault(page = 0, size = 9) Pageable pageable, @AuthenticationPrincipal UserDetails userDetails) { - return ResponseEntity.ok().body(codePostMemberService.findPostByMemberId(pageable, userDetails.getUsername())); + return ResponseEntity.ok().body(codePostService.findByMemberId(pageable, userDetails.getUsername())); } @Operation(summary = "내 코드 포스트 검색", description = "내 코드 포스트 검색") @@ -66,21 +66,21 @@ public ResponseEntity myCodePostPage(@PageableDefault(p public ResponseEntity mySearch(@RequestParam String keyword, @PageableDefault(page = 0, size = 9) Pageable pageable, @AuthenticationPrincipal UserDetails userDetails) { - return ResponseEntity.ok().body(codePostMemberService.postMySearch(keyword, pageable, userDetails.getUsername())); + return ResponseEntity.ok().body(codePostService.mySearch(keyword, pageable, userDetails.getUsername())); } @Operation(summary = "코드 포스트 수정", description = "코드 포스트 수정") @PutMapping public ResponseEntity update(@RequestBody CodePostUpdateRequestDto requestDto, @AuthenticationPrincipal UserDetails userDetails) { - return ResponseEntity.ok().body(codePostMemberService.postUpdate(requestDto, userDetails.getUsername())); + return ResponseEntity.ok().body(codePostService.update(requestDto, userDetails.getUsername())); } @Operation(summary = "코드 포스트 삭제", description = "코드 포스트 삭제") @DeleteMapping("/{id}") public ResponseEntity delete(@PathVariable("id") Long codePostId, @AuthenticationPrincipal UserDetails userDetails) { - return ResponseEntity.ok().body(codePostMemberService.postDelete(codePostId, userDetails.getUsername())); + return ResponseEntity.ok().body(codePostService.delete(codePostId, userDetails.getUsername())); } } diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostCommand.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostCommand.java index 6f37934..73447da 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostCommand.java +++ b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostCommand.java @@ -1,11 +1,17 @@ package org.example.autoreview.domain.codepost.service; import lombok.RequiredArgsConstructor; +import org.example.autoreview.domain.codepost.dto.request.CodePostUpdateRequestDto; +import org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto; import org.example.autoreview.domain.codepost.entity.CodePost; import org.example.autoreview.domain.codepost.entity.CodePostRepository; +import org.example.autoreview.domain.codepost.entity.Language; import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; import org.example.autoreview.global.exception.errorcode.ErrorCode; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor @Component @@ -13,9 +19,62 @@ public class CodePostCommand { private final CodePostRepository codePostRepository; + @Transactional + public CodePost save(CodePost codePost) { + return codePostRepository.save(codePost); + } + + @Transactional(readOnly = true) public CodePost findById(Long id) { return codePostRepository.findById(id).orElseThrow( () -> new CustomRuntimeException(ErrorCode.NOT_FOUND_POST) ); } + + @Transactional(readOnly = true) + public CodePost findByIdIsPublic(Long codePostId, Long memberId) { + return codePostRepository.findByIdIsPublic(codePostId, memberId).orElseThrow( + () -> new CustomRuntimeException(ErrorCode.NOT_FOUND_POST) + ); + } + + @Transactional(readOnly = true) + public Page search(String wildcardKeyword, Pageable pageable) { + return codePostRepository.search(wildcardKeyword, pageable); + } + + @Transactional(readOnly = true) + public Page findByMemberId(Long memberId, Pageable pageable) { + return codePostRepository.findByMemberId(memberId, pageable); + } + + @Transactional(readOnly = true) + public Page mySearch(String keyword, Pageable pageable, Long memberId) { + return codePostRepository.mySearch(keyword, pageable, memberId); + } + + @Transactional(readOnly = true) + public Page findByPage(Pageable pageable, Language language) { + return codePostRepository.findByPage(pageable, language); + } + + @Transactional(readOnly = true) + public Page findByPageSortByCommentCountDesc(Pageable pageable, Language language) { + return codePostRepository.findByPageSortByCommentCountDesc(pageable, language); + } + + @Transactional(readOnly = true) + public Page findByPageSortByCommentCountAsc(Pageable pageable, Language language) { + return codePostRepository.findByPageSortByCommentCountAsc(pageable, language); + } + + @Transactional + public void update(CodePost codePost, CodePostUpdateRequestDto requestDto) { + codePost.update(requestDto); + } + + @Transactional + public void delete(CodePost codePost) { + codePostRepository.delete(codePost); + } } diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java deleted file mode 100644 index ce33233..0000000 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostDtoService.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.example.autoreview.domain.codepost.service; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.example.autoreview.domain.codepost.dto.request.CodePostSaveRequestDto; -import org.example.autoreview.domain.codepost.dto.request.CodePostUpdateRequestDto; -import org.example.autoreview.domain.codepost.dto.response.CodePostListResponseDto; -import org.example.autoreview.domain.codepost.dto.response.CodePostResponseDto; -import org.example.autoreview.domain.codepost.entity.CodePost; -import org.example.autoreview.domain.codepost.entity.Language; -import org.example.autoreview.domain.member.entity.Member; -import org.example.autoreview.domain.member.service.MemberService; -import org.example.autoreview.domain.notification.enums.NotificationStatus; -import org.example.autoreview.domain.notification.service.NotificationService; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; - -@Slf4j -@RequiredArgsConstructor -@Service -public class CodePostDtoService { - - private final CodePostService codePostService; - private final MemberService memberService; - private final NotificationService notificationService; - - public Long postSave(CodePostSaveRequestDto requestDto, String email){ - Member member = memberService.findByEmail(email); - CodePost codePost = codePostService.save(requestDto, member); - - if (requestDto.reviewDay() != null) { - notificationService.save(member, codePost); - } - - return codePost.getId(); - } - - public CodePostResponseDto findPostById(Long id, String email){ - return codePostService.findById(id,email); - } - - public CodePostListResponseDto postSearch(String keyword, Pageable pageable){ - return codePostService.search(keyword, pageable); - } - - public CodePostListResponseDto postMySearch(String keyword, Pageable pageable, String email){ - Member member = memberService.findByEmail(email); - return codePostService.mySearch(keyword, pageable, member); - } - - public CodePostListResponseDto findPostByMemberId(Pageable pageable, String email){ - Member member = memberService.findByEmail(email); - return codePostService.findByMemberId(pageable, member); - } - - public CodePostListResponseDto findPostByPage(int page, int size, String direction, String sortBy, String language){ - Language lang = Language.of(language); - if(sortBy.equals("commentCount")) { - Pageable pageable = PageRequest.of(page, size); - return codePostService.findByPageSortByCommentCount(pageable,direction,lang); - } - Sort.Direction sortDirection = Sort.Direction.fromString(direction); - Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection,sortBy)); - return codePostService.findByPage(pageable,lang); - } - - public Long postUpdate(CodePostUpdateRequestDto requestDto, String email) { - CodePost codePost = codePostService.update(requestDto, email); - boolean notificationExists = notificationService.existsByCodePostId(requestDto.getId()); - - if (notificationExists) { - if (requestDto.getReviewDay() == null) { - notificationService.delete(email, codePost.getId()); - } else { - notificationService.update(email, codePost, NotificationStatus.PENDING); - } - } else { - Member member = memberService.findByEmail(email); - notificationService.save(member, codePost); - } - - return codePost.getId(); - } - - public Long postDelete(Long id, String email){ - if (notificationService.existsByCodePostId(id)) { - notificationService.delete(email,id); - } - return codePostService.delete(id, email); - } -} diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java index 346e928..a206d8c 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java +++ b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java @@ -10,17 +10,20 @@ import org.example.autoreview.domain.codepost.dto.response.CodePostResponseDto; import org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto; import org.example.autoreview.domain.codepost.entity.CodePost; -import org.example.autoreview.domain.codepost.entity.CodePostRepository; import org.example.autoreview.domain.codepost.entity.Language; import org.example.autoreview.domain.member.entity.Member; import org.example.autoreview.domain.member.service.MemberCommand; +import org.example.autoreview.domain.notification.entity.Notification; +import org.example.autoreview.domain.notification.enums.NotificationStatus; +import org.example.autoreview.domain.notification.service.NotificationCommand; import org.example.autoreview.domain.review.dto.response.ReviewResponseDto; import org.example.autoreview.domain.review.entity.Review; import org.example.autoreview.global.exception.errorcode.ErrorCode; import org.example.autoreview.global.exception.sub_exceptions.BadRequestException; -import org.example.autoreview.global.exception.sub_exceptions.NotFoundException; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -29,34 +32,66 @@ import java.util.stream.Collectors; @Slf4j -@Transactional(readOnly = true) @RequiredArgsConstructor @Service public class CodePostService { - private final CodePostRepository codePostRepository; + private final CodePostCommand codePostCommand; private final MemberCommand memberCommand; private final CodePostBookmarkCommand codePostBookmarkCommand; + private final NotificationCommand notificationCommand; - @Transactional - public CodePost save(CodePostSaveRequestDto requestDto, Member member) { - CodePost codePost = requestDto.toEntity(member); - return codePostRepository.save(codePost); + /** + * 코드 포스트 저장 메서드이다. + * 복습일을 설정했을 경우에 notification Entity 를 저장한다. + */ + public Long save(CodePostSaveRequestDto requestDto, String email) { + Member member = memberCommand.findByEmail(email); + CodePost codePost = codePostCommand.save(requestDto.toEntity(member)); + + if (requestDto.reviewDay() != null) { + Notification notification = Notification.builder() + .title("ORI 복습 알림") + .content(codePost.getTitle()) + .status(NotificationStatus.PENDING) + .executeTime(codePost.getReviewDay()) + .member(member) + .codePostId(codePost.getId()) + .build(); + notificationCommand.save(notification); + } + return codePost.getId(); } - public CodePost findEntityById(Long id) { - return codePostRepository.findById(id).orElseThrow( - () -> new NotFoundException(ErrorCode.NOT_FOUND_POST) - ); + /** + * 키워드에 맞는 제목을 가진 코드 포스트를 조회하는 검색 메서드이다. + * 키워드 뒤에 와일드 카드를 붙혀서 연관된 결과도 조회한다. + */ + @Transactional(readOnly = true) + public CodePostListResponseDto search(String keyword, Pageable pageable) { + keywordValidator(keyword); + String wildcardKeyword = keyword + "*"; + Page codePostPage = codePostCommand.search(wildcardKeyword, pageable) + .map(post -> { + Member member = memberCommand.findById(post.getWriterId()); + return new CodePostThumbnailResponseDto(post, member); + }); + + return new CodePostListResponseDto(codePostPage.getContent(), codePostPage.getTotalPages()); } - public CodePostResponseDto findById(Long id,String email) { + /** + * code_post_id를 통해 단일 조회하는 메서드이다. + * 1. member_id를 통해 해당 글이 공개인지 비공개인지 결정한다. + * 2. 반환값에서는 포스트, 작성자, 리뷰, 북마크 여부를 포함한다. + */ + @Transactional(readOnly = true) + public CodePostResponseDto findById(Long id, String email) { Member member = memberCommand.findByEmail(email); - CodePost codePost = codePostRepository.findByIdIsPublic(id,member.getId()).orElseThrow( - () -> new NotFoundException(ErrorCode.NOT_FOUND_POST) - ); + CodePost codePost = codePostCommand.findByIdIsPublic(id, member.getId()); Member writer = memberCommand.findById(codePost.getWriterId()); - Optional codePostBookmark = codePostBookmarkCommand.findByCodePostBookmark(member.getId(),codePost.getId()); + Optional codePostBookmark = codePostBookmarkCommand.findByCodePostBookmark(member.getId(), codePost.getId()); + boolean isBookmarked = false; if (codePostBookmark.isPresent()) { isBookmarked = !codePostBookmark.get().isDeleted(); @@ -70,27 +105,53 @@ public CodePostResponseDto findById(Long id,String email) { return new CodePostResponseDto(codePost, dtoList, writer, isBookmarked); } - public CodePostListResponseDto search(String keyword, Pageable pageable) { - keywordValidator(keyword); - String wildcardKeyword = keyword + "*"; - Page codePostPage = codePostRepository.search(wildcardKeyword, pageable) - .map(post -> { - Member member = memberCommand.findById(post.getWriterId()); - return new CodePostThumbnailResponseDto(post, member); - }); + /** + * 페이지네이션 정보와 필터할 키워드를 통해 포스트를 페이징 조회하는 메서드이다. + * 1.[direction: 오(내)림차순] / [sortBy: 정렬 기준] / [language: 프로그래밍 언어] + * 2. 정렬 기준이 commentCount 일 때에는 CodePost Entity 에 존재하지 않는 컬럼이기 때문에 따로 페이징 처리한다. + */ + public CodePostListResponseDto findPostByPage(int page, int size, String direction, String sortBy, String language){ + Language lang = Language.of(language); + if(sortBy.equals("commentCount")) { + Pageable pageable = PageRequest.of(page, size); + return findByPageSortByCommentCount(pageable,direction,lang); + } + Sort.Direction sortDirection = Sort.Direction.fromString(direction); + Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection,sortBy)); + return findByPage(pageable,lang); + } - return new CodePostListResponseDto(codePostPage.getContent(), codePostPage.getTotalPages()); + private CodePostListResponseDto findByPageSortByCommentCount(Pageable pageable, String direction, Language language) { + if (direction.equals("desc")) { + Page page = codePostCommand.findByPageSortByCommentCountDesc(pageable, language); + return new CodePostListResponseDto(page.getContent(), page.getTotalPages()); + } + Page page = codePostCommand.findByPageSortByCommentCountAsc(pageable, language); + return new CodePostListResponseDto(page.getContent(), page.getTotalPages()); } - public CodePostListResponseDto findByMemberId(Pageable pageable, Member member) { - Page codePostPage = codePostRepository.findByMemberId(member.getId(), pageable); - return new CodePostListResponseDto(convertListDto(codePostPage,member), codePostPage.getTotalPages()); + private CodePostListResponseDto findByPage(Pageable pageable, Language language) { + Page page = codePostCommand.findByPage(pageable, language); + return new CodePostListResponseDto(page.getContent(), page.getTotalPages()); } - public CodePostListResponseDto mySearch(String keyword, Pageable pageable, Member member) { + /** + * 사용자가 작성한 모든 코드 포스트를 조회하는 메서드이다. + */ + public CodePostListResponseDto findByMemberId(Pageable pageable, String email) { + Member member = memberCommand.findByEmail(email); + Page codePostPage = codePostCommand.findByMemberId(member.getId(), pageable); + return new CodePostListResponseDto(convertListDto(codePostPage, member), codePostPage.getTotalPages()); + } + + /** + * 사용자가 작성한 포스트들 중 키워드에 맞는 포스트들을 조회하는 메서드이다. + */ + public CodePostListResponseDto mySearch(String keyword, Pageable pageable, String email) { + Member member = memberCommand.findByEmail(email); keywordValidator(keyword); - Page codePostPage = codePostRepository.mySearch(keyword, pageable, member.getId()); - return new CodePostListResponseDto(convertListDto(codePostPage,member), codePostPage.getTotalPages()); + Page codePostPage = codePostCommand.mySearch(keyword, pageable, member.getId()); + return new CodePostListResponseDto(convertListDto(codePostPage, member), codePostPage.getTotalPages()); } private static void keywordValidator(String keyword) { @@ -101,7 +162,7 @@ private static void keywordValidator(String keyword) { private List convertListDto(Page page, Member member) { return page.stream() - .map(post -> getCodePostThumbnailResponseDto(post,member)) + .map(post -> getCodePostThumbnailResponseDto(post, member)) .collect(Collectors.toList()); } @@ -109,38 +170,57 @@ private CodePostThumbnailResponseDto getCodePostThumbnailResponseDto(CodePost co return new CodePostThumbnailResponseDto(codePost, member); } - public CodePostListResponseDto findByPage(Pageable pageable, Language language) { - Page page = codePostRepository.findByPage(pageable,language); - - return new CodePostListResponseDto(page.getContent(), page.getTotalPages()); - } - - public CodePostListResponseDto findByPageSortByCommentCount(Pageable pageable, String direction, Language language) { - if (direction.equals("desc")) { - Page page = codePostRepository.findByPageSortByCommentCountDesc(pageable,language); - return new CodePostListResponseDto(page.getContent(), page.getTotalPages()); + /** + * 사용자 유효성 검사 후 포스트를 업데이트하는 메서드이다. + * 1. 복습일 설정을 on 에서 off 로 변경할 경우 삭제 + * 2. 복습일 설정을 on 에서 on 으로 변경할 경우 업데이트 + * 3. 복습일 설정을 off 에서 on 으로 변경할 경우 저장 + */ + public Long update(CodePostUpdateRequestDto requestDto, String email) { + CodePost codePost = codePostCommand.findById(requestDto.getId()); + memberValidator(email, codePost); + Member member = memberCommand.findByEmail(email); + codePostCommand.update(codePost, requestDto); + + boolean notificationExists = notificationCommand.existsByCodePostId(requestDto.getId()); + + if (notificationExists) { + Notification notification = notificationCommand.findByCodePostId(codePost.getId()); + if (requestDto.getReviewDay() == null) { + notificationCommand.delete(notification); + } else { + notificationCommand.update(notification, codePost, NotificationStatus.PENDING); + } + } else if (requestDto.getReviewDay() != null){ + notificationSave(member, requestDto); } - Page page = codePostRepository.findByPageSortByCommentCountAsc(pageable,language); - return new CodePostListResponseDto(page.getContent(), page.getTotalPages()); + return codePost.getId(); } - @Transactional - public CodePost update(CodePostUpdateRequestDto requestDto, String email) { - CodePost codePost = codePostRepository.findById(requestDto.getId()).orElseThrow( - () -> new NotFoundException(ErrorCode.NOT_FOUND_POST) - ); - memberValidator(email, codePost); - codePost.update(requestDto); - return codePost; + private void notificationSave(Member member, CodePostUpdateRequestDto requestDto) { + Notification notification = Notification.builder() + .title("ORI 복습 알림") + .content(requestDto.getTitle()) + .status(NotificationStatus.PENDING) + .executeTime(requestDto.getReviewDay()) + .member(member) + .codePostId(requestDto.getId()) + .build(); + notificationCommand.save(notification); } - @Transactional + /** + * 사용자 유효성 검사 후 포스트를 삭제하는 메서드이다. + * 1. 해당 포스트에 알림이 존재할 경우 삭제한다. + */ public Long delete(Long id, String email) { - CodePost codePost = codePostRepository.findById(id).orElseThrow( - () -> new NotFoundException(ErrorCode.NOT_FOUND_POST) - ); + if (notificationCommand.existsByCodePostId(id)) { + Notification notification = notificationCommand.findByCodePostId(id); + notificationCommand.delete(notification); + } + CodePost codePost = codePostCommand.findById(id); memberValidator(email, codePost); - codePostRepository.delete(codePost); + codePostCommand.delete(codePost); return id; } diff --git a/src/main/java/org/example/autoreview/domain/comment/base/CommentCommand.java b/src/main/java/org/example/autoreview/domain/comment/base/CommentCommand.java index da53cc6..39eee27 100644 --- a/src/main/java/org/example/autoreview/domain/comment/base/CommentCommand.java +++ b/src/main/java/org/example/autoreview/domain/comment/base/CommentCommand.java @@ -1,17 +1,49 @@ package org.example.autoreview.domain.comment.base; import lombok.RequiredArgsConstructor; +import org.example.autoreview.domain.comment.base.dto.request.CommentUpdateRequestDto; import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; import org.example.autoreview.global.exception.errorcode.ErrorCode; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor public abstract class CommentCommand> { protected final R repository; + @Transactional + public C save(C comment) { + return repository.save(comment); + } + + @Transactional(readOnly = true) public C findById(Long id) { return repository.findById(id).orElseThrow( () -> new CustomRuntimeException(ErrorCode.NOT_FOUND_COMMENT) ); } + + @Transactional(readOnly = true) + public Page findByCommentPage(Long id, Pageable pageable) { + return repository.findByCommentPage(id, pageable); + } + + @Transactional(readOnly = true) + public Page findByReplyPage(Long id, Long parentId, Pageable pageable) { + return repository.findByReplyPage(id, parentId, pageable); + } + + @Transactional + public void update(C comment, CommentUpdateRequestDto requestDto) { + comment.update(requestDto); + } + + @Transactional + public void delete(C comment) { + repository.delete(comment); + } + + } diff --git a/src/main/java/org/example/autoreview/domain/comment/base/CommentService.java b/src/main/java/org/example/autoreview/domain/comment/base/CommentService.java index 0f7d421..33145c9 100644 --- a/src/main/java/org/example/autoreview/domain/comment/base/CommentService.java +++ b/src/main/java/org/example/autoreview/domain/comment/base/CommentService.java @@ -12,7 +12,6 @@ import org.example.autoreview.global.exception.errorcode.ErrorCode; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.List; @@ -21,37 +20,41 @@ public abstract class CommentService> { private final String SECRETE_COMMENT = "비밀 댓글 입니다."; - protected final R commentRepository; - protected final CommentCommand commentCommand; + protected final CommentCommand commentCommand; protected final MemberCommand memberCommand; - @Transactional + /** + * (대)댓글 저장하는 메서드이다. + * parent_id가 존재할 경우 대댓글로서 저장 + * 그렇지 않으면 댓글로 저장 + */ public Long save(CommentSaveRequestDto requestDto, String email) { Member writer = memberCommand.findByEmail(email); if (requestDto.parentId() != null) { C parent = commentCommand.findById(requestDto.parentId()); - return commentRepository.save(createReplyEntity(requestDto, parent, writer)).getId(); + return commentCommand.save(createReplyEntity(requestDto, parent, writer)).getId(); } - return commentRepository.save(createCommentEntity(requestDto, writer)).getId(); + return commentCommand.save(createCommentEntity(requestDto, writer)).getId(); } protected abstract C createReplyEntity(CommentSaveRequestDto requestDto, C parent, Member writer); + protected abstract C createCommentEntity(CommentSaveRequestDto requestDto, Member writer); // User 상위 댓글 조회 public CommentListResponseDto userFindCommentPage(Long postId, Pageable pageable, String email) { - Page commentPage = commentRepository.findByCommentPage(postId, pageable); + Page commentPage = commentCommand.findByCommentPage(postId, pageable); List dtoList = new ArrayList<>(); for (C c : commentPage.getContent()) { Member writer = memberCommand.findById(c.getWriterId()); // 공개 or 게시글 작성자 or 댓글 작성자 일 경우 - if (c.isPublic() || isPostWriter(postId,email) || writer.getEmail().equals(email)) { - dtoList.add(new CommentResponseDto(c,c.getBody(),writer.getEmail(),writer.getNickname())); + if (c.isPublic() || isPostWriter(postId, email) || writer.getEmail().equals(email)) { + dtoList.add(new CommentResponseDto(c, c.getBody(), writer.getEmail(), writer.getNickname())); continue; } - dtoList.add(new CommentResponseDto(c,SECRETE_COMMENT,writer.getEmail(),writer.getNickname())); + dtoList.add(new CommentResponseDto(c, SECRETE_COMMENT, writer.getEmail(), writer.getNickname())); } return new CommentListResponseDto(dtoList, commentPage.getTotalPages()); @@ -61,24 +64,24 @@ public CommentListResponseDto userFindCommentPage(Long postId, Pageable pageable // User 하위 댓글 조회 public CommentListResponseDto userFindReplyPage(Long postId, Long parentId, Pageable pageable, String email) { - Page replyPage = commentRepository.findByReplyPage(postId, parentId, pageable); + Page replyPage = commentCommand.findByReplyPage(postId, parentId, pageable); List dtoList = new ArrayList<>(); for (C c : replyPage.getContent()) { Member writer = memberCommand.findById(c.getWriterId()); // 공개 or 언급된 사용자 or 댓글 작성자 일 경우 if (c.isPublic() || c.getMentionEmail().equals(email) || writer.getEmail().equals(email)) { - dtoList.add(new CommentResponseDto(c,c.getBody(),writer.getEmail(),writer.getNickname())); + dtoList.add(new CommentResponseDto(c, c.getBody(), writer.getEmail(), writer.getNickname())); continue; } - dtoList.add(new CommentResponseDto(c,SECRETE_COMMENT,writer.getEmail(),writer.getNickname())); + dtoList.add(new CommentResponseDto(c, SECRETE_COMMENT, writer.getEmail(), writer.getNickname())); } return new CommentListResponseDto(dtoList, replyPage.getTotalPages()); } // Guest 상위 댓글 조회 public CommentListResponseDto guestFindCommentPage(Long postId, Pageable pageable) { - Page commentPage = commentRepository.findByCommentPage(postId, pageable); + Page commentPage = commentCommand.findByCommentPage(postId, pageable); List dtoList = new ArrayList<>(); return getCommentListResponseDto(commentPage, dtoList); @@ -86,7 +89,7 @@ public CommentListResponseDto guestFindCommentPage(Long postId, Pageable pageabl // Guest 하위 댓글 조회 public CommentListResponseDto guestFindReplyPage(Long postId, Long parentId, Pageable pageable) { - Page replyPage = commentRepository.findByReplyPage(postId, parentId, pageable); + Page replyPage = commentCommand.findByReplyPage(postId, parentId, pageable); List dtoList = new ArrayList<>(); return getCommentListResponseDto(replyPage, dtoList); @@ -96,30 +99,28 @@ private CommentListResponseDto getCommentListResponseDto(Page replyPage, List for (C c : replyPage.getContent()) { Member writer = memberCommand.findById(c.getWriterId()); if (c.isPublic()) { - dtoList.add(new CommentResponseDto(c,c.getBody(),writer.getEmail(),writer.getNickname())); + dtoList.add(new CommentResponseDto(c, c.getBody(), writer.getEmail(), writer.getNickname())); continue; } - dtoList.add(new CommentResponseDto(c,SECRETE_COMMENT,writer.getEmail(),writer.getNickname())); + dtoList.add(new CommentResponseDto(c, SECRETE_COMMENT, writer.getEmail(), writer.getNickname())); } return new CommentListResponseDto(dtoList, replyPage.getTotalPages()); } // 수정 - @Transactional public Long update(CommentUpdateRequestDto requestDto, String email) { C comment = commentCommand.findById(requestDto.commentId()); memberValidator(comment.getWriterId(), email); - comment.update(requestDto); + commentCommand.update(comment, requestDto); return comment.getId(); } // 삭제 - @Transactional public Long delete(CommentDeleteRequestDto requestDto, String email) { C comment = commentCommand.findById(requestDto.commentId()); memberValidator(comment.getWriterId(), email); - commentRepository.delete(comment); // deleteById 는 내부적으로 findById가 존재해서 조회가 한 번 더 일어남 + commentCommand.delete(comment); // deleteById 는 내부적으로 findById가 존재해서 조회가 한 번 더 일어남 return requestDto.commentId(); } diff --git a/src/main/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentCommand.java b/src/main/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentCommand.java index 475d7d9..ce86666 100644 --- a/src/main/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentCommand.java +++ b/src/main/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentCommand.java @@ -11,4 +11,5 @@ public class CodePostCommentCommand extends CommentCommand save(@RequestBody FcmTokenSaveRequestDto requestDto, @AuthenticationPrincipal UserDetails userDetails) { - return ResponseEntity.ok().body(fcmTokenMemberService.save(requestDto, userDetails.getUsername())); + return ResponseEntity.ok().body(fcmTokenService.save(requestDto, userDetails.getUsername())); } } diff --git a/src/main/java/org/example/autoreview/domain/fcm/entity/FcmToken.java b/src/main/java/org/example/autoreview/domain/fcm/entity/FcmToken.java index a8e9ab1..4d47786 100644 --- a/src/main/java/org/example/autoreview/domain/fcm/entity/FcmToken.java +++ b/src/main/java/org/example/autoreview/domain/fcm/entity/FcmToken.java @@ -36,7 +36,7 @@ public FcmToken(Member member, String token) { this.lastUsedDate = LocalDate.now(); } - public void updateDate() { - this.lastUsedDate = LocalDate.now(); + public void updateDate(LocalDate newDate) { + this.lastUsedDate = newDate; } } diff --git a/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenCommand.java b/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenCommand.java new file mode 100644 index 0000000..537e637 --- /dev/null +++ b/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenCommand.java @@ -0,0 +1,29 @@ +package org.example.autoreview.domain.fcm.service; + +import lombok.RequiredArgsConstructor; +import org.example.autoreview.domain.fcm.entity.FcmToken; +import org.example.autoreview.domain.fcm.entity.FcmTokenRepository; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.util.Map; + +@RequiredArgsConstructor +@Component +public class FcmTokenCommand { + + private final FcmTokenRepository fcmTokenRepository; + + @Transactional + public Long save(FcmToken fcmToken) { + return fcmTokenRepository.save(fcmToken).getId(); + } + + @Transactional + public void fcmTokensUpdate(Map fcmTokenMap) { + for (Map.Entry entry : fcmTokenMap.entrySet()) { + entry.getKey().updateDate(entry.getValue()); + } + } +} diff --git a/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenMemberService.java b/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenMemberService.java deleted file mode 100644 index 0e36416..0000000 --- a/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenMemberService.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.example.autoreview.domain.fcm.service; - -import lombok.RequiredArgsConstructor; -import org.example.autoreview.domain.fcm.dto.request.FcmTokenSaveRequestDto; -import org.example.autoreview.domain.member.entity.Member; -import org.example.autoreview.domain.member.service.MemberService; -import org.springframework.stereotype.Service; - -@RequiredArgsConstructor -@Service -public class FcmTokenMemberService { - - private final MemberService memberService; - private final FcmTokenService fcmTokenService; - - public Long save(FcmTokenSaveRequestDto requestDto, String email) { - Member member = memberService.findByEmail(email); - return fcmTokenService.save(requestDto, member); - } -} diff --git a/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenService.java b/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenService.java index 87f4b1c..c245d66 100644 --- a/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenService.java +++ b/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenService.java @@ -1,55 +1,24 @@ package org.example.autoreview.domain.fcm.service; -import com.google.firebase.messaging.FirebaseMessaging; -import com.google.firebase.messaging.FirebaseMessagingException; -import com.google.firebase.messaging.Message; -import java.util.List; -import java.util.concurrent.CompletableFuture; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.example.autoreview.domain.fcm.dto.request.FcmTokenSaveRequestDto; import org.example.autoreview.domain.fcm.entity.FcmToken; -import org.example.autoreview.domain.fcm.entity.FcmTokenRepository; import org.example.autoreview.domain.member.entity.Member; +import org.example.autoreview.domain.member.service.MemberCommand; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.TransactionSynchronizationManager; @Slf4j @RequiredArgsConstructor @Service public class FcmTokenService { - private final FcmTokenRepository fcmTokenRepository; + private final MemberCommand memberCommand; + private final FcmTokenCommand fcmTokenCommand; - @Transactional(readOnly = false) - public Long save(FcmTokenSaveRequestDto requestDto, Member member) { + public Long save(FcmTokenSaveRequestDto requestDto, String email) { + Member member = memberCommand.findByEmail(email); FcmToken fcmToken = requestDto.toEntity(member); - return fcmTokenRepository.save(fcmToken).getId(); + return fcmTokenCommand.save(fcmToken); } - - public void pushNotification(List fcmTokens, String title, String content) { - log.info("pushNotification 트랜잭션 존재 여부: {}", TransactionSynchronizationManager.isActualTransactionActive()); - - for (FcmToken fcmToken : fcmTokens) { - CompletableFuture.runAsync(() -> { - try { - Message message = Message.builder() - .putData("title", title) - .putData("body", content) - .setToken(fcmToken.getToken()) - .build(); - - FirebaseMessaging.getInstance().send(message); - fcmToken.updateDate(); - - } catch (FirebaseMessagingException e) { - log.error("Failed to send message to device {}: {}", fcmToken.getId(), e.getMessage()); - } catch (Exception e) { - log.error("An unexpected error occurred: {}", e.getMessage()); - } - }); - } - } - } diff --git a/src/main/java/org/example/autoreview/domain/github/entity/GithubToken.java b/src/main/java/org/example/autoreview/domain/github/entity/GithubToken.java index eeec429..6b24e3e 100644 --- a/src/main/java/org/example/autoreview/domain/github/entity/GithubToken.java +++ b/src/main/java/org/example/autoreview/domain/github/entity/GithubToken.java @@ -29,9 +29,9 @@ public GithubToken(String email, String githubToken) { this.githubToken = githubToken; } - public Long update(String githubToken) { + public GithubToken update(String githubToken) { this.githubToken = githubToken; - return this.id; + return this; } } diff --git a/src/main/java/org/example/autoreview/domain/github/service/GithubCommand.java b/src/main/java/org/example/autoreview/domain/github/service/GithubCommand.java new file mode 100644 index 0000000..923052d --- /dev/null +++ b/src/main/java/org/example/autoreview/domain/github/service/GithubCommand.java @@ -0,0 +1,39 @@ +package org.example.autoreview.domain.github.service; + +import lombok.RequiredArgsConstructor; +import org.example.autoreview.domain.github.entity.GithubToken; +import org.example.autoreview.domain.github.entity.GithubTokenRepository; +import org.example.autoreview.global.exception.errorcode.ErrorCode; +import org.example.autoreview.global.exception.sub_exceptions.NotFoundException; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Component +public class GithubCommand { + + private final GithubTokenRepository githubTokenRepository; + + @Transactional + public GithubToken save(GithubToken githubToken) { + return githubTokenRepository.save(githubToken); + } + + @Transactional + public GithubToken update(GithubToken githubToken, String newToken) { + return githubToken.update(newToken); + } + + @Transactional(readOnly = true) + public GithubToken findByEmail(String email) { + return githubTokenRepository.findByEmail(email).orElseThrow( + () -> new NotFoundException(ErrorCode.NOT_FOUND_GITHUB_TOKEN) + ); + } + + @Transactional(readOnly = true) + public boolean existsByEmail(String email) { + return githubTokenRepository.existsByEmail(email); + } + +} diff --git a/src/main/java/org/example/autoreview/domain/github/service/GithubService.java b/src/main/java/org/example/autoreview/domain/github/service/GithubService.java index fd9ba74..2d6b394 100644 --- a/src/main/java/org/example/autoreview/domain/github/service/GithubService.java +++ b/src/main/java/org/example/autoreview/domain/github/service/GithubService.java @@ -7,9 +7,6 @@ import org.example.autoreview.domain.github.dto.request.GithubCodeRequestDto; import org.example.autoreview.domain.github.dto.request.GithubTokenRequestDto; import org.example.autoreview.domain.github.entity.GithubToken; -import org.example.autoreview.domain.github.entity.GithubTokenRepository; -import org.example.autoreview.global.exception.errorcode.ErrorCode; -import org.example.autoreview.global.exception.sub_exceptions.NotFoundException; import org.kohsuke.github.GHFileNotFoundException; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; @@ -17,7 +14,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import org.springframework.web.reactive.function.client.WebClient; import java.io.IOException; @@ -25,9 +21,12 @@ @Slf4j @RequiredArgsConstructor -@Transactional(readOnly = true) @Service public class GithubService { + private final String REPO_NAME = "Ori"; + + private final GithubCommand githubCommand; + private final WebClient webClient; @Value("${github.client-id}") private String clientId; @@ -35,60 +34,36 @@ public class GithubService { @Value("${github.client-secret}") private String clientSecret; - private final GithubTokenRepository githubTokenRepository; - private final WebClient webClient; - private final String REPO_NAME = "Ori"; - - @Transactional public Long save(String accessToken, String email) { - GithubToken githubToken = GithubToken.builder() - .githubToken(accessToken) - .email(email) - .build(); + GithubToken githubToken = GithubToken.builder().githubToken(accessToken).email(email).build(); - return githubTokenRepository.save(githubToken).getId(); + return githubCommand.save(githubToken).getId(); } - @Transactional public Long update(GithubTokenRequestDto requestDto) { - GithubToken githubToken = githubTokenRepository.findByEmail(requestDto.email()).orElseThrow( - () -> new NotFoundException(ErrorCode.NOT_FOUND_GITHUB_TOKEN) - ); - return githubToken.update(requestDto.githubToken()); + GithubToken githubToken = githubCommand.findByEmail(requestDto.email()); + return githubCommand.update(githubToken, requestDto.githubToken()).getId(); } public boolean tokenCheck(String email) { - return githubTokenRepository.existsByEmail(email); + return githubCommand.existsByEmail(email); } public String getAccessToken(GithubCodeRequestDto requestDto) { - Map body = Map.of( - "client_id", clientId, - "client_secret", clientSecret, - "code", requestDto.code() - ); - - Map response = webClient.post() - .uri("https://github.com/login/oauth/access_token") - .header(HttpHeaders.ACCEPT, "application/json") - .bodyValue(body) - .retrieve() - .bodyToMono(Map.class) - .block(); + Map body = Map.of("client_id", clientId, "client_secret", clientSecret, "code", requestDto.code()); + + Map response = webClient.post().uri("https://github.com/login/oauth/access_token").header(HttpHeaders.ACCEPT, "application/json").bodyValue(body).retrieve().bodyToMono(Map.class).block(); return (String) response.get("access_token"); } - @Transactional public void pushToGithub(String email, GithubCodePushRequestDto requestDto) throws IOException { String fileExtension = Language.of(requestDto.language()).getFileExtension(); String codePath = requestDto.title() + "[" + requestDto.language() + "]" + "/code." + fileExtension; String readmePath = requestDto.title() + "[" + requestDto.language() + "]" + "/description.md"; - String githubToken = githubTokenRepository.findByEmail(email).orElseThrow( - () -> new NotFoundException(ErrorCode.NOT_FOUND_GITHUB_TOKEN) - ).getGithubToken(); + String githubToken = githubCommand.findByEmail(email).getGithubToken(); GitHub github = new GitHubBuilder().withOAuthToken(githubToken).build(); GHRepository repo = getOrCreateRepository(github, getGithubName(github)); @@ -104,10 +79,7 @@ private GHRepository getOrCreateRepository(GitHub github, String username) throw try { return github.getRepository(username + "/" + REPO_NAME); } catch (GHFileNotFoundException e) { - return github.createRepository(REPO_NAME) - .description("Auto-created ori repo") - .private_(false) - .create(); + return github.createRepository(REPO_NAME).description("Auto-created ori repo").private_(false).create(); } } @@ -116,11 +88,7 @@ private void pushFile(GHRepository repo, String path, String content, String ini org.kohsuke.github.GHContent existing = repo.getFileContent(path, "main"); existing.update(content, updateMsg, "main"); } catch (GHFileNotFoundException e) { - repo.createContent() - .path(path) - .message(initialMsg) - .content(content) - .commit(); + repo.createContent().path(path).message(initialMsg).content(content).commit(); } } diff --git a/src/main/java/org/example/autoreview/domain/member/service/MemberCommand.java b/src/main/java/org/example/autoreview/domain/member/service/MemberCommand.java index 2094740..e3fe7d2 100644 --- a/src/main/java/org/example/autoreview/domain/member/service/MemberCommand.java +++ b/src/main/java/org/example/autoreview/domain/member/service/MemberCommand.java @@ -14,13 +14,14 @@ public class MemberCommand { private final MemberRepository memberRepository; - @Transactional + @Transactional(readOnly = true) public Member findById(Long id) { return memberRepository.findById(id).orElseThrow( () -> new CustomRuntimeException(ErrorCode.NOT_FOUND_MEMBER) ); } + @Transactional(readOnly = true) public Member findByEmail(String email) { return memberRepository.findByEmail(email).orElseThrow( () -> new CustomRuntimeException(ErrorCode.NOT_FOUND_MEMBER) diff --git a/src/main/java/org/example/autoreview/domain/notification/controller/NotificationController.java b/src/main/java/org/example/autoreview/domain/notification/controller/NotificationController.java index a2f8972..8da7d11 100644 --- a/src/main/java/org/example/autoreview/domain/notification/controller/NotificationController.java +++ b/src/main/java/org/example/autoreview/domain/notification/controller/NotificationController.java @@ -2,20 +2,16 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.List; import lombok.RequiredArgsConstructor; import org.example.autoreview.domain.notification.dto.response.NotificationResponseDto; -import org.example.autoreview.domain.notification.service.NotificationDtoService; +import org.example.autoreview.domain.notification.service.NotificationService; import org.example.autoreview.global.scheduler.NotificationScheduler; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @Tag(name = "알림 API", description = "알림 API") @RequestMapping("/v1/api/notification") @@ -24,18 +20,18 @@ public class NotificationController { private final NotificationScheduler notificationScheduler; - private final NotificationDtoService notificationDtoService; + private final NotificationService notificationService; @Operation(summary = "알림 전체 조회", description = "회원 정보는 헤더에서") @GetMapping("/list") public ResponseEntity> findAll() { - return ResponseEntity.ok().body(notificationDtoService.findAll()); + return ResponseEntity.ok().body(notificationService.findAll()); } @Operation(summary = "회원 알림 전체 조회", description = "회원 정보는 헤더에서") @GetMapping("/own") public ResponseEntity> findAll(@AuthenticationPrincipal UserDetails userDetails) { - return ResponseEntity.ok().body(notificationDtoService.findAllByMemberId(userDetails.getUsername())); + return ResponseEntity.ok().body(notificationService.findAllByMemberId(userDetails.getUsername())); } @Operation(summary = "회원 알림 날짜별 조회", description = "회원 정보는 헤더에서") @@ -43,22 +39,23 @@ public ResponseEntity> findAll(@AuthenticationPrin public ResponseEntity> findAllByDate(@AuthenticationPrincipal UserDetails userDetails, @RequestParam int year, @RequestParam int month) { - return ResponseEntity.ok().body(notificationDtoService.findAllByDate(userDetails.getUsername(),year,month)); + return ResponseEntity.ok().body(notificationService.findAllByDate(userDetails.getUsername(),year,month)); } @Operation(summary = "회원 안읽은 알림 전체 조회", description = "회원이 안읽은 알림을 조회한다.") @GetMapping("/own/unchecked") public ResponseEntity> findAllNotificationIsNotCheckedByMemberId(@AuthenticationPrincipal UserDetails userDetails) { - return ResponseEntity.ok().body(notificationDtoService.findAllNotificationIsNotCheckedByMemberId(userDetails.getUsername())); + return ResponseEntity.ok().body(notificationService.findAllNotificationIsNotCheckedByMemberId(userDetails.getUsername())); } @Operation(summary = "알림 상태 변경", description = "알림을 읽음 상태로 변경한다.") @PutMapping - public ResponseEntity statUpdate(@RequestParam Long id) { - notificationDtoService.stateUpdate(id); + public ResponseEntity stateUpdate(@RequestParam Long id) { + notificationService.stateUpdate(id); return ResponseEntity.ok().body("update success"); } + // swagger 에서 test 하기 위해 scheduler 클래스 사용 @Operation(summary = "푸쉬 알림 강제 시작") @PutMapping("/push") public ResponseEntity push() { diff --git a/src/main/java/org/example/autoreview/domain/notification/service/NotificationCommand.java b/src/main/java/org/example/autoreview/domain/notification/service/NotificationCommand.java index 327dd33..acda5ca 100644 --- a/src/main/java/org/example/autoreview/domain/notification/service/NotificationCommand.java +++ b/src/main/java/org/example/autoreview/domain/notification/service/NotificationCommand.java @@ -2,11 +2,17 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.example.autoreview.domain.codepost.entity.CodePost; +import org.example.autoreview.domain.notification.dto.response.NotificationResponseDto; import org.example.autoreview.domain.notification.entity.Notification; import org.example.autoreview.domain.notification.entity.NotificationRepository; +import org.example.autoreview.domain.notification.enums.NotificationStatus; +import org.example.autoreview.global.exception.errorcode.ErrorCode; +import org.example.autoreview.global.exception.sub_exceptions.NotFoundException; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDate; import java.util.List; @Slf4j @@ -16,6 +22,70 @@ public class NotificationCommand { private final NotificationRepository notificationRepository; + @Transactional + public void save(Notification notification) { + notificationRepository.save(notification); + } + + @Transactional(readOnly = true) + public boolean existsByCodePostId(Long id) { + return notificationRepository.existsByCodePostId(id); + } + + @Transactional(readOnly = true) + public Notification findById(Long id) { + return notificationRepository.findById(id).orElseThrow( + () -> new NotFoundException(ErrorCode.NOT_FOUND_NOTIFICATION) + ); + } + + @Transactional(readOnly = true) + public Notification findByCodePostId(Long codePostId) { + return notificationRepository.findByCodePostId(codePostId).orElseThrow( + () -> new NotFoundException(ErrorCode.NOT_FOUND_NOTIFICATION) + ); + } + + @Transactional(readOnly = true) + public List findAll() { + return notificationRepository.findAll(); + } + + @Transactional(readOnly = true) + public List findAllByMemberId(Long memberId) { + return notificationRepository.findAllByMemberId(memberId); + } + + @Transactional(readOnly = true) + public List findAllByDate(Long memberId, int year, int month) { + return notificationRepository.findAllByDate(memberId,year,month); + } + + @Transactional(readOnly = true) + public List findAllNotificationIsNotCheckedByMemberId(Long memberId, LocalDate now) { + return notificationRepository.findAllNotificationIsNotCheckedByMemberId(memberId, now); + } + + @Transactional + public void update(Notification notification, CodePost codePost, NotificationStatus status) { + notification.update(codePost, status); + } + + @Transactional + public void readNotification(Notification notification) { + notification.readNotification(); + } + + @Transactional + public void delete(Notification notification) { + notificationRepository.delete(notification); + } + + @Transactional + public void deleteAll(List completedNotifications) { + notificationRepository.deleteAll(completedNotifications); + } + @Transactional(readOnly = true) public List getNotifications() { return notificationRepository.findAll(); diff --git a/src/main/java/org/example/autoreview/domain/notification/service/NotificationDtoService.java b/src/main/java/org/example/autoreview/domain/notification/service/NotificationDtoService.java deleted file mode 100644 index 28ed73f..0000000 --- a/src/main/java/org/example/autoreview/domain/notification/service/NotificationDtoService.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.example.autoreview.domain.notification.service; - -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.example.autoreview.domain.fcm.entity.FcmToken; -import org.example.autoreview.domain.fcm.service.FcmTokenService; -import org.example.autoreview.domain.member.service.MemberService; -import org.example.autoreview.domain.notification.dto.response.NotificationResponseDto; -import org.example.autoreview.domain.notification.entity.Notification; -import org.example.autoreview.domain.notification.enums.NotificationStatus; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@RequiredArgsConstructor -@Service -public class NotificationDtoService { - - private final NotificationService notificationService; - private final MemberService memberService; - private final FcmTokenService fcmTokenService; - private final NotificationCommand notificationCommand; - - public void delete(String email, Long id) { - notificationService.delete(email, id); - } - - public List findAll() { - return notificationService.findAll(); - } - - public List findAllByMemberId(String email) { - Long memberId = memberService.findByEmail(email).getId(); - return notificationService.findAllByMemberId(memberId); - } - - public List findAllByDate(String email, int year, int month) { - Long memberId = memberService.findByEmail(email).getId(); - return notificationService.findAllByDate(memberId, year, month); - } - - public List findAllNotificationIsNotCheckedByMemberId(String email) { - Long memberId = memberService.findByEmail(email).getId(); - return notificationService.findAllNotificationIsNotCheckedByMemberId(memberId); - } - - @Transactional - public void stateUpdate(Long id) { - Notification notification = notificationService.findEntityById(id); - notification.readNotification(); - } - - public void sendNotification() { - LocalDate today = LocalDate.now(); - List notificationList = notificationCommand.getNotifications(); - - for (Notification notification : notificationList) { - if (notification.getStatus().equals(NotificationStatus.PENDING) && notification.getExecuteTime().isEqual(today)) { - notificationCommand.updateStatus(notification); - List fcmTokens = notification.getMember().getFcmTokens(); - fcmTokenService.pushNotification(fcmTokens, notification.getTitle(), notification.getContent()); - } - } - } - - @Transactional - public void deleteCompleteNotification() { - List completedNotifications = new ArrayList<>(); - List notificationList = notificationService.findEntityAll(); - - for (Notification notification : notificationList) { - if(notification.getStatus().equals(NotificationStatus.COMPLETE)) { - completedNotifications.add(notification); - } - } - notificationService.deleteAll(completedNotifications); - } -} diff --git a/src/main/java/org/example/autoreview/domain/notification/service/NotificationService.java b/src/main/java/org/example/autoreview/domain/notification/service/NotificationService.java index 048fe19..7322588 100644 --- a/src/main/java/org/example/autoreview/domain/notification/service/NotificationService.java +++ b/src/main/java/org/example/autoreview/domain/notification/service/NotificationService.java @@ -1,114 +1,137 @@ package org.example.autoreview.domain.notification.service; -import java.time.LocalDate; -import java.util.List; -import java.util.stream.Collectors; +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.FirebaseMessagingException; +import com.google.firebase.messaging.Message; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.example.autoreview.domain.codepost.entity.CodePost; -import org.example.autoreview.domain.member.entity.Member; +import org.example.autoreview.domain.fcm.entity.FcmToken; +import org.example.autoreview.domain.fcm.service.FcmTokenCommand; +import org.example.autoreview.domain.member.service.MemberCommand; import org.example.autoreview.domain.notification.dto.response.NotificationResponseDto; import org.example.autoreview.domain.notification.entity.Notification; -import org.example.autoreview.domain.notification.entity.NotificationRepository; import org.example.autoreview.domain.notification.enums.NotificationStatus; -import org.example.autoreview.global.exception.errorcode.ErrorCode; -import org.example.autoreview.global.exception.sub_exceptions.BadRequestException; -import org.example.autoreview.global.exception.sub_exceptions.NotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; @Slf4j @RequiredArgsConstructor -@Transactional(readOnly = true) @Service public class NotificationService { - private final NotificationRepository notificationRepository; - - @Transactional - public void save(Member member, CodePost codePost) { - Notification notification = Notification.builder() - .title("ORI 복습 알림") - .content(codePost.getTitle()) - .status(NotificationStatus.PENDING) - .executeTime(codePost.getReviewDay()) - .member(member) - .codePostId(codePost.getId()) - .build(); - - notificationRepository.save(notification); - } - - public boolean existsById(Long id) { - return notificationRepository.existsById(id); - } - - public boolean existsByCodePostId(Long id) { - return notificationRepository.existsByCodePostId(id); - } - - public Notification findEntityById(Long id) { - return notificationRepository.findById(id).orElseThrow( - () -> new NotFoundException(ErrorCode.NOT_FOUND_NOTIFICATION) - ); - } - - public List findEntityAll() { - return notificationRepository.findAll(); - } + private final NotificationCommand notificationCommand; + private final MemberCommand memberCommand; + private final FcmTokenCommand fcmTokenCommand; + /** + * 알림 전체 조회하는 메서드이다. + */ public List findAll() { - return notificationRepository.findAll().stream() + return notificationCommand.findAll().stream() .map(NotificationResponseDto::new) .collect(Collectors.toList()); } - public List findAllByMemberId(Long memberId) { - return notificationRepository.findAllByMemberId(memberId).stream() + /** + * 사용자가 설정한 모든 알림을 조회하는 메서드이다. + */ + public List findAllByMemberId(String email) { + Long memberId = memberCommand.findByEmail(email).getId(); + return notificationCommand.findAllByMemberId(memberId).stream() .map(NotificationResponseDto::new) .collect(Collectors.toList()); } - public List findAllByDate(Long memberId, int year, int month) { - return notificationRepository.findAllByDate(memberId,year,month); + /** + * 특정 날짜에 사용자가 설정해놓은 알림 전체 조회하는 메서드이다. + */ + public List findAllByDate(String email, int year, int month) { + Long memberId = memberCommand.findByEmail(email).getId(); + return notificationCommand.findAllByDate(memberId,year,month); } - public List findAllNotificationIsNotCheckedByMemberId(Long memberId) { + /** + * 사용자가 읽지 않은 알림을 전체 조회하는 메서드이다. + */ + public List findAllNotificationIsNotCheckedByMemberId(String email) { LocalDate now = LocalDate.now(); - List notifications = notificationRepository.findAllNotificationIsNotCheckedByMemberId(memberId, now); - - return notifications.stream() + Long memberId = memberCommand.findByEmail(email).getId(); + return notificationCommand.findAllNotificationIsNotCheckedByMemberId(memberId, now).stream() .map(NotificationResponseDto::new) .collect(Collectors.toList()); } - @Transactional - public void update(String email, CodePost codePost, NotificationStatus status) { - Notification notification = notificationRepository.findByCodePostId(codePost.getId()).orElseThrow( - () -> new NotFoundException(ErrorCode.NOT_FOUND_NOTIFICATION) - ); - userValidator(email, notification); - notification.update(codePost, status); + /** + * 알림을 읽음 상태로 변경해주는 메서드이다. + */ + public void stateUpdate(Long id) { + Notification notification = notificationCommand.findById(id); + notificationCommand.readNotification(notification); } - @Transactional - public void delete(String email, Long id) { - Notification notification = notificationRepository.findByCodePostId(id).orElseThrow( - () -> new NotFoundException(ErrorCode.NOT_FOUND_NOTIFICATION) - ); - userValidator(email,notification); - notificationRepository.delete(notification); + /** + * FCM으로 전송할 알림을 요청하는 외부 API이다. + */ + public void sendNotification() { + LocalDate today = LocalDate.now(); + List notificationList = notificationCommand.getNotifications(); + + // + for (Notification notification : notificationList) { + if (notification.getStatus().equals(NotificationStatus.PENDING) && notification.getExecuteTime().isEqual(today)) { + notificationCommand.updateStatus(notification); + List fcmTokens = notification.getMember().getFcmTokens(); + pushNotification(fcmTokens, notification.getTitle(), notification.getContent()); + } + } } - @Transactional - public void deleteAll(List completedNotifications) { - notificationRepository.deleteAll(completedNotifications); + private void pushNotification(List fcmTokens, String title, String content) { + log.info("pushNotification 트랜잭션 존재 여부: {}", TransactionSynchronizationManager.isActualTransactionActive()); + + Map fcmTokenMap = new ConcurrentHashMap<>(); + for (FcmToken fcmToken : fcmTokens) { + CompletableFuture.runAsync(() -> { + try { + Message message = Message.builder() + .putData("title", title) + .putData("body", content) + .setToken(fcmToken.getToken()) + .build(); + + FirebaseMessaging.getInstance().send(message); + fcmTokenMap.put(fcmToken, LocalDate.now()); + + } catch (FirebaseMessagingException e) { + log.error("Failed to send message to device {}: {}", fcmToken.getId(), e.getMessage()); + } catch (Exception e) { + log.error("An unexpected error occurred: {}", e.getMessage()); + } + }); + } + fcmTokenCommand.fcmTokensUpdate(fcmTokenMap); } - private static void userValidator(String email, Notification notification) { - if (!notification.getMember().getEmail().equals(email)) { - throw new BadRequestException(ErrorCode.UNMATCHED_EMAIL); + @Transactional + public void deleteCompleteNotification() { + List completedNotifications = new ArrayList<>(); + List notificationList = notificationCommand.findAll(); + + for (Notification notification : notificationList) { + if(notification.getStatus().equals(NotificationStatus.COMPLETE)) { + completedNotifications.add(notification); + } } + notificationCommand.deleteAll(completedNotifications); } } diff --git a/src/main/java/org/example/autoreview/domain/review/service/ReviewCodePostService.java b/src/main/java/org/example/autoreview/domain/review/service/ReviewCodePostService.java index d9df9d7..808c24e 100644 --- a/src/main/java/org/example/autoreview/domain/review/service/ReviewCodePostService.java +++ b/src/main/java/org/example/autoreview/domain/review/service/ReviewCodePostService.java @@ -1,26 +1,27 @@ package org.example.autoreview.domain.review.service; -import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.example.autoreview.domain.codepost.entity.CodePost; -import org.example.autoreview.domain.codepost.service.CodePostService; +import org.example.autoreview.domain.codepost.service.CodePostCommand; import org.example.autoreview.domain.review.dto.request.ReviewDeleteRequestDto; import org.example.autoreview.domain.review.dto.request.ReviewSaveRequestDto; import org.example.autoreview.domain.review.dto.request.ReviewUpdateRequestDto; import org.example.autoreview.domain.review.dto.response.ReviewResponseDto; import org.springframework.stereotype.Service; +import java.util.List; + @Slf4j @RequiredArgsConstructor @Service public class ReviewCodePostService { private final ReviewService reviewService; - private final CodePostService codePostService; + private final CodePostCommand codePostCommand; public Long save(ReviewSaveRequestDto requestDto) { - CodePost codePost = codePostService.findEntityById(requestDto.codePostId()); + CodePost codePost = codePostCommand.findById(requestDto.codePostId()); return reviewService.save(requestDto, codePost).getId(); } @@ -29,7 +30,7 @@ public ReviewResponseDto findOne(Long reviewId) { } public List findAllByCodePostId(Long codePostId) { - CodePost codePost = codePostService.findEntityById(codePostId); + CodePost codePost = codePostCommand.findById(codePostId); return reviewService.findAllByCodePost(codePost); } diff --git a/src/main/java/org/example/autoreview/global/scheduler/NotificationScheduler.java b/src/main/java/org/example/autoreview/global/scheduler/NotificationScheduler.java index d22442c..aa2d2c9 100644 --- a/src/main/java/org/example/autoreview/global/scheduler/NotificationScheduler.java +++ b/src/main/java/org/example/autoreview/global/scheduler/NotificationScheduler.java @@ -2,7 +2,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.example.autoreview.domain.notification.service.NotificationDtoService; +import org.example.autoreview.domain.notification.service.NotificationService; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -11,13 +11,13 @@ @Component public class NotificationScheduler { - private final NotificationDtoService notificationMemberService; + private final NotificationService notificationService; // 매일 오전 8시에 호출 @Scheduled(cron = "0 0 8 * * ?") public void pushNotification(){ log.info("start push notification"); - notificationMemberService.sendNotification(); + notificationService.sendNotification(); log.info("finish push notification"); } @@ -26,7 +26,7 @@ public void pushNotification(){ @Scheduled(cron = "0 10 7 * * ?") public void deleteCompleteNotification(){ log.info("start delete notification"); - notificationMemberService.deleteCompleteNotification(); + notificationService.deleteCompleteNotification(); log.info("end delete notification"); } } diff --git a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceTest.java b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceTest.java new file mode 100644 index 0000000..caca6e5 --- /dev/null +++ b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceTest.java @@ -0,0 +1,101 @@ +package org.example.autoreview.domain.bookmark.CodePostBookmark.service; + +import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.request.CodePostBookmarkSaveRequestDto; +import org.example.autoreview.domain.member.entity.Member; +import org.example.autoreview.domain.member.entity.Role; +import org.example.autoreview.domain.member.service.MemberCommand; +import org.junit.jupiter.api.BeforeEach; +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 org.springframework.dao.DataIntegrityViolationException; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class CodePostBookmarkServiceTest { + + @Mock + private CodePostBookmarkCommand codePostBookmarkCommand; + + @Mock + private MemberCommand memberCommand; + + @InjectMocks + private CodePostBookmarkService codePostBookmarkService; + + private Member mockMember; + + @BeforeEach + void setUp() { + mockMember = Member.builder() + .nickname("tester") + .role(Role.USER) + .email("test@example.com") + .build(); + } + + @Test + void saveOrUpdate_성공적으로_북마크를_저장한다() { + // given + CodePostBookmarkSaveRequestDto requestDto = new CodePostBookmarkSaveRequestDto(123L); + when(memberCommand.findByEmail("test@example.com")).thenReturn(mockMember); + when(codePostBookmarkCommand.trySave(requestDto, mockMember)).thenReturn(1L); + + // when + Long result = codePostBookmarkService.saveOrUpdate(requestDto, "test@example.com"); + + // then + assertThat(result).isEqualTo(1L); + verify(codePostBookmarkCommand).trySave(requestDto, mockMember); + } + + @Test + void saveOrUpdate_UNIQUE_제약조건_위반시_업데이트로_전환한다() { + // given + CodePostBookmarkSaveRequestDto requestDto = new CodePostBookmarkSaveRequestDto(123L); + when(memberCommand.findByEmail("test@example.com")).thenReturn(mockMember); + when(codePostBookmarkCommand.trySave(requestDto, mockMember)).thenThrow(DataIntegrityViolationException.class); + when(codePostBookmarkCommand.fallbackToUpdate(123L, 1L)).thenReturn(2L); + + // when + Long result = codePostBookmarkService.saveOrUpdate(requestDto, "test@example.com"); + + // then + assertThat(result).isEqualTo(2L); + verify(codePostBookmarkCommand).fallbackToUpdate(123L, 1L); + } + +// @Test +// void findAllByMemberId_정상조회() { +// // given +// CodePostBookmarkResponseDto dto1 = new CodePostBookmarkResponseDto(1L, "Post 1"); +// CodePostBookmarkResponseDto dto2 = new CodePostBookmarkResponseDto(2L, "Post 2"); +// +// var dtoList = List.of(dto1, dto2); +// Page page = new PageImpl<>(dtoList, PageRequest.of(0, 10), 2); +// +// when(memberCommand.findByEmail("test@example.com")).thenReturn(mockMember); +// when(codePostBookmarkCommand.findAllByMemberId(1L, PageRequest.of(0, 10))).thenReturn(page); +// +// // when +// CodePostBookmarkListResponseDto result = codePostBookmarkService.findAllByMemberId("test@example.com", PageRequest.of(0, 10)); +// +// // then +// assertThat(result.dtoList().size()).isEqualTo(2); +// assertThat(result.totalPage()).isEqualTo(1); +// } + + @Test + void deleteExpiredSoftDeletedBookmarks_정상작동() { + // when + codePostBookmarkService.deleteExpiredSoftDeletedBookmarks(); + + // then + verify(codePostBookmarkCommand).deleteExpiredSoftDeletedBookmarks(); + } +} \ No newline at end of file From dd04e75544de45fb0f6f4a08730ea12f5c988576 Mon Sep 17 00:00:00 2001 From: ehgur Date: Thu, 5 Jun 2025 18:34:19 +0900 Subject: [PATCH 23/66] =?UTF-8?q?refactor:=20code=20post=20bookmark=20?= =?UTF-8?q?=EB=8B=A8=EC=9D=BC=20&=20=ED=86=B5=ED=95=A9=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../til/service/TilPostCommentService.java | 9 +- .../github/controller/GithubController.java | 2 +- ...odePostBookmarkServiceIntegrationTest.java | 97 +++++++++++++ .../service/CodePostBookmarkServiceTest.java | 101 ------------- .../CodePostBookmarkServiceUnitTest.java | 136 ++++++++++++++++++ .../codepost/service/CodePostServiceTest.java | 5 + 6 files changed, 243 insertions(+), 107 deletions(-) create mode 100644 src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java delete mode 100644 src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceTest.java create mode 100644 src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceUnitTest.java create mode 100644 src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceTest.java diff --git a/src/main/java/org/example/autoreview/domain/comment/til/service/TilPostCommentService.java b/src/main/java/org/example/autoreview/domain/comment/til/service/TilPostCommentService.java index 9e96dbe..af9cada 100644 --- a/src/main/java/org/example/autoreview/domain/comment/til/service/TilPostCommentService.java +++ b/src/main/java/org/example/autoreview/domain/comment/til/service/TilPostCommentService.java @@ -17,11 +17,10 @@ public class TilPostCommentService extends CommentService callbackAndSave(@RequestBody GithubCodeRequestDto re @Operation @PostMapping("/push/post/code") public ResponseEntity push(@AuthenticationPrincipal UserDetails userDetails, - @RequestBody GithubCodePushRequestDto requestDto) throws IOException { + @RequestBody GithubCodePushRequestDto requestDto) throws IOException { githubService.pushToGithub(userDetails.getUsername(), requestDto); return ResponseEntity.ok().body("Push to GitHub completed successfully."); } diff --git a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java new file mode 100644 index 0000000..5e905e2 --- /dev/null +++ b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java @@ -0,0 +1,97 @@ +package org.example.autoreview.domain.bookmark.CodePostBookmark.service; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.springframework.data.domain.PageRequest.of; + +import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.request.CodePostBookmarkSaveRequestDto; +import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkListResponseDto; +import org.example.autoreview.domain.codepost.entity.CodePost; +import org.example.autoreview.domain.codepost.entity.CodePostRepository; +import org.example.autoreview.domain.member.entity.Member; +import org.example.autoreview.domain.member.entity.MemberRepository; +import org.example.autoreview.domain.member.entity.Role; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest(properties = "spring.profiles.active=test") +@Transactional +class CodePostBookmarkServiceIntegrationTest { + + @Autowired + private CodePostBookmarkService service; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private CodePostRepository codePostRepository; + + private CodePostBookmarkSaveRequestDto saveRequestDto; + private Member testMember; + private CodePost testCodePost; + private String email; + + @BeforeEach + void setUp() { + email = "test@example.com"; + testMember = memberRepository.save(Member.builder() + .email(email) + .nickname("tester") + .role(Role.USER) + .build()); + + testCodePost = codePostRepository.save(CodePost.builder() + .writerId(1L) + .isPublic(true) + .build()); + + saveRequestDto = new CodePostBookmarkSaveRequestDto(testCodePost.getId()); + } + + @Test + void saveOrUpdate_trySave() { + // when + Long bookmarkId = service.saveOrUpdate(saveRequestDto, testMember.getEmail()); + + // then + CodePostBookmarkListResponseDto bookmarks = service.findAllByMemberId(testMember.getEmail(), of(0, 10)); + + assertThat(bookmarkId).isNotNull(); + assertThat(bookmarks.dtoList().size()).isEqualTo(1); + assertThat(bookmarks.dtoList().get(0).getCodePostId()).isEqualTo(testCodePost.getId()); + } + + @Test + void saveOrUpdate_fallbackToUpdate() { + // when + // bookmark 여부 true -> false 로 변경 + Long saveFirstBookmarkId = service.saveOrUpdate(saveRequestDto, testMember.getEmail()); + Long saveSecondBookmarkId = service.saveOrUpdate(saveRequestDto, testMember.getEmail()); + + // then + CodePostBookmarkListResponseDto bookmarks = service.findAllByMemberId(testMember.getEmail(), of(0, 10)); + + assertThat(bookmarks.dtoList().size()).isEqualTo(0); + assertThat(saveFirstBookmarkId).isEqualTo(saveSecondBookmarkId); + } + + + @Test + void saveOrUpdate_then_findAllByMemberId() { + // given + CodePostBookmarkSaveRequestDto requestDto = new CodePostBookmarkSaveRequestDto(testCodePost.getId()); + + // when + Long bookmarkId = service.saveOrUpdate(requestDto, testMember.getEmail()); + + // then + CodePostBookmarkListResponseDto bookmarks = service.findAllByMemberId(testMember.getEmail(), of(0, 10)); + + assertThat(bookmarks.dtoList().size()).isEqualTo(1); + assertThat(bookmarks.dtoList().get(0).getCodePostId()).isEqualTo(testCodePost.getId()); + assertThat(bookmarkId).isNotNull(); + } +} \ No newline at end of file diff --git a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceTest.java b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceTest.java deleted file mode 100644 index caca6e5..0000000 --- a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package org.example.autoreview.domain.bookmark.CodePostBookmark.service; - -import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.request.CodePostBookmarkSaveRequestDto; -import org.example.autoreview.domain.member.entity.Member; -import org.example.autoreview.domain.member.entity.Role; -import org.example.autoreview.domain.member.service.MemberCommand; -import org.junit.jupiter.api.BeforeEach; -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 org.springframework.dao.DataIntegrityViolationException; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class CodePostBookmarkServiceTest { - - @Mock - private CodePostBookmarkCommand codePostBookmarkCommand; - - @Mock - private MemberCommand memberCommand; - - @InjectMocks - private CodePostBookmarkService codePostBookmarkService; - - private Member mockMember; - - @BeforeEach - void setUp() { - mockMember = Member.builder() - .nickname("tester") - .role(Role.USER) - .email("test@example.com") - .build(); - } - - @Test - void saveOrUpdate_성공적으로_북마크를_저장한다() { - // given - CodePostBookmarkSaveRequestDto requestDto = new CodePostBookmarkSaveRequestDto(123L); - when(memberCommand.findByEmail("test@example.com")).thenReturn(mockMember); - when(codePostBookmarkCommand.trySave(requestDto, mockMember)).thenReturn(1L); - - // when - Long result = codePostBookmarkService.saveOrUpdate(requestDto, "test@example.com"); - - // then - assertThat(result).isEqualTo(1L); - verify(codePostBookmarkCommand).trySave(requestDto, mockMember); - } - - @Test - void saveOrUpdate_UNIQUE_제약조건_위반시_업데이트로_전환한다() { - // given - CodePostBookmarkSaveRequestDto requestDto = new CodePostBookmarkSaveRequestDto(123L); - when(memberCommand.findByEmail("test@example.com")).thenReturn(mockMember); - when(codePostBookmarkCommand.trySave(requestDto, mockMember)).thenThrow(DataIntegrityViolationException.class); - when(codePostBookmarkCommand.fallbackToUpdate(123L, 1L)).thenReturn(2L); - - // when - Long result = codePostBookmarkService.saveOrUpdate(requestDto, "test@example.com"); - - // then - assertThat(result).isEqualTo(2L); - verify(codePostBookmarkCommand).fallbackToUpdate(123L, 1L); - } - -// @Test -// void findAllByMemberId_정상조회() { -// // given -// CodePostBookmarkResponseDto dto1 = new CodePostBookmarkResponseDto(1L, "Post 1"); -// CodePostBookmarkResponseDto dto2 = new CodePostBookmarkResponseDto(2L, "Post 2"); -// -// var dtoList = List.of(dto1, dto2); -// Page page = new PageImpl<>(dtoList, PageRequest.of(0, 10), 2); -// -// when(memberCommand.findByEmail("test@example.com")).thenReturn(mockMember); -// when(codePostBookmarkCommand.findAllByMemberId(1L, PageRequest.of(0, 10))).thenReturn(page); -// -// // when -// CodePostBookmarkListResponseDto result = codePostBookmarkService.findAllByMemberId("test@example.com", PageRequest.of(0, 10)); -// -// // then -// assertThat(result.dtoList().size()).isEqualTo(2); -// assertThat(result.totalPage()).isEqualTo(1); -// } - - @Test - void deleteExpiredSoftDeletedBookmarks_정상작동() { - // when - codePostBookmarkService.deleteExpiredSoftDeletedBookmarks(); - - // then - verify(codePostBookmarkCommand).deleteExpiredSoftDeletedBookmarks(); - } -} \ No newline at end of file diff --git a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceUnitTest.java b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceUnitTest.java new file mode 100644 index 0000000..99987bc --- /dev/null +++ b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceUnitTest.java @@ -0,0 +1,136 @@ +package org.example.autoreview.domain.bookmark.CodePostBookmark.service; + +import static java.util.List.of; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.request.CodePostBookmarkSaveRequestDto; +import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkListResponseDto; +import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkResponseDto; +import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmark; +import org.example.autoreview.domain.codepost.entity.CodePost; +import org.example.autoreview.domain.member.entity.Member; +import org.example.autoreview.domain.member.entity.Role; +import org.example.autoreview.domain.member.service.MemberCommand; +import org.junit.jupiter.api.BeforeEach; +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 org.springframework.dao.DataIntegrityViolationException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; + +@ExtendWith(MockitoExtension.class) +class CodePostBookmarkServiceUnitTest { + + @Mock + private CodePostBookmarkCommand codePostBookmarkCommand; + + @Mock + private MemberCommand memberCommand; + + @InjectMocks + private CodePostBookmarkService codePostBookmarkService; + + private Member mockMember; + + private String email; + + private Long codePostId; + + @BeforeEach + void setUp() { + codePostId = 123L; + email = "test@example.com"; + mockMember = Member.builder() + .nickname("tester") + .role(Role.USER) + .email(email) + .build(); + } + + @Test + void saveOrUpdate_성공적으로_북마크를_저장한다() { + // given + CodePostBookmarkSaveRequestDto requestDto = new CodePostBookmarkSaveRequestDto(codePostId); + when(memberCommand.findByEmail(email)).thenReturn(mockMember); + when(codePostBookmarkCommand.trySave(requestDto, mockMember)).thenReturn(1L); + + // when + Long result = codePostBookmarkService.saveOrUpdate(requestDto, email); + + // then + assertThat(result).isEqualTo(1L); + verify(codePostBookmarkCommand).trySave(requestDto, mockMember); + } + + @Test + void saveOrUpdate_UNIQUE_제약조건_위반시_업데이트로_전환한다() { + // given + CodePostBookmarkSaveRequestDto requestDto = new CodePostBookmarkSaveRequestDto(codePostId); + when(memberCommand.findByEmail(email)).thenReturn(mockMember); + when(codePostBookmarkCommand.trySave(requestDto, mockMember)).thenThrow(DataIntegrityViolationException.class); + + // mockMember 의 id값이 null이기 때문에 memberId에 null 삽입한 채로 진행 + when(codePostBookmarkCommand.fallbackToUpdate(codePostId, null)).thenReturn(2L); + + // when + Long result = codePostBookmarkService.saveOrUpdate(requestDto, email); + + // then + assertThat(result).isEqualTo(2L); + verify(codePostBookmarkCommand).fallbackToUpdate(codePostId, null); + } + + @Test + void findAllByMemberId_정상조회() { + // given + CodePostBookmark bookmark1 = CodePostBookmark.builder() + .codePostId(1L) + .member(mockMember) + .isDeleted(false) + .build(); + + CodePostBookmark bookmark2 = CodePostBookmark.builder() + .codePostId(2L) + .member(mockMember) + .isDeleted(false) + .build(); + + CodePost codePost = CodePost.builder() + .writerId(1L) + .isPublic(true) + .build(); + + CodePostBookmarkResponseDto dto1 = new CodePostBookmarkResponseDto(bookmark1, codePost, mockMember); + CodePostBookmarkResponseDto dto2 = new CodePostBookmarkResponseDto(bookmark2, codePost, mockMember); + + var dtoList = of(dto1, dto2); + Page page = new PageImpl<>(dtoList, PageRequest.of(0, 10), 2); + + when(memberCommand.findByEmail(email)).thenReturn(mockMember); + + // mockMember 의 id값이 null이기 때문에 memberId에 null 삽입한 채로 진행 + when(codePostBookmarkCommand.findAllByMemberId(null, PageRequest.of(0, 10))).thenReturn(page); + + // when + CodePostBookmarkListResponseDto result = codePostBookmarkService.findAllByMemberId(email, PageRequest.of(0, 10)); + + // then + assertThat(result.dtoList().size()).isEqualTo(2); + assertThat(result.totalPage()).isEqualTo(1); + } + + @Test + void deleteExpiredSoftDeletedBookmarks_정상작동() { + // when + codePostBookmarkService.deleteExpiredSoftDeletedBookmarks(); + + // then + verify(codePostBookmarkCommand).deleteExpiredSoftDeletedBookmarks(); + } +} \ No newline at end of file diff --git a/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceTest.java b/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceTest.java new file mode 100644 index 0000000..4976d7a --- /dev/null +++ b/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceTest.java @@ -0,0 +1,5 @@ +package org.example.autoreview.domain.codepost.service; + +class CodePostServiceTest { + +} \ No newline at end of file From e3ef7bc7f4b449a85c3b61e1ad4f2330104cf2ed Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Sat, 7 Jun 2025 21:01:58 +0900 Subject: [PATCH 24/66] =?UTF-8?q?refactor:=20repository=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=20->=20command=20=ED=98=B8=EC=B6=9C=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 --- .../CodePostBookmarkServiceIntegrationTest.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java index 5e905e2..5232f11 100644 --- a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java +++ b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java @@ -6,7 +6,7 @@ import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.request.CodePostBookmarkSaveRequestDto; import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkListResponseDto; import org.example.autoreview.domain.codepost.entity.CodePost; -import org.example.autoreview.domain.codepost.entity.CodePostRepository; +import org.example.autoreview.domain.codepost.service.CodePostCommand; import org.example.autoreview.domain.member.entity.Member; import org.example.autoreview.domain.member.entity.MemberRepository; import org.example.autoreview.domain.member.entity.Role; @@ -27,23 +27,21 @@ class CodePostBookmarkServiceIntegrationTest { private MemberRepository memberRepository; @Autowired - private CodePostRepository codePostRepository; + private CodePostCommand codePostCommand; private CodePostBookmarkSaveRequestDto saveRequestDto; private Member testMember; private CodePost testCodePost; - private String email; @BeforeEach void setUp() { - email = "test@example.com"; testMember = memberRepository.save(Member.builder() - .email(email) + .email("test@example.com") .nickname("tester") .role(Role.USER) .build()); - testCodePost = codePostRepository.save(CodePost.builder() + testCodePost = codePostCommand.save(CodePost.builder() .writerId(1L) .isPublic(true) .build()); From c818150e0efd4dcf5ede818297bf35a92c18a2f6 Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Sun, 8 Jun 2025 14:40:09 +0900 Subject: [PATCH 25/66] =?UTF-8?q?refactor:=20code=20post=20service=20?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20&=20=ED=86=B5=ED=95=A9=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CodePostServiceIntegrationTest.java | 181 ++++++++++++++++++ .../codepost/service/CodePostServiceTest.java | 5 - .../service/CodePostServiceUnitTest.java | 180 +++++++++++++++++ 3 files changed, 361 insertions(+), 5 deletions(-) create mode 100644 src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java delete mode 100644 src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceTest.java create mode 100644 src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceUnitTest.java diff --git a/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java b/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java new file mode 100644 index 0000000..3b45cc5 --- /dev/null +++ b/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java @@ -0,0 +1,181 @@ +package org.example.autoreview.domain.codepost.service; + +import org.example.autoreview.domain.bookmark.CodePostBookmark.service.CodePostBookmarkCommand; +import org.example.autoreview.domain.codepost.dto.request.CodePostSaveRequestDto; +import org.example.autoreview.domain.codepost.dto.request.CodePostUpdateRequestDto; +import org.example.autoreview.domain.codepost.dto.response.CodePostResponseDto; +import org.example.autoreview.domain.codepost.entity.CodePost; +import org.example.autoreview.domain.codepost.entity.Language; +import org.example.autoreview.domain.member.entity.Member; +import org.example.autoreview.domain.member.entity.MemberRepository; +import org.example.autoreview.domain.member.entity.Role; +import org.example.autoreview.domain.notification.service.NotificationCommand; +import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ActiveProfiles("test") +@SpringBootTest +@Transactional +public class CodePostServiceIntegrationTest { + + @Autowired + private CodePostCommand codePostCommand; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + CodePostBookmarkCommand codePostBookmarkCommand; + + @Autowired + private NotificationCommand notificationCommand; + + @Autowired + private CodePostService codePostService; + + private CodePostSaveRequestDto saveRequestDto; + private Member testMember; + private CodePost testCodePost; + + @BeforeEach + void setUp() { + testMember = memberRepository.save(Member.builder() + .email("test@example.com") + .nickname("tester") + .role(Role.USER) + .build()); + + testCodePost = codePostCommand.save(CodePost.builder() + .writerId(testMember.getId()) + .language(Language.JAVA) + .isPublic(false) + .build()); + } + + @Test + void save_post_review_day_is_null() { + // given + saveRequestDto = new CodePostSaveRequestDto( + "test", + 1, + true, + null, + "test", + "java", + "import test" + ); + + // when + Long codePostId = codePostService.save(saveRequestDto, testMember.getEmail()); + + // then + CodePostResponseDto responseDto = codePostService.findById(codePostId, testMember.getEmail()); + + assertThat(responseDto.getReviewDay()).isNull(); + assertThat(responseDto.getWriterId()).isEqualTo(testMember.getId()); + } + + @Test + void save_post_review_day_is_not_null() { + // given + saveRequestDto = new CodePostSaveRequestDto( + "test", + 1, + true, + LocalDate.now().plusDays(1), + "test", + "java", + "import test" + ); + + // when + Long codePostId = codePostService.save(saveRequestDto, testMember.getEmail()); + + // then + CodePostResponseDto responseDto = codePostService.findById(codePostId, testMember.getEmail()); + + assertThat(notificationCommand.existsByCodePostId(codePostId)).isTrue(); + assertThat(responseDto.getReviewDay()).isEqualTo(LocalDate.now().plusDays(1)); + assertThat(responseDto.getWriterId()).isEqualTo(testMember.getId()); + + } + + @Test + void 비공개_포스트를_다른_사용자가_조회() { + // given + Member user = memberRepository.save(Member.builder() + .email("user@example.com") + .nickname("user") + .role(Role.USER) + .build()); + + // when + then + assertThrows(CustomRuntimeException.class, () -> { + codePostService.findById(testCodePost.getId(), user.getEmail()); + }); + } + + @Test + void 비공개_포스트를_작성자가_조회() { + // when + CodePostResponseDto responseDto = codePostService.findById(testCodePost.getId(), testMember.getEmail()); + + // then + assertThat(responseDto).isNotNull(); + assertThat(responseDto.getWriterId()).isEqualTo(testMember.getId()); + } + + @Test + void update() { + // given + CodePostUpdateRequestDto requestDto = CodePostUpdateRequestDto.builder() + .id(testCodePost.getId()) + .language("cpp") + .build(); + + String beforeLanguage = testCodePost.getLanguage().getType(); + + // when + codePostService.update(requestDto, testMember.getEmail()); + + // then + assertThat(testCodePost.getLanguage().getType()).isNotEqualTo(beforeLanguage); + assertThat(testCodePost.getLanguage()).isEqualTo(Language.CPP); + } + + @Test + void delete() { + // given + saveRequestDto = new CodePostSaveRequestDto( + "test", + 1, + true, + LocalDate.now().plusDays(1), + "test", + "java", + "import test" + ); + + // when + Long codePostId = codePostService.save(saveRequestDto, testMember.getEmail()); + + // when + int notificationCount = notificationCommand.findAll().size(); + + //then + codePostService.delete(codePostId, testMember.getEmail()); + + assertThat(notificationCount).isOne(); + assertThat(notificationCommand.findAll().size()).isZero(); + } +} diff --git a/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceTest.java b/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceTest.java deleted file mode 100644 index 4976d7a..0000000 --- a/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceTest.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.example.autoreview.domain.codepost.service; - -class CodePostServiceTest { - -} \ No newline at end of file diff --git a/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceUnitTest.java b/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceUnitTest.java new file mode 100644 index 0000000..7688dfb --- /dev/null +++ b/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceUnitTest.java @@ -0,0 +1,180 @@ +package org.example.autoreview.domain.codepost.service; + +import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmark; +import org.example.autoreview.domain.bookmark.CodePostBookmark.service.CodePostBookmarkCommand; +import org.example.autoreview.domain.codepost.dto.request.CodePostSaveRequestDto; +import org.example.autoreview.domain.codepost.dto.response.CodePostResponseDto; +import org.example.autoreview.domain.codepost.entity.CodePost; +import org.example.autoreview.domain.codepost.entity.Language; +import org.example.autoreview.domain.member.entity.Member; +import org.example.autoreview.domain.member.service.MemberCommand; +import org.example.autoreview.domain.notification.entity.Notification; +import org.example.autoreview.domain.notification.enums.NotificationStatus; +import org.example.autoreview.domain.notification.service.NotificationCommand; +import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; +import org.junit.jupiter.api.BeforeEach; +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 org.springframework.test.util.ReflectionTestUtils; + +import java.time.LocalDate; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class CodePostServiceUnitTest { + + @Mock + private CodePostCommand codePostCommand; + + @Mock + private MemberCommand memberCommand; + + @Mock + CodePostBookmarkCommand codePostBookmarkCommand; + + @Mock + private NotificationCommand notificationCommand; + + @InjectMocks + private CodePostService codePostService; + + private Long codePostId; + private Long memberId; + private Long notificationId; + private String email; + private Member mockMember; + private CodePost mockCodePost; + private Notification mockNotification; + + @BeforeEach + void setUp() { + codePostId = 123L; + memberId = 1L; + notificationId = 2L; + email = "test@example.com"; + mockMember = Member.builder() + .email(email) + .nickname("tester") + .build(); + + mockCodePost = CodePost.builder() + .writerId(memberId) + .language(Language.JAVA) + .isPublic(false) + .build(); + + mockNotification = Notification.builder() + .title("test") + .content("test") + .status(NotificationStatus.PENDING) + .executeTime(null) + .member(mockMember) + .codePostId(codePostId) + .build(); + + ReflectionTestUtils.setField(mockMember, "id", memberId); + ReflectionTestUtils.setField(mockCodePost, "id", codePostId); + ReflectionTestUtils.setField(mockNotification, "id", notificationId); + } + + @Test + void save_post_review_day_is_null() { + // given + CodePostSaveRequestDto requestDto = new CodePostSaveRequestDto( + "test", + 1, + true, + null, + "test", + "java", + "import test" + ); + when(memberCommand.findByEmail(email)).thenReturn(mockMember); + when(codePostCommand.save(any(CodePost.class))).thenReturn(mockCodePost); + + + // when + Long result = codePostService.save(requestDto, email); + + // then + assertThat(result).isEqualTo(codePostId); + verify(notificationCommand, never()).save(any(Notification.class)); + } + + @Test + void save_post_review_day_is_not_null() { + // given + LocalDate reviewDay = LocalDate.now().plusDays(2); + CodePostSaveRequestDto requestDto = new CodePostSaveRequestDto( + "test", + 1, + true, + reviewDay, + "test", + "java", + "import test" + ); + when(memberCommand.findByEmail(email)).thenReturn(mockMember); + when(codePostCommand.save(any(CodePost.class))).thenReturn(mockCodePost); + doNothing().when(notificationCommand).save(any(Notification.class)); + + + // when + Long result = codePostService.save(requestDto, email); + + // then + assertThat(result).isEqualTo(codePostId); + verify(notificationCommand).save(any(Notification.class)); + } + + @Test + void 비공개_포스트를_다른_사용자가_조회() { + //given + Long userId = 10L; + Member mockUser = Member.builder() + .email("user@example.com") + .nickname("user") + .build(); + + ReflectionTestUtils.setField(mockUser, "id", userId); + + when(memberCommand.findByEmail(email)).thenReturn(mockUser); + when(codePostCommand.findByIdIsPublic(mockCodePost.getId(), mockUser.getId())) + .thenThrow(CustomRuntimeException.class); + + //when + then + assertThrows(CustomRuntimeException.class, () -> { + codePostService.findById(mockCodePost.getId(), email); + }); + } + + @Test + void 비공개_포스트를_작성자가_조회() { + //given + CodePostBookmark mockBookmark = CodePostBookmark.builder() + .member(mockMember) + .codePostId(codePostId) + .isDeleted(false) + .build(); + + ReflectionTestUtils.setField(mockBookmark, "id", 1L); + + when(memberCommand.findByEmail(email)).thenReturn(mockMember); + when(codePostCommand.findByIdIsPublic(codePostId, memberId)).thenReturn(mockCodePost); + when(memberCommand.findById(memberId)).thenReturn(mockMember); + + //when + CodePostResponseDto responseDto = codePostService.findById(codePostId, email); + + //then + assertThat(responseDto.getWriterEmail()).isEqualTo(mockMember.getEmail()); + assertThat(responseDto.getWriterNickName()).isEqualTo(mockMember.getNickname()); + } +} \ No newline at end of file From ae459625854e9dba7164dad7f8a4596355eb8a82 Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Sun, 8 Jun 2025 17:33:45 +0900 Subject: [PATCH 26/66] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EB=B0=8F=20=EB=8D=94=ED=8B=B0=20?= =?UTF-8?q?=EC=B2=B4=ED=82=B9=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ci에 테스트 실행 코드 추가 - update 로직에서 더티 체킹 문제 해결 --- .github/workflows/ci.yml | 15 +++++ .../codepost/service/CodePostCommand.java | 5 +- .../codepost/service/CodePostService.java | 2 +- .../domain/comment/base/CommentCommand.java | 5 +- .../domain/comment/base/CommentService.java | 2 +- .../domain/github/service/GithubCommand.java | 6 +- .../domain/github/service/GithubService.java | 2 +- .../AutoreviewApplicationTests.java | 2 + ...odePostBookmarkServiceIntegrationTest.java | 56 ++++++++++++------- .../CodePostServiceIntegrationTest.java | 26 ++++++--- 10 files changed, 87 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58d3b0a..6934f9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,21 @@ on: - main jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'adopt' + + - name: Run tests + run: ./gradlew test + build: name: Run on Ubuntu runs-on: ubuntu-latest diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostCommand.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostCommand.java index 73447da..4565d09 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostCommand.java +++ b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostCommand.java @@ -69,7 +69,10 @@ public Page findByPageSortByCommentCountAsc(Pageab } @Transactional - public void update(CodePost codePost, CodePostUpdateRequestDto requestDto) { + public void update(Long codePostId, CodePostUpdateRequestDto requestDto) { + CodePost codePost = codePostRepository.findById(codePostId).orElseThrow( + () -> new CustomRuntimeException(ErrorCode.NOT_FOUND_POST) + ); codePost.update(requestDto); } diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java index a206d8c..9381b06 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java +++ b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java @@ -180,7 +180,7 @@ public Long update(CodePostUpdateRequestDto requestDto, String email) { CodePost codePost = codePostCommand.findById(requestDto.getId()); memberValidator(email, codePost); Member member = memberCommand.findByEmail(email); - codePostCommand.update(codePost, requestDto); + codePostCommand.update(codePost.getId(), requestDto); boolean notificationExists = notificationCommand.existsByCodePostId(requestDto.getId()); diff --git a/src/main/java/org/example/autoreview/domain/comment/base/CommentCommand.java b/src/main/java/org/example/autoreview/domain/comment/base/CommentCommand.java index 39eee27..96356f6 100644 --- a/src/main/java/org/example/autoreview/domain/comment/base/CommentCommand.java +++ b/src/main/java/org/example/autoreview/domain/comment/base/CommentCommand.java @@ -36,7 +36,10 @@ public Page findByReplyPage(Long id, Long parentId, Pageable pageable) { } @Transactional - public void update(C comment, CommentUpdateRequestDto requestDto) { + public void update(Long commentId, CommentUpdateRequestDto requestDto) { + C comment = repository.findById(commentId).orElseThrow( + () -> new CustomRuntimeException(ErrorCode.NOT_FOUND_COMMENT) + ); comment.update(requestDto); } diff --git a/src/main/java/org/example/autoreview/domain/comment/base/CommentService.java b/src/main/java/org/example/autoreview/domain/comment/base/CommentService.java index 33145c9..171d125 100644 --- a/src/main/java/org/example/autoreview/domain/comment/base/CommentService.java +++ b/src/main/java/org/example/autoreview/domain/comment/base/CommentService.java @@ -111,7 +111,7 @@ private CommentListResponseDto getCommentListResponseDto(Page replyPage, List public Long update(CommentUpdateRequestDto requestDto, String email) { C comment = commentCommand.findById(requestDto.commentId()); memberValidator(comment.getWriterId(), email); - commentCommand.update(comment, requestDto); + commentCommand.update(comment.getId(), requestDto); return comment.getId(); } diff --git a/src/main/java/org/example/autoreview/domain/github/service/GithubCommand.java b/src/main/java/org/example/autoreview/domain/github/service/GithubCommand.java index 923052d..7ded920 100644 --- a/src/main/java/org/example/autoreview/domain/github/service/GithubCommand.java +++ b/src/main/java/org/example/autoreview/domain/github/service/GithubCommand.java @@ -3,6 +3,7 @@ import lombok.RequiredArgsConstructor; import org.example.autoreview.domain.github.entity.GithubToken; import org.example.autoreview.domain.github.entity.GithubTokenRepository; +import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; import org.example.autoreview.global.exception.errorcode.ErrorCode; import org.example.autoreview.global.exception.sub_exceptions.NotFoundException; import org.springframework.stereotype.Component; @@ -20,7 +21,10 @@ public GithubToken save(GithubToken githubToken) { } @Transactional - public GithubToken update(GithubToken githubToken, String newToken) { + public GithubToken update(Long githubTokenId, String newToken) { + GithubToken githubToken = githubTokenRepository.findById(githubTokenId).orElseThrow( + () -> new CustomRuntimeException(ErrorCode.NOT_FOUND_GITHUB_TOKEN) + ); return githubToken.update(newToken); } diff --git a/src/main/java/org/example/autoreview/domain/github/service/GithubService.java b/src/main/java/org/example/autoreview/domain/github/service/GithubService.java index 2d6b394..5c8901e 100644 --- a/src/main/java/org/example/autoreview/domain/github/service/GithubService.java +++ b/src/main/java/org/example/autoreview/domain/github/service/GithubService.java @@ -42,7 +42,7 @@ public Long save(String accessToken, String email) { public Long update(GithubTokenRequestDto requestDto) { GithubToken githubToken = githubCommand.findByEmail(requestDto.email()); - return githubCommand.update(githubToken, requestDto.githubToken()).getId(); + return githubCommand.update(githubToken.getId(), requestDto.githubToken()).getId(); } public boolean tokenCheck(String email) { diff --git a/src/test/java/org/example/autoreview/AutoreviewApplicationTests.java b/src/test/java/org/example/autoreview/AutoreviewApplicationTests.java index efe71bc..9464ac7 100644 --- a/src/test/java/org/example/autoreview/AutoreviewApplicationTests.java +++ b/src/test/java/org/example/autoreview/AutoreviewApplicationTests.java @@ -2,7 +2,9 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +@ActiveProfiles("test") @SpringBootTest class AutoreviewApplicationTests { diff --git a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java index 5232f11..cb66f19 100644 --- a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java +++ b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java @@ -1,38 +1,48 @@ package org.example.autoreview.domain.bookmark.CodePostBookmark.service; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.springframework.data.domain.PageRequest.of; - import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.request.CodePostBookmarkSaveRequestDto; import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkListResponseDto; +import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmarkRepository; import org.example.autoreview.domain.codepost.entity.CodePost; +import org.example.autoreview.domain.codepost.entity.CodePostRepository; import org.example.autoreview.domain.codepost.service.CodePostCommand; import org.example.autoreview.domain.member.entity.Member; import org.example.autoreview.domain.member.entity.MemberRepository; import org.example.autoreview.domain.member.entity.Role; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.transaction.annotation.Transactional; +import org.springframework.test.context.ActiveProfiles; -@SpringBootTest(properties = "spring.profiles.active=test") -@Transactional +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.springframework.data.domain.PageRequest.of; + +@ActiveProfiles("test") +@SpringBootTest class CodePostBookmarkServiceIntegrationTest { @Autowired - private CodePostBookmarkService service; + private CodePostBookmarkService codePostBookmarkService; + + @Autowired + private CodePostCommand codePostCommand; @Autowired private MemberRepository memberRepository; @Autowired - private CodePostCommand codePostCommand; + private CodePostRepository codePostRepository; + + @Autowired + private CodePostBookmarkRepository codePostBookmarkRepository; private CodePostBookmarkSaveRequestDto saveRequestDto; private Member testMember; private CodePost testCodePost; + @BeforeEach void setUp() { testMember = memberRepository.save(Member.builder() @@ -42,20 +52,27 @@ void setUp() { .build()); testCodePost = codePostCommand.save(CodePost.builder() - .writerId(1L) + .writerId(testMember.getId()) .isPublic(true) .build()); saveRequestDto = new CodePostBookmarkSaveRequestDto(testCodePost.getId()); } + @AfterEach + void cleanUp() { + memberRepository.deleteAll(); + codePostRepository.deleteAll(); + codePostBookmarkRepository.deleteAll(); + } + @Test void saveOrUpdate_trySave() { // when - Long bookmarkId = service.saveOrUpdate(saveRequestDto, testMember.getEmail()); + Long bookmarkId = codePostBookmarkService.saveOrUpdate(saveRequestDto, testMember.getEmail()); // then - CodePostBookmarkListResponseDto bookmarks = service.findAllByMemberId(testMember.getEmail(), of(0, 10)); + CodePostBookmarkListResponseDto bookmarks = codePostBookmarkService.findAllByMemberId(testMember.getEmail(), of(0, 10)); assertThat(bookmarkId).isNotNull(); assertThat(bookmarks.dtoList().size()).isEqualTo(1); @@ -66,27 +83,26 @@ void saveOrUpdate_trySave() { void saveOrUpdate_fallbackToUpdate() { // when // bookmark 여부 true -> false 로 변경 - Long saveFirstBookmarkId = service.saveOrUpdate(saveRequestDto, testMember.getEmail()); - Long saveSecondBookmarkId = service.saveOrUpdate(saveRequestDto, testMember.getEmail()); + Long bookmarkId = codePostBookmarkService.saveOrUpdate(saveRequestDto, testMember.getEmail()); + boolean beforeState = codePostBookmarkRepository.findById(bookmarkId).get().isDeleted(); - // then - CodePostBookmarkListResponseDto bookmarks = service.findAllByMemberId(testMember.getEmail(), of(0, 10)); + codePostBookmarkService.saveOrUpdate(saveRequestDto, testMember.getEmail()); - assertThat(bookmarks.dtoList().size()).isEqualTo(0); - assertThat(saveFirstBookmarkId).isEqualTo(saveSecondBookmarkId); + // then + assertThat(beforeState).isFalse(); + assertThat(codePostBookmarkRepository.findById(bookmarkId).get().isDeleted()).isTrue(); } - @Test void saveOrUpdate_then_findAllByMemberId() { // given CodePostBookmarkSaveRequestDto requestDto = new CodePostBookmarkSaveRequestDto(testCodePost.getId()); // when - Long bookmarkId = service.saveOrUpdate(requestDto, testMember.getEmail()); + Long bookmarkId = codePostBookmarkService.saveOrUpdate(requestDto, testMember.getEmail()); // then - CodePostBookmarkListResponseDto bookmarks = service.findAllByMemberId(testMember.getEmail(), of(0, 10)); + CodePostBookmarkListResponseDto bookmarks = codePostBookmarkService.findAllByMemberId(testMember.getEmail(), of(0, 10)); assertThat(bookmarks.dtoList().size()).isEqualTo(1); assertThat(bookmarks.dtoList().get(0).getCodePostId()).isEqualTo(testCodePost.getId()); diff --git a/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java b/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java index 3b45cc5..e346174 100644 --- a/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java +++ b/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java @@ -5,18 +5,19 @@ import org.example.autoreview.domain.codepost.dto.request.CodePostUpdateRequestDto; import org.example.autoreview.domain.codepost.dto.response.CodePostResponseDto; import org.example.autoreview.domain.codepost.entity.CodePost; +import org.example.autoreview.domain.codepost.entity.CodePostRepository; import org.example.autoreview.domain.codepost.entity.Language; import org.example.autoreview.domain.member.entity.Member; import org.example.autoreview.domain.member.entity.MemberRepository; import org.example.autoreview.domain.member.entity.Role; import org.example.autoreview.domain.notification.service.NotificationCommand; import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; -import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; @@ -25,7 +26,6 @@ @ActiveProfiles("test") @SpringBootTest -@Transactional public class CodePostServiceIntegrationTest { @Autowired @@ -34,6 +34,9 @@ public class CodePostServiceIntegrationTest { @Autowired private MemberRepository memberRepository; + @Autowired + private CodePostRepository codePostRepository; + @Autowired CodePostBookmarkCommand codePostBookmarkCommand; @@ -62,6 +65,12 @@ void setUp() { .build()); } + @AfterEach + void cleanUp() { + memberRepository.deleteAll(); + codePostRepository.deleteAll(); + } + @Test void save_post_review_day_is_null() { // given @@ -120,9 +129,9 @@ void save_post_review_day_is_not_null() { .build()); // when + then - assertThrows(CustomRuntimeException.class, () -> { - codePostService.findById(testCodePost.getId(), user.getEmail()); - }); + assertThrows(CustomRuntimeException.class, + () -> codePostService.findById(testCodePost.getId(), user.getEmail()) + ); } @Test @@ -146,11 +155,12 @@ void update() { String beforeLanguage = testCodePost.getLanguage().getType(); // when - codePostService.update(requestDto, testMember.getEmail()); + Long updatePostId = codePostService.update(requestDto, testMember.getEmail()); + CodePost updatePost = codePostRepository.findById(updatePostId).get(); // then - assertThat(testCodePost.getLanguage().getType()).isNotEqualTo(beforeLanguage); - assertThat(testCodePost.getLanguage()).isEqualTo(Language.CPP); + assertThat(updatePost.getLanguage().getType()).isNotEqualTo(beforeLanguage); + assertThat(updatePost.getLanguage()).isEqualTo(Language.CPP); } @Test From 4a68d3230957158a7f0ce789af8a590881445996 Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Mon, 9 Jun 2025 10:35:52 +0900 Subject: [PATCH 27/66] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=8B=A4=ED=96=89=20=EC=A0=84=20yml=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=B3=B5=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6934f9e..95f6d20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,8 +19,14 @@ jobs: java-version: '17' distribution: 'adopt' + - name: Grant execute permission for gradlew + run: chmod +x ./gradlew + + - name: Copy private resources + run: ./gradlew copyYml --no-daemon + - name: Run tests - run: ./gradlew test + run: ./gradlew test -Dspring.profiles.active=test build: name: Run on Ubuntu @@ -43,8 +49,7 @@ jobs: run: chmod +x ./gradlew - name: Copy private resources - run: | - ./gradlew copyYml --no-daemon + run: ./gradlew copyYml --no-daemon - name: Create Firebase JSON file run: | @@ -73,7 +78,6 @@ jobs: if: github.ref == 'refs/heads/main' run: docker build . --tag ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPOSITORY_NAME }}:prod -f Dockerfile.prod - - name: Push Dev Docker image if: github.ref == 'refs/heads/dev' run: | @@ -85,7 +89,6 @@ jobs: run: | echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPOSITORY_NAME }}:prod - deploy: runs-on: ubuntu-latest @@ -110,7 +113,6 @@ jobs: username: ${{ secrets.DEV_USERNAME }} key: ${{ secrets.DEV_PRIVATE_KEY }} script: | - # 도커 컨테이너 재배포 sudo docker stop ${{ secrets.DOCKER_CONTAINER_NAME }} sudo docker rm ${{ secrets.DOCKER_CONTAINER_NAME }} sudo docker rmi ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPOSITORY_NAME }}:dev @@ -124,8 +126,7 @@ jobs: username: ${{ secrets.PROD_USERNAME }} key: ${{ secrets.PROD_PRIVATE_KEY }} script: | - # 도커 컨테이너 재배포 sudo docker stop ${{ secrets.DOCKER_CONTAINER_NAME }} sudo docker rm ${{ secrets.DOCKER_CONTAINER_NAME }} sudo docker rmi ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPOSITORY_NAME }}:prod - sudo docker run -d --name ${{ secrets.DOCKER_CONTAINER_NAME }} --network ${{ secrets.DOCKER_NETWORK_NAME }} -p 8080:8080 ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPOSITORY_NAME }}:prod \ No newline at end of file + sudo docker run -d --name ${{ secrets.DOCKER_CONTAINER_NAME }} --network ${{ secrets.DOCKER_NETWORK_NAME }} -p 8080:8080 ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPOSITORY_NAME }}:prod From 6040db2632a2cdd4a330623d59011358bef758fb Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Mon, 9 Jun 2025 15:43:13 +0900 Subject: [PATCH 28/66] =?UTF-8?q?refactor:=20test=20=EC=A0=95=EC=83=81=20?= =?UTF-8?q?=EB=8F=99=EC=9E=91=EC=9D=84=20=EC=9C=84=ED=95=9C=20mySql=20?= =?UTF-8?q?=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95f6d20..37193d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,26 @@ jobs: java-version: '17' distribution: 'adopt' + - name: MySQL 설치 + uses: samin/mysql-action@v1 + with: + host port: 3306 # Optional, default value is 3306. The port of host + container port: 3307 # Optional, default value is 3306. The port of container + character set server: 'utf8' # Optional, default value is 'utf8mb4'. The '--character-set-server' option for mysqld + collation server: 'utf8_general_ci' # Optional, default value is 'utf8mb4_general_ci'. The '--collation-server' option for mysqld + mysql version: '8.0' # Optional, default value is "latest". The version of the MySQL + mysql database: test # Optional, default value is "test". The specified database which will be create + mysql user: tester # Required if "mysql root password" is empty, default is empty. The superuser for the specified database. Of course you can use secrets, too + mysql password: ${{ secrets.TEST_DB_PASSWORD }} + + - name: yml 파일 생성 + run: | + cd ./src/test/resources + rm -rf ./application.properties + touch ./application.yml + echo "${{ secrets.APPLICATION_TEST_YML }}" > ./application-test.yml + shell: bash + - name: Grant execute permission for gradlew run: chmod +x ./gradlew From d2086f5fbc5a3dcd28d05062d98d548b5099d584 Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Mon, 9 Jun 2025 16:01:12 +0900 Subject: [PATCH 29/66] =?UTF-8?q?refactor:=20yml=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37193d6..b44f14a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: cd ./src/test/resources rm -rf ./application.properties touch ./application.yml - echo "${{ secrets.APPLICATION_TEST_YML }}" > ./application-test.yml + echo "${{ secrets.APPLICATION_TEST_YML }}" > ./src/test/resources/application-test.yml shell: bash - name: Grant execute permission for gradlew From 20fe22057cdf44724ab8a8d05071bce536a58f6d Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Mon, 9 Jun 2025 16:04:03 +0900 Subject: [PATCH 30/66] =?UTF-8?q?refactor:=20dir=20=EC=97=86=EC=9D=84=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EC=83=9D=EC=84=B1=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b44f14a..7e31f73 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,9 +33,9 @@ jobs: - name: yml 파일 생성 run: | - cd ./src/test/resources - rm -rf ./application.properties - touch ./application.yml + mkdir -p ./src/test/resources + rm -f ./src/test/resources/application.properties + touch ./src/test/resources/application.yml echo "${{ secrets.APPLICATION_TEST_YML }}" > ./src/test/resources/application-test.yml shell: bash From f296ee9897d4183fefdabc586fe790a75935aadd Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Mon, 9 Jun 2025 17:08:36 +0900 Subject: [PATCH 31/66] =?UTF-8?q?refactor:=20ci=20test=20=EB=B9=8C?= =?UTF-8?q?=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e31f73..f757338 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,11 +42,11 @@ jobs: - name: Grant execute permission for gradlew run: chmod +x ./gradlew - - name: Copy private resources - run: ./gradlew copyYml --no-daemon + - name: 어플리케이션 실행 테스트(테스트 코드 제외하고 실행) + run: ./gradlew clean build --exclude-task test - - name: Run tests - run: ./gradlew test -Dspring.profiles.active=test + - name: Test 실행(테스트 코드만 실행) + run: ./gradlew --info test build: name: Run on Ubuntu From b78ce8df8beaabaa7d11841089a67f9653b7dbd0 Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Mon, 9 Jun 2025 17:28:16 +0900 Subject: [PATCH 32/66] =?UTF-8?q?refactor:=20yml=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=A0=9C=EB=8C=80=EB=A1=9C=20=EC=83=9D=EC=84=B1=EB=90=90?= =?UTF-8?q?=EB=8A=94=EC=A7=80=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f757338..4d24aea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,10 @@ jobs: echo "${{ secrets.APPLICATION_TEST_YML }}" > ./src/test/resources/application-test.yml shell: bash + - name: Debug application-test.yml + run: | + head -n 10 ./src/test/resources/application-test.yml + - name: Grant execute permission for gradlew run: chmod +x ./gradlew From bdf2d9ea34db85883b1d6e002b061efbd50c6a93 Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Mon, 9 Jun 2025 17:39:11 +0900 Subject: [PATCH 33/66] =?UTF-8?q?refactor:=20yml=20=EB=B6=88=EB=9F=AC?= =?UTF-8?q?=EC=98=A4=EB=8A=94=20=EB=B0=A9=EC=8B=9D=EC=9D=84=20base64=20?= =?UTF-8?q?=EB=94=94=EC=BD=94=EB=94=A9=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d24aea..08b8713 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,11 +34,11 @@ jobs: - name: yml 파일 생성 run: | mkdir -p ./src/test/resources - rm -f ./src/test/resources/application.properties - touch ./src/test/resources/application.yml - echo "${{ secrets.APPLICATION_TEST_YML }}" > ./src/test/resources/application-test.yml + rm -f ./src/test/resources/application-test.yml + printf "%s" "${{ secrets.APPLICATION_TEST_YML_BASE64 }}" | base64 -d > ./src/test/resources/application-test.yml shell: bash + - name: Debug application-test.yml run: | head -n 10 ./src/test/resources/application-test.yml From 8341b8c913584a173a75aab2cfeba0341a24b12c Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Mon, 9 Jun 2025 23:17:00 +0900 Subject: [PATCH 34/66] =?UTF-8?q?feat:=20=EA=B2=80=EC=83=89=20=EB=B0=8F=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?api=EC=97=90=20=ED=95=84=ED=84=B0=EB=A7=81=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CodePostController.java | 30 +++-- .../codepost/entity/CodePostRepository.java | 88 +++++++++++++ .../codepost/service/CodePostCommand.java | 30 +++++ .../codepost/service/CodePostService.java | 117 +++++++++++++++--- 4 files changed, 235 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java b/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java index 013ba9f..77cce3f 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java +++ b/src/main/java/org/example/autoreview/domain/codepost/controller/CodePostController.java @@ -8,8 +8,6 @@ import org.example.autoreview.domain.codepost.dto.response.CodePostListResponseDto; import org.example.autoreview.domain.codepost.dto.response.CodePostResponseDto; import org.example.autoreview.domain.codepost.service.CodePostService; -import org.springframework.data.domain.Pageable; -import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; @@ -33,8 +31,12 @@ public ResponseEntity save(@RequestBody CodePostSaveRequestDto requestDto, @Operation(summary = "제목으로 코드 포스트 검색", description = "공백 또는 null 입력 시 에러 반환") @GetMapping("/search") public ResponseEntity search(@RequestParam String keyword, - @PageableDefault(page = 0, size = 9) Pageable pageable) { - return ResponseEntity.ok().body(codePostService.search(keyword, pageable)); + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "9") int size, + @RequestParam(defaultValue = "desc") String direction, + @RequestParam(defaultValue = "id") String sortBy, + @RequestParam(defaultValue = "all") String language) { + return ResponseEntity.ok().body(codePostService.search(keyword, page,size,direction,sortBy,language)); } @Operation(summary = "코드 포스트 단일 조회", description = "공개된 포스트 or 작성자일 경우만 조회됨 + 북마크 여부 포함") @@ -56,17 +58,25 @@ public ResponseEntity viewAll(@RequestParam(defaultValu @Operation(summary = "내가 쓴 코드 포스트 조회", description = "내가 쓴 코드 포스트 조회") @GetMapping("/own") - public ResponseEntity myCodePostPage(@PageableDefault(page = 0, size = 9) Pageable pageable, - @AuthenticationPrincipal UserDetails userDetails) { - return ResponseEntity.ok().body(codePostService.findByMemberId(pageable, userDetails.getUsername())); + public ResponseEntity myCodePostPage(@AuthenticationPrincipal UserDetails userDetails, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "9") int size, + @RequestParam(defaultValue = "desc") String direction, + @RequestParam(defaultValue = "id") String sortBy, + @RequestParam(defaultValue = "all") String language) { + return ResponseEntity.ok().body(codePostService.findByMemberId(userDetails.getUsername(),page,size,direction,sortBy,language)); } @Operation(summary = "내 코드 포스트 검색", description = "내 코드 포스트 검색") @GetMapping("/own/search") public ResponseEntity mySearch(@RequestParam String keyword, - @PageableDefault(page = 0, size = 9) Pageable pageable, - @AuthenticationPrincipal UserDetails userDetails) { - return ResponseEntity.ok().body(codePostService.mySearch(keyword, pageable, userDetails.getUsername())); + @AuthenticationPrincipal UserDetails userDetails, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "9") int size, + @RequestParam(defaultValue = "desc") String direction, + @RequestParam(defaultValue = "id") String sortBy, + @RequestParam(defaultValue = "all") String language) { + return ResponseEntity.ok().body(codePostService.mySearch(keyword, userDetails.getUsername(), page,size,direction,sortBy,language)); } @Operation(summary = "코드 포스트 수정", description = "코드 포스트 수정") diff --git a/src/main/java/org/example/autoreview/domain/codepost/entity/CodePostRepository.java b/src/main/java/org/example/autoreview/domain/codepost/entity/CodePostRepository.java index d15e471..b74667c 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/entity/CodePostRepository.java +++ b/src/main/java/org/example/autoreview/domain/codepost/entity/CodePostRepository.java @@ -31,9 +31,71 @@ public interface CodePostRepository extends JpaRepository { @Query("SELECT c FROM CodePost c WHERE c.writerId =:id AND c.title LIKE %:keyword% ORDER BY c.id DESC") Page mySearch(@Param("keyword") String keyword, Pageable pageable, @Param("id") Long id); + @Query(""" + SELECT new org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto(c, m) + FROM CodePost c + LEFT JOIN c.codePostCommentList cm + INNER JOIN Member m ON c.writerId = m.id + WHERE c.writerId = :writerId + AND LOWER(c.title) LIKE LOWER(CONCAT('%', :keyword, '%')) + AND (:language = 'all' OR c.language = :language) + GROUP BY c, m + ORDER BY COUNT(cm) DESC + """) + Page mySearchSortByCommentCountDesc( + @Param("keyword") String keyword, + Pageable pageable, + @Param("writerId") Long writerId, + @Param("language") Language language + ); + + @Query(""" + SELECT new org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto(c, m) + FROM CodePost c + LEFT JOIN c.codePostCommentList cm + INNER JOIN Member m ON c.writerId = m.id + WHERE c.writerId = :writerId + AND LOWER(c.title) LIKE LOWER(CONCAT('%', :keyword, '%')) + AND (:language = 'all' OR c.language = :language) + GROUP BY c, m + ORDER BY COUNT(cm) ASC + """) + Page mySearchSortByCommentCountAsc( + @Param("keyword") String keyword, + Pageable pageable, + @Param("writerId") Long writerId, + @Param("language") Language language + ); + + @Query("SELECT c FROM CodePost c WHERE c.writerId =:id") Page findByMemberId(@Param("id") Long id, Pageable pageable); + @Query(""" + SELECT new org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto(c, m) + FROM CodePost c + LEFT JOIN c.codePostCommentList cm + INNER JOIN Member m ON c.writerId = m.id + WHERE c.writerId = :memberId + AND c.isPublic = TRUE + AND (:language = 'all' OR c.language = :language) + GROUP BY c, m + ORDER BY COUNT(cm) DESC + """) + Page findByMemberIdSortByCommentCountDesc(Pageable pageable, @Param("memberId") Long memberId, @Param("language") Language language); + + @Query(""" + SELECT new org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto(c, m) + FROM CodePost c + LEFT JOIN c.codePostCommentList cm + INNER JOIN Member m ON c.writerId = m.id + WHERE c.writerId = :memberId + AND c.isPublic = TRUE + AND (:language = 'all' OR c.language = :language) + GROUP BY c, m + ORDER BY COUNT(cm) ASC + """) + Page findByMemberIdSortByCommentCountAsc(Pageable pageable, @Param("memberId") Long memberId, @Param("language") Language language); @Query(""" SELECT new org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto(c, m) @@ -41,6 +103,7 @@ public interface CodePostRepository extends JpaRepository { LEFT JOIN c.codePostCommentList cm INNER JOIN Member m ON c.writerId = m.id AND c.isPublic = TRUE + AND LOWER(c.title) LIKE LOWER(CONCAT('%', :keyword, '%')) AND (:language = 'all' OR c.language = :language) GROUP BY c, m ORDER BY COUNT(cm) DESC @@ -59,6 +122,31 @@ ORDER BY COUNT(cm) ASC """) Page findByPageSortByCommentCountAsc(Pageable pageable, @Param("language") Language language); + @Query(""" + SELECT new org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto(c, m) + FROM CodePost c + LEFT JOIN c.codePostCommentList cm + INNER JOIN Member m ON c.writerId = m.id + AND c.isPublic = TRUE + AND LOWER(c.title) LIKE LOWER(CONCAT('%', :keyword, '%')) + AND (:language = 'all' OR c.language = :language) + GROUP BY c, m + ORDER BY COUNT(cm) DESC + """) + Page searchSortByCommentCountDesc(@Param("keyword") String keyword, Pageable pageable, @Param("language") Language language); + + @Query(""" + SELECT new org.example.autoreview.domain.codepost.dto.response.CodePostThumbnailResponseDto(c, m) + FROM CodePost c + LEFT JOIN c.codePostCommentList cm + INNER JOIN Member m ON c.writerId = m.id + AND c.isPublic = TRUE + AND LOWER(c.title) LIKE LOWER(CONCAT('%', :keyword, '%')) + AND (:language = 'all' OR c.language = :language) + GROUP BY c, m + ORDER BY COUNT(cm) ASC + """) + Page searchSortByCommentCountAsc(@Param("keyword") String keyword, Pageable pageable, @Param("language") Language language); } diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostCommand.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostCommand.java index 4565d09..dce0e3b 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostCommand.java +++ b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostCommand.java @@ -48,11 +48,31 @@ public Page findByMemberId(Long memberId, Pageable pageable) { return codePostRepository.findByMemberId(memberId, pageable); } + @Transactional(readOnly = true) + public Page findByMemberIdSortByCommentCountDesc(Pageable pageable, Long memberId, Language language) { + return codePostRepository.findByMemberIdSortByCommentCountDesc(pageable, memberId, language); + } + + @Transactional(readOnly = true) + public Page findByMemberIdSortByCommentCountAsc(Pageable pageable, Long memberId, Language language) { + return codePostRepository.findByMemberIdSortByCommentCountAsc(pageable, memberId, language); + } + @Transactional(readOnly = true) public Page mySearch(String keyword, Pageable pageable, Long memberId) { return codePostRepository.mySearch(keyword, pageable, memberId); } + @Transactional(readOnly = true) + public Page mySearchSortByCommentCountDesc(String keyword, Pageable pageable, Long memberId, Language language) { + return codePostRepository.mySearchSortByCommentCountDesc(keyword, pageable, memberId, language); + } + + @Transactional(readOnly = true) + public Page mySearchSortByCommentCountAsc(String keyword, Pageable pageable, Long memberId, Language language) { + return codePostRepository.mySearchSortByCommentCountAsc(keyword, pageable, memberId, language); + } + @Transactional(readOnly = true) public Page findByPage(Pageable pageable, Language language) { return codePostRepository.findByPage(pageable, language); @@ -68,6 +88,16 @@ public Page findByPageSortByCommentCountAsc(Pageab return codePostRepository.findByPageSortByCommentCountAsc(pageable, language); } + @Transactional(readOnly = true) + public Page searchSortByCommentCountDesc(String keyword, Pageable pageable, Language language) { + return codePostRepository.searchSortByCommentCountDesc(keyword, pageable, language); + } + + @Transactional(readOnly = true) + public Page searchSortByCommentCountAsc(String keyword, Pageable pageable, Language language) { + return codePostRepository.searchSortByCommentCountAsc(keyword, pageable, language); + } + @Transactional public void update(Long codePostId, CodePostUpdateRequestDto requestDto) { CodePost codePost = codePostRepository.findById(codePostId).orElseThrow( diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java index 9381b06..d68a623 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java +++ b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java @@ -29,7 +29,6 @@ import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; @Slf4j @RequiredArgsConstructor @@ -67,17 +66,44 @@ public Long save(CodePostSaveRequestDto requestDto, String email) { * 키워드에 맞는 제목을 가진 코드 포스트를 조회하는 검색 메서드이다. * 키워드 뒤에 와일드 카드를 붙혀서 연관된 결과도 조회한다. */ - @Transactional(readOnly = true) - public CodePostListResponseDto search(String keyword, Pageable pageable) { + public CodePostListResponseDto search(String keyword, int page, int size, String direction, String sortBy, String language) { keywordValidator(keyword); String wildcardKeyword = keyword + "*"; - Page codePostPage = codePostCommand.search(wildcardKeyword, pageable) + Language lang = Language.of(language); + + if (sortBy.equals("commentCount")) { + Pageable pageable = PageRequest.of(page, size); + return searchSortByCommentCount(wildcardKeyword, pageable, direction, lang); + } + + Sort.Direction sortDirection = Sort.Direction.fromString(direction); + Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sortBy)); + return searchSorted(wildcardKeyword, pageable, lang); + } + + private CodePostListResponseDto searchSortByCommentCount(String keyword, Pageable pageable, String direction, Language language) { + Page page; + if (direction.equals("desc")) { + page = codePostCommand.searchSortByCommentCountDesc(keyword, pageable, language); + } else { + page = codePostCommand.searchSortByCommentCountAsc(keyword, pageable, language); + } + return new CodePostListResponseDto(page.getContent(), page.getTotalPages()); + } + + private CodePostListResponseDto searchSorted(String keyword, Pageable pageable, Language language) { + Page codePostPage = codePostCommand.search(keyword, pageable); + + List content = codePostPage + .stream() + .filter(post -> post.isPublic() && (language == Language.ALL || post.getLanguage() == language)) .map(post -> { Member member = memberCommand.findById(post.getWriterId()); return new CodePostThumbnailResponseDto(post, member); - }); + }) + .toList(); - return new CodePostListResponseDto(codePostPage.getContent(), codePostPage.getTotalPages()); + return new CodePostListResponseDto(content, codePostPage.getTotalPages()); } /** @@ -138,36 +164,87 @@ private CodePostListResponseDto findByPage(Pageable pageable, Language language) /** * 사용자가 작성한 모든 코드 포스트를 조회하는 메서드이다. */ - public CodePostListResponseDto findByMemberId(Pageable pageable, String email) { + public CodePostListResponseDto findByMemberId(String email, int page, int size, String direction, String sortBy, String language) { Member member = memberCommand.findByEmail(email); + Language lang = Language.of(language); + + Pageable pageable; + if (sortBy.equals("commentCount")) { + pageable = PageRequest.of(page, size); + return findByMemberIdSortByCommentCount(pageable, direction, member.getId(), lang); + } + + Sort.Direction sortDirection = Sort.Direction.fromString(direction); + pageable = PageRequest.of(page, size, Sort.by(sortDirection, sortBy)); + Page codePostPage = codePostCommand.findByMemberId(member.getId(), pageable); - return new CodePostListResponseDto(convertListDto(codePostPage, member), codePostPage.getTotalPages()); + + List content = codePostPage + .stream() + .filter(post -> post.isPublic() && (lang == Language.ALL || post.getLanguage() == lang)) + .map(post -> new CodePostThumbnailResponseDto(post, member)) + .toList(); + + return new CodePostListResponseDto(content, codePostPage.getTotalPages()); + } + + private CodePostListResponseDto findByMemberIdSortByCommentCount(Pageable pageable, String direction, Long memberId, Language language) { + Page page; + + if (direction.equalsIgnoreCase("desc")) { + page = codePostCommand.findByMemberIdSortByCommentCountDesc(pageable, memberId, language); + } else { + page = codePostCommand.findByMemberIdSortByCommentCountAsc(pageable, memberId, language); + } + + return new CodePostListResponseDto(page.getContent(), page.getTotalPages()); } + /** * 사용자가 작성한 포스트들 중 키워드에 맞는 포스트들을 조회하는 메서드이다. */ - public CodePostListResponseDto mySearch(String keyword, Pageable pageable, String email) { + public CodePostListResponseDto mySearch(String keyword, String email, int page, int size, String direction, String sortBy, String language) { Member member = memberCommand.findByEmail(email); keywordValidator(keyword); - Page codePostPage = codePostCommand.mySearch(keyword, pageable, member.getId()); - return new CodePostListResponseDto(convertListDto(codePostPage, member), codePostPage.getTotalPages()); + Language lang = Language.of(language); + + if (sortBy.equals("commentCount")) { + Pageable pageable = PageRequest.of(page, size); + return mySearchSortByCommentCount(keyword, member, pageable, direction, lang); + } + + Sort.Direction sortDirection = Sort.Direction.fromString(direction); + Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sortBy)); + return mySearchSorted(keyword, member, pageable, lang); } - private static void keywordValidator(String keyword) { - if (keyword == null || keyword.isBlank()) { - throw new IllegalArgumentException(ErrorCode.INVALID_PARAMETER.getMessage()); + private CodePostListResponseDto mySearchSortByCommentCount(String keyword, Member member, Pageable pageable, String direction, Language language) { + Page page; + if (direction.equals("desc")) { + page = codePostCommand.mySearchSortByCommentCountDesc(keyword, pageable, member.getId(), language); + } else { + page = codePostCommand.mySearchSortByCommentCountAsc(keyword, pageable, member.getId(), language); } + return new CodePostListResponseDto(page.getContent(), page.getTotalPages()); } - private List convertListDto(Page page, Member member) { - return page.stream() - .map(post -> getCodePostThumbnailResponseDto(post, member)) - .collect(Collectors.toList()); + private CodePostListResponseDto mySearchSorted(String keyword, Member member, Pageable pageable, Language language) { + Page codePostPage = codePostCommand.mySearch(keyword, pageable, member.getId()); + + List content = codePostPage + .stream() + .filter(post -> post.isPublic() && (language == Language.ALL || post.getLanguage() == language)) + .map(post -> new CodePostThumbnailResponseDto(post, member)) + .toList(); + + return new CodePostListResponseDto(content, codePostPage.getTotalPages()); } - private CodePostThumbnailResponseDto getCodePostThumbnailResponseDto(CodePost codePost, Member member) { - return new CodePostThumbnailResponseDto(codePost, member); + private static void keywordValidator(String keyword) { + if (keyword == null || keyword.isBlank()) { + throw new IllegalArgumentException(ErrorCode.INVALID_PARAMETER.getMessage()); + } } /** From 5830674f21b6546c0cd5d1be35c652577d12e7b2 Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Thu, 12 Jun 2025 11:40:40 +0900 Subject: [PATCH 35/66] =?UTF-8?q?test:=20code=20post=20=EB=8C=93=EA=B8=80?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...CodePostCommentServiceIntegrationTest.java | 26 ++++ .../CodePostCommentServiceUnitTest.java | 140 ++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java create mode 100644 src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceUnitTest.java diff --git a/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java b/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java new file mode 100644 index 0000000..827e5ec --- /dev/null +++ b/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java @@ -0,0 +1,26 @@ +package org.example.autoreview.domain.comment.codepost.service; + +import org.example.autoreview.domain.codepost.service.CodePostCommand; +import org.example.autoreview.domain.member.service.MemberCommand; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@ActiveProfiles("test") +@SpringBootTest +public class CodePostCommentServiceIntegrationTest { + + @Autowired + private CodePostCommentCommand codePostCommentCommand; + + @Autowired + private CodePostCommand codePostCommand; + + @Autowired + private MemberCommand memberCommand; + + @Autowired + private CodePostCommentService codePostCommentService; + + +} diff --git a/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceUnitTest.java b/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceUnitTest.java new file mode 100644 index 0000000..afd1cb2 --- /dev/null +++ b/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceUnitTest.java @@ -0,0 +1,140 @@ +package org.example.autoreview.domain.comment.codepost.service; + +import org.example.autoreview.domain.codepost.entity.CodePost; +import org.example.autoreview.domain.codepost.entity.Language; +import org.example.autoreview.domain.codepost.service.CodePostCommand; +import org.example.autoreview.domain.comment.base.dto.request.CommentSaveRequestDto; +import org.example.autoreview.domain.comment.codepost.entity.CodePostComment; +import org.example.autoreview.domain.member.entity.Member; +import org.example.autoreview.domain.member.service.MemberCommand; +import org.junit.jupiter.api.BeforeEach; +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 org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class CodePostCommentServiceUnitTest { + + @Mock + private CodePostCommentCommand codePostCommentCommand; + + @Mock + private CodePostCommand codePostCommand; + + @Mock + private MemberCommand memberCommand; + + @InjectMocks + private CodePostCommentService codePostCommentService; + + private Long memberId; + private String email; + private Member mockMember; + + private Long codePostId; + private CodePost mockCodePost; + + private Long commentId; + private CodePostComment mockComment; + + @BeforeEach + void setUp() { + commentId = 1L; + codePostId = 123L; + memberId = 1L; + email = "test@example.com"; + + mockMember = Member.builder() + .email(email) + .nickname("tester") + .build(); + + mockCodePost = CodePost.builder() + .writerId(memberId) + .language(Language.JAVA) + .isPublic(false) + .build(); + + mockComment = CodePostComment.builder() + .codePost(mockCodePost) + .writerId(memberId) + .body("test") + .isPublic(false) + .build(); + + ReflectionTestUtils.setField(mockMember, "id", memberId); + ReflectionTestUtils.setField(mockCodePost, "id", codePostId); + ReflectionTestUtils.setField(mockComment, "id", 1L); + } + + @Test + void save_comment() { + // given + CommentSaveRequestDto requestDto = new CommentSaveRequestDto( + codePostId, + null, + false, + null, + null, + null + ); + + CodePostComment comment = CodePostComment.builder() + .codePost(mockCodePost) + .writerId(memberId) + .body("test") + .isPublic(false) + .build(); + ReflectionTestUtils.setField(comment, "id", 1L); + when(memberCommand.findByEmail(email)).thenReturn(mockMember); + when(codePostCommand.findById(codePostId)).thenReturn(mockCodePost); + when(codePostCommentCommand.save(any())).thenReturn(comment); + + // when + Long commentId = codePostCommentService.save(requestDto, email); + + // then + assertThat(commentId).isEqualTo(1L); + assertThat(mockCodePost).isEqualTo(comment.getCodePost()); + } + + @Test + void save_reply() { + // given + CommentSaveRequestDto requestDto = new CommentSaveRequestDto( + codePostId, + null, + false, + null, + null, + commentId + ); + + CodePostComment reply = CodePostComment.builder() + .codePost(mockCodePost) + .writerId(memberId) + .body("test") + .isPublic(false) + .parent(mockComment) + .build(); + + ReflectionTestUtils.setField(reply, "id", 2L); + when(memberCommand.findByEmail(email)).thenReturn(mockMember); + when(codePostCommand.findById(codePostId)).thenReturn(mockCodePost); + when(codePostCommentCommand.save(any())).thenReturn(reply); + + // when + Long replyId = codePostCommentService.save(requestDto, email); + + // then + assertThat(replyId).isEqualTo(2L); + assertThat(reply.getParent()).isEqualTo(mockComment); + } +} \ No newline at end of file From 7a80d601a1ef6526085b51d62d3e19fd39400225 Mon Sep 17 00:00:00 2001 From: ehgur Date: Sat, 14 Jun 2025 00:49:41 +0900 Subject: [PATCH 36/66] =?UTF-8?q?refactor:=20git=20actions=20=EA=B0=80?= =?UTF-8?q?=EC=83=81=20=EB=A8=B8=EC=8B=A0=EC=9D=84=20=ED=86=B5=ED=95=B4=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=84=B1=EA=B3=B5=20=EC=8B=9C?= =?UTF-8?q?=EB=8F=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 가상 머신에 MySQL & Redis 설치 --- .github/workflows/ci.yml | 73 +++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08b8713..01f2801 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,39 +9,56 @@ on: jobs: test: runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up JDK - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'adopt' + services: + mysql: + image: mysql:8.0 + ports: + - 3306:3306 + env: + MYSQL_ROOT_PASSWORD: ${{ secrets.TEST_DB_PASSWORD}} + MYSQL_DATABASE: test + MYSQL_USER: tester + MYSQL_PASSWORD: ${{ secrets.TEST_DB_PASSWORD}} + options: >- + --health-cmd "mysqladmin ping -h localhost" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + redis: + image: redis:7.0 + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 - - name: MySQL 설치 - uses: samin/mysql-action@v1 - with: - host port: 3306 # Optional, default value is 3306. The port of host - container port: 3307 # Optional, default value is 3306. The port of container - character set server: 'utf8' # Optional, default value is 'utf8mb4'. The '--character-set-server' option for mysqld - collation server: 'utf8_general_ci' # Optional, default value is 'utf8mb4_general_ci'. The '--collation-server' option for mysqld - mysql version: '8.0' # Optional, default value is "latest". The version of the MySQL - mysql database: test # Optional, default value is "test". The specified database which will be create - mysql user: tester # Required if "mysql root password" is empty, default is empty. The superuser for the specified database. Of course you can use secrets, too - mysql password: ${{ secrets.TEST_DB_PASSWORD }} - - - name: yml 파일 생성 - run: | - mkdir -p ./src/test/resources - rm -f ./src/test/resources/application-test.yml - printf "%s" "${{ secrets.APPLICATION_TEST_YML_BASE64 }}" | base64 -d > ./src/test/resources/application-test.yml - shell: bash + steps: + - uses: actions/checkout@v3 + - name: MySQL 연결 확인 + run: | + sudo apt-get update && sudo apt-get install -y mysql-client + mysql -htest -P3306 -utester -p${{ secrets.TEST_DB_PASSWORD}} -e "SHOW DATABASES;" - - name: Debug application-test.yml + - name: Redis 연결 확인 run: | - head -n 10 ./src/test/resources/application-test.yml + sudo apt-get update && sudo apt-get install -y redis-tools + redis-cli -h localhost ping + + - name: 테스트 실행 + run: ./gradlew test + env: + DB_HOST: 127.0.0.1 + DB_PORT: 3306 + DB_NAME: test + DB_USER: tester + DB_PASSWORD: ${{ secrets.TEST_DB_PASSWORD}} + REDIS_HOST: 127.0.0.1 + REDIS_PORT: 6379 - name: Grant execute permission for gradlew run: chmod +x ./gradlew From c5a7ec5b7b063853e6c56508dddcc453942537b7 Mon Sep 17 00:00:00 2001 From: ehgur Date: Sat, 14 Jun 2025 00:54:13 +0900 Subject: [PATCH 37/66] =?UTF-8?q?refactor:=20git=20actions=20=EA=B0=80?= =?UTF-8?q?=EC=83=81=20=EB=A8=B8=EC=8B=A0=EC=9D=84=20=ED=86=B5=ED=95=B4=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=84=B1=EA=B3=B5=20=EC=8B=9C?= =?UTF-8?q?=EB=8F=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GitHub Actions의 services에서 설정한 MySQL 컨테이너의 호스트 이름은 localhost 또는 127.0.0.1로 접근해야 하는데 "-htest" 라고 작성해서 "test"라는 별도 호스트 이름을 찾으려고 했기 때문에 실패함 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01f2801..ea8dfe1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: - name: MySQL 연결 확인 run: | sudo apt-get update && sudo apt-get install -y mysql-client - mysql -htest -P3306 -utester -p${{ secrets.TEST_DB_PASSWORD}} -e "SHOW DATABASES;" + mysql -hlocalhost -P3306 -utester -p${{ secrets.TEST_DB_PASSWORD}} -e "SHOW DATABASES;" - name: Redis 연결 확인 run: | From ec8296884c2645bf641e9a7bf406f5630873711f Mon Sep 17 00:00:00 2001 From: ehgur Date: Sat, 14 Jun 2025 00:57:36 +0900 Subject: [PATCH 38/66] =?UTF-8?q?refactor:=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea8dfe1..3e5ef56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: - name: MySQL 연결 확인 run: | sudo apt-get update && sudo apt-get install -y mysql-client - mysql -hlocalhost -P3306 -utester -p${{ secrets.TEST_DB_PASSWORD}} -e "SHOW DATABASES;" + mysql -hlocalhost -P3306 -utester -p${{ secrets.TEST_DB_PASSWORD }} -e "SHOW DATABASES;" - name: Redis 연결 확인 run: | From 5a135888cd60a7a3cb8d6fd4b4261e094dfb1e35 Mon Sep 17 00:00:00 2001 From: ehgur Date: Sat, 14 Jun 2025 01:00:28 +0900 Subject: [PATCH 39/66] =?UTF-8?q?refactor:=20tcp=20=EA=B0=95=EC=A0=9C=20?= =?UTF-8?q?=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e5ef56..16e03c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: - name: MySQL 연결 확인 run: | sudo apt-get update && sudo apt-get install -y mysql-client - mysql -hlocalhost -P3306 -utester -p${{ secrets.TEST_DB_PASSWORD }} -e "SHOW DATABASES;" + mysql -h127.0.0.1 -P3306 -utester -p${{ secrets.TEST_DB_PASSWORD }} -e "SHOW DATABASES;" - name: Redis 연결 확인 run: | From c38f3ad9c959cd4ee53301658c2f2456bf1af29c Mon Sep 17 00:00:00 2001 From: ehgur Date: Sat, 14 Jun 2025 01:05:50 +0900 Subject: [PATCH 40/66] =?UTF-8?q?refactor:=20test=20=EC=8B=A4=ED=96=89=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16e03c0..8babcb3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,6 +68,12 @@ jobs: - name: Test 실행(테스트 코드만 실행) run: ./gradlew --info test + env: + SPRING_DATASOURCE_URL: jdbc:mysql://127.0.0.1:3306/test + SPRING_DATASOURCE_USERNAME: tester + SPRING_DATASOURCE_PASSWORD: ${{ secrets.TEST_DB_PASSWORD }} + SPRING_REDIS_HOST: 127.0.0.1 + SPRING_REDIS_PORT: 6379 build: name: Run on Ubuntu From 642a8164b5a2e63eebb5923375c7442b2da70067 Mon Sep 17 00:00:00 2001 From: ehgur Date: Sat, 14 Jun 2025 01:34:35 +0900 Subject: [PATCH 41/66] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20yml=20=ED=8C=8C=EC=9D=BC=20=EB=B3=B5=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 3 +++ build.gradle | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8babcb3..e531b21 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,9 @@ jobs: sudo apt-get update && sudo apt-get install -y redis-tools redis-cli -h localhost ping + - name: Copy private test resources + run: ./gradlew copyTestYml --no-daemon + - name: 테스트 실행 run: ./gradlew test env: diff --git a/build.gradle b/build.gradle index b461c84..99ecbac 100644 --- a/build.gradle +++ b/build.gradle @@ -73,6 +73,12 @@ tasks.register('copyYml', Copy) { } } +tasks.register('copyTestYml', Copy) { + from 'security_setting' + include 'application-test.yml' + into 'src/test/resources' +} + tasks.register('copyLog', Copy) { copy { from 'security_setting' From 617d78a809a402ad8af508cc9318f5c0a207c71b Mon Sep 17 00:00:00 2001 From: ehgur Date: Sat, 14 Jun 2025 01:42:21 +0900 Subject: [PATCH 42/66] =?UTF-8?q?refactor:=20test=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- security_setting | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security_setting b/security_setting index c96f0b3..7cd2f2a 160000 --- a/security_setting +++ b/security_setting @@ -1 +1 @@ -Subproject commit c96f0b30f51ad929493e3061f9d401087801a5e3 +Subproject commit 7cd2f2ae4309942b58db3f00068b9a86f8529ba2 From f594c8ae696e4c1c1a71d2be6140af6043f5aff8 Mon Sep 17 00:00:00 2001 From: ehgur Date: Sat, 14 Jun 2025 02:07:02 +0900 Subject: [PATCH 43/66] =?UTF-8?q?refactor:=20test=20active=20profile=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20ci.yml=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ci 와 로컬 환경에서 설정 파일 차이를 두기 위해 @ActiveProfile() 어노테이션 삭제 - 앞으로는 로컬환경에서 테스트하려면 직접 test 환경으로 설정해야함 --- .github/workflows/ci.yml | 7 ++++--- build.gradle | 4 ++-- security_setting | 2 +- .../autoreview/AutoreviewApplicationTests.java | 2 -- .../CodePostBookmarkServiceIntegrationTest.java | 8 +++----- .../service/CodePostServiceIntegrationTest.java | 11 ++++------- .../CodePostCommentServiceIntegrationTest.java | 2 -- 7 files changed, 14 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e531b21..a71f6c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,17 +49,18 @@ jobs: sudo apt-get update && sudo apt-get install -y redis-tools redis-cli -h localhost ping - - name: Copy private test resources - run: ./gradlew copyTestYml --no-daemon + - name: Copy private ci resources + run: ./gradlew copyCiYml --no-daemon - name: 테스트 실행 run: ./gradlew test env: + SPRING_PROFILES_ACTIVE: ci DB_HOST: 127.0.0.1 DB_PORT: 3306 DB_NAME: test DB_USER: tester - DB_PASSWORD: ${{ secrets.TEST_DB_PASSWORD}} + DB_PASSWORD: ${{ secrets.TEST_DB_PASSWORD }} REDIS_HOST: 127.0.0.1 REDIS_PORT: 6379 diff --git a/build.gradle b/build.gradle index 99ecbac..99f4f31 100644 --- a/build.gradle +++ b/build.gradle @@ -73,9 +73,9 @@ tasks.register('copyYml', Copy) { } } -tasks.register('copyTestYml', Copy) { +tasks.register('copyCiYml', Copy) { from 'security_setting' - include 'application-test.yml' + include 'application-ci.yml' into 'src/test/resources' } diff --git a/security_setting b/security_setting index 7cd2f2a..0b19ace 160000 --- a/security_setting +++ b/security_setting @@ -1 +1 @@ -Subproject commit 7cd2f2ae4309942b58db3f00068b9a86f8529ba2 +Subproject commit 0b19acee966b552c7137b0fe5c3b135990086346 diff --git a/src/test/java/org/example/autoreview/AutoreviewApplicationTests.java b/src/test/java/org/example/autoreview/AutoreviewApplicationTests.java index 9464ac7..efe71bc 100644 --- a/src/test/java/org/example/autoreview/AutoreviewApplicationTests.java +++ b/src/test/java/org/example/autoreview/AutoreviewApplicationTests.java @@ -2,9 +2,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; -@ActiveProfiles("test") @SpringBootTest class AutoreviewApplicationTests { diff --git a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java index cb66f19..aa056a4 100644 --- a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java +++ b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java @@ -1,5 +1,8 @@ package org.example.autoreview.domain.bookmark.CodePostBookmark.service; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.springframework.data.domain.PageRequest.of; + import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.request.CodePostBookmarkSaveRequestDto; import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkListResponseDto; import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmarkRepository; @@ -14,12 +17,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.springframework.data.domain.PageRequest.of; -@ActiveProfiles("test") @SpringBootTest class CodePostBookmarkServiceIntegrationTest { diff --git a/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java b/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java index e346174..e1b2a3b 100644 --- a/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java +++ b/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java @@ -1,5 +1,9 @@ package org.example.autoreview.domain.codepost.service; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.time.LocalDate; import org.example.autoreview.domain.bookmark.CodePostBookmark.service.CodePostBookmarkCommand; import org.example.autoreview.domain.codepost.dto.request.CodePostSaveRequestDto; import org.example.autoreview.domain.codepost.dto.request.CodePostUpdateRequestDto; @@ -17,14 +21,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; - -import java.time.LocalDate; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -@ActiveProfiles("test") @SpringBootTest public class CodePostServiceIntegrationTest { diff --git a/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java b/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java index 827e5ec..f6c40aa 100644 --- a/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java +++ b/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java @@ -4,9 +4,7 @@ import org.example.autoreview.domain.member.service.MemberCommand; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; -@ActiveProfiles("test") @SpringBootTest public class CodePostCommentServiceIntegrationTest { From 1172630ca59cc47fe0940f5ad9af12507c0afa0e Mon Sep 17 00:00:00 2001 From: ehgur Date: Sat, 14 Jun 2025 14:41:17 +0900 Subject: [PATCH 44/66] =?UTF-8?q?refactor:=20yml=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=EB=90=90=EB=8A=94=EC=A7=80=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a71f6c1..d8499f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,6 +52,9 @@ jobs: - name: Copy private ci resources run: ./gradlew copyCiYml --no-daemon + - name: Show copied ci yml + run: cat src/test/resources/application-ci.yml + - name: 테스트 실행 run: ./gradlew test env: From f3b1da1518cada2a08f9bb4207fc2e918348deb7 Mon Sep 17 00:00:00 2001 From: ehgur Date: Sat, 14 Jun 2025 15:03:22 +0900 Subject: [PATCH 45/66] =?UTF-8?q?refactor:=20test=20=EC=8B=9C=20submodule?= =?UTF-8?q?=20=EC=97=90=20=EC=A0=91=EA=B7=BC=ED=95=98=EA=B8=B0=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=ED=86=A0=ED=81=B0=20=EC=82=AC=EC=9A=A9=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d8499f7..1d83ce4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,10 @@ jobs: steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 + submodules: 'recursive' + token: ${{ secrets.ACTION_TOKEN }} - name: MySQL 연결 확인 run: | From edbde7dffe85371ae4c39a794c8ac49204da3940 Mon Sep 17 00:00:00 2001 From: ArcticFoox Date: Sun, 15 Jun 2025 17:18:34 +0900 Subject: [PATCH 46/66] fix: add logback file in ci --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d83ce4..74e8c10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,6 +109,9 @@ jobs: - name: Copy private resources run: ./gradlew copyYml --no-daemon + - name: Copy private logback config + run: ./gradlew copyLog --no-daemon + - name: Create Firebase JSON file run: | echo '{ From 63de32960a97b00f64b6bcdc1d970caa63706f38 Mon Sep 17 00:00:00 2001 From: ehgur Date: Sun, 15 Jun 2025 17:50:15 +0900 Subject: [PATCH 47/66] =?UTF-8?q?refactor:=20test=20=EC=8B=A4=ED=96=89=20?= =?UTF-8?q?=EC=8B=9C=20fcm=20=EC=84=A4=EC=A0=95=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EC=9D=B4=20=EC=97=86=EC=96=B4=EC=84=9C=20?= =?UTF-8?q?=EB=B0=9C=EC=83=9D=ED=95=9C=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d83ce4..373f883 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,8 +56,21 @@ jobs: - name: Copy private ci resources run: ./gradlew copyCiYml --no-daemon - - name: Show copied ci yml - run: cat src/test/resources/application-ci.yml + - name: Create Firebase JSON file + run: | + echo '{ + "type": "${{ secrets.FIREBASE_TYPE }}", + "project_id": "${{ secrets.FIREBASE_PROJECT_ID }}", + "private_key_id": "${{ secrets.FIREBASE_PRIVATE_KEY_ID }}", + "private_key": "${{ secrets.FIREBASE_PRIVATE_KEY }}", + "client_email": "${{ secrets.FIREBASE_CLIENT_EMAIL }}", + "client_id": "${{ secrets.FIREBASE_CLIENT_ID }}", + "auth_uri": "${{ secrets.FIREBASE_AUTH_URI }}", + "token_uri": "${{ secrets.FIREBASE_TOKEN_URI }}", + "auth_provider_x509_cert_url": "${{ secrets.FIREBASE_AUTH_PROVIDER_CERT_URL }}", + "client_x509_cert_url": "${{ secrets.FIREBASE_CLIENT_CERT_URL }}", + "universe_domain": "${{ secrets.FIREBASE_UNIVERSE_DOMAIN }}" + }' > src/main/resources/ori-push-notification-firebase-adminsdk-k9qqe-d0581b0c52.json - name: 테스트 실행 run: ./gradlew test From 51c4a4097579fc1f4057fa198c5e211b91d279e6 Mon Sep 17 00:00:00 2001 From: ehgur Date: Sun, 15 Jun 2025 18:01:51 +0900 Subject: [PATCH 48/66] =?UTF-8?q?refactor:=20test=20=EC=8B=A4=ED=96=89=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A4=91=EB=B3=B5=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 373f883..f1535ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,15 +90,6 @@ jobs: - name: 어플리케이션 실행 테스트(테스트 코드 제외하고 실행) run: ./gradlew clean build --exclude-task test - - name: Test 실행(테스트 코드만 실행) - run: ./gradlew --info test - env: - SPRING_DATASOURCE_URL: jdbc:mysql://127.0.0.1:3306/test - SPRING_DATASOURCE_USERNAME: tester - SPRING_DATASOURCE_PASSWORD: ${{ secrets.TEST_DB_PASSWORD }} - SPRING_REDIS_HOST: 127.0.0.1 - SPRING_REDIS_PORT: 6379 - build: name: Run on Ubuntu runs-on: ubuntu-latest From 099fcfc2eb2c2cbf8e065412f45702778b407664 Mon Sep 17 00:00:00 2001 From: ArcticFoox Date: Sun, 15 Jun 2025 20:16:10 +0900 Subject: [PATCH 49/66] fix: change log file name for rolling policies --- security_setting | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security_setting b/security_setting index 0b19ace..ec689eb 160000 --- a/security_setting +++ b/security_setting @@ -1 +1 @@ -Subproject commit 0b19acee966b552c7137b0fe5c3b135990086346 +Subproject commit ec689eb7b576b4e3aad7877efd5d30d795c4d3cb From 2a34b4a1c71ab80796ef059c47abd7643d8a6d1e Mon Sep 17 00:00:00 2001 From: ArcticFoox Date: Sun, 15 Jun 2025 21:02:52 +0900 Subject: [PATCH 50/66] fix: change log file name for rolling policies &i and exclude commons-logging module --- build.gradle | 4 +++- security_setting | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 99f4f31..e6663f7 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,9 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-web' + implementation ('org.springframework.boot:spring-boot-starter-web') { + exclude module: 'commons-logging' + } implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-webflux' diff --git a/security_setting b/security_setting index ec689eb..0ce94ba 160000 --- a/security_setting +++ b/security_setting @@ -1 +1 @@ -Subproject commit ec689eb7b576b4e3aad7877efd5d30d795c4d3cb +Subproject commit 0ce94ba8d8f7c10e6ccb33d083939d4ebe43babf From 77273c9957ee5eca342bdfb7d6c7180eeba31462 Mon Sep 17 00:00:00 2001 From: ArcticFoox Date: Mon, 16 Jun 2025 21:28:06 +0900 Subject: [PATCH 51/66] feat: connect volume ec2 and spring docker container --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4a7386..8c8428b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -181,7 +181,7 @@ jobs: sudo docker stop ${{ secrets.DOCKER_CONTAINER_NAME }} sudo docker rm ${{ secrets.DOCKER_CONTAINER_NAME }} sudo docker rmi ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPOSITORY_NAME }}:dev - sudo docker run -d --name ${{ secrets.DOCKER_CONTAINER_NAME }} --network ${{ secrets.DOCKER_NETWORK_NAME }} -p 8080:8080 ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPOSITORY_NAME }}:dev + sudo docker run -d --name ${{ secrets.DOCKER_CONTAINER_NAME }} --volume /ori/log:/ori/log --network ${{ secrets.DOCKER_NETWORK_NAME }} -p 8080:8080 ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPOSITORY_NAME }}:dev - name: Deploy to Main Server if: github.ref == 'refs/heads/main' From b2e806e0479a8559b1ee241e071898ee3a7e8b6c Mon Sep 17 00:00:00 2001 From: ehgur Date: Tue, 17 Jun 2025 10:18:14 +0900 Subject: [PATCH 52/66] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=ED=99=98=EA=B2=BD=20=EC=84=A4=EC=A0=95=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CodePostBookmarkServiceIntegrationTest.java | 6 ++++-- .../codepost/service/CodePostServiceIntegrationTest.java | 2 ++ .../service/CodePostCommentServiceIntegrationTest.java | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java index aa056a4..45c8162 100644 --- a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java +++ b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java @@ -17,7 +17,9 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +@ActiveProfiles("${spring.profiles.active:test}") @SpringBootTest class CodePostBookmarkServiceIntegrationTest { @@ -65,7 +67,7 @@ void cleanUp() { } @Test - void saveOrUpdate_trySave() { + void save() { // when Long bookmarkId = codePostBookmarkService.saveOrUpdate(saveRequestDto, testMember.getEmail()); @@ -78,7 +80,7 @@ void saveOrUpdate_trySave() { } @Test - void saveOrUpdate_fallbackToUpdate() { + void update() { // when // bookmark 여부 true -> false 로 변경 Long bookmarkId = codePostBookmarkService.saveOrUpdate(saveRequestDto, testMember.getEmail()); diff --git a/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java b/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java index e1b2a3b..9ca531e 100644 --- a/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java +++ b/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java @@ -21,7 +21,9 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +@ActiveProfiles("${spring.profiles.active:test}") @SpringBootTest public class CodePostServiceIntegrationTest { diff --git a/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java b/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java index f6c40aa..d063102 100644 --- a/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java +++ b/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java @@ -4,7 +4,9 @@ import org.example.autoreview.domain.member.service.MemberCommand; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +@ActiveProfiles("${spring.profiles.active:test}") @SpringBootTest public class CodePostCommentServiceIntegrationTest { @@ -21,4 +23,6 @@ public class CodePostCommentServiceIntegrationTest { private CodePostCommentService codePostCommentService; + + } From 1f921be3ce761b5c661ffd7e2b91026db80e65c8 Mon Sep 17 00:00:00 2001 From: ehgur Date: Tue, 17 Jun 2025 16:46:02 +0900 Subject: [PATCH 53/66] =?UTF-8?q?refactor:=20=EB=B6=81=EB=A7=88=ED=81=AC?= =?UTF-8?q?=20=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=EC=9E=90=20=EC=A0=95=EB=B3=B4=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CodePostBookmark/entity/CodePostBookmarkRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java index 543c555..5fe5581 100644 --- a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java @@ -15,7 +15,7 @@ public interface CodePostBookmarkRepository extends JpaRepository findById(@Param("memberId") Long memberId, @Param("codePostId") Long codePostId); @Query("SELECT new org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkResponseDto(b,c,m) from CodePostBookmark b " - + "JOIN CodePost c ON b.codePostId = c.id JOIN Member m ON m.id = :memberId WHERE b.isDeleted = FALSE") + + "JOIN CodePost c ON b.codePostId = c.id JOIN Member m ON m.id = c.writerId WHERE b.isDeleted = FALSE") Page findAllByMemberId(@Param("memberId") Long memberId, Pageable pageable); @Modifying From 88c58f32d29132317feb775517d0117b015054dc Mon Sep 17 00:00:00 2001 From: ehgur Date: Tue, 17 Jun 2025 10:14:10 +0900 Subject: [PATCH 54/66] =?UTF-8?q?refactor:=20save=20or=20update=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20Upsert=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MySQL 에서 지원하는 Upsert 기능 사용 - Upsert 는 PK를 반환하지 않기 때문에 조회 쿼리 추가 사용 - test 코드 환경 설정 방식 변경 --- .../entity/CodePostBookmarkRepository.java | 10 +++++ .../service/CodePostBookmarkCommand.java | 17 ++----- .../service/CodePostBookmarkService.java | 15 +++---- .../CodePostBookmarkServiceUnitTest.java | 44 ++++++++++++++----- 4 files changed, 54 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java index 5fe5581..a89deb4 100644 --- a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java @@ -11,6 +11,16 @@ public interface CodePostBookmarkRepository extends JpaRepository { + @Modifying + @Query( + value = " INSERT INTO code_post_bookmark (member_id, code_post_id, is_deleted, create_date, update_date) " + + " VALUES (:memberId, :codePostId, false, NOW(), NOW()) " + + " ON DUPLICATE KEY UPDATE is_deleted = NOT is_deleted, update_date = NOW() ", + nativeQuery = true + ) + void upsert(@Param("memberId") Long memberId, @Param("codePostId") Long codePostId); + + @Query("SELECT b FROM CodePostBookmark b WHERE b.member.id = :memberId AND b.codePostId = :codePostId") Optional findById(@Param("memberId") Long memberId, @Param("codePostId") Long codePostId); diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkCommand.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkCommand.java index 6f3852c..90e71ae 100644 --- a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkCommand.java +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkCommand.java @@ -1,20 +1,17 @@ package org.example.autoreview.domain.bookmark.CodePostBookmark.service; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.request.CodePostBookmarkSaveRequestDto; import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkResponseDto; import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmark; import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmarkRepository; import org.example.autoreview.domain.member.entity.Member; -import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; -import org.example.autoreview.global.exception.errorcode.ErrorCode; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -import java.util.Optional; - @RequiredArgsConstructor @Component public class CodePostBookmarkCommand { @@ -22,15 +19,9 @@ public class CodePostBookmarkCommand { private final CodePostBookmarkRepository codePostBookmarkRepository; @Transactional - public Long trySave(CodePostBookmarkSaveRequestDto requestDto, Member member) { - return codePostBookmarkRepository.save(requestDto.toEntity(member)).getId(); - } - - @Transactional - public Long fallbackToUpdate(Long codePostId, Long memberId) { - return codePostBookmarkRepository.findById(memberId, codePostId) - .map(CodePostBookmark::update) - .orElseThrow(() -> new CustomRuntimeException(ErrorCode.NOT_FOUND_BOOKMARK)); + public Optional saveOrUpdate(CodePostBookmarkSaveRequestDto requestDto, Member member) { + codePostBookmarkRepository.upsert(member.getId(), requestDto.codePostId()); + return codePostBookmarkRepository.findById(member.getId(), requestDto.codePostId()); } @Transactional diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java index 64e59fb..f86c644 100644 --- a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java @@ -7,7 +7,8 @@ import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkResponseDto; import org.example.autoreview.domain.member.entity.Member; import org.example.autoreview.domain.member.service.MemberCommand; -import org.springframework.dao.DataIntegrityViolationException; +import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; +import org.example.autoreview.global.exception.errorcode.ErrorCode; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -22,17 +23,13 @@ public class CodePostBookmarkService { /** * 북마크가 없을 시 생성, 있으면 상태 변경하는 메서드이다. - * 1. DB에 code_post_id, member_id로 UNIQUE 제약 조건 설정 - * 2. UNIQUE 제약 조건 위반 시 catch를 통해 update() 로직 실행 + * MySQL 에서 지원하는 Upsert 기능을 통해 유니크 키가 중복될 경우 update 실행 */ public Long saveOrUpdate(CodePostBookmarkSaveRequestDto requestDto, String email) { Member member = memberCommand.findByEmail(email); - - try { - return codePostBookmarkCommand.trySave(requestDto, member); - } catch (DataIntegrityViolationException e) { - return codePostBookmarkCommand.fallbackToUpdate(requestDto.codePostId(), member.getId()); - } + return codePostBookmarkCommand.saveOrUpdate(requestDto, member).orElseThrow( + () -> new CustomRuntimeException(ErrorCode.NOT_FOUND_BOOKMARK) + ).getId(); } /** diff --git a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceUnitTest.java b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceUnitTest.java index 99987bc..61a4b96 100644 --- a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceUnitTest.java +++ b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceUnitTest.java @@ -5,6 +5,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.Optional; import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.request.CodePostBookmarkSaveRequestDto; import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkListResponseDto; import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkResponseDto; @@ -19,10 +20,10 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; +import org.springframework.test.util.ReflectionTestUtils; @ExtendWith(MockitoExtension.class) class CodePostBookmarkServiceUnitTest { @@ -38,19 +39,36 @@ class CodePostBookmarkServiceUnitTest { private Member mockMember; + private CodePostBookmark mockCodePostBookmark; + private String email; private Long codePostId; + private Long memberId; + + private Long bookmarkId; + @BeforeEach void setUp() { + memberId = 1L; codePostId = 123L; + bookmarkId = 1L; email = "test@example.com"; mockMember = Member.builder() .nickname("tester") .role(Role.USER) .email(email) .build(); + + mockCodePostBookmark = CodePostBookmark.builder() + .member(mockMember) + .codePostId(codePostId) + .isDeleted(false) + .build(); + + ReflectionTestUtils.setField(mockMember,"id", memberId); + ReflectionTestUtils.setField(mockCodePostBookmark,"id", bookmarkId); } @Test @@ -58,32 +76,38 @@ void setUp() { // given CodePostBookmarkSaveRequestDto requestDto = new CodePostBookmarkSaveRequestDto(codePostId); when(memberCommand.findByEmail(email)).thenReturn(mockMember); - when(codePostBookmarkCommand.trySave(requestDto, mockMember)).thenReturn(1L); + when(codePostBookmarkCommand.saveOrUpdate(requestDto, mockMember)) + .thenReturn(Optional.ofNullable(mockCodePostBookmark)); // when Long result = codePostBookmarkService.saveOrUpdate(requestDto, email); // then - assertThat(result).isEqualTo(1L); - verify(codePostBookmarkCommand).trySave(requestDto, mockMember); + assertThat(result).isEqualTo(mockCodePostBookmark.getId()); + verify(codePostBookmarkCommand).saveOrUpdate(requestDto, mockMember); } @Test void saveOrUpdate_UNIQUE_제약조건_위반시_업데이트로_전환한다() { // given + CodePostBookmark updateBookmark = CodePostBookmark.builder() + .member(mockMember) + .codePostId(codePostId) + .isDeleted(!mockCodePostBookmark.isDeleted()) + .build(); + ReflectionTestUtils.setField(updateBookmark,"id",2L); + CodePostBookmarkSaveRequestDto requestDto = new CodePostBookmarkSaveRequestDto(codePostId); when(memberCommand.findByEmail(email)).thenReturn(mockMember); - when(codePostBookmarkCommand.trySave(requestDto, mockMember)).thenThrow(DataIntegrityViolationException.class); - - // mockMember 의 id값이 null이기 때문에 memberId에 null 삽입한 채로 진행 - when(codePostBookmarkCommand.fallbackToUpdate(codePostId, null)).thenReturn(2L); + when(codePostBookmarkCommand.saveOrUpdate(requestDto, mockMember)).thenReturn( + Optional.of(updateBookmark)); // when Long result = codePostBookmarkService.saveOrUpdate(requestDto, email); // then assertThat(result).isEqualTo(2L); - verify(codePostBookmarkCommand).fallbackToUpdate(codePostId, null); + verify(codePostBookmarkCommand).saveOrUpdate(requestDto, mockMember); } @Test @@ -115,7 +139,7 @@ void setUp() { when(memberCommand.findByEmail(email)).thenReturn(mockMember); // mockMember 의 id값이 null이기 때문에 memberId에 null 삽입한 채로 진행 - when(codePostBookmarkCommand.findAllByMemberId(null, PageRequest.of(0, 10))).thenReturn(page); + when(codePostBookmarkCommand.findAllByMemberId(memberId, PageRequest.of(0, 10))).thenReturn(page); // when CodePostBookmarkListResponseDto result = codePostBookmarkService.findAllByMemberId(email, PageRequest.of(0, 10)); From d08eb324cbbb5ce0b7f6e4bbc92b2332392c7316 Mon Sep 17 00:00:00 2001 From: ehgur Date: Tue, 17 Jun 2025 10:16:50 +0900 Subject: [PATCH 55/66] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EB=B3=80=EA=B2=BD=20=EC=82=AC=ED=95=AD?= =?UTF-8?q?=20=EB=8B=A4=EC=8B=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CodePostBookmarkServiceIntegrationTest.java | 2 -- .../domain/codepost/service/CodePostServiceIntegrationTest.java | 2 -- .../codepost/service/CodePostCommentServiceIntegrationTest.java | 2 -- 3 files changed, 6 deletions(-) diff --git a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java index 45c8162..3570234 100644 --- a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java +++ b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java @@ -17,9 +17,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; -@ActiveProfiles("${spring.profiles.active:test}") @SpringBootTest class CodePostBookmarkServiceIntegrationTest { diff --git a/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java b/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java index 9ca531e..e1b2a3b 100644 --- a/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java +++ b/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java @@ -21,9 +21,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; -@ActiveProfiles("${spring.profiles.active:test}") @SpringBootTest public class CodePostServiceIntegrationTest { diff --git a/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java b/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java index d063102..2b85752 100644 --- a/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java +++ b/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java @@ -4,9 +4,7 @@ import org.example.autoreview.domain.member.service.MemberCommand; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; -@ActiveProfiles("${spring.profiles.active:test}") @SpringBootTest public class CodePostCommentServiceIntegrationTest { From 6c84353dab5752e26880af1c4eb808a1cb9ddd3b Mon Sep 17 00:00:00 2001 From: ehgur Date: Tue, 17 Jun 2025 10:31:37 +0900 Subject: [PATCH 56/66] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=ED=99=98=EA=B2=BD=20=EC=84=A4=EC=A0=95=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CodePostBookmarkServiceIntegrationTest.java | 2 ++ .../domain/codepost/service/CodePostServiceIntegrationTest.java | 2 ++ .../codepost/service/CodePostCommentServiceIntegrationTest.java | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java index 3570234..45c8162 100644 --- a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java +++ b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java @@ -17,7 +17,9 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +@ActiveProfiles("${spring.profiles.active:test}") @SpringBootTest class CodePostBookmarkServiceIntegrationTest { diff --git a/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java b/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java index e1b2a3b..9ca531e 100644 --- a/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java +++ b/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceIntegrationTest.java @@ -21,7 +21,9 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +@ActiveProfiles("${spring.profiles.active:test}") @SpringBootTest public class CodePostServiceIntegrationTest { diff --git a/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java b/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java index 2b85752..d063102 100644 --- a/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java +++ b/src/test/java/org/example/autoreview/domain/comment/codepost/service/CodePostCommentServiceIntegrationTest.java @@ -4,7 +4,9 @@ import org.example.autoreview.domain.member.service.MemberCommand; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +@ActiveProfiles("${spring.profiles.active:test}") @SpringBootTest public class CodePostCommentServiceIntegrationTest { From d324873d87a5bb69a81a5cf442df63a136e2ea21 Mon Sep 17 00:00:00 2001 From: ehgur Date: Wed, 18 Jun 2025 11:44:33 +0900 Subject: [PATCH 57/66] =?UTF-8?q?refactor:=20=EB=B6=81=EB=A7=88=ED=81=AC?= =?UTF-8?q?=20=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자 상관없이 모두 조회하고 있는걸 수정 - 회원이 북마크한 정보만 볼 수 있게 수정 --- .../CodePostBookmark/entity/CodePostBookmarkRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java index a89deb4..298f5a3 100644 --- a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java @@ -25,7 +25,7 @@ public interface CodePostBookmarkRepository extends JpaRepository findById(@Param("memberId") Long memberId, @Param("codePostId") Long codePostId); @Query("SELECT new org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkResponseDto(b,c,m) from CodePostBookmark b " - + "JOIN CodePost c ON b.codePostId = c.id JOIN Member m ON m.id = c.writerId WHERE b.isDeleted = FALSE") + + "JOIN CodePost c ON b.codePostId = c.id JOIN Member m ON m.id = c.writerId WHERE b.isDeleted = FALSE AND b.member.id = :memberId") Page findAllByMemberId(@Param("memberId") Long memberId, Pageable pageable); @Modifying From 3e2c66912ef42c18adf53e44d56f71d29ec5e397 Mon Sep 17 00:00:00 2001 From: ehgur Date: Wed, 18 Jun 2025 23:09:27 +0900 Subject: [PATCH 58/66] =?UTF-8?q?refactor:=20=EB=B6=81=EB=A7=88=ED=81=AC?= =?UTF-8?q?=20=EC=8A=A4=ED=82=A4=EB=A7=88=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - member 외래키 삭제 및 email 컬럼 추가 --- .../CodePostBookmarkController.java | 2 +- .../CodePostBookmarkSaveRequestDto.java | 9 ------ .../entity/CodePostBookmark.java | 22 +++++++++------ .../entity/CodePostBookmarkRepository.java | 14 +++++----- .../service/CodePostBookmarkCommand.java | 15 +++++----- .../service/CodePostBookmarkService.java | 11 +++----- .../codepost/service/CodePostService.java | 7 ++--- .../domain/member/entity/Member.java | 4 --- .../V9.1__replace_member_id_with_email.sql | 28 +++++++++++++++++++ 9 files changed, 63 insertions(+), 49 deletions(-) create mode 100644 src/main/resources/db/migration/V9.1__replace_member_id_with_email.sql diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/controller/CodePostBookmarkController.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/controller/CodePostBookmarkController.java index 63a3eb6..71d5b81 100644 --- a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/controller/CodePostBookmarkController.java +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/controller/CodePostBookmarkController.java @@ -37,7 +37,7 @@ public ResponseEntity saveOrUpdate(@RequestBody CodePostBookmarkSaveReques @GetMapping("/list") public ResponseEntity findAll(@AuthenticationPrincipal UserDetails userDetails, @PageableDefault(page = 0, size = 10) Pageable pageable) { - return ResponseEntity.ok().body(codePostBookmarkService.findAllByMemberId(userDetails.getUsername(), pageable)); + return ResponseEntity.ok().body(codePostBookmarkService.findAllByEmail(userDetails.getUsername(), pageable)); } @Operation(summary = "북마크 삭제") diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/request/CodePostBookmarkSaveRequestDto.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/request/CodePostBookmarkSaveRequestDto.java index 379fb6e..3d0042c 100644 --- a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/request/CodePostBookmarkSaveRequestDto.java +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/dto/request/CodePostBookmarkSaveRequestDto.java @@ -1,19 +1,10 @@ package org.example.autoreview.domain.bookmark.CodePostBookmark.dto.request; import io.swagger.v3.oas.annotations.media.Schema; -import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmark; -import org.example.autoreview.domain.member.entity.Member; public record CodePostBookmarkSaveRequestDto( @Schema(description = "코드 포스트 아이디", example = "1") Long codePostId ) { - public CodePostBookmark toEntity(Member member){ - return CodePostBookmark.builder() - .codePostId(codePostId) - .member(member) - .isDeleted(false) - .build(); - } } diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmark.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmark.java index 9a76396..a706e3d 100644 --- a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmark.java +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmark.java @@ -1,17 +1,22 @@ package org.example.autoreview.domain.bookmark.CodePostBookmark.entity; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.example.autoreview.domain.member.entity.Member; import org.example.autoreview.global.common.basetime.BaseEntity; import org.hibernate.annotations.ColumnDefault; @Getter @NoArgsConstructor -@Table(uniqueConstraints = {@UniqueConstraint(name = "uq_codepost_member", - columnNames = {"code_post_id", "member_id"})} +@Table(uniqueConstraints = {@UniqueConstraint(name = "uq_email_codepost", + columnNames = {"email", "code_post_id"})} ) @Entity public class CodePostBookmark extends BaseEntity { @@ -19,9 +24,8 @@ public class CodePostBookmark extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn - private Member member; + @Column(nullable = false) + private String email; @Column(nullable = false) private Long codePostId; @@ -31,9 +35,9 @@ public class CodePostBookmark extends BaseEntity { private boolean isDeleted; @Builder - public CodePostBookmark(Long codePostId, Member member, boolean isDeleted) { + public CodePostBookmark(Long codePostId, String email, boolean isDeleted) { this.codePostId = codePostId; - this.member = member; + this.email = email; this.isDeleted = isDeleted; } diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java index 298f5a3..8dbd273 100644 --- a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/entity/CodePostBookmarkRepository.java @@ -13,20 +13,20 @@ public interface CodePostBookmarkRepository extends JpaRepository findById(@Param("memberId") Long memberId, @Param("codePostId") Long codePostId); + @Query("SELECT b FROM CodePostBookmark b WHERE b.email = :email AND b.codePostId = :codePostId") + Optional findById(@Param("email") String email, @Param("codePostId") Long codePostId); @Query("SELECT new org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkResponseDto(b,c,m) from CodePostBookmark b " - + "JOIN CodePost c ON b.codePostId = c.id JOIN Member m ON m.id = c.writerId WHERE b.isDeleted = FALSE AND b.member.id = :memberId") - Page findAllByMemberId(@Param("memberId") Long memberId, Pageable pageable); + + "JOIN CodePost c ON b.codePostId = c.id JOIN Member m ON m.id = c.writerId WHERE b.isDeleted = FALSE AND b.email = :email") + Page findAllByEmail(@Param("email") String email, Pageable pageable); @Modifying @Query("DELETE FROM CodePostBookmark b WHERE b.isDeleted = TRUE") diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkCommand.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkCommand.java index 90e71ae..986147a 100644 --- a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkCommand.java +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkCommand.java @@ -6,7 +6,6 @@ import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkResponseDto; import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmark; import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmarkRepository; -import org.example.autoreview.domain.member.entity.Member; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Component; @@ -19,9 +18,9 @@ public class CodePostBookmarkCommand { private final CodePostBookmarkRepository codePostBookmarkRepository; @Transactional - public Optional saveOrUpdate(CodePostBookmarkSaveRequestDto requestDto, Member member) { - codePostBookmarkRepository.upsert(member.getId(), requestDto.codePostId()); - return codePostBookmarkRepository.findById(member.getId(), requestDto.codePostId()); + public Optional saveOrUpdate(CodePostBookmarkSaveRequestDto requestDto, String email) { + codePostBookmarkRepository.upsert(email, requestDto.codePostId()); + return codePostBookmarkRepository.findById(email, requestDto.codePostId()); } @Transactional @@ -30,12 +29,12 @@ public void deleteExpiredSoftDeletedBookmarks() { } @Transactional(readOnly = true) - public Optional findByCodePostBookmark(Long memberId, Long codePostId) { - return codePostBookmarkRepository.findById(memberId,codePostId); + public Optional findByCodePostBookmark(String email, Long codePostId) { + return codePostBookmarkRepository.findById(email,codePostId); } @Transactional(readOnly = true) - public Page findAllByMemberId(Long memberId, Pageable pageable) { - return codePostBookmarkRepository.findAllByMemberId(memberId, pageable); + public Page findAllByEmail(String email, Pageable pageable) { + return codePostBookmarkRepository.findAllByEmail(email, pageable); } } diff --git a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java index f86c644..b74cc18 100644 --- a/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java +++ b/src/main/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkService.java @@ -5,7 +5,6 @@ import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.request.CodePostBookmarkSaveRequestDto; import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkListResponseDto; import org.example.autoreview.domain.bookmark.CodePostBookmark.dto.response.CodePostBookmarkResponseDto; -import org.example.autoreview.domain.member.entity.Member; import org.example.autoreview.domain.member.service.MemberCommand; import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; import org.example.autoreview.global.exception.errorcode.ErrorCode; @@ -26,18 +25,16 @@ public class CodePostBookmarkService { * MySQL 에서 지원하는 Upsert 기능을 통해 유니크 키가 중복될 경우 update 실행 */ public Long saveOrUpdate(CodePostBookmarkSaveRequestDto requestDto, String email) { - Member member = memberCommand.findByEmail(email); - return codePostBookmarkCommand.saveOrUpdate(requestDto, member).orElseThrow( + return codePostBookmarkCommand.saveOrUpdate(requestDto, email).orElseThrow( () -> new CustomRuntimeException(ErrorCode.NOT_FOUND_BOOKMARK) ).getId(); } /** - * 회원 아이디를 통해 북마크한 포스트를 조회하는 메서드이다. + * 회원 이메일을 통해 북마크한 포스트를 조회하는 메서드이다. */ - public CodePostBookmarkListResponseDto findAllByMemberId(String email, Pageable pageable) { - Long memberId = memberCommand.findByEmail(email).getId(); - Page pageDto = codePostBookmarkCommand.findAllByMemberId(memberId,pageable); + public CodePostBookmarkListResponseDto findAllByEmail(String email, Pageable pageable) { + Page pageDto = codePostBookmarkCommand.findAllByEmail(email,pageable); return new CodePostBookmarkListResponseDto(pageDto.getContent(),pageDto.getTotalPages()); } diff --git a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java index d68a623..f7044f2 100644 --- a/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java +++ b/src/main/java/org/example/autoreview/domain/codepost/service/CodePostService.java @@ -1,5 +1,7 @@ package org.example.autoreview.domain.codepost.service; +import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmark; @@ -27,9 +29,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; -import java.util.Optional; - @Slf4j @RequiredArgsConstructor @Service @@ -116,7 +115,7 @@ public CodePostResponseDto findById(Long id, String email) { Member member = memberCommand.findByEmail(email); CodePost codePost = codePostCommand.findByIdIsPublic(id, member.getId()); Member writer = memberCommand.findById(codePost.getWriterId()); - Optional codePostBookmark = codePostBookmarkCommand.findByCodePostBookmark(member.getId(), codePost.getId()); + Optional codePostBookmark = codePostBookmarkCommand.findByCodePostBookmark(email, codePost.getId()); boolean isBookmarked = false; if (codePostBookmark.isPresent()) { diff --git a/src/main/java/org/example/autoreview/domain/member/entity/Member.java b/src/main/java/org/example/autoreview/domain/member/entity/Member.java index 4ad4212..72a2edc 100644 --- a/src/main/java/org/example/autoreview/domain/member/entity/Member.java +++ b/src/main/java/org/example/autoreview/domain/member/entity/Member.java @@ -13,7 +13,6 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmark; import org.example.autoreview.domain.fcm.entity.FcmToken; import org.example.autoreview.domain.notification.entity.Notification; import org.example.autoreview.global.common.basetime.BaseEntity; @@ -30,9 +29,6 @@ public class Member extends BaseEntity { @Column(nullable = false, unique = true) private String email; - @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) - private List codePostBookmarks = new ArrayList<>(); - @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) private List fcmTokens = new ArrayList<>(); diff --git a/src/main/resources/db/migration/V9.1__replace_member_id_with_email.sql b/src/main/resources/db/migration/V9.1__replace_member_id_with_email.sql new file mode 100644 index 0000000..aaca49f --- /dev/null +++ b/src/main/resources/db/migration/V9.1__replace_member_id_with_email.sql @@ -0,0 +1,28 @@ +-- 1. email 컬럼 추가 +ALTER TABLE code_post_bookmark + ADD COLUMN email VARCHAR(255); + +-- 2. member_id → member_email 데이터 복사 +UPDATE code_post_bookmark cb + JOIN member m ON cb.member_id = m.id + SET cb.email = m.email; + +-- 3. member_email NOT NULL 설정 +ALTER TABLE code_post_bookmark + MODIFY COLUMN email VARCHAR(255) NOT NULL; + +-- 4. 기존 Unique 제약조건 삭제 +ALTER TABLE code_post_bookmark + DROP INDEX uq_codepost_member; + +-- 5. 새로운 Unique 제약조건 추가 +ALTER TABLE code_post_bookmark + ADD CONSTRAINT uq_email_codepost UNIQUE (email, code_post_id); + +-- 6. 외래키 제약 조건 제거 (필요 시) +ALTER TABLE code_post_bookmark + DROP FOREIGN KEY FKjy49hk8gdyg92iir31mofw9fh; -- 실제 제약조건 이름은 확인 필요 + +-- 7. member_id 컬럼 제거 +ALTER TABLE code_post_bookmark + DROP COLUMN member_id; From 7774b49b07fcc7bd5e0ee3ad553345598351a5ba Mon Sep 17 00:00:00 2001 From: ehgur Date: Thu, 19 Jun 2025 08:44:32 +0900 Subject: [PATCH 59/66] =?UTF-8?q?refactor:=20=EB=B6=81=EB=A7=88=ED=81=AC?= =?UTF-8?q?=20=EC=8A=A4=ED=82=A4=EB=A7=88=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AutoreviewApplicationTests.java | 2 + ...odePostBookmarkServiceIntegrationTest.java | 4 +- .../CodePostBookmarkServiceUnitTest.java | 41 +++++++---------- .../service/CodePostServiceUnitTest.java | 44 +++++++------------ 4 files changed, 36 insertions(+), 55 deletions(-) diff --git a/src/test/java/org/example/autoreview/AutoreviewApplicationTests.java b/src/test/java/org/example/autoreview/AutoreviewApplicationTests.java index efe71bc..fbf4f90 100644 --- a/src/test/java/org/example/autoreview/AutoreviewApplicationTests.java +++ b/src/test/java/org/example/autoreview/AutoreviewApplicationTests.java @@ -2,7 +2,9 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +@ActiveProfiles("${spring.profiles.active:test}") @SpringBootTest class AutoreviewApplicationTests { diff --git a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java index 45c8162..35ca73a 100644 --- a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java +++ b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceIntegrationTest.java @@ -72,7 +72,7 @@ void save() { Long bookmarkId = codePostBookmarkService.saveOrUpdate(saveRequestDto, testMember.getEmail()); // then - CodePostBookmarkListResponseDto bookmarks = codePostBookmarkService.findAllByMemberId(testMember.getEmail(), of(0, 10)); + CodePostBookmarkListResponseDto bookmarks = codePostBookmarkService.findAllByEmail(testMember.getEmail(), of(0, 10)); assertThat(bookmarkId).isNotNull(); assertThat(bookmarks.dtoList().size()).isEqualTo(1); @@ -102,7 +102,7 @@ void saveOrUpdate_then_findAllByMemberId() { Long bookmarkId = codePostBookmarkService.saveOrUpdate(requestDto, testMember.getEmail()); // then - CodePostBookmarkListResponseDto bookmarks = codePostBookmarkService.findAllByMemberId(testMember.getEmail(), of(0, 10)); + CodePostBookmarkListResponseDto bookmarks = codePostBookmarkService.findAllByEmail(testMember.getEmail(), of(0, 10)); assertThat(bookmarks.dtoList().size()).isEqualTo(1); assertThat(bookmarks.dtoList().get(0).getCodePostId()).isEqualTo(testCodePost.getId()); diff --git a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceUnitTest.java b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceUnitTest.java index 61a4b96..8815232 100644 --- a/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceUnitTest.java +++ b/src/test/java/org/example/autoreview/domain/bookmark/CodePostBookmark/service/CodePostBookmarkServiceUnitTest.java @@ -13,7 +13,6 @@ import org.example.autoreview.domain.codepost.entity.CodePost; import org.example.autoreview.domain.member.entity.Member; import org.example.autoreview.domain.member.entity.Role; -import org.example.autoreview.domain.member.service.MemberCommand; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -31,9 +30,6 @@ class CodePostBookmarkServiceUnitTest { @Mock private CodePostBookmarkCommand codePostBookmarkCommand; - @Mock - private MemberCommand memberCommand; - @InjectMocks private CodePostBookmarkService codePostBookmarkService; @@ -45,16 +41,13 @@ class CodePostBookmarkServiceUnitTest { private Long codePostId; - private Long memberId; - - private Long bookmarkId; - @BeforeEach void setUp() { - memberId = 1L; + Long memberId = 1L; + Long bookmarkId = 1L; codePostId = 123L; - bookmarkId = 1L; email = "test@example.com"; + mockMember = Member.builder() .nickname("tester") .role(Role.USER) @@ -62,7 +55,7 @@ void setUp() { .build(); mockCodePostBookmark = CodePostBookmark.builder() - .member(mockMember) + .email(mockMember.getEmail()) .codePostId(codePostId) .isDeleted(false) .build(); @@ -75,8 +68,7 @@ void setUp() { void saveOrUpdate_성공적으로_북마크를_저장한다() { // given CodePostBookmarkSaveRequestDto requestDto = new CodePostBookmarkSaveRequestDto(codePostId); - when(memberCommand.findByEmail(email)).thenReturn(mockMember); - when(codePostBookmarkCommand.saveOrUpdate(requestDto, mockMember)) + when(codePostBookmarkCommand.saveOrUpdate(requestDto, mockMember.getEmail())) .thenReturn(Optional.ofNullable(mockCodePostBookmark)); // when @@ -84,22 +76,22 @@ void setUp() { // then assertThat(result).isEqualTo(mockCodePostBookmark.getId()); - verify(codePostBookmarkCommand).saveOrUpdate(requestDto, mockMember); + verify(codePostBookmarkCommand).saveOrUpdate(requestDto, mockMember.getEmail()); } @Test void saveOrUpdate_UNIQUE_제약조건_위반시_업데이트로_전환한다() { // given CodePostBookmark updateBookmark = CodePostBookmark.builder() - .member(mockMember) + .email(mockMember.getEmail()) .codePostId(codePostId) .isDeleted(!mockCodePostBookmark.isDeleted()) .build(); ReflectionTestUtils.setField(updateBookmark,"id",2L); CodePostBookmarkSaveRequestDto requestDto = new CodePostBookmarkSaveRequestDto(codePostId); - when(memberCommand.findByEmail(email)).thenReturn(mockMember); - when(codePostBookmarkCommand.saveOrUpdate(requestDto, mockMember)).thenReturn( + + when(codePostBookmarkCommand.saveOrUpdate(requestDto, mockMember.getEmail())).thenReturn( Optional.of(updateBookmark)); // when @@ -107,21 +99,21 @@ void setUp() { // then assertThat(result).isEqualTo(2L); - verify(codePostBookmarkCommand).saveOrUpdate(requestDto, mockMember); + verify(codePostBookmarkCommand).saveOrUpdate(requestDto, mockMember.getEmail()); } @Test - void findAllByMemberId_정상조회() { + void findAllByEmail_정상조회() { // given CodePostBookmark bookmark1 = CodePostBookmark.builder() .codePostId(1L) - .member(mockMember) + .email(mockMember.getEmail()) .isDeleted(false) .build(); CodePostBookmark bookmark2 = CodePostBookmark.builder() .codePostId(2L) - .member(mockMember) + .email(mockMember.getEmail()) .isDeleted(false) .build(); @@ -136,13 +128,10 @@ void setUp() { var dtoList = of(dto1, dto2); Page page = new PageImpl<>(dtoList, PageRequest.of(0, 10), 2); - when(memberCommand.findByEmail(email)).thenReturn(mockMember); - - // mockMember 의 id값이 null이기 때문에 memberId에 null 삽입한 채로 진행 - when(codePostBookmarkCommand.findAllByMemberId(memberId, PageRequest.of(0, 10))).thenReturn(page); + when(codePostBookmarkCommand.findAllByEmail(email, PageRequest.of(0, 10))).thenReturn(page); // when - CodePostBookmarkListResponseDto result = codePostBookmarkService.findAllByMemberId(email, PageRequest.of(0, 10)); + CodePostBookmarkListResponseDto result = codePostBookmarkService.findAllByEmail(email, PageRequest.of(0, 10)); // then assertThat(result.dtoList().size()).isEqualTo(2); diff --git a/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceUnitTest.java b/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceUnitTest.java index 7688dfb..643d692 100644 --- a/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceUnitTest.java +++ b/src/test/java/org/example/autoreview/domain/codepost/service/CodePostServiceUnitTest.java @@ -1,5 +1,15 @@ package org.example.autoreview.domain.codepost.service; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.time.LocalDate; +import java.util.Optional; import org.example.autoreview.domain.bookmark.CodePostBookmark.entity.CodePostBookmark; import org.example.autoreview.domain.bookmark.CodePostBookmark.service.CodePostBookmarkCommand; import org.example.autoreview.domain.codepost.dto.request.CodePostSaveRequestDto; @@ -9,7 +19,6 @@ import org.example.autoreview.domain.member.entity.Member; import org.example.autoreview.domain.member.service.MemberCommand; import org.example.autoreview.domain.notification.entity.Notification; -import org.example.autoreview.domain.notification.enums.NotificationStatus; import org.example.autoreview.domain.notification.service.NotificationCommand; import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; import org.junit.jupiter.api.BeforeEach; @@ -20,13 +29,6 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; -import java.time.LocalDate; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - @ExtendWith(MockitoExtension.class) class CodePostServiceUnitTest { @@ -37,27 +39,24 @@ class CodePostServiceUnitTest { private MemberCommand memberCommand; @Mock - CodePostBookmarkCommand codePostBookmarkCommand; + private NotificationCommand notificationCommand; @Mock - private NotificationCommand notificationCommand; + private CodePostBookmarkCommand codePostBookmarkCommand; @InjectMocks private CodePostService codePostService; private Long codePostId; private Long memberId; - private Long notificationId; private String email; private Member mockMember; private CodePost mockCodePost; - private Notification mockNotification; @BeforeEach void setUp() { codePostId = 123L; memberId = 1L; - notificationId = 2L; email = "test@example.com"; mockMember = Member.builder() .email(email) @@ -70,18 +69,8 @@ void setUp() { .isPublic(false) .build(); - mockNotification = Notification.builder() - .title("test") - .content("test") - .status(NotificationStatus.PENDING) - .executeTime(null) - .member(mockMember) - .codePostId(codePostId) - .build(); - ReflectionTestUtils.setField(mockMember, "id", memberId); ReflectionTestUtils.setField(mockCodePost, "id", codePostId); - ReflectionTestUtils.setField(mockNotification, "id", notificationId); } @Test @@ -150,16 +139,16 @@ void save_post_review_day_is_not_null() { .thenThrow(CustomRuntimeException.class); //when + then - assertThrows(CustomRuntimeException.class, () -> { - codePostService.findById(mockCodePost.getId(), email); - }); + assertThrows(CustomRuntimeException.class, + () -> codePostService.findById(mockCodePost.getId(), email) + ); } @Test void 비공개_포스트를_작성자가_조회() { //given CodePostBookmark mockBookmark = CodePostBookmark.builder() - .member(mockMember) + .email(mockMember.getEmail()) .codePostId(codePostId) .isDeleted(false) .build(); @@ -169,6 +158,7 @@ void save_post_review_day_is_not_null() { when(memberCommand.findByEmail(email)).thenReturn(mockMember); when(codePostCommand.findByIdIsPublic(codePostId, memberId)).thenReturn(mockCodePost); when(memberCommand.findById(memberId)).thenReturn(mockMember); + when(codePostBookmarkCommand.findByCodePostBookmark(email,codePostId)).thenReturn(Optional.empty()); //when CodePostResponseDto responseDto = codePostService.findById(codePostId, email); From 0d3bcb58943f4b58ef1339a27c25415939abcb21 Mon Sep 17 00:00:00 2001 From: ehgur Date: Fri, 20 Jun 2025 00:34:28 +0900 Subject: [PATCH 60/66] =?UTF-8?q?refactor:=20flyway=20=EC=A4=91=EB=B3=B5?= =?UTF-8?q?=20=EC=8B=A4=ED=96=89=20=EB=B0=A9=EC=A7=80=20=EB=B0=8F=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=20=EC=99=B8=EB=9E=98=ED=82=A4=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../V9.1__replace_member_id_with_email.sql | 49 ++++++++----------- .../V9.2_delete_member_foreign_key.sql | 7 +++ 2 files changed, 28 insertions(+), 28 deletions(-) create mode 100644 src/main/resources/db/migration/V9.2_delete_member_foreign_key.sql diff --git a/src/main/resources/db/migration/V9.1__replace_member_id_with_email.sql b/src/main/resources/db/migration/V9.1__replace_member_id_with_email.sql index aaca49f..8ff4c8b 100644 --- a/src/main/resources/db/migration/V9.1__replace_member_id_with_email.sql +++ b/src/main/resources/db/migration/V9.1__replace_member_id_with_email.sql @@ -1,28 +1,21 @@ --- 1. email 컬럼 추가 -ALTER TABLE code_post_bookmark - ADD COLUMN email VARCHAR(255); - --- 2. member_id → member_email 데이터 복사 -UPDATE code_post_bookmark cb - JOIN member m ON cb.member_id = m.id - SET cb.email = m.email; - --- 3. member_email NOT NULL 설정 -ALTER TABLE code_post_bookmark - MODIFY COLUMN email VARCHAR(255) NOT NULL; - --- 4. 기존 Unique 제약조건 삭제 -ALTER TABLE code_post_bookmark - DROP INDEX uq_codepost_member; - --- 5. 새로운 Unique 제약조건 추가 -ALTER TABLE code_post_bookmark - ADD CONSTRAINT uq_email_codepost UNIQUE (email, code_post_id); - --- 6. 외래키 제약 조건 제거 (필요 시) -ALTER TABLE code_post_bookmark - DROP FOREIGN KEY FKjy49hk8gdyg92iir31mofw9fh; -- 실제 제약조건 이름은 확인 필요 - --- 7. member_id 컬럼 제거 -ALTER TABLE code_post_bookmark - DROP COLUMN member_id; +-- -- 1. email 컬럼 추가 +-- ALTER TABLE code_post_bookmark +-- ADD COLUMN email VARCHAR(255); +-- +-- -- 2. member_id → member_email 데이터 복사 +-- UPDATE code_post_bookmark cb +-- JOIN member m ON cb.member_id = m.id +-- SET cb.email = m.email; +-- +-- -- 3. member_email NOT NULL 설정 +-- ALTER TABLE code_post_bookmark +-- MODIFY COLUMN email VARCHAR(255) NOT NULL; +-- +-- -- 4. 기존 Unique 제약조건 삭제 +-- ALTER TABLE code_post_bookmark +-- DROP INDEX uq_codepost_member; +-- +-- -- 5. 새로운 Unique 제약조건 추가 +-- ALTER TABLE code_post_bookmark +-- ADD CONSTRAINT uq_email_codepost UNIQUE (email, code_post_id); +-- diff --git a/src/main/resources/db/migration/V9.2_delete_member_foreign_key.sql b/src/main/resources/db/migration/V9.2_delete_member_foreign_key.sql new file mode 100644 index 0000000..69c1c01 --- /dev/null +++ b/src/main/resources/db/migration/V9.2_delete_member_foreign_key.sql @@ -0,0 +1,7 @@ +-- 1. 외래키 제약 조건 제거 (필요 시) +ALTER TABLE code_post_bookmark + DROP FOREIGN KEY FKjy49hk8gdyg92iir31mofw9fh; -- 실제 제약조건 이름은 확인 필요 + +-- 2. member_id 컬럼 제거 +ALTER TABLE code_post_bookmark + DROP COLUMN member_id; \ No newline at end of file From b23bfd37b1a0327288de5c352d66958f698872f6 Mon Sep 17 00:00:00 2001 From: ArcticFoox Date: Sun, 29 Jun 2025 23:35:05 +0900 Subject: [PATCH 61/66] refactor: changed file name --- security_setting | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security_setting b/security_setting index 0ce94ba..43cafd8 160000 --- a/security_setting +++ b/security_setting @@ -1 +1 @@ -Subproject commit 0ce94ba8d8f7c10e6ccb33d083939d4ebe43babf +Subproject commit 43cafd8b9e032982c5c9895e57e101bad76e4951 From 3429a9dcd4bd5723f9627d5e02a8a56bec7fee00 Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Tue, 1 Jul 2025 16:24:16 +0900 Subject: [PATCH 62/66] refactor: flyway version upgrade --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index e6663f7..382543e 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java' id 'org.springframework.boot' version '3.3.4' id 'io.spring.dependency-management' version '1.1.6' - id 'org.flywaydb.flyway' version '8.5.13' + id 'org.flywaydb.flyway' version '11.10.0' } group = 'org.example' @@ -47,7 +47,7 @@ dependencies { implementation 'io.micrometer:micrometer-registry-prometheus' // Flyway 라이브러리 사용을 위한 기본적인 의존성 추가 - implementation 'org.flywaydb:flyway-core' + implementation 'org.flywaydb:flyway-core:11.10.0' // MySQL 을 사용해야한다면 추가 implementation 'org.flywaydb:flyway-mysql' From 2913d11e4dd6544a42ab32a7e62a211618cc0c57 Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Fri, 4 Jul 2025 00:44:23 +0900 Subject: [PATCH 63/66] =?UTF-8?q?feat:=20delete=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fcm/controller/FcmTokenController.java | 18 ++++++++----- ...equestDto.java => FcmTokenRequestDto.java} | 2 +- .../domain/fcm/entity/FcmTokenRepository.java | 5 ++++ .../domain/fcm/service/FcmTokenCommand.java | 7 +++++ .../domain/fcm/service/FcmTokenService.java | 27 ++++++++++++++++--- 5 files changed, 47 insertions(+), 12 deletions(-) rename src/main/java/org/example/autoreview/domain/fcm/dto/request/{FcmTokenSaveRequestDto.java => FcmTokenRequestDto.java} (92%) diff --git a/src/main/java/org/example/autoreview/domain/fcm/controller/FcmTokenController.java b/src/main/java/org/example/autoreview/domain/fcm/controller/FcmTokenController.java index cd9098e..7d1b153 100644 --- a/src/main/java/org/example/autoreview/domain/fcm/controller/FcmTokenController.java +++ b/src/main/java/org/example/autoreview/domain/fcm/controller/FcmTokenController.java @@ -3,15 +3,12 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.example.autoreview.domain.fcm.dto.request.FcmTokenSaveRequestDto; +import org.example.autoreview.domain.fcm.dto.request.FcmTokenRequestDto; import org.example.autoreview.domain.fcm.service.FcmTokenService; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; -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 org.springframework.web.bind.annotation.*; @Tag(name = "FCM API") @RequiredArgsConstructor @@ -23,8 +20,15 @@ public class FcmTokenController { @Operation(summary = "fcm 저장 API", description = "권한 요청 후 FCM에서 받아온 토큰 저장") @PostMapping - public ResponseEntity save(@RequestBody FcmTokenSaveRequestDto requestDto, - @AuthenticationPrincipal UserDetails userDetails) { + public ResponseEntity save(@RequestBody FcmTokenRequestDto requestDto, + @AuthenticationPrincipal UserDetails userDetails) { return ResponseEntity.ok().body(fcmTokenService.save(requestDto, userDetails.getUsername())); } + + @Operation(summary = "fcm 삭제 API", description = "FCM 삭제") + @DeleteMapping + public ResponseEntity delete(@RequestBody FcmTokenRequestDto fcmTokenRequestDto, + @AuthenticationPrincipal UserDetails userDetails) { + return ResponseEntity.ok().body(fcmTokenService.delete(fcmTokenRequestDto, userDetails.getUsername())); + } } diff --git a/src/main/java/org/example/autoreview/domain/fcm/dto/request/FcmTokenSaveRequestDto.java b/src/main/java/org/example/autoreview/domain/fcm/dto/request/FcmTokenRequestDto.java similarity index 92% rename from src/main/java/org/example/autoreview/domain/fcm/dto/request/FcmTokenSaveRequestDto.java rename to src/main/java/org/example/autoreview/domain/fcm/dto/request/FcmTokenRequestDto.java index 0d55d3e..135c5b4 100644 --- a/src/main/java/org/example/autoreview/domain/fcm/dto/request/FcmTokenSaveRequestDto.java +++ b/src/main/java/org/example/autoreview/domain/fcm/dto/request/FcmTokenRequestDto.java @@ -7,7 +7,7 @@ @Getter @NoArgsConstructor -public class FcmTokenSaveRequestDto { +public class FcmTokenRequestDto { private String fcmToken; diff --git a/src/main/java/org/example/autoreview/domain/fcm/entity/FcmTokenRepository.java b/src/main/java/org/example/autoreview/domain/fcm/entity/FcmTokenRepository.java index 56c699a..fc1d0a0 100644 --- a/src/main/java/org/example/autoreview/domain/fcm/entity/FcmTokenRepository.java +++ b/src/main/java/org/example/autoreview/domain/fcm/entity/FcmTokenRepository.java @@ -1,7 +1,12 @@ package org.example.autoreview.domain.fcm.entity; +import io.lettuce.core.dynamic.annotation.Param; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface FcmTokenRepository extends JpaRepository { + @Query("SELECT f FROM FcmToken f WHERE f.member.id = :memberId AND f.token = :token") + FcmToken findByTokenAndMember(@Param("token") String token, @Param("memberId") Long memberId); + } diff --git a/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenCommand.java b/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenCommand.java index 537e637..57203ad 100644 --- a/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenCommand.java +++ b/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenCommand.java @@ -26,4 +26,11 @@ public void fcmTokensUpdate(Map fcmTokenMap) { entry.getKey().updateDate(entry.getValue()); } } + + @Transactional + public Long delete(String token, Long memberId) { + FcmToken fcmToken = fcmTokenRepository.findByTokenAndMember(token, memberId); + fcmTokenRepository.delete(fcmToken); + return fcmToken.getId(); + } } diff --git a/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenService.java b/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenService.java index c245d66..ad68b85 100644 --- a/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenService.java +++ b/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenService.java @@ -2,12 +2,16 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.example.autoreview.domain.fcm.dto.request.FcmTokenSaveRequestDto; +import org.example.autoreview.domain.fcm.dto.request.FcmTokenRequestDto; import org.example.autoreview.domain.fcm.entity.FcmToken; import org.example.autoreview.domain.member.entity.Member; import org.example.autoreview.domain.member.service.MemberCommand; import org.springframework.stereotype.Service; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; + @Slf4j @RequiredArgsConstructor @Service @@ -16,9 +20,24 @@ public class FcmTokenService { private final MemberCommand memberCommand; private final FcmTokenCommand fcmTokenCommand; - public Long save(FcmTokenSaveRequestDto requestDto, String email) { + // 토큰 단위 락 (메모리 기준) + private final Map lockMap = new ConcurrentHashMap<>(); + + public Long save(FcmTokenRequestDto requestDto, String email) { + ReentrantLock lock = lockMap.computeIfAbsent(requestDto.getFcmToken(), e -> new ReentrantLock()); + lock.lock(); + + try { + Member member = memberCommand.findByEmail(email); + FcmToken fcmToken = requestDto.toEntity(member); + return fcmTokenCommand.save(fcmToken); + } finally { + lock.unlock(); + } + } + + public Long delete(FcmTokenRequestDto requestDto, String email) { Member member = memberCommand.findByEmail(email); - FcmToken fcmToken = requestDto.toEntity(member); - return fcmTokenCommand.save(fcmToken); + return fcmTokenCommand.delete(requestDto.getFcmToken(), member.getId()); } } From 18675ab1d833a4419dca962ec1a5c44dd4653461 Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Fri, 4 Jul 2025 14:00:50 +0900 Subject: [PATCH 64/66] =?UTF-8?q?refactor:=20FcmToken=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=20=EB=8F=99=EC=8B=9C=EC=84=B1=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 서버가 하나이기 때문에 비즈니스 로직에서 해결 --- .../domain/fcm/service/FcmTokenService.java | 12 ++++++++++-- .../global/exception/errorcode/ErrorCode.java | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenService.java b/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenService.java index ad68b85..d84cd38 100644 --- a/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenService.java +++ b/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenService.java @@ -6,6 +6,8 @@ import org.example.autoreview.domain.fcm.entity.FcmToken; import org.example.autoreview.domain.member.entity.Member; import org.example.autoreview.domain.member.service.MemberCommand; +import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; +import org.example.autoreview.global.exception.errorcode.ErrorCode; import org.springframework.stereotype.Service; import java.util.Map; @@ -24,8 +26,13 @@ public class FcmTokenService { private final Map lockMap = new ConcurrentHashMap<>(); public Long save(FcmTokenRequestDto requestDto, String email) { - ReentrantLock lock = lockMap.computeIfAbsent(requestDto.getFcmToken(), e -> new ReentrantLock()); - lock.lock(); + String tokenKey = requestDto.getFcmToken(); + ReentrantLock lock = lockMap.computeIfAbsent(tokenKey, k -> new ReentrantLock()); + + boolean locked = lock.tryLock(); + if (!locked) { + throw new CustomRuntimeException(ErrorCode.DUPLICATE_ERROR); + } try { Member member = memberCommand.findByEmail(email); @@ -36,6 +43,7 @@ public Long save(FcmTokenRequestDto requestDto, String email) { } } + public Long delete(FcmTokenRequestDto requestDto, String email) { Member member = memberCommand.findByEmail(email); return fcmTokenCommand.delete(requestDto.getFcmToken(), member.getId()); diff --git a/src/main/java/org/example/autoreview/global/exception/errorcode/ErrorCode.java b/src/main/java/org/example/autoreview/global/exception/errorcode/ErrorCode.java index ed2fa40..876c558 100644 --- a/src/main/java/org/example/autoreview/global/exception/errorcode/ErrorCode.java +++ b/src/main/java/org/example/autoreview/global/exception/errorcode/ErrorCode.java @@ -12,6 +12,7 @@ public enum ErrorCode { INVALID_PARAMETER(400, HttpStatus.BAD_REQUEST, "잘못된 매개변수가 포함되었습니다."), NOT_FOUND_RESOURCE(404, HttpStatus.NOT_FOUND, "자원을 찾을 수 없습니다."), INTERNAL_SERVER_ERROR(500, HttpStatus.INTERNAL_SERVER_ERROR, "내부 서버 오류가 발생했습니다."), + DUPLICATE_ERROR(409, HttpStatus.CONFLICT, "중복 요청 오류가 발생했습니다."), // SECURITY UNAUTHORIZED_SECURITY(401, HttpStatus.UNAUTHORIZED, "자격 증명에 실패했습니다."), From 2a1e139d924c851a62669ff4b947c6f67d8333ee Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Fri, 4 Jul 2025 16:52:49 +0900 Subject: [PATCH 65/66] =?UTF-8?q?refactor:=20FcmToken=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - token 컬럼 unique 설정 - db 마이그레이션 진행 --- .../example/autoreview/domain/fcm/entity/FcmToken.java | 10 +++------- .../V10.1__alter_fcm_token_add_unique_token.sql | 7 +++++++ .../db/migration/V9.2__delete_member_foreign_key.sql | 10 ++++++++++ .../db/migration/V9.2_delete_member_foreign_key.sql | 7 ------- 4 files changed, 20 insertions(+), 14 deletions(-) create mode 100644 src/main/resources/db/migration/V10.1__alter_fcm_token_add_unique_token.sql create mode 100644 src/main/resources/db/migration/V9.2__delete_member_foreign_key.sql delete mode 100644 src/main/resources/db/migration/V9.2_delete_member_foreign_key.sql diff --git a/src/main/java/org/example/autoreview/domain/fcm/entity/FcmToken.java b/src/main/java/org/example/autoreview/domain/fcm/entity/FcmToken.java index 4d47786..d100582 100644 --- a/src/main/java/org/example/autoreview/domain/fcm/entity/FcmToken.java +++ b/src/main/java/org/example/autoreview/domain/fcm/entity/FcmToken.java @@ -1,12 +1,7 @@ package org.example.autoreview.domain.fcm.entity; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; +import jakarta.persistence.*; + import java.time.LocalDate; import lombok.Builder; import lombok.Getter; @@ -25,6 +20,7 @@ public class FcmToken { @JoinColumn private Member member; + @Column(nullable = false, unique = true) private String token; private LocalDate lastUsedDate; diff --git a/src/main/resources/db/migration/V10.1__alter_fcm_token_add_unique_token.sql b/src/main/resources/db/migration/V10.1__alter_fcm_token_add_unique_token.sql new file mode 100644 index 0000000..b2ecf92 --- /dev/null +++ b/src/main/resources/db/migration/V10.1__alter_fcm_token_add_unique_token.sql @@ -0,0 +1,7 @@ +-- 1. token 컬럼 NOT NULL 제약 추가 (있다면 무시) +ALTER TABLE fcm_token + MODIFY COLUMN token VARCHAR(255) NOT NULL; + +-- 2. token 컬럼에 UNIQUE 제약 추가 +ALTER TABLE fcm_token + ADD CONSTRAINT uq_fcm_token UNIQUE (token); diff --git a/src/main/resources/db/migration/V9.2__delete_member_foreign_key.sql b/src/main/resources/db/migration/V9.2__delete_member_foreign_key.sql new file mode 100644 index 0000000..f7bc2dc --- /dev/null +++ b/src/main/resources/db/migration/V9.2__delete_member_foreign_key.sql @@ -0,0 +1,10 @@ +-- 1. 외래키 제약 조건 제거 +ALTER TABLE code_post_bookmark +DROP FOREIGN KEY fk_code_post_bookmark_member; + +-- 2. 인덱스 제거 (외래키에 대한 인덱스였다면 함께 제거) +DROP INDEX fk_code_post_bookmark_member ON code_post_bookmark; + +-- 3. 컬럼 제거 +ALTER TABLE code_post_bookmark +DROP COLUMN member_id; diff --git a/src/main/resources/db/migration/V9.2_delete_member_foreign_key.sql b/src/main/resources/db/migration/V9.2_delete_member_foreign_key.sql deleted file mode 100644 index 69c1c01..0000000 --- a/src/main/resources/db/migration/V9.2_delete_member_foreign_key.sql +++ /dev/null @@ -1,7 +0,0 @@ --- 1. 외래키 제약 조건 제거 (필요 시) -ALTER TABLE code_post_bookmark - DROP FOREIGN KEY FKjy49hk8gdyg92iir31mofw9fh; -- 실제 제약조건 이름은 확인 필요 - --- 2. member_id 컬럼 제거 -ALTER TABLE code_post_bookmark - DROP COLUMN member_id; \ No newline at end of file From c84895dbe53d60ba3a853fa94adf3cea44ed71ce Mon Sep 17 00:00:00 2001 From: ehgur062300 Date: Fri, 4 Jul 2025 17:17:36 +0900 Subject: [PATCH 66/66] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EC=A0=80=EC=9E=A5=20=EC=8B=9C=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../autoreview/domain/fcm/service/FcmTokenService.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenService.java b/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenService.java index d84cd38..5b1ad03 100644 --- a/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenService.java +++ b/src/main/java/org/example/autoreview/domain/fcm/service/FcmTokenService.java @@ -8,6 +8,7 @@ import org.example.autoreview.domain.member.service.MemberCommand; import org.example.autoreview.global.exception.base_exceptions.CustomRuntimeException; import org.example.autoreview.global.exception.errorcode.ErrorCode; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import java.util.Map; @@ -38,7 +39,9 @@ public Long save(FcmTokenRequestDto requestDto, String email) { Member member = memberCommand.findByEmail(email); FcmToken fcmToken = requestDto.toEntity(member); return fcmTokenCommand.save(fcmToken); - } finally { + } catch (DataIntegrityViolationException e) { + throw new CustomRuntimeException(ErrorCode.DUPLICATE_ERROR); + }finally { lock.unlock(); } }