From ee994ea3c25846b85101455606c01121442b52f4 Mon Sep 17 00:00:00 2001 From: donghoony Date: Sat, 10 Aug 2024 18:30:52 +0900 Subject: [PATCH 01/19] =?UTF-8?q?feat:=20=EC=9D=91=EB=8B=B5=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../detail/OptionGroupAnswerResponse.java | 11 +++++++++++ .../detail/OptionItemAnswerResponse.java | 8 ++++++++ .../response/detail/QuestionAnswerResponse.java | 16 ++++++++++++++++ .../response/detail/SectionAnswerResponse.java | 10 ++++++++++ .../response/detail/TemplateAnswerResponse.java | 11 +++++++++++ 5 files changed, 56 insertions(+) create mode 100644 backend/src/main/java/reviewme/review/service/dto/response/detail/OptionGroupAnswerResponse.java create mode 100644 backend/src/main/java/reviewme/review/service/dto/response/detail/OptionItemAnswerResponse.java create mode 100644 backend/src/main/java/reviewme/review/service/dto/response/detail/QuestionAnswerResponse.java create mode 100644 backend/src/main/java/reviewme/review/service/dto/response/detail/SectionAnswerResponse.java create mode 100644 backend/src/main/java/reviewme/review/service/dto/response/detail/TemplateAnswerResponse.java diff --git a/backend/src/main/java/reviewme/review/service/dto/response/detail/OptionGroupAnswerResponse.java b/backend/src/main/java/reviewme/review/service/dto/response/detail/OptionGroupAnswerResponse.java new file mode 100644 index 000000000..894dbaae8 --- /dev/null +++ b/backend/src/main/java/reviewme/review/service/dto/response/detail/OptionGroupAnswerResponse.java @@ -0,0 +1,11 @@ +package reviewme.review.service.dto.response.detail; + +import java.util.List; + +public record OptionGroupAnswerResponse( + long optionGroupId, + long minCount, + long maxCount, + List options +) { +} diff --git a/backend/src/main/java/reviewme/review/service/dto/response/detail/OptionItemAnswerResponse.java b/backend/src/main/java/reviewme/review/service/dto/response/detail/OptionItemAnswerResponse.java new file mode 100644 index 000000000..6bd424f5f --- /dev/null +++ b/backend/src/main/java/reviewme/review/service/dto/response/detail/OptionItemAnswerResponse.java @@ -0,0 +1,8 @@ +package reviewme.review.service.dto.response.detail; + +public record OptionItemAnswerResponse( + long optionId, + String content, + boolean isChecked +) { +} diff --git a/backend/src/main/java/reviewme/review/service/dto/response/detail/QuestionAnswerResponse.java b/backend/src/main/java/reviewme/review/service/dto/response/detail/QuestionAnswerResponse.java new file mode 100644 index 000000000..48672e414 --- /dev/null +++ b/backend/src/main/java/reviewme/review/service/dto/response/detail/QuestionAnswerResponse.java @@ -0,0 +1,16 @@ +package reviewme.review.service.dto.response.detail; + +import jakarta.annotation.Nullable; +import reviewme.question.domain.QuestionType; + +public record QuestionAnswerResponse( + long questionId, + boolean required, + QuestionType questionType, + String content, + boolean hasGuideline, + @Nullable String guideline, + @Nullable OptionGroupAnswerResponse optionGroup, + @Nullable String answer +) { +} diff --git a/backend/src/main/java/reviewme/review/service/dto/response/detail/SectionAnswerResponse.java b/backend/src/main/java/reviewme/review/service/dto/response/detail/SectionAnswerResponse.java new file mode 100644 index 000000000..ad2887644 --- /dev/null +++ b/backend/src/main/java/reviewme/review/service/dto/response/detail/SectionAnswerResponse.java @@ -0,0 +1,10 @@ +package reviewme.review.service.dto.response.detail; + +import java.util.List; + +public record SectionAnswerResponse( + long sectionId, + String header, + List questions +) { +} diff --git a/backend/src/main/java/reviewme/review/service/dto/response/detail/TemplateAnswerResponse.java b/backend/src/main/java/reviewme/review/service/dto/response/detail/TemplateAnswerResponse.java new file mode 100644 index 000000000..0cb061878 --- /dev/null +++ b/backend/src/main/java/reviewme/review/service/dto/response/detail/TemplateAnswerResponse.java @@ -0,0 +1,11 @@ +package reviewme.review.service.dto.response.detail; + +import java.util.List; + +public record TemplateAnswerResponse( + long formId, + String revieweeName, + String projectName, + List sections +) { +} From de26cc7827113e4a3f2b18da8ed83f161dd3b85e Mon Sep 17 00:00:00 2001 From: donghoony Date: Sat, 10 Aug 2024 23:40:52 +0900 Subject: [PATCH 02/19] =?UTF-8?q?feat:=20=EA=B0=9D=EC=B2=B4=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=9E=90,=20EqualsAndHashCode,=20=EC=A4=91=EA=B0=84?= =?UTF-8?q?=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=84=A4=EC=A0=95=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reviewme/question/domain/OptionGroup.java | 8 ++++++++ .../reviewme/question/domain/OptionItem.java | 8 ++++++++ .../reviewme/question/domain/Question2.java | 18 ++++++++++++++++++ .../reviewme/review/domain/CheckboxAnswer.java | 17 ++++++++++++++++- .../java/reviewme/review/domain/Review2.java | 18 ++++++++++++++++++ .../reviewme/review/domain/TextAnswer.java | 11 +++++++++-- .../java/reviewme/template/domain/Section.java | 15 ++++++++++++++- .../reviewme/template/domain/Template.java | 12 ++++++++++-- 8 files changed, 101 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/reviewme/question/domain/OptionGroup.java b/backend/src/main/java/reviewme/question/domain/OptionGroup.java index 330ff963a..61aa3d23a 100644 --- a/backend/src/main/java/reviewme/question/domain/OptionGroup.java +++ b/backend/src/main/java/reviewme/question/domain/OptionGroup.java @@ -7,12 +7,14 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Table(name = "option_group") @NoArgsConstructor(access = AccessLevel.PROTECTED) +@EqualsAndHashCode(of = "id") @Getter public class OptionGroup { @@ -28,4 +30,10 @@ public class OptionGroup { @Column(name = "max_selection_count", nullable = false) private int maxSelectionCount; + + public OptionGroup(long questionId, int minSelectionCount, int maxSelectionCount) { + this.questionId = questionId; + this.minSelectionCount = minSelectionCount; + this.maxSelectionCount = maxSelectionCount; + } } diff --git a/backend/src/main/java/reviewme/question/domain/OptionItem.java b/backend/src/main/java/reviewme/question/domain/OptionItem.java index bd318b945..152f9099d 100644 --- a/backend/src/main/java/reviewme/question/domain/OptionItem.java +++ b/backend/src/main/java/reviewme/question/domain/OptionItem.java @@ -7,12 +7,14 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Table(name = "option_item") @NoArgsConstructor(access = AccessLevel.PROTECTED) +@EqualsAndHashCode(of = "id") @Getter public class OptionItem { @@ -28,4 +30,10 @@ public class OptionItem { @Column(name = "position", nullable = false) private int position; + + public OptionItem(String content, long optionGroupId, int position) { + this.content = content; + this.optionGroupId = optionGroupId; + this.position = position; + } } diff --git a/backend/src/main/java/reviewme/question/domain/Question2.java b/backend/src/main/java/reviewme/question/domain/Question2.java index 55ae48c2a..6639346ac 100644 --- a/backend/src/main/java/reviewme/question/domain/Question2.java +++ b/backend/src/main/java/reviewme/question/domain/Question2.java @@ -9,12 +9,14 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Table(name = "question2") @NoArgsConstructor(access = AccessLevel.PROTECTED) +@EqualsAndHashCode(of = "id") @Getter public class Question2 { @@ -37,4 +39,20 @@ public class Question2 { @Column(name = "position", nullable = false) private int position; + + public Question2(boolean required, QuestionType questionType, String content, String guideline, int position) { + this.required = required; + this.questionType = questionType; + this.content = content; + this.guideline = guideline; + this.position = position; + } + + public boolean isSelectable() { + return questionType == QuestionType.CHECKBOX; + } + + public boolean hasGuideline() { + return guideline != null; + } } diff --git a/backend/src/main/java/reviewme/review/domain/CheckboxAnswer.java b/backend/src/main/java/reviewme/review/domain/CheckboxAnswer.java index ae8ce51fb..9e19b75f2 100644 --- a/backend/src/main/java/reviewme/review/domain/CheckboxAnswer.java +++ b/backend/src/main/java/reviewme/review/domain/CheckboxAnswer.java @@ -7,15 +7,18 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; import jakarta.persistence.Table; import java.util.List; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Table(name = "checkbox_answer") @NoArgsConstructor(access = AccessLevel.PROTECTED) +@EqualsAndHashCode(of = "id") @Getter public class CheckboxAnswer { @@ -23,10 +26,22 @@ public class CheckboxAnswer { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(name = "review_id", nullable = false, insertable = false, updatable = false) + private long reviewId; + @Column(name = "question_id", nullable = false) private long questionId; @ElementCollection - @CollectionTable(name = "selected_option_ids") + @CollectionTable( + name = "checkbox_answer_selected_option", + joinColumns = @JoinColumn(name = "checkbox_answer_id", nullable = false) + ) + @Column(name = "selected_option_id", nullable = false) private List selectedOptionIds; + + public CheckboxAnswer(long questionId, List selectedOptionIds) { + this.questionId = questionId; + this.selectedOptionIds = selectedOptionIds; + } } diff --git a/backend/src/main/java/reviewme/review/domain/Review2.java b/backend/src/main/java/reviewme/review/domain/Review2.java index c01612fe1..4a18134c3 100644 --- a/backend/src/main/java/reviewme/review/domain/Review2.java +++ b/backend/src/main/java/reviewme/review/domain/Review2.java @@ -10,13 +10,18 @@ import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Table(name = "review2") @NoArgsConstructor(access = AccessLevel.PROTECTED) +@EqualsAndHashCode(of = "id") @Getter public class Review2 { @@ -37,4 +42,17 @@ public class Review2 { @OneToMany(cascade = CascadeType.PERSIST) @JoinColumn(name = "review_id", nullable = false, updatable = false) private List checkboxAnswers; + + public Review2(long templateId, long reviewGroupId, + List textAnswers, List checkboxAnswers) { + this.templateId = templateId; + this.reviewGroupId = reviewGroupId; + this.textAnswers = textAnswers; + this.checkboxAnswers = checkboxAnswers; + } + + public Map getTextAnswers() { + return textAnswers.stream() + .collect(Collectors.toMap(TextAnswer::getQuestionId, Function.identity())); + } } diff --git a/backend/src/main/java/reviewme/review/domain/TextAnswer.java b/backend/src/main/java/reviewme/review/domain/TextAnswer.java index 32894645c..5200d6ff9 100644 --- a/backend/src/main/java/reviewme/review/domain/TextAnswer.java +++ b/backend/src/main/java/reviewme/review/domain/TextAnswer.java @@ -7,12 +7,14 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Table(name = "text_answer") @NoArgsConstructor(access = AccessLevel.PROTECTED) +@EqualsAndHashCode(of = "id") @Getter public class TextAnswer { @@ -23,6 +25,11 @@ public class TextAnswer { @Column(name = "question_id", nullable = false) private long questionId; - @Column(name = "text", nullable = false, length = 1_000) - private String text; + @Column(name = "content", nullable = false, length = 1_000) + private String content; + + public TextAnswer(long questionId, String content) { + this.questionId = questionId; + this.content = content; + } } diff --git a/backend/src/main/java/reviewme/template/domain/Section.java b/backend/src/main/java/reviewme/template/domain/Section.java index fec4f84d6..48175bb22 100644 --- a/backend/src/main/java/reviewme/template/domain/Section.java +++ b/backend/src/main/java/reviewme/template/domain/Section.java @@ -11,14 +11,17 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.Table; +import java.util.Collection; import java.util.List; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Table(name = "section") @NoArgsConstructor(access = AccessLevel.PROTECTED) +@EqualsAndHashCode(of = "id") @Getter public class Section { @@ -31,7 +34,8 @@ public class Section { private VisibleType visibleType; @ElementCollection - @CollectionTable(name = "question_ids", joinColumns = @JoinColumn(name = "section_id")) + @CollectionTable(name = "section_question", joinColumns = @JoinColumn(name = "section_id")) + @Column(name = "question_id", nullable = false) private List questionIds; @Column(name = "on_selected_option_id", nullable = true) @@ -42,4 +46,13 @@ public class Section { @Column(name = "position", nullable = false) private int position; + + public Section(VisibleType visibleType, List questionIds, + Long onSelectedOptionId, String header, int position) { + this.visibleType = visibleType; + this.questionIds = questionIds; + this.onSelectedOptionId = onSelectedOptionId; + this.header = header; + this.position = position; + } } diff --git a/backend/src/main/java/reviewme/template/domain/Template.java b/backend/src/main/java/reviewme/template/domain/Template.java index 310e51ced..9e8f229de 100644 --- a/backend/src/main/java/reviewme/template/domain/Template.java +++ b/backend/src/main/java/reviewme/template/domain/Template.java @@ -1,6 +1,7 @@ package reviewme.template.domain; import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; import jakarta.persistence.ElementCollection; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -10,12 +11,14 @@ import jakarta.persistence.Table; import java.util.List; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Table(name = "template") @NoArgsConstructor(access = AccessLevel.PROTECTED) +@EqualsAndHashCode(of = "id") @Getter public class Template { @@ -24,6 +27,11 @@ public class Template { private Long id; @ElementCollection - @CollectionTable(name = "section_ids", joinColumns = @JoinColumn(name = "template_id")) - List sectionIds; + @CollectionTable(name = "template_section", joinColumns = @JoinColumn(name = "template_id", nullable = false)) + @Column(name = "section_id", nullable = false) + private List sectionIds; + + public Template(List sectionIds) { + this.sectionIds = sectionIds; + } } From 8fafdaf05428f3c332adc2a0193ffca3988329a6 Mon Sep 17 00:00:00 2001 From: donghoony Date: Sat, 10 Aug 2024 23:41:13 +0900 Subject: [PATCH 03/19] =?UTF-8?q?feat:=20=EC=84=A0=ED=83=9D=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EA=B0=80=EC=8B=9C=EC=84=B1=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reviewme/template/domain/Section.java | 4 ++ .../reviewme/template/domain/SectionTest.java | 45 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 backend/src/test/java/reviewme/template/domain/SectionTest.java diff --git a/backend/src/main/java/reviewme/template/domain/Section.java b/backend/src/main/java/reviewme/template/domain/Section.java index 48175bb22..c3dc8f2df 100644 --- a/backend/src/main/java/reviewme/template/domain/Section.java +++ b/backend/src/main/java/reviewme/template/domain/Section.java @@ -55,4 +55,8 @@ public Section(VisibleType visibleType, List questionIds, this.header = header; this.position = position; } + + public boolean isVisibleBySelectedOptionIds(Collection selectedOptionIds) { + return visibleType == VisibleType.ALWAYS || selectedOptionIds.contains(onSelectedOptionId); + } } diff --git a/backend/src/test/java/reviewme/template/domain/SectionTest.java b/backend/src/test/java/reviewme/template/domain/SectionTest.java new file mode 100644 index 000000000..7757f768f --- /dev/null +++ b/backend/src/test/java/reviewme/template/domain/SectionTest.java @@ -0,0 +1,45 @@ +package reviewme.template.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.Test; + +class SectionTest { + + @Test + void 조건_옵션을_선택하면_섹션이_보인다() { + // given + Section section = new Section(VisibleType.CONDITIONAL, List.of(), 1L, "1", 1); + + // when + boolean actual = section.isVisibleBySelectedOptionIds(List.of(1L, 2L, 3L)); + + // then + assertThat(actual).isTrue(); + } + + @Test + void 조건_옵션을_선택하지_않으면_섹션이_보이지_않는다() { + // given + Section section = new Section(VisibleType.CONDITIONAL, List.of(), 1L, "1", 1); + + // when + boolean actual = section.isVisibleBySelectedOptionIds(List.of(4L, 5L, 6L)); + + // then + assertThat(actual).isFalse(); + } + + @Test + void 타입이_ALWAYS라면_조건과_상관없이_모두_보인다() { + // given + Section section = new Section(VisibleType.ALWAYS, List.of(), null, "1", 1); + + // when + boolean actual = section.isVisibleBySelectedOptionIds(List.of()); + + // then + assertThat(actual).isTrue(); + } +} From 6c1c4a93cb1181f71096fcaca2e629033a2ac2c0 Mon Sep 17 00:00:00 2001 From: donghoony Date: Sat, 10 Aug 2024 23:47:08 +0900 Subject: [PATCH 04/19] =?UTF-8?q?feat:=20=ED=85=9C=ED=94=8C=EB=A6=BF,=20?= =?UTF-8?q?=EC=84=B9=EC=85=98=20=EC=A0=80=EC=9E=A5=EC=86=8C,=20=EC=84=B9?= =?UTF-8?q?=EC=85=98=20=EC=88=9C=EC=84=9C=EB=8C=80=EB=A1=9C=20=EB=B6=88?= =?UTF-8?q?=EB=9F=AC=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/SectionRepository.java | 18 +++++++++ .../repository/TemplateRepository.java | 7 ++++ .../repository/SectionRepositoryTest.java | 38 +++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 backend/src/main/java/reviewme/template/repository/SectionRepository.java create mode 100644 backend/src/main/java/reviewme/template/repository/TemplateRepository.java create mode 100644 backend/src/test/java/reviewme/template/repository/SectionRepositoryTest.java diff --git a/backend/src/main/java/reviewme/template/repository/SectionRepository.java b/backend/src/main/java/reviewme/template/repository/SectionRepository.java new file mode 100644 index 000000000..a2a2bb35c --- /dev/null +++ b/backend/src/main/java/reviewme/template/repository/SectionRepository.java @@ -0,0 +1,18 @@ +package reviewme.template.repository; + +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import reviewme.template.domain.Section; + +public interface SectionRepository extends JpaRepository { + + @Query(value = "" + + "SELECT * FROM section s LEFT JOIN template_section ts " + + "ON ts.section_id = s.id " + + "WHERE ts.template_id = :templateId " + + "ORDER BY s.position ASC", + nativeQuery = true + ) + List
findAllByTemplateId(long templateId); +} diff --git a/backend/src/main/java/reviewme/template/repository/TemplateRepository.java b/backend/src/main/java/reviewme/template/repository/TemplateRepository.java new file mode 100644 index 000000000..48996a159 --- /dev/null +++ b/backend/src/main/java/reviewme/template/repository/TemplateRepository.java @@ -0,0 +1,7 @@ +package reviewme.template.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import reviewme.template.domain.Template; + +public interface TemplateRepository extends JpaRepository { +} diff --git a/backend/src/test/java/reviewme/template/repository/SectionRepositoryTest.java b/backend/src/test/java/reviewme/template/repository/SectionRepositoryTest.java new file mode 100644 index 000000000..8eb22ac13 --- /dev/null +++ b/backend/src/test/java/reviewme/template/repository/SectionRepositoryTest.java @@ -0,0 +1,38 @@ +package reviewme.template.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import reviewme.template.domain.Section; +import reviewme.template.domain.Template; +import reviewme.template.domain.VisibleType; + +@DataJpaTest +class SectionRepositoryTest { + + @Autowired + private SectionRepository sectionRepository; + @Autowired + private TemplateRepository templateRepository; + + @Test + void 템플릿_아이디로_섹션을_불러온다() { + // given + Section section1 = sectionRepository.save(new Section(VisibleType.ALWAYS, List.of(), null, "1", 1)); + Section section2 = sectionRepository.save(new Section(VisibleType.ALWAYS, List.of(), null, "2", 1)); + Section section3 = sectionRepository.save(new Section(VisibleType.ALWAYS, List.of(), null, "3", 1)); + sectionRepository.save(new Section(VisibleType.ALWAYS, List.of(), null, "4", 1)); + Template template = templateRepository.save( + new Template(List.of(section1.getId(), section2.getId(), section3.getId())) + ); + + // when + List
actual = sectionRepository.findAllByTemplateId(template.getId()); + + // then + assertThat(actual).containsExactly(section1, section2, section3); + } +} From 747278d6bdd51226b2efd21a48a2b29548bfb6f8 Mon Sep 17 00:00:00 2001 From: donghoony Date: Sat, 10 Aug 2024 23:47:26 +0900 Subject: [PATCH 05/19] =?UTF-8?q?feat:=20=EC=A7=88=EB=AC=B8=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=EC=97=90=EC=84=9C=20=EC=88=9C=EC=84=9C?= =?UTF-8?q?=EB=8C=80=EB=A1=9C=20=EC=A7=88=EB=AC=B8=20=EB=B6=88=EB=9F=AC?= =?UTF-8?q?=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/QuestionRepository2.java | 19 +++++++++ .../repository/QuestionRepository2Test.java | 42 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 backend/src/main/java/reviewme/review/repository/QuestionRepository2.java create mode 100644 backend/src/test/java/reviewme/review/repository/QuestionRepository2Test.java diff --git a/backend/src/main/java/reviewme/review/repository/QuestionRepository2.java b/backend/src/main/java/reviewme/review/repository/QuestionRepository2.java new file mode 100644 index 000000000..2e2bf3d76 --- /dev/null +++ b/backend/src/main/java/reviewme/review/repository/QuestionRepository2.java @@ -0,0 +1,19 @@ +package reviewme.review.repository; + +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import reviewme.question.domain.Question2; + +public interface QuestionRepository2 extends JpaRepository { + + @Query(value = "" + + "SELECT * FROM question2 q " + + "LEFT JOIN section_question sq " + + "ON sq.question_id = q.id " + + "WHERE sq.section_id = :sectionId " + + "ORDER BY q.position ASC", + nativeQuery = true + ) + List findAllBySectionId(long sectionId); +} diff --git a/backend/src/test/java/reviewme/review/repository/QuestionRepository2Test.java b/backend/src/test/java/reviewme/review/repository/QuestionRepository2Test.java new file mode 100644 index 000000000..d84c01de0 --- /dev/null +++ b/backend/src/test/java/reviewme/review/repository/QuestionRepository2Test.java @@ -0,0 +1,42 @@ +package reviewme.review.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import reviewme.question.domain.Question2; +import reviewme.question.domain.QuestionType; +import reviewme.template.domain.Section; +import reviewme.template.domain.VisibleType; +import reviewme.template.repository.SectionRepository; + +@DataJpaTest +class QuestionRepository2Test { + + @Autowired + private QuestionRepository2 questionRepository; + + @Autowired + private SectionRepository sectionRepository; + + @Test + void 섹션_아이디로_질문_목록을_순서대로_가져온다() { + // given + Question2 question1 = questionRepository.save(new Question2(true, QuestionType.TEXT, "질문1", null, 1)); + Question2 question2 = questionRepository.save(new Question2(true, QuestionType.TEXT, "질문2", null, 2)); + Question2 question3 = questionRepository.save(new Question2(true, QuestionType.TEXT, "질문3", null, 3)); + questionRepository.save(new Question2(true, QuestionType.TEXT, "질문4", null, 1)); + + List questionIds = List.of(question3.getId(), question1.getId(), question2.getId()); + Section section = sectionRepository.save(new Section(VisibleType.ALWAYS, questionIds, null, "header", 0)); + + // when + List actual = questionRepository.findAllBySectionId(section.getId()); + + // then + assertThat(actual).extracting(Question2::getId) + .containsExactly(question1.getId(), question2.getId(), question3.getId()); + } +} From 0657ade8dd2c7ea81409d43a94d20ed4a8ba126c Mon Sep 17 00:00:00 2001 From: donghoony Date: Sat, 10 Aug 2024 23:48:11 +0900 Subject: [PATCH 06/19] =?UTF-8?q?feat:=20=EC=98=B5=EC=85=98=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EC=86=8C,=20=EC=84=A0=ED=83=9D=20=ED=95=AD=EB=AA=A9?= =?UTF-8?q?=EC=9D=84=20=EB=A6=AC=EB=B7=B0/=EC=A7=88=EB=AC=B8=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=ED=95=84=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OptionGroupNotFoundException.java | 13 +++ .../repository/OptionGroupRepository.java | 16 ++++ .../repository/OptionItemRepository.java | 32 ++++++++ .../review/repository/ReviewRepository2.java | 10 +++ .../repository/OptionItemRepositoryTest.java | 79 +++++++++++++++++++ 5 files changed, 150 insertions(+) create mode 100644 backend/src/main/java/reviewme/question/domain/exception/OptionGroupNotFoundException.java create mode 100644 backend/src/main/java/reviewme/question/repository/OptionGroupRepository.java create mode 100644 backend/src/main/java/reviewme/question/repository/OptionItemRepository.java create mode 100644 backend/src/main/java/reviewme/review/repository/ReviewRepository2.java create mode 100644 backend/src/test/java/reviewme/question/repository/OptionItemRepositoryTest.java diff --git a/backend/src/main/java/reviewme/question/domain/exception/OptionGroupNotFoundException.java b/backend/src/main/java/reviewme/question/domain/exception/OptionGroupNotFoundException.java new file mode 100644 index 000000000..885df39e9 --- /dev/null +++ b/backend/src/main/java/reviewme/question/domain/exception/OptionGroupNotFoundException.java @@ -0,0 +1,13 @@ +package reviewme.question.domain.exception; + +import lombok.extern.slf4j.Slf4j; +import reviewme.global.exception.NotFoundException; + +@Slf4j +public class OptionGroupNotFoundException extends NotFoundException { + + public OptionGroupNotFoundException(long questionId) { + super("질문에 해당하는 체크박스 그룹을 찾을 수 없어요."); + log.info("OptionGroup not found for questionId: {}", questionId); + } +} diff --git a/backend/src/main/java/reviewme/question/repository/OptionGroupRepository.java b/backend/src/main/java/reviewme/question/repository/OptionGroupRepository.java new file mode 100644 index 000000000..700f7293f --- /dev/null +++ b/backend/src/main/java/reviewme/question/repository/OptionGroupRepository.java @@ -0,0 +1,16 @@ +package reviewme.question.repository; + +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import reviewme.question.domain.OptionGroup; +import reviewme.question.domain.exception.OptionGroupNotFoundException; + +public interface OptionGroupRepository extends JpaRepository { + + Optional findByQuestionId(long questionId); + + default OptionGroup getByQuestionId(long questionId) { + return findByQuestionId(questionId) + .orElseThrow(() -> new OptionGroupNotFoundException(questionId)); + } +} diff --git a/backend/src/main/java/reviewme/question/repository/OptionItemRepository.java b/backend/src/main/java/reviewme/question/repository/OptionItemRepository.java new file mode 100644 index 000000000..b7f3da82a --- /dev/null +++ b/backend/src/main/java/reviewme/question/repository/OptionItemRepository.java @@ -0,0 +1,32 @@ +package reviewme.question.repository; + +import java.util.List; +import java.util.Set; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import reviewme.question.domain.OptionItem; + +public interface OptionItemRepository extends JpaRepository { + + @Query(value = "" + + "SELECT o.id FROM option_item o " + + "LEFT JOIN checkbox_answer ca " + + "LEFT JOIN checkbox_answer_selected_option c " + + "ON c.checkbox_answer_id = ca.id " + + "WHERE ca.review_id = :reviewId " + + "AND c.selected_option_id = o.id", + nativeQuery = true) + Set findSelectedOptionItemIdsByReviewId(long reviewId); + + @Query(value = "" + + "SELECT o.* FROM option_item o " + + "LEFT JOIN checkbox_answer ca " + + "LEFT JOIN checkbox_answer_selected_option c " + + "ON c.checkbox_answer_id = ca.id " + + "WHERE ca.review_id = :reviewId " + + "AND ca.question_id = :questionId " + + "AND c.selected_option_id = o.id " + + "ORDER BY o.position", + nativeQuery = true) + List findSelectedOptionItemsByReviewIdAndQuestionId(long reviewId, long questionId); +} diff --git a/backend/src/main/java/reviewme/review/repository/ReviewRepository2.java b/backend/src/main/java/reviewme/review/repository/ReviewRepository2.java new file mode 100644 index 000000000..2553956b2 --- /dev/null +++ b/backend/src/main/java/reviewme/review/repository/ReviewRepository2.java @@ -0,0 +1,10 @@ +package reviewme.review.repository; + +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import reviewme.review.domain.Review2; + +public interface ReviewRepository2 extends JpaRepository { + + Optional findByIdAndReviewGroupId(long reviewId, long reviewGroupId); +} diff --git a/backend/src/test/java/reviewme/question/repository/OptionItemRepositoryTest.java b/backend/src/test/java/reviewme/question/repository/OptionItemRepositoryTest.java new file mode 100644 index 000000000..8753921a4 --- /dev/null +++ b/backend/src/test/java/reviewme/question/repository/OptionItemRepositoryTest.java @@ -0,0 +1,79 @@ +package reviewme.question.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import reviewme.question.domain.OptionItem; +import reviewme.question.domain.Question2; +import reviewme.question.domain.QuestionType; +import reviewme.review.domain.CheckboxAnswer; +import reviewme.review.domain.Review2; +import reviewme.review.repository.QuestionRepository2; +import reviewme.review.repository.ReviewRepository2; + +@DataJpaTest +class OptionItemRepositoryTest { + + @Autowired + private OptionItemRepository optionItemRepository; + + @Autowired + private ReviewRepository2 reviewRepository; + + @Autowired + private QuestionRepository2 questionRepository; + + @Test + void 리뷰_아이디로_선택한_옵션_아이템_아이디를_불러온다() { + // given + long optionId1 = optionItemRepository.save(new OptionItem("1", 0, 1)).getId(); + long optionId2 = optionItemRepository.save(new OptionItem("2", 0, 1)).getId(); + long optionId3 = optionItemRepository.save(new OptionItem("3", 0, 1)).getId(); + long optionId4 = optionItemRepository.save(new OptionItem("4", 0, 1)).getId(); + optionItemRepository.save(new OptionItem("5", 0, 1)); + + List checkboxAnswers = List.of( + new CheckboxAnswer(1, List.of(optionId1, optionId2)), + new CheckboxAnswer(2, List.of(optionId3, optionId4)) + ); + Review2 review = reviewRepository.save(new Review2(0, 0, List.of(), checkboxAnswers)); + + // when + Set actual = optionItemRepository.findSelectedOptionItemIdsByReviewId(review.getId()); + + // then + assertThat(actual).containsExactlyInAnyOrder(optionId1, optionId2, optionId3, optionId4); + } + + @Test + void 리뷰_아이디와_질문_아이디로_선택한_옵션_아이템을_순서대로_불러온다() { + // given + long optionId1 = optionItemRepository.save(new OptionItem("1", 0, 3)).getId(); + long optionId2 = optionItemRepository.save(new OptionItem("2", 0, 2)).getId(); + long optionId3 = optionItemRepository.save(new OptionItem("3", 0, 1)).getId(); + long optionId4 = optionItemRepository.save(new OptionItem("4", 0, 1)).getId(); + optionItemRepository.save(new OptionItem("5", 0, 1)); + + List checkboxAnswers = List.of( + new CheckboxAnswer(1, List.of(optionId1, optionId3)), + new CheckboxAnswer(2, List.of(optionId4)) + ); + Question2 question1 = questionRepository.save(new Question2(true, QuestionType.CHECKBOX, "질문", null, 1)); + questionRepository.save(new Question2(true, QuestionType.CHECKBOX, "질문", null, 1)); + + Review2 review = reviewRepository.save(new Review2(0, 0, List.of(), checkboxAnswers)); + + // when + List actual = optionItemRepository.findSelectedOptionItemsByReviewIdAndQuestionId( + review.getId(), question1.getId() + ); + + // then + assertThat(actual).extracting(OptionItem::getId).containsExactly(optionId3, optionId1); + } + +} From 9e719c0cd338a5e0f0bb2c27969e981b59006f3f Mon Sep 17 00:00:00 2001 From: donghoony Date: Sat, 10 Aug 2024 23:48:54 +0900 Subject: [PATCH 07/19] style: apply code convention --- .../reviewme/question/repository/OptionGroupRepository.java | 4 ++-- .../reviewme/question/repository/OptionItemRepository.java | 4 ++-- .../java/reviewme/review/repository/ReviewRepositoryTest.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/reviewme/question/repository/OptionGroupRepository.java b/backend/src/main/java/reviewme/question/repository/OptionGroupRepository.java index 700f7293f..421dc0eeb 100644 --- a/backend/src/main/java/reviewme/question/repository/OptionGroupRepository.java +++ b/backend/src/main/java/reviewme/question/repository/OptionGroupRepository.java @@ -10,7 +10,7 @@ public interface OptionGroupRepository extends JpaRepository Optional findByQuestionId(long questionId); default OptionGroup getByQuestionId(long questionId) { - return findByQuestionId(questionId) - .orElseThrow(() -> new OptionGroupNotFoundException(questionId)); + return findByQuestionId(questionId) + .orElseThrow(() -> new OptionGroupNotFoundException(questionId)); } } diff --git a/backend/src/main/java/reviewme/question/repository/OptionItemRepository.java b/backend/src/main/java/reviewme/question/repository/OptionItemRepository.java index b7f3da82a..5caba7fea 100644 --- a/backend/src/main/java/reviewme/question/repository/OptionItemRepository.java +++ b/backend/src/main/java/reviewme/question/repository/OptionItemRepository.java @@ -10,7 +10,7 @@ public interface OptionItemRepository extends JpaRepository { @Query(value = "" + "SELECT o.id FROM option_item o " + - "LEFT JOIN checkbox_answer ca " + + "LEFT JOIN checkbox_answer ca " + "LEFT JOIN checkbox_answer_selected_option c " + "ON c.checkbox_answer_id = ca.id " + "WHERE ca.review_id = :reviewId " + @@ -20,7 +20,7 @@ public interface OptionItemRepository extends JpaRepository { @Query(value = "" + "SELECT o.* FROM option_item o " + - "LEFT JOIN checkbox_answer ca " + + "LEFT JOIN checkbox_answer ca " + "LEFT JOIN checkbox_answer_selected_option c " + "ON c.checkbox_answer_id = ca.id " + "WHERE ca.review_id = :reviewId " + diff --git a/backend/src/test/java/reviewme/review/repository/ReviewRepositoryTest.java b/backend/src/test/java/reviewme/review/repository/ReviewRepositoryTest.java index 7b8adda28..517707082 100644 --- a/backend/src/test/java/reviewme/review/repository/ReviewRepositoryTest.java +++ b/backend/src/test/java/reviewme/review/repository/ReviewRepositoryTest.java @@ -28,6 +28,6 @@ class ReviewRepositoryTest { // then assertThat(actual).map(Review::getId) - .containsExactly(review4.getId(), review2.getId(), review1.getId(), review3.getId()); + .containsExactly(review4.getId(), review2.getId(), review1.getId(), review3.getId()); } } From 5ab81c3056ca7ee94af5989e738f45a8dcdc5ea1 Mon Sep 17 00:00:00 2001 From: donghoony Date: Sun, 11 Aug 2024 00:35:52 +0900 Subject: [PATCH 08/19] =?UTF-8?q?feat:=20=EC=84=9C=EC=88=A0=ED=98=95=20?= =?UTF-8?q?=EC=A7=88=EB=AC=B8=20=EC=9D=BC=EA=B8=89=20=EC=BB=AC=EB=A0=89?= =?UTF-8?q?=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/reviewme/review/domain/Review2.java | 13 ++------ .../reviewme/review/domain/TextAnswers.java | 26 ++++++++++++++++ .../TextAnswerNotFoundException.java | 13 ++++++++ .../review/domain/TextAnswersTest.java | 31 +++++++++++++++++++ 4 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 backend/src/main/java/reviewme/review/domain/TextAnswers.java create mode 100644 backend/src/main/java/reviewme/review/domain/exception/TextAnswerNotFoundException.java create mode 100644 backend/src/test/java/reviewme/review/domain/TextAnswersTest.java diff --git a/backend/src/main/java/reviewme/review/domain/Review2.java b/backend/src/main/java/reviewme/review/domain/Review2.java index 4a18134c3..def5a8f59 100644 --- a/backend/src/main/java/reviewme/review/domain/Review2.java +++ b/backend/src/main/java/reviewme/review/domain/Review2.java @@ -3,6 +3,7 @@ import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -10,9 +11,6 @@ import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; import lombok.AccessLevel; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -35,11 +33,11 @@ public class Review2 { @Column(name = "review_group_id", nullable = false) private long reviewGroupId; - @OneToMany(cascade = CascadeType.PERSIST) + @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) @JoinColumn(name = "review_id", nullable = false, updatable = false) private List textAnswers; - @OneToMany(cascade = CascadeType.PERSIST) + @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) @JoinColumn(name = "review_id", nullable = false, updatable = false) private List checkboxAnswers; @@ -50,9 +48,4 @@ public Review2(long templateId, long reviewGroupId, this.textAnswers = textAnswers; this.checkboxAnswers = checkboxAnswers; } - - public Map getTextAnswers() { - return textAnswers.stream() - .collect(Collectors.toMap(TextAnswer::getQuestionId, Function.identity())); - } } diff --git a/backend/src/main/java/reviewme/review/domain/TextAnswers.java b/backend/src/main/java/reviewme/review/domain/TextAnswers.java new file mode 100644 index 000000000..2a7be122f --- /dev/null +++ b/backend/src/main/java/reviewme/review/domain/TextAnswers.java @@ -0,0 +1,26 @@ +package reviewme.review.domain; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import reviewme.review.domain.exception.TextAnswerNotFoundException; + +@Slf4j +public class TextAnswers { + + private final Map textAnswers; + + public TextAnswers(List textAnswers) { + this.textAnswers = textAnswers.stream() + .collect(Collectors.toMap(TextAnswer::getQuestionId, Function.identity())); + } + + public TextAnswer getAnswerByQuestionId(long questionId) { + if (!textAnswers.containsKey(questionId)) { + throw new TextAnswerNotFoundException(questionId); + } + return textAnswers.get(questionId); + } +} diff --git a/backend/src/main/java/reviewme/review/domain/exception/TextAnswerNotFoundException.java b/backend/src/main/java/reviewme/review/domain/exception/TextAnswerNotFoundException.java new file mode 100644 index 000000000..e160abf4c --- /dev/null +++ b/backend/src/main/java/reviewme/review/domain/exception/TextAnswerNotFoundException.java @@ -0,0 +1,13 @@ +package reviewme.review.domain.exception; + +import lombok.extern.slf4j.Slf4j; +import reviewme.global.exception.NotFoundException; + +@Slf4j +public class TextAnswerNotFoundException extends NotFoundException { + + public TextAnswerNotFoundException(long questionId) { + super("질문에 해당하는 서술형 답변을 찾지 못했어요."); + log.warn("Text Answer not found for questionId: {}", questionId); + } +} diff --git a/backend/src/test/java/reviewme/review/domain/TextAnswersTest.java b/backend/src/test/java/reviewme/review/domain/TextAnswersTest.java new file mode 100644 index 000000000..ee13f0373 --- /dev/null +++ b/backend/src/test/java/reviewme/review/domain/TextAnswersTest.java @@ -0,0 +1,31 @@ +package reviewme.review.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import org.junit.jupiter.api.Test; +import reviewme.review.domain.exception.TextAnswerNotFoundException; + +class TextAnswersTest { + + @Test + void 질문에_해당하는_답변이_없으면_예외를_발생한다() { + // given + TextAnswers textAnswers = new TextAnswers(List.of(new TextAnswer(1, "답변"))); + + // when, then + assertThatThrownBy(() -> textAnswers.getAnswerByQuestionId(2)) + .isInstanceOf(TextAnswerNotFoundException.class); + } + + @Test + void 질문_ID로_서술형_답변을_반환한다() { + // given + TextAnswers textAnswers = new TextAnswers(List.of(new TextAnswer(1, "답변"))); + // when + TextAnswer actual = textAnswers.getAnswerByQuestionId(1); + // then + assertThat(actual.getContent()).isEqualTo("답변"); + } +} From 56be08c2b6f31fcffaafa86e65689b287435d3f3 Mon Sep 17 00:00:00 2001 From: donghoony Date: Sun, 11 Aug 2024 01:18:53 +0900 Subject: [PATCH 09/19] =?UTF-8?q?feat:=20`CollectionTable`=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C,=20`OneToMany`=20+=20`JoinColumn`=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CheckBoxAnswerSelectedOptionId.java | 32 +++++++++++++++++++ .../review/domain/CheckboxAnswer.java | 19 ++++++----- .../repository/QuestionRepository2.java | 2 +- .../reviewme/template/domain/Section.java | 16 ++++++---- .../template/domain/SectionQuestionId.java | 32 +++++++++++++++++++ .../reviewme/template/domain/Template.java | 17 +++++----- .../template/domain/TemplateSectionId.java | 32 +++++++++++++++++++ .../repository/SectionRepository.java | 2 +- 8 files changed, 125 insertions(+), 27 deletions(-) create mode 100644 backend/src/main/java/reviewme/review/domain/CheckBoxAnswerSelectedOptionId.java create mode 100644 backend/src/main/java/reviewme/template/domain/SectionQuestionId.java create mode 100644 backend/src/main/java/reviewme/template/domain/TemplateSectionId.java diff --git a/backend/src/main/java/reviewme/review/domain/CheckBoxAnswerSelectedOptionId.java b/backend/src/main/java/reviewme/review/domain/CheckBoxAnswerSelectedOptionId.java new file mode 100644 index 000000000..16eaf61a3 --- /dev/null +++ b/backend/src/main/java/reviewme/review/domain/CheckBoxAnswerSelectedOptionId.java @@ -0,0 +1,32 @@ +package reviewme.review.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "checkbox_answer_selected_option") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class CheckBoxAnswerSelectedOptionId { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "checkbox_answer_id", nullable = false, insertable = false, updatable = false) + private long checkboxAnswerId; + + @Column(name = "selected_option_id", nullable = false) + private long selectedOptionId; + + public CheckBoxAnswerSelectedOptionId(long selectedOptionId) { + this.selectedOptionId = selectedOptionId; + } +} diff --git a/backend/src/main/java/reviewme/review/domain/CheckboxAnswer.java b/backend/src/main/java/reviewme/review/domain/CheckboxAnswer.java index 9e19b75f2..dc6f83540 100644 --- a/backend/src/main/java/reviewme/review/domain/CheckboxAnswer.java +++ b/backend/src/main/java/reviewme/review/domain/CheckboxAnswer.java @@ -1,13 +1,14 @@ package reviewme.review.domain; -import jakarta.persistence.CollectionTable; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; -import jakarta.persistence.ElementCollection; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import java.util.List; import lombok.AccessLevel; @@ -32,16 +33,14 @@ public class CheckboxAnswer { @Column(name = "question_id", nullable = false) private long questionId; - @ElementCollection - @CollectionTable( - name = "checkbox_answer_selected_option", - joinColumns = @JoinColumn(name = "checkbox_answer_id", nullable = false) - ) - @Column(name = "selected_option_id", nullable = false) - private List selectedOptionIds; + @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "checkbox_answer_id", nullable = false, updatable = false) + private List selectedOptionIds; public CheckboxAnswer(long questionId, List selectedOptionIds) { this.questionId = questionId; - this.selectedOptionIds = selectedOptionIds; + this.selectedOptionIds = selectedOptionIds.stream() + .map(CheckBoxAnswerSelectedOptionId::new) + .toList(); } } diff --git a/backend/src/main/java/reviewme/review/repository/QuestionRepository2.java b/backend/src/main/java/reviewme/review/repository/QuestionRepository2.java index 2e2bf3d76..876c6f99f 100644 --- a/backend/src/main/java/reviewme/review/repository/QuestionRepository2.java +++ b/backend/src/main/java/reviewme/review/repository/QuestionRepository2.java @@ -8,7 +8,7 @@ public interface QuestionRepository2 extends JpaRepository { @Query(value = "" + - "SELECT * FROM question2 q " + + "SELECT q.* FROM question2 q " + "LEFT JOIN section_question sq " + "ON sq.question_id = q.id " + "WHERE sq.section_id = :sectionId " + diff --git a/backend/src/main/java/reviewme/template/domain/Section.java b/backend/src/main/java/reviewme/template/domain/Section.java index c3dc8f2df..a2ddb7ace 100644 --- a/backend/src/main/java/reviewme/template/domain/Section.java +++ b/backend/src/main/java/reviewme/template/domain/Section.java @@ -1,15 +1,16 @@ package reviewme.template.domain; -import jakarta.persistence.CollectionTable; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; -import jakarta.persistence.ElementCollection; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import java.util.Collection; import java.util.List; @@ -33,10 +34,9 @@ public class Section { @Enumerated(EnumType.STRING) private VisibleType visibleType; - @ElementCollection - @CollectionTable(name = "section_question", joinColumns = @JoinColumn(name = "section_id")) - @Column(name = "question_id", nullable = false) - private List questionIds; + @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "section_id", nullable = false, updatable = false) + private List questionIds; @Column(name = "on_selected_option_id", nullable = true) private Long onSelectedOptionId; @@ -50,7 +50,9 @@ public class Section { public Section(VisibleType visibleType, List questionIds, Long onSelectedOptionId, String header, int position) { this.visibleType = visibleType; - this.questionIds = questionIds; + this.questionIds = questionIds.stream() + .map(SectionQuestionId::new) + .toList(); this.onSelectedOptionId = onSelectedOptionId; this.header = header; this.position = position; diff --git a/backend/src/main/java/reviewme/template/domain/SectionQuestionId.java b/backend/src/main/java/reviewme/template/domain/SectionQuestionId.java new file mode 100644 index 000000000..f1570e666 --- /dev/null +++ b/backend/src/main/java/reviewme/template/domain/SectionQuestionId.java @@ -0,0 +1,32 @@ +package reviewme.template.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "section_question") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class SectionQuestionId { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "section_id", nullable = false, insertable = false, updatable = false) + private long sectionId; + + @Column(name = "question_id", nullable = false) + private long questionId; + + public SectionQuestionId(long questionId) { + this.questionId = questionId; + } +} diff --git a/backend/src/main/java/reviewme/template/domain/Template.java b/backend/src/main/java/reviewme/template/domain/Template.java index 9e8f229de..264e7c86b 100644 --- a/backend/src/main/java/reviewme/template/domain/Template.java +++ b/backend/src/main/java/reviewme/template/domain/Template.java @@ -1,13 +1,13 @@ package reviewme.template.domain; -import jakarta.persistence.CollectionTable; -import jakarta.persistence.Column; -import jakarta.persistence.ElementCollection; +import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import java.util.List; import lombok.AccessLevel; @@ -26,12 +26,13 @@ public class Template { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ElementCollection - @CollectionTable(name = "template_section", joinColumns = @JoinColumn(name = "template_id", nullable = false)) - @Column(name = "section_id", nullable = false) - private List sectionIds; + @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "template_id", nullable = false, updatable = false) + private List sectionIds; public Template(List sectionIds) { - this.sectionIds = sectionIds; + this.sectionIds = sectionIds.stream() + .map(TemplateSectionId::new) + .toList(); } } diff --git a/backend/src/main/java/reviewme/template/domain/TemplateSectionId.java b/backend/src/main/java/reviewme/template/domain/TemplateSectionId.java new file mode 100644 index 000000000..b7f99f695 --- /dev/null +++ b/backend/src/main/java/reviewme/template/domain/TemplateSectionId.java @@ -0,0 +1,32 @@ +package reviewme.template.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "template_section") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class TemplateSectionId { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "template_id", nullable = false, insertable = false, updatable = false) + private long templateId; + + @Column(name = "section_id", nullable = false) + private long sectionId; + + public TemplateSectionId(long sectionId) { + this.sectionId = sectionId; + } +} diff --git a/backend/src/main/java/reviewme/template/repository/SectionRepository.java b/backend/src/main/java/reviewme/template/repository/SectionRepository.java index a2a2bb35c..066d3c8ed 100644 --- a/backend/src/main/java/reviewme/template/repository/SectionRepository.java +++ b/backend/src/main/java/reviewme/template/repository/SectionRepository.java @@ -8,7 +8,7 @@ public interface SectionRepository extends JpaRepository { @Query(value = "" + - "SELECT * FROM section s LEFT JOIN template_section ts " + + "SELECT s.* FROM section s LEFT JOIN template_section ts " + "ON ts.section_id = s.id " + "WHERE ts.template_id = :templateId " + "ORDER BY s.position ASC", From f8ee4dd3bfbbd9a4b3f4f1b27fc0f710f9a4b3b4 Mon Sep 17 00:00:00 2001 From: donghoony Date: Sun, 11 Aug 2024 01:19:16 +0900 Subject: [PATCH 10/19] =?UTF-8?q?feat:=20=EB=A6=AC=EB=B7=B0=20=EB=8B=A8?= =?UTF-8?q?=EA=B1=B4=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/service/ReviewDetailService.java | 127 ++++++++++++++++++ .../service/ReviewDetailServiceTest.java | 125 +++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 backend/src/main/java/reviewme/review/service/ReviewDetailService.java create mode 100644 backend/src/test/java/reviewme/review/service/ReviewDetailServiceTest.java diff --git a/backend/src/main/java/reviewme/review/service/ReviewDetailService.java b/backend/src/main/java/reviewme/review/service/ReviewDetailService.java new file mode 100644 index 000000000..ecf55e102 --- /dev/null +++ b/backend/src/main/java/reviewme/review/service/ReviewDetailService.java @@ -0,0 +1,127 @@ +package reviewme.review.service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import reviewme.question.domain.OptionGroup; +import reviewme.question.domain.Question2; +import reviewme.question.repository.OptionGroupRepository; +import reviewme.question.repository.OptionItemRepository; +import reviewme.review.domain.Review2; +import reviewme.review.domain.TextAnswer; +import reviewme.review.domain.TextAnswers; +import reviewme.review.domain.exception.ReviewGroupNotFoundByGroupAccessCodeException; +import reviewme.review.repository.QuestionRepository2; +import reviewme.review.repository.ReviewRepository2; +import reviewme.review.service.dto.response.detail.OptionGroupAnswerResponse; +import reviewme.review.service.dto.response.detail.OptionItemAnswerResponse; +import reviewme.review.service.dto.response.detail.QuestionAnswerResponse; +import reviewme.review.service.dto.response.detail.SectionAnswerResponse; +import reviewme.review.service.dto.response.detail.TemplateAnswerResponse; +import reviewme.reviewgroup.domain.ReviewGroup; +import reviewme.reviewgroup.repository.ReviewGroupRepository; +import reviewme.template.domain.Section; +import reviewme.template.repository.SectionRepository; + +@Service +@Transactional(readOnly = true) +@AllArgsConstructor +public class ReviewDetailService { + + private final SectionRepository sectionRepository; + private final ReviewRepository2 reviewRepository; + private final ReviewGroupRepository reviewGroupRepository; + private final QuestionRepository2 questionRepository; + private final OptionItemRepository optionItemRepository; + private final OptionGroupRepository optionGroupRepository; + + public TemplateAnswerResponse getReviewDetail(String groupAccessCode, long reviewId) { + ReviewGroup reviewGroup = reviewGroupRepository.findByGroupAccessCode(groupAccessCode) + .orElseThrow(() -> new ReviewGroupNotFoundByGroupAccessCodeException(groupAccessCode)); + + Review2 review = reviewRepository.findByIdAndReviewGroupId(reviewId, reviewGroup.getId()) + .orElseThrow(() -> new ReviewGroupNotFoundByGroupAccessCodeException(groupAccessCode)); + long templateId = review.getTemplateId(); + + Set selectedOptionItemIds = optionItemRepository.findSelectedOptionItemIdsByReviewId(reviewId); + List sectionResponses = sectionRepository.findAllByTemplateId(templateId) + .stream() + .filter(section -> section.isVisibleBySelectedOptionIds(selectedOptionItemIds)) + .map(section -> getSectionAnswerResponse(review, section)) + .toList(); + + return new TemplateAnswerResponse( + templateId, + reviewGroup.getReviewee(), + reviewGroup.getProjectName(), + sectionResponses + ); + } + + private SectionAnswerResponse getSectionAnswerResponse(Review2 review, Section section) { + TextAnswers textAnswers = new TextAnswers(review.getTextAnswers()); + ArrayList questionResponses = new ArrayList<>(); + + for (Question2 question : questionRepository.findAllBySectionId(section.getId())) { + if (question.isSelectable()) { + questionResponses.add(getCheckboxAnswerResponse(review, question)); + continue; + } + questionResponses.add(getTextAnswerResponse(question, textAnswers)); + } + + return new SectionAnswerResponse( + section.getId(), + section.getHeader(), + questionResponses + ); + } + + private QuestionAnswerResponse getTextAnswerResponse(Question2 question, TextAnswers textAnswers) { + TextAnswer textAnswer = textAnswers.getAnswerByQuestionId(question.getId()); + return new QuestionAnswerResponse( + question.getId(), + question.isRequired(), + question.getQuestionType(), + question.getContent(), + question.hasGuideline(), + question.getGuideline(), + null, + textAnswer.getContent() + ); + } + + private QuestionAnswerResponse getCheckboxAnswerResponse(Review2 review, Question2 question) { + OptionGroup optionGroup = optionGroupRepository.getByQuestionId(question.getId()); + Set selectedOptionItemIds = optionItemRepository.findSelectedOptionItemIdsByReviewId(review.getId()); + List optionItemResponse = + optionItemRepository.findSelectedOptionItemsByReviewIdAndQuestionId(review.getId(), question.getId()) + .stream() + .map(optionItem -> new OptionItemAnswerResponse( + optionItem.getId(), + optionItem.getContent(), + selectedOptionItemIds.contains(optionItem.getId())) + ).toList(); + + OptionGroupAnswerResponse optionGroupAnswerResponse = new OptionGroupAnswerResponse( + optionGroup.getId(), + optionGroup.getMinSelectionCount(), + optionGroup.getMaxSelectionCount(), + optionItemResponse + ); + + return new QuestionAnswerResponse( + question.getId(), + question.isRequired(), + question.getQuestionType(), + question.getContent(), + question.hasGuideline(), + question.getGuideline(), + optionGroupAnswerResponse, + null + ); + } +} diff --git a/backend/src/test/java/reviewme/review/service/ReviewDetailServiceTest.java b/backend/src/test/java/reviewme/review/service/ReviewDetailServiceTest.java new file mode 100644 index 000000000..7b5c7fbf9 --- /dev/null +++ b/backend/src/test/java/reviewme/review/service/ReviewDetailServiceTest.java @@ -0,0 +1,125 @@ +package reviewme.review.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import reviewme.question.domain.OptionGroup; +import reviewme.question.domain.OptionItem; +import reviewme.question.domain.Question2; +import reviewme.question.domain.QuestionType; +import reviewme.question.repository.OptionGroupRepository; +import reviewme.question.repository.OptionItemRepository; +import reviewme.review.domain.CheckboxAnswer; +import reviewme.review.domain.Review2; +import reviewme.review.domain.TextAnswer; +import reviewme.review.repository.QuestionRepository2; +import reviewme.review.repository.ReviewRepository2; +import reviewme.review.service.dto.response.detail.TemplateAnswerResponse; +import reviewme.reviewgroup.domain.ReviewGroup; +import reviewme.reviewgroup.repository.ReviewGroupRepository; +import reviewme.support.ServiceTest; +import reviewme.template.domain.Section; +import reviewme.template.domain.Template; +import reviewme.template.domain.VisibleType; +import reviewme.template.repository.SectionRepository; +import reviewme.template.repository.TemplateRepository; + +@ServiceTest +class ReviewDetailServiceTest { + + @Autowired + private ReviewDetailService reviewDetailService; + + @Autowired + private ReviewGroupRepository reviewGroupRepository; + + @Autowired + private ReviewRepository2 reviewRepository2; + + @Autowired + private SectionRepository sectionRepository; + + @Autowired + private QuestionRepository2 questionRepository; + + @Autowired + private OptionGroupRepository optionGroupRepository; + + @Autowired + private OptionItemRepository optionItemRepository; + + @Autowired + private TemplateRepository templateRepository; + + @Test + void 사용자가_작성한_리뷰를_확인한다() { + // given + ReviewGroup reviewGroup = reviewGroupRepository.save(new ReviewGroup("aru", "reviewme", "ABCD", "0000")); + Question2 question1 = questionRepository.save(new Question2(true, QuestionType.TEXT, "질문", null, 1)); + Question2 question2 = questionRepository.save(new Question2(true, QuestionType.CHECKBOX, "질문", null, 1)); + Question2 question3 = questionRepository.save(new Question2(true, QuestionType.TEXT, "체크 1 조건", "가이드라인", 1)); + OptionGroup optionGroup = optionGroupRepository.save(new OptionGroup(question2.getId(), 1, 3)); + OptionItem optionItem1 = optionItemRepository.save(new OptionItem("체크 1", optionGroup.getId(), 1)); + OptionItem optionItem2 = optionItemRepository.save(new OptionItem("체크 2", optionGroup.getId(), 1)); + + Section section1 = sectionRepository.save( + new Section(VisibleType.ALWAYS, List.of(question1.getId(), question2.getId()), null, "1번 섹션", 1) + ); + Section section2 = sectionRepository.save( + new Section(VisibleType.CONDITIONAL, List.of(question3.getId()), optionItem1.getId(), "2번 섹션", 2) + ); + Template template = templateRepository.save(new Template(List.of(section1.getId(), section2.getId()))); + + List textAnswers = List.of( + new TextAnswer(1, "질문 1 답변"), + new TextAnswer(3, "질문 3 답변") + ); + List checkboxAnswers = List.of( + new CheckboxAnswer(2, List.of(optionItem1.getId(), optionItem2.getId())) + ); + Review2 review = reviewRepository2.save( + new Review2(template.getId(), reviewGroup.getId(), textAnswers, checkboxAnswers) + ); + + // when + TemplateAnswerResponse reviewDetail = reviewDetailService.getReviewDetail("0000", review.getId()); + + // then + assertThat(reviewDetail.sections()).hasSize(2); + } + + @Test + void 섹션을_보이게_하는_옵션을_선택하지_않은_경우_해당_섹션을_제외하고_보여준다() { + // given + ReviewGroup reviewGroup = reviewGroupRepository.save(new ReviewGroup("aru", "reviewme", "ABCD", "0000")); + Question2 question1 = questionRepository.save(new Question2(true, QuestionType.TEXT, "질문", null, 1)); + Question2 question2 = questionRepository.save(new Question2(true, QuestionType.CHECKBOX, "질문", null, 2)); + Question2 question3 = questionRepository.save(new Question2(true, QuestionType.TEXT, "체크 1 조건", "가이드라인", 3)); + OptionGroup optionGroup = optionGroupRepository.save(new OptionGroup(question2.getId(), 1, 3)); + OptionItem optionItem1 = optionItemRepository.save(new OptionItem("체크 1", optionGroup.getId(), 1)); + OptionItem optionItem2 = optionItemRepository.save(new OptionItem("체크 2", optionGroup.getId(), 2)); + + Section section1 = sectionRepository.save( + new Section(VisibleType.ALWAYS, List.of(question1.getId(), question2.getId()), null, "1번 섹션", 1) + ); + Section section2 = sectionRepository.save( + new Section(VisibleType.CONDITIONAL, List.of(question3.getId()), optionItem1.getId(), "2번 섹션", 2) + ); + Template template = templateRepository.save(new Template(List.of(section1.getId(), section2.getId()))); + + List textAnswers = List.of(new TextAnswer(question1.getId(), "질문 1 답변")); + List checkboxAnswers = List.of( + new CheckboxAnswer(question2.getId(), List.of(optionItem2.getId()))); + Review2 review = reviewRepository2.save( + new Review2(template.getId(), reviewGroup.getId(), textAnswers, checkboxAnswers) + ); + + // when + TemplateAnswerResponse reviewDetail = reviewDetailService.getReviewDetail("0000", review.getId()); + + // then + assertThat(reviewDetail.sections()).hasSize(1); + } +} From 13c459b138cd8098ec7ebcdc7f8b81fc0708b1f9 Mon Sep 17 00:00:00 2001 From: donghoony Date: Sun, 11 Aug 2024 01:21:02 +0900 Subject: [PATCH 11/19] =?UTF-8?q?refactor:=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EB=AA=85=20=EB=AA=85=ED=99=95=ED=95=98=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...wDetailService.java => ReviewDetailLookupService.java} | 2 +- ...erviceTest.java => ReviewDetailLookupServiceTest.java} | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) rename backend/src/main/java/reviewme/review/service/{ReviewDetailService.java => ReviewDetailLookupService.java} (99%) rename backend/src/test/java/reviewme/review/service/{ReviewDetailServiceTest.java => ReviewDetailLookupServiceTest.java} (94%) diff --git a/backend/src/main/java/reviewme/review/service/ReviewDetailService.java b/backend/src/main/java/reviewme/review/service/ReviewDetailLookupService.java similarity index 99% rename from backend/src/main/java/reviewme/review/service/ReviewDetailService.java rename to backend/src/main/java/reviewme/review/service/ReviewDetailLookupService.java index ecf55e102..2c63ebea6 100644 --- a/backend/src/main/java/reviewme/review/service/ReviewDetailService.java +++ b/backend/src/main/java/reviewme/review/service/ReviewDetailLookupService.java @@ -29,7 +29,7 @@ @Service @Transactional(readOnly = true) @AllArgsConstructor -public class ReviewDetailService { +public class ReviewDetailLookupService { private final SectionRepository sectionRepository; private final ReviewRepository2 reviewRepository; diff --git a/backend/src/test/java/reviewme/review/service/ReviewDetailServiceTest.java b/backend/src/test/java/reviewme/review/service/ReviewDetailLookupServiceTest.java similarity index 94% rename from backend/src/test/java/reviewme/review/service/ReviewDetailServiceTest.java rename to backend/src/test/java/reviewme/review/service/ReviewDetailLookupServiceTest.java index 7b5c7fbf9..9704fcba8 100644 --- a/backend/src/test/java/reviewme/review/service/ReviewDetailServiceTest.java +++ b/backend/src/test/java/reviewme/review/service/ReviewDetailLookupServiceTest.java @@ -27,10 +27,10 @@ import reviewme.template.repository.TemplateRepository; @ServiceTest -class ReviewDetailServiceTest { +class ReviewDetailLookupServiceTest { @Autowired - private ReviewDetailService reviewDetailService; + private ReviewDetailLookupService reviewDetailLookupService; @Autowired private ReviewGroupRepository reviewGroupRepository; @@ -84,7 +84,7 @@ class ReviewDetailServiceTest { ); // when - TemplateAnswerResponse reviewDetail = reviewDetailService.getReviewDetail("0000", review.getId()); + TemplateAnswerResponse reviewDetail = reviewDetailLookupService.getReviewDetail("0000", review.getId()); // then assertThat(reviewDetail.sections()).hasSize(2); @@ -117,7 +117,7 @@ class ReviewDetailServiceTest { ); // when - TemplateAnswerResponse reviewDetail = reviewDetailService.getReviewDetail("0000", review.getId()); + TemplateAnswerResponse reviewDetail = reviewDetailLookupService.getReviewDetail("0000", review.getId()); // then assertThat(reviewDetail.sections()).hasSize(1); From 1572be4b4667291fdb594dc54b1c8a1e303ca4ad Mon Sep 17 00:00:00 2001 From: donghoony Date: Sun, 11 Aug 2024 01:21:10 +0900 Subject: [PATCH 12/19] =?UTF-8?q?feat:=20API=20=EC=97=B0=EB=8F=99,=20V2?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reviewme/review/controller/ReviewController.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/backend/src/main/java/reviewme/review/controller/ReviewController.java b/backend/src/main/java/reviewme/review/controller/ReviewController.java index 00bdf9917..e69317b3d 100644 --- a/backend/src/main/java/reviewme/review/controller/ReviewController.java +++ b/backend/src/main/java/reviewme/review/controller/ReviewController.java @@ -15,7 +15,9 @@ import reviewme.review.dto.response.ReceivedReviewsResponse; import reviewme.review.dto.response.ReviewDetailResponse; import reviewme.review.dto.response.ReviewSetupResponse; +import reviewme.review.service.ReviewDetailLookupService; import reviewme.review.service.ReviewService; +import reviewme.review.service.dto.response.detail.TemplateAnswerResponse; @RestController @RequiredArgsConstructor @@ -24,6 +26,7 @@ public class ReviewController implements ReviewApi { private static final String GROUP_ACCESS_CODE_HEADER = "GroupAccessCode"; private final ReviewService reviewService; + private final ReviewDetailLookupService reviewDetailLookupService; @PostMapping("/reviews") public ResponseEntity createReview(@Valid @RequestBody CreateReviewRequest request) { @@ -52,4 +55,12 @@ public ResponseEntity findReceivedReviewDetail( ReviewDetailResponse response = reviewService.findReceivedReviewDetail(groupAccessCode, id); return ResponseEntity.ok(response); } + + @GetMapping("/v2/reviews/{id}") + public ResponseEntity findReceivedReviewDetailV2( + @PathVariable long id, + @HeaderProperty(GROUP_ACCESS_CODE_HEADER) String groupAccessCode) { + TemplateAnswerResponse response = reviewDetailLookupService.getReviewDetail(groupAccessCode, id); + return ResponseEntity.ok(response); + } } From 8ec2e0fd6ea83d11a6c655cc8ecc0a0221df9805 Mon Sep 17 00:00:00 2001 From: donghoony Date: Sun, 11 Aug 2024 12:33:13 +0900 Subject: [PATCH 13/19] =?UTF-8?q?feat:=20=EB=A7=8C=EB=93=A4=EC=96=B4?= =?UTF-8?q?=EC=A7=84=20=EC=8B=9C=EA=B0=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/reviewme/review/domain/Review2.java | 10 ++++++++++ .../review/service/ReviewDetailLookupService.java | 1 + .../dto/response/detail/TemplateAnswerResponse.java | 2 ++ 3 files changed, 13 insertions(+) diff --git a/backend/src/main/java/reviewme/review/domain/Review2.java b/backend/src/main/java/reviewme/review/domain/Review2.java index def5a8f59..251b54eda 100644 --- a/backend/src/main/java/reviewme/review/domain/Review2.java +++ b/backend/src/main/java/reviewme/review/domain/Review2.java @@ -10,6 +10,8 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; import lombok.AccessLevel; import lombok.EqualsAndHashCode; @@ -41,11 +43,19 @@ public class Review2 { @JoinColumn(name = "review_id", nullable = false, updatable = false) private List checkboxAnswers; + @Column(name = "created_at", nullable = false) + private LocalDateTime createdAt; + public Review2(long templateId, long reviewGroupId, List textAnswers, List checkboxAnswers) { this.templateId = templateId; this.reviewGroupId = reviewGroupId; this.textAnswers = textAnswers; this.checkboxAnswers = checkboxAnswers; + this.createdAt = LocalDateTime.now(); + } + + public LocalDate getCreatedDate() { + return createdAt.toLocalDate(); } } diff --git a/backend/src/main/java/reviewme/review/service/ReviewDetailLookupService.java b/backend/src/main/java/reviewme/review/service/ReviewDetailLookupService.java index 2c63ebea6..6715b7135 100644 --- a/backend/src/main/java/reviewme/review/service/ReviewDetailLookupService.java +++ b/backend/src/main/java/reviewme/review/service/ReviewDetailLookupService.java @@ -57,6 +57,7 @@ public TemplateAnswerResponse getReviewDetail(String groupAccessCode, long revie templateId, reviewGroup.getReviewee(), reviewGroup.getProjectName(), + review.getCreatedDate(), sectionResponses ); } diff --git a/backend/src/main/java/reviewme/review/service/dto/response/detail/TemplateAnswerResponse.java b/backend/src/main/java/reviewme/review/service/dto/response/detail/TemplateAnswerResponse.java index 0cb061878..0e838236b 100644 --- a/backend/src/main/java/reviewme/review/service/dto/response/detail/TemplateAnswerResponse.java +++ b/backend/src/main/java/reviewme/review/service/dto/response/detail/TemplateAnswerResponse.java @@ -1,11 +1,13 @@ package reviewme.review.service.dto.response.detail; +import java.time.LocalDate; import java.util.List; public record TemplateAnswerResponse( long formId, String revieweeName, String projectName, + LocalDate createdAt, List sections ) { } From c3b3788281490f2d915def21648d41513c8767dc Mon Sep 17 00:00:00 2001 From: donghoony Date: Sun, 11 Aug 2024 12:34:06 +0900 Subject: [PATCH 14/19] style: apply code convention --- .../src/test/java/reviewme/review/domain/TextAnswersTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/test/java/reviewme/review/domain/TextAnswersTest.java b/backend/src/test/java/reviewme/review/domain/TextAnswersTest.java index ee13f0373..eaeeca70b 100644 --- a/backend/src/test/java/reviewme/review/domain/TextAnswersTest.java +++ b/backend/src/test/java/reviewme/review/domain/TextAnswersTest.java @@ -23,8 +23,10 @@ class TextAnswersTest { void 질문_ID로_서술형_답변을_반환한다() { // given TextAnswers textAnswers = new TextAnswers(List.of(new TextAnswer(1, "답변"))); + // when TextAnswer actual = textAnswers.getAnswerByQuestionId(1); + // then assertThat(actual.getContent()).isEqualTo("답변"); } From e29150a0c4f35641dea49a6887c76434dc848295 Mon Sep 17 00:00:00 2001 From: donghoony Date: Sun, 11 Aug 2024 16:17:37 +0900 Subject: [PATCH 15/19] =?UTF-8?q?refactor:=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ....java => InvalidReviewAccessByReviewGroupException.java} | 6 +++--- .../reviewme/review/service/ReviewDetailLookupService.java | 3 ++- .../main/java/reviewme/review/service/ReviewService.java | 4 ++-- .../java/reviewme/review/service/ReviewServiceTest.java | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) rename backend/src/main/java/reviewme/review/domain/exception/{ReviewIsNotInReviewGroupException.java => InvalidReviewAccessByReviewGroupException.java} (52%) diff --git a/backend/src/main/java/reviewme/review/domain/exception/ReviewIsNotInReviewGroupException.java b/backend/src/main/java/reviewme/review/domain/exception/InvalidReviewAccessByReviewGroupException.java similarity index 52% rename from backend/src/main/java/reviewme/review/domain/exception/ReviewIsNotInReviewGroupException.java rename to backend/src/main/java/reviewme/review/domain/exception/InvalidReviewAccessByReviewGroupException.java index 72442d522..6716a8348 100644 --- a/backend/src/main/java/reviewme/review/domain/exception/ReviewIsNotInReviewGroupException.java +++ b/backend/src/main/java/reviewme/review/domain/exception/InvalidReviewAccessByReviewGroupException.java @@ -4,10 +4,10 @@ import reviewme.global.exception.BadRequestException; @Slf4j -public class ReviewIsNotInReviewGroupException extends BadRequestException { +public class InvalidReviewAccessByReviewGroupException extends BadRequestException { - public ReviewIsNotInReviewGroupException(long reviewId, long reviewGroupId) { - super("리뷰 그룹에 해당하는 리뷰가 아니에요."); + public InvalidReviewAccessByReviewGroupException(long reviewId, long reviewGroupId) { + super("리뷰가 존재하지 않아요."); log.info("Review is not in review group - reviewId: {}, reviewGroupId: {}", reviewId, reviewGroupId); } } diff --git a/backend/src/main/java/reviewme/review/service/ReviewDetailLookupService.java b/backend/src/main/java/reviewme/review/service/ReviewDetailLookupService.java index 6715b7135..3651a1de7 100644 --- a/backend/src/main/java/reviewme/review/service/ReviewDetailLookupService.java +++ b/backend/src/main/java/reviewme/review/service/ReviewDetailLookupService.java @@ -14,6 +14,7 @@ import reviewme.review.domain.TextAnswer; import reviewme.review.domain.TextAnswers; import reviewme.review.domain.exception.ReviewGroupNotFoundByGroupAccessCodeException; +import reviewme.review.domain.exception.InvalidReviewAccessByReviewGroupException; import reviewme.review.repository.QuestionRepository2; import reviewme.review.repository.ReviewRepository2; import reviewme.review.service.dto.response.detail.OptionGroupAnswerResponse; @@ -43,7 +44,7 @@ public TemplateAnswerResponse getReviewDetail(String groupAccessCode, long revie .orElseThrow(() -> new ReviewGroupNotFoundByGroupAccessCodeException(groupAccessCode)); Review2 review = reviewRepository.findByIdAndReviewGroupId(reviewId, reviewGroup.getId()) - .orElseThrow(() -> new ReviewGroupNotFoundByGroupAccessCodeException(groupAccessCode)); + .orElseThrow(() -> new InvalidReviewAccessByReviewGroupException(reviewId, reviewGroup.getId())); long templateId = review.getTemplateId(); Set selectedOptionItemIds = optionItemRepository.findSelectedOptionItemIdsByReviewId(reviewId); diff --git a/backend/src/main/java/reviewme/review/service/ReviewService.java b/backend/src/main/java/reviewme/review/service/ReviewService.java index 3b34adebd..0c027cdcd 100644 --- a/backend/src/main/java/reviewme/review/service/ReviewService.java +++ b/backend/src/main/java/reviewme/review/service/ReviewService.java @@ -12,7 +12,7 @@ import reviewme.review.domain.ReviewKeyword; import reviewme.review.domain.exception.ReviewGroupNotFoundByGroupAccessCodeException; import reviewme.review.domain.exception.ReviewGroupNotFoundByRequestReviewCodeException; -import reviewme.review.domain.exception.ReviewIsNotInReviewGroupException; +import reviewme.review.domain.exception.InvalidReviewAccessByReviewGroupException; import reviewme.review.dto.request.CreateReviewContentRequest; import reviewme.review.dto.request.CreateReviewRequest; import reviewme.review.dto.response.KeywordResponse; @@ -110,7 +110,7 @@ public ReviewDetailResponse findReceivedReviewDetail(String groupAccessCode, lon .orElseThrow(() -> new ReviewGroupNotFoundByGroupAccessCodeException(groupAccessCode)); Review review = reviewRepository.findByIdAndReviewGroupId(reviewId, reviewGroup.getId()) - .orElseThrow(() -> new ReviewIsNotInReviewGroupException(reviewId, reviewGroup.getId())); + .orElseThrow(() -> new InvalidReviewAccessByReviewGroupException(reviewId, reviewGroup.getId())); return createReviewDetailResponse(review, reviewGroup); } diff --git a/backend/src/test/java/reviewme/review/service/ReviewServiceTest.java b/backend/src/test/java/reviewme/review/service/ReviewServiceTest.java index b8186c80a..fdc5ec4bb 100644 --- a/backend/src/test/java/reviewme/review/service/ReviewServiceTest.java +++ b/backend/src/test/java/reviewme/review/service/ReviewServiceTest.java @@ -21,7 +21,7 @@ import reviewme.review.domain.ReviewContent; import reviewme.review.domain.ReviewKeyword; import reviewme.review.domain.exception.ReviewGroupNotFoundByGroupAccessCodeException; -import reviewme.review.domain.exception.ReviewIsNotInReviewGroupException; +import reviewme.review.domain.exception.InvalidReviewAccessByReviewGroupException; import reviewme.review.dto.request.CreateReviewContentRequest; import reviewme.review.dto.request.CreateReviewRequest; import reviewme.review.dto.response.QuestionSetupResponse; @@ -194,7 +194,7 @@ class ReviewServiceTest { // when, then assertThatThrownBy( () -> reviewService.findReceivedReviewDetail(reviewGroup1.getGroupAccessCode(), review2.getId())) - .isInstanceOf(ReviewIsNotInReviewGroupException.class); + .isInstanceOf(InvalidReviewAccessByReviewGroupException.class); } @Test From 6241cdce71e67ed4d02291becd4954e020659385 Mon Sep 17 00:00:00 2001 From: donghoony Date: Sun, 11 Aug 2024 16:25:13 +0900 Subject: [PATCH 16/19] =?UTF-8?q?refactor:=20=EC=BF=BC=EB=A6=AC=20multilin?= =?UTF-8?q?e=20string=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/OptionItemRepository.java | 36 +++++++++---------- .../repository/QuestionRepository2.java | 15 ++++---- .../repository/SectionRepository.java | 13 ++++--- 3 files changed, 31 insertions(+), 33 deletions(-) diff --git a/backend/src/main/java/reviewme/question/repository/OptionItemRepository.java b/backend/src/main/java/reviewme/question/repository/OptionItemRepository.java index 5caba7fea..4d2fd2f13 100644 --- a/backend/src/main/java/reviewme/question/repository/OptionItemRepository.java +++ b/backend/src/main/java/reviewme/question/repository/OptionItemRepository.java @@ -8,25 +8,25 @@ public interface OptionItemRepository extends JpaRepository { - @Query(value = "" + - "SELECT o.id FROM option_item o " + - "LEFT JOIN checkbox_answer ca " + - "LEFT JOIN checkbox_answer_selected_option c " + - "ON c.checkbox_answer_id = ca.id " + - "WHERE ca.review_id = :reviewId " + - "AND c.selected_option_id = o.id", - nativeQuery = true) + @Query(value = """ + SELECT o.id FROM option_item o + LEFT JOIN checkbox_answer ca + LEFT JOIN checkbox_answer_selected_option c + ON c.checkbox_answer_id = ca.id + WHERE ca.review_id = :reviewId + AND c.selected_option_id = o.id + """, nativeQuery = true) Set findSelectedOptionItemIdsByReviewId(long reviewId); - @Query(value = "" + - "SELECT o.* FROM option_item o " + - "LEFT JOIN checkbox_answer ca " + - "LEFT JOIN checkbox_answer_selected_option c " + - "ON c.checkbox_answer_id = ca.id " + - "WHERE ca.review_id = :reviewId " + - "AND ca.question_id = :questionId " + - "AND c.selected_option_id = o.id " + - "ORDER BY o.position", - nativeQuery = true) + @Query(value = """ + SELECT o.* FROM option_item o + LEFT JOIN checkbox_answer ca + LEFT JOIN checkbox_answer_selected_option c + ON c.checkbox_answer_id = ca.id + WHERE ca.review_id = :reviewId + AND ca.question_id = :questionId + AND c.selected_option_id = o.id + ORDER BY o.position ASC + """, nativeQuery = true) List findSelectedOptionItemsByReviewIdAndQuestionId(long reviewId, long questionId); } diff --git a/backend/src/main/java/reviewme/review/repository/QuestionRepository2.java b/backend/src/main/java/reviewme/review/repository/QuestionRepository2.java index 876c6f99f..bf3765ddc 100644 --- a/backend/src/main/java/reviewme/review/repository/QuestionRepository2.java +++ b/backend/src/main/java/reviewme/review/repository/QuestionRepository2.java @@ -7,13 +7,12 @@ public interface QuestionRepository2 extends JpaRepository { - @Query(value = "" + - "SELECT q.* FROM question2 q " + - "LEFT JOIN section_question sq " + - "ON sq.question_id = q.id " + - "WHERE sq.section_id = :sectionId " + - "ORDER BY q.position ASC", - nativeQuery = true - ) + @Query(value = """ + SELECT q.* FROM question2 q + LEFT JOIN section_question sq + ON sq.question_id = q.id + WHERE sq.section_id = :sectionId + ORDER BY q.position ASC + """, nativeQuery = true) List findAllBySectionId(long sectionId); } diff --git a/backend/src/main/java/reviewme/template/repository/SectionRepository.java b/backend/src/main/java/reviewme/template/repository/SectionRepository.java index 066d3c8ed..920f1ef75 100644 --- a/backend/src/main/java/reviewme/template/repository/SectionRepository.java +++ b/backend/src/main/java/reviewme/template/repository/SectionRepository.java @@ -7,12 +7,11 @@ public interface SectionRepository extends JpaRepository { - @Query(value = "" + - "SELECT s.* FROM section s LEFT JOIN template_section ts " + - "ON ts.section_id = s.id " + - "WHERE ts.template_id = :templateId " + - "ORDER BY s.position ASC", - nativeQuery = true - ) + @Query(value = """ + SELECT s.* FROM section s LEFT JOIN template_section ts + ON ts.section_id = s.id + WHERE ts.template_id = :templateId + ORDER BY s.position ASC + """, nativeQuery = true) List
findAllByTemplateId(long templateId); } From 93aec4d0e8a4617a4561f5658cf4d9aec6bf785a Mon Sep 17 00:00:00 2001 From: donghoony Date: Sun, 11 Aug 2024 16:26:36 +0900 Subject: [PATCH 17/19] =?UTF-8?q?refactor:=20=EC=98=88=EC=99=B8=EB=AA=85?= =?UTF-8?q?=20=EB=AA=85=ED=99=95=ED=95=98=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ption.java => MissingOptionGroupForQuestionException.java} | 4 ++-- .../reviewme/question/repository/OptionGroupRepository.java | 4 ++-- backend/src/main/java/reviewme/review/domain/TextAnswers.java | 4 ++-- ...eption.java => MissingTextAnswerForQuestionException.java} | 4 ++-- .../src/test/java/reviewme/review/domain/TextAnswersTest.java | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) rename backend/src/main/java/reviewme/question/domain/exception/{OptionGroupNotFoundException.java => MissingOptionGroupForQuestionException.java} (67%) rename backend/src/main/java/reviewme/review/domain/exception/{TextAnswerNotFoundException.java => MissingTextAnswerForQuestionException.java} (67%) diff --git a/backend/src/main/java/reviewme/question/domain/exception/OptionGroupNotFoundException.java b/backend/src/main/java/reviewme/question/domain/exception/MissingOptionGroupForQuestionException.java similarity index 67% rename from backend/src/main/java/reviewme/question/domain/exception/OptionGroupNotFoundException.java rename to backend/src/main/java/reviewme/question/domain/exception/MissingOptionGroupForQuestionException.java index 885df39e9..b820ccf15 100644 --- a/backend/src/main/java/reviewme/question/domain/exception/OptionGroupNotFoundException.java +++ b/backend/src/main/java/reviewme/question/domain/exception/MissingOptionGroupForQuestionException.java @@ -4,9 +4,9 @@ import reviewme.global.exception.NotFoundException; @Slf4j -public class OptionGroupNotFoundException extends NotFoundException { +public class MissingOptionGroupForQuestionException extends NotFoundException { - public OptionGroupNotFoundException(long questionId) { + public MissingOptionGroupForQuestionException(long questionId) { super("질문에 해당하는 체크박스 그룹을 찾을 수 없어요."); log.info("OptionGroup not found for questionId: {}", questionId); } diff --git a/backend/src/main/java/reviewme/question/repository/OptionGroupRepository.java b/backend/src/main/java/reviewme/question/repository/OptionGroupRepository.java index 421dc0eeb..1992e6865 100644 --- a/backend/src/main/java/reviewme/question/repository/OptionGroupRepository.java +++ b/backend/src/main/java/reviewme/question/repository/OptionGroupRepository.java @@ -3,7 +3,7 @@ import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import reviewme.question.domain.OptionGroup; -import reviewme.question.domain.exception.OptionGroupNotFoundException; +import reviewme.question.domain.exception.MissingOptionGroupForQuestionException; public interface OptionGroupRepository extends JpaRepository { @@ -11,6 +11,6 @@ public interface OptionGroupRepository extends JpaRepository default OptionGroup getByQuestionId(long questionId) { return findByQuestionId(questionId) - .orElseThrow(() -> new OptionGroupNotFoundException(questionId)); + .orElseThrow(() -> new MissingOptionGroupForQuestionException(questionId)); } } diff --git a/backend/src/main/java/reviewme/review/domain/TextAnswers.java b/backend/src/main/java/reviewme/review/domain/TextAnswers.java index 2a7be122f..3ef84e1f1 100644 --- a/backend/src/main/java/reviewme/review/domain/TextAnswers.java +++ b/backend/src/main/java/reviewme/review/domain/TextAnswers.java @@ -5,7 +5,7 @@ import java.util.function.Function; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; -import reviewme.review.domain.exception.TextAnswerNotFoundException; +import reviewme.review.domain.exception.MissingTextAnswerForQuestionException; @Slf4j public class TextAnswers { @@ -19,7 +19,7 @@ public TextAnswers(List textAnswers) { public TextAnswer getAnswerByQuestionId(long questionId) { if (!textAnswers.containsKey(questionId)) { - throw new TextAnswerNotFoundException(questionId); + throw new MissingTextAnswerForQuestionException(questionId); } return textAnswers.get(questionId); } diff --git a/backend/src/main/java/reviewme/review/domain/exception/TextAnswerNotFoundException.java b/backend/src/main/java/reviewme/review/domain/exception/MissingTextAnswerForQuestionException.java similarity index 67% rename from backend/src/main/java/reviewme/review/domain/exception/TextAnswerNotFoundException.java rename to backend/src/main/java/reviewme/review/domain/exception/MissingTextAnswerForQuestionException.java index e160abf4c..09550f007 100644 --- a/backend/src/main/java/reviewme/review/domain/exception/TextAnswerNotFoundException.java +++ b/backend/src/main/java/reviewme/review/domain/exception/MissingTextAnswerForQuestionException.java @@ -4,9 +4,9 @@ import reviewme.global.exception.NotFoundException; @Slf4j -public class TextAnswerNotFoundException extends NotFoundException { +public class MissingTextAnswerForQuestionException extends NotFoundException { - public TextAnswerNotFoundException(long questionId) { + public MissingTextAnswerForQuestionException(long questionId) { super("질문에 해당하는 서술형 답변을 찾지 못했어요."); log.warn("Text Answer not found for questionId: {}", questionId); } diff --git a/backend/src/test/java/reviewme/review/domain/TextAnswersTest.java b/backend/src/test/java/reviewme/review/domain/TextAnswersTest.java index eaeeca70b..34a4b1ba7 100644 --- a/backend/src/test/java/reviewme/review/domain/TextAnswersTest.java +++ b/backend/src/test/java/reviewme/review/domain/TextAnswersTest.java @@ -5,7 +5,7 @@ import java.util.List; import org.junit.jupiter.api.Test; -import reviewme.review.domain.exception.TextAnswerNotFoundException; +import reviewme.review.domain.exception.MissingTextAnswerForQuestionException; class TextAnswersTest { @@ -16,7 +16,7 @@ class TextAnswersTest { // when, then assertThatThrownBy(() -> textAnswers.getAnswerByQuestionId(2)) - .isInstanceOf(TextAnswerNotFoundException.class); + .isInstanceOf(MissingTextAnswerForQuestionException.class); } @Test From 498d28d599d2627b996f76ddd9b6d7a09d7de48d Mon Sep 17 00:00:00 2001 From: donghoony Date: Sun, 11 Aug 2024 16:28:02 +0900 Subject: [PATCH 18/19] =?UTF-8?q?refactor:=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EB=AA=85=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...electedOptionId.java => CheckBoxAnswerSelectedOption.java} | 4 ++-- .../src/main/java/reviewme/review/domain/CheckboxAnswer.java | 4 ++-- backend/src/main/java/reviewme/template/domain/Section.java | 4 ++-- .../domain/{SectionQuestionId.java => SectionQuestion.java} | 4 ++-- backend/src/main/java/reviewme/template/domain/Template.java | 4 ++-- .../domain/{TemplateSectionId.java => TemplateSection.java} | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) rename backend/src/main/java/reviewme/review/domain/{CheckBoxAnswerSelectedOptionId.java => CheckBoxAnswerSelectedOption.java} (87%) rename backend/src/main/java/reviewme/template/domain/{SectionQuestionId.java => SectionQuestion.java} (90%) rename backend/src/main/java/reviewme/template/domain/{TemplateSectionId.java => TemplateSection.java} (90%) diff --git a/backend/src/main/java/reviewme/review/domain/CheckBoxAnswerSelectedOptionId.java b/backend/src/main/java/reviewme/review/domain/CheckBoxAnswerSelectedOption.java similarity index 87% rename from backend/src/main/java/reviewme/review/domain/CheckBoxAnswerSelectedOptionId.java rename to backend/src/main/java/reviewme/review/domain/CheckBoxAnswerSelectedOption.java index 16eaf61a3..8a19dc049 100644 --- a/backend/src/main/java/reviewme/review/domain/CheckBoxAnswerSelectedOptionId.java +++ b/backend/src/main/java/reviewme/review/domain/CheckBoxAnswerSelectedOption.java @@ -14,7 +14,7 @@ @Table(name = "checkbox_answer_selected_option") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -public class CheckBoxAnswerSelectedOptionId { +public class CheckBoxAnswerSelectedOption { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -26,7 +26,7 @@ public class CheckBoxAnswerSelectedOptionId { @Column(name = "selected_option_id", nullable = false) private long selectedOptionId; - public CheckBoxAnswerSelectedOptionId(long selectedOptionId) { + public CheckBoxAnswerSelectedOption(long selectedOptionId) { this.selectedOptionId = selectedOptionId; } } diff --git a/backend/src/main/java/reviewme/review/domain/CheckboxAnswer.java b/backend/src/main/java/reviewme/review/domain/CheckboxAnswer.java index dc6f83540..6f6cf5aab 100644 --- a/backend/src/main/java/reviewme/review/domain/CheckboxAnswer.java +++ b/backend/src/main/java/reviewme/review/domain/CheckboxAnswer.java @@ -35,12 +35,12 @@ public class CheckboxAnswer { @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "checkbox_answer_id", nullable = false, updatable = false) - private List selectedOptionIds; + private List selectedOptionIds; public CheckboxAnswer(long questionId, List selectedOptionIds) { this.questionId = questionId; this.selectedOptionIds = selectedOptionIds.stream() - .map(CheckBoxAnswerSelectedOptionId::new) + .map(CheckBoxAnswerSelectedOption::new) .toList(); } } diff --git a/backend/src/main/java/reviewme/template/domain/Section.java b/backend/src/main/java/reviewme/template/domain/Section.java index a2ddb7ace..86e40c752 100644 --- a/backend/src/main/java/reviewme/template/domain/Section.java +++ b/backend/src/main/java/reviewme/template/domain/Section.java @@ -36,7 +36,7 @@ public class Section { @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "section_id", nullable = false, updatable = false) - private List questionIds; + private List questionIds; @Column(name = "on_selected_option_id", nullable = true) private Long onSelectedOptionId; @@ -51,7 +51,7 @@ public Section(VisibleType visibleType, List questionIds, Long onSelectedOptionId, String header, int position) { this.visibleType = visibleType; this.questionIds = questionIds.stream() - .map(SectionQuestionId::new) + .map(SectionQuestion::new) .toList(); this.onSelectedOptionId = onSelectedOptionId; this.header = header; diff --git a/backend/src/main/java/reviewme/template/domain/SectionQuestionId.java b/backend/src/main/java/reviewme/template/domain/SectionQuestion.java similarity index 90% rename from backend/src/main/java/reviewme/template/domain/SectionQuestionId.java rename to backend/src/main/java/reviewme/template/domain/SectionQuestion.java index f1570e666..eaac6e73e 100644 --- a/backend/src/main/java/reviewme/template/domain/SectionQuestionId.java +++ b/backend/src/main/java/reviewme/template/domain/SectionQuestion.java @@ -14,7 +14,7 @@ @Table(name = "section_question") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -public class SectionQuestionId { +public class SectionQuestion { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -26,7 +26,7 @@ public class SectionQuestionId { @Column(name = "question_id", nullable = false) private long questionId; - public SectionQuestionId(long questionId) { + public SectionQuestion(long questionId) { this.questionId = questionId; } } diff --git a/backend/src/main/java/reviewme/template/domain/Template.java b/backend/src/main/java/reviewme/template/domain/Template.java index 264e7c86b..29b6f36d7 100644 --- a/backend/src/main/java/reviewme/template/domain/Template.java +++ b/backend/src/main/java/reviewme/template/domain/Template.java @@ -28,11 +28,11 @@ public class Template { @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "template_id", nullable = false, updatable = false) - private List sectionIds; + private List sectionIds; public Template(List sectionIds) { this.sectionIds = sectionIds.stream() - .map(TemplateSectionId::new) + .map(TemplateSection::new) .toList(); } } diff --git a/backend/src/main/java/reviewme/template/domain/TemplateSectionId.java b/backend/src/main/java/reviewme/template/domain/TemplateSection.java similarity index 90% rename from backend/src/main/java/reviewme/template/domain/TemplateSectionId.java rename to backend/src/main/java/reviewme/template/domain/TemplateSection.java index b7f99f695..6d451ee80 100644 --- a/backend/src/main/java/reviewme/template/domain/TemplateSectionId.java +++ b/backend/src/main/java/reviewme/template/domain/TemplateSection.java @@ -14,7 +14,7 @@ @Table(name = "template_section") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -public class TemplateSectionId { +public class TemplateSection { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -26,7 +26,7 @@ public class TemplateSectionId { @Column(name = "section_id", nullable = false) private long sectionId; - public TemplateSectionId(long sectionId) { + public TemplateSection(long sectionId) { this.sectionId = sectionId; } } From cd44cf431e8cea3b29cb0554fa9b96d5e5d967f9 Mon Sep 17 00:00:00 2001 From: hyeonjilee Date: Mon, 12 Aug 2024 01:16:00 +0900 Subject: [PATCH 19/19] =?UTF-8?q?refactor:=20CollectionTable=EB=A5=BC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=ED=95=98=EA=B3=A0=20=EB=8B=A4=EB=8C=80?= =?UTF-8?q?=EC=9D=BC=20=EC=97=B0=EA=B4=80=EA=B4=80=EA=B3=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=9D=84=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OptionItemNotFoundException.java | 13 ++++++++++++ .../repository/OptionItemRepository.java | 9 ++++++++ .../review/service/ReviewService.java | 18 ++++++++++------ .../repository/OptionItemRepositoryTest.java | 21 ++++++++++--------- .../ReviewDetailLookupServiceTest.java | 9 ++++---- .../review/service/ReviewServiceTest.java | 7 +++---- 6 files changed, 53 insertions(+), 24 deletions(-) create mode 100644 backend/src/main/java/reviewme/question/domain/exception/OptionItemNotFoundException.java diff --git a/backend/src/main/java/reviewme/question/domain/exception/OptionItemNotFoundException.java b/backend/src/main/java/reviewme/question/domain/exception/OptionItemNotFoundException.java new file mode 100644 index 000000000..9f5200aeb --- /dev/null +++ b/backend/src/main/java/reviewme/question/domain/exception/OptionItemNotFoundException.java @@ -0,0 +1,13 @@ +package reviewme.question.domain.exception; + +import lombok.extern.slf4j.Slf4j; +import reviewme.global.exception.NotFoundException; + +@Slf4j +public class OptionItemNotFoundException extends NotFoundException { + + public OptionItemNotFoundException(long id) { + super("선택지가 존재하지 않아요."); + log.info("OptionItemNotFoundException is occurred - id: {}", id); + } +} diff --git a/backend/src/main/java/reviewme/question/repository/OptionItemRepository.java b/backend/src/main/java/reviewme/question/repository/OptionItemRepository.java index 622a2caa4..78403a045 100644 --- a/backend/src/main/java/reviewme/question/repository/OptionItemRepository.java +++ b/backend/src/main/java/reviewme/question/repository/OptionItemRepository.java @@ -1,16 +1,25 @@ package reviewme.question.repository; import java.util.List; +import java.util.Optional; import java.util.Set; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import reviewme.question.domain.OptionItem; import reviewme.question.domain.OptionType; +import reviewme.question.domain.exception.OptionItemNotFoundException; @Repository public interface OptionItemRepository extends JpaRepository { + Optional findById(long id); + + default OptionItem getOptionItemById(long id) { + return findById(id) + .orElseThrow(() -> new OptionItemNotFoundException(id)); + } + @Query(value = """ SELECT o.id FROM option_item o LEFT JOIN checkbox_answer ca diff --git a/backend/src/main/java/reviewme/review/service/ReviewService.java b/backend/src/main/java/reviewme/review/service/ReviewService.java index 09ebd471e..c333fec55 100644 --- a/backend/src/main/java/reviewme/review/service/ReviewService.java +++ b/backend/src/main/java/reviewme/review/service/ReviewService.java @@ -6,6 +6,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import reviewme.keyword.repository.KeywordRepository; +import reviewme.question.domain.OptionItem; import reviewme.question.domain.OptionType; import reviewme.question.domain.Question; import reviewme.question.repository.OptionItemRepository; @@ -15,9 +16,9 @@ import reviewme.review.domain.ReviewContent; import reviewme.review.domain.ReviewKeyword; import reviewme.review.domain.exception.CategoryOptionByReviewNotFoundException; +import reviewme.review.domain.exception.InvalidReviewAccessByReviewGroupException; import reviewme.review.domain.exception.ReviewGroupNotFoundByGroupAccessCodeException; import reviewme.review.domain.exception.ReviewGroupNotFoundByRequestReviewCodeException; -import reviewme.review.domain.exception.InvalidReviewAccessByReviewGroupException; import reviewme.review.dto.request.CreateReviewContentRequest; import reviewme.review.dto.request.CreateReviewRequest; import reviewme.review.dto.response.KeywordResponse; @@ -172,17 +173,22 @@ private ReceivedReviewResponse2 createReceivedReviewResponse2(Review2 review) { CheckboxAnswer checkboxAnswer = review.getCheckboxAnswers() .stream() .filter(answer -> optionItemRepository.existsByOptionTypeAndId( - OptionType.CATEGORY, answer.getSelectedOptionIds().get(0) + OptionType.CATEGORY, answer.getSelectedOptionIds().get(0).getSelectedOptionId() )) .findFirst() .orElseThrow(() -> new CategoryOptionByReviewNotFoundException(review.getId())); List categoryResponses = - optionItemRepository.findAllById(checkboxAnswer.getSelectedOptionIds()) + checkboxAnswer.getSelectedOptionIds() .stream() - .map(optionItem -> new ReceivedReviewCategoryResponse( - optionItem.getId(), optionItem.getContent() - )) + .map(checkBoxAnswerSelectedOptionId -> { + OptionItem optionItem = optionItemRepository.getOptionItemById( + checkBoxAnswerSelectedOptionId.getSelectedOptionId() + ); + return new ReceivedReviewCategoryResponse( + optionItem.getId(), optionItem.getContent() + ); + }) .toList(); return new ReceivedReviewResponse2( diff --git a/backend/src/test/java/reviewme/question/repository/OptionItemRepositoryTest.java b/backend/src/test/java/reviewme/question/repository/OptionItemRepositoryTest.java index 8753921a4..4ed3120b6 100644 --- a/backend/src/test/java/reviewme/question/repository/OptionItemRepositoryTest.java +++ b/backend/src/test/java/reviewme/question/repository/OptionItemRepositoryTest.java @@ -8,6 +8,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import reviewme.question.domain.OptionItem; +import reviewme.question.domain.OptionType; import reviewme.question.domain.Question2; import reviewme.question.domain.QuestionType; import reviewme.review.domain.CheckboxAnswer; @@ -30,11 +31,11 @@ class OptionItemRepositoryTest { @Test void 리뷰_아이디로_선택한_옵션_아이템_아이디를_불러온다() { // given - long optionId1 = optionItemRepository.save(new OptionItem("1", 0, 1)).getId(); - long optionId2 = optionItemRepository.save(new OptionItem("2", 0, 1)).getId(); - long optionId3 = optionItemRepository.save(new OptionItem("3", 0, 1)).getId(); - long optionId4 = optionItemRepository.save(new OptionItem("4", 0, 1)).getId(); - optionItemRepository.save(new OptionItem("5", 0, 1)); + long optionId1 = optionItemRepository.save(new OptionItem("1", 0, 1, OptionType.KEYWORD)).getId(); + long optionId2 = optionItemRepository.save(new OptionItem("2", 0, 1, OptionType.KEYWORD)).getId(); + long optionId3 = optionItemRepository.save(new OptionItem("3", 0, 1, OptionType.KEYWORD)).getId(); + long optionId4 = optionItemRepository.save(new OptionItem("4", 0, 1, OptionType.KEYWORD)).getId(); + optionItemRepository.save(new OptionItem("5", 0, 1, OptionType.KEYWORD)); List checkboxAnswers = List.of( new CheckboxAnswer(1, List.of(optionId1, optionId2)), @@ -52,11 +53,11 @@ class OptionItemRepositoryTest { @Test void 리뷰_아이디와_질문_아이디로_선택한_옵션_아이템을_순서대로_불러온다() { // given - long optionId1 = optionItemRepository.save(new OptionItem("1", 0, 3)).getId(); - long optionId2 = optionItemRepository.save(new OptionItem("2", 0, 2)).getId(); - long optionId3 = optionItemRepository.save(new OptionItem("3", 0, 1)).getId(); - long optionId4 = optionItemRepository.save(new OptionItem("4", 0, 1)).getId(); - optionItemRepository.save(new OptionItem("5", 0, 1)); + long optionId1 = optionItemRepository.save(new OptionItem("1", 0, 3, OptionType.KEYWORD)).getId(); + long optionId2 = optionItemRepository.save(new OptionItem("2", 0, 2, OptionType.KEYWORD)).getId(); + long optionId3 = optionItemRepository.save(new OptionItem("3", 0, 1, OptionType.KEYWORD)).getId(); + long optionId4 = optionItemRepository.save(new OptionItem("4", 0, 1, OptionType.KEYWORD)).getId(); + optionItemRepository.save(new OptionItem("5", 0, 1, OptionType.KEYWORD)); List checkboxAnswers = List.of( new CheckboxAnswer(1, List.of(optionId1, optionId3)), diff --git a/backend/src/test/java/reviewme/review/service/ReviewDetailLookupServiceTest.java b/backend/src/test/java/reviewme/review/service/ReviewDetailLookupServiceTest.java index 9704fcba8..d9defe07e 100644 --- a/backend/src/test/java/reviewme/review/service/ReviewDetailLookupServiceTest.java +++ b/backend/src/test/java/reviewme/review/service/ReviewDetailLookupServiceTest.java @@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired; import reviewme.question.domain.OptionGroup; import reviewme.question.domain.OptionItem; +import reviewme.question.domain.OptionType; import reviewme.question.domain.Question2; import reviewme.question.domain.QuestionType; import reviewme.question.repository.OptionGroupRepository; @@ -61,8 +62,8 @@ class ReviewDetailLookupServiceTest { Question2 question2 = questionRepository.save(new Question2(true, QuestionType.CHECKBOX, "질문", null, 1)); Question2 question3 = questionRepository.save(new Question2(true, QuestionType.TEXT, "체크 1 조건", "가이드라인", 1)); OptionGroup optionGroup = optionGroupRepository.save(new OptionGroup(question2.getId(), 1, 3)); - OptionItem optionItem1 = optionItemRepository.save(new OptionItem("체크 1", optionGroup.getId(), 1)); - OptionItem optionItem2 = optionItemRepository.save(new OptionItem("체크 2", optionGroup.getId(), 1)); + OptionItem optionItem1 = optionItemRepository.save(new OptionItem("체크 1", optionGroup.getId(), 1, OptionType.KEYWORD)); + OptionItem optionItem2 = optionItemRepository.save(new OptionItem("체크 2", optionGroup.getId(), 1, OptionType.KEYWORD)); Section section1 = sectionRepository.save( new Section(VisibleType.ALWAYS, List.of(question1.getId(), question2.getId()), null, "1번 섹션", 1) @@ -98,8 +99,8 @@ class ReviewDetailLookupServiceTest { Question2 question2 = questionRepository.save(new Question2(true, QuestionType.CHECKBOX, "질문", null, 2)); Question2 question3 = questionRepository.save(new Question2(true, QuestionType.TEXT, "체크 1 조건", "가이드라인", 3)); OptionGroup optionGroup = optionGroupRepository.save(new OptionGroup(question2.getId(), 1, 3)); - OptionItem optionItem1 = optionItemRepository.save(new OptionItem("체크 1", optionGroup.getId(), 1)); - OptionItem optionItem2 = optionItemRepository.save(new OptionItem("체크 2", optionGroup.getId(), 2)); + OptionItem optionItem1 = optionItemRepository.save(new OptionItem("체크 1", optionGroup.getId(), 1, OptionType.KEYWORD)); + OptionItem optionItem2 = optionItemRepository.save(new OptionItem("체크 2", optionGroup.getId(), 2, OptionType.KEYWORD)); Section section1 = sectionRepository.save( new Section(VisibleType.ALWAYS, List.of(question1.getId(), question2.getId()), null, "1번 섹션", 1) diff --git a/backend/src/test/java/reviewme/review/service/ReviewServiceTest.java b/backend/src/test/java/reviewme/review/service/ReviewServiceTest.java index 6aeb610a8..5fa630cd6 100644 --- a/backend/src/test/java/reviewme/review/service/ReviewServiceTest.java +++ b/backend/src/test/java/reviewme/review/service/ReviewServiceTest.java @@ -169,8 +169,8 @@ class ReviewServiceTest { ); CheckboxAnswer categoryAnswer1 = new CheckboxAnswer(question1.getId(), List.of(categoryOption1.getId())); CheckboxAnswer categoryAnswer2 = new CheckboxAnswer(question1.getId(), List.of(categoryOption2.getId())); - Review2 review1 = new Review2(template.getId(), reviewGroup.getId(), List.of(), List.of(categoryAnswer1), LocalDateTime.now()); - Review2 review2 = new Review2(template.getId(), reviewGroup.getId(), List.of(), List.of(categoryAnswer2), LocalDateTime.now()); + Review2 review1 = new Review2(template.getId(), reviewGroup.getId(), List.of(), List.of(categoryAnswer1)); + Review2 review2 = new Review2(template.getId(), reviewGroup.getId(), List.of(), List.of(categoryAnswer2)); review2Repository.saveAll(List.of(review1, review2)); // when @@ -271,8 +271,7 @@ class ReviewServiceTest { CheckboxAnswer categoryAnswer = new CheckboxAnswer(question1.getId(), List.of(categoryOption1.getId())); CheckboxAnswer keywordAnswer = new CheckboxAnswer(question2.getId(), List.of(keywordOption.getId())); review2Repository.save( - new Review2(template.getId(), reviewGroup.getId(), List.of(), List.of(categoryAnswer, keywordAnswer), - LocalDateTime.now()) + new Review2(template.getId(), reviewGroup.getId(), List.of(), List.of(categoryAnswer, keywordAnswer)) ); // when