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
34 changes: 34 additions & 0 deletions src/docs/asciidoc/api/pick/v2/pick-detail-v2.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[[Pick-Detail-V2]]
== 픽픽픽 상세 API V2(GET: /devdevdev/api/v2/picks/{pickId})

* 픽픽픽 아이디로 상세 화면을 조회할 수 있다.
* 픽픽픽 게시글의 상태가 승인 상태(`APPROVAL`)가 아니면 조회할 수 없다.
* V1과 비교하여 응답에 **투표수(voteTotalCount)**와 **댓글수(commentTotalCount)** 필드가 추가되었다.

=== 정상 요청/응답

==== HTTP Request

include::{snippets}/pick-detail-v2/http-request.adoc[]

==== HTTP Request Header Fields

include::{snippets}/pick-detail-v2/request-headers.adoc[]

==== HTTP Request Path Parameters Fields

include::{snippets}/pick-detail-v2/path-parameters.adoc[]

==== HTTP Response

include::{snippets}/pick-detail-v2/http-response.adoc[]

==== HTTP Response Fields

include::{snippets}/pick-detail-v2/response-fields.adoc[]

=== V1과의 차이점

* `data.voteTotalCount`: 픽픽픽 전체 투표 수 (NEW)
* `data.commentTotalCount`: 픽픽픽 전체 댓글 수 (NEW)

1 change: 1 addition & 0 deletions src/docs/asciidoc/api/pick/v2/pick.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
= 픽픽픽 V2

include::pick-main-v2.adoc[]
include::pick-detail-v2.adoc[]
include::pick-search-v2.adoc[]
include::pick-similarity-v2.adoc[]
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,35 @@

import com.dreamypatisiel.devdevdev.domain.entity.AnonymousMember;
import com.dreamypatisiel.devdevdev.domain.entity.Pick;
import com.dreamypatisiel.devdevdev.domain.entity.PickOption;
import com.dreamypatisiel.devdevdev.domain.entity.enums.ContentStatus;
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.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.PickSearchDto;
import com.dreamypatisiel.devdevdev.domain.repository.pick.PickSort;
import com.dreamypatisiel.devdevdev.domain.repository.pick.*;
import com.dreamypatisiel.devdevdev.domain.repository.pick.mybatis.PickMapper;
import com.dreamypatisiel.devdevdev.domain.service.member.AnonymousMemberService;
import com.dreamypatisiel.devdevdev.exception.NotFoundException;
import com.dreamypatisiel.devdevdev.global.common.TimeProvider;
import com.dreamypatisiel.devdevdev.global.utils.AuthenticationMemberUtils;
import com.dreamypatisiel.devdevdev.openai.embeddings.EmbeddingsService;
import com.dreamypatisiel.devdevdev.web.dto.SliceCustom;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainResponseV2;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainSearchResponseV2;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.SimilarPickResponseV2;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.*;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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;

@Service
@Transactional(readOnly = true)
Expand Down Expand Up @@ -77,6 +78,40 @@ public Slice<PickMainResponseV2> findPicksMain(Pageable pageable, Long pickId, P
return new SliceCustom<>(pickMainResponse, pageable, picks.hasNext(), totalElements);
}

/**
* 픽픽픽 상세 조회 V2
*/
@Transactional
@Override
public PickDetailResponseV2 findPickDetail(Long pickId, String anonymousMemberId, Authentication authentication) {

// 익명 사용자 호출인지 확인
AuthenticationMemberUtils.validateAnonymousMethodCall(authentication);

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

// 픽픽픽 상세 조회(pickOption 페치조인)
Pick findPick = pickRepository.findPickWithPickOptionByPickId(pickId)
.orElseThrow(() -> new NotFoundException(INVALID_NOT_FOUND_PICK_MESSAGE));

// 픽픽픽 게시글의 승인 상태가 아니면
if (!findPick.isTrueContentStatus(ContentStatus.APPROVAL)) {
throw new IllegalArgumentException(INVALID_NOT_APPROVAL_STATUS_PICK_MESSAGE);
}

findPick.plusOneViewTotalCount(); // 조회수 증가
findPick.changePopularScore(pickPopularScorePolicy); // 인기점수 계산

// 픽픽픽 옵션 가공
Map<PickOptionType, PickDetailOptionResponse> pickDetailOptions = findPick.getPickOptions().stream()
.collect(Collectors.toMap(PickOption::getPickOptionType,
pickOption -> PickDetailOptionResponse.of(pickOption, findPick, anonymousMember)));

// 픽픽픽 상세
return PickDetailResponseV2.of(findPick, findPick.getMember(), anonymousMember, pickDetailOptions);
}

