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

[BE] refactor: 구조화된 템플릿 사용 #980

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
Open
30 changes: 15 additions & 15 deletions backend/src/main/java/reviewme/DatabaseInitializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ public void setup() {
long growthOptionId = optionItemRepository.save(new OptionItem("🌱성장 마인드셋 (예: 새로운 분야나 잘 모르는 분야에 도전하는 마음, 꾸준한 노력으로 프로젝트 이전보다 성장하는 모습)",categoryOptionGroupId,5, OptionType.CATEGORY )).getId();

// 커뮤니케이션 능력 섹션
long checkBoxCommunicationQuestionId = questionRepository.save(new Question(true, QuestionType.CHECKBOX, "커뮤니케이션, 협업 능력에서 어떤 부분이 인상 깊었는지 선택해주세요.", null, 1)).getId();
long checkboxCommunicationQuestionId = questionRepository.save(new Question(true, QuestionType.CHECKBOX, "커뮤니케이션, 협업 능력에서 어떤 부분이 인상 깊었는지 선택해주세요.", null, 1)).getId();
Copy link
Contributor

Choose a reason for hiding this comment

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

checkbox 통일 좋네요 👍

long textCommunicationQuestionId = questionRepository.save(new Question(true, QuestionType.TEXT, CATEGORY_TEXT_QUESTION, "상황을 자세하게 기록할수록 ${revieweeName}에게 도움이 돼요. ${revieweeName} 덕분에 팀이 원활한 소통을 이뤘거나, 함께 일하면서 배울 점이 있었는지 떠올려 보세요.", 2)).getId();
long communicationSectionId = sectionRepository.save(new Section(VisibleType.CONDITIONAL, List.of(checkBoxCommunicationQuestionId, textCommunicationQuestionId), communicationOptionId, "커뮤니케이션 능력", CATEGORY_HEADER, 2)).getId();
long communicationOptionGroupId = optionGroupRepository.save(new OptionGroup(checkBoxCommunicationQuestionId, KEYWORD_CHECKBOX_MIN_COUNT, KEYWORD_CHECKBOX_MAX_COUNT)).getId();
long communicationSectionId = sectionRepository.save(new Section(VisibleType.CONDITIONAL, List.of(checkboxCommunicationQuestionId, textCommunicationQuestionId), communicationOptionId, "커뮤니케이션 능력", CATEGORY_HEADER, 2)).getId();
long communicationOptionGroupId = optionGroupRepository.save(new OptionGroup(checkboxCommunicationQuestionId, KEYWORD_CHECKBOX_MIN_COUNT, KEYWORD_CHECKBOX_MAX_COUNT)).getId();
optionItemRepository.save(new OptionItem("반대 의견을 내더라도 듣는 사람이 기분 나쁘지 않게 이야기해요.",communicationOptionGroupId,1, OptionType.KEYWORD ));
optionItemRepository.save(new OptionItem("팀원들의 의견을 잘 모아서 회의가 매끄럽게 진행되도록 해요.",communicationOptionGroupId,2, OptionType.KEYWORD ));
optionItemRepository.save(new OptionItem("팀의 분위기를 주도해요.",communicationOptionGroupId,3, OptionType.KEYWORD ));
Expand All @@ -66,10 +66,10 @@ public void setup() {
optionItemRepository.save(new OptionItem("서로 다른 분야간의 소통도 중요하게 생각해요.",communicationOptionGroupId,7, OptionType.KEYWORD ));

// 문제해결 능력 섹션
long checkBoxProblemSolvingQuestionId = questionRepository.save(new Question(true, QuestionType.CHECKBOX, "문제해결 능력에서 어느 부분이 인상 깊었는지 선택해주세요.", null, 1)).getId();
long checkboxProblemSolvingQuestionId = questionRepository.save(new Question(true, QuestionType.CHECKBOX, "문제해결 능력에서 어느 부분이 인상 깊었는지 선택해주세요.", null, 1)).getId();
long textProblemSolvingQuestionId = questionRepository.save(new Question(true, QuestionType.TEXT, CATEGORY_TEXT_QUESTION, "상황을 자세하게 기록할수록 ${revieweeName}에게 도움이 돼요. 어떤 문제 상황이 발생했고, ${revieweeName/가:이} 어떻게 해결했는지 그 과정을 떠올려 보세요.", 2)).getId();
long problemSolvingSectionId = sectionRepository.save(new Section(VisibleType.CONDITIONAL, List.of(checkBoxProblemSolvingQuestionId, textProblemSolvingQuestionId), problemSolvingOptionId, "문제해결 능력", CATEGORY_HEADER, 3)).getId();
long problemSolvingOptionGroupId = optionGroupRepository.save(new OptionGroup(checkBoxProblemSolvingQuestionId, KEYWORD_CHECKBOX_MIN_COUNT, KEYWORD_CHECKBOX_MAX_COUNT)).getId();
long problemSolvingSectionId = sectionRepository.save(new Section(VisibleType.CONDITIONAL, List.of(checkboxProblemSolvingQuestionId, textProblemSolvingQuestionId), problemSolvingOptionId, "문제해결 능력", CATEGORY_HEADER, 3)).getId();
long problemSolvingOptionGroupId = optionGroupRepository.save(new OptionGroup(checkboxProblemSolvingQuestionId, KEYWORD_CHECKBOX_MIN_COUNT, KEYWORD_CHECKBOX_MAX_COUNT)).getId();
optionItemRepository.save(new OptionItem("큰 문제를 작은 단위로 쪼개서 단계별로 해결해나가요.",problemSolvingOptionGroupId,1, OptionType.KEYWORD ));
optionItemRepository.save(new OptionItem("낯선 문제를 만나도 당황하지 않고 차분하게 풀어나가요.",problemSolvingOptionGroupId,2, OptionType.KEYWORD ));
optionItemRepository.save(new OptionItem("문제 해결을 위해 GPT등의 자원을 적극적으로 활용해요.",problemSolvingOptionGroupId,3, OptionType.KEYWORD ));
Expand All @@ -80,21 +80,21 @@ public void setup() {
optionItemRepository.save(new OptionItem("문제 원인과 해결책에 대한 가설을 세우고 직접 실험해봐요.",problemSolvingOptionGroupId,8, OptionType.KEYWORD ));

// 시간 관리 능력 섹션
long checkBoxTimeManagingQuestionId = questionRepository.save(new Question(true, QuestionType.CHECKBOX, "시간 관리 능력에서 어느 부분이 인상 깊었는지 선택해주세요.", null, 1)).getId();
long checkboxTimeManagingQuestionId = questionRepository.save(new Question(true, QuestionType.CHECKBOX, "시간 관리 능력에서 어느 부분이 인상 깊었는지 선택해주세요.", null, 1)).getId();
long textTimeManagingQuestionId = questionRepository.save(new Question(true, QuestionType.TEXT, CATEGORY_TEXT_QUESTION, "상황을 자세하게 기록할수록 ${revieweeName}에게 도움이 돼요. ${revieweeName} 덕분에 팀이 효율적으로 시간관리를 할 수 있었는지 떠올려 보세요.", 2)).getId();
long timeManagingSectionId = sectionRepository.save(new Section(VisibleType.CONDITIONAL, List.of(checkBoxTimeManagingQuestionId, textTimeManagingQuestionId), timeManagingOptionId, "시간관리 능력", CATEGORY_HEADER, 4)).getId();
long timeManagingOptionGroupId = optionGroupRepository.save(new OptionGroup(checkBoxTimeManagingQuestionId, KEYWORD_CHECKBOX_MIN_COUNT, KEYWORD_CHECKBOX_MAX_COUNT)).getId();
long timeManagingSectionId = sectionRepository.save(new Section(VisibleType.CONDITIONAL, List.of(checkboxTimeManagingQuestionId, textTimeManagingQuestionId), timeManagingOptionId, "시간관리 능력", CATEGORY_HEADER, 4)).getId();
long timeManagingOptionGroupId = optionGroupRepository.save(new OptionGroup(checkboxTimeManagingQuestionId, KEYWORD_CHECKBOX_MIN_COUNT, KEYWORD_CHECKBOX_MAX_COUNT)).getId();
optionItemRepository.save(new OptionItem("프로젝트의 일정과 주요 마일스톤을 설정하여 체계적으로 일정을 관리해요.",timeManagingOptionGroupId,1, OptionType.KEYWORD ));
optionItemRepository.save(new OptionItem("일정에 따라 마감 기한을 잘 지켜요.",timeManagingOptionGroupId,2, OptionType.KEYWORD ));
optionItemRepository.save(new OptionItem("업무의 중요도와 긴급성을 고려하여 우선 순위를 정하고, 그에 따라 작업을 분배해요.",timeManagingOptionGroupId,3, OptionType.KEYWORD ));
optionItemRepository.save(new OptionItem("예기치 않은 일정 변경에도 유연하게 대처해요.",timeManagingOptionGroupId,4, OptionType.KEYWORD ));
optionItemRepository.save(new OptionItem("회의 시간과 같은 약속된 시간을 잘 지켜요.",timeManagingOptionGroupId,5, OptionType.KEYWORD ));

// 기술 역량 섹션
long checkBoxTechnicalQuestionId = questionRepository.save(new Question(true, QuestionType.CHECKBOX, "기술 역량, 전문 지식에서 어떤 부분이 인상 깊었는지 선택해주세요.", null, 1)).getId();
long checkboxTechnicalQuestionId = questionRepository.save(new Question(true, QuestionType.CHECKBOX, "기술 역량, 전문 지식에서 어떤 부분이 인상 깊었는지 선택해주세요.", null, 1)).getId();
long textTechnicalQuestionId = questionRepository.save(new Question(true, QuestionType.TEXT, CATEGORY_TEXT_QUESTION, "상황을 자세하게 기록할수록 ${revieweeName}에게 도움이 돼요. ${revieweeName} 덕분에 기술적 역량, 전문 지식적으로 도움을 받은 경험을 떠올려 보세요.", 2)).getId();
long technicalSectionId = sectionRepository.save(new Section(VisibleType.CONDITIONAL, List.of(checkBoxTechnicalQuestionId, textTechnicalQuestionId), technicalOptionId, "기술 역량", CATEGORY_HEADER, 5)).getId();
long technicalOptionGroupId = optionGroupRepository.save(new OptionGroup(checkBoxTechnicalQuestionId, KEYWORD_CHECKBOX_MIN_COUNT, KEYWORD_CHECKBOX_MAX_COUNT)).getId();
long technicalSectionId = sectionRepository.save(new Section(VisibleType.CONDITIONAL, List.of(checkboxTechnicalQuestionId, textTechnicalQuestionId), technicalOptionId, "기술 역량", CATEGORY_HEADER, 5)).getId();
long technicalOptionGroupId = optionGroupRepository.save(new OptionGroup(checkboxTechnicalQuestionId, KEYWORD_CHECKBOX_MIN_COUNT, KEYWORD_CHECKBOX_MAX_COUNT)).getId();
optionItemRepository.save(new OptionItem("관련 언어 / 라이브러리 / 프레임워크 지식이 풍부해요.",technicalOptionGroupId,1, OptionType.KEYWORD ));
optionItemRepository.save(new OptionItem("인프라 지식이 풍부해요.",technicalOptionGroupId,2, OptionType.KEYWORD ));
optionItemRepository.save(new OptionItem("CS 지식이 풍부해요.",technicalOptionGroupId,3, OptionType.KEYWORD ));
Expand All @@ -109,10 +109,10 @@ public void setup() {
optionItemRepository.save(new OptionItem("지속적인 학습과 공유를 통해 팀의 기술 수준을 높였어요.",technicalOptionGroupId,12, OptionType.KEYWORD ));

// 성장 마인드셋 섹션
long checkBoxGrowthQuestionId = questionRepository.save(new Question(true, QuestionType.CHECKBOX, "성장 마인드셋에서 어떤 부분이 인상 깊었는지 선택해주세요.", null, 1)).getId();
long checkboxGrowthQuestionId = questionRepository.save(new Question(true, QuestionType.CHECKBOX, "성장 마인드셋에서 어떤 부분이 인상 깊었는지 선택해주세요.", null, 1)).getId();
long textGrowthQuestionId = questionRepository.save(new Question(true, QuestionType.TEXT, CATEGORY_TEXT_QUESTION, "상황을 자세하게 기록할수록 ${revieweeName}에게 도움이 돼요. 인상깊었던 ${revieweeName}의 성장 마인드셋을 떠올려 보세요.", 2)).getId();
long growthSectionId = sectionRepository.save(new Section(VisibleType.CONDITIONAL, List.of(checkBoxGrowthQuestionId, textGrowthQuestionId), growthOptionId, "성장 마인드셋", CATEGORY_HEADER, 6)).getId();
long growthOptionGroupId = optionGroupRepository.save(new OptionGroup(checkBoxGrowthQuestionId, KEYWORD_CHECKBOX_MIN_COUNT, KEYWORD_CHECKBOX_MAX_COUNT)).getId();
long growthSectionId = sectionRepository.save(new Section(VisibleType.CONDITIONAL, List.of(checkboxGrowthQuestionId, textGrowthQuestionId), growthOptionId, "성장 마인드셋", CATEGORY_HEADER, 6)).getId();
long growthOptionGroupId = optionGroupRepository.save(new OptionGroup(checkboxGrowthQuestionId, KEYWORD_CHECKBOX_MIN_COUNT, KEYWORD_CHECKBOX_MAX_COUNT)).getId();
optionItemRepository.save(new OptionItem("어떤 상황에도 긍정적인 태도로 임해요.",growthOptionGroupId,1, OptionType.KEYWORD ));
optionItemRepository.save(new OptionItem("주변 사람들한테 질문하는 것을 부끄러워하지 않아요.",growthOptionGroupId,2, OptionType.KEYWORD ));
optionItemRepository.save(new OptionItem("어려움이 있어도 끝까지 해내요.",growthOptionGroupId,3, OptionType.KEYWORD ));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
import reviewme.global.exception.BadRequestException;

@Slf4j
public class CheckBoxAnswerIncludedNotProvidedOptionItemException extends BadRequestException {
public class CheckboxAnswerIncludedNotProvidedOptionItemException extends BadRequestException {

public CheckBoxAnswerIncludedNotProvidedOptionItemException(long questionId,
public CheckboxAnswerIncludedNotProvidedOptionItemException(long questionId,
List<Long> providedOptionIds,
List<Long> submittedOptionIds) {
super("제공되는 선택지에 없는 선택지를 응답했어요.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ public class OptionGroupNotFoundByQuestionIdException extends DataInconsistencyE

public OptionGroupNotFoundByQuestionIdException(long questionId) {
super("서버 내부에 문제가 발생했습니다. 잠시 후 다시 시도해주세요.");
log.error("User submitted checkBoxAnswer without provided options - questionId: {}", questionId, this);
log.error("User submitted checkboxAnswer without provided options - questionId: {}", questionId, this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ private SectionAnswerResponse mapToSectionResponse(Review review, Section sectio
private QuestionAnswerResponse mapToQuestionResponse(Review review, Question question,
Map<Long, OptionGroup> optionGroupsByQuestion,
Map<Long, List<OptionItem>> optionItemsByOptionGroup) {
if (question.isSelectable()) {
if (question.isCheckboxType()) {
return mapToCheckboxQuestionResponse(review, question, optionGroupsByQuestion, optionItemsByOptionGroup);
} else {
return mapToTextQuestionResponse(review, question);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ private ReviewsGatheredByQuestionResponse mapToReviewsGatheredByQuestion(Questio
@Nullable
private List<TextResponse> mapToTextResponse(Question question, List<Answer> answers,
List<Highlight> highlights) {
if (question.isSelectable()) {
if (question.isCheckboxType()) {
return null;
}
Map<Long, List<Highlight>> answerIdHighlights = highlights.stream()
Expand Down Expand Up @@ -84,7 +84,7 @@ private List<HighlightResponse> mapToHighlightResponse(List<Highlight> highlight

@Nullable
private List<VoteResponse> mapToVoteResponse(Question question, List<Answer> answers) {
if (!question.isSelectable()) {
if (!question.isCheckboxType()) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ private ReviewListElementResponse mapToReviewListElementResponse(Review review,

private List<ReviewCategoryResponse> mapToCategoryOptionResponse(Review review,
List<OptionItem> categoryOptionItems) {
Set<Long> checkBoxOptionIds = review.getAnswersByType(CheckboxAnswer.class)
Set<Long> checkboxOptionIds = review.getAnswersByType(CheckboxAnswer.class)
.stream()
.flatMap(answer -> answer.getSelectedOptionIds().stream())
.map(CheckboxAnswerSelectedOption::getSelectedOptionId)
.collect(Collectors.toSet());
return categoryOptionItems.stream()
.filter(optionItem -> checkBoxOptionIds.contains(optionItem.getId()))
.filter(optionItem -> checkboxOptionIds.contains(optionItem.getId()))
.map(optionItem -> new ReviewCategoryResponse(optionItem.getId(), optionItem.getContent()))
.toList();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import reviewme.review.domain.Answer;
import reviewme.review.domain.CheckboxAnswerSelectedOption;
import reviewme.review.domain.CheckboxAnswer;
import reviewme.review.service.exception.CheckBoxAnswerIncludedNotProvidedOptionItemException;
import reviewme.review.service.exception.CheckboxAnswerIncludedNotProvidedOptionItemException;
import reviewme.review.service.exception.OptionGroupNotFoundByQuestionIdException;
import reviewme.review.service.exception.SelectedOptionItemCountOutOfRangeException;
import reviewme.review.service.exception.SubmittedQuestionNotFoundException;
Expand Down Expand Up @@ -53,7 +53,7 @@ private void validateOnlyIncludingProvidedOptionItem(CheckboxAnswer checkboxAnsw
List<Long> answeredOptionItemIds = extractAnsweredOptionItemIds(checkboxAnswer);

if (!new HashSet<>(providedOptionItemIds).containsAll(answeredOptionItemIds)) {
throw new CheckBoxAnswerIncludedNotProvidedOptionItemException(
throw new CheckboxAnswerIncludedNotProvidedOptionItemException(
checkboxAnswer.getQuestionId(), providedOptionItemIds, answeredOptionItemIds
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import reviewme.review.service.exception.SubmittedQuestionAndProvidedQuestionMismatchException;
import reviewme.template.domain.Question;
import reviewme.template.domain.Section;
import reviewme.template.domain.SectionQuestion;
import reviewme.template.repository.QuestionRepository;
import reviewme.template.repository.SectionRepository;

Expand Down Expand Up @@ -76,7 +75,6 @@ private Set<Long> extractDisplayedQuestionIds(Review review) {
return sections.stream()
.filter(section -> section.isVisibleBySelectedOptionIds(selectedOptionIds))
.flatMap(section -> section.getQuestionIds().stream())
.map(SectionQuestion::getQuestionId)
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public Question(boolean required, QuestionType questionType, String content, Str
this.position = position;
}

public boolean isSelectable() {
public boolean isCheckboxType() {
return questionType == QuestionType.CHECKBOX;
}

Expand Down
34 changes: 34 additions & 0 deletions backend/src/main/java/reviewme/template/domain/Section.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import reviewme.template.domain.exception.DuplicateQuestionIdException;
import reviewme.template.domain.exception.QuestionIdsNotExistException;

@Entity
@Table(name = "section")
Expand Down Expand Up @@ -52,6 +55,7 @@ public class Section {

public Section(VisibleType visibleType, List<Long> questionIds,
Long onSelectedOptionId, String sectionName, String header, int position) {
validateQuestionIds(questionIds);
this.visibleType = visibleType;
this.questionIds = questionIds.stream()
.map(SectionQuestion::new)
Expand All @@ -62,6 +66,36 @@ public Section(VisibleType visibleType, List<Long> questionIds,
this.position = position;
}

private void validateQuestionIds(List<Long> questionIds) {
validateNotEmpty(questionIds);
validateNoDuplicates(questionIds);
}

private void validateNotEmpty(List<Long> questionIds) {
if (questionIds == null || questionIds.isEmpty()) {
throw new QuestionIdsNotExistException();
}
}

private void validateNoDuplicates(List<Long> questionIds) {
int originalSize = questionIds.size();
int deduplicatedSize = new HashSet<>(questionIds).size();

if (originalSize != deduplicatedSize) {
throw new DuplicateQuestionIdException(questionIds);
}
}

public List<Long> getQuestionIds() {
return questionIds.stream()
.map(SectionQuestion::getQuestionId)
.toList();
}

public boolean isAlwaysVisible() {
return visibleType == VisibleType.ALWAYS;
}

public boolean isVisibleBySelectedOptionIds(Collection<Long> selectedOptionIds) {
return visibleType == VisibleType.ALWAYS || selectedOptionIds.contains(onSelectedOptionId);
}
Expand Down
Loading