Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/docs/asciidoc/api/tech-article/recommend.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[[TechArticleRecommend]]
== 기술블로그 추천 API(POST: /devdevdev/api/v1/articles/{techArticleId}/recommend)
* 회원과 익명회원은 기술블로그를 추천하거나 추천 취소할 수 있다.

=== 정상 요청/응답
==== HTTP Request
include::{snippets}/tech-article-recommend/http-request.adoc[]
==== HTTP Request Header Fields
include::{snippets}/tech-article-recommend/request-headers.adoc[]
==== HTTP Request Path Parameters Fields
include::{snippets}/tech-article-recommend/path-parameters.adoc[]

==== HTTP Response
include::{snippets}/tech-article-recommend/http-response.adoc[]
==== HTTP Response Fields
include::{snippets}/tech-article-recommend/response-fields.adoc[]


=== 예외
==== HTTP Response
include::{snippets}/not-found-tech-article-exception/response-body.adoc[]
include::{snippets}/not-found-member-exception/response-body.adoc[]
1 change: 1 addition & 0 deletions src/docs/asciidoc/api/tech-article/tech-article.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ include::main.adoc[]
include::detail.adoc[]
include::bookmark.adoc[]
include::keyword.adoc[]
include::recommend.adoc[]
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import com.dreamypatisiel.devdevdev.domain.entity.enums.WordType;
import com.dreamypatisiel.devdevdev.domain.policy.PickPopularScorePolicy;
import com.dreamypatisiel.devdevdev.domain.repository.BlameTypeRepository;
import com.dreamypatisiel.devdevdev.domain.repository.BookmarkRepository;
import com.dreamypatisiel.devdevdev.domain.repository.techArticle.BookmarkRepository;
import com.dreamypatisiel.devdevdev.domain.repository.CompanyRepository;
import com.dreamypatisiel.devdevdev.domain.repository.member.MemberRepository;
import com.dreamypatisiel.devdevdev.domain.repository.member.memberNicknameDictionary.MemberNicknameDictionaryRepository;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ public class TechArticle extends BasicTime {
@OneToMany(mappedBy = "techArticle")
private List<Bookmark> bookmarks = new ArrayList<>();

@OneToMany(mappedBy = "techArticle")
private List<TechArticleRecommend> recommends = new ArrayList<>();

@Builder
private TechArticle(Count viewTotalCount, Count recommendTotalCount, Count commentTotalCount, Count popularScore,
Url techArticleUrl, Company company, String elasticId) {
Expand Down Expand Up @@ -127,6 +130,10 @@ public void changePopularScore(TechArticlePopularScorePolicy policy) {
this.popularScore = this.calculatePopularScore(policy);
}

private Count calculatePopularScore(TechArticlePopularScorePolicy policy) {
return policy.calculatePopularScore(this);
}

public void changeCompany(Company company) {
company.getTechArticles().add(this);
this.company = company;
Expand All @@ -136,15 +143,20 @@ public void incrementViewTotalCount() {
this.viewTotalCount = Count.plusOne(this.viewTotalCount);
}

private Count calculatePopularScore(TechArticlePopularScorePolicy policy) {
return policy.calculatePopularScore(this);
}

public void incrementCommentCount() {
this.commentTotalCount = Count.plusOne(this.commentTotalCount);
}

public void decrementCommentCount() {
this.commentTotalCount = Count.minusOne(this.commentTotalCount);
}


public void incrementRecommendTotalCount() {
this.recommendTotalCount = Count.plusOne(this.recommendTotalCount);
}

public void decrementRecommendTotalCount() {
this.recommendTotalCount = Count.minusOne(this.recommendTotalCount);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TechArticleRecommend 클래스에 TechArticle 관련 연관관계 편의 메소드가 있어야 할 것 같아요~!
양방향인 경우에 연관관계 편의 메소드가 있는것이 편하고 실수를 방지합니당.

아래 처럼 서로 관계를 맺어주는 메소드가 필요 합니다~!

  • TechArticleRecommend -> TechArticle
  • TechArticle -> TechArticleRecommend

e.g)

public void(TechArticle techArticle) {
   this.techArticle = techArticle;
   techArticle.getRecommends().add(this);
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changeTechArticle() 메서드에 techArticle.getRecommends().add(this); 이 코드 추가하겠습니다!

Original file line number Diff line number Diff line change
@@ -1,27 +1,73 @@
package com.dreamypatisiel.devdevdev.domain.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class TechArticleRecommend extends BasicTime {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private boolean status;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
@JoinColumn(name = "member_id")
private Member member;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "anonymous_member_id")
private AnonymousMember anonymousMember;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "tech_article_id", nullable = false)
private TechArticle techArticle;

@Builder
private TechArticleRecommend(boolean status, Member member, AnonymousMember anonymousMember, TechArticle techArticle) {
this.status = status;
this.member = member;
this.anonymousMember = anonymousMember;
this.techArticle = techArticle;
}


public static TechArticleRecommend create(Member member, TechArticle techArticle) {
return TechArticleRecommend.builder()
.member(member)
.techArticle(techArticle)
.status(true)
.build();
}

public static TechArticleRecommend create(AnonymousMember anonymousMember, TechArticle techArticle) {
return TechArticleRecommend.builder()
.anonymousMember(anonymousMember)
.techArticle(techArticle)
.status(true)
.build();
}

public void changeTechArticle(TechArticle techArticle) {
this.techArticle = techArticle;
techArticle.getRecommends().add(this);
}

public void cancelRecommend() {
this.status = false;
}

public void registerRecommend() {
this.status = true;
}

public boolean isRecommended() {
return this.status;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.dreamypatisiel.devdevdev.domain.repository;
package com.dreamypatisiel.devdevdev.domain.repository.techArticle;

import com.dreamypatisiel.devdevdev.domain.entity.Bookmark;
import com.dreamypatisiel.devdevdev.domain.entity.Member;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.dreamypatisiel.devdevdev.domain.repository.techArticle;

import com.dreamypatisiel.devdevdev.domain.entity.AnonymousMember;
import com.dreamypatisiel.devdevdev.domain.entity.Member;
import com.dreamypatisiel.devdevdev.domain.entity.TechArticle;
import com.dreamypatisiel.devdevdev.domain.entity.TechArticleRecommend;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface TechArticleRecommendRepository extends JpaRepository<TechArticleRecommend, Long> {
Optional<TechArticleRecommend> findByTechArticleAndMember(TechArticle techArticle, Member member);

Optional<TechArticleRecommend> findByTechArticleAndAnonymousMember(TechArticle techArticle, AnonymousMember anonymousMember);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.dreamypatisiel.devdevdev.domain.service.member;

import com.dreamypatisiel.devdevdev.domain.entity.AnonymousMember;
import com.dreamypatisiel.devdevdev.domain.repository.member.AnonymousMemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_ANONYMOUS_MEMBER_ID_MESSAGE;

@Transactional(readOnly = true)
@Service
@RequiredArgsConstructor
public class AnonymousMemberService {

private final AnonymousMemberRepository anonymousMemberRepository;

@Transactional
public AnonymousMember findOrCreateAnonymousMember(String anonymousMemberId) {
// 익명 사용자 검증
validateAnonymousMemberId(anonymousMemberId);

// 익명회원 조회 또는 생성
return anonymousMemberRepository.findByAnonymousMemberId(anonymousMemberId)
.orElseGet(() -> anonymousMemberRepository.save(AnonymousMember.create(anonymousMemberId)));
}

private void validateAnonymousMemberId(String anonymousMemberId) {
if (!StringUtils.hasText(anonymousMemberId)) {
throw new IllegalArgumentException(INVALID_ANONYMOUS_MEMBER_ID_MESSAGE);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
package com.dreamypatisiel.devdevdev.domain.service.pick;

import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_ANONYMOUS_MEMBER_ID_MESSAGE;
import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_CAN_NOT_VOTE_SAME_PICK_OPTION_MESSAGE;
import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_APPROVAL_STATUS_PICK_MESSAGE;
import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_FOUND_PICK_MESSAGE;

import com.dreamypatisiel.devdevdev.domain.entity.AnonymousMember;
import com.dreamypatisiel.devdevdev.domain.entity.Pick;
import com.dreamypatisiel.devdevdev.domain.entity.PickOption;
Expand All @@ -13,12 +8,8 @@
import com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType;
import com.dreamypatisiel.devdevdev.domain.policy.PickBestCommentsPolicy;
import com.dreamypatisiel.devdevdev.domain.policy.PickPopularScorePolicy;
import com.dreamypatisiel.devdevdev.domain.repository.member.AnonymousMemberRepository;
import com.dreamypatisiel.devdevdev.domain.repository.pick.PickCommentRecommendRepository;
import com.dreamypatisiel.devdevdev.domain.repository.pick.PickCommentRepository;
import com.dreamypatisiel.devdevdev.domain.repository.pick.PickRepository;
import com.dreamypatisiel.devdevdev.domain.repository.pick.PickSort;
import com.dreamypatisiel.devdevdev.domain.repository.pick.PickVoteRepository;
import com.dreamypatisiel.devdevdev.domain.repository.pick.*;
import com.dreamypatisiel.devdevdev.domain.service.member.AnonymousMemberService;
import com.dreamypatisiel.devdevdev.domain.service.pick.dto.VotePickOptionDto;
import com.dreamypatisiel.devdevdev.exception.NotFoundException;
import com.dreamypatisiel.devdevdev.exception.VotePickOptionException;
Expand All @@ -27,30 +18,24 @@
import com.dreamypatisiel.devdevdev.openai.embeddings.EmbeddingsService;
import com.dreamypatisiel.devdevdev.web.dto.request.pick.ModifyPickRequest;
import com.dreamypatisiel.devdevdev.web.dto.request.pick.RegisterPickRequest;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickDetailOptionResponse;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickDetailResponse;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainResponse;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickModifyResponse;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickRegisterResponse;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickUploadImageResponse;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.SimilarPickResponse;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.VotePickOptionResponse;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.VotePickResponse;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.*;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.*;

@Service
@Transactional(readOnly = true)
public class GuestPickService extends PickCommonService implements PickService {
Expand All @@ -60,21 +45,21 @@ public class GuestPickService extends PickCommonService implements PickService {

private final PickPopularScorePolicy pickPopularScorePolicy;
private final PickVoteRepository pickVoteRepository;
private final AnonymousMemberRepository anonymousMemberRepository;
private final TimeProvider timeProvider;
private final AnonymousMemberService anonymousMemberService;

public GuestPickService(PickRepository pickRepository, EmbeddingsService embeddingsService,
PickBestCommentsPolicy pickBestCommentsPolicy,
PickCommentRepository pickCommentRepository,
PickCommentRecommendRepository pickCommentRecommendRepository,
PickPopularScorePolicy pickPopularScorePolicy,
PickVoteRepository pickVoteRepository,
AnonymousMemberRepository anonymousMemberRepository, TimeProvider timeProvider) {
TimeProvider timeProvider, AnonymousMemberService anonymousMemberService) {
super(embeddingsService, pickBestCommentsPolicy, pickRepository, pickCommentRepository,
pickCommentRecommendRepository);
this.pickPopularScorePolicy = pickPopularScorePolicy;
this.pickVoteRepository = pickVoteRepository;
this.anonymousMemberRepository = anonymousMemberRepository;
this.anonymousMemberService = anonymousMemberService;
this.timeProvider = timeProvider;
}

Expand All @@ -86,7 +71,7 @@ public Slice<PickMainResponse> findPicksMain(Pageable pageable, Long pickId, Pic
AuthenticationMemberUtils.validateAnonymousMethodCall(authentication);

// anonymousMemberId 검증
AnonymousMember anonymousMember = findOrCreateAnonymousMember(anonymousMemberId);
AnonymousMember anonymousMember = anonymousMemberService.findOrCreateAnonymousMember(anonymousMemberId);

// 픽픽픽 조회
Slice<Pick> picks = pickRepository.findPicksByCursor(pageable, pickId, pickSort);
Expand Down Expand Up @@ -123,7 +108,7 @@ public PickDetailResponse findPickDetail(Long pickId, String anonymousMemberId,
AuthenticationMemberUtils.validateAnonymousMethodCall(authentication);

// 익명 회원 조회 또는 생성
AnonymousMember anonymousMember = findOrCreateAnonymousMember(anonymousMemberId);
AnonymousMember anonymousMember = anonymousMemberService.findOrCreateAnonymousMember(anonymousMemberId);

// 픽픽픽 상세 조회(pickOption 페치조인)
Pick findPick = pickRepository.findPickWithPickOptionByPickId(pickId)
Expand All @@ -146,15 +131,6 @@ public PickDetailResponse findPickDetail(Long pickId, String anonymousMemberId,
return PickDetailResponse.of(findPick, findPick.getMember(), anonymousMember, pickDetailOptions);
}

private AnonymousMember findOrCreateAnonymousMember(String anonymousMemberId) {
// 익명 사용자 검증
validateAnonymousMemberId(anonymousMemberId);

// 익명회원 조회 또는 생성
return anonymousMemberRepository.findByAnonymousMemberId(anonymousMemberId)
.orElseGet(() -> anonymousMemberRepository.save(AnonymousMember.create(anonymousMemberId)));
}

/**
* 익명 회원이 픽픽픽을 투표한다.
*/
Expand All @@ -171,7 +147,7 @@ public VotePickResponse votePickOption(VotePickOptionDto votePickOptionDto,
String anonymousMemberId = votePickOptionDto.getAnonymousMemberId();

// 익명 회원을 조회하거나 생성
AnonymousMember anonymousMember = findOrCreateAnonymousMember(anonymousMemberId);
AnonymousMember anonymousMember = anonymousMemberService.findOrCreateAnonymousMember(anonymousMemberId);

Optional<PickVote> pickVoteOptional = pickVoteRepository.findWithPickAndPickOptionByPickIdAndAnonymousMemberAndDeletedAtIsNull(
pickId, anonymousMember);
Expand Down Expand Up @@ -300,12 +276,6 @@ public List<SimilarPickResponse> findTop3SimilarPicks(Long pickId) {
return super.findTop3SimilarPicks(pickId);
}

private void validateAnonymousMemberId(String anonymousMemberId) {
if (!StringUtils.hasText(anonymousMemberId)) {
throw new IllegalArgumentException(INVALID_ANONYMOUS_MEMBER_ID_MESSAGE);
}
}

@Override
public void deleteImage(Long pickOptionImageId) {
throw new AccessDeniedException(INVALID_ANONYMOUS_CAN_NOT_USE_THIS_FUNCTION_MESSAGE);
Expand Down
Loading
Loading