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

[SAMBAD-228] 모임원 답변 등록 시 리스트 요청 규격 수정 #89

Merged
merged 3 commits into from
Aug 7, 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
Expand Up @@ -4,9 +4,9 @@

import java.time.Clock;
import java.time.LocalDateTime;
import java.util.List;

import org.depromeet.sambad.moring.answer.application.AnswerService;
import org.depromeet.sambad.moring.answer.domain.Answer;
import org.depromeet.sambad.moring.event.application.EventService;
import org.depromeet.sambad.moring.meeting.answer.domain.MeetingAnswer;
import org.depromeet.sambad.moring.meeting.answer.presentation.exception.DuplicateMeetingAnswerException;
Expand Down Expand Up @@ -41,22 +41,38 @@ public class MeetingAnswerService {
public void save(Long userId, Long meetingId, Long meetingQuestionId, MeetingAnswerRequest request) {
MeetingMember loginMember = meetingMemberService.getByUserIdAndMeetingId(userId, meetingId);
MeetingQuestion meetingQuestion = meetingQuestionService.getById(meetingId, meetingQuestionId);

meetingQuestion.validateNotFinished(LocalDateTime.now(clock));
validateNonDuplicateMeetingAnswer(meetingQuestion.getId(), loginMember.getId());
meetingQuestion.validateMeetingAnswerCount(request.answerIds().size());

Answer selectedAnswer = answerService.getById(meetingQuestion.getQuestion().getId(), request.answerId());
MeetingAnswer meetingAnswer = MeetingAnswer
.builder()
.meetingMember(loginMember)
.meetingQuestion(meetingQuestion)
.answer(selectedAnswer)
.build();
meetingAnswerRepository.save(meetingAnswer);
saveMeetingAnswers(meetingQuestion, loginMember, request.answerIds());

eventService.inactivateLastEventByType(userId, meetingId, QUESTION_REGISTERED);
advanceToNextQuestionIfAllAnswered(meetingId, meetingQuestion);
}

public MyMeetingAnswerListResponse getMyList(Long userId, Long meetingId) {
MeetingMember loginMember = meetingMemberService.getByUserIdAndMeetingId(userId, meetingId);
return meetingAnswerRepository.findAllByMeetingMemberId(loginMember.getId());
}

private void saveMeetingAnswers(MeetingQuestion meetingQuestion, MeetingMember loginMember, List<Long> answerIds) {
Long questionId = meetingQuestion.getQuestion().getId();
List<MeetingAnswer> meetingAnswers = answerIds.stream()
.map(answerId -> answerService.getById(questionId, answerId))
.map(answer -> new MeetingAnswer(meetingQuestion, answer, loginMember))
.toList();
meetingAnswers.forEach(meetingAnswerRepository::save);
}

private void validateNonDuplicateMeetingAnswer(Long meetingQuestionId, Long meetingMemberId) {
boolean exists = meetingAnswerRepository.existsByMeetingMember(meetingQuestionId, meetingMemberId);
if (exists) {
throw new DuplicateMeetingAnswerException();
}
}

private void advanceToNextQuestionIfAllAnswered(Long meetingId, MeetingQuestion currentQuestion) {
boolean isAllAnswered = meetingAnswerRepository.isAllAnsweredByMeetingIdAndMeetingQuestionId(
meetingId, currentQuestion.getId());
Expand All @@ -71,16 +87,4 @@ private void advanceToNextQuestionIfAllAnswered(Long meetingId, MeetingQuestion
});
}
}

public MyMeetingAnswerListResponse getMyList(Long userId, Long meetingId) {
MeetingMember loginMember = meetingMemberService.getByUserIdAndMeetingId(userId, meetingId);
return meetingAnswerRepository.findAllByMeetingMemberId(loginMember.getId());
}

