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

[FEAT] 가까워지기 API 구현 #137

Merged
merged 11 commits into from
Mar 9, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package sopt.org.umbba.api.controller.closer;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import sopt.org.umbba.api.config.jwt.JwtProvider;
import sopt.org.umbba.api.controller.closer.dto.request.TodayCloserAnswerRequestDto;
import sopt.org.umbba.api.controller.closer.dto.response.TodayCloserQnAResponseDto;
import sopt.org.umbba.api.service.closer.CloserService;
import sopt.org.umbba.common.exception.SuccessType;
import sopt.org.umbba.common.exception.dto.ApiResponse;

import javax.validation.Valid;
import java.security.Principal;

import static sopt.org.umbba.common.exception.SuccessType.ANSWER_TODAY_CLOSER_QUESTION_SUCCESS;
import static sopt.org.umbba.common.exception.SuccessType.PASS_TO_NEXT_CLOSER_QUESTION_SUCCESS;

@Slf4j
@RestController
@RequestMapping("/closer")
@RequiredArgsConstructor
public class CloserController {

private final CloserService closerService;

@GetMapping("/today")
@ResponseStatus(HttpStatus.OK)
public ApiResponse<TodayCloserQnAResponseDto> getTodayCloserQnA(Principal principal) {
return ApiResponse.success(SuccessType.GET_TODAY_CLOSER_QNA_SUCCESS, closerService.getTodayCloserQnA(JwtProvider.getUserFromPrincial(principal)));
}

@PatchMapping("/answer")
@ResponseStatus(HttpStatus.OK)
public ApiResponse<?> answerTodayCloserQnA(Principal principal, @Valid @RequestBody final TodayCloserAnswerRequestDto request) {
closerService.answerTodayCloserQnA(JwtProvider.getUserFromPrincial(principal), request);
return ApiResponse.success(ANSWER_TODAY_CLOSER_QUESTION_SUCCESS);
}

@PatchMapping("/next")
@ResponseStatus(HttpStatus.OK)
public ApiResponse<?> passToNextCloserQnA(Principal principal) {
closerService.passToNextCloserQnA(JwtProvider.getUserFromPrincial(principal));
return ApiResponse.success(PASS_TO_NEXT_CLOSER_QUESTION_SUCCESS);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package sopt.org.umbba.api.controller.closer.dto.request;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;

@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class TodayCloserAnswerRequestDto {

@Min(value = 1, message = "답변은 1 혹은 2여야 합니다.")
@Max(value = 2, message = "답변은 1 혹은 2여야 합니다.")
int answer;

public static TodayCloserAnswerRequestDto of (int answer) {
return new TodayCloserAnswerRequestDto(answer);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package sopt.org.umbba.api.controller.closer.dto.response;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Builder;
import lombok.Getter;
import sopt.org.umbba.domain.domain.closer.CloserQnA;
import sopt.org.umbba.domain.domain.closer.CloserQuestion;

@Getter
@Builder
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class TodayCloserQnAResponseDto {

private Long closerQnaId;

private int responseCase;

private String balanceQuestion;
private String choiceAnswer1;
private String choiceAnswer2;

private String myChoice;
private String opponentChoice;

public static TodayCloserQnAResponseDto of(CloserQnA closerQna, int responseCase, boolean isMeChild) {

CloserQuestion closerQuestion = closerQna.getCloserQuestion();
int myAnswer;
int opponentAnswer;
if (isMeChild) {
myAnswer = closerQna.getChildAnswer();
opponentAnswer = closerQna.getParentAnswer();
} else {
myAnswer = closerQna.getParentAnswer();
opponentAnswer = closerQna.getChildAnswer();
}

String myChoice;
if (myAnswer == 0) {
myChoice = null;
} else if (myAnswer == 1) {
myChoice = closerQuestion.getChoiceAnswer1();
} else {
myChoice = closerQuestion.getChoiceAnswer2();
}
String opponentChoice;
if (opponentAnswer == 0) {
opponentChoice = null;
} else if (opponentAnswer == 1) {
opponentChoice = closerQuestion.getChoiceAnswer1();
} else {
opponentChoice = closerQuestion.getChoiceAnswer2();
}

if (responseCase == 3 && (myAnswer != opponentAnswer)) {
responseCase = 4;
}

return TodayCloserQnAResponseDto.builder()
.closerQnaId(closerQna.getId())
.responseCase(responseCase)
.balanceQuestion(closerQuestion.getBalanceQuestion())
.choiceAnswer1(closerQuestion.getChoiceAnswer1())
.choiceAnswer2(closerQuestion.getChoiceAnswer2())
.myChoice(myChoice)
.opponentChoice(opponentChoice)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package sopt.org.umbba.api.service.closer;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import sopt.org.umbba.api.controller.closer.dto.request.TodayCloserAnswerRequestDto;
import sopt.org.umbba.api.controller.closer.dto.response.TodayCloserQnAResponseDto;
import sopt.org.umbba.common.exception.ErrorType;
import sopt.org.umbba.common.exception.model.CustomException;
import sopt.org.umbba.domain.domain.closer.CloserQnA;
import sopt.org.umbba.domain.domain.closer.CloserQuestion;
import sopt.org.umbba.domain.domain.closer.repository.CloserQnARepository;
import sopt.org.umbba.domain.domain.closer.repository.CloserQuestionRepository;
import sopt.org.umbba.domain.domain.parentchild.Parentchild;
import sopt.org.umbba.domain.domain.user.User;
import sopt.org.umbba.domain.domain.user.repository.UserRepository;

import java.util.List;
import java.util.stream.Collectors;

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

private final UserRepository userRepository;
private final CloserQuestionRepository closerQuestionRepository;
private final CloserQnARepository closerQnARepository;

public TodayCloserQnAResponseDto getTodayCloserQnA(Long userId) {
User user = getUserById(userId);
Parentchild parentchild = user.getParentChild();
if (parentchild == null) {
throw new CustomException(ErrorType.USER_HAVE_NO_PARENTCHILD);
}

if (user.isMeChild()) {
int closerCount = parentchild.getCloserChildCount();
CloserQnA todayQnA = parentchild.getCloserQnaList().get(closerCount);

if (!todayQnA.isChildAnswer()) { // Case 1 (내가 답변하지 않은 경우)
return TodayCloserQnAResponseDto.of(todayQnA, 1, true);
} else if (!todayQnA.isParentAnswer()) { // Case 2 (상대가 답변하지 않은 경우)
return TodayCloserQnAResponseDto.of(todayQnA, 2, true);
} else { // Case 3,4 (둘다 답변한 경우)
return TodayCloserQnAResponseDto.of(todayQnA, 3, true);
}

} else {
int closerCount = parentchild.getCloserParentCount();
CloserQnA todayQnA = parentchild.getCloserQnaList().get(closerCount);

if (!todayQnA.isParentAnswer()) { // Case 1 (내가 답변하지 않은 경우)
return TodayCloserQnAResponseDto.of(todayQnA, 1, false);
} else if (!todayQnA.isChildAnswer()) { // Case 2 (상대가 답변하지 않은 경우)
return TodayCloserQnAResponseDto.of(todayQnA, 2, false);
} else { // Case 3,4 (둘다 답변한 경우)
return TodayCloserQnAResponseDto.of(todayQnA, 3, false);
}
}
}

@Transactional
public void answerTodayCloserQnA(Long userId, TodayCloserAnswerRequestDto request) {
User user = getUserById(userId);
Parentchild parentchild = user.getParentChild();
if (parentchild == null) {
throw new CustomException(ErrorType.USER_HAVE_NO_PARENTCHILD);
}

if (user.isMeChild()) {
int closerCount = parentchild.getCloserChildCount();
CloserQnA todayQnA = parentchild.getCloserQnaList().get(closerCount);

todayQnA.saveChildAnswer(request.getAnswer());
} else {
int closerCount = parentchild.getCloserParentCount();
CloserQnA todayQnA = parentchild.getCloserQnaList().get(closerCount);

todayQnA.saveParentAnswer(request.getAnswer());
}
}

@Transactional
public void passToNextCloserQnA(Long userId) {
User user = getUserById(userId);
Parentchild parentchild = user.getParentChild();
if (parentchild == null) {
throw new CustomException(ErrorType.USER_HAVE_NO_PARENTCHILD);
}

if (user.isMeChild()) {
if (parentchild.getCloserChildCount() < parentchild.getCloserParentCount()) {
parentchild.addCloserChildCount();
} else if (parentchild.getCloserChildCount() == parentchild.getCloserParentCount()) {
parentchild.addCloserChildCount();
CloserQuestion newCloserQuestion = closerQuestionRepository.findRandomExceptIds(getCloserQuestionIds(parentchild))
.orElseThrow(() -> new CustomException(ErrorType.NOT_FOUND_CLOSER_QUESTION));
CloserQnA newCloserQnA = CloserQnA.builder()
.closerQuestion(newCloserQuestion)
.isParentAnswer(false)
.isChildAnswer(false)
.build();
closerQnARepository.save(newCloserQnA);
parentchild.addCloserQna(newCloserQnA);
} else {
throw new CustomException(ErrorType.INVALID_COUNT_STATUS);
}
} else {
if (parentchild.getCloserParentCount() < parentchild.getCloserChildCount()) {
parentchild.addCloserParentCount();
} else if (parentchild.getCloserParentCount() == parentchild.getCloserChildCount()) {
parentchild.addCloserParentCount();
CloserQuestion newCloserQuestion = closerQuestionRepository.findRandomExceptIds(getCloserQuestionIds(parentchild))
.orElseThrow(() -> new CustomException(ErrorType.NOT_FOUND_CLOSER_QUESTION));
CloserQnA newCloserQnA = CloserQnA.builder()
.closerQuestion(newCloserQuestion)
.isParentAnswer(false)
.isChildAnswer(false)
.build();
closerQnARepository.save(newCloserQnA);
parentchild.addCloserQna(newCloserQnA);
} else {
throw new CustomException(ErrorType.INVALID_COUNT_STATUS);
}
}
}

private static List<Long> getCloserQuestionIds(Parentchild parentchild) {
return parentchild.getCloserQnaList().stream()
.map(closerQnA -> closerQnA.getCloserQuestion().getId())
.collect(Collectors.toList());
}

private User getUserById(Long userId) {

return userRepository.findById(userId).orElseThrow(
() -> new CustomException(ErrorType.INVALID_USER)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
import sopt.org.umbba.api.service.notification.NotificationService;
import sopt.org.umbba.common.exception.ErrorType;
import sopt.org.umbba.common.exception.model.CustomException;
import sopt.org.umbba.domain.domain.closer.CloserQnA;
import sopt.org.umbba.domain.domain.closer.CloserQuestion;
import sopt.org.umbba.domain.domain.closer.repository.CloserQnARepository;
import sopt.org.umbba.domain.domain.closer.repository.CloserQuestionRepository;
import sopt.org.umbba.domain.domain.parentchild.Parentchild;
import sopt.org.umbba.domain.domain.parentchild.dao.ParentchildDao;
import sopt.org.umbba.domain.domain.qna.*;
Expand Down Expand Up @@ -49,6 +53,9 @@ public class QnAService {
private final ParentchildDao parentchildDao;
private final NotificationService notificationService;

private final CloserQuestionRepository closerQuestionRepository;
private final CloserQnARepository closerQnARepository;

public TodayQnAResponseDto getTodayQnA(Long userId) {

User myUser = getUserById(userId);
Expand Down Expand Up @@ -195,7 +202,6 @@ public void filterFirstQuestion(Long userId) {
throw new CustomException(ErrorType.USER_HAVE_NO_PARENTCHILD);
}


// 첫번째 질문은 MVP 단에서는 고정
QnA newQnA = QnA.builder()
.question(questionRepository.findByType(FIX).get(0))
Expand All @@ -204,7 +210,6 @@ public void filterFirstQuestion(Long userId) {
.build();
qnARepository.save(newQnA);

parentchild.initQna();
parentchild.setQna(newQnA);
}

Expand Down Expand Up @@ -243,6 +248,20 @@ public void filterAllQuestion(Long userId) {
for (QnA qnA : forLogging) {
log.info(qnA.getQuestion().getParentQuestion());
}

// 가까워지기 QnA도 추가
if (parentchild.getCloserQnaList().isEmpty()) {
CloserQuestion firstCloserQuestion = closerQuestionRepository.findRandomExceptIds(new ArrayList<>())
.orElseThrow(() -> new CustomException(ErrorType.NOT_FOUND_CLOSER_QUESTION));

CloserQnA newCloserQnA = CloserQnA.builder()
.closerQuestion(firstCloserQuestion)
.isParentAnswer(false)
.isChildAnswer(false)
.build();
closerQnARepository.save(newCloserQnA);
parentchild.addCloserQna(newCloserQnA);
}
}

// 마이페이지 - 부모자식 관계 정보 조회
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ public enum ErrorType {
// Album
INVALID_BUCKET_PREFIX(HttpStatus.BAD_REQUEST, "유효하지 않은 S3 버킷 디렉토리명입니다."),

// Closer
INVALID_COUNT_STATUS(HttpStatus.BAD_REQUEST, "count 조건으로 인해 다음 가까워지기 질문으로 넘어갈 수 없습니다."),

/**
* 401 UNAUTHORIZED
*/
Expand Down Expand Up @@ -64,6 +67,7 @@ public enum ErrorType {
PARENTCHILD_HAVE_NO_OPPONENT(HttpStatus.NOT_FOUND, "부모자식 관계에 1명만 참여하고 있습니다."),
NOT_FOUND_SECTION(HttpStatus.NOT_FOUND, "해당 아이디와 일치하는 섹션이 없습니다."),
NOT_FOUND_ALBUM(HttpStatus.NOT_FOUND, "존재하지 않는 앨범입니다."),
NOT_FOUND_CLOSER_QUESTION(HttpStatus.NOT_FOUND, "일치하는 가까워지기 질문이 없습니다."),

/**
* About Apple (HttpStatus 고민)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ public enum SuccessType {
IMAGE_S3_DELETE_SUCCESS(HttpStatus.OK, "S3 버킷에서 이미지를 삭제하는 데 성공했습니다."),
DELETE_ALBUM_SUCCESS(HttpStatus.OK, "앨범의 기록 삭제에 성공했습니다."),
GET_ALBUM_LIST_SUCCESS(HttpStatus.OK, "앨범의 기록 목록 조회에 성공했습니다."),

GET_TODAY_CLOSER_QNA_SUCCESS(HttpStatus.OK, "오늘의 가까워지기 문답 조회에 성공했습니다."),
ANSWER_TODAY_CLOSER_QUESTION_SUCCESS(HttpStatus.OK, "오늘의 가까워지기 문답에 답변을 완료하였습니다."),
PASS_TO_NEXT_CLOSER_QUESTION_SUCCESS(HttpStatus.OK, "다음 가까워지기 문답으로 넘어가는 데에 성공했습니다."),

/**
* 201 CREATED
Expand Down
Loading
Loading