/**
* 나도 고민했는데 픽픽픽 V2
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@

import com.dreamypatisiel.devdevdev.domain.entity.Member;
import com.dreamypatisiel.devdevdev.domain.entity.Pick;
import com.dreamypatisiel.devdevdev.domain.entity.PickOption;
import com.dreamypatisiel.devdevdev.domain.entity.enums.ContentStatus;
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.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.PickSearchDto;
import com.dreamypatisiel.devdevdev.domain.repository.pick.PickSort;
import com.dreamypatisiel.devdevdev.exception.NotFoundException;
import com.dreamypatisiel.devdevdev.domain.repository.pick.mybatis.PickMapper;
import com.dreamypatisiel.devdevdev.global.common.MemberProvider;
import com.dreamypatisiel.devdevdev.global.common.TimeProvider;
import com.dreamypatisiel.devdevdev.openai.embeddings.EmbeddingsService;
import com.dreamypatisiel.devdevdev.web.dto.SliceCustom;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickDetailOptionResponse;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickDetailResponseV2;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainResponseV2;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainSearchResponseV2;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.SimilarPickResponseV2;
Expand All @@ -30,6 +35,9 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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;

@Service
@Transactional(readOnly = true)
public class MemberPickServiceV2 extends PickCommonService implements PickServiceV2 {
Expand Down Expand Up @@ -72,6 +80,37 @@ public Slice<PickMainResponseV2> findPicksMain(Pageable pageable, Long pickId, P
return new SliceCustom<>(pickMainResponse, pageable, picks.hasNext(), totalElements);
}

/**
* 픽픽픽 상세 조회 V2
*/
@Transactional
@Override
public PickDetailResponseV2 findPickDetail(Long pickId, String anonymousMemberId, Authentication authentication) {

// 회원 조회
Member findMember = memberProvider.getMemberByAuthentication(authentication);

// 픽픽픽 상세 조회(pickOption 페치조인)
Pick findPick = pickRepository.findPickWithPickOptionByPickId(pickId)
.orElseThrow(() -> new NotFoundException(INVALID_NOT_FOUND_PICK_MESSAGE));

// 픽픽픽 게시글의 승인 상태가 아니면
if (!findPick.isTrueContentStatus(ContentStatus.APPROVAL)) {
throw new IllegalArgumentException(INVALID_NOT_APPROVAL_STATUS_PICK_MESSAGE);
}

findPick.plusOneViewTotalCount(); // 조회수 증가
findPick.changePopularScore(pickPopularScorePolicy); // 인기점수 계산

// 픽픽픽 옵션 가공
Map<PickOptionType, PickDetailOptionResponse> pickDetailOptions = findPick.getPickOptions().stream()
.collect(Collectors.toMap(PickOption::getPickOptionType,
pickOption -> PickDetailOptionResponse.of(pickOption, findPick, findMember)));

// 픽픽픽 상세
return PickDetailResponseV2.of(findPick, findPick.getMember(), findMember, pickDetailOptions);
}

