diff --git a/src/main/java/com/api/ttoklip/domain/member/domain/Member.java b/src/main/java/com/api/ttoklip/domain/member/domain/Member.java index ca6a7f90..9584cdea 100644 --- a/src/main/java/com/api/ttoklip/domain/member/domain/Member.java +++ b/src/main/java/com/api/ttoklip/domain/member/domain/Member.java @@ -7,6 +7,7 @@ import com.api.ttoklip.domain.honeytip.post.domain.HoneyTip; import com.api.ttoklip.domain.member.editor.MemberEditor; import com.api.ttoklip.domain.member.editor.MemberEditor.MemberEditorBuilder; +import com.api.ttoklip.domain.newsletter.scarp.entity.NewsletterScrap; import com.api.ttoklip.domain.privacy.domain.Interest; import com.api.ttoklip.domain.privacy.domain.Profile; import com.api.ttoklip.domain.question.post.domain.Question; @@ -75,6 +76,23 @@ public class Member extends BaseEntity { @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) private List honeyTipLikes = new ArrayList<>(); + @Builder.Default + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) + private List communities = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) + private List communityLikes = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) + private List communityScraps = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) + private List newsletterScraps = new ArrayList<>(); + + public MemberEditorBuilder toEditor() { return MemberEditor.builder() .independentYear(independentYear) @@ -88,16 +106,4 @@ public void insertPrivacy(MemberEditor memberEditor) { independentMonth = memberEditor.getIndependentMonth(); nickname = memberEditor.getNickname(); } - - @Builder.Default - @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) - private List communities = new ArrayList<>(); - - @Builder.Default - @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) - private List communityLikes = new ArrayList<>(); - - @Builder.Default - @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) - private List communityScraps = new ArrayList<>(); } \ No newline at end of file diff --git a/src/main/java/com/api/ttoklip/domain/newsletter/post/domain/Newsletter.java b/src/main/java/com/api/ttoklip/domain/newsletter/post/domain/Newsletter.java index d23dcefb..f37eba8a 100644 --- a/src/main/java/com/api/ttoklip/domain/newsletter/post/domain/Newsletter.java +++ b/src/main/java/com/api/ttoklip/domain/newsletter/post/domain/Newsletter.java @@ -1,20 +1,16 @@ package com.api.ttoklip.domain.newsletter.post.domain; +import static com.api.ttoklip.global.util.SecurityUtil.getCurrentMember; + import com.api.ttoklip.domain.common.Category; import com.api.ttoklip.domain.common.base.BaseEntity; +import com.api.ttoklip.domain.member.domain.Member; import com.api.ttoklip.domain.newsletter.comment.domain.NewsletterComment; import com.api.ttoklip.domain.newsletter.image.domain.NewsletterImage; import com.api.ttoklip.domain.newsletter.post.dto.request.NewsletterCreateReq; +import com.api.ttoklip.domain.newsletter.scarp.entity.NewsletterScrap; import com.api.ttoklip.domain.newsletter.url.domain.NewsletterUrl; -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 jakarta.persistence.*; import java.util.ArrayList; import java.util.List; import lombok.AccessLevel; @@ -45,6 +41,10 @@ public class Newsletter extends BaseEntity { private String mainImageUrl; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + @Builder.Default @OneToMany(mappedBy = "newsletter", cascade = CascadeType.ALL, orphanRemoval = true) private List newsletterImageList = new ArrayList<>(); @@ -57,12 +57,21 @@ public class Newsletter extends BaseEntity { @OneToMany(mappedBy = "newsletter", cascade = CascadeType.ALL, orphanRemoval = true) private List newsletterComments = new ArrayList<>(); + @Builder.Default + @OneToMany(mappedBy = "newsletter", cascade = CascadeType.ALL, orphanRemoval = true) + private List newsletterScraps = new ArrayList<>(); + public static Newsletter from(final NewsletterCreateReq req, final String mainImageUrl) { return Newsletter.builder() .title(req.getTitle()) .content(req.getContent()) .category(req.getCategory()) .mainImageUrl(mainImageUrl) + .member(getCurrentMember()) .build(); } + + public long getScrapsCount() { + return newsletterScraps.size(); + } } diff --git a/src/main/java/com/api/ttoklip/domain/newsletter/post/repository/NewsletterQueryDslRepository.java b/src/main/java/com/api/ttoklip/domain/newsletter/post/repository/NewsletterQueryDslRepository.java index d210bd01..cbf7cab5 100644 --- a/src/main/java/com/api/ttoklip/domain/newsletter/post/repository/NewsletterQueryDslRepository.java +++ b/src/main/java/com/api/ttoklip/domain/newsletter/post/repository/NewsletterQueryDslRepository.java @@ -2,6 +2,8 @@ import com.api.ttoklip.domain.newsletter.comment.domain.NewsletterComment; import com.api.ttoklip.domain.newsletter.post.domain.Newsletter; +import com.api.ttoklip.domain.town.community.post.entity.Community; + import java.util.List; public interface NewsletterQueryDslRepository { @@ -11,4 +13,9 @@ public interface NewsletterQueryDslRepository { List findActiveCommentsByNewsletterId(final Long postId); Long findNewsletterCount(); + + Newsletter findByIdActivated(final Long newsletterId); + + Long countNewsletterScrapsByCommunityId(final Long newsletterId); + } diff --git a/src/main/java/com/api/ttoklip/domain/newsletter/post/service/NewsletterCommonService.java b/src/main/java/com/api/ttoklip/domain/newsletter/post/service/NewsletterCommonService.java new file mode 100644 index 00000000..fbc7fbbb --- /dev/null +++ b/src/main/java/com/api/ttoklip/domain/newsletter/post/service/NewsletterCommonService.java @@ -0,0 +1,44 @@ +package com.api.ttoklip.domain.newsletter.post.service; + +import com.api.ttoklip.domain.newsletter.post.domain.Newsletter; +import com.api.ttoklip.domain.newsletter.post.repository.NewsletterRepository; +import com.api.ttoklip.global.exception.ApiException; +import com.api.ttoklip.global.exception.ErrorType; +import com.api.ttoklip.global.s3.S3FileUploader; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +import static com.api.ttoklip.global.util.SecurityUtil.getCurrentMember; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class NewsletterCommonService { + + private final S3FileUploader s3FileUploader; + private final NewsletterRepository newsletterRepository; + + /* -------------------------------------------- COMMON -------------------------------------------- */ + public Newsletter getNewsletter(final Long postId) { + return newsletterRepository.findByIdActivated(postId); + } + + public List uploadImages(final List uploadImages) { + return s3FileUploader.uploadMultipartFiles(uploadImages); + } + + public void checkEditPermission(final Newsletter newsletter) { + Long writerId = newsletter.getMember().getId(); + Long currentMemberId = getCurrentMember().getId(); + + if (!writerId.equals(currentMemberId)) { + throw new ApiException(ErrorType.UNAUTHORIZED_EDIT_POST); + } + } + + /* -------------------------------------------- COMMON 끝 -------------------------------------------- */ +} diff --git a/src/main/java/com/api/ttoklip/domain/newsletter/post/service/NewsletterPostService.java b/src/main/java/com/api/ttoklip/domain/newsletter/post/service/NewsletterPostService.java index 555be37c..29da88d2 100644 --- a/src/main/java/com/api/ttoklip/domain/newsletter/post/service/NewsletterPostService.java +++ b/src/main/java/com/api/ttoklip/domain/newsletter/post/service/NewsletterPostService.java @@ -8,6 +8,7 @@ import com.api.ttoklip.domain.newsletter.post.dto.request.NewsletterCreateReq; import com.api.ttoklip.domain.newsletter.post.dto.response.NewsletterWithCommentRes; import com.api.ttoklip.domain.newsletter.post.repository.NewsletterRepository; +import com.api.ttoklip.domain.newsletter.scarp.repository.NewsletterScrapRepository; import com.api.ttoklip.domain.newsletter.url.service.NewsletterUrlService; import com.api.ttoklip.global.exception.ApiException; import com.api.ttoklip.global.exception.ErrorType; @@ -30,14 +31,16 @@ public class NewsletterPostService { private final NewsletterImageService imageService; private final NewsletterUrlService urlService; private final ReportService reportService; + private final NewsletterScrapRepository newsletterScrapRepository; + private final NewsletterCommonService newsletterCommonService; - /* -------------------------------------------- 존재 여부 확인 -------------------------------------------- */ - public Newsletter findById(final Long postId) { - return newsletterRepository.findById(postId) - .orElseThrow(() -> new ApiException(ErrorType.NEWSLETTER_NOT_FOUND)); - } - /* -------------------------------------------- 존재 여부 확인 -------------------------------------------- */ +// /* -------------------------------------------- 존재 여부 확인 -------------------------------------------- */ +// public Newsletter findById(final Long postId) { +// return newsletterRepository.findById(postId) +// .orElseThrow(() -> new ApiException(ErrorType.NEWSLETTER_NOT_FOUND)); +// } +// /* -------------------------------------------- 존재 여부 확인 -------------------------------------------- */ /* -------------------------------------------- CREATE -------------------------------------------- */ @@ -101,8 +104,9 @@ public NewsletterWithCommentRes getSinglePost(final Long postId) { /* -------------------------------------------- REPORT -------------------------------------------- */ @Transactional public Message report(final Long postId, final ReportCreateRequest request) { - Newsletter newsletter = findById(postId); + Newsletter newsletter = newsletterCommonService.getNewsletter(postId); reportService.reportNewsletter(request, newsletter); + return Message.reportPostSuccess(Newsletter.class, postId); } /* -------------------------------------------- REPORT 끝 -------------------------------------------- */ diff --git a/src/main/java/com/api/ttoklip/domain/newsletter/scarp/repository/NewsletterScrapRepository.java b/src/main/java/com/api/ttoklip/domain/newsletter/scarp/repository/NewsletterScrapRepository.java new file mode 100644 index 00000000..47eed9dc --- /dev/null +++ b/src/main/java/com/api/ttoklip/domain/newsletter/scarp/repository/NewsletterScrapRepository.java @@ -0,0 +1,12 @@ +package com.api.ttoklip.domain.newsletter.scarp.repository; + +import com.api.ttoklip.domain.newsletter.scarp.entity.NewsletterScrap; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface NewsletterScrapRepository extends JpaRepository, NewsletterScrapRepositoryCustom { + Optional findByNewsletterIdAndMemberId(Long newsletterId, Long memberId); + + boolean existsByNewsletterIdAndMemberId(Long postId, Long memberId); +} diff --git a/src/main/java/com/api/ttoklip/domain/newsletter/scarp/repository/NewsletterScrapRepositoryCustom.java b/src/main/java/com/api/ttoklip/domain/newsletter/scarp/repository/NewsletterScrapRepositoryCustom.java new file mode 100644 index 00000000..c9a740c5 --- /dev/null +++ b/src/main/java/com/api/ttoklip/domain/newsletter/scarp/repository/NewsletterScrapRepositoryCustom.java @@ -0,0 +1,6 @@ +package com.api.ttoklip.domain.newsletter.scarp.repository; + +public interface NewsletterScrapRepositoryCustom { + + Long countNewsletterScrapsByNewsletterId(final Long newsletterId); +} diff --git a/src/main/java/com/api/ttoklip/domain/newsletter/scarp/service/NewsletterScrapService.java b/src/main/java/com/api/ttoklip/domain/newsletter/scarp/service/NewsletterScrapService.java new file mode 100644 index 00000000..fb57b1a3 --- /dev/null +++ b/src/main/java/com/api/ttoklip/domain/newsletter/scarp/service/NewsletterScrapService.java @@ -0,0 +1,50 @@ +package com.api.ttoklip.domain.newsletter.scarp.service; + +import com.api.ttoklip.domain.newsletter.post.domain.Newsletter; +import com.api.ttoklip.domain.newsletter.post.service.NewsletterCommonService; +import com.api.ttoklip.domain.newsletter.scarp.entity.NewsletterScrap; +import com.api.ttoklip.domain.newsletter.scarp.repository.NewsletterScrapRepository; +import com.api.ttoklip.global.exception.ApiException; +import com.api.ttoklip.global.exception.ErrorType; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import static com.api.ttoklip.global.util.SecurityUtil.getCurrentMember; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class NewsletterScrapService { + + private final NewsletterScrapRepository newsletterScrapRepository; + private final NewsletterCommonService newsletterCommonService; + + // 스크랩 생성 + public void registerScrap(final Long newsletterId) { + Long currentMemberId = getCurrentMember().getId(); + boolean exists = newsletterScrapRepository.existsByNewsletterIdAndMemberId(newsletterId, currentMemberId); + if (exists) { + return; // 이미 스크랩이 존재하면 스크랩을 생성하지 않고 return + } + Newsletter findNewsletter = newsletterCommonService.getNewsletter(newsletterId); + NewsletterScrap newsletterScrap = NewsletterScrap.from(findNewsletter); + newsletterScrapRepository.save(newsletterScrap); + } + + // 스크랩 취소 + public void cancelScrap(final Long newsletterId) { + // HoneyTipId (게시글 ID) + Newsletter findNewsletter = newsletterCommonService.getNewsletter(newsletterId); + Long findNewsletterId = findNewsletter.getId(); + Long currentMemberId = getCurrentMember().getId(); + NewsletterScrap newsletterScrap = newsletterScrapRepository.findByNewsletterIdAndMemberId(findNewsletterId, currentMemberId) + .orElseThrow(() -> new ApiException(ErrorType.SCRAP_NOT_FOUND)); + // 자격 검증: 이 단계에서는 findByHoneyTipIdAndMemberId 결과가 존재하므로, 현재 사용자가 좋아요를 누른 것입니다. + // 별도의 자격 검증 로직이 필요 없으며, 바로 삭제를 진행할 수 있습니다. + newsletterScrapRepository.deleteById(newsletterScrap.getId()); + } + public Long countNewsletterScraps(final Long newsletterId) { + return newsletterScrapRepository.countNewsletterScrapsByNewsletterId(newsletterId); + } +} diff --git a/src/main/java/com/api/ttoklip/domain/town/community/post/service/CommunityCommonService.java b/src/main/java/com/api/ttoklip/domain/town/community/post/service/CommunityCommonService.java index e522ff7e..c0b4c280 100644 --- a/src/main/java/com/api/ttoklip/domain/town/community/post/service/CommunityCommonService.java +++ b/src/main/java/com/api/ttoklip/domain/town/community/post/service/CommunityCommonService.java @@ -1,12 +1,12 @@ package com.api.ttoklip.domain.town.community.post.service; -import com.api.ttoklip.domain.member.domain.Member; +import static com.api.ttoklip.global.util.SecurityUtil.getCurrentMember; + import com.api.ttoklip.domain.town.community.post.entity.Community; import com.api.ttoklip.domain.town.community.post.repository.CommunityRepository; import com.api.ttoklip.global.exception.ApiException; import com.api.ttoklip.global.exception.ErrorType; import com.api.ttoklip.global.s3.S3FileUploader; -import com.api.ttoklip.global.util.SecurityUtil; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -14,7 +14,6 @@ import java.util.List; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -37,15 +36,10 @@ public void checkEditPermission(final Community community) { Long currentMemberId = getCurrentMember().getId(); if (!writerId.equals(currentMemberId)) { - // todo 게시글 별 커스텀 에러 수정 throw new ApiException(ErrorType.UNAUTHORIZED_EDIT_POST); } } - public static Member getCurrentMember() { - return SecurityUtil.getCurrentMember(); - } - /* -------------------------------------------- COMMON 끝 -------------------------------------------- */ } diff --git a/src/main/java/com/api/ttoklip/global/success/Message.java b/src/main/java/com/api/ttoklip/global/success/Message.java index a2eaf57e..9f49f386 100644 --- a/src/main/java/com/api/ttoklip/global/success/Message.java +++ b/src/main/java/com/api/ttoklip/global/success/Message.java @@ -75,18 +75,6 @@ public static Message likePostCancel(Class itemType, Long itemId) { return actionSuccess(itemType, itemId, LIKE, DELETE); } - public static Message insertPrivacy() { - return Message.builder() - .message("회원가입 후 개인정보를 추가했습니다.") - .build(); - } - - public static Message validNickname() { - return Message.builder() - .message("닉네임 중복 확인에 통과하였습니다.") - .build(); - } - public static Message editStatusSuccess(Class itemType, Long itemId) { return actionSuccess(itemType, itemId, STATUS, EDIT); } @@ -98,4 +86,16 @@ public static Message scrapPostSuccess(Class itemType, Long itemId) { public static Message scrapPostCancel(Class itemType, Long itemId) { return actionSuccess(itemType, itemId, SCRAP, DELETE); } + + public static Message insertPrivacy() { + return Message.builder() + .message("회원가입 후 개인정보를 추가했습니다.") + .build(); + } + + public static Message validNickname() { + return Message.builder() + .message("닉네임 중복 확인에 통과하였습니다.") + .build(); + } }