diff --git a/backend/src/main/java/reviewme/DatabaseInitializer.java b/backend/src/main/java/reviewme/DatabaseInitializer.java index 495687a5a..84cfcaed5 100644 --- a/backend/src/main/java/reviewme/DatabaseInitializer.java +++ b/backend/src/main/java/reviewme/DatabaseInitializer.java @@ -2,36 +2,20 @@ import jakarta.annotation.PostConstruct; import jakarta.transaction.Transactional; -import java.time.LocalDateTime; -import java.util.List; -import java.util.stream.Stream; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; import reviewme.keyword.domain.Keyword; import reviewme.keyword.repository.KeywordRepository; -import reviewme.member.domain.Member; -import reviewme.member.domain.ReviewerGroup; -import reviewme.member.repository.MemberRepository; -import reviewme.member.repository.ReviewerGroupRepository; -import reviewme.review.domain.Question; -import reviewme.review.domain.Review; -import reviewme.review.domain.ReviewContent; +import reviewme.question.domain.Question; import reviewme.review.repository.QuestionRepository; -import reviewme.review.repository.ReviewContentRepository; -import reviewme.review.repository.ReviewRepository; @Profile("local") @Component @RequiredArgsConstructor public class DatabaseInitializer { - private final MemberRepository memberRepository; - private final ReviewRepository reviewRepository; - private final ReviewerGroupRepository reviewerGroupRepository; - private final QuestionRepository questionRepository; - private final ReviewContentRepository reviewContentRepository; private final KeywordRepository keywordRepository; @PostConstruct @@ -45,48 +29,5 @@ void setup() { Keyword keyword3 = keywordRepository.save(new Keyword("의견을 잘 조율해요")); Keyword keyword4 = keywordRepository.save(new Keyword("꼼꼼하게 기록해요")); Keyword keyword5 = keywordRepository.save(new Keyword("말투가 상냥해요")); - - Member 아루 = memberRepository.save(new Member("아루", 1L)); - Member 산초 = memberRepository.save(new Member("산초", 2L)); - Member 커비 = memberRepository.save(new Member("커비", 3L)); - Member 테드 = memberRepository.save(new Member("테드", 4L)); - Member 올리 = memberRepository.save(new Member("올리", 5L)); - Member 바다 = memberRepository.save(new Member("바다", 6L)); - Member 쑤쑤 = memberRepository.save(new Member("쑤쑤", 7L)); - Member 에프이 = memberRepository.save(new Member("에프이", 8L)); - - ReviewerGroup 산초리뷰그룹 = reviewerGroupRepository.save( - new ReviewerGroup( - 산초, - Stream.of(아루, 커비, 테드, 올리, 바다, 쑤쑤, 에프이).map(Member::getGithubId).toList(), - "2024-review-me", - "우테코에서 진행한 상호 리뷰 프로젝트입니다.", - LocalDateTime.now().plusDays(7) - ) - ); - - Review review = reviewRepository.save( - new Review(아루, 산초, 산초리뷰그룹, List.of(keyword1, keyword2), LocalDateTime.now().minusHours(10)) - ); - String answer = "안녕하세요, 산초님. 먼저 리뷰 프로젝트에 대한 헌신과 노력에 감사드립니다. 산초 님의 기여는 팀 전체의 성과에 큰 도움이 되었습니다. 산초 님은 코드의 가독성과 유지보수성을 높이기 위해 항상 깨끗하고 잘 구조화된 코드를 작성하셨습니다. 주석을 통해 코드의 의도를 명확히 설명하는 점도 인상적이었습니다. 또한, 복잡한 문제를 분석하고 효과적으로 해결하는 능력이 뛰어납니다. 특히 리뷰 과정에서 발생한 버그를 신속하게 찾아내고 수정하는 능력은 매우 탁월했습니다. 팀원들과의 원활한 소통을 통해 협업을 촉진하고, 피드백을 적극 수용하는 태도가 매우 좋았습니다. 덕분에 프로젝트 진행이 순조로웠습니다. 개선할 부분으로는 현재 작성하신 테스트가 기본적인 시나리오를 잘 다루고 있으나, 다양한 엣지 케이스와 예외 상황을 다루는 테스트가 부족한 경향이 있습니다. 테스트 커버리지를 넓히면 코드의 안정성을 더욱 높일 수 있습니다. 또한 코드의 효율성 측면에서 개선할 여지가 있습니다. 특히, 리뷰 과정에서 성능이 중요한 부분에서는 알고리즘 최적화나 데이터 처리 방식을 개선하는 방법을 고민해 볼 필요가 있습니다. 마지막으로 코드 자체에 대한 주석은 훌륭하지만, 프로젝트 전반에 대한 문서화가 조금 더 상세했으면 합니다. 이를 통해 새로운 팀원이 프로젝트에 빠르게 적응할 수 있도록 도울 수 있습니다. 최신 기술 동향을 지속적으로 학습하고, 새로운 프레임워크나 도구를 실험해 보는 것도 추천드립니다. 예를 들어, 테스트 자동화 도구나 성능 최적화 기법을 도입하면 더욱 효율적인 개발을 할 수 있을 것입니다. 앞으로도 지속적인 발전을 기대합니다. 감사합니다."; - String answer2 = "산초는 자바 백엔드 개발자로서, Spring Framework 및 Hibernate를 이용한 RESTful API 개발에 능숙합니다. 또한, 마이크로서비스 아키텍처 설계에 대한 깊은 이해를 바탕으로 Docker 및 Kubernetes를 활용한 컨테이너화에 숙련되어 있습니다. 데이터베이스 최적화와 보안에 관한 전문 지식을 갖추고 있어 복잡한 시스템을 효율적으로 관리합니다. 소프트 스킬 측면에서 산초는 팀 내 커뮤니케이션을 강화하는 데 중점을 두고 있습니다. 그는 명확한 커뮤니케이션과 활발한 피드백을 통해 프로젝트의 투명성과 팀원 간의 신뢰를 높이는 데 기여하고 있습니다. 이러한 능력은 프로젝트 관리와 협업에 있어서 큰 자산이 되며, 팀 내에서 문제 해결자로서의 역할을 탁월하게 수행하고 있습니다."; - reviewContentRepository.save(new ReviewContent(review, question1, answer)); - reviewContentRepository.save(new ReviewContent(review, question2, answer2)); - - Review review2 = reviewRepository.save( - new Review(커비, 산초, 산초리뷰그룹, List.of(keyword3, keyword4, keyword5), LocalDateTime.now().minusHours(5)) - ); - String answer3 = "산초는 자바 기반의 백엔드 개발에서 뛰어난 실력을 보여줍니다. 특히, 스프링과 하이버네이트를 활용한 복잡한 서버 사이드 애플리케이션을 구축하는데 숙련되어 있으며, 클라우드 인프라와 서비스 운영에도 밝습니다. 그의 기술력은 대규모 데이터 처리와 시스템 통합 프로젝트에서도 두각을 나타내며, 기술적 문제 해결에 강한 역량을 가지고 있습니다."; - String answer4 = "인간적인 면에서 산초는 팀워크를 중시하는 개발자로, 팀 동료와의 원활한 소통을 위해 끊임없이 노력합니다. 그는 프로젝트의 성공을 위해 동료들과 지속적으로 지식을 공유하며, 협업하는 과정에서 발생하는 문제들을 해결하기 위해 적극적으로 나서는 것을 주저하지 않습니다. 이러한 성향은 프로젝트 진행에 있어 매우 긍정적인 영향을 미치며, 팀 내에서의 그의 역할은 매우 중요합니다."; - reviewContentRepository.save(new ReviewContent(review2, question1, answer3)); - reviewContentRepository.save(new ReviewContent(review2, question2, answer4)); - - Review review3 = reviewRepository.save( - new Review(테드, 산초, 산초리뷰그룹, List.of(keyword1, keyword2, keyword3, keyword4, keyword5), LocalDateTime.now().minusHours(3)) - ); - String answer5 = "산초는 자바 백엔드 개발 분야에서 두각을 나타내는 개발자이다. 스프링 부트와 JPA를 활용한 효율적인 웹 서비스 구축에 능숙하며, 오픈 소스 도구들을 통합해 개발 프로세스를 최적화하는 데 깊은 지식을 갖고 있다. 또한, 비동기 프로그래밍과 멀티스레딩을 이용하여 고성능 백엔드 시스템을 설계하고 구현하는 능력도 탁월하다."; - String answer6 = "소프트 스킬 측면에서도 산초는 눈에 띄는 역량을 보여준다. 팀원들과의 의사소통을 원활하게 하며, 갈등 상황에서 중재자 역할을 하여 팀 내 긴장을 완화시키는 데 큰 도움을 준다. 이러한 노력으로 프로젝트의 분위기를 긍정적으로 이끌고, 팀의 동기 부여를 향상시키는 데 중요한 역할을 하고 있다. 그의 리더십과 팀워크는 프로젝트의 성공적인 완수에 결정적인 영향을 미치고 있다."; - reviewContentRepository.save(new ReviewContent(review3, question1, answer5)); - reviewContentRepository.save(new ReviewContent(review3, question2, answer6)); } } diff --git a/backend/src/main/java/reviewme/keyword/domain/Keyword.java b/backend/src/main/java/reviewme/keyword/domain/Keyword.java index 73a66b1b6..d140bb7c0 100644 --- a/backend/src/main/java/reviewme/keyword/domain/Keyword.java +++ b/backend/src/main/java/reviewme/keyword/domain/Keyword.java @@ -6,7 +6,6 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; -import java.util.Objects; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -28,25 +27,4 @@ public Keyword(String content) { this.content = content; } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Keyword keyword)) { - return false; - } - if (id == null) { - return Objects.equals(content, keyword.content); - } - return Objects.equals(id, keyword.id); - } - - @Override - public int hashCode() { - if (id == null) { - return Objects.hash(content); - } - return Objects.hash(id); - } } diff --git a/backend/src/main/java/reviewme/keyword/domain/Keywords.java b/backend/src/main/java/reviewme/keyword/domain/Keywords.java deleted file mode 100644 index 8d8f5e15d..000000000 --- a/backend/src/main/java/reviewme/keyword/domain/Keywords.java +++ /dev/null @@ -1,51 +0,0 @@ -package reviewme.keyword.domain; - -import jakarta.persistence.CollectionTable; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.Embeddable; -import jakarta.persistence.FetchType; -import jakarta.persistence.JoinColumn; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import reviewme.keyword.domain.exception.DuplicateKeywordException; -import reviewme.keyword.domain.exception.KeywordLimitExceedException; - -@Embeddable -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -public class Keywords { - - private static final int MAX_KEYWORD_COUNT = 5; - - @ElementCollection(fetch = FetchType.EAGER) - @CollectionTable(name = "review_keyword", joinColumns = @JoinColumn(name = "review_id")) - private Set keywordIds; - - public Keywords(List selectedKeywords) { - if (selectedKeywords.size() > MAX_KEYWORD_COUNT) { - throw new KeywordLimitExceedException(MAX_KEYWORD_COUNT); - } - if (hasDuplicateKeywords(selectedKeywords)) { - throw new DuplicateKeywordException(); - } - this.keywordIds = selectedKeywords.stream() - .map(Keyword::getId) - .collect(Collectors.toSet()); - } - - private boolean hasDuplicateKeywords(List selectedKeywords) { - long distinctKeywordCount = selectedKeywords.stream() - .distinct() - .count(); - return selectedKeywords.size() != distinctKeywordCount; - } - - public Set getKeywordIds() { - return Collections.unmodifiableSet(keywordIds); - } -} diff --git a/backend/src/main/java/reviewme/keyword/domain/exception/KeywordLimitExceedException.java b/backend/src/main/java/reviewme/keyword/domain/exception/KeywordLimitExceedException.java index 955f02b24..384621a4b 100644 --- a/backend/src/main/java/reviewme/keyword/domain/exception/KeywordLimitExceedException.java +++ b/backend/src/main/java/reviewme/keyword/domain/exception/KeywordLimitExceedException.java @@ -4,7 +4,7 @@ public class KeywordLimitExceedException extends BadRequestException { - public KeywordLimitExceedException(int maxSize) { - super("키워드는 최대 %d개 선택할 수 있습니다.".formatted(maxSize)); + public KeywordLimitExceedException(int minSize, int maxSize) { + super("키워드는 최소 %d개, 최대 %d개 선택할 수 있어요.".formatted(minSize, maxSize)); } } diff --git a/backend/src/main/java/reviewme/keyword/exception/KeywordNotFoundException.java b/backend/src/main/java/reviewme/keyword/domain/exception/KeywordNotFoundException.java similarity index 63% rename from backend/src/main/java/reviewme/keyword/exception/KeywordNotFoundException.java rename to backend/src/main/java/reviewme/keyword/domain/exception/KeywordNotFoundException.java index 11dc2da92..ea438ef91 100644 --- a/backend/src/main/java/reviewme/keyword/exception/KeywordNotFoundException.java +++ b/backend/src/main/java/reviewme/keyword/domain/exception/KeywordNotFoundException.java @@ -1,10 +1,10 @@ -package reviewme.keyword.exception; +package reviewme.keyword.domain.exception; import reviewme.global.exception.NotFoundException; public class KeywordNotFoundException extends NotFoundException { public KeywordNotFoundException() { - super("키워드가 존재하지 않습니다."); + super("키워드가 존재하지 않아요."); } } diff --git a/backend/src/main/java/reviewme/keyword/dto/response/KeywordsResponse.java b/backend/src/main/java/reviewme/keyword/dto/response/KeywordsResponse.java deleted file mode 100644 index fdc71cc2a..000000000 --- a/backend/src/main/java/reviewme/keyword/dto/response/KeywordsResponse.java +++ /dev/null @@ -1,12 +0,0 @@ -package reviewme.keyword.dto.response; - -import io.swagger.v3.oas.annotations.media.Schema; -import java.util.List; - -@Schema(description = "키워드 목록 응답") -public record KeywordsResponse( - - @Schema(description = "키워드 목록") - List keywords -) { -} diff --git a/backend/src/main/java/reviewme/keyword/repository/KeywordRepository.java b/backend/src/main/java/reviewme/keyword/repository/KeywordRepository.java index a0ed67b33..39613f267 100644 --- a/backend/src/main/java/reviewme/keyword/repository/KeywordRepository.java +++ b/backend/src/main/java/reviewme/keyword/repository/KeywordRepository.java @@ -3,7 +3,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import reviewme.keyword.domain.Keyword; -import reviewme.keyword.exception.KeywordNotFoundException; +import reviewme.keyword.domain.exception.KeywordNotFoundException; @Repository public interface KeywordRepository extends JpaRepository { diff --git a/backend/src/main/java/reviewme/keyword/service/KeywordService.java b/backend/src/main/java/reviewme/keyword/service/KeywordService.java deleted file mode 100644 index f971f5127..000000000 --- a/backend/src/main/java/reviewme/keyword/service/KeywordService.java +++ /dev/null @@ -1,23 +0,0 @@ -package reviewme.keyword.service; - -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import reviewme.keyword.dto.response.KeywordResponse; -import reviewme.keyword.repository.KeywordRepository; - -@Service -@RequiredArgsConstructor -public class KeywordService { - - private final KeywordRepository keywordRepository; - - @Transactional(readOnly = true) - public List findAllKeywords() { - return keywordRepository.findAll() - .stream() - .map(keyword -> new KeywordResponse(keyword.getId(), keyword.getContent())) - .toList(); - } -} diff --git a/backend/src/main/java/reviewme/member/controller/ReviewerGroupApi.java b/backend/src/main/java/reviewme/member/controller/ReviewerGroupApi.java deleted file mode 100644 index c9066c526..000000000 --- a/backend/src/main/java/reviewme/member/controller/ReviewerGroupApi.java +++ /dev/null @@ -1,17 +0,0 @@ -package reviewme.member.controller; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import reviewme.member.dto.response.ReviewerGroupResponse; - -@Tag(name = "리뷰어 그룹 관리") -public interface ReviewerGroupApi { - - @Operation( - summary = "리뷰어 그룹 조회", - description = "리뷰어 그룹을 조회한다." - ) - ResponseEntity findReviewerGroup(@PathVariable long id); -} diff --git a/backend/src/main/java/reviewme/member/controller/ReviewerGroupController.java b/backend/src/main/java/reviewme/member/controller/ReviewerGroupController.java deleted file mode 100644 index 6c2ef4853..000000000 --- a/backend/src/main/java/reviewme/member/controller/ReviewerGroupController.java +++ /dev/null @@ -1,22 +0,0 @@ -package reviewme.member.controller; - -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RestController; -import reviewme.member.dto.response.ReviewerGroupResponse; -import reviewme.member.service.ReviewerGroupService; - -@RestController -@RequiredArgsConstructor -public class ReviewerGroupController implements ReviewerGroupApi { - - private final ReviewerGroupService reviewerGroupService; - - @GetMapping("/reviewer-groups/{id}") - public ResponseEntity findReviewerGroup(@PathVariable long id) { - ReviewerGroupResponse response = reviewerGroupService.findReviewerGroup(id); - return ResponseEntity.ok(response); - } -} diff --git a/backend/src/main/java/reviewme/member/domain/GithubId.java b/backend/src/main/java/reviewme/member/domain/GithubId.java deleted file mode 100644 index c15a245c2..000000000 --- a/backend/src/main/java/reviewme/member/domain/GithubId.java +++ /dev/null @@ -1,22 +0,0 @@ -package reviewme.member.domain; - -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; -import lombok.AccessLevel; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Embeddable -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@EqualsAndHashCode(of = "id") -@Getter -public class GithubId { - - @Column(name = "github_id", nullable = false) - private long id; - - public GithubId(long id) { - this.id = id; - } -} diff --git a/backend/src/main/java/reviewme/member/domain/GithubIdReviewerGroup.java b/backend/src/main/java/reviewme/member/domain/GithubIdReviewerGroup.java deleted file mode 100644 index 7eb185600..000000000 --- a/backend/src/main/java/reviewme/member/domain/GithubIdReviewerGroup.java +++ /dev/null @@ -1,60 +0,0 @@ -package reviewme.member.domain; - -import jakarta.persistence.Embedded; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; -import java.util.Objects; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Table(name = "github_id_reviewer_group") -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -public class GithubIdReviewerGroup { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Embedded - private GithubId githubId; - - @ManyToOne - @JoinColumn(name = "reviewer_group_id", nullable = false) - private ReviewerGroup reviewerGroup; - - public GithubIdReviewerGroup(GithubId githubId, ReviewerGroup reviewerGroup) { - this.githubId = githubId; - this.reviewerGroup = reviewerGroup; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof GithubIdReviewerGroup githubIdReviewerGroup)) { - return false; - } - if (id == null) { - return Objects.equals(githubId, githubIdReviewerGroup.githubId); - } - - return Objects.equals(id, githubIdReviewerGroup.id); - } - - @Override - public int hashCode() { - if (id == null) { - return Objects.hash(githubId); - } - return Objects.hash(id); - } -} diff --git a/backend/src/main/java/reviewme/member/domain/Member.java b/backend/src/main/java/reviewme/member/domain/Member.java deleted file mode 100644 index 0e142fb2d..000000000 --- a/backend/src/main/java/reviewme/member/domain/Member.java +++ /dev/null @@ -1,57 +0,0 @@ -package reviewme.member.domain; - -import jakarta.persistence.Column; -import jakarta.persistence.Embedded; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import java.util.Objects; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Table(name = "member") -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -public class Member { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "name", nullable = false) - private String name; - - @Embedded - private GithubId githubId; - - public Member(String name, long githubId) { - this.name = name; - this.githubId = new GithubId(githubId); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Member member)) { - return false; - } - if (id == null) { - return Objects.equals(githubId, member.githubId); - } - return Objects.equals(id, member.id); - } - - @Override - public int hashCode() { - if (id == null) { - return Objects.hash(githubId); - } - return Objects.hash(id); - } -} diff --git a/backend/src/main/java/reviewme/member/domain/ReviewerGroup.java b/backend/src/main/java/reviewme/member/domain/ReviewerGroup.java deleted file mode 100644 index b1fcb948e..000000000 --- a/backend/src/main/java/reviewme/member/domain/ReviewerGroup.java +++ /dev/null @@ -1,116 +0,0 @@ -package reviewme.member.domain; - -import jakarta.persistence.Column; -import jakarta.persistence.Embedded; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import lombok.AccessLevel; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import reviewme.member.domain.exception.InvalidDescriptionLengthException; -import reviewme.member.domain.exception.InvalidGroupNameLengthException; -import reviewme.member.domain.exception.SelfReviewException; -import reviewme.review.domain.Review; -import reviewme.review.domain.exception.DeadlineExpiredException; -import reviewme.review.domain.exception.RevieweeMismatchException; -import reviewme.review.exception.GithubReviewerGroupUnAuthorizedException; -import reviewme.review.exception.ReviewAlreadySubmittedException; - -@Entity -@Table(name = "reviewer_group") -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@EqualsAndHashCode(of = "id") -@Getter -public class ReviewerGroup { - - private static final Duration DEADLINE_DURATION = Duration.ofDays(7); - private static final int MAX_DESCRIPTION_LENGTH = 50; - private static final int MAX_GROUP_NAME_LENGTH = 100; - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Embedded - private ReviewerGroupGithubIds reviewerGithubIds; - - @ManyToOne - @JoinColumn(name = "reviewee_id", nullable = false) - private Member reviewee; - - @OneToMany(mappedBy = "reviewerGroup") - private List reviews; - - @Column(name = "group_name", nullable = false) - private String groupName; - - @Column(name = "description", nullable = false) - private String description; - - @Column(name = "deadline", nullable = false) - private LocalDateTime deadline; - - @Column(name = "thumbnail_url", nullable = false) - private String thumbnailUrl; - - public ReviewerGroup(Member reviewee, List reviewerGithubIds, - String groupName, String description, LocalDateTime deadline) { - if (groupName.isBlank() || groupName.length() > MAX_GROUP_NAME_LENGTH) { - throw new InvalidGroupNameLengthException(MAX_GROUP_NAME_LENGTH); - } - if (description.length() > MAX_DESCRIPTION_LENGTH) { - throw new InvalidDescriptionLengthException(MAX_DESCRIPTION_LENGTH); - } - if (reviewerGithubIds.contains(reviewee.getGithubId())) { - throw new SelfReviewException(); - } - this.reviewee = reviewee; - this.reviewerGithubIds = new ReviewerGroupGithubIds(this, reviewerGithubIds); - this.groupName = groupName; - this.description = description; - this.deadline = deadline; - this.reviews = new ArrayList<>(); - this.thumbnailUrl = "https://github.com/octocat.png"; - } - - public boolean isDeadlineExceeded(LocalDateTime now) { - return now.isAfter(deadline); - } - - public void addReview(Review review) { - Member reviewer = review.getReviewer(); - if (isDeadlineExceeded(review.getCreatedAt())) { - throw new DeadlineExpiredException(); - } - if (hasSubmittedReviewBy(reviewer)) { - throw new ReviewAlreadySubmittedException(); - } - if (reviewerGithubIds.doesNotContain(reviewer)) { - throw new GithubReviewerGroupUnAuthorizedException(); - } - if (!review.getReviewee().equals(reviewee)) { - throw new RevieweeMismatchException(); - } - reviews.add(review); - } - - private boolean hasSubmittedReviewBy(Member reviewer) { - return reviews.stream() - .anyMatch(review -> review.isSubmittedBy(reviewer)); - } - - public void addReviewerGithubId(GithubIdReviewerGroup githubIdReviewerGroup) { - reviewerGithubIds.add(githubIdReviewerGroup); - } -} diff --git a/backend/src/main/java/reviewme/member/domain/ReviewerGroupGithubIds.java b/backend/src/main/java/reviewme/member/domain/ReviewerGroupGithubIds.java deleted file mode 100644 index fd00922be..000000000 --- a/backend/src/main/java/reviewme/member/domain/ReviewerGroupGithubIds.java +++ /dev/null @@ -1,47 +0,0 @@ -package reviewme.member.domain; - -import jakarta.persistence.CascadeType; -import jakarta.persistence.Embeddable; -import jakarta.persistence.OneToMany; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import reviewme.member.domain.exception.DuplicateReviewerException; -import reviewme.member.domain.exception.EmptyReviewerException; - -@Embeddable -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class ReviewerGroupGithubIds { - - @OneToMany(mappedBy = "reviewerGroup", cascade = CascadeType.PERSIST) - private Set reviewerGithubIds; - - public ReviewerGroupGithubIds(ReviewerGroup reviewerGroup, List githubIds) { - if (githubIds.isEmpty()) { - throw new EmptyReviewerException(); - } - Set reviewers = githubIds.stream() - .map(githubId -> new GithubIdReviewerGroup(githubId, reviewerGroup)) - .collect(Collectors.toSet()); -// if (reviewers.size() != githubIds.size()) { -// throw new DuplicateReviewerException(); -// } - this.reviewerGithubIds = reviewers; - } - - public void add(GithubIdReviewerGroup githubIdReviewerGroup) { - if (reviewerGithubIds.contains(githubIdReviewerGroup)) { - throw new DuplicateReviewerException(); - } - reviewerGithubIds.add(githubIdReviewerGroup); - } - - public boolean doesNotContain(Member reviewer) { - GithubId githubId = reviewer.getGithubId(); - return reviewerGithubIds.stream() - .map(GithubIdReviewerGroup::getGithubId) - .noneMatch(githubId::equals); - } -} diff --git a/backend/src/main/java/reviewme/member/domain/exception/DuplicateReviewerException.java b/backend/src/main/java/reviewme/member/domain/exception/DuplicateReviewerException.java deleted file mode 100644 index 133dee121..000000000 --- a/backend/src/main/java/reviewme/member/domain/exception/DuplicateReviewerException.java +++ /dev/null @@ -1,10 +0,0 @@ -package reviewme.member.domain.exception; - -import reviewme.global.exception.BadRequestException; - -public class DuplicateReviewerException extends BadRequestException { - - public DuplicateReviewerException() { - super("리뷰어는 중복될 수 없습니다."); - } -} diff --git a/backend/src/main/java/reviewme/member/domain/exception/EmptyReviewerException.java b/backend/src/main/java/reviewme/member/domain/exception/EmptyReviewerException.java deleted file mode 100644 index 3d66df0df..000000000 --- a/backend/src/main/java/reviewme/member/domain/exception/EmptyReviewerException.java +++ /dev/null @@ -1,10 +0,0 @@ -package reviewme.member.domain.exception; - -import reviewme.global.exception.BadRequestException; - -public class EmptyReviewerException extends BadRequestException { - - public EmptyReviewerException() { - super("리뷰어는 최소 한 명 이상이어야 합니다."); - } -} diff --git a/backend/src/main/java/reviewme/member/domain/exception/InvalidDescriptionLengthException.java b/backend/src/main/java/reviewme/member/domain/exception/InvalidDescriptionLengthException.java deleted file mode 100644 index 35a19cd1f..000000000 --- a/backend/src/main/java/reviewme/member/domain/exception/InvalidDescriptionLengthException.java +++ /dev/null @@ -1,10 +0,0 @@ -package reviewme.member.domain.exception; - -import reviewme.global.exception.BadRequestException; - -public class InvalidDescriptionLengthException extends BadRequestException { - - public InvalidDescriptionLengthException(int maxLength) { - super("리뷰어 그룹 설명은 %d자 이하로 작성해야 합니다.".formatted(maxLength)); - } -} diff --git a/backend/src/main/java/reviewme/member/domain/exception/InvalidGroupNameLengthException.java b/backend/src/main/java/reviewme/member/domain/exception/InvalidGroupNameLengthException.java deleted file mode 100644 index ca719614e..000000000 --- a/backend/src/main/java/reviewme/member/domain/exception/InvalidGroupNameLengthException.java +++ /dev/null @@ -1,10 +0,0 @@ -package reviewme.member.domain.exception; - -import reviewme.global.exception.BadRequestException; - -public class InvalidGroupNameLengthException extends BadRequestException { - - public InvalidGroupNameLengthException(int maxLength) { - super("리뷰 그룹 이름은 1글자 이상 %d글자 이하여야 합니다.".formatted(maxLength)); - } -} diff --git a/backend/src/main/java/reviewme/member/domain/exception/SelfReviewException.java b/backend/src/main/java/reviewme/member/domain/exception/SelfReviewException.java deleted file mode 100644 index 289e8bd12..000000000 --- a/backend/src/main/java/reviewme/member/domain/exception/SelfReviewException.java +++ /dev/null @@ -1,10 +0,0 @@ -package reviewme.member.domain.exception; - -import reviewme.global.exception.BadRequestException; - -public class SelfReviewException extends BadRequestException { - - public SelfReviewException() { - super("자신을 리뷰어로 추가할 수 없습니다."); - } -} diff --git a/backend/src/main/java/reviewme/member/dto/response/MemberResponse.java b/backend/src/main/java/reviewme/member/dto/response/MemberResponse.java deleted file mode 100644 index b78899935..000000000 --- a/backend/src/main/java/reviewme/member/dto/response/MemberResponse.java +++ /dev/null @@ -1,14 +0,0 @@ -package reviewme.member.dto.response; - -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "사용자 응답") -public record MemberResponse( - - @Schema(description = "사용자 ID") - long id, - - @Schema(description = "사용자 이름") - String name -) { -} diff --git a/backend/src/main/java/reviewme/member/dto/response/ReviewCreationReviewerGroupResponse.java b/backend/src/main/java/reviewme/member/dto/response/ReviewCreationReviewerGroupResponse.java deleted file mode 100644 index 7c902532a..000000000 --- a/backend/src/main/java/reviewme/member/dto/response/ReviewCreationReviewerGroupResponse.java +++ /dev/null @@ -1,27 +0,0 @@ -package reviewme.member.dto.response; - -import io.swagger.v3.oas.annotations.media.Schema; -import java.time.LocalDateTime; - -@Schema(description = "리뷰 생성 시 필요한 리뷰어 그룹 응답") -public record ReviewCreationReviewerGroupResponse( - - @Schema(description = "리뷰어 그룹 아이디") - long id, - - @Schema(description = "리뷰 그룹 이름 (레포지토리명)") - String name, - - @Schema(description = "그룹 소개") - String description, - - @Schema(description = "리뷰 작성 기한") - LocalDateTime deadline, - - @Schema(description = "썸네일 URL") - String thumbnailUrl, - - @Schema(description = "리뷰이") - MemberResponse reviewee -) { -} diff --git a/backend/src/main/java/reviewme/member/dto/response/ReviewerGroupResponse.java b/backend/src/main/java/reviewme/member/dto/response/ReviewerGroupResponse.java deleted file mode 100644 index a8c313415..000000000 --- a/backend/src/main/java/reviewme/member/dto/response/ReviewerGroupResponse.java +++ /dev/null @@ -1,21 +0,0 @@ -package reviewme.member.dto.response; - -import io.swagger.v3.oas.annotations.media.Schema; -import java.time.LocalDateTime; - -@Schema(description = "리뷰어 그룹 응답") -public record ReviewerGroupResponse( - - @Schema(description = "리뷰어 그룹 아이디") - long id, - - @Schema(description = "리뷰 그룹 이름 (레포지토리명)") - String name, - - @Schema(description = "리뷰 작성 기한") - LocalDateTime deadline, - - @Schema(description = "리뷰이") - MemberResponse reviewee -) { -} diff --git a/backend/src/main/java/reviewme/member/exception/MemberNotFoundException.java b/backend/src/main/java/reviewme/member/exception/MemberNotFoundException.java deleted file mode 100644 index a089f0dc0..000000000 --- a/backend/src/main/java/reviewme/member/exception/MemberNotFoundException.java +++ /dev/null @@ -1,10 +0,0 @@ -package reviewme.member.exception; - -import reviewme.global.exception.NotFoundException; - -public class MemberNotFoundException extends NotFoundException { - - public MemberNotFoundException() { - super("회원이 존재하지 않습니다."); - } -} diff --git a/backend/src/main/java/reviewme/member/exception/ReviewerGroupNotFoundException.java b/backend/src/main/java/reviewme/member/exception/ReviewerGroupNotFoundException.java deleted file mode 100644 index 02ee77b72..000000000 --- a/backend/src/main/java/reviewme/member/exception/ReviewerGroupNotFoundException.java +++ /dev/null @@ -1,10 +0,0 @@ -package reviewme.member.exception; - -import reviewme.global.exception.NotFoundException; - -public class ReviewerGroupNotFoundException extends NotFoundException { - - public ReviewerGroupNotFoundException() { - super("리뷰어 그룹이 존재하지 않습니다."); - } -} diff --git a/backend/src/main/java/reviewme/member/repository/MemberRepository.java b/backend/src/main/java/reviewme/member/repository/MemberRepository.java deleted file mode 100644 index 277a46a8b..000000000 --- a/backend/src/main/java/reviewme/member/repository/MemberRepository.java +++ /dev/null @@ -1,14 +0,0 @@ -package reviewme.member.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -import reviewme.member.domain.Member; -import reviewme.member.exception.MemberNotFoundException; - -@Repository -public interface MemberRepository extends JpaRepository { - - default Member getMemberById(long id) { - return findById(id).orElseThrow(MemberNotFoundException::new); - } -} diff --git a/backend/src/main/java/reviewme/member/repository/ReviewerGroupRepository.java b/backend/src/main/java/reviewme/member/repository/ReviewerGroupRepository.java deleted file mode 100644 index 35decc56a..000000000 --- a/backend/src/main/java/reviewme/member/repository/ReviewerGroupRepository.java +++ /dev/null @@ -1,14 +0,0 @@ -package reviewme.member.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -import reviewme.member.domain.ReviewerGroup; -import reviewme.member.exception.ReviewerGroupNotFoundException; - -@Repository -public interface ReviewerGroupRepository extends JpaRepository { - - default ReviewerGroup getReviewerGroupById(long id) { - return findById(id).orElseThrow(ReviewerGroupNotFoundException::new); - } -} diff --git a/backend/src/main/java/reviewme/member/service/ReviewerGroupService.java b/backend/src/main/java/reviewme/member/service/ReviewerGroupService.java deleted file mode 100644 index 180c5f045..000000000 --- a/backend/src/main/java/reviewme/member/service/ReviewerGroupService.java +++ /dev/null @@ -1,43 +0,0 @@ -package reviewme.member.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import reviewme.member.domain.Member; -import reviewme.member.domain.ReviewerGroup; -import reviewme.member.dto.response.MemberResponse; -import reviewme.member.dto.response.ReviewCreationReviewerGroupResponse; -import reviewme.member.dto.response.ReviewerGroupResponse; -import reviewme.member.repository.ReviewerGroupRepository; - -@Service -@RequiredArgsConstructor -public class ReviewerGroupService { - - private final ReviewerGroupRepository reviewerGroupRepository; - - public ReviewerGroupResponse findReviewerGroup(long reviewerGroupId) { - ReviewerGroup reviewerGroup = reviewerGroupRepository.getReviewerGroupById(reviewerGroupId); - Member reviewee = reviewerGroup.getReviewee(); - return new ReviewerGroupResponse( - reviewerGroup.getId(), - reviewerGroup.getGroupName(), - reviewerGroup.getDeadline(), - new MemberResponse(reviewee.getId(), reviewee.getName()) - ); - } - - @Transactional(readOnly = true) - public ReviewCreationReviewerGroupResponse findReviewCreationReviewerGroup(long reviewerGroupId) { - ReviewerGroup reviewerGroup = reviewerGroupRepository.getReviewerGroupById(reviewerGroupId); - Member reviewee = reviewerGroup.getReviewee(); - return new ReviewCreationReviewerGroupResponse( - reviewerGroup.getId(), - reviewerGroup.getGroupName(), - reviewerGroup.getDescription(), - reviewerGroup.getDeadline(), - reviewerGroup.getThumbnailUrl(), - new MemberResponse(reviewee.getId(), reviewee.getName()) - ); - } -} diff --git a/backend/src/main/java/reviewme/review/domain/Question.java b/backend/src/main/java/reviewme/question/domain/Question.java similarity index 95% rename from backend/src/main/java/reviewme/review/domain/Question.java rename to backend/src/main/java/reviewme/question/domain/Question.java index 775457355..c38a9697e 100644 --- a/backend/src/main/java/reviewme/review/domain/Question.java +++ b/backend/src/main/java/reviewme/question/domain/Question.java @@ -1,4 +1,4 @@ -package reviewme.review.domain; +package reviewme.question.domain; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/backend/src/main/java/reviewme/question/domain/exception/DuplicateQuestionException.java b/backend/src/main/java/reviewme/question/domain/exception/DuplicateQuestionException.java new file mode 100644 index 000000000..aba667354 --- /dev/null +++ b/backend/src/main/java/reviewme/question/domain/exception/DuplicateQuestionException.java @@ -0,0 +1,10 @@ +package reviewme.question.domain.exception; + +import reviewme.global.exception.BadRequestException; + +public class DuplicateQuestionException extends BadRequestException { + + public DuplicateQuestionException() { + super("질문은 중복될 수 없어요."); + } +} diff --git a/backend/src/main/java/reviewme/review/exception/QuestionNotFoundException.java b/backend/src/main/java/reviewme/question/domain/exception/QuestionNotFoundException.java similarity index 64% rename from backend/src/main/java/reviewme/review/exception/QuestionNotFoundException.java rename to backend/src/main/java/reviewme/question/domain/exception/QuestionNotFoundException.java index a01ce2e16..42221d820 100644 --- a/backend/src/main/java/reviewme/review/exception/QuestionNotFoundException.java +++ b/backend/src/main/java/reviewme/question/domain/exception/QuestionNotFoundException.java @@ -1,10 +1,10 @@ -package reviewme.review.exception; +package reviewme.question.domain.exception; import reviewme.global.exception.NotFoundException; public class QuestionNotFoundException extends NotFoundException { public QuestionNotFoundException() { - super("질문이 존재하지 않습니다."); + super("질문이 존재하지 않아요."); } } diff --git a/backend/src/main/java/reviewme/review/controller/ReviewApi.java b/backend/src/main/java/reviewme/review/controller/ReviewApi.java index 78a35208d..ce8641b62 100644 --- a/backend/src/main/java/reviewme/review/controller/ReviewApi.java +++ b/backend/src/main/java/reviewme/review/controller/ReviewApi.java @@ -1,44 +1,44 @@ -package reviewme.review.controller; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; -import reviewme.review.dto.request.CreateReviewRequest; -import reviewme.review.dto.response.ReceivedReviewsResponse; -import reviewme.review.dto.response.ReviewCreationResponse; -import reviewme.review.dto.response.ReviewDetailResponse; - -@Tag(name = "리뷰 관리") -public interface ReviewApi { - - @Operation( - summary = "리뷰 등록", - description = "리뷰 작성 정보를 받아 리뷰를 등록한다.", - responses = @ApiResponse(responseCode = "201") - ) - ResponseEntity createReview(@RequestBody CreateReviewRequest request); - - @Operation( - summary = "리뷰 조회", - description = "단일 리뷰를 조회한다." - ) - ResponseEntity findReview(@PathVariable long id, @RequestParam long memberId); - - @Operation( - summary = "내가 받은 리뷰 조회", - description = "내가 받은 리뷰를 조회한다. (로그인을 구현하지 않은 지금 시점에서는 memberId로 로그인했다고 가정한다.)" - ) - ResponseEntity findMyReceivedReview(@RequestParam long memberId, - @RequestParam(required = false) Long lastReviewId, - @RequestParam(defaultValue = "10") int size); - - @Operation( - summary = "리뷰 작성 정보 조회", - description = "리뷰 생성 시 필요한 정보를 조회한다." - ) - ResponseEntity findReviewCreationSetup(@RequestParam long reviewerGroupId); -} +//package reviewme.review.controller; +// +//import io.swagger.v3.oas.annotations.Operation; +//import io.swagger.v3.oas.annotations.responses.ApiResponse; +//import io.swagger.v3.oas.annotations.tags.Tag; +//import org.springframework.http.ResponseEntity; +//import org.springframework.web.bind.annotation.PathVariable; +//import org.springframework.web.bind.annotation.RequestBody; +//import org.springframework.web.bind.annotation.RequestParam; +//import reviewme.review.dto.request.CreateReviewRequest; +//import reviewme.review.dto.response.ReceivedReviewsResponse; +//import reviewme.review.dto.response.ReviewCreationResponse; +//import reviewme.review.dto.response.ReviewDetailResponse; +// +//@Tag(name = "리뷰 관리") +//public interface ReviewApi { +// +// @Operation( +// summary = "리뷰 등록", +// description = "리뷰 작성 정보를 받아 리뷰를 등록한다.", +// responses = @ApiResponse(responseCode = "201") +// ) +// ResponseEntity createReview(@RequestBody CreateReviewRequest request); +// +// @Operation( +// summary = "리뷰 조회", +// description = "단일 리뷰를 조회한다." +// ) +// ResponseEntity findReview(@PathVariable long id, @RequestParam long memberId); +// +// @Operation( +// summary = "내가 받은 리뷰 조회", +// description = "내가 받은 리뷰를 조회한다. (로그인을 구현하지 않은 지금 시점에서는 memberId로 로그인했다고 가정한다.)" +// ) +// ResponseEntity findMyReceivedReview(@RequestParam long memberId, +// @RequestParam(required = false) Long lastReviewId, +// @RequestParam(defaultValue = "10") int size); +// +// @Operation( +// summary = "리뷰 작성 정보 조회", +// description = "리뷰 생성 시 필요한 정보를 조회한다." +// ) +// ResponseEntity findReviewCreationSetup(@RequestParam long reviewerGroupId); +//} diff --git a/backend/src/main/java/reviewme/review/controller/ReviewController.java b/backend/src/main/java/reviewme/review/controller/ReviewController.java deleted file mode 100644 index d1b7d0681..000000000 --- a/backend/src/main/java/reviewme/review/controller/ReviewController.java +++ /dev/null @@ -1,51 +0,0 @@ -package reviewme.review.controller; - -import jakarta.validation.Valid; -import java.net.URI; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import reviewme.review.dto.request.CreateReviewRequest; -import reviewme.review.dto.response.ReceivedReviewsResponse; -import reviewme.review.dto.response.ReviewCreationResponse; -import reviewme.review.dto.response.ReviewDetailResponse; -import reviewme.review.service.ReviewService; - -@RestController -@RequiredArgsConstructor -public class ReviewController implements ReviewApi { - - private final ReviewService reviewService; - - @PostMapping("/reviews") - public ResponseEntity createReview(@Valid @RequestBody CreateReviewRequest request) { - long id = reviewService.createReview(request); - return ResponseEntity.created(URI.create("/reviews/" + id)).build(); - } - - @GetMapping("/reviews/{id}") - public ResponseEntity findReview(@PathVariable long id, - @RequestParam long memberId) { - ReviewDetailResponse response = reviewService.findReview(id, memberId); - return ResponseEntity.ok(response); - } - - @GetMapping("/reviews/write") - public ResponseEntity findReviewCreationSetup(@RequestParam long reviewerGroupId) { - ReviewCreationResponse response = reviewService.findReviewCreationSetup(reviewerGroupId); - return ResponseEntity.ok(response); - } - - @GetMapping("/reviews") - public ResponseEntity findMyReceivedReview(@RequestParam long memberId, - @RequestParam(required = false) Long lastReviewId, - @RequestParam(defaultValue = "10") int size) { - ReceivedReviewsResponse myReceivedReview = reviewService.findMyReceivedReview(memberId, lastReviewId, size); - return ResponseEntity.ok(myReceivedReview); - } -} diff --git a/backend/src/main/java/reviewme/review/domain/Review.java b/backend/src/main/java/reviewme/review/domain/Review.java index 2eada01ee..ed6620eae 100644 --- a/backend/src/main/java/reviewme/review/domain/Review.java +++ b/backend/src/main/java/reviewme/review/domain/Review.java @@ -1,26 +1,19 @@ package reviewme.review.domain; import jakarta.persistence.Column; -import jakarta.persistence.Embedded; 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.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import java.time.LocalDateTime; -import java.util.ArrayList; import java.util.List; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import reviewme.keyword.domain.Keyword; -import reviewme.keyword.domain.Keywords; -import reviewme.member.domain.Member; -import reviewme.member.domain.ReviewerGroup; -import reviewme.review.domain.exception.IllegalReviewerException; @Entity @Table(name = "review") @@ -32,54 +25,19 @@ public class Review { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne - @JoinColumn(name = "reviewer_id", nullable = false) - private Member reviewer; + @Column(name = "review_group_id", nullable = false) + private long reviewGroupId; - @ManyToOne - @JoinColumn(name = "reviewee_id", nullable = false) - private Member reviewee; - - @ManyToOne - @JoinColumn(name = "reviewer_group_id", nullable = false) - private ReviewerGroup reviewerGroup; - - @OneToMany(mappedBy = "review") + @OneToMany(fetch = FetchType.EAGER) + @JoinColumn(name = "review_id", nullable = false) private List reviewContents; - @Embedded - private Keywords keywords; - @Column(name = "created_at", nullable = false) private LocalDateTime createdAt; - @Column(name = "is_public", nullable = false) - private boolean isPublic; - - public Review(Member reviewer, Member reviewee, ReviewerGroup reviewerGroup, - List keywords, LocalDateTime createdAt) { - if (reviewer.equals(reviewee)) { - throw new IllegalReviewerException(); - } - this.reviewer = reviewer; - this.reviewee = reviewee; - this.reviewerGroup = reviewerGroup; - this.reviewContents = new ArrayList<>(); - this.keywords = new Keywords(keywords); + public Review(long reviewGroupId, List reviewContents, LocalDateTime createdAt) { + this.reviewGroupId = reviewGroupId; + this.reviewContents = reviewContents; this.createdAt = createdAt; - reviewerGroup.addReview(this); - this.isPublic = false; - } - - public boolean isSubmittedBy(Member member) { - return reviewer.equals(member); - } - - public boolean isForReviewee(Member member) { - return reviewee.equals(member); - } - - public void addReviewContents(ReviewContent reviewContent) { - reviewContents.add(reviewContent); } } diff --git a/backend/src/main/java/reviewme/review/domain/ReviewContent.java b/backend/src/main/java/reviewme/review/domain/ReviewContent.java index 2aa4ef84f..3efa0a863 100644 --- a/backend/src/main/java/reviewme/review/domain/ReviewContent.java +++ b/backend/src/main/java/reviewme/review/domain/ReviewContent.java @@ -5,8 +5,6 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.Getter; @@ -21,29 +19,21 @@ public class ReviewContent { private static final int MIN_ANSWER_LENGTH = 20; private static final int MAX_ANSWER_LENGTH = 1_000; - private static final int REVIEW_CONTENT_PREVIEW_MAX_LENGTH = 150; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne - @JoinColumn(name = "review_id", nullable = false) - private Review review; - - @ManyToOne - @JoinColumn(name = "question_id", nullable = false) - private Question question; + @Column(name = "question_id", nullable = false) + private long questionId; @Column(name = "answer", nullable = false, length = MAX_ANSWER_LENGTH) private String answer; - public ReviewContent(Review review, Question question, String answer) { + public ReviewContent(long questionId, String answer) { validateAnswerLength(answer); - this.review = review; - this.question = question; + this.questionId = questionId; this.answer = answer; - review.addReviewContents(this); } private void validateAnswerLength(String answer) { @@ -51,15 +41,4 @@ private void validateAnswerLength(String answer) { throw new InvalidAnswerLengthException(MIN_ANSWER_LENGTH, MAX_ANSWER_LENGTH); } } - - public String getAnswerPreview() { - if (answer.length() <= REVIEW_CONTENT_PREVIEW_MAX_LENGTH) { - return answer; - } - return answer.substring(0, REVIEW_CONTENT_PREVIEW_MAX_LENGTH); - } - - public String getQuestion() { - return question.getContent(); - } } diff --git a/backend/src/main/java/reviewme/review/domain/ReviewKeyword.java b/backend/src/main/java/reviewme/review/domain/ReviewKeyword.java new file mode 100644 index 000000000..13159fe2e --- /dev/null +++ b/backend/src/main/java/reviewme/review/domain/ReviewKeyword.java @@ -0,0 +1,33 @@ +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 = "review_keyword") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class ReviewKeyword { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "review_id", nullable = false) + private long reviewId; + + @Column(name = "keyword_id", nullable = false) + private long keywordId; + + public ReviewKeyword(long reviewId, long keywordId) { + this.reviewId = reviewId; + this.keywordId = keywordId; + } +} diff --git a/backend/src/main/java/reviewme/review/domain/exception/DeadlineExpiredException.java b/backend/src/main/java/reviewme/review/domain/exception/DeadlineExpiredException.java deleted file mode 100644 index f2d05ae3f..000000000 --- a/backend/src/main/java/reviewme/review/domain/exception/DeadlineExpiredException.java +++ /dev/null @@ -1,10 +0,0 @@ -package reviewme.review.domain.exception; - -import reviewme.global.exception.BadRequestException; - -public class DeadlineExpiredException extends BadRequestException { - - public DeadlineExpiredException() { - super("리뷰 기간이 만료되었습니다."); - } -} diff --git a/backend/src/main/java/reviewme/review/domain/exception/IllegalReviewerException.java b/backend/src/main/java/reviewme/review/domain/exception/IllegalReviewerException.java deleted file mode 100644 index dfd8082bc..000000000 --- a/backend/src/main/java/reviewme/review/domain/exception/IllegalReviewerException.java +++ /dev/null @@ -1,10 +0,0 @@ -package reviewme.review.domain.exception; - -import reviewme.global.exception.BadRequestException; - -public class IllegalReviewerException extends BadRequestException { - - public IllegalReviewerException() { - super("리뷰어와 리뷰이가 같을 수 없습니다."); - } -} diff --git a/backend/src/main/java/reviewme/review/domain/exception/InvalidProjectNameLengthException.java b/backend/src/main/java/reviewme/review/domain/exception/InvalidProjectNameLengthException.java new file mode 100644 index 000000000..7e5e21b02 --- /dev/null +++ b/backend/src/main/java/reviewme/review/domain/exception/InvalidProjectNameLengthException.java @@ -0,0 +1,10 @@ +package reviewme.review.domain.exception; + +import reviewme.global.exception.BadRequestException; + +public class InvalidProjectNameLengthException extends BadRequestException { + + public InvalidProjectNameLengthException(int maxLength) { + super("프로젝트 이름은 1글자 이상 %d글자 이하여야 해요.".formatted(maxLength)); + } +} diff --git a/backend/src/main/java/reviewme/review/domain/exception/InvalidRevieweeNameLengthException.java b/backend/src/main/java/reviewme/review/domain/exception/InvalidRevieweeNameLengthException.java new file mode 100644 index 000000000..4454c5396 --- /dev/null +++ b/backend/src/main/java/reviewme/review/domain/exception/InvalidRevieweeNameLengthException.java @@ -0,0 +1,10 @@ +package reviewme.review.domain.exception; + +import reviewme.global.exception.BadRequestException; + +public class InvalidRevieweeNameLengthException extends BadRequestException { + + public InvalidRevieweeNameLengthException(int maxLength) { + super("리뷰이 이름은 1글자 이상 %d글자 이하여야 해요.".formatted(maxLength)); + } +} diff --git a/backend/src/main/java/reviewme/review/exception/ReviewNotFoundException.java b/backend/src/main/java/reviewme/review/domain/exception/ReviewNotFoundException.java similarity index 63% rename from backend/src/main/java/reviewme/review/exception/ReviewNotFoundException.java rename to backend/src/main/java/reviewme/review/domain/exception/ReviewNotFoundException.java index 31a81908c..44e72cb01 100644 --- a/backend/src/main/java/reviewme/review/exception/ReviewNotFoundException.java +++ b/backend/src/main/java/reviewme/review/domain/exception/ReviewNotFoundException.java @@ -1,10 +1,10 @@ -package reviewme.review.exception; +package reviewme.review.domain.exception; import reviewme.global.exception.NotFoundException; public class ReviewNotFoundException extends NotFoundException { public ReviewNotFoundException() { - super("리뷰가 존재하지 않습니다."); + super("리뷰가 존재하지 않아요."); } } diff --git a/backend/src/main/java/reviewme/review/domain/exception/RevieweeMismatchException.java b/backend/src/main/java/reviewme/review/domain/exception/RevieweeMismatchException.java deleted file mode 100644 index 0cc4ad7db..000000000 --- a/backend/src/main/java/reviewme/review/domain/exception/RevieweeMismatchException.java +++ /dev/null @@ -1,10 +0,0 @@ -package reviewme.review.domain.exception; - -import reviewme.global.exception.BadRequestException; - -public class RevieweeMismatchException extends BadRequestException { - - public RevieweeMismatchException() { - super("리뷰 대상이 일치하지 않습니다."); - } -} diff --git a/backend/src/main/java/reviewme/review/dto/request/CreateReviewRequest.java b/backend/src/main/java/reviewme/review/dto/request/CreateReviewRequest.java index dc838c899..662921749 100644 --- a/backend/src/main/java/reviewme/review/dto/request/CreateReviewRequest.java +++ b/backend/src/main/java/reviewme/review/dto/request/CreateReviewRequest.java @@ -1,28 +1,18 @@ package reviewme.review.dto.request; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.util.List; -@Schema(description = "리뷰 등록 요청") public record CreateReviewRequest( - @Schema(description = "리뷰어 ID") - @NotNull(message = "리뷰어 아이디를 입력해주세요.") - Long reviewerId, + @NotBlank + String reviewRequestCode, - @Schema(description = "리뷰어 그룹 ID") - @NotNull(message = "리뷰어 그룹 아이디를 입력해주세요.") - Long reviewerGroupId, - - @Schema(description = "리뷰 내용 목록") - @Valid - @NotNull(message = "리뷰 내용을 입력해주세요.") + @NotNull List reviewContents, - @Schema(description = "선택된 키워드 ID 목록") - @NotNull(message = "키워드를 입력해주세요.") + @NotNull List keywords ) { } diff --git a/backend/src/main/java/reviewme/review/dto/response/ReviewCreationResponse.java b/backend/src/main/java/reviewme/review/dto/response/ReviewCreationResponse.java deleted file mode 100644 index cba063dfd..000000000 --- a/backend/src/main/java/reviewme/review/dto/response/ReviewCreationResponse.java +++ /dev/null @@ -1,20 +0,0 @@ -package reviewme.review.dto.response; - -import io.swagger.v3.oas.annotations.media.Schema; -import java.util.List; -import reviewme.keyword.dto.response.KeywordResponse; -import reviewme.member.dto.response.ReviewCreationReviewerGroupResponse; - -@Schema(description = "리뷰 생성 시 필요한 정보 응답") -public record ReviewCreationResponse( - - @Schema(description = "리뷰어 그룹") - ReviewCreationReviewerGroupResponse reviewerGroup, - - @Schema(description = "리뷰 문항 목록") - List questions, - - @Schema(description = "키워드 목록") - List keywords -) { -} diff --git a/backend/src/main/java/reviewme/review/exception/GithubReviewerGroupUnAuthorizedException.java b/backend/src/main/java/reviewme/review/exception/GithubReviewerGroupUnAuthorizedException.java deleted file mode 100644 index 6e5ab8749..000000000 --- a/backend/src/main/java/reviewme/review/exception/GithubReviewerGroupUnAuthorizedException.java +++ /dev/null @@ -1,10 +0,0 @@ -package reviewme.review.exception; - -import reviewme.global.exception.UnAuthorizedException; - -public class GithubReviewerGroupUnAuthorizedException extends UnAuthorizedException { - - public GithubReviewerGroupUnAuthorizedException() { - super("리뷰어 그룹에 등록되지 않은 github 사용자입니다."); - } -} diff --git a/backend/src/main/java/reviewme/review/exception/ReviewAlreadySubmittedException.java b/backend/src/main/java/reviewme/review/exception/ReviewAlreadySubmittedException.java deleted file mode 100644 index f4ae46187..000000000 --- a/backend/src/main/java/reviewme/review/exception/ReviewAlreadySubmittedException.java +++ /dev/null @@ -1,10 +0,0 @@ -package reviewme.review.exception; - -import reviewme.global.exception.BadRequestException; - -public class ReviewAlreadySubmittedException extends BadRequestException { - - public ReviewAlreadySubmittedException() { - super("이미 리뷰를 작성한 경우 리뷰를 작성할 수 없습니다."); - } -} diff --git a/backend/src/main/java/reviewme/review/exception/ReviewUnAuthorizedException.java b/backend/src/main/java/reviewme/review/exception/ReviewUnAuthorizedException.java deleted file mode 100644 index 1721bd565..000000000 --- a/backend/src/main/java/reviewme/review/exception/ReviewUnAuthorizedException.java +++ /dev/null @@ -1,10 +0,0 @@ -package reviewme.review.exception; - -import reviewme.global.exception.UnAuthorizedException; - -public class ReviewUnAuthorizedException extends UnAuthorizedException { - - public ReviewUnAuthorizedException() { - super("리뷰에 대한 권한이 없습니다."); - } -} diff --git a/backend/src/main/java/reviewme/review/repository/QuestionRepository.java b/backend/src/main/java/reviewme/review/repository/QuestionRepository.java index 27a8c18ad..47346900e 100644 --- a/backend/src/main/java/reviewme/review/repository/QuestionRepository.java +++ b/backend/src/main/java/reviewme/review/repository/QuestionRepository.java @@ -2,8 +2,8 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import reviewme.review.domain.Question; -import reviewme.review.exception.QuestionNotFoundException; +import reviewme.question.domain.Question; +import reviewme.question.domain.exception.QuestionNotFoundException; @Repository public interface QuestionRepository extends JpaRepository { diff --git a/backend/src/main/java/reviewme/review/repository/ReviewContentRepository.java b/backend/src/main/java/reviewme/review/repository/ReviewContentRepository.java index 075a73475..5b475f65d 100644 --- a/backend/src/main/java/reviewme/review/repository/ReviewContentRepository.java +++ b/backend/src/main/java/reviewme/review/repository/ReviewContentRepository.java @@ -1,12 +1,9 @@ package reviewme.review.repository; -import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import reviewme.review.domain.ReviewContent; @Repository public interface ReviewContentRepository extends JpaRepository { - - List findAllByReviewId(long reviewId); } diff --git a/backend/src/main/java/reviewme/review/repository/ReviewKeywordRepository.java b/backend/src/main/java/reviewme/review/repository/ReviewKeywordRepository.java new file mode 100644 index 000000000..3a6be964a --- /dev/null +++ b/backend/src/main/java/reviewme/review/repository/ReviewKeywordRepository.java @@ -0,0 +1,12 @@ +package reviewme.review.repository; + +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import reviewme.review.domain.ReviewKeyword; + +@Repository +public interface ReviewKeywordRepository extends JpaRepository { + + List findAllByReviewId(long reviewId); +} diff --git a/backend/src/main/java/reviewme/review/repository/ReviewRepository.java b/backend/src/main/java/reviewme/review/repository/ReviewRepository.java index 34be4e8f9..528708cde 100644 --- a/backend/src/main/java/reviewme/review/repository/ReviewRepository.java +++ b/backend/src/main/java/reviewme/review/repository/ReviewRepository.java @@ -1,31 +1,14 @@ package reviewme.review.repository; -import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; -import reviewme.member.domain.Member; -import reviewme.member.domain.ReviewerGroup; import reviewme.review.domain.Review; -import reviewme.review.exception.ReviewNotFoundException; +import reviewme.review.domain.exception.ReviewNotFoundException; @Repository public interface ReviewRepository extends JpaRepository { - boolean existsByReviewerAndReviewerGroup(Member reviewer, ReviewerGroup reviewerGroup); - default Review getReviewById(Long id) { return findById(id).orElseThrow(ReviewNotFoundException::new); } - - @Query(""" - SELECT r - FROM Review r - WHERE r.reviewee.id = :revieweeId - AND :lastViewedReviewId IS NULL OR r.id < :lastViewedReviewId - ORDER BY r.createdAt DESC - LIMIT :size - """ - ) - List findLimitedReviewsWrittenForReviewee(long revieweeId, Long lastViewedReviewId, int size); } diff --git a/backend/src/main/java/reviewme/review/service/QuestionService.java b/backend/src/main/java/reviewme/review/service/QuestionService.java deleted file mode 100644 index 41d482ee7..000000000 --- a/backend/src/main/java/reviewme/review/service/QuestionService.java +++ /dev/null @@ -1,23 +0,0 @@ -package reviewme.review.service; - -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import reviewme.review.dto.response.QuestionResponse; -import reviewme.review.repository.QuestionRepository; - -@Service -@RequiredArgsConstructor -public class QuestionService { - - private final QuestionRepository questionRepository; - - @Transactional(readOnly = true) - public List findAllQuestions() { - return questionRepository.findAll() - .stream() - .map(question -> new QuestionResponse(question.getId(), question.getContent())) - .toList(); - } -} diff --git a/backend/src/main/java/reviewme/review/service/ReviewCreationKeywordValidator.java b/backend/src/main/java/reviewme/review/service/ReviewCreationKeywordValidator.java new file mode 100644 index 000000000..4cf96396b --- /dev/null +++ b/backend/src/main/java/reviewme/review/service/ReviewCreationKeywordValidator.java @@ -0,0 +1,49 @@ +package reviewme.review.service; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import reviewme.keyword.domain.exception.DuplicateKeywordException; +import reviewme.keyword.domain.exception.KeywordLimitExceedException; +import reviewme.keyword.domain.exception.KeywordNotFoundException; +import reviewme.keyword.repository.KeywordRepository; + +@Component +@RequiredArgsConstructor +public class ReviewCreationKeywordValidator { + + private static final int MIN_KEYWORD_COUNT = 1; + private static final int MAX_KEYWORD_COUNT = 5; + + private final KeywordRepository keywordRepository; + + void validate(List selectedKeywordIds) { + validateKeywordCount(selectedKeywordIds.size()); + validateUniqueKeyword(selectedKeywordIds); + validateExistsKeyword(selectedKeywordIds); + } + + private void validateUniqueKeyword(List selectedKeywordIds) { + int keywordsCount = selectedKeywordIds.size(); + long distinctCount = selectedKeywordIds.stream() + .distinct() + .count(); + if (keywordsCount != distinctCount) { + throw new DuplicateKeywordException(); + } + } + + private void validateExistsKeyword(List selectedKeywordIds) { + boolean doesKeywordExist = selectedKeywordIds.stream() + .anyMatch(keywordRepository::existsById); + if (!doesKeywordExist) { + throw new KeywordNotFoundException(); + } + } + + private void validateKeywordCount(int keywordsCount) { + if (keywordsCount < MIN_KEYWORD_COUNT || keywordsCount > MAX_KEYWORD_COUNT) { + throw new KeywordLimitExceedException(MIN_KEYWORD_COUNT, MAX_KEYWORD_COUNT); + } + } +} diff --git a/backend/src/main/java/reviewme/review/service/ReviewCreationQuestionValidator.java b/backend/src/main/java/reviewme/review/service/ReviewCreationQuestionValidator.java new file mode 100644 index 000000000..f0e904548 --- /dev/null +++ b/backend/src/main/java/reviewme/review/service/ReviewCreationQuestionValidator.java @@ -0,0 +1,38 @@ +package reviewme.review.service; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import reviewme.question.domain.exception.DuplicateQuestionException; +import reviewme.question.domain.exception.QuestionNotFoundException; +import reviewme.review.repository.QuestionRepository; + +@Component +@RequiredArgsConstructor +public class ReviewCreationQuestionValidator { + + private final QuestionRepository questionRepository; + + void validate(List questionIds) { + validateUniqueQuestion(questionIds); + validateExistsQuestion(questionIds); + } + + private void validateUniqueQuestion(List questionIds) { + int questionsCount = questionIds.size(); + long distinctCount = questionIds.stream() + .distinct() + .count(); + if (questionsCount != distinctCount) { + throw new DuplicateQuestionException(); + } + } + + private void validateExistsQuestion(List questionIds) { + boolean doesQuestionExist = questionIds.stream() + .anyMatch(questionRepository::existsById); + if (!doesQuestionExist) { + throw new QuestionNotFoundException(); + } + } +} diff --git a/backend/src/main/java/reviewme/review/service/ReviewService.java b/backend/src/main/java/reviewme/review/service/ReviewService.java index 0d83485db..29fd43a1b 100644 --- a/backend/src/main/java/reviewme/review/service/ReviewService.java +++ b/backend/src/main/java/reviewme/review/service/ReviewService.java @@ -5,168 +5,62 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import reviewme.keyword.domain.Keyword; -import reviewme.keyword.domain.Keywords; -import reviewme.keyword.dto.response.KeywordResponse; -import reviewme.keyword.repository.KeywordRepository; -import reviewme.keyword.service.KeywordService; -import reviewme.member.domain.Member; -import reviewme.member.domain.ReviewerGroup; -import reviewme.member.dto.response.ReviewCreationReviewerGroupResponse; -import reviewme.member.repository.MemberRepository; -import reviewme.member.repository.ReviewerGroupRepository; -import reviewme.member.service.ReviewerGroupService; -import reviewme.review.domain.Question; import reviewme.review.domain.Review; import reviewme.review.domain.ReviewContent; +import reviewme.review.domain.ReviewKeyword; +import reviewme.review.dto.request.CreateReviewContentRequest; import reviewme.review.dto.request.CreateReviewRequest; -import reviewme.review.dto.response.QuestionResponse; -import reviewme.review.dto.response.ReceivedReviewKeywordsResponse; -import reviewme.review.dto.response.ReceivedReviewResponse; -import reviewme.review.dto.response.ReceivedReviewReviewerGroupResponse; -import reviewme.review.dto.response.ReceivedReviewsResponse; -import reviewme.review.dto.response.ReviewCreationResponse; -import reviewme.review.dto.response.ReviewDetailResponse; -import reviewme.review.dto.response.ReviewDetailReviewContentResponse; -import reviewme.review.dto.response.ReviewDetailReviewerGroupResponse; -import reviewme.review.exception.ReviewUnAuthorizedException; -import reviewme.review.repository.QuestionRepository; import reviewme.review.repository.ReviewContentRepository; +import reviewme.review.repository.ReviewKeywordRepository; import reviewme.review.repository.ReviewRepository; +import reviewme.reviewgroup.domain.ReviewGroup; +import reviewme.reviewgroup.domain.exception.InvalidReviewRequestCodeException; +import reviewme.reviewgroup.repository.ReviewGroupRepository; @Service @RequiredArgsConstructor public class ReviewService { - private final ReviewerGroupService reviewerGroupService; - private final KeywordService keywordService; - private final QuestionService questionService; private final ReviewRepository reviewRepository; - private final MemberRepository memberRepository; - private final ReviewerGroupRepository reviewerGroupRepository; + private final ReviewKeywordRepository reviewKeywordRepository; private final ReviewContentRepository reviewContentRepository; - private final QuestionRepository questionRepository; - private final KeywordRepository keywordRepository; + private final ReviewGroupRepository reviewGroupRepository; + private final ReviewCreationQuestionValidator reviewCreationQuestionValidator; + private final ReviewCreationKeywordValidator reviewCreationKeywordValidator; @Transactional public Long createReview(CreateReviewRequest request) { - ReviewerGroup reviewerGroup = reviewerGroupRepository.getReviewerGroupById(request.reviewerGroupId()); - Member reviewer = memberRepository.getMemberById(request.reviewerId()); - - List keywordList = request.keywords() - .stream() - .map(keywordRepository::getKeywordById) - .toList(); - - Review review = new Review(reviewer, reviewerGroup.getReviewee(), - reviewerGroup, keywordList, LocalDateTime.now()); - Review savedReview = reviewRepository.save(review); - - request.reviewContents() - .forEach(contentsRequest -> { - Question question = questionRepository.getQuestionById(contentsRequest.questionId()); - String answer = contentsRequest.answer(); - - ReviewContent reviewContent = new ReviewContent(savedReview, question, answer); - reviewContentRepository.save(reviewContent); - }); - + Review savedReview = saveReview(request); + saveReviewKeywords(request.keywords(), savedReview.getId()); return savedReview.getId(); } - @Transactional(readOnly = true) - public ReviewDetailResponse findReview(long reviewId, long memberId) { - Review review = reviewRepository.getReviewById(reviewId); - Member member = memberRepository.getMemberById(memberId); - if (!review.isForReviewee(member)) { - throw new ReviewUnAuthorizedException(); - } - - ReviewerGroup reviewerGroup = review.getReviewerGroup(); - Keywords keywords = review.getKeywords(); - List reviewContents = reviewContentRepository.findAllByReviewId(reviewId); + private Review saveReview(CreateReviewRequest request) { + ReviewGroup reviewGroup = reviewGroupRepository.findByReviewRequestCode(request.reviewRequestCode()) + .orElseThrow(InvalidReviewRequestCodeException::new); - ReviewDetailReviewerGroupResponse reviewerGroupResponse = new ReviewDetailReviewerGroupResponse( - reviewerGroup.getId(), - reviewerGroup.getGroupName(), - reviewerGroup.getDescription(), - reviewerGroup.getThumbnailUrl() - ); - List reviewContentResponses = reviewContents.stream() - .map(content -> new ReviewDetailReviewContentResponse( - content.getQuestion(), - content.getAnswer() - )) - .toList(); - List keywordContents = keywords.getKeywordIds() + List questionIds = request.reviewContents() .stream() - .map(keywordRepository::getKeywordById) - .map(Keyword::getContent) + .map(CreateReviewContentRequest::questionId) .toList(); + reviewCreationQuestionValidator.validate(questionIds); - return new ReviewDetailResponse( - reviewId, - review.getCreatedAt().toLocalDate(), - review.isPublic(), - reviewerGroupResponse, - reviewContentResponses, - keywordContents - ); - } - - @Transactional(readOnly = true) - public ReviewCreationResponse findReviewCreationSetup(long reviewerGroupId) { - ReviewCreationReviewerGroupResponse reviewerGroup = reviewerGroupService.findReviewCreationReviewerGroup( - reviewerGroupId); - List questions = questionService.findAllQuestions(); - List keywords = keywordService.findAllKeywords(); - return new ReviewCreationResponse(reviewerGroup, questions, keywords); - } - - @Transactional(readOnly = true) - public ReceivedReviewsResponse findMyReceivedReview(long memberId, Long lastReviewId, int size) { - List reviews = reviewRepository.findLimitedReviewsWrittenForReviewee(memberId, lastReviewId, size); - - if (reviews.isEmpty()) { - return new ReceivedReviewsResponse(0, 0, List.of()); - } - - return new ReceivedReviewsResponse( - reviews.size(), - reviews.get(reviews.size() - 1).getId(), - reviews.stream() - .map(this::createReceivedReviewResponse) - .toList()); - } - - private ReceivedReviewResponse createReceivedReviewResponse(Review review) { - return new ReceivedReviewResponse( - review.getId(), - review.isPublic(), - review.getCreatedAt().toLocalDate(), - createReviewContentPreview(review), - new ReceivedReviewReviewerGroupResponse( - review.getReviewerGroup().getId(), - review.getReviewerGroup().getGroupName(), - review.getReviewerGroup().getThumbnailUrl() - ), - createKeywordResponse(review)); - } + List reviewContents = request.reviewContents() + .stream() + .map(r -> new ReviewContent(r.questionId(), r.answer())) + .toList(); + Review review = new Review(reviewGroup.getId(), reviewContents, LocalDateTime.now()); - private String createReviewContentPreview(Review review) { - return reviewContentRepository.findAllByReviewId(review.getId()) - .get(0) - .getAnswerPreview(); + Review savedReview = reviewRepository.save(review); + reviewContentRepository.saveAll(reviewContents); + return savedReview; } - private List createKeywordResponse(Review review) { - return review.getKeywords().getKeywordIds() - .stream() - .map(keywordRepository::getKeywordById) - .map(keyword -> new ReceivedReviewKeywordsResponse( - keyword.getId(), - keyword.getContent() - )) + private void saveReviewKeywords(List selectedKeywordIds, long savedReviewId) { + reviewCreationKeywordValidator.validate(selectedKeywordIds); + List reviewKeywords = selectedKeywordIds.stream() + .map(keyword -> new ReviewKeyword(savedReviewId, keyword)) .toList(); + reviewKeywordRepository.saveAll(reviewKeywords); } } diff --git a/backend/src/main/java/reviewme/reviewgroup/domain/ReviewGroup.java b/backend/src/main/java/reviewme/reviewgroup/domain/ReviewGroup.java new file mode 100644 index 000000000..2170fc8f4 --- /dev/null +++ b/backend/src/main/java/reviewme/reviewgroup/domain/ReviewGroup.java @@ -0,0 +1,60 @@ +package reviewme.reviewgroup.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; +import reviewme.review.domain.exception.InvalidProjectNameLengthException; +import reviewme.review.domain.exception.InvalidRevieweeNameLengthException; + +@Entity +@Table(name = "review_group") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class ReviewGroup { + + private static final int MAX_REVIEWEE_LENGTH = 50; + private static final int MAX_PROJECT_NAME_LENGTH = 50; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "reviewee", nullable = false) + private String reviewee; + + @Column(name = "project_name", nullable = false) + private String projectName; + + @Column(name = "review_request_code", nullable = false) + private String reviewRequestCode; + + @Column(name = "group_access_code", nullable = false) + private String groupAccessCode; + + public ReviewGroup(String reviewee, String projectName, String reviewRequestCode, String groupAccessCode) { + validateRevieweeLength(reviewee); + validateProjectNameLength(projectName); + this.reviewee = reviewee; + this.projectName = projectName; + this.reviewRequestCode = reviewRequestCode; + this.groupAccessCode = groupAccessCode; + } + + private void validateRevieweeLength(String reviewee) { + if (reviewee.isBlank() || reviewee.length() > MAX_REVIEWEE_LENGTH) { + throw new InvalidRevieweeNameLengthException(MAX_REVIEWEE_LENGTH); + } + } + + private void validateProjectNameLength(String projectName) { + if (projectName.isBlank() || projectName.length() > MAX_PROJECT_NAME_LENGTH) { + throw new InvalidProjectNameLengthException(MAX_PROJECT_NAME_LENGTH); + } + } +} diff --git a/backend/src/main/java/reviewme/reviewgroup/domain/exception/InvalidReviewRequestCodeException.java b/backend/src/main/java/reviewme/reviewgroup/domain/exception/InvalidReviewRequestCodeException.java new file mode 100644 index 000000000..da2e393e1 --- /dev/null +++ b/backend/src/main/java/reviewme/reviewgroup/domain/exception/InvalidReviewRequestCodeException.java @@ -0,0 +1,10 @@ +package reviewme.reviewgroup.domain.exception; + +import reviewme.global.exception.BadRequestException; + +public class InvalidReviewRequestCodeException extends BadRequestException { + + public InvalidReviewRequestCodeException() { + super("잘못된 리뷰 요청코드입니다."); + } +} diff --git a/backend/src/main/java/reviewme/reviewgroup/repository/ReviewGroupRepository.java b/backend/src/main/java/reviewme/reviewgroup/repository/ReviewGroupRepository.java new file mode 100644 index 000000000..f683de845 --- /dev/null +++ b/backend/src/main/java/reviewme/reviewgroup/repository/ReviewGroupRepository.java @@ -0,0 +1,12 @@ +package reviewme.reviewgroup.repository; + +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import reviewme.reviewgroup.domain.ReviewGroup; + +@Repository +public interface ReviewGroupRepository extends JpaRepository { + + Optional findByReviewRequestCode(String reviewRequestCode); +} diff --git a/backend/src/test/java/reviewme/fixture/MemberFixture.java b/backend/src/test/java/reviewme/fixture/MemberFixture.java deleted file mode 100644 index b278ee8ce..000000000 --- a/backend/src/test/java/reviewme/fixture/MemberFixture.java +++ /dev/null @@ -1,23 +0,0 @@ -package reviewme.fixture; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import reviewme.member.domain.Member; - -@RequiredArgsConstructor -@Getter -public enum MemberFixture { - - 회원_산초("산초", 1L), - 회원_아루("아루", 2L), - 회원_커비("커비", 3L), - 회원_테드("테드", 4L), - ; - - private final String name; - private final long githubId; - - public Member create() { - return new Member(name, githubId); - } -} diff --git a/backend/src/test/java/reviewme/fixture/QuestionFixure.java b/backend/src/test/java/reviewme/fixture/QuestionFixure.java index cfcba7b27..23c6612d0 100644 --- a/backend/src/test/java/reviewme/fixture/QuestionFixure.java +++ b/backend/src/test/java/reviewme/fixture/QuestionFixure.java @@ -1,6 +1,6 @@ package reviewme.fixture; -import reviewme.review.domain.Question; +import reviewme.question.domain.Question; public enum QuestionFixure { diff --git a/backend/src/test/java/reviewme/fixture/ReviewerGroupFixture.java b/backend/src/test/java/reviewme/fixture/ReviewerGroupFixture.java deleted file mode 100644 index 08b35c72c..000000000 --- a/backend/src/test/java/reviewme/fixture/ReviewerGroupFixture.java +++ /dev/null @@ -1,27 +0,0 @@ -package reviewme.fixture; - -import java.time.LocalDateTime; -import java.util.List; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import reviewme.member.domain.GithubId; -import reviewme.member.domain.Member; -import reviewme.member.domain.ReviewerGroup; - -@RequiredArgsConstructor -@Getter -public enum ReviewerGroupFixture { - - 리뷰_그룹("리뷰 그룹", "그룹 설명", LocalDateTime.of(2024, 1, 1, 12, 0)), - 데드라인_남은_그룹("데드라인 이전 그룹", "설명", LocalDateTime.now().plusDays(1)), - 데드라인_지난_그룹("데드라인 지난 그룹", "설명", LocalDateTime.now().minusDays(1)), - ; - - private final String groupName; - private final String description; - private final LocalDateTime deadline; - - public ReviewerGroup create(Member reviewee, List reviewerGithubIds) { - return new ReviewerGroup(reviewee, reviewerGithubIds, groupName, description, deadline); - } -} diff --git a/backend/src/test/java/reviewme/keyword/domain/KeywordsTest.java b/backend/src/test/java/reviewme/keyword/domain/KeywordsTest.java deleted file mode 100644 index 0c5a63403..000000000 --- a/backend/src/test/java/reviewme/keyword/domain/KeywordsTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package reviewme.keyword.domain; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static reviewme.fixture.KeywordFixture.꼼꼼하게_기록해요; - -import java.util.List; -import java.util.stream.Stream; -import org.junit.jupiter.api.Test; -import reviewme.keyword.domain.exception.DuplicateKeywordException; -import reviewme.keyword.domain.exception.KeywordLimitExceedException; - -class KeywordsTest { - - @Test - void 키워드는_최대_5개만_선택할_수_있다() { - // given - List keywords = Stream.of("1", "2", "3", "4", "5") - .map(Keyword::new) - .toList(); - - // when, then - assertDoesNotThrow(() -> new Keywords(keywords)); - } - - @Test - void 키워드는_5개를_초과해서_선택할_수_없다() { - // given - List keywords = Stream.of("1", "2", "3", "4", "5", "6") - .map(Keyword::new) - .toList(); - - // when, then - assertThatThrownBy(() -> new Keywords(keywords)) - .isInstanceOf(KeywordLimitExceedException.class); - } - - @Test - void 키워드는_중복으로_선택할_수_없다() { - // given - List keywords = List.of( - 꼼꼼하게_기록해요.create(), - 꼼꼼하게_기록해요.create() - ); - - // when, then - assertThatThrownBy(() -> new Keywords(keywords)) - .isInstanceOf(DuplicateKeywordException.class); - } -} diff --git a/backend/src/test/java/reviewme/keyword/service/KeywordServiceTest.java b/backend/src/test/java/reviewme/keyword/service/KeywordServiceTest.java deleted file mode 100644 index 55f1e9417..000000000 --- a/backend/src/test/java/reviewme/keyword/service/KeywordServiceTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package reviewme.keyword.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.fixture.KeywordFixture; -import reviewme.keyword.dto.response.KeywordResponse; -import reviewme.keyword.repository.KeywordRepository; -import reviewme.support.ServiceTest; - -@ServiceTest -class KeywordServiceTest { - - @Autowired - KeywordService keywordService; - - @Autowired - KeywordRepository keywordRepository; - - @Test - void 모든_키워드를_조회한다() { - // given - keywordRepository.save(KeywordFixture.회의를_이끌어요.create()); - - // when - List keywords = keywordService.findAllKeywords(); - - // then - assertThat(keywords).hasSize(1); - } -} diff --git a/backend/src/test/java/reviewme/member/domain/ReviewerGroupTest.java b/backend/src/test/java/reviewme/member/domain/ReviewerGroupTest.java deleted file mode 100644 index e26e865b0..000000000 --- a/backend/src/test/java/reviewme/member/domain/ReviewerGroupTest.java +++ /dev/null @@ -1,122 +0,0 @@ -package reviewme.member.domain; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static reviewme.fixture.MemberFixture.회원_산초; -import static reviewme.fixture.MemberFixture.회원_커비; - -import java.time.LocalDateTime; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import reviewme.member.domain.exception.EmptyReviewerException; -import reviewme.member.domain.exception.InvalidDescriptionLengthException; -import reviewme.member.domain.exception.InvalidGroupNameLengthException; -import reviewme.member.domain.exception.SelfReviewException; - -class ReviewerGroupTest { - - @Test - void 리뷰_그룹이_올바르게_생성된다() { - // given - Member reviewee = 회원_산초.create(); - String groupName = "a".repeat(100); - String description = "a".repeat(50); - LocalDateTime deadline = LocalDateTime.now().plusDays(1); - List reviewerGithubIds = List.of(new GithubId(3)); - - // when, then - assertDoesNotThrow( - () -> new ReviewerGroup(reviewee, reviewerGithubIds, groupName, description, deadline)); - } - - @ParameterizedTest - @ValueSource(ints = {0, 101}) - void 리뷰_그룹_이름_길이_제한을_벗어날_수_없다(int length) { - // given - String groupName = "a".repeat(length); - Member reviewee = 회원_산초.create(); - Member reviewer = 회원_커비.create(); - LocalDateTime deadline = LocalDateTime.now().plusDays(1); - List reviewerGithubIds = List.of(reviewer.getGithubId()); - - // when, then - assertThatThrownBy(() -> new ReviewerGroup(reviewee, reviewerGithubIds, groupName, "설명", deadline)) - .isInstanceOf(InvalidGroupNameLengthException.class); - } - - @Test - void 리뷰_그룹_설명_길이_제한을_벗어날_수_없다() { - // given - String description = "a".repeat(51); - Member reviewee = 회원_산초.create(); - Member reviewer = 회원_커비.create(); - LocalDateTime deadline = LocalDateTime.now().plusDays(1); - List reviewerGithubIds = List.of(reviewer.getGithubId()); - - // when, then - assertThatThrownBy(() -> new ReviewerGroup(reviewee, reviewerGithubIds, "그룹 이름", description, deadline)) - .isInstanceOf(InvalidDescriptionLengthException.class); - } - - @Test - void 리뷰어_목록에_리뷰이가_들어갈_수_없다() { - // given - Member reviewee = 회원_산초.create(); - String groupName = "Group"; - String description = "Description"; - LocalDateTime deadline = LocalDateTime.now().plusDays(1); - List reviewerGithubIds = List.of(reviewee.getGithubId()); - - // when, then - assertThatThrownBy(() -> new ReviewerGroup(reviewee, reviewerGithubIds, groupName, description, deadline)) - .isInstanceOf(SelfReviewException.class); - } - - @Test - void 리뷰어_목록이_비어있을_수_없다() { - Member reviewee = 회원_산초.create(); - String groupName = "Group"; - String description = "Description"; - LocalDateTime deadline = LocalDateTime.now().plusDays(1); - List reviewerGithubIds = List.of(); - - // when, then - assertThatThrownBy(() -> new ReviewerGroup(reviewee, reviewerGithubIds, groupName, description, deadline)) - .isInstanceOf(EmptyReviewerException.class); - } - -// @Test -// void 리뷰어를_중복으로_가지게_그룹을_생성할_수_없다() { -// // given -// Member reviewer = 회원_산초.create(); -// Member reviewee = 회원_커비.create(); -// String groupName = "Group"; -// String description = "Description"; -// LocalDateTime deadline = LocalDateTime.now().plusDays(1); -// List reviewerGithubIds = List.of(reviewer.getGithubId(), reviewer.getGithubId()); -// -// // when, then -// assertThatThrownBy(() -> new ReviewerGroup(reviewee, reviewerGithubIds, groupName, description, deadline)) -// .isInstanceOf(DuplicateReviewerException.class); -// } - -// @Test -// void 리뷰어를_중복으로_추가할_수_없다() { -// // given -// Member reviewer = 회원_커비.create(); -// Member reviewee = 회원_산초.create(); -// -// String groupName = "Group"; -// String description = "Description"; -// LocalDateTime deadline = LocalDateTime.now().plusDays(1); -// List reviewerGithubIds = List.of(reviewer.getGithubId()); -// ReviewerGroup reviewerGroup = new ReviewerGroup(reviewee, reviewerGithubIds, groupName, description, deadline); -// GithubIdReviewerGroup githubIdReviewerGroup = new GithubIdReviewerGroup(reviewee.getGithubId(), reviewerGroup); -// -// // when, then -// assertThatThrownBy(() -> reviewerGroup.addReviewerGithubId(githubIdReviewerGroup)) -// .isInstanceOf(DuplicateReviewerException.class); -// } -} diff --git a/backend/src/test/java/reviewme/member/service/ReviewerGroupServiceTest.java b/backend/src/test/java/reviewme/member/service/ReviewerGroupServiceTest.java deleted file mode 100644 index 4b3dfbe8b..000000000 --- a/backend/src/test/java/reviewme/member/service/ReviewerGroupServiceTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package reviewme.member.service; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static reviewme.fixture.ReviewerGroupFixture.리뷰_그룹; - -import java.util.List; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import reviewme.member.domain.GithubId; -import reviewme.member.domain.Member; -import reviewme.member.domain.ReviewerGroup; -import reviewme.member.dto.response.ReviewCreationReviewerGroupResponse; -import reviewme.member.repository.MemberRepository; -import reviewme.member.repository.ReviewerGroupRepository; -import reviewme.support.ServiceTest; - -@ServiceTest -class ReviewerGroupServiceTest { - - @Autowired - ReviewerGroupService reviewerGroupService; - - @Autowired - ReviewerGroupRepository reviewerGroupRepository; - - @Autowired - MemberRepository memberRepository; - - @Test - void 리뷰_생성_시_필요한_리뷰어_그룹_정보를_조회한다() { - // given - Member reviewee = memberRepository.save(new Member("산초", 1)); - List reviewergithubIds = List.of(new GithubId(2), new GithubId(3)); - ReviewerGroup reviewerGroup = reviewerGroupRepository.save(리뷰_그룹.create(reviewee, reviewergithubIds)); - - // when - ReviewCreationReviewerGroupResponse actual = reviewerGroupService.findReviewCreationReviewerGroup( - reviewerGroup.getId()); - - // then - assertAll( - () -> assertThat(actual.id()).isEqualTo(reviewerGroup.getId()), - () -> assertThat(actual.reviewee().id()).isEqualTo(reviewee.getId()) - ); - } -} diff --git a/backend/src/test/java/reviewme/review/domain/ReviewContentTest.java b/backend/src/test/java/reviewme/review/domain/ReviewContentTest.java new file mode 100644 index 000000000..6dde3d516 --- /dev/null +++ b/backend/src/test/java/reviewme/review/domain/ReviewContentTest.java @@ -0,0 +1,45 @@ +package reviewme.review.domain; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + +import org.junit.jupiter.api.Test; +import reviewme.global.exception.BadRequestException; + +class ReviewContentTest { + + + @Test + void 정상_생성된다() { + // given + int minLength = 20; + int maxLength = 1000; + String minLengthAnswer = "*".repeat(minLength); + String maxLengthAnswer = "*".repeat(maxLength); + + // when, then + assertAll( + () -> assertThatCode(() -> new ReviewContent(1L, minLengthAnswer)) + .doesNotThrowAnyException(), + () -> assertThatCode(() -> new ReviewContent(1L, maxLengthAnswer)) + .doesNotThrowAnyException() + ); + } + + @Test + void 답변이_정해진_길이에_맞지_않으면_예외가_발생한다() { + // given + int minLength = 20; + int maxLength = 1000; + String insufficientLengthAnswer = "*".repeat(minLength - 1); + String exceedLengthAnswer = "*".repeat(maxLength + 1); + + // when, then + assertAll( + () -> assertThatCode(() -> new ReviewContent(1L, insufficientLengthAnswer)) + .isInstanceOf(BadRequestException.class), + () -> assertThatCode(() -> new ReviewContent(1L, exceedLengthAnswer)) + .isInstanceOf(BadRequestException.class) + ); + } +} diff --git a/backend/src/test/java/reviewme/review/domain/ReviewTest.java b/backend/src/test/java/reviewme/review/domain/ReviewTest.java deleted file mode 100644 index 22d356655..000000000 --- a/backend/src/test/java/reviewme/review/domain/ReviewTest.java +++ /dev/null @@ -1,96 +0,0 @@ -package reviewme.review.domain; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static reviewme.fixture.KeywordFixture.꼼꼼하게_기록해요; -import static reviewme.fixture.KeywordFixture.의견을_잘_조율해요; -import static reviewme.fixture.MemberFixture.회원_산초; -import static reviewme.fixture.MemberFixture.회원_아루; -import static reviewme.fixture.MemberFixture.회원_커비; -import static reviewme.fixture.ReviewerGroupFixture.데드라인_남은_그룹; -import static reviewme.fixture.ReviewerGroupFixture.데드라인_지난_그룹; - -import java.time.LocalDateTime; -import java.util.List; -import org.junit.jupiter.api.Test; -import reviewme.keyword.domain.Keyword; -import reviewme.member.domain.GithubId; -import reviewme.member.domain.Member; -import reviewme.member.domain.ReviewerGroup; -import reviewme.review.domain.exception.DeadlineExpiredException; -import reviewme.review.domain.exception.IllegalReviewerException; -import reviewme.review.domain.exception.RevieweeMismatchException; -import reviewme.review.exception.GithubReviewerGroupUnAuthorizedException; -import reviewme.review.exception.ReviewAlreadySubmittedException; - -class ReviewTest { - - @Test - void 리뷰어와_리뷰이가_같을_수_없다() { - // given - Member member = 회원_산초.create(); - LocalDateTime createdAt = LocalDateTime.now(); - List keywords = List.of(꼼꼼하게_기록해요.create(), 의견을_잘_조율해요.create()); - - // when, then - assertThatThrownBy(() -> new Review(member, member, null, keywords, createdAt)) - .isInstanceOf(IllegalReviewerException.class); - } - - @Test - void 마감_기한이_지난_그룹에_리뷰를_등록할_수_없다() { - // given - Member reviewer = 회원_산초.create(); - Member reviewee = 회원_아루.create(); - ReviewerGroup reviewerGroup = 데드라인_지난_그룹.create(reviewee, List.of(reviewer.getGithubId())); - LocalDateTime createdAt = LocalDateTime.now(); - List keywords = List.of(); - - // when, then - assertThatThrownBy(() -> new Review(reviewer, reviewee, reviewerGroup, keywords, createdAt)) - .isInstanceOf(DeadlineExpiredException.class); - } - - @Test - void 하나의_리뷰_그룹에_중복으로_리뷰를_등록할_수_없다() { - // given - Member reviewer = 회원_산초.create(); - Member reviewee = 회원_아루.create(); - ReviewerGroup reviewerGroup = 데드라인_남은_그룹.create(reviewee, List.of(reviewer.getGithubId())); - new Review(reviewer, reviewee, reviewerGroup, List.of(), LocalDateTime.now()); - LocalDateTime createdAt = LocalDateTime.now(); - List keywords = List.of(); - - // when, then - assertThatThrownBy(() -> new Review(reviewer, reviewee, reviewerGroup, keywords, createdAt)) - .isInstanceOf(ReviewAlreadySubmittedException.class); - } - - @Test - void 리뷰어로_등록되지_않은_회원은_리뷰를_등록할_수_없다() { - // given - Member reviewer = new Member("reviewer", 1); - Member reviewee = new Member("reviewee", 2); - ReviewerGroup reviewerGroup = 데드라인_남은_그룹.create(reviewee, List.of(new GithubId(3))); - LocalDateTime createdAt = LocalDateTime.now(); - List keywords = List.of(); - - // when, then - assertThatThrownBy(() -> new Review(reviewer, reviewee, reviewerGroup, keywords, createdAt)) - .isInstanceOf(GithubReviewerGroupUnAuthorizedException.class); - } - - @Test - void 그룹_내에서_그룹_밖으로_리뷰를_작성할_수_없다() { - // given - Member reviewer = 회원_산초.create(); - Member reviewee = 회원_아루.create(); - Member other = 회원_커비.create(); - ReviewerGroup reviewerGroup = 데드라인_남은_그룹.create(reviewee, List.of(reviewer.getGithubId())); - LocalDateTime createdAt = LocalDateTime.now(); - List keywords = List.of(); - - // when, then - assertThatThrownBy(() -> new Review(reviewer, other, reviewerGroup, keywords, createdAt)) - .isInstanceOf(RevieweeMismatchException.class); - } -} diff --git a/backend/src/test/java/reviewme/review/service/QuestionServiceTest.java b/backend/src/test/java/reviewme/review/service/QuestionServiceTest.java deleted file mode 100644 index a9c056a4b..000000000 --- a/backend/src/test/java/reviewme/review/service/QuestionServiceTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package reviewme.review.service; - -import static org.assertj.core.api.Assertions.assertThat; -import static reviewme.fixture.QuestionFixure.소프트스킬이_어떤가요; - -import java.util.List; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import reviewme.review.dto.response.QuestionResponse; -import reviewme.review.repository.QuestionRepository; -import reviewme.support.ServiceTest; - -@ServiceTest -class QuestionServiceTest { - - @Autowired - QuestionService questionService; - - @Autowired - QuestionRepository questionRepository; - - @Test - void 모든_리뷰_문항을_조회한다() { - // given - questionRepository.save(소프트스킬이_어떤가요.create()); - - // when - List questions = questionService.findAllQuestions(); - - // then - assertThat(questions).hasSize(1); - } -} diff --git a/backend/src/test/java/reviewme/review/service/ReviewCreationKeywordValidatorTest.java b/backend/src/test/java/reviewme/review/service/ReviewCreationKeywordValidatorTest.java new file mode 100644 index 000000000..b295a413d --- /dev/null +++ b/backend/src/test/java/reviewme/review/service/ReviewCreationKeywordValidatorTest.java @@ -0,0 +1,79 @@ +package reviewme.review.service; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; +import static reviewme.fixture.KeywordFixture.꼼꼼하게_기록해요; +import static reviewme.fixture.KeywordFixture.의견을_잘_조율해요; +import static reviewme.fixture.KeywordFixture.추진력이_좋아요; +import static reviewme.fixture.KeywordFixture.회의를_이끌어요; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import reviewme.keyword.domain.Keyword; +import reviewme.keyword.domain.exception.DuplicateKeywordException; +import reviewme.keyword.domain.exception.KeywordLimitExceedException; +import reviewme.keyword.domain.exception.KeywordNotFoundException; +import reviewme.keyword.repository.KeywordRepository; +import reviewme.support.ServiceTest; + +@ServiceTest +class ReviewCreationKeywordValidatorTest { + + @Autowired + ReviewCreationKeywordValidator reviewCreationKeywordValidator; + + @Autowired + KeywordRepository keywordRepository; + + @Test + void 존재하는_키워드의_아이디인지_검사한다() { + // given + Keyword keyword1 = keywordRepository.save(회의를_이끌어요.create()); + Keyword keyword2 = keywordRepository.save(추진력이_좋아요.create()); + long nonExistKeywordId = Long.MAX_VALUE; + + // when, then + assertAll( + () -> assertThatCode( + () -> reviewCreationKeywordValidator.validate(List.of(keyword1.getId(), keyword2.getId()))) + .doesNotThrowAnyException(), + () -> assertThatCode(() -> reviewCreationKeywordValidator.validate(List.of(nonExistKeywordId))) + .isInstanceOf(KeywordNotFoundException.class) + ); + } + + @Test + void 중복되는_아이디의_키워드인지_검사한다() { + // given + Keyword keyword = keywordRepository.save(회의를_이끌어요.create()); + + // when, then + assertAll( + () -> assertThatCode(() -> reviewCreationKeywordValidator.validate(List.of(keyword.getId()))) + .doesNotThrowAnyException(), + () -> assertThatCode( + () -> reviewCreationKeywordValidator.validate(List.of(keyword.getId(), keyword.getId()))) + .isInstanceOf(DuplicateKeywordException.class) + ); + } + + @Test + void 유효한_개수의_키워드인지_검사한다() { + // given + Keyword keyword1 = keywordRepository.save(회의를_이끌어요.create()); + Keyword keyword2 = keywordRepository.save(추진력이_좋아요.create()); + Keyword keyword3 = keywordRepository.save(꼼꼼하게_기록해요.create()); + Keyword keyword4 = keywordRepository.save(의견을_잘_조율해요.create()); + Keyword keyword5 = keywordRepository.save(new Keyword("간식을 잘 나눠줘요.")); + Keyword keyword6 = keywordRepository.save(new Keyword("감정을 잘 공감해줘요.")); + List keywordIds = List.of( + keyword1.getId(), keyword2.getId(), keyword3.getId(), keyword4.getId(), keyword5.getId(), + keyword6.getId() + ); + + // when, then + assertThatCode(() -> reviewCreationKeywordValidator.validate(keywordIds)) + .isInstanceOf(KeywordLimitExceedException.class); + } +} diff --git a/backend/src/test/java/reviewme/review/service/ReviewCreationQuestionValidatorTest.java b/backend/src/test/java/reviewme/review/service/ReviewCreationQuestionValidatorTest.java new file mode 100644 index 000000000..b21b1fbe9 --- /dev/null +++ b/backend/src/test/java/reviewme/review/service/ReviewCreationQuestionValidatorTest.java @@ -0,0 +1,54 @@ +package reviewme.review.service; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; +import static reviewme.fixture.QuestionFixure.기술역량이_어떤가요; +import static reviewme.fixture.QuestionFixure.소프트스킬이_어떤가요; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import reviewme.question.domain.exception.DuplicateQuestionException; +import reviewme.question.domain.exception.QuestionNotFoundException; +import reviewme.review.repository.QuestionRepository; +import reviewme.support.ServiceTest; + +@ServiceTest +class ReviewCreationQuestionValidatorTest { + + @Autowired + private ReviewCreationQuestionValidator reviewCreationQuestionValidator; + + @Autowired + private QuestionRepository questionRepository; + + @Test + void 존재하는_질문의_아이디인지_검사한다() { + // given + long existQuestionId = questionRepository.save(소프트스킬이_어떤가요.create()).getId(); + long nonExistQuestionId = Long.MAX_VALUE; + + // when, then + assertAll( + () -> assertThatCode(() -> reviewCreationQuestionValidator.validate(List.of(existQuestionId))) + .doesNotThrowAnyException(), + () -> assertThatCode(() -> reviewCreationQuestionValidator.validate(List.of(nonExistQuestionId))) + .isInstanceOf(QuestionNotFoundException.class) + ); + } + + @Test + void 중복되는_아이디의_질문인지_검사한다() { + // given + long questionId1 = questionRepository.save(소프트스킬이_어떤가요.create()).getId(); + long questionId2 = questionRepository.save(기술역량이_어떤가요.create()).getId(); + + // when, then + assertAll( + () -> assertThatCode(() -> reviewCreationQuestionValidator.validate(List.of(questionId1, questionId2))) + .doesNotThrowAnyException(), + () -> assertThatCode(() -> reviewCreationQuestionValidator.validate(List.of(questionId1, questionId1))) + .isInstanceOf(DuplicateQuestionException.class) + ); + } +} diff --git a/backend/src/test/java/reviewme/review/service/ReviewServiceTest.java b/backend/src/test/java/reviewme/review/service/ReviewServiceTest.java index 664e9b430..e7270e048 100644 --- a/backend/src/test/java/reviewme/review/service/ReviewServiceTest.java +++ b/backend/src/test/java/reviewme/review/service/ReviewServiceTest.java @@ -4,31 +4,25 @@ import static org.junit.jupiter.api.Assertions.assertAll; import static reviewme.fixture.KeywordFixture.추진력이_좋아요; import static reviewme.fixture.KeywordFixture.회의를_이끌어요; -import static reviewme.fixture.MemberFixture.회원_산초; -import static reviewme.fixture.MemberFixture.회원_아루; -import static reviewme.fixture.MemberFixture.회원_커비; -import static reviewme.fixture.MemberFixture.회원_테드; +import static reviewme.fixture.QuestionFixure.기술역량이_어떤가요; +import static reviewme.fixture.QuestionFixure.소프트스킬이_어떤가요; -import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import reviewme.keyword.domain.Keyword; import reviewme.keyword.repository.KeywordRepository; -import reviewme.member.domain.GithubId; -import reviewme.member.domain.Member; -import reviewme.member.domain.ReviewerGroup; -import reviewme.member.repository.MemberRepository; -import reviewme.member.repository.ReviewerGroupRepository; -import reviewme.review.domain.Question; +import reviewme.question.domain.Question; import reviewme.review.domain.Review; -import reviewme.review.domain.ReviewContent; import reviewme.review.dto.request.CreateReviewContentRequest; import reviewme.review.dto.request.CreateReviewRequest; -import reviewme.review.dto.response.ReceivedReviewsResponse; import reviewme.review.repository.QuestionRepository; import reviewme.review.repository.ReviewContentRepository; +import reviewme.review.repository.ReviewKeywordRepository; import reviewme.review.repository.ReviewRepository; +import reviewme.reviewgroup.domain.ReviewGroup; +import reviewme.reviewgroup.repository.ReviewGroupRepository; import reviewme.support.ServiceTest; @ServiceTest @@ -40,12 +34,6 @@ class ReviewServiceTest { @Autowired ReviewRepository reviewRepository; - @Autowired - MemberRepository memberRepository; - - @Autowired - ReviewerGroupRepository reviewerGroupRepository; - @Autowired KeywordRepository keywordRepository; @@ -55,91 +43,40 @@ class ReviewServiceTest { @Autowired QuestionRepository questionRepository; - @Test - void 리뷰를_작성한다() { - // given - Member reviewer = memberRepository.save(회원_산초.create()); - Member reviewee = memberRepository.save(회원_아루.create()); - List reviewerGithubIds = List.of(reviewer.getGithubId()); - - ReviewerGroup reviewerGroup = reviewerGroupRepository.save( - new ReviewerGroup(reviewee, reviewerGithubIds, "그룹명", "그룹설명", LocalDateTime.now().plusDays(1)) - ); - Question question = questionRepository.save(new Question("질문")); - Keyword keyword = keywordRepository.save(추진력이_좋아요.create()); - CreateReviewContentRequest contentRequest = new CreateReviewContentRequest(question.getId(), "답변".repeat(10)); - - CreateReviewRequest createReviewRequest = new CreateReviewRequest(reviewer.getId(), reviewerGroup.getId(), - List.of(contentRequest), List.of(keyword.getId()) - ); - - // when - reviewService.createReview(createReviewRequest); + @Autowired + ReviewGroupRepository reviewGroupRepository; - // then - List actual = reviewRepository.findAll(); - assertThat(actual).hasSize(1); - } + @Autowired + ReviewKeywordRepository reviewKeywordRepository; @Test - void 내가_받은_리뷰를_조회한다() { + void 리뷰를_생성한다() { // given - Member reviewee = memberRepository.save(회원_아루.create()); - Member reviewerSancho = memberRepository.save(회원_산초.create()); - Member reviewerKirby = memberRepository.save(회원_커비.create()); - Member reviewerTed = memberRepository.save(회원_테드.create()); Keyword keyword1 = keywordRepository.save(추진력이_좋아요.create()); Keyword keyword2 = keywordRepository.save(회의를_이끌어요.create()); - ReviewerGroup reviewerGroup = reviewerGroupRepository.save(new ReviewerGroup( - reviewee, - List.of(reviewerSancho.getGithubId(), - reviewerKirby.getGithubId(), - reviewerTed.getGithubId()), - "빼깬드그룹", - "빼깬드그룹 설명", - LocalDateTime.now().plusDays(3) - )); - Question question = questionRepository.save(new Question("질문")); + Question question1 = questionRepository.save(소프트스킬이_어떤가요.create()); + Question question2 = questionRepository.save(기술역량이_어떤가요.create()); - Review sanchoReview = reviewRepository.save( - new Review(reviewerSancho, reviewee, reviewerGroup, List.of(keyword1), LocalDateTime.now().minusDays(1)) - ); - Review kirbyReview = reviewRepository.save( - new Review(reviewerKirby, reviewee, reviewerGroup, List.of(keyword2), LocalDateTime.now()) - ); - Review tedReview = reviewRepository.save( - new Review(reviewerTed, reviewee, reviewerGroup, List.of(keyword1, keyword2), - LocalDateTime.now().plusDays(1)) - ); - reviewContentRepository.saveAll(List.of( - new ReviewContent(sanchoReview, question, "산초의 답변".repeat(50)), - new ReviewContent(kirbyReview, question, "커비의 답변".repeat(50)), - new ReviewContent(tedReview, question, "테드의 답변".repeat(50))) - ); + String reviewRequestCode = "reviewRequestCode"; + reviewGroupRepository.save(new ReviewGroup("산초", "리뷰미 프로젝트", reviewRequestCode, "groupAccessCode")); + + CreateReviewContentRequest request1 = new CreateReviewContentRequest(question1.getId(), "답변".repeat(20)); + CreateReviewContentRequest request2 = new CreateReviewContentRequest(question2.getId(), "응답".repeat(20)); + CreateReviewRequest reviewRequest1 = new CreateReviewRequest(reviewRequestCode, List.of(request1, request2), + List.of(keyword1.getId(), keyword2.getId())); + CreateReviewRequest reviewRequest2 = new CreateReviewRequest(reviewRequestCode, List.of(request1), + List.of(keyword1.getId())); // when - ReceivedReviewsResponse 가장_최근에_받은_리뷰_조회 - = reviewService.findMyReceivedReview(reviewee.getId(), null, 2); - ReceivedReviewsResponse 특정_리뷰_이전_리뷰_조회 - = reviewService.findMyReceivedReview(reviewee.getId(), 2L, 2); + long reviewId = reviewService.createReview(reviewRequest1); + reviewService.createReview(reviewRequest2); // then + Optional optionalReview = reviewRepository.findById(reviewId); + assertThat(optionalReview).isPresent(); assertAll( - () -> assertThat(가장_최근에_받은_리뷰_조회.reviews()) - .hasSize(2), - () -> assertThat(가장_최근에_받은_리뷰_조회.reviews().get(0).id()) - .isEqualTo(tedReview.getId()), - () -> assertThat(가장_최근에_받은_리뷰_조회.reviews().get(1).id()) - .isEqualTo(kirbyReview.getId()), - () -> assertThat(가장_최근에_받은_리뷰_조회.reviews().get(0).contentPreview().length()) - .isLessThanOrEqualTo(150), - - () -> assertThat(특정_리뷰_이전_리뷰_조회.reviews()) - .hasSize(1), - () -> assertThat(특정_리뷰_이전_리뷰_조회.reviews().get(0).id()) - .isEqualTo(sanchoReview.getId()), - () -> assertThat(특정_리뷰_이전_리뷰_조회.reviews().get(0).contentPreview().length()) - .isLessThanOrEqualTo(150) + () -> assertThat(optionalReview.get().getReviewContents()).hasSize(2), + () -> assertThat(reviewKeywordRepository.findAllByReviewId(reviewId)).hasSize(2) ); } } diff --git a/backend/src/test/java/reviewme/reviewgroup/ReviewGroupTest.java b/backend/src/test/java/reviewme/reviewgroup/ReviewGroupTest.java new file mode 100644 index 000000000..d6ac6055a --- /dev/null +++ b/backend/src/test/java/reviewme/reviewgroup/ReviewGroupTest.java @@ -0,0 +1,63 @@ +package reviewme.reviewgroup; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import org.junit.jupiter.api.Test; +import reviewme.global.exception.BadRequestException; +import reviewme.reviewgroup.domain.ReviewGroup; + +class ReviewGroupTest { + + @Test + void 정상_생성된다() { + // given + int maxLength = 50; + int minLength = 1; + String minLengthName = "*".repeat(minLength); + String maxLengthName = "*".repeat(maxLength); + + // when, then + assertAll( + () -> assertThatCode(() -> new ReviewGroup(minLengthName, "project", "reviewCode", "groupCode")) + .doesNotThrowAnyException(), + () -> assertThatCode(() -> new ReviewGroup(maxLengthName, "project", "reviewCode", "groupCode")) + .doesNotThrowAnyException() + ); + } + + @Test + void 리뷰이_이름이_정해진_길이에_맞지_않으면_예외가_발생한다() { + // given + int maxLength = 50; + int minLength = 1; + String insufficientName = "*".repeat(minLength - 1); + String exceedName = "*".repeat(maxLength + 1); + + // when, then + assertAll( + () -> assertThatCode(() -> new ReviewGroup(insufficientName, "project", "reviewCode", "groupCode")) + .isInstanceOf(BadRequestException.class), + () -> assertThatThrownBy(() -> new ReviewGroup(exceedName, "project", "reviewCode", "groupCode")) + .isInstanceOf(BadRequestException.class) + ); + } + + @Test + void 프로젝트_이름이_정해진_길이에_맞지_않으면_예외가_발생한다() { + // given + int maxLength = 50; + int minLength = 1; + String insufficientName = "*".repeat(minLength - 1); + String exceedName = "*".repeat(maxLength + 1); + + // when, then + assertAll( + () -> assertThatThrownBy(() -> new ReviewGroup("reviwee", insufficientName, "reviewCode", "groupCode")) + .isInstanceOf(BadRequestException.class), + () -> assertThatThrownBy(() -> new ReviewGroup("reviwee", exceedName, "reviewCode", "groupCode")) + .isInstanceOf(BadRequestException.class) + ); + } +}