Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PC-270] 매칭 조각 확인 기능 구현 #30

Open
wants to merge 11 commits into
base: feature/PC-268-matching-algorithm
Choose a base branch
from
Open
194 changes: 192 additions & 2 deletions api/src/main/java/org/yapp/domain/match/application/MatchService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,27 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.yapp.application.AuthenticationService;
import org.yapp.domain.match.MatchInfo;
import org.yapp.domain.match.dao.MatchInfoRepository;
import org.yapp.domain.match.enums.MatchStatus;
import org.yapp.domain.match.presentation.dto.response.MatchInfoResponse;
import org.yapp.domain.match.presentation.dto.response.MatchProfileBasicResponse;
import org.yapp.domain.match.presentation.dto.response.MatchValuePickInnerResponse;
import org.yapp.domain.match.presentation.dto.response.MatchValuePickResponse;
import org.yapp.domain.match.presentation.dto.response.MatchValueTalkInnerResponse;
import org.yapp.domain.match.presentation.dto.response.MatchValueTalkResponse;
import org.yapp.domain.profile.Profile;
import org.yapp.domain.profile.ProfileValuePick;
import org.yapp.domain.profile.ProfileValueTalk;
import org.yapp.domain.profile.application.ProfileValuePickService;
import org.yapp.domain.user.User;
import org.yapp.domain.user.application.UserService;
import org.yapp.error.dto.MatchErrorCode;
Expand All @@ -20,14 +35,33 @@ public class MatchService {

private final MatchInfoRepository matchInfoRepository;
private final AuthenticationService authenticationService;
private final ProfileValuePickService profileValuePickService;
private final UserService userService;

@Transactional
public MatchInfo createMatchInfo(Long user1Id, Long user2Id) {
User user1 = userService.getUserById(user1Id);
User user2 = userService.getUserById(user2Id);
return matchInfoRepository.save(new MatchInfo(LocalDate.now(), user1, user2));
}

@Transactional(readOnly = true)
public MatchProfileBasicResponse getMatchProfileBasic() {
Long userId = authenticationService.getUserId();
MatchInfo matchInfo = getMatchInfo(userId);

User matchedUser = getMatchedUser(userId, matchInfo);
Profile matchedProfile = matchedUser.getProfile();
return MatchProfileBasicResponse.fromProfile(matchInfo.getId(), matchedProfile);
}

@Transactional
public void checkPiece() {
Long userId = authenticationService.getUserId();
MatchInfo matchInfo = getMatchInfo(userId);
matchInfo.checkPiece(userId);
}

public LocalDate getMatchDate() {
LocalDateTime nowDateTime = LocalDateTime.now();
LocalDate nowDate = nowDateTime.toLocalDate();
Expand All @@ -39,14 +73,170 @@ public LocalDate getMatchDate() {
return nowDate;
}

@Transactional(readOnly = true)
public boolean wasUsersMatched(Long user1Id, Long user2Id) {
Optional<MatchInfo> matchInfoByIds = matchInfoRepository.findMatchInfoByIds(user1Id, user2Id);
return matchInfoByIds.isPresent();
}

public MatchInfo getMatchInfo() {
Long userId = authenticationService.getUserId();
@Transactional(readOnly = true)
Lujaec marked this conversation as resolved.
Show resolved Hide resolved
public MatchInfo getMatchInfo(Long userId) {
return matchInfoRepository.findByUserIdAndDate(userId, getMatchDate())
.orElseThrow(() -> new ApplicationException(MatchErrorCode.NOTFOUND_MATCH));
}

@Transactional(readOnly = true)
public MatchInfoResponse getMatchInfoResponse() {
Long userId = authenticationService.getUserId();
MatchInfo matchInfo = getMatchInfo(userId);

User matchedUser = getMatchedUser(userId, matchInfo);
User user = userService.getUserById(userId);
List<String> matchedValues = getMatchedValues(user.getProfile().getId(),
matchedUser.getProfile().getId());

//TODO : 왜 deprecated 된 ProfileBio에만 introduce가 있는지 논의가 필요
Lujaec marked this conversation as resolved.
Show resolved Hide resolved
MatchInfoResponse response = MatchInfoResponse.builder()
.matchId(matchInfo.getId())
.matchStatus(getMatchStatus(userId, matchInfo))
.shortIntroduce("") // Deprecated 된 BIO 에서 넣어야하는지?
.nickname(matchedUser.getProfile().getProfileBasic().getNickname())
.birthYear(
String.valueOf(matchedUser.getProfile().getProfileBasic().getBirthdate().getYear()))
.location(matchedUser.getProfile().getProfileBasic().getLocation())
.job(matchedUser.getProfile().getProfileBasic().getJob())
.matchedValueCount(matchedValues.size())
.matchedValueList(matchedValues)
.build();

return response;
}

private User getMatchedUser(Long userId, MatchInfo matchInfo) {
if (userId.equals(matchInfo.getUser1().getId())) {
return matchInfo.getUser2();
}
return matchInfo.getUser1();
}

private String getMatchStatus(Long userId, MatchInfo matchInfo) {
if (userId.equals(matchInfo.getUser1().getId())) {
if (!matchInfo.getUser1PieceChecked()) {
return MatchStatus.BEFORE_OPEN.getStatus();
}
if (matchInfo.getUser1Accepted() && matchInfo.getUser2Accepted()) {
return MatchStatus.MATCHED.getStatus();
}
if (matchInfo.getUser1Accepted()) {
return MatchStatus.RESPONDED.getStatus();
}
if (matchInfo.getUser2Accepted()) {
return MatchStatus.GREEN_LIGHT.getStatus();
}
return MatchStatus.WAITING.getStatus();
} else {
if (!matchInfo.getUser2PieceChecked()) {
return MatchStatus.BEFORE_OPEN.getStatus();
}
if (matchInfo.getUser1Accepted() && matchInfo.getUser2Accepted()) {
return MatchStatus.MATCHED.getStatus();
}
if (matchInfo.getUser2Accepted()) {
return MatchStatus.RESPONDED.getStatus();
}
if (matchInfo.getUser1Accepted()) {
return MatchStatus.GREEN_LIGHT.getStatus();
}
return MatchStatus.WAITING.getStatus();
}
}

@Transactional(readOnly = true)
public MatchValueTalkResponse getMatchValueTalk() {
Long userId = authenticationService.getUserId();
MatchInfo matchInfo = getMatchInfo(userId);
User matchedUser = getMatchedUser(userId, matchInfo);
List<ProfileValueTalk> profileValueTalks = matchedUser.getProfile().getProfileValueTalks();
List<MatchValueTalkInnerResponse> talkResponses = new ArrayList<>();
for (ProfileValueTalk profileValueTalk : profileValueTalks) {
String summary = profileValueTalk.getSummary();
String answer = profileValueTalk.getAnswer();
String category = profileValueTalk.getValueTalk().getCategory();
talkResponses.add(new MatchValueTalkInnerResponse(category, summary, answer));
}
return new MatchValueTalkResponse(matchInfo.getId(), "",
matchedUser.getProfile().getProfileBasic().getNickname(), talkResponses);
}

@Transactional(readOnly = true)
public MatchValuePickResponse getMatchedUserValuePicks() {
Long userId = authenticationService.getUserId();
User user = userService.getUserById(userId);
MatchInfo matchInfo = getMatchInfo(userId);
User matchedUser = getMatchedUser(userId, matchInfo);
List<MatchValuePickInnerResponse> matchValuePickInnerResponses = getMatchValuePickInnerResponses(
user.getProfile().getId(), matchedUser.getProfile().getId());

return new MatchValuePickResponse(matchInfo.getId(), "",
matchedUser.getProfile().getProfileBasic().getNickname(), matchValuePickInnerResponses);
}

private List<MatchValuePickInnerResponse> getMatchValuePickInnerResponses(Long fromProfileId,
Long toProfileId) {
List<ProfileValuePick> profileValuePicksOfFrom =
profileValuePickService.getAllProfileValuesByProfileId(fromProfileId);
List<ProfileValuePick> profileValuePicksOfTo = profileValuePickService.getAllProfileValuesByProfileId(
toProfileId);

List<MatchValuePickInnerResponse> talkInnerResponses = new ArrayList<>();
int valueListSize = profileValuePicksOfFrom.size();
for (int i = 0; i < valueListSize; i++) {
ProfileValuePick profileValuePickFrom = profileValuePicksOfFrom.get(i);
ProfileValuePick profileValuePickTo = profileValuePicksOfTo.get(i);
String category = profileValuePickTo.getValuePick().getCategory();
String question = profileValuePickTo.getValuePick().getQuestion();
Integer selectedAnswer = profileValuePickTo.getSelectedAnswer();
Map<Integer, Object> answers = profileValuePickTo.getValuePick().getAnswers();
if (profileValuePickTo.getSelectedAnswer().equals(profileValuePickFrom.getSelectedAnswer())) {
talkInnerResponses.add(
new MatchValuePickInnerResponse(category, question, true, answers, selectedAnswer));
} else {
talkInnerResponses.add(
new MatchValuePickInnerResponse(category, question, false, answers, selectedAnswer)
);
}
}
return talkInnerResponses;
}

@Transactional(readOnly = true)
public String getMatchedUserImageUrl() {
Long userId = authenticationService.getUserId();
MatchInfo matchInfo = getMatchInfo(userId);
User matchedUser = getMatchedUser(userId, matchInfo);

return matchedUser.getProfile().getProfileBasic().getImageUrl();
}

private List<String> getMatchedValues(Long fromProfileId, Long toProfileId) {
List<ProfileValuePick> profileValuePicksOfFrom =
profileValuePickService.getAllProfileValuesByProfileId(fromProfileId);
List<ProfileValuePick> profileValuePicksOfTo = profileValuePickService.getAllProfileValuesByProfileId(
toProfileId);

int valueListSize = profileValuePicksOfFrom.size();
List<String> matchedValues = new ArrayList<>();
for (int i = 0; i < valueListSize; i++) {
ProfileValuePick profileValuePickOfFrom = profileValuePicksOfFrom.get(i);
ProfileValuePick profileValuePickOfTo = profileValuePicksOfTo.get(i);
if (profileValuePickOfFrom.getSelectedAnswer()
.equals(profileValuePickOfTo.getSelectedAnswer())) {
Integer selectedAnswer = profileValuePickOfTo.getSelectedAnswer();
Map<Integer, Object> answers = profileValuePickOfTo.getValuePick().getAnswers();
String value = (String) answers.get(selectedAnswer);
matchedValues.add(value);
}
}
return matchedValues;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package org.yapp.domain.match.presentation;

import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.yapp.domain.match.application.MatchService;
import org.yapp.domain.match.presentation.dto.response.ImageUrlResponse;
import org.yapp.domain.match.presentation.dto.response.MatchInfoResponse;
import org.yapp.domain.match.presentation.dto.response.MatchProfileBasicResponse;
import org.yapp.domain.match.presentation.dto.response.MatchValuePickResponse;
import org.yapp.domain.match.presentation.dto.response.MatchValueTalkResponse;
import org.yapp.util.CommonResponse;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/matches")
public class MatchController {

private final MatchService matchService;

@GetMapping("/infos")
@Operation(summary = "매칭 정보 조회", description = "이번 매칭의 정보를 조회합니다.", tags = {"매칭"})
public ResponseEntity<CommonResponse<MatchInfoResponse>> getMatchInfo() {
MatchInfoResponse matchInfoResponse = matchService.getMatchInfoResponse();
return ResponseEntity.status(HttpStatus.OK)
.body(CommonResponse.createSuccess(matchInfoResponse));
}

@PatchMapping("/pieces/check")
@Operation(summary = "매칭 조각 확인 체크", description = "이번 매칭의 조각을 확인했음을 서버에 알립니다.", tags = {"매칭"})
public ResponseEntity<CommonResponse<Void>> checkMatchPiece() {
matchService.checkPiece();
return ResponseEntity.status(HttpStatus.OK).body(CommonResponse.createSuccessWithNoContent());
}

@GetMapping("/profiles/basic")
Lujaec marked this conversation as resolved.
Show resolved Hide resolved
@Operation(summary = "매칭 프로필 기본정보 확인", description = "매칭 상대의 프로필 기본정보를 확인합니다.", tags = {"매칭"})
public ResponseEntity<CommonResponse<MatchProfileBasicResponse>> getBasicMatchProfile() {
MatchProfileBasicResponse matchProfileBasic = matchService.getMatchProfileBasic();
return ResponseEntity.status(HttpStatus.OK)
.body(CommonResponse.createSuccess(matchProfileBasic));
}

@GetMapping("/values/talks")
@Operation(summary = "매칭 상대 가치관 톡 확인", description = "매칭 상대의 가치관 톡을 확인합니다.", tags = {"매칭"})
public ResponseEntity<CommonResponse<MatchValueTalkResponse>> getMatchTalkValues() {
MatchValueTalkResponse matchValueTalk = matchService.getMatchValueTalk();
return ResponseEntity.status(HttpStatus.OK).body(CommonResponse.createSuccess(matchValueTalk));
}


@GetMapping("/values/picks")
@Operation(summary = "매칭 상대 가치관 픽 확인", description = "매칭 상대의 가치관 픽을 확인합니다.", tags = {"매칭"})
public ResponseEntity<CommonResponse<MatchValuePickResponse>> getMatchValuePicks() {
MatchValuePickResponse matchValuePickResponse = matchService.getMatchedUserValuePicks();
return ResponseEntity.status(HttpStatus.OK)
.body(CommonResponse.createSuccess(matchValuePickResponse));
}

@GetMapping("/images")
@Operation(summary = "매칭 상대 프로필 이미지 확인", description = "매칭 상대의 프로필 이미지를 확인합니다.", tags = {"매칭"})
public ResponseEntity<CommonResponse<ImageUrlResponse>> getMatchedUserImages() {
String matchedUserImageUrl = matchService.getMatchedUserImageUrl();
ImageUrlResponse imageUrlResponse = new ImageUrlResponse(matchedUserImageUrl);
return ResponseEntity.status(HttpStatus.OK)
.body(CommonResponse.createSuccess(imageUrlResponse));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.yapp.domain.match.presentation.dto.response;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
Copy link
Member

Choose a reason for hiding this comment

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

url 하나만 Response 객체로 만든 이유가 있으실까요 ?

Copy link
Member Author

Choose a reason for hiding this comment

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

json 형식으로 반환하는것이 PlainText로 반환하는 것보다 클라이언트가 사용할 때 편리할 것이라고 생각해서 Response 객체를 만들었습니다.
Map보다는 Response 객체를 만드는 것이 코드 통일성면이나 이후 확장성면에서 좋다고 생각합니다.

@NoArgsConstructor
@AllArgsConstructor
public class ImageUrlResponse {

private String url;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.yapp.domain.match.presentation.dto.response;

import java.util.List;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class MatchInfoResponse {
private Long matchId;
private String matchStatus;
private String shortIntroduce;
private String nickname;
private String birthYear;
private String location;
private String job;
private Integer matchedValueCount;
private List<String> matchedValueList;

@Builder
public MatchInfoResponse(Long matchId, String matchStatus, String shortIntroduce, String nickname, String birthYear,
String location, String job, Integer matchedValueCount, List<String> matchedValueList) {
this.matchId = matchId;
this.matchStatus = matchStatus;
this.shortIntroduce = shortIntroduce;
this.nickname = nickname;
this.birthYear = birthYear;
this.location = location;
this.job = job;
this.matchedValueCount = matchedValueCount;
this.matchedValueList = matchedValueList;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.yapp.domain.match.presentation.dto.response;

import java.time.LocalDate;
import java.time.Period;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.yapp.domain.profile.Profile;

@NoArgsConstructor
@Getter
@AllArgsConstructor
Copy link
Member

Choose a reason for hiding this comment

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

저는 Response 객체는 record를 사용했는데, 통일성을 위해 record를 사용하는 것은 어떨까요 ?

Copy link
Member Author

Choose a reason for hiding this comment

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

좋은 것 같습니다!
PR이 쌓여있어서...충돌이 날 것 같으니 다음 PR에 record로 전부 수정해놓겠습니다!

public class MatchProfileBasicResponse {

private Long matchId;
private String shortIntroduce;
private String nickname;
private String age;
private String birthYear;
private String location;
private String job;

public static MatchProfileBasicResponse fromProfile(Long matchId, Profile profile) {
String nickname = profile.getProfileBasic().getNickname();
LocalDate birthDate = profile.getProfileBasic().getBirthdate();
LocalDate now = LocalDate.now();
String age = String.valueOf(Period.between(birthDate, now).getYears());
String birthYearFormatted = String.valueOf(birthDate.getYear()).substring(2);
String location = profile.getProfileBasic().getLocation();
String job = profile.getProfileBasic().getJob();
return new MatchProfileBasicResponse(matchId, "", nickname, birthYearFormatted, age, location,
job);
}
}
Loading