Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Release] 리뷰미 v1.1.2 배포 #780

Merged
merged 3 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package reviewme.review.domain.abstraction;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
import jakarta.persistence.InheritanceType;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "answer")
@Inheritance(strategy = InheritanceType.JOINED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(of = "id")
@Getter
public abstract class Answer {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;

@Column(name = "question_id", nullable = false)
protected long questionId;

@Column(name = "review_id", nullable = false, insertable = false, updatable = false)
private long reviewId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package reviewme.review.domain.abstraction;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.util.List;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import reviewme.review.domain.exception.QuestionNotAnsweredException;

@Entity
@Table(name = "new_checkbox_answer")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(callSuper = true)
@Getter
public class NewCheckboxAnswer extends Answer {

@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "checkbox_answer_id", nullable = false, updatable = false)
private List<NewCheckboxAnswerSelectedOption> selectedOptionIds;

public NewCheckboxAnswer(long questionId, List<Long> selectedOptionIds) {
validateSelectedOptionIds(questionId, selectedOptionIds);
this.questionId = questionId;
this.selectedOptionIds = selectedOptionIds.stream()
.map(NewCheckboxAnswerSelectedOption::new)
.toList();
}

private void validateSelectedOptionIds(long questionId, List<Long> selectedOptionIds) {
if (selectedOptionIds == null || selectedOptionIds.isEmpty()) {
throw new QuestionNotAnsweredException(questionId);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package reviewme.review.domain.abstraction;

import org.springframework.data.jpa.repository.JpaRepository;

public interface NewCheckboxAnswerRepository extends JpaRepository<NewCheckboxAnswer, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package reviewme.review.domain.abstraction;

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.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "new_checkbox_answer_selected_option")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(of = "id")
@Getter
public class NewCheckboxAnswerSelectedOption {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "checkbox_answer_id", nullable = false, insertable = false, updatable = false)
private long checkboxAnswerId;

@Column(name = "selected_option_id", nullable = false)
private long selectedOptionId;

public NewCheckboxAnswerSelectedOption(long selectedOptionId) {
this.selectedOptionId = selectedOptionId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package reviewme.review.domain.abstraction;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "new_review")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(of = "id")
@Getter
public class NewReview {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "template_id", nullable = false)
private long templateId;

@Column(name = "review_group_id", nullable = false)
private long reviewGroupId;

@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
@JoinColumn(name = "review_id", nullable = false, updatable = false)
private List<Answer> answers;

@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;

public NewReview(long templateId, long reviewGroupId, List<Answer> answers) {
this.templateId = templateId;
this.reviewGroupId = reviewGroupId;
this.answers = answers;
this.createdAt = LocalDateTime.now();
}

public Set<Long> getAnsweredQuestionIds() {
return answers.stream()
.map(Answer::getQuestionId)
.collect(Collectors.toSet());
}

public boolean hasAnsweredQuestion(long questionId) {
return getAnsweredQuestionIds().contains(questionId);
}

public <T extends Answer> List<T> getAnswersByType(Class<T> clazz) {
return answers.stream()
.filter(clazz::isInstance)
.map(clazz::cast)
.toList();
}

public LocalDate getCreatedDate() {
return createdAt.toLocalDate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package reviewme.review.domain.abstraction;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface NewReviewRepository extends JpaRepository<NewReview, Long> {

@Query(value = """
SELECT r.* FROM new_review r
WHERE r.review_group_id = :reviewGroupId
ORDER BY r.created_at DESC
""", nativeQuery = true)
List<NewReview> findAllByGroupId(long reviewGroupId);

@Query(value = """
SELECT r.* FROM new_review r
WHERE r.review_group_id = :reviewGroupId
AND (:lastReviewId IS NULL OR r.id < :lastReviewId)
ORDER BY r.created_at DESC, r.id DESC
LIMIT :limit
""", nativeQuery = true)
List<NewReview> findByReviewGroupIdWithLimit(long reviewGroupId, Long lastReviewId, int limit);

Optional<NewReview> findByIdAndReviewGroupId(long reviewId, long reviewGroupId);

@Query(value = """
SELECT COUNT(r.id) FROM new_review r
WHERE r.review_group_id = :reviewGroupId
AND r.id < :reviewId
AND CAST(r.created_at AS DATE) <= :createdDate
""", nativeQuery = true)
Long existsOlderReviewInGroupInLong(long reviewGroupId, long reviewId, LocalDate createdDate);

default boolean existsOlderReviewInGroup(long reviewGroupId, long reviewId, LocalDate createdDate) {
return existsOlderReviewInGroupInLong(reviewGroupId, reviewId, createdDate) > 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package reviewme.review.domain.abstraction;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import reviewme.review.domain.exception.QuestionNotAnsweredException;

@Entity
@Table(name = "new_text_answer")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(callSuper = true)
@Getter
public class NewTextAnswer extends Answer {

@Column(name = "content", nullable = false, length = 5000)
private String content;

public NewTextAnswer(long questionId, String content) {
validateContent(questionId, content);
this.questionId = questionId;
this.content = content;
}

private void validateContent(long questionId, String content) {
if (content == null || content.isEmpty()) {
throw new QuestionNotAnsweredException(questionId);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package reviewme.review.domain.abstraction;

import org.springframework.data.jpa.repository.JpaRepository;

public interface NewTextAnswerRepository extends JpaRepository<NewTextAnswer, Long> {
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package reviewme.review.service;

import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reviewme.review.domain.Review;
import reviewme.review.domain.abstraction.NewReview;
import reviewme.review.domain.abstraction.NewReviewRepository;
import reviewme.review.repository.ReviewRepository;
import reviewme.review.service.abstraction.mapper.NewReviewMapper;
import reviewme.review.service.abstraction.validator.NewReviewValidator;
import reviewme.review.service.dto.request.ReviewRegisterRequest;
import reviewme.review.service.mapper.ReviewMapper;
import reviewme.review.service.validator.ReviewValidator;
Expand All @@ -13,16 +19,28 @@
@RequiredArgsConstructor
public class ReviewRegisterService {

private static final Logger log = LoggerFactory.getLogger(ReviewRegisterService.class);
private final ReviewMapper reviewMapper;
private final ReviewValidator reviewValidator;

private final ReviewRepository reviewRepository;

// 리뷰 추상화, 같은 Transactional에 넣어 처리
private final NewReviewMapper newReviewMapper;
private final NewReviewValidator newReviewValidator;
private final NewReviewRepository newReviewRepository;

@Transactional
public long registerReview(ReviewRegisterRequest request) {
Review review = reviewMapper.mapToReview(request);
reviewValidator.validate(review);
Review registeredReview = reviewRepository.save(review);

// 새로운 테이블에 중복해서 저장
NewReview newReview = newReviewMapper.mapToReview(request);
newReviewValidator.validate(newReview);
newReviewRepository.save(newReview);

return registeredReview.getId();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package reviewme.review.service.abstraction.mapper;

import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import reviewme.question.domain.QuestionType;

@Component
@RequiredArgsConstructor
public class AnswerMapperFactory {

private final List<NewAnswerMapper> answerMappers;

public NewAnswerMapper getAnswerMapper(QuestionType questionType) {
return answerMappers.stream()
.filter(answerMapper -> answerMapper.supports(questionType))
.findFirst()
.orElseThrow(() -> new UnsupportedQuestionTypeException(questionType));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package reviewme.review.service.abstraction.mapper;

import reviewme.question.domain.QuestionType;
import reviewme.review.domain.abstraction.Answer;
import reviewme.review.service.dto.request.ReviewAnswerRequest;

public interface NewAnswerMapper {

boolean supports(QuestionType questionType);

Answer mapToAnswer(ReviewAnswerRequest answerRequest);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package reviewme.review.service.abstraction.mapper;

import org.springframework.stereotype.Component;
import reviewme.question.domain.QuestionType;
import reviewme.review.domain.abstraction.NewCheckboxAnswer;
import reviewme.review.service.dto.request.ReviewAnswerRequest;
import reviewme.review.service.exception.CheckBoxAnswerIncludedTextException;

@Component
public class NewCheckboxAnswerMapper implements NewAnswerMapper {

@Override
public boolean supports(QuestionType questionType) {
return questionType == QuestionType.CHECKBOX;
}

@Override
public NewCheckboxAnswer mapToAnswer(ReviewAnswerRequest answerRequest) {
if (answerRequest.text() != null) {
throw new CheckBoxAnswerIncludedTextException(answerRequest.questionId());
}
return new NewCheckboxAnswer(answerRequest.questionId(), answerRequest.selectedOptionIds());
}
}
Loading
Loading