From 59d380405cd1408ebd14534e3e5b8cc73544124b Mon Sep 17 00:00:00 2001 From: Jaehyeon Date: Fri, 5 Apr 2024 16:28:52 +0900 Subject: [PATCH 1/7] [KAN-8] feat: test for jira --- .../com/numberone/backend/domain/article/entity/Article.java | 1 + 1 file changed, 1 insertion(+) diff --git a/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java b/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java index c0bee56e..6b00f60b 100644 --- a/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java +++ b/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java @@ -100,6 +100,7 @@ public void updateAddress(String address) { public void updateAddressDetail (String[] addressDetails) { int length = addressDetails.length; + this.lv1 = length > 0 ? addressDetails[0] : ""; this.lv2 = length > 1 ? addressDetails[1] : ""; this.lv3 = length > 2 ? addressDetails[2] : ""; From f16bd9f896dc31806436ee342d06273105cefc7c Mon Sep 17 00:00:00 2001 From: Jaehyeon Date: Fri, 5 Apr 2024 22:36:53 +0900 Subject: [PATCH 2/7] [KAN-8] feat: removed ArticleParticipant entity --- .../article/service/ArticleService.java | 10 ------ .../comment/service/CommentService.java | 5 --- .../domain/article/entity/Article.java | 4 --- .../entity/ArticleParticipant.java | 36 ------------------- .../ArticleParticipantRepository.java | 7 ---- 5 files changed, 62 deletions(-) delete mode 100644 daepiro-core/src/main/java/com/numberone/backend/domain/articleparticipant/entity/ArticleParticipant.java delete mode 100644 daepiro-core/src/main/java/com/numberone/backend/domain/articleparticipant/repository/ArticleParticipantRepository.java diff --git a/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java b/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java index a6773ba7..5f851b64 100644 --- a/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java +++ b/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java @@ -9,8 +9,6 @@ import com.numberone.backend.domain.article.repository.ArticleRepository; import com.numberone.backend.domain.articleimage.entity.ArticleImage; import com.numberone.backend.domain.articleimage.repository.ArticleImageRepository; -import com.numberone.backend.domain.articleparticipant.entity.ArticleParticipant; -import com.numberone.backend.domain.articleparticipant.repository.ArticleParticipantRepository; import com.numberone.backend.domain.comment.dto.request.CreateCommentRequest; import com.numberone.backend.domain.comment.dto.response.CreateCommentResponse; import com.numberone.backend.domain.comment.entity.CommentEntity; @@ -55,7 +53,6 @@ public class ArticleService { private final ArticleRepository articleRepository; private final MemberRepository memberRepository; - private final ArticleParticipantRepository articleParticipantRepository; private final ArticleImageRepository articleImageRepository; private final CommentRepository commentRepository; private final ArticleLikeRepository articleLikeRepository; @@ -81,10 +78,6 @@ public UploadArticleResponse uploadArticle(UploadArticleRequest request) { request.getArticleTag()) ); - articleParticipantRepository.save( - new ArticleParticipant(article, owner) - ); - // 2. 이미지 업로드 todo: 비동기 업로드 List articleImages = new ArrayList<>(); List imageUrls = new ArrayList<>(); @@ -224,9 +217,6 @@ public CreateCommentResponse createComment(Long articleId, CreateCommentRequest Member articleOwner = memberRepository.findById(article.getArticleOwnerId()) .orElseThrow(NotFoundMemberException::new); - articleParticipantRepository.save(new ArticleParticipant(article, member)); - - String memberName = member.getNickName() != null ? member.getNickName() : member.getRealName(); String title = String.format(""" 나의 게시글에 %s님이 댓글을 달았어요.""", memberName); diff --git a/daepiro-api/src/main/java/com/numberone/backend/domain/comment/service/CommentService.java b/daepiro-api/src/main/java/com/numberone/backend/domain/comment/service/CommentService.java index ab9d268f..d7a4ab54 100644 --- a/daepiro-api/src/main/java/com/numberone/backend/domain/comment/service/CommentService.java +++ b/daepiro-api/src/main/java/com/numberone/backend/domain/comment/service/CommentService.java @@ -2,8 +2,6 @@ import com.numberone.backend.domain.article.entity.Article; import com.numberone.backend.domain.article.repository.ArticleRepository; -import com.numberone.backend.domain.articleparticipant.entity.ArticleParticipant; -import com.numberone.backend.domain.articleparticipant.repository.ArticleParticipantRepository; import com.numberone.backend.domain.comment.dto.request.CreateChildCommentRequest; import com.numberone.backend.domain.comment.dto.response.CreateChildCommentResponse; import com.numberone.backend.domain.comment.dto.response.DeleteCommentResponse; @@ -37,7 +35,6 @@ public class CommentService { private final CommentRepository commentRepository; private final ArticleRepository articleRepository; - private final ArticleParticipantRepository articleParticipantRepository; private final MemberRepository memberRepository; private final CommentLikeRepository commentLikeRepository; private final NotificationRepository notificationRepository; @@ -60,8 +57,6 @@ public CreateChildCommentResponse createChildComment( CommentEntity childComment = commentRepository.save(new CommentEntity(request.getContent(), article, member)); childComment.updateParent(parentComment); - articleParticipantRepository.save(new ArticleParticipant(article, member)); - Member owner = memberRepository.findById(parentComment.getAuthorId()) .orElseThrow(NotFoundMemberException::new); String memberName = member.getNickName() != null ? member.getNickName() : member.getRealName(); diff --git a/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java b/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java index 6b00f60b..621c659f 100644 --- a/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java +++ b/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java @@ -2,7 +2,6 @@ import com.numberone.backend.config.basetime.BaseTimeEntity; import com.numberone.backend.domain.articleimage.entity.ArticleImage; -import com.numberone.backend.domain.articleparticipant.entity.ArticleParticipant; import com.numberone.backend.domain.comment.entity.CommentEntity; import jakarta.persistence.*; import lombok.AccessLevel; @@ -28,9 +27,6 @@ public class Article extends BaseTimeEntity { @OneToMany(mappedBy = "article", cascade = {CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval = true) private List comments = new ArrayList<>(); - @OneToMany(mappedBy = "article", cascade = {CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval = true) - private List articleParticipants = new ArrayList<>(); - @OneToMany(mappedBy = "article", cascade = {CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval = true) private List articleImages = new ArrayList<>(); diff --git a/daepiro-core/src/main/java/com/numberone/backend/domain/articleparticipant/entity/ArticleParticipant.java b/daepiro-core/src/main/java/com/numberone/backend/domain/articleparticipant/entity/ArticleParticipant.java deleted file mode 100644 index 0e3e9452..00000000 --- a/daepiro-core/src/main/java/com/numberone/backend/domain/articleparticipant/entity/ArticleParticipant.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.numberone.backend.domain.articleparticipant.entity; - -import com.numberone.backend.config.basetime.BaseTimeEntity; -import com.numberone.backend.domain.article.entity.Article; -import com.numberone.backend.domain.member.entity.Member; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.hibernate.annotations.Comment; - -@Comment("동네생활 게시글 참여자") -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -@Entity -@Table(name = "ARTICLE_PARTICIPANT") -public class ArticleParticipant extends BaseTimeEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "article_participant_id") - private Long id; - - - @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) - @JoinColumn(name = "article_id") - private Article article; - - @Comment("회원 id") - private Long memberId; - - public ArticleParticipant(Article article, Member member){ - this.article = article; - this.memberId= member.getId(); - } -} diff --git a/daepiro-core/src/main/java/com/numberone/backend/domain/articleparticipant/repository/ArticleParticipantRepository.java b/daepiro-core/src/main/java/com/numberone/backend/domain/articleparticipant/repository/ArticleParticipantRepository.java deleted file mode 100644 index 6e57bde9..00000000 --- a/daepiro-core/src/main/java/com/numberone/backend/domain/articleparticipant/repository/ArticleParticipantRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.numberone.backend.domain.articleparticipant.repository; - -import com.numberone.backend.domain.articleparticipant.entity.ArticleParticipant; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface ArticleParticipantRepository extends JpaRepository { -} From 396b35e6c01fa60763a2a577106e305129c68d8d Mon Sep 17 00:00:00 2001 From: Jaehyeon Date: Sat, 6 Apr 2024 00:21:20 +0900 Subject: [PATCH 3/7] [KAN-8] refactor: upload article API --- .../article/controller/ArticleController.java | 5 +- .../dto/request/UploadArticleRequest.java | 45 +++----- .../dto/response/UploadArticleResponse.java | 39 ++++--- .../article/service/ArticleService.java | 103 +++++++----------- .../backend/provider/s3/S3Provider.java | 1 + .../domain/article/entity/Article.java | 40 ++++--- 6 files changed, 99 insertions(+), 134 deletions(-) diff --git a/daepiro-api/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java b/daepiro-api/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java index 093daf10..b277055e 100644 --- a/daepiro-api/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java +++ b/daepiro-api/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java @@ -7,6 +7,7 @@ import com.numberone.backend.domain.article.service.ArticleService; import com.numberone.backend.domain.comment.dto.request.CreateCommentRequest; import com.numberone.backend.domain.comment.dto.response.CreateCommentResponse; +import com.numberone.backend.provider.security.SecurityContextProvider; import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -43,8 +44,8 @@ public class ArticleController { @PostMapping public ResponseEntity uploadArticle(@ModelAttribute @Valid UploadArticleRequest request) { - return ResponseEntity.created(URI.create("/api/articles")) - .body(articleService.uploadArticle(request)); + Long userId = SecurityContextProvider.getAuthenticatedUserId(); + return ResponseEntity.created(URI.create("/api/articles")).body(articleService.uploadArticle(request, userId)); } @Operation(summary = "게시글을 삭제하는 API 입니다.", description = """ diff --git a/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/request/UploadArticleRequest.java b/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/request/UploadArticleRequest.java index 34650b0d..46785c5b 100644 --- a/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/request/UploadArticleRequest.java +++ b/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/request/UploadArticleRequest.java @@ -2,37 +2,24 @@ import com.numberone.backend.domain.article.entity.ArticleTag; import jakarta.validation.constraints.NotNull; -import lombok.*; import org.springframework.web.multipart.MultipartFile; import java.util.List; -@ToString -@Builder -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class UploadArticleRequest { - - // 글 관련 - @NotNull(message = "글 제목은 null 일 수 없습니다.") - private String title; // 제목 - - @NotNull(message = "내용은 null 일 수 없습니다.") - private String content; // 내용 - - @NotNull(message = """ - 게시글의 태그를 하나 선택해주세요. - """) - private ArticleTag articleTag; // 게시글 태그 - - // 이미지 관련 - private List imageList; // 이미지 리스트 - - private Double longitude; - private Double latitude; - - @NotNull(message = "동 위치 정보 제공 동의는 null 일 수 없습니다.") - private boolean regionAgreementCheck; // 동 정보 제공 동의 - +public record UploadArticleRequest( + @NotNull(message = "글 제목은 null 일 수 없습니다.") + String title, + @NotNull(message = "내용은 null 일 수 없습니다.") + String content, + @NotNull(message = "게시글 태그는 null 일 수 없습니다.") + ArticleTag articleTag, + List imageList, + Double longitude, + Double latitude, + @NotNull(message = "위치 정보 제공 동의는 null 일 수 없습니다.") + Boolean regionAgreementCheck +) { + public boolean isValidPosition(){ + return (longitude != null) && (latitude != null); + } } diff --git a/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/response/UploadArticleResponse.java b/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/response/UploadArticleResponse.java index c18a13c7..d4b649f6 100644 --- a/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/response/UploadArticleResponse.java +++ b/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/response/UploadArticleResponse.java @@ -1,36 +1,35 @@ package com.numberone.backend.domain.article.dto.response; import com.numberone.backend.domain.article.entity.Article; -import lombok.*; +import com.numberone.backend.domain.articleimage.entity.ArticleImage; +import lombok.Builder; import java.time.LocalDateTime; import java.util.List; -@ToString @Builder -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class UploadArticleResponse { - - private Long articleId; - private LocalDateTime createdAt; - - // 이미지 관련 - private List imageUrls; - private String thumbNailImageUrl; - - // 작성자 주소 - private String address; - - public static UploadArticleResponse of(Article article, List imageUrls, String thumbNailImageUrl){ +public record UploadArticleResponse( + Long articleId, + LocalDateTime createdAt, + List imageUrls, + String thumbNailImageUrl, + String address +) { + public static UploadArticleResponse from (Article article){ return UploadArticleResponse.builder() .articleId(article.getId()) .createdAt(article.getCreatedAt()) - .imageUrls(imageUrls) - .thumbNailImageUrl(thumbNailImageUrl) .address(article.getAddress()) .build(); } + public static UploadArticleResponse ofImages (Article article, List images) { + return UploadArticleResponse.builder() + .articleId(article.getId()) + .createdAt(article.getCreatedAt()) + .imageUrls(images.stream().map(ArticleImage::getImageUrl).toList()) + .thumbNailImageUrl(images.isEmpty() ? "" : images.get(0).getImageUrl()) + .address(article.getAddress()) + .build(); + } } diff --git a/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java b/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java index 5f851b64..d2b4e501 100644 --- a/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java +++ b/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java @@ -50,89 +50,34 @@ public class ArticleService { // todo: 리팩토링 (db 정리하고, 불필요한 쿼리 + 비효율적인 코드 모두 제거 ) - private final ArticleRepository articleRepository; private final MemberRepository memberRepository; private final ArticleImageRepository articleImageRepository; private final CommentRepository commentRepository; private final ArticleLikeRepository articleLikeRepository; - private final NotificationRepository notificationRepository; + private final S3Provider s3Provider; private final LocationProvider locationProvider; private final FcmMessageProvider fcmMessageProvider; @Transactional - public UploadArticleResponse uploadArticle(UploadArticleRequest request) { - // todo: 리팩토링 - long id = SecurityContextProvider.getAuthenticatedUserId(); - Member owner = memberRepository.findById(id) + public UploadArticleResponse uploadArticle(UploadArticleRequest request, Long userId) { + Member owner = memberRepository.findById(userId) .orElseThrow(NotFoundMemberException::new); + Article article = articleRepository.save(Article.of(request.title(), request.content(), owner, request.articleTag())); - // 1. 게시글 생성 ( 제목, 내용, 작성자 아이디, 태그) - Article article = articleRepository.save( - new Article( - request.getTitle(), - request.getContent(), - owner.getId(), - request.getArticleTag()) - ); - - // 2. 이미지 업로드 todo: 비동기 업로드 - List articleImages = new ArrayList<>(); - List imageUrls = new ArrayList<>(); - String thumbNailImageUrl = ""; - Long thumbNailImageId = 1L; - if (!Objects.isNull(request.getImageList())) { - // todo: refactoring - List imageList = request.getImageList(); - - for (int i = 0; i < imageList.size(); i++) { - String imageUrl = s3Provider.uploadImage(imageList.get(i)); - imageUrls.add(imageUrl); - - ArticleImage savedArticleImage = articleImageRepository.save( - new ArticleImage(article, imageUrl) - ); - articleImages.add(savedArticleImage); - if (i == 0) { - thumbNailImageUrl = imageUrl; - thumbNailImageId = savedArticleImage.getId(); - } - - } - } - - // 3. 게시글 - 이미지 연관 관계 설정 - article.updateArticleImage(articleImages, thumbNailImageId); - - // 4. 작성자 주소 설정 - Double latitude = request.getLatitude(); - Double longitude = request.getLongitude(); - String address = ""; - if (latitude != null && longitude != null && request.isRegionAgreementCheck()) { - // 주소가 null 이 아닌 경우에만 api 요청하여 update - address = locationProvider.pos2address(request.getLatitude(), request.getLongitude()); - article.updateAddress(address); + if (request.regionAgreementCheck() && request.isValidPosition()) { + updateArticleAddress(request, article, owner); } - if (!address.isEmpty()) { - String[] regionInfo = address.split(" "); - article.updateAddressDetail(regionInfo); - validateLocation(owner, address); + if (request.imageList() != null) { + List articleImages = uploadImages(article, request.imageList()); + article.updateThumbNailImageUrlId(articleImages.get(0).getId()); + return UploadArticleResponse.ofImages(article, articleImages); } - return UploadArticleResponse.of(article, imageUrls, thumbNailImageUrl); - } - - public void validateLocation(Member member, String realLocation) { - List regionLv2List = member.getNotificationRegions() - .stream().map(NotificationRegion::getLv2).toList(); - String[] realRegions = realLocation.split(" "); - - if (realRegions.length >= 1 && !regionLv2List.contains(realRegions[1])) { - throw new UnauthorizedLocationException(); - } + return UploadArticleResponse.from(article); } @Transactional @@ -268,4 +213,30 @@ public ModifyArticleResponse modifyArticle(Long articleId, ModifyArticleRequest return ModifyArticleResponse.of(article, imageUrls, thumbNailImageUrl); } + + private void updateArticleAddress(UploadArticleRequest request, Article article, Member owner) { + String address = locationProvider.pos2address(request.latitude(), request.longitude()); + article.updateAddress(address); + validateLocation(owner, address); + } + + private List uploadImages(Article article, List images) { + List articleImages = new ArrayList<>(); + for (MultipartFile image : images) { + String url = s3Provider.uploadImage(image); + articleImages.add(new ArticleImage(article, url)); + } + return articleImageRepository.saveAll(articleImages); + } + + private void validateLocation(Member member, String realLocation) { + List regionLv2List = member.getNotificationRegions() + .stream().map(NotificationRegion::getLv2).toList(); + String[] realRegions = realLocation.split(" "); + + if (realRegions.length >= 1 && !regionLv2List.contains(realRegions[1])) { + throw new UnauthorizedLocationException(); + } + } + } diff --git a/daepiro-common/src/main/java/com/numberone/backend/provider/s3/S3Provider.java b/daepiro-common/src/main/java/com/numberone/backend/provider/s3/S3Provider.java index f7fcc78d..b8fe37c0 100644 --- a/daepiro-common/src/main/java/com/numberone/backend/provider/s3/S3Provider.java +++ b/daepiro-common/src/main/java/com/numberone/backend/provider/s3/S3Provider.java @@ -39,6 +39,7 @@ public class S3Provider { * @return ImageUrl */ public String uploadImage(MultipartFile multipartFile) { + // todo: 성능 개선 checkInvalidUploadFile(multipartFile); ObjectMetadata objectMetadata = new ObjectMetadata(); diff --git a/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java b/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java index 621c659f..19043f57 100644 --- a/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java +++ b/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java @@ -3,10 +3,9 @@ import com.numberone.backend.config.basetime.BaseTimeEntity; import com.numberone.backend.domain.articleimage.entity.ArticleImage; import com.numberone.backend.domain.comment.entity.CommentEntity; +import com.numberone.backend.domain.member.entity.Member; import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.Comment; @@ -14,9 +13,11 @@ import java.util.List; @Comment("동네생활 게시글 정보") -@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @Entity +@Builder +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "ARTICLE") public class Article extends BaseTimeEntity { @Id @@ -66,13 +67,15 @@ public class Article extends BaseTimeEntity { @Comment("작성자 ID") private Long articleOwnerId; - public Article(String title, String content, Long articleOwnerId, ArticleTag tag) { - this.title = title; - this.content = content; - this.articleOwnerId = articleOwnerId; - this.articleTag = tag; - this.articleStatus = ArticleStatus.ACTIVATED; - this.likeCount = 0; + public static Article of(String title, String content, Member owner, ArticleTag tag){ + return Article.builder() + .title(title) + .content(content) + .articleOwnerId(owner.getId()) + .articleTag(tag) + .articleStatus(ArticleStatus.ACTIVATED) + .likeCount(0) + .build(); } public void updateArticleImage(List images, Long thumbNailImageUrlId) { @@ -84,6 +87,10 @@ public void updateArticleStatus(ArticleStatus status) { this.articleStatus = status; } + public void updateThumbNailImageUrlId(Long thumbNailImageUrlId){ + this.thumbNailImageUrlId = thumbNailImageUrlId; + } + public void modifyArticle(String title, String content, ArticleTag tag) { this.title = title; this.content = content; @@ -92,14 +99,13 @@ public void modifyArticle(String title, String content, ArticleTag tag) { public void updateAddress(String address) { this.address = address; - } - public void updateAddressDetail (String[] addressDetails) { - int length = addressDetails.length; + String [] tokens = address.split(" "); + int length = tokens.length; - this.lv1 = length > 0 ? addressDetails[0] : ""; - this.lv2 = length > 1 ? addressDetails[1] : ""; - this.lv3 = length > 2 ? addressDetails[2] : ""; + this.lv1 = length > 0 ? tokens[0] : ""; + this.lv2 = length > 1 ? tokens[1] : ""; + this.lv3 = length > 2 ? tokens[2] : ""; } public void increaseLikeCount() { From ed245edbfc9426c2a1e2c95aabcb6c6294f09575 Mon Sep 17 00:00:00 2001 From: Jaehyeon Date: Sat, 6 Apr 2024 01:02:47 +0900 Subject: [PATCH 4/7] [KAN-8] refactor: get article details --- .../article/controller/ArticleController.java | 3 +- .../response/GetArticleDetailResponse.java | 112 +++++++----------- .../article/service/ArticleService.java | 56 ++++----- .../domain/like/service/LikeService.java | 11 +- .../domain/article/entity/Article.java | 6 +- .../article/repository/ArticleRepository.java | 8 ++ 6 files changed, 86 insertions(+), 110 deletions(-) diff --git a/daepiro-api/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java b/daepiro-api/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java index b277055e..623120e9 100644 --- a/daepiro-api/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java +++ b/daepiro-api/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java @@ -62,7 +62,8 @@ public ResponseEntity deleteArticle(@PathVariable("articl """) @GetMapping("{article-id}") public ResponseEntity getArticleDetails(@PathVariable("article-id") Long articleId) { - return ResponseEntity.ok(articleService.getArticleDetail(articleId)); + Long userId = SecurityContextProvider.getAuthenticatedUserId(); + return ResponseEntity.ok(articleService.getArticleDetail(articleId, userId)); } diff --git a/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java b/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java index b8e41df7..e5cf13b3 100644 --- a/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java +++ b/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java @@ -3,93 +3,69 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.numberone.backend.domain.article.entity.Article; import com.numberone.backend.domain.article.entity.ArticleTag; +import com.numberone.backend.domain.articleimage.entity.ArticleImage; import com.numberone.backend.domain.member.entity.Member; -import lombok.*; +import lombok.Builder; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; -@ToString @Builder -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class GetArticleDetailResponse { - - // 게시글 관련 - private Long articleId; - private Integer likeCount; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm", timezone = "Asia/Seoul") - private LocalDateTime createdAt; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm", timezone = "Asia/Seoul") - private LocalDateTime modifiedAt; - private String title; - private String content; - private boolean isLiked; - private ArticleTag articleTag; - private Long commentCount; - - // 작성자 관련 - private String ownerName; - private String ownerNickName; - private String address; - private String regionLv2; - private Long ownerMemberId; - private String ownerProfileImageUrl; - - // 이미지 관련 - private List imageUrls; - private String thumbNailImageUrl; - - public static GetArticleDetailResponse of( - Article article, - List imageUrls, - String thumbNailImageUrl, - Member owner, - List memberLikedArticleList, - Long commentCount ) { - - String address = ""; - - String articleAddress = article.getAddress(); - if(!articleAddress.isEmpty()){ - String[] elements = articleAddress.split(" "); - switch (elements.length){ - case 3 -> address = elements[2]; - case 2 -> address = elements[1]; - case 1 -> address = elements[0]; - default ->address = ""; - } - } else { - address = ""; - } - - +public record GetArticleDetailResponse( + Long articleId, + Integer likeCount, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm", timezone = "Asia/Seoul") + LocalDateTime createdAt, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm", timezone = "Asia/Seoul") + LocalDateTime modifiedAt, + String title, + String content, + Boolean isLiked, + ArticleTag articleTag, + Long commentCount, + String ownerName, + String ownerNickName, + String address, + String regionLv2, + Long ownerMemberId, + String ownerProfileImageUrl, + List imageUrls, + String thumbNailImageUrl +) { + public static GetArticleDetailResponse of(Article article, + List images, + Member owner, + boolean isLiked, + Long commentCount) { + List imageUrls = images.stream().map(ArticleImage::getImageUrl).toList(); return GetArticleDetailResponse.builder() .articleId(article.getId()) .title(article.getTitle()) .content(article.getContent()) - .likeCount( - Optional.ofNullable( - article.getLikeCount() - ).orElse(0) - ) + .likeCount(Optional.ofNullable(article.getLikeCount()).orElse(0)) .createdAt(article.getCreatedAt()) .modifiedAt(article.getModifiedAt()) .ownerMemberId(owner.getId()) .ownerName(owner.getRealName()) .ownerNickName(owner.getNickName()) - .imageUrls(imageUrls) - .thumbNailImageUrl(thumbNailImageUrl) - .address(address) + .imageUrls(images.stream().map(ArticleImage::getImageUrl).toList()) + .thumbNailImageUrl(imageUrls.isEmpty() ? "" : imageUrls.get(0)) + .address(getAddress(article)) .ownerProfileImageUrl(owner.getProfileImageUrl()) - .isLiked(memberLikedArticleList.contains(article.getId())) + .isLiked(isLiked) .articleTag(article.getArticleTag()) .commentCount(commentCount) - .regionLv2(Optional.ofNullable(article.getLv2()) - .orElse("")) + .regionLv2(Optional.ofNullable(article.getLv2()).orElse("")) .build(); } - + private static String getAddress(Article article) { + String address = article.getAddress(); + if (!address.isEmpty()) { + String[] tokens = address.split(" "); + int length = tokens.length; + return length > 0 ? tokens[length - 1] : ""; + } + return ""; + } } diff --git a/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java b/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java index d2b4e501..d866b5d5 100644 --- a/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java +++ b/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java @@ -37,10 +37,8 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.util.*; +import java.util.stream.Collectors; @Slf4j @RequiredArgsConstructor @@ -88,35 +86,22 @@ public DeleteArticleResponse deleteArticle(Long articleId) { return DeleteArticleResponse.of(article); } - public GetArticleDetailResponse getArticleDetail(Long articleId) { - long id = SecurityContextProvider.getAuthenticatedUserId(); - Member member = memberRepository.findById(id) // 회원 + public GetArticleDetailResponse getArticleDetail(Long articleId, Long memberId){ + Member member = memberRepository.findById(memberId) .orElseThrow(NotFoundMemberException::new); - Article article = articleRepository.findById(articleId) + Article article = articleRepository.findByIdFetchJoin(articleId) .orElseThrow(NotFoundArticleException::new); - Member owner = memberRepository.findById(article.getArticleOwnerId()) // 작성자 - .orElseThrow(NotFoundMemberException::new); - - List imageUrls = articleImageRepository.findByArticle(article) - .stream() - .map(ArticleImage::getImageUrl) - .toList(); - - Optional thumbNailImage = articleImageRepository.findById(article.getThumbNailImageUrlId()); Long commentCount = commentRepository.countAllByArticle(articleId); - - String thumbNailImageUrl = ""; - if (thumbNailImage.isPresent()) { - thumbNailImageUrl = thumbNailImage.get().getImageUrl(); - } - - // 내가 좋아요 한 게시글의 ID 리스트 - List memberLikedArticleIdList = articleLikeRepository.findByMember(member) - .stream().map(ArticleLike::getArticleId) - .toList(); - - return GetArticleDetailResponse.of(article, imageUrls, thumbNailImageUrl, owner, memberLikedArticleIdList, commentCount); + boolean isLiked = getIsLikedArticle(member, articleId); + + return GetArticleDetailResponse.of( + article, + article.getArticleImages(), + article.getArticleOwner(), + isLiked, + commentCount + ); } public Slice getArticleListPaging(ArticleSearchParameter param, Pageable pageable) { @@ -154,13 +139,12 @@ public CreateCommentResponse createComment(Long articleId, CreateCommentRequest long id = SecurityContextProvider.getAuthenticatedUserId(); Member member = memberRepository.findById(id) .orElseThrow(NotFoundMemberException::new); - Article article = articleRepository.findById(articleId) + Article article = articleRepository.findByIdFetchJoin(articleId) .orElseThrow(NotFoundArticleException::new); CommentEntity savedComment = commentRepository.save( new CommentEntity(request.getContent(), article, member) ); - Member articleOwner = memberRepository.findById(article.getArticleOwnerId()) - .orElseThrow(NotFoundMemberException::new); + Member articleOwner = article.getArticleOwner(); String memberName = member.getNickName() != null ? member.getNickName() : member.getRealName(); String title = String.format(""" @@ -239,4 +223,12 @@ private void validateLocation(Member member, String realLocation) { } } + private boolean getIsLikedArticle(Member member, Long articleId){ + Set likedArticleIds = member.getArticleLikes() + .stream() + .map(ArticleLike::getArticleId) + .collect(Collectors.toSet()); + return likedArticleIds.contains(articleId); + } + } diff --git a/daepiro-api/src/main/java/com/numberone/backend/domain/like/service/LikeService.java b/daepiro-api/src/main/java/com/numberone/backend/domain/like/service/LikeService.java index 8a97a5f7..99c02e4e 100644 --- a/daepiro-api/src/main/java/com/numberone/backend/domain/like/service/LikeService.java +++ b/daepiro-api/src/main/java/com/numberone/backend/domain/like/service/LikeService.java @@ -48,7 +48,7 @@ public Integer increaseArticleLike(Long articleId) { long principal = SecurityContextProvider.getAuthenticatedUserId(); Member member = memberRepository.findById(principal) .orElseThrow(NotFoundMemberException::new); - Article article = articleRepository.findById(articleId) + Article article = articleRepository.findByIdFetchJoin(articleId) .orElseThrow(NotFoundApiException::new); if (isAlreadyLikedArticle(member, articleId)) { // 이미 좋아요를 누른 게시글입니다. @@ -57,17 +57,14 @@ public Integer increaseArticleLike(Long articleId) { article.increaseLikeCount(); articleLikeRepository.save(new ArticleLike(member, article)); - Long ownerId = article.getArticleOwnerId(); - Member owner = memberRepository.findById(ownerId) - .orElseThrow(NotFoundMemberException::new); - + Member articleOwner = article.getArticleOwner(); String memberName = member.getNickName() != null ? member.getNickName() : member.getRealName(); String title = String.format(""" 나의 게시글에 %s님이 좋아요를 눌렀어요.""", memberName); String body = "대피로에 접속하여 확인하세요!"; - fcmMessageProvider.sendFcm(owner.getFcmToken(), title, body); + fcmMessageProvider.sendFcm(articleOwner.getFcmToken(), title, body); notificationRepository.save( - new NotificationEntity(owner, NotificationTag.COMMUNITY, title, body, true) + new NotificationEntity(articleOwner, NotificationTag.COMMUNITY, title, body, true) ); return article.getLikeCount(); diff --git a/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java b/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java index 19043f57..44cd57ca 100644 --- a/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java +++ b/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java @@ -65,13 +65,15 @@ public class Article extends BaseTimeEntity { private Integer likeCount; @Comment("작성자 ID") - private Long articleOwnerId; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "article_owner_id") + private Member articleOwner; public static Article of(String title, String content, Member owner, ArticleTag tag){ return Article.builder() .title(title) .content(content) - .articleOwnerId(owner.getId()) + .articleOwner(owner) .articleTag(tag) .articleStatus(ArticleStatus.ACTIVATED) .likeCount(0) diff --git a/daepiro-core/src/main/java/com/numberone/backend/domain/article/repository/ArticleRepository.java b/daepiro-core/src/main/java/com/numberone/backend/domain/article/repository/ArticleRepository.java index b0a9d891..8965c0de 100644 --- a/daepiro-core/src/main/java/com/numberone/backend/domain/article/repository/ArticleRepository.java +++ b/daepiro-core/src/main/java/com/numberone/backend/domain/article/repository/ArticleRepository.java @@ -3,6 +3,14 @@ import com.numberone.backend.domain.article.entity.Article; import com.numberone.backend.domain.article.repository.custom.ArticleRepositoryCustom; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; public interface ArticleRepository extends JpaRepository, ArticleRepositoryCustom { + + @Query("SELECT a FROM Article a LEFT JOIN FETCH a.articleOwner LEFT JOIN FETCH a.articleImages WHERE a.id = :articleId") + Optional
findByIdFetchJoin(@Param("articleId") Long articleId); + } From 6674aa7aa7acbc8318a64e8153a2fbaf79e6c825 Mon Sep 17 00:00:00 2001 From: Jaehyeon Date: Fri, 12 Apr 2024 00:37:52 +0900 Subject: [PATCH 5/7] [KAN-8] refactor: get articles no offset --- .../article/controller/ArticleController.java | 6 +- .../article/service/ArticleService.java | 57 ++++++----- .../dto/response/GetArticleListResponse.java | 98 +++++++++---------- .../custom/ArticleRepositoryCustom.java | 7 +- .../custom/ArticleRepositoryCustomImpl.java | 49 ++++++++-- .../custom/CommentRepositoryCustomImpl.java | 1 - 6 files changed, 124 insertions(+), 94 deletions(-) diff --git a/daepiro-api/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java b/daepiro-api/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java index 623120e9..a4acfab6 100644 --- a/daepiro-api/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java +++ b/daepiro-api/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java @@ -86,12 +86,14 @@ public ResponseEntity getArticleDetails(@PathVariable( """) @GetMapping - public ResponseEntity> getArticlePages( + public ResponseEntity> getArticleList( Pageable pageable, @ModelAttribute ArticleSearchParameterDto paramDto) { - return ResponseEntity.ok(articleService.getArticleListPaging(paramDto.toParameter(), pageable)); + Long userId = SecurityContextProvider.getAuthenticatedUserId(); + return ResponseEntity.ok(articleService.getArticleList(userId, paramDto.toParameter(), pageable)); } + @Operation(summary = "게시글에 댓글 작성하기", description = """ 게시글에 댓글을 작성하는 API 입니다. diff --git a/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java b/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java index d866b5d5..855e3b8d 100644 --- a/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java +++ b/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java @@ -86,7 +86,7 @@ public DeleteArticleResponse deleteArticle(Long articleId) { return DeleteArticleResponse.of(article); } - public GetArticleDetailResponse getArticleDetail(Long articleId, Long memberId){ + public GetArticleDetailResponse getArticleDetail(Long articleId, Long memberId) { Member member = memberRepository.findById(memberId) .orElseThrow(NotFoundMemberException::new); Article article = articleRepository.findByIdFetchJoin(articleId) @@ -104,34 +104,17 @@ public GetArticleDetailResponse getArticleDetail(Long articleId, Long memberId){ ); } - public Slice getArticleListPaging(ArticleSearchParameter param, Pageable pageable) { - long id = SecurityContextProvider.getAuthenticatedUserId(); - Member member = memberRepository.findById(id) + public Slice getArticleList(Long memberId, ArticleSearchParameter param, Pageable pageable) { + Member member = memberRepository.findById(memberId) .orElseThrow(NotFoundMemberException::new); - List memberLikedArticleIdList = articleLikeRepository.findByMember(member) - .stream().map(ArticleLike::getArticleId) - .toList(); - Slice slices = articleRepository.getArticlesNoOffSetPaging(param, pageable); - List content = slices.getContent().stream() - .peek(article -> { - updateArticleInfo(article, memberLikedArticleIdList); - }) - .toList(); + Slice slice = articleRepository.findAllNoOffset(param, pageable); + List content = slice.getContent(); - // todo: 리팩토링 ( slices 크기가 n 일 때, n * (3) 개의 쿼리가 발생하고 있음.) - return new SliceImpl<>(content, pageable, slices.hasNext()); - } - - private void updateArticleInfo(GetArticleListResponse articleInfo, List memberLikedArticleIdList) { - Long ownerId = articleInfo.getOwnerId(); - Long thumbNailImageUrlId = articleInfo.getThumbNailImageId(); + updateArticleCommentCount(content); + updateArticleLiked(member, content); - Optional owner = memberRepository.findById(ownerId); - Optional articleImage = articleImageRepository.findById(thumbNailImageUrlId); - Long commentCount = commentRepository.countAllByArticle(articleInfo.getId()); - - articleInfo.updateInfo(owner, articleImage, memberLikedArticleIdList, commentCount); + return new SliceImpl<>(content, pageable, slice.hasNext()); } @Transactional @@ -198,6 +181,28 @@ public ModifyArticleResponse modifyArticle(Long articleId, ModifyArticleRequest return ModifyArticleResponse.of(article, imageUrls, thumbNailImageUrl); } + private void updateArticleLiked(Member member, List content) { + Set likedArticleIds = articleLikeRepository.findByMember(member) + .stream().map(ArticleLike::getArticleId) + .collect(Collectors.toSet()); + content.forEach(e -> { + if (likedArticleIds.contains(e.getId())) { + e.setLiked(true); + } + }); + } + + private void updateArticleCommentCount(List content) { + Set articleIds = content.stream().map(GetArticleListResponse::getId).collect(Collectors.toSet()); + Map commentCountByArticleId = articleRepository.findCommentCountByArticleIdIn(articleIds); + content.forEach(e -> { + Long articleId = e.getId(); + if (commentCountByArticleId.containsKey(articleId)) { + e.setCommentCount(commentCountByArticleId.get(articleId)); + } + }); + } + private void updateArticleAddress(UploadArticleRequest request, Article article, Member owner) { String address = locationProvider.pos2address(request.latitude(), request.longitude()); article.updateAddress(address); @@ -223,7 +228,7 @@ private void validateLocation(Member member, String realLocation) { } } - private boolean getIsLikedArticle(Member member, Long articleId){ + private boolean getIsLikedArticle(Member member, Long articleId) { Set likedArticleIds = member.getArticleLikes() .stream() .map(ArticleLike::getArticleId) diff --git a/daepiro-core/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleListResponse.java b/daepiro-core/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleListResponse.java index 1c1d8004..89294864 100644 --- a/daepiro-core/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleListResponse.java +++ b/daepiro-core/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleListResponse.java @@ -1,87 +1,77 @@ package com.numberone.backend.domain.article.dto.response; import com.fasterxml.jackson.annotation.JsonFormat; -import com.numberone.backend.domain.article.entity.Article; import com.numberone.backend.domain.article.entity.ArticleStatus; import com.numberone.backend.domain.article.entity.ArticleTag; -import com.numberone.backend.domain.articleimage.entity.ArticleImage; -import com.numberone.backend.domain.member.entity.Member; import com.querydsl.core.annotations.QueryProjection; -import lombok.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; import java.time.LocalDateTime; -import java.util.List; import java.util.Optional; -@ToString -@Builder @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class GetArticleListResponse { // todo: record +public class GetArticleListResponse { - private ArticleTag tag; private Long id; + private ArticleTag tag; private String title; private String content; private String address; - @Setter + private ArticleStatus articleStatus; + private Integer articleLikeCount; + private String ownerNickName; private Long ownerId; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm", timezone = "Asia/Seoul") private LocalDateTime createdAt; - private ArticleStatus articleStatus; - @Setter + private String thumbNailImageUrl; private Long thumbNailImageId; - private Integer articleLikeCount; @Setter - private Long commentCount; - private Boolean isLiked; - + private long commentCount; + @Setter + private boolean isLiked; @QueryProjection - public GetArticleListResponse(Article article, Long ownerId, Long thumbNailImageId) { - this.tag = article.getArticleTag(); - this.id = article.getId(); - this.title = article.getTitle(); - this.content = article.getContent(); + public GetArticleListResponse(Long id, + ArticleTag tag, + String title, + String content, + String address, + ArticleStatus articleStatus, + Integer articleLikeCount, + String ownerNickName, + Long ownerId, + LocalDateTime createdAt, + String thumbNailImageUrl, + Long thumbNailImageId) { + this.id = id; + this.tag = tag; + this.title = title; + this.content = content; + this.address = address; + this.articleStatus = articleStatus; + this.articleLikeCount = articleLikeCount; + this.ownerNickName = ownerNickName; this.ownerId = ownerId; - this.createdAt = article.getCreatedAt(); - this.articleStatus = article.getArticleStatus(); + this.createdAt = createdAt; + this.thumbNailImageUrl = thumbNailImageUrl; this.thumbNailImageId = thumbNailImageId; - this.articleLikeCount = article.getLikeCount(); - String articleAddress = article.getAddress(); - if(!articleAddress.isEmpty()){ - String[] elements = articleAddress.split(" "); - switch (elements.length){ - case 3 -> this.address = elements[2]; - case 2 -> this.address = elements[1]; - case 1 -> this.address = elements[0]; - default -> this.address = ""; - } - } else { - this.address = ""; - } + this.address = Optional.ofNullable(address).map(this::getAddress).orElse(null); } - - // todo: 파라미터로 optional 을 받지말자. - public void updateInfo(Optional owner, - Optional articleImage, - List memberLikedArticleIdList, - Long commentCount ){ - owner.ifPresentOrElse( - o -> setOwnerNickName(o.getNickName()), - () -> setOwnerNickName("알 수 없는 사용자") - ); - articleImage.ifPresentOrElse( - image -> setThumbNailImageUrl(image.getImageUrl()), - () -> setThumbNailImageUrl("") - ); - this.isLiked = memberLikedArticleIdList.contains(id); - this.commentCount = commentCount; + private String getAddress(String address) { + if (!address.isEmpty()) { + String[] tokens = address.split(" "); + int length = tokens.length; + return length > 0 ? tokens[length - 1] : ""; + } + return ""; } - } diff --git a/daepiro-core/src/main/java/com/numberone/backend/domain/article/repository/custom/ArticleRepositoryCustom.java b/daepiro-core/src/main/java/com/numberone/backend/domain/article/repository/custom/ArticleRepositoryCustom.java index 298478db..9f6f84b4 100644 --- a/daepiro-core/src/main/java/com/numberone/backend/domain/article/repository/custom/ArticleRepositoryCustom.java +++ b/daepiro-core/src/main/java/com/numberone/backend/domain/article/repository/custom/ArticleRepositoryCustom.java @@ -5,6 +5,11 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; +import java.util.Map; +import java.util.Set; + public interface ArticleRepositoryCustom { - Slice getArticlesNoOffSetPaging(ArticleSearchParameter param, Pageable pageable); + Slice findAllNoOffset(ArticleSearchParameter param, Pageable pageable); + + Map findCommentCountByArticleIdIn(Set articleIds); } diff --git a/daepiro-core/src/main/java/com/numberone/backend/domain/article/repository/custom/ArticleRepositoryCustomImpl.java b/daepiro-core/src/main/java/com/numberone/backend/domain/article/repository/custom/ArticleRepositoryCustomImpl.java index 68a99486..e3ab8ff9 100644 --- a/daepiro-core/src/main/java/com/numberone/backend/domain/article/repository/custom/ArticleRepositoryCustomImpl.java +++ b/daepiro-core/src/main/java/com/numberone/backend/domain/article/repository/custom/ArticleRepositoryCustomImpl.java @@ -5,32 +5,45 @@ import com.numberone.backend.domain.article.dto.response.QGetArticleListResponse; import com.numberone.backend.domain.article.entity.ArticleStatus; import com.numberone.backend.domain.article.entity.ArticleTag; +import com.querydsl.core.Tuple; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; -import jakarta.persistence.EntityManager; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.SliceImpl; -import java.util.List; -import java.util.Objects; +import java.util.*; import static com.numberone.backend.domain.article.entity.QArticle.article; +import static com.numberone.backend.domain.articleimage.entity.QArticleImage.articleImage; +import static com.numberone.backend.domain.comment.entity.QCommentEntity.commentEntity; +import static com.numberone.backend.domain.member.entity.QMember.member; @RequiredArgsConstructor public class ArticleRepositoryCustomImpl implements ArticleRepositoryCustom { + private final JPAQueryFactory queryFactory; @Override - public Slice getArticlesNoOffSetPaging(ArticleSearchParameter param, Pageable pageable) { - List result = queryFactory.select( - new QGetArticleListResponse( - article, - article.articleOwnerId, - article.thumbNailImageUrlId + public Slice findAllNoOffset(ArticleSearchParameter param, Pageable pageable) { + List content = queryFactory.select(new QGetArticleListResponse( + article.id, + article.articleTag, + article.title, + article.content, + article.address, + article.articleStatus, + article.likeCount, + member.nickName, + member.id, + article.createdAt, + articleImage.imageUrl, + articleImage.id )) .from(article) + .leftJoin(member).on(article.articleOwner.id.eq(member.id)) + .leftJoin(articleImage).on(article.thumbNailImageUrlId.eq(articleImage.id)) .where( ltArticleId(param.getLastArticleId()), checkTagCondition(param.getTag()), @@ -41,9 +54,24 @@ public Slice getArticlesNoOffSetPaging(ArticleSearchPara .orderBy(article.id.desc()) .fetch(); - return new SliceImpl<>(result, pageable, checkLastPage(pageable, result)); + return new SliceImpl<>(content, pageable, checkLastPage(pageable, content)); + } + + @Override + public Map findCommentCountByArticleIdIn(Set articleIds) { + List tuples = queryFactory.select(article.id, commentEntity.count()) + .from(article) + .leftJoin(article.comments, commentEntity) + .fetch(); + + HashMap result = new HashMap<>(); + for (Tuple tuple : tuples) { + result.put(tuple.get(article.id), tuple.get(commentEntity.count())); + } + return result; } + private BooleanExpression checkTagCondition(ArticleTag tag) { if (Objects.isNull(tag)) { return null; @@ -69,4 +97,5 @@ private boolean checkLastPage(Pageable pageable, List re return hasNext; } + } diff --git a/daepiro-core/src/main/java/com/numberone/backend/domain/comment/repository/custom/CommentRepositoryCustomImpl.java b/daepiro-core/src/main/java/com/numberone/backend/domain/comment/repository/custom/CommentRepositoryCustomImpl.java index 35e52ce8..b5e3ece0 100644 --- a/daepiro-core/src/main/java/com/numberone/backend/domain/comment/repository/custom/CommentRepositoryCustomImpl.java +++ b/daepiro-core/src/main/java/com/numberone/backend/domain/comment/repository/custom/CommentRepositoryCustomImpl.java @@ -3,7 +3,6 @@ import com.numberone.backend.domain.comment.dto.GetCommentDto; import com.numberone.backend.domain.comment.dto.QGetCommentDto; import com.querydsl.jpa.impl.JPAQueryFactory; -import jakarta.persistence.EntityManager; import lombok.RequiredArgsConstructor; import java.util.List; From 178ac796c9306989cc6cffb858afe15a925cbae7 Mon Sep 17 00:00:00 2001 From: Jaehyeon Date: Fri, 12 Apr 2024 00:55:16 +0900 Subject: [PATCH 6/7] [KAN-8] refactor: modify article --- .../article/controller/ArticleController.java | 2 - .../dto/request/ModifyArticleRequest.java | 44 ++++++------------ .../dto/request/UploadArticleRequest.java | 3 ++ .../dto/response/ModifyArticleResponse.java | 46 +++++++++---------- .../article/service/ArticleService.java | 43 +++++------------ .../domain/article/entity/Article.java | 2 +- 6 files changed, 52 insertions(+), 88 deletions(-) diff --git a/daepiro-api/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java b/daepiro-api/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java index a4acfab6..71fc1d8a 100644 --- a/daepiro-api/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java +++ b/daepiro-api/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java @@ -124,6 +124,4 @@ public ResponseEntity modifyArticle( return ResponseEntity.ok(articleService.modifyArticle(articleId, request)); } - // todo: 게시글 신고 기능 - } diff --git a/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/request/ModifyArticleRequest.java b/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/request/ModifyArticleRequest.java index 3348de84..a8e61c3c 100644 --- a/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/request/ModifyArticleRequest.java +++ b/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/request/ModifyArticleRequest.java @@ -2,37 +2,23 @@ import com.numberone.backend.domain.article.entity.ArticleTag; import jakarta.validation.constraints.NotNull; -import lombok.*; import org.springframework.web.multipart.MultipartFile; import java.util.List; -@ToString -@Builder -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class ModifyArticleRequest { - - // 글 관련 - @NotNull(message = "글 제목은 null 일 수 없습니다.") - private String title; // 제목 - - @NotNull(message = "내용은 null 일 수 없습니다.") - private String content; // 내용 - - @NotNull(message = """ - 게시글의 태그를 하나 선택해주세요. - - LIFE(일상), FRAUD(사기), SAFETY(안전), REPORT(제보) - """) - private ArticleTag articleTag; // 게시글 태그 - - // 이미지 관련 - private List imageList; // 이미지 리스트 - private Long thumbNailImageIdx; // 썸네일 이미지의 순서 (0,1,2,...) - - private Double longitude; - private Double latitude; - +public record ModifyArticleRequest( + @NotNull(message = "글 제목은 null 일 수 없습니다.") + String title, + @NotNull(message = "내용은 null 일 수 없습니다.") + String content, + @NotNull(message = "게시글의 태그를 하나 선택해주세요. LIFE(일상), FRAUD(사기), SAFETY(안전), REPORT(제보)") + ArticleTag articleTag, + List imageList, + Long thumbNailImageIdx, + Double longitude, + Double latitude +) { + public boolean hasImage() { + return imageList != null && !imageList.isEmpty(); + } } diff --git a/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/request/UploadArticleRequest.java b/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/request/UploadArticleRequest.java index 46785c5b..8eea5ac4 100644 --- a/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/request/UploadArticleRequest.java +++ b/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/request/UploadArticleRequest.java @@ -22,4 +22,7 @@ public record UploadArticleRequest( public boolean isValidPosition(){ return (longitude != null) && (latitude != null); } + public boolean hasImage() { + return imageList != null && !imageList.isEmpty(); + } } diff --git a/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/response/ModifyArticleResponse.java b/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/response/ModifyArticleResponse.java index 3595e1fb..b46b476e 100644 --- a/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/response/ModifyArticleResponse.java +++ b/daepiro-api/src/main/java/com/numberone/backend/domain/article/dto/response/ModifyArticleResponse.java @@ -2,40 +2,38 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.numberone.backend.domain.article.entity.Article; -import lombok.*; +import com.numberone.backend.domain.articleimage.entity.ArticleImage; +import lombok.Builder; import java.time.LocalDateTime; import java.util.List; -@ToString @Builder -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class ModifyArticleResponse { - - private Long articleId; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm", timezone = "Asia/Seoul") - private LocalDateTime createdAt; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm", timezone = "Asia/Seoul") - private LocalDateTime modifiedAt; - - // 이미지 관련 - private List imageUrls; - private String thumbNailImageUrl; - - // 작성자 주소 - private String address; - - public static ModifyArticleResponse of(Article article, List imageUrls, String thumbNailImageUrl){ +public record ModifyArticleResponse( + Long articleId, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm", timezone = "Asia/Seoul") + LocalDateTime createdAt, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm", timezone = "Asia/Seoul") + LocalDateTime modifiedAt, + List imageUrls, + String thumbNailImageUrl, + String address +) { + public static ModifyArticleResponse from(Article article) { return ModifyArticleResponse.builder() .articleId(article.getId()) .createdAt(article.getCreatedAt()) - .modifiedAt(article.getModifiedAt()) - .imageUrls(imageUrls) - .thumbNailImageUrl(thumbNailImageUrl) .address(article.getAddress()) .build(); } + public static ModifyArticleResponse ofImages(Article article, List images) { + return ModifyArticleResponse.builder() + .articleId(article.getId()) + .createdAt(article.getCreatedAt()) + .imageUrls(images.stream().map(ArticleImage::getImageUrl).toList()) + .thumbNailImageUrl(images.isEmpty() ? "" : images.get(0).getImageUrl()) + .address(article.getAddress()) + .build(); + } } diff --git a/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java b/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java index 855e3b8d..3a6c9867 100644 --- a/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java +++ b/daepiro-api/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java @@ -37,7 +37,10 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; @Slf4j @@ -69,7 +72,7 @@ public UploadArticleResponse uploadArticle(UploadArticleRequest request, Long us updateArticleAddress(request, article, owner); } - if (request.imageList() != null) { + if (request.hasImage()) { List articleImages = uploadImages(article, request.imageList()); article.updateThumbNailImageUrlId(articleImages.get(0).getId()); return UploadArticleResponse.ofImages(article, articleImages); @@ -144,41 +147,17 @@ public CreateCommentResponse createComment(Long articleId, CreateCommentRequest @Transactional public ModifyArticleResponse modifyArticle(Long articleId, ModifyArticleRequest request) { - long id = SecurityContextProvider.getAuthenticatedUserId(); - Member member = memberRepository.findById(id) - .orElseThrow(NotFoundMemberException::new); Article article = articleRepository.findById(articleId) .orElseThrow(NotFoundArticleException::new); + article.modify(request.title(), request.content(), request.articleTag()); - article.modifyArticle(request.getTitle(), request.getContent(), request.getArticleTag()); - - - List articleImages = new ArrayList<>(); - List imageUrls = new ArrayList<>(); - String thumbNailImageUrl = ""; - Long thumbNailImageId = 1L; - if (!Objects.isNull(request.getImageList())) { - // todo: refactoring - List imageList = request.getImageList(); - - for (int i = 0; i < imageList.size(); i++) { - String imageUrl = s3Provider.uploadImage(imageList.get(i)); - imageUrls.add(imageUrl); - - ArticleImage savedArticleImage = articleImageRepository.save( - new ArticleImage(article, imageUrl) - ); - articleImages.add(savedArticleImage); - if (Objects.equals(i, request.getThumbNailImageIdx())) { - thumbNailImageUrl = imageUrl; - thumbNailImageId = savedArticleImage.getId(); - } - - } - article.updateArticleImage(articleImages, thumbNailImageId); + if (request.hasImage()) { + List articleImages = uploadImages(article, request.imageList()); + article.updateThumbNailImageUrlId(articleImages.get(0).getId()); + return ModifyArticleResponse.ofImages(article, articleImages); } - return ModifyArticleResponse.of(article, imageUrls, thumbNailImageUrl); + return ModifyArticleResponse.from(article); } private void updateArticleLiked(Member member, List content) { diff --git a/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java b/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java index 44cd57ca..6840fda3 100644 --- a/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java +++ b/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java @@ -93,7 +93,7 @@ public void updateThumbNailImageUrlId(Long thumbNailImageUrlId){ this.thumbNailImageUrlId = thumbNailImageUrlId; } - public void modifyArticle(String title, String content, ArticleTag tag) { + public void modify(String title, String content, ArticleTag tag) { this.title = title; this.content = content; this.articleTag = tag; From f51af54a6f3964fd42c2aa2d8a239dbe60d31efb Mon Sep 17 00:00:00 2001 From: Jaehyeon Date: Fri, 12 Apr 2024 23:27:45 +0900 Subject: [PATCH 7/7] [KAN-8] refactor: removed unused --- .../numberone/backend/domain/like/service/LikeService.java | 2 -- .../com/numberone/backend/domain/article/entity/Article.java | 5 ----- .../backend/domain/article/entity/ArticleStatus.java | 4 ---- .../numberone/backend/domain/article/entity/ArticleTag.java | 4 ---- .../articleimage/repository/ArticleImageRepository.java | 5 ----- .../numberone/backend/domain/comment/dto/GetCommentDto.java | 1 - 6 files changed, 21 deletions(-) diff --git a/daepiro-api/src/main/java/com/numberone/backend/domain/like/service/LikeService.java b/daepiro-api/src/main/java/com/numberone/backend/domain/like/service/LikeService.java index 99c02e4e..2a558396 100644 --- a/daepiro-api/src/main/java/com/numberone/backend/domain/like/service/LikeService.java +++ b/daepiro-api/src/main/java/com/numberone/backend/domain/like/service/LikeService.java @@ -41,8 +41,6 @@ public class LikeService { private final FcmMessageProvider fcmMessageProvider; private final NotificationRepository notificationRepository; - private final Integer BEST_ARTICLE_LIKE_COUNT = 20; - @Transactional public Integer increaseArticleLike(Long articleId) { long principal = SecurityContextProvider.getAuthenticatedUserId(); diff --git a/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java b/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java index 6840fda3..876b26c4 100644 --- a/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java +++ b/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/Article.java @@ -80,11 +80,6 @@ public static Article of(String title, String content, Member owner, ArticleTag .build(); } - public void updateArticleImage(List images, Long thumbNailImageUrlId) { - this.articleImages = images; - this.thumbNailImageUrlId = thumbNailImageUrlId; - } - public void updateArticleStatus(ArticleStatus status) { this.articleStatus = status; } diff --git a/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/ArticleStatus.java b/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/ArticleStatus.java index 97e66db9..cb325d9b 100644 --- a/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/ArticleStatus.java +++ b/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/ArticleStatus.java @@ -1,10 +1,6 @@ package com.numberone.backend.domain.article.entity; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor public enum ArticleStatus { ACTIVATED, DELETED; - private String value; } diff --git a/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/ArticleTag.java b/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/ArticleTag.java index f5215017..de337fe1 100644 --- a/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/ArticleTag.java +++ b/daepiro-core/src/main/java/com/numberone/backend/domain/article/entity/ArticleTag.java @@ -1,12 +1,8 @@ package com.numberone.backend.domain.article.entity; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor public enum ArticleTag { LIFE, // 일상 TRAFFIC, // 교통 SAFETY, // 치안 NONE; // 기타 - private String value; } diff --git a/daepiro-core/src/main/java/com/numberone/backend/domain/articleimage/repository/ArticleImageRepository.java b/daepiro-core/src/main/java/com/numberone/backend/domain/articleimage/repository/ArticleImageRepository.java index 08bf579b..5a835d7f 100644 --- a/daepiro-core/src/main/java/com/numberone/backend/domain/articleimage/repository/ArticleImageRepository.java +++ b/daepiro-core/src/main/java/com/numberone/backend/domain/articleimage/repository/ArticleImageRepository.java @@ -1,12 +1,7 @@ package com.numberone.backend.domain.articleimage.repository; -import com.numberone.backend.domain.article.entity.Article; import com.numberone.backend.domain.articleimage.entity.ArticleImage; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.List; - public interface ArticleImageRepository extends JpaRepository { - - List findByArticle(Article article); } diff --git a/daepiro-core/src/main/java/com/numberone/backend/domain/comment/dto/GetCommentDto.java b/daepiro-core/src/main/java/com/numberone/backend/domain/comment/dto/GetCommentDto.java index 73a9a2ea..d060180e 100644 --- a/daepiro-core/src/main/java/com/numberone/backend/domain/comment/dto/GetCommentDto.java +++ b/daepiro-core/src/main/java/com/numberone/backend/domain/comment/dto/GetCommentDto.java @@ -33,7 +33,6 @@ public class GetCommentDto { private String authorProfileImageUrl; private boolean isLiked; - @QueryProjection public GetCommentDto(CommentEntity comment){ if(!Objects.isNull(comment.getParent())){