From da90752c230bf1bd3b6e42e8fe0eaa94e24f558d Mon Sep 17 00:00:00 2001 From: Jaehyeon Date: Sun, 12 Nov 2023 14:40:20 +0900 Subject: [PATCH] Feat(#54): resolved rebase conflicts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Feat(#54): 커뮤니티 API 기초 구현 Feat(#54): 게시글 상세 조회 시 태그 포함 Feat(#54): todo 작성 Feat(#54): 게시글 생성 시 이미지 업로드 bug fix Feat(#54): 게시글 상세 조회 시 좋아요 여부와 작성자 프로필 사진 url 반환 Feat(#54): 게시글 상세 조회 시 좋아요 여부와 작성자 프로필 사진 url 반환 Feat(#54): 게시글 리스트 조회 시, 좋아요 누른 여부를 포함 Feat(#54): todo 주석 제거 및 더미데이터 제거 Feat(#54): 썸네일 이미지 관련 요구사항 적용 - 첫 이미지를 썸네일 이미지로 지정 Feat(#54): 게시물 카테고리 수정 - 일상, 교통, 치안, 기타 Hotfix: npe fix Feat(#54): rename field for resolve rebase conflict Docs(#54): update todo Fix(#54): 좋아요 버그 수정 Fix(#54): 스웨거 jwt 버그 수정 Feat(#54): 게시글/댓글 좋아요 api 구현 Feat(#54): 알림 저장 시 닉네임도 저장 Feat(#54): 좋아요 관련 예외 로직 Feat(#54): 푸시 알림 태그 구분 Feat(#54): 푸시 알림 발송 시 db 에 저장 Feat(#54): 게시글 좋아요 api (wip) Feat(#54): 게시글 좋아요 api (wip) Feat(#54): improvement community apis - 게시글 작성 시, 작성자의 주소 로깅 - 게시글에 댓글 등록 시 푸시 알림 전송 Feat(#54): improvement community apis - 게시글 작성 시, 작성자의 주소 로깅 - 게시글에 댓글 등록 시 푸시 알림 전송 Feat(#54): catch exception Docs(#54): todo 작성 Feat(#54): 게시글 수정하기 api 구현 및 게시글 삭제 http method 를 patch 로 변경 Feat(#54): 대댓글 계층형 조회 api 구현 Feat(#54): 대댓글 작성 api 구현 Feat(#54): 게시글에 댓글 작성하기 api 구현 Feat(#54): 게시글 조회 no offset paging api 구현 Feat(#54): removed test api for fcm Feat(#54): removed test api for fcm Feat(#54): 게시글 상세 조회 시, 제목 및 내용 포함 Feat(#54): 게시글 상세 조회 및 삭제 API 구현 Docs(#54): 게시글 작성 API Feat(#54): removed Qclass Feat(#54): 게시글 활성화 상태 필드 추가 Feat(#54): 게시글 작성 API 구현 Feat(#54): rename CommunityParticipant - to ArticleParticipant Feat(#54): 게시글, 댓글, 커뮤니티 참여자 레포지토리 구현 Feat(#54): relocate s3Provider Docs(#54): 프로필 사진 업로드 api - swagger api summary, description Feat(#54): 커뮤니티 엔티티 설계 - Article: 동네생활 게시글 - ArticleTag: 게시글 태그 - CommentEntity: 댓글 - CommunityParticipant: 게시글 참여자 Feat(#54): 회원 프로필 사진 업로드 API 구현 Feat(#54): 회원 엔티티 필드 추가 - 프로필 사진, 닉네임, 실명, fcm 토큰 Feat(#54): implement SecurityContextProvider - for parsing principal from jwt token Feat(#54): member extends baseTimeEntity --- .../article/controller/ArticleController.java | 6 ++-- .../dto/request/UploadArticleRequest.java | 1 - .../response/GetArticleDetailResponse.java | 30 +++++++++++----- .../dto/response/GetArticleListResponse.java | 6 ++-- .../dto/response/ModifyArticleResponse.java | 4 +-- .../dto/response/UploadArticleResponse.java | 2 +- .../domain/article/entity/Article.java | 2 +- .../domain/article/entity/ArticleTag.java | 4 +-- .../article/service/ArticleService.java | 35 ++++++++++++++----- .../domain/comment/entity/CommentEntity.java | 2 +- .../member/controller/MemberController.java | 1 - .../domain/member/service/MemberService.java | 2 +- .../fcm/service/FcmMessageProvider.java | 1 + 13 files changed, 65 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java b/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java index 9bcb9151..071f59c7 100644 --- a/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java +++ b/src/main/java/com/numberone/backend/domain/article/controller/ArticleController.java @@ -34,16 +34,16 @@ public class ArticleController { 1. title 은 글 제목 입니다 (not null) 2. content 는 글 내용 입니다 (not null) - 3. articleTag 는 게시글 태그 입니다. LIFE(일상), FRAUD(사기), SAFETY(안전), REPORT(제보) + 3. articleTag 는 게시글 태그 입니다. LIFE(일상), TRAFFIC(교통), SAFETY(치안), NONE(기타) -> 영어로 보내주세요 4. imageList 는 이미지 (MultiPart) 리스트 입니다. - 5. thumbNailImageIdx 는 썸네일 이미지의 인덱스 입니다. (0,1,2, ... + 5. imageList 의 첫 원소를 썸네일로 지정합니다. imageList 에 이미지를 담아서 보내는 경우, idx 에 따라서 썸네일 이미지를 결정합니다. """) @PostMapping - public ResponseEntity uploadArticle(@RequestBody @Valid UploadArticleRequest request) { + public ResponseEntity uploadArticle(@ModelAttribute @Valid UploadArticleRequest request) { return ResponseEntity.created(URI.create("/api/articles")) .body(articleService.uploadArticle(request)); } diff --git a/src/main/java/com/numberone/backend/domain/article/dto/request/UploadArticleRequest.java b/src/main/java/com/numberone/backend/domain/article/dto/request/UploadArticleRequest.java index 04b76643..9bcea7c0 100644 --- a/src/main/java/com/numberone/backend/domain/article/dto/request/UploadArticleRequest.java +++ b/src/main/java/com/numberone/backend/domain/article/dto/request/UploadArticleRequest.java @@ -30,7 +30,6 @@ public class UploadArticleRequest { // 이미지 관련 private List imageList; // 이미지 리스트 - private Long thumbNailImageIdx; // 썸네일 이미지의 순서 (0,1,2,...) private Double longitude; private Double latitude; diff --git a/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java b/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java index cc356667..ec940a48 100644 --- a/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java +++ b/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java @@ -1,6 +1,8 @@ package com.numberone.backend.domain.article.dto.response; 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.member.entity.Member; import lombok.*; @@ -22,18 +24,26 @@ public class GetArticleDetailResponse { private LocalDateTime modifiedAt; private String title; private String content; + private boolean isLiked; + private ArticleTag articleTag; // 작성자 관련 - private String memberName; - private String memberNickName; - private String address; // todo: 더미 데이터 + private String ownerName; + private String ownerNickName; + private String address; private Long ownerMemberId; + private String ownerProfileImageUrl; // 이미지 관련 private List imageUrls; private String thumbNailImageUrl; - public static GetArticleDetailResponse of(Article article, List imageUrls, String thumbNailImageUrl, Member member){ + public static GetArticleDetailResponse of( + Article article, + List imageUrls, + String thumbNailImageUrl, + Member owner, + List memberLikedArticleList) { return GetArticleDetailResponse.builder() .articleId(article.getId()) .title(article.getTitle()) @@ -45,13 +55,17 @@ public static GetArticleDetailResponse of(Article article, List imageUrl ) .createdAt(article.getCreatedAt()) .modifiedAt(article.getModifiedAt()) - .ownerMemberId(member.getId()) - .memberName(member.getRealName()) - .memberNickName(member.getNickName()) + .ownerMemberId(owner.getId()) + .ownerName(owner.getRealName()) + .ownerNickName(owner.getNickName()) .imageUrls(imageUrls) .thumbNailImageUrl(thumbNailImageUrl) - .address("서울시 광진구 자양동") // 교체 + .address(article.getAddress()) + .ownerProfileImageUrl(owner.getProfileImageUrl()) + .isLiked(memberLikedArticleList.contains(article.getId())) + .articleTag(article.getArticleTag()) .build(); } + } diff --git a/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleListResponse.java b/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleListResponse.java index 6b88af6d..cfbaa595 100644 --- a/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleListResponse.java +++ b/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleListResponse.java @@ -9,6 +9,7 @@ import lombok.*; import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; @ToString @@ -32,6 +33,7 @@ public class GetArticleListResponse { private Integer articleLikeCount; private Integer commentCount; + private Boolean isLiked; @QueryProjection @@ -57,7 +59,7 @@ public void setThumbNailImageUrl(String thumbNailImageUrl){ this.thumbNailImageUrl = thumbNailImageUrl; } - public void updateInfo(Optional owner, Optional articleImage){ + public void updateInfo(Optional owner, Optional articleImage, List memberLikedArticleIdList){ owner.ifPresentOrElse( o -> setOwnerNickName(o.getNickName()), () -> setOwnerNickName("알 수 없는 사용자") @@ -66,7 +68,7 @@ public void updateInfo(Optional owner, Optional articleIma image -> setThumbNailImageUrl(image.getImageUrl()), () -> setThumbNailImageUrl("") ); + this.isLiked = memberLikedArticleIdList.contains(id); } - } diff --git a/src/main/java/com/numberone/backend/domain/article/dto/response/ModifyArticleResponse.java b/src/main/java/com/numberone/backend/domain/article/dto/response/ModifyArticleResponse.java index 88a57536..62ce0b63 100644 --- a/src/main/java/com/numberone/backend/domain/article/dto/response/ModifyArticleResponse.java +++ b/src/main/java/com/numberone/backend/domain/article/dto/response/ModifyArticleResponse.java @@ -25,7 +25,7 @@ public class ModifyArticleResponse { private String thumbNailImageUrl; // 작성자 주소 - private String address; // todo: 더미 데이터 + private String address; public static ModifyArticleResponse of(Article article, List imageUrls, String thumbNailImageUrl){ return ModifyArticleResponse.builder() @@ -34,7 +34,7 @@ public static ModifyArticleResponse of(Article article, List imageUrls, .modifiedAt(article.getModifiedAt()) .imageUrls(imageUrls) .thumbNailImageUrl(thumbNailImageUrl) - .address("서울시 광진구 자양동") + .address(article.getAddress()) .build(); } diff --git a/src/main/java/com/numberone/backend/domain/article/dto/response/UploadArticleResponse.java b/src/main/java/com/numberone/backend/domain/article/dto/response/UploadArticleResponse.java index fef61aa5..c18a13c7 100644 --- a/src/main/java/com/numberone/backend/domain/article/dto/response/UploadArticleResponse.java +++ b/src/main/java/com/numberone/backend/domain/article/dto/response/UploadArticleResponse.java @@ -21,7 +21,7 @@ public class UploadArticleResponse { private String thumbNailImageUrl; // 작성자 주소 - private String address; // todo: 더미 데이터 + private String address; public static UploadArticleResponse of(Article article, List imageUrls, String thumbNailImageUrl){ return UploadArticleResponse.builder() diff --git a/src/main/java/com/numberone/backend/domain/article/entity/Article.java b/src/main/java/com/numberone/backend/domain/article/entity/Article.java index c0bea662..aec9aee7 100644 --- a/src/main/java/com/numberone/backend/domain/article/entity/Article.java +++ b/src/main/java/com/numberone/backend/domain/article/entity/Article.java @@ -56,7 +56,7 @@ public class Article extends BaseTimeEntity { @ColumnDefault("0") @Comment("게시글 좋아요 개수") - private Integer likeCount; // todo: 동시성 처리 + private Integer likeCount; @ColumnDefault("0") @Comment("게시글에 달린 댓글 개수") diff --git a/src/main/java/com/numberone/backend/domain/article/entity/ArticleTag.java b/src/main/java/com/numberone/backend/domain/article/entity/ArticleTag.java index 1ebf82a4..f5215017 100644 --- a/src/main/java/com/numberone/backend/domain/article/entity/ArticleTag.java +++ b/src/main/java/com/numberone/backend/domain/article/entity/ArticleTag.java @@ -5,8 +5,8 @@ @RequiredArgsConstructor public enum ArticleTag { LIFE, // 일상 - FRAUD, // 사기 + TRAFFIC, // 교통 SAFETY, // 치안 - REPORT; // 제보 + NONE; // 기타 private String value; } diff --git a/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java b/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java index c2c5d312..97ca9ece 100644 --- a/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java +++ b/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java @@ -14,6 +14,8 @@ import com.numberone.backend.domain.comment.dto.response.CreateCommentResponse; import com.numberone.backend.domain.comment.entity.CommentEntity; import com.numberone.backend.domain.comment.repository.CommentRepository; +import com.numberone.backend.domain.like.entity.ArticleLike; +import com.numberone.backend.domain.like.repository.ArticleLikeRepository; import com.numberone.backend.domain.member.entity.Member; import com.numberone.backend.domain.member.repository.MemberRepository; import com.numberone.backend.domain.notification.entity.NotificationTag; @@ -50,6 +52,7 @@ public class ArticleService { private final ArticleParticipantRepository articleParticipantRepository; private final ArticleImageRepository articleImageRepository; private final CommentRepository commentRepository; + private final ArticleLikeRepository articleLikeRepository; private final S3Provider s3Provider; private final LocationProvider locationProvider; private final FcmMessageProvider fcmMessageProvider; @@ -68,6 +71,7 @@ public UploadArticleResponse uploadArticle(UploadArticleRequest request) { owner.getId(), request.getArticleTag()) ); + articleParticipantRepository.save( new ArticleParticipant(article, owner) ); @@ -89,7 +93,7 @@ public UploadArticleResponse uploadArticle(UploadArticleRequest request) { new ArticleImage(article, imageUrl) ); articleImages.add(savedArticleImage); - if (Objects.equals(i, request.getThumbNailImageIdx())) { + if (i == 0) { thumbNailImageUrl = imageUrl; thumbNailImageId = savedArticleImage.getId(); } @@ -104,6 +108,7 @@ public UploadArticleResponse uploadArticle(UploadArticleRequest request) { Double latitude = request.getLatitude(); Double longitude = request.getLongitude(); if (latitude != null && longitude != null) { + // 주소가 null 이 아닌 경우에만 api 요청하여 update String address = locationProvider.pos2address(request.getLatitude(), request.getLongitude()); article.updateAddress(address); } @@ -122,10 +127,12 @@ public DeleteArticleResponse deleteArticle(Long articleId) { public GetArticleDetailResponse getArticleDetail(Long articleId) { String principal = SecurityContextProvider.getAuthenticatedUserEmail(); - Member owner = memberRepository.findByEmail(principal) + Member member = memberRepository.findByEmail(principal) // 회원 .orElseThrow(NotFoundMemberException::new); Article article = articleRepository.findById(articleId) .orElseThrow(NotFoundArticleException::new); + Member owner = memberRepository.findById(article.getArticleOwnerId()) // 작성자 + .orElseThrow(NotFoundMemberException::new); List imageUrls = articleImageRepository.findByArticle(article) .stream() @@ -140,26 +147,38 @@ public GetArticleDetailResponse getArticleDetail(Long articleId) { thumbNailImageUrl = thumbNailImage.get().getImageUrl(); } - return GetArticleDetailResponse.of(article, imageUrls, thumbNailImageUrl, owner); + // 내가 좋아요 한 게시글의 ID 리스트 + List memberLikedArticleIdList = articleLikeRepository.findByMember(member) + .stream().map(ArticleLike::getArticleId) + .toList(); + + return GetArticleDetailResponse.of(article, imageUrls, thumbNailImageUrl, owner, memberLikedArticleIdList); } public Slice getArticleListPaging(ArticleSearchParameter param, Pageable pageable) { + String principal = SecurityContextProvider.getAuthenticatedUserEmail(); + Member member = memberRepository.findByEmail(principal) + .orElseThrow(NotFoundMemberException::new); + List memberLikedArticleIdList = articleLikeRepository.findByMember(member) + .stream().map(ArticleLike::getArticleId) + .toList(); return new SliceImpl<>( articleRepository.getArticlesNoOffSetPaging(param, pageable) .stream() - .peek(this::updateArticleInfo) - .toList() - ); + .peek(article -> { + updateArticleInfo(article, memberLikedArticleIdList); + }) + .toList()); } - public void updateArticleInfo(GetArticleListResponse articleInfo) { + public void updateArticleInfo(GetArticleListResponse articleInfo, List memberLikedArticleIdList) { Long ownerId = articleInfo.getOwnerId(); Long thumbNailImageUrlId = articleInfo.getThumbNailImageId(); Optional owner = memberRepository.findById(ownerId); Optional articleImage = articleImageRepository.findById(thumbNailImageUrlId); - articleInfo.updateInfo(owner, articleImage); + articleInfo.updateInfo(owner, articleImage, memberLikedArticleIdList); } @Transactional diff --git a/src/main/java/com/numberone/backend/domain/comment/entity/CommentEntity.java b/src/main/java/com/numberone/backend/domain/comment/entity/CommentEntity.java index 91fb1299..24978a86 100644 --- a/src/main/java/com/numberone/backend/domain/comment/entity/CommentEntity.java +++ b/src/main/java/com/numberone/backend/domain/comment/entity/CommentEntity.java @@ -31,7 +31,7 @@ public class CommentEntity extends BaseTimeEntity { private Integer depth; @Comment("댓글 좋아요 개수") - private Integer likeCount; // todo: 동시성 처리 + private Integer likeCount; @Comment("댓글 내용") private String content; diff --git a/src/main/java/com/numberone/backend/domain/member/controller/MemberController.java b/src/main/java/com/numberone/backend/domain/member/controller/MemberController.java index 6acfc70b..64e7cea8 100644 --- a/src/main/java/com/numberone/backend/domain/member/controller/MemberController.java +++ b/src/main/java/com/numberone/backend/domain/member/controller/MemberController.java @@ -29,7 +29,6 @@ @Tag(name = "members", description = "사용자 관련 API") @RestController @RequiredArgsConstructor -@RequestMapping("/api/members") public class MemberController { private final MemberService memberService; diff --git a/src/main/java/com/numberone/backend/domain/member/service/MemberService.java b/src/main/java/com/numberone/backend/domain/member/service/MemberService.java index cab4b378..7107ebc6 100644 --- a/src/main/java/com/numberone/backend/domain/member/service/MemberService.java +++ b/src/main/java/com/numberone/backend/domain/member/service/MemberService.java @@ -46,8 +46,8 @@ public void create(String email, String realName) { public void initMemberData(String email, OnboardingRequest onboardingRequest) { Member member = memberRepository.findByEmail(email) .orElseThrow(NotFoundMemberException::new); - member.setOnboardingData(onboardingRequest.getNickname(), onboardingRequest.getFcmToken()); notificationDisasterRepository.deleteAllByMemberId(member.getId()); + member.setOnboardingData(onboardingRequest.getNickname(), onboardingRequest.getFcmToken()); notificationRegionRepository.deleteAllByMemberId(member.getId()); for (OnboardingAddress address : onboardingRequest.getAddresses()) { notificationRegionRepository.save(NotificationRegion.of( diff --git a/src/main/java/com/numberone/backend/support/fcm/service/FcmMessageProvider.java b/src/main/java/com/numberone/backend/support/fcm/service/FcmMessageProvider.java index 5bfff921..62131f2e 100644 --- a/src/main/java/com/numberone/backend/support/fcm/service/FcmMessageProvider.java +++ b/src/main/java/com/numberone/backend/support/fcm/service/FcmMessageProvider.java @@ -27,6 +27,7 @@ public void sendFcm(Member member, NotificationMessage notificationMessage, Noti String token = member.getFcmToken(); if (Objects.isNull(token)){ log.error("해당 회원의 fcm 토큰이 존재하지 않아, 푸시알람을 전송할 수 없습니다."); + // todo : 예외 핸들링 return; }