/**
* 나도 고민했는데 픽픽픽 V2
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.dreamypatisiel.devdevdev.domain.service.pick;

import com.dreamypatisiel.devdevdev.domain.repository.pick.PickSort;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickDetailResponseV2;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainResponseV2;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainSearchResponseV2;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.SimilarPickResponseV2;
Expand All @@ -13,6 +14,8 @@ public interface PickServiceV2 extends PickService {
Slice<PickMainResponseV2> findPicksMain(Pageable pageable, Long pickId, PickSort pickSort, String anonymousMemberId,
Authentication authentication);

PickDetailResponseV2 findPickDetail(Long pickId, String anonymousMemberId, Authentication authentication);

List<SimilarPickResponseV2> findTop3SimilarPicksV2(Long pickId);

Slice<PickMainSearchResponseV2> findPickMainSearch(Pageable pageable, Long pickId, Double searchScore, String keyword,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.dreamypatisiel.devdevdev.global.utils.HttpRequestUtils;
import com.dreamypatisiel.devdevdev.web.controller.ApiVersion;
import com.dreamypatisiel.devdevdev.web.dto.response.BasicResponse;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickDetailResponseV2;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainResponseV2;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainSearchResponseV2;
import com.dreamypatisiel.devdevdev.web.dto.response.pick.SimilarPickResponseV2;
Expand Down Expand Up @@ -54,6 +55,18 @@ public ResponseEntity<BasicResponse<Slice<PickMainResponseV2>>> getPicksMain(
return ResponseEntity.ok(BasicResponse.success(response));
}

@Operation(summary = "픽픽픽 상세 조회 V2", description = "픽픽픽 상세 페이지를 조회합니다.")
@GetMapping("/picks/{pickId}")
public ResponseEntity<BasicResponse<PickDetailResponseV2>> getPickDetail(@PathVariable Long pickId,
@RequestHeader(value = HEADER_ANONYMOUS_MEMBER_ID, required = false) String anonymousMemberId) {
Authentication authentication = AuthenticationMemberUtils.getAuthentication();

PickServiceV2 pickService = (PickServiceV2) pickServiceStrategy.getPickService(ApiVersion.V2);
PickDetailResponseV2 response = pickService.findPickDetail(pickId, anonymousMemberId, authentication);

return ResponseEntity.ok(BasicResponse.success(response));
}

@Operation(summary = "나도 고민했는데 픽픽픽 V2", description = "픽픽픽 상세와 유사한 성격의 픽픽픽 3개를 추천합니다.")
@GetMapping("picks/{pickId}/similarties")
public ResponseEntity<BasicResponse<SimilarPickResponseV2>> getSimilarPicks(@PathVariable Long pickId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.dreamypatisiel.devdevdev.web.dto.response.pick;

import com.dreamypatisiel.devdevdev.domain.entity.AnonymousMember;
import com.dreamypatisiel.devdevdev.domain.entity.Member;
import com.dreamypatisiel.devdevdev.domain.entity.Pick;
import com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType;
import com.dreamypatisiel.devdevdev.web.dto.util.CommonResponseUtil;
import com.dreamypatisiel.devdevdev.web.dto.util.PickResponseUtils;
import com.dreamypatisiel.devdevdev.global.common.TimeProvider;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
import java.time.LocalDateTime;
import java.util.Map;
import lombok.Builder;
import lombok.Data;

@Data
public class PickDetailResponseV2 {
private final String userId;
private final String nickname;

@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = TimeProvider.DEFAULT_ZONE_ID)
private final LocalDateTime pickCreatedAt;

private final String pickTitle;
private final long voteTotalCount;
private final long commentTotalCount;
private final Boolean isAuthor;
private final Boolean isVoted;
private final Map<PickOptionType, PickDetailOptionResponse> pickOptions;

@Builder
public PickDetailResponseV2(String userId, String nickname, LocalDateTime pickCreatedAt, String pickTitle,
long voteTotalCount, long commentTotalCount, boolean isAuthor, boolean isVoted,
Map<PickOptionType, PickDetailOptionResponse> pickOptions) {
this.userId = userId;
this.nickname = nickname;
this.pickCreatedAt = pickCreatedAt;
this.pickTitle = pickTitle;
this.voteTotalCount = voteTotalCount;
this.commentTotalCount = commentTotalCount;
this.isAuthor = isAuthor;
this.isVoted = isVoted;
this.pickOptions = pickOptions;
}

// 회원 전용
public static PickDetailResponseV2 of(Pick pick, Member pickMember, Member member,
Map<PickOptionType, PickDetailOptionResponse> pickDetailOptions) {
return PickDetailResponseV2.builder()
.userId(CommonResponseUtil.sliceAndMaskEmail(pickMember.getEmail().getEmail()))
.nickname(pickMember.getNickname().getNickname())
.pickCreatedAt(pick.getCreatedAt())
.pickTitle(pick.getTitle().getTitle())
.voteTotalCount(pick.getVoteTotalCount().getCount())
.commentTotalCount(pick.getCommentTotalCount().getCount())
.isAuthor(pick.isEqualMember(member))
.isVoted(PickResponseUtils.isVotedMember(pick, member))
.pickOptions(pickDetailOptions)
.build();
}

// 익명 회원 전용
public static PickDetailResponseV2 of(Pick pick, Member pickMember, AnonymousMember anonymousMember,
Map<PickOptionType, PickDetailOptionResponse> pickDetailOptions) {
return PickDetailResponseV2.builder()
.isAuthor(false)
.pickCreatedAt(pick.getCreatedAt())
.nickname(pickMember.getNickname().getNickname())
.userId(CommonResponseUtil.sliceAndMaskEmail(pickMember.getEmail().getEmail()))
.pickTitle(pick.getTitle().getTitle())
.voteTotalCount(pick.getVoteTotalCount().getCount())
.commentTotalCount(pick.getCommentTotalCount().getCount())
.pickOptions(pickDetailOptions)
.isVoted(PickResponseUtils.isVotedAnonymousMember(pick, anonymousMember))
.build();
}
}

Loading