Skip to content

Commit

Permalink
Merge pull request #137 from Team-Umbba/feat/#136-get_closer_api
Browse files Browse the repository at this point in the history
[FEAT] 가까워지기 API 구현
  • Loading branch information
ddongseop authored Mar 9, 2024
2 parents 6887693 + 6c5ecf8 commit d448f12
Show file tree
Hide file tree
Showing 12 changed files with 469 additions and 8 deletions.
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

0 comments on commit d448f12

Please sign in to comment.