private void validateNonDuplicateMeetingAnswer(Long meetingQuestionId, Long meetingMemberId) {
boolean exists = meetingAnswerRepository.existsByMeetingMember(meetingQuestionId, meetingMemberId);
if (exists) {
throw new DuplicateMeetingAnswerException();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
package org.depromeet.sambad.moring.meeting.answer.infrastructure;

import static com.querydsl.core.types.dsl.Expressions.*;
import static com.querydsl.jpa.JPAExpressions.*;
import static org.depromeet.sambad.moring.meeting.answer.domain.QMeetingAnswer.*;
import static org.depromeet.sambad.moring.meeting.comment.domain.comment.QMeetingQuestionComment.*;
import static org.depromeet.sambad.moring.meeting.member.domain.QMeetingMember.*;
import static org.depromeet.sambad.moring.meeting.question.domain.QMeetingQuestion.*;

import java.util.List;
import java.util.Objects;

import org.depromeet.sambad.moring.answer.domain.Answer;
import org.depromeet.sambad.moring.meeting.answer.domain.MeetingAnswer;
import org.depromeet.sambad.moring.meeting.answer.infrastructure.dto.MyMeetingAnswerResponseCustom;
import org.depromeet.sambad.moring.meeting.answer.presentation.response.MyMeetingAnswerListResponse;
import org.depromeet.sambad.moring.meeting.comment.domain.comment.MeetingQuestionComment;
import org.depromeet.sambad.moring.meeting.member.domain.MeetingMember;
import org.depromeet.sambad.moring.meeting.question.domain.MeetingQuestion;
import org.springframework.stereotype.Repository;

import com.querydsl.core.Tuple;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.jpa.JPQLQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;

import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -86,23 +85,36 @@ public List<MeetingMember> findMeetingMembersSelectWith(Long meetingQuestionId,
}

public MyMeetingAnswerListResponse findAllByMeetingMemberId(Long meetingMemberId) {
List<MyMeetingAnswerResponseCustom> responseCustoms = queryFactory.select(Projections.constructor(
MyMeetingAnswerResponseCustom.class,
meetingAnswer.meetingQuestion.as("meetingQuestion"),
meetingAnswer.as("meetingAnswer"),
as(getMyComment(meetingMemberId), "comment")
))
.from(meetingAnswer)
.where(meetingAnswer.meetingMember.id.eq(meetingMemberId))
List<MeetingQuestion> meetingQuestions = queryFactory.select(meetingQuestion)
.from(meetingQuestion)
.join(meetingAnswer).on(meetingQuestion.eq(meetingAnswer.meetingQuestion)).fetchJoin()
.join(meetingMember).on(meetingMember.eq(meetingAnswer.meetingMember)).fetchJoin()
.where(meetingMember.id.eq(meetingMemberId))
.orderBy(meetingQuestion.createdAt.asc())
.fetch();

List<MyMeetingAnswerResponseCustom> responseCustoms = meetingQuestions.stream()
.map(question -> new MyMeetingAnswerResponseCustom(question.getTitle(),
getMyAnswers(meetingMemberId, question),
getMyComment(meetingMemberId, question)))
.toList();

return MyMeetingAnswerListResponse.from(responseCustoms);
}

private JPQLQuery<MeetingQuestionComment> getMyComment(Long memberId) {
return select(meetingQuestionComment)
private List<Answer> getMyAnswers(Long memberId, MeetingQuestion meetingQuestion) {
return queryFactory.select(meetingAnswer.answer)
.from(meetingAnswer)
.where(meetingAnswer.meetingQuestion.id.eq(meetingQuestion.getId()),
meetingAnswer.meetingMember.id.eq(memberId))
.fetch();
}

private String getMyComment(Long memberId, MeetingQuestion meetingQuestion) {
return queryFactory.select(meetingQuestionComment.content)
.from(meetingQuestionComment)
.where(meetingQuestionComment.meetingMember.id.eq(memberId),
meetingQuestionComment.meetingQuestion.eq(meetingAnswer.meetingQuestion));
meetingQuestionComment.meetingQuestion.eq(meetingQuestion))
.fetchFirst();
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
package org.depromeet.sambad.moring.meeting.answer.infrastructure.dto;

import org.depromeet.sambad.moring.meeting.answer.domain.MeetingAnswer;
import org.depromeet.sambad.moring.meeting.comment.domain.comment.MeetingQuestionComment;
import org.depromeet.sambad.moring.meeting.question.domain.MeetingQuestion;
import java.util.List;

import org.depromeet.sambad.moring.answer.domain.Answer;

import com.querydsl.core.annotations.QueryProjection;

public record MyMeetingAnswerResponseCustom(
MeetingQuestion meetingQuestion,
MeetingAnswer meetingAnswer,
MeetingQuestionComment comment
String meetingQuestionTitle,
List<Answer> meetingAnswers,
String comment
) {

@QueryProjection
public MyMeetingAnswerResponseCustom {
}

public List<String> getMeetingAnswers() {
return meetingAnswers.stream()
.map(Answer::getContent)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public class MeetingAnswerController {

@Operation(summary = "모임원 답변 등록")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "meetingQuestion(모임원 답변) 등록 성공"),
@ApiResponse(responseCode = "201", description = "모임원 답변 등록 성공"),
@ApiResponse(responseCode = "400", description = "ANSWER_COUNT_OUT_OF_RANGE"),
@ApiResponse(responseCode = "404", description = "MEETING_MEMBER_NOT_FOUND / NOT_FOUND_MEETING_QUESTION / "
+ "NOT_FOUND_ANSWER"),
@ApiResponse(responseCode = "409", description = "DUPLICATE_MEETING_ANSWER / FINISHED_MEETING_QUESTION")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package org.depromeet.sambad.moring.meeting.answer.presentation.request;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.*;

import java.util.List;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public record MeetingAnswerRequest(
@Schema(example = "1", description = "선택한 답변 식별자")
@Schema(description = "선택한 Answer ID 리스트", example = "[1, 2, 3]", requiredMode = REQUIRED)
@Size(max = 9)
@NotNull
Long answerId
List<Long> answerIds
) {
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.depromeet.sambad.moring.meeting.answer.presentation.response;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.*;

import java.util.List;

Expand All @@ -9,11 +9,7 @@
import io.swagger.v3.oas.annotations.media.Schema;

public record MyMeetingAnswerListResponse(
@Schema(
description = "내가 작성한 답변 목록",
example = "[{\"idx\":1,\"title\":\"갖고 싶은 초능력은?\",\"content\":\"분신술\",\"commentContent\":\"요새 할 일이 너무 많아요ㅠ 분신술로 시간 단축!!\"}]",
requiredMode = REQUIRED
)
@Schema(description = "내가 작성한 답변 목록", requiredMode = REQUIRED)
List<MyMeetingAnswerListResponseDetail> contents
) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ public record MyMeetingAnswerListResponseDetail(
@Schema(title = "릴레이 질문 제목", example = "갖고 싶은 초능력은?", requiredMode = REQUIRED)
String title,

@Schema(title = "유저가 선택한 답변", example = "분신술", requiredMode = REQUIRED)
String content,
@Schema(description = "유저가 선택한 답변 리스트", example = "[\"토마토\"]", requiredMode = REQUIRED)
List<String> content,

@Schema(title = "유저가 단 댓글", example = "요새 할 일이 너무 많아요ㅠ 분신술로 시간 단축!!",
description = "댓글이 없으면 null 응답합니다.", requiredMode = NOT_REQUIRED)
Expand All @@ -30,9 +30,9 @@ public static List<MyMeetingAnswerListResponseDetail> from(List<MyMeetingAnswerR
MyMeetingAnswerResponseCustom response = responseCustoms.get(i);
return new MyMeetingAnswerListResponseDetail(
i + 1,
response.meetingQuestion().getTitle(),
response.meetingAnswer().getAnswer().getContent(),
response.comment() != null ? response.comment().getContent() : null
response.meetingQuestionTitle(),
response.getMeetingAnswers(),
response.comment()
);
})
.toList();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.depromeet.sambad.moring.meeting.question.domain;

import static org.depromeet.sambad.moring.meeting.question.domain.MeetingQuestionStatus.NOT_STARTED;
import static org.depromeet.sambad.moring.meeting.question.domain.MeetingQuestionStatus.*;

import java.time.LocalDateTime;
import java.time.ZoneId;
Expand All @@ -15,6 +15,7 @@
import org.depromeet.sambad.moring.meeting.question.presentation.exception.FinishedMeetingQuestionException;
import org.depromeet.sambad.moring.meeting.question.presentation.exception.InvalidMeetingMemberTargetException;
import org.depromeet.sambad.moring.question.domain.Question;
import org.depromeet.sambad.moring.question.domain.QuestionType;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
Expand Down Expand Up @@ -138,6 +139,10 @@ public void validateNotFinished(LocalDateTime now) {
}
}

public void validateMeetingAnswerCount(int answerSize) {
QuestionType.validateAnswerCount(question.getQuestionType(), answerSize);
}

public Long getEpochMilliStartTime() {
return startTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
}
Expand All @@ -147,5 +152,4 @@ private void validateTarget(MeetingMember targetMember) {
throw new InvalidMeetingMemberTargetException();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ public record MeetingQuestionAndAnswerListResponse(
@Schema(description = "모임의 질문 ID", example = "!", requiredMode = REQUIRED)
Long meetingQuestionId,

@Schema(description = "질문 유형", examples = {"SINGLE_CHOICE", "MULTIPLE_CHOICE"}, requiredMode = REQUIRED)
@Schema(description = "질문 유형",
examples = {"SINGLE_CHOICE", "MULTIPLE_SHORT_CHOICE", "MULTIPLE_DESCRIPTIVE_CHOICE"},
requiredMode = REQUIRED)
QuestionType questionType,

@Schema(description = "질문과 답 관련 응답", requiredMode = REQUIRED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import org.depromeet.sambad.moring.common.domain.BaseTimeEntity;
import org.depromeet.sambad.moring.file.domain.FileEntity;
import org.depromeet.sambad.moring.meeting.question.domain.MeetingQuestion;
import org.depromeet.sambad.moring.question.presentation.exception.AnswerCountOutOfRangeException;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
Expand Down Expand Up @@ -58,7 +57,7 @@ public class Question extends BaseTimeEntity {
@Builder
public Question(String title, FileEntity questionImageFile, QuestionType questionType,
List<String> answerContents) {
validateAnswerCount(answerContents);
QuestionType.validateAnswerCount(questionType, answerContents.size());
this.questionType = questionType;
this.title = title;
this.questionImageFile = questionImageFile;
Expand Down Expand Up @@ -86,10 +85,4 @@ public String getQuestionImageUrl() {
public QuestionType getQuestionType() {
return questionType;
}

private void validateAnswerCount(List<String> answerContents) {
if (answerContents.size() < MIN_ANSWER_COUNT || answerContents.size() > MAX_ANSWER_COUNT) {
throw new AnswerCountOutOfRangeException();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
package org.depromeet.sambad.moring.question.domain;

import org.depromeet.sambad.moring.question.presentation.exception.AnswerCountOutOfRangeException;

public enum QuestionType {
SINGLE_CHOICE, MULTIPLE_SHORT_CHOICE, MULTIPLE_DESCRIPTIVE_CHOICE;

private static final int MIN_ANSWER_COUNT = 1;
private static final int MULTI_CHOICE_MAX_ANSWER_COUNT = 9;

public static void validateAnswerCount(QuestionType questionType, int answerCount) {
if (SINGLE_CHOICE.equals(questionType)) {
if (answerCount != MIN_ANSWER_COUNT) {
throw new AnswerCountOutOfRangeException();
}
return;
}
if (answerCount < MIN_ANSWER_COUNT || answerCount > MULTI_CHOICE_MAX_ANSWER_COUNT) {
throw new AnswerCountOutOfRangeException();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
package org.depromeet.sambad.moring.question.presentation.response;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.*;

import java.util.List;

import org.depromeet.sambad.moring.file.presentation.annotation.FullFileUrl;
import org.depromeet.sambad.moring.meeting.answer.presentation.response.AnswerResponse;
import org.depromeet.sambad.moring.question.domain.Question;
import org.depromeet.sambad.moring.question.domain.QuestionType;

import io.swagger.v3.oas.annotations.media.Schema;

public record QuestionResponse(
@Schema(description = "질문 ID", example = "1", requiredMode = REQUIRED)
Long questionId,

@FullFileUrl
@Schema(description = "질문 유형",
examples = {"SINGLE_CHOICE", "MULTIPLE_SHORT_CHOICE", "MULTIPLE_DESCRIPTIVE_CHOICE"},
requiredMode = REQUIRED)
QuestionType questionType,

@FullFileUrl
@Schema(description = "질문 이미지 URL", example = "https://example.com", requiredMode = REQUIRED)
String questionImageFileUrl,

Expand All @@ -28,6 +34,7 @@ public record QuestionResponse(
public static QuestionResponse from(final Question question) {
return new QuestionResponse(
question.getId(),
question.getQuestionType(),
question.getQuestionImageUrl(),
question.getTitle(),
AnswerResponse.from(question.getAnswers())
Expand Down
Loading
Loading