From 46251f3e00494e666ee4cd69b84b9c69af6dfaad Mon Sep 17 00:00:00 2001 From: mins1031 Date: Tue, 9 Dec 2025 17:49:26 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat=20:=20=EB=A0=88=EA=B1=B0=EC=8B=9C=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81=20*?= =?UTF-8?q?=20=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=20=ED=86=A0=EB=8C=80?= =?UTF-8?q?=EB=A1=9C=20=EC=A7=88=EB=AC=B8,=20=EB=8B=B5=EB=B3=80=EC=9D=98?= =?UTF-8?q?=20=ED=96=89=EC=9C=84=20=EC=84=A4=EC=A0=95=20*=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=EB=90=9C=20=ED=96=89=EC=9C=84=EB=93=A4=20TDD=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20*=20=EB=A0=88=ED=8F=AC=EC=A7=80=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=9D=B4=EA=B4=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/nextstep/qna/domain/Answer.java | 49 ++++++++--- .../java/nextstep/qna/domain/Question.java | 84 +++++++++++++------ .../infrastructure/JdbcAnswerRepository.java | 2 +- .../JdbcDeleteHistoryRepository.java | 2 +- .../JdbcQuestionRepository.java | 2 +- .../qna/service/DeleteHistoryService.java | 2 +- .../java/nextstep/qna/service/QnAService.java | 26 ++++++ .../repository}/AnswerRepository.java | 3 +- .../repository}/DeleteHistoryRepository.java | 3 +- .../repository}/QuestionRepository.java | 3 +- .../java/nextstep/qna/domain/AnswerTest.java | 13 +++ .../nextstep/qna/domain/QuestionTest.java | 72 ++++++++++++++++ .../nextstep/qna/service/QnaServiceTest.java | 1 + 13 files changed, 218 insertions(+), 44 deletions(-) rename src/main/java/nextstep/qna/{domain => service/repository}/AnswerRepository.java (59%) rename src/main/java/nextstep/qna/{domain => service/repository}/DeleteHistoryRepository.java (60%) rename src/main/java/nextstep/qna/{domain => service/repository}/QuestionRepository.java (58%) diff --git a/src/main/java/nextstep/qna/domain/Answer.java b/src/main/java/nextstep/qna/domain/Answer.java index cf681811e..bbb409b78 100644 --- a/src/main/java/nextstep/qna/domain/Answer.java +++ b/src/main/java/nextstep/qna/domain/Answer.java @@ -1,5 +1,6 @@ package nextstep.qna.domain; +import nextstep.qna.CannotDeleteException; import nextstep.qna.NotFoundException; import nextstep.qna.UnAuthorizedException; import nextstep.users.domain.NsUser; @@ -10,6 +11,7 @@ public class Answer { private Long id; private NsUser writer; + private Long writerId; private Question question; @@ -28,6 +30,10 @@ public Answer(NsUser writer, Question question, String contents) { this(null, writer, question, contents); } + public Answer(long writerId, Question question, String contents) { + this(null, writerId, question, contents); + } + public Answer(Long id, NsUser writer, Question question, String contents) { this.id = id; if(writer == null) { @@ -43,29 +49,52 @@ public Answer(Long id, NsUser writer, Question question, String contents) { this.contents = contents; } - public Long getId() { - return id; - } + public Answer(Long id, long writerId, Question question, String contents) { + this.id = id; + if(writerId <= 0L) { + throw new UnAuthorizedException(); + } - public Answer setDeleted(boolean deleted) { - this.deleted = deleted; - return this; + if(question == null) { + throw new NotFoundException(); + } + + this.writerId = writerId; + this.question = question; + this.contents = contents; } public boolean isDeleted() { return deleted; } + public void putOnDelete(long requesterId) { +// if (!isOwner(requesterId)) { +// throw new CannotDeleteException("질문을 삭제할 권한이 없습니다."); +// } + + this.deleted = true; + } + public boolean isOwner(NsUser writer) { return this.writer.equals(writer); } - public NsUser getWriter() { - return writer; + public boolean isOwner(long writerId) { + return this.writerId == writerId; + } + + public Long getId() { + return id; + } + + public Answer setDeleted(boolean deleted) { + this.deleted = deleted; + return this; } - public String getContents() { - return contents; + public NsUser getWriter() { + return writer; } public void toQuestion(Question question) { diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index b623c52c7..89742d784 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -1,12 +1,13 @@ package nextstep.qna.domain; -import nextstep.users.domain.NsUser; - import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import nextstep.qna.CannotDeleteException; +import nextstep.users.domain.NsUser; public class Question { + private Long id; private String title; @@ -14,6 +15,7 @@ public class Question { private String contents; private NsUser writer; + private Long writerId; private List answers = new ArrayList<>(); @@ -30,6 +32,10 @@ public Question(NsUser writer, String title, String contents) { this(0L, writer, title, contents); } + public Question(long writerId, String title, String contents) { + this(0L, writerId, title, contents); + } + public Question(Long id, NsUser writer, String title, String contents) { this.id = id; this.writer = writer; @@ -37,56 +43,80 @@ public Question(Long id, NsUser writer, String title, String contents) { this.contents = contents; } - public Long getId() { - return id; + public Question(Long id, long writerId, String title, String contents) { + this.id = id; + this.writerId = writerId; + this.title = title; + this.contents = contents; } - public String getTitle() { - return title; + public boolean isOwner(NsUser loginUser) { + return writer.equals(loginUser); } - public Question setTitle(String title) { - this.title = title; - return this; + public boolean isOwner(long requesterId) { + return writerId == requesterId; } - public String getContents() { - return contents; + public void addAnswer(Answer answer) { + answer.toQuestion(this); + answers.add(answer); } - public Question setContents(String contents) { - this.contents = contents; - return this; + public boolean isDeleted() { + return deleted; } - public NsUser getWriter() { - return writer; + public boolean hasAnswers() { + return !this.answers.isEmpty(); } - public void addAnswer(Answer answer) { - answer.toQuestion(this); - answers.add(answer); + public boolean isAllSameContentsWriter() { + return this.answers.stream() + .allMatch(answer -> answer.isOwner(this.writerId)); } - public boolean isOwner(NsUser loginUser) { - return writer.equals(loginUser); + public void putOnDelete(long requesterId) { + if (requesterId <= 0L) { + throw new IllegalArgumentException("잘못된 요청자 정보 입니다."); + } + +// if (!isOwner(requesterId)) { +// throw new CannotDeleteException("질문을 삭제할 권한이 없습니다."); +// } +// +// if (hasAnswers() || !isAllSameContentsWriter()) { +// throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); +// } + + this.deleted = true; +// putOnAllAnswersDelete(requesterId); } - public Question setDeleted(boolean deleted) { - this.deleted = deleted; - return this; + public void putOnAllAnswersDelete(long requesterId) { + this.answers.forEach(answer -> answer.putOnDelete(requesterId)); } - public boolean isDeleted() { - return deleted; + public Long getId() { + return id; + } + + public NsUser getWriter() { + return writer; } public List getAnswers() { return answers; } + public Question setDeleted(boolean deleted) { + this.deleted = deleted; + return this; + } + @Override public String toString() { - return "Question [id=" + getId() + ", title=" + title + ", contents=" + contents + ", writer=" + writer + "]"; + return "Question [id=" + getId() + ", title=" + title + ", contents=" + contents + + ", writer=" + writer + "]"; } } diff --git a/src/main/java/nextstep/qna/infrastructure/JdbcAnswerRepository.java b/src/main/java/nextstep/qna/infrastructure/JdbcAnswerRepository.java index 7c27c6dd8..65a826e29 100644 --- a/src/main/java/nextstep/qna/infrastructure/JdbcAnswerRepository.java +++ b/src/main/java/nextstep/qna/infrastructure/JdbcAnswerRepository.java @@ -1,7 +1,7 @@ package nextstep.qna.infrastructure; import nextstep.qna.domain.Answer; -import nextstep.qna.domain.AnswerRepository; +import nextstep.qna.service.repository.AnswerRepository; import org.springframework.stereotype.Repository; import java.util.List; diff --git a/src/main/java/nextstep/qna/infrastructure/JdbcDeleteHistoryRepository.java b/src/main/java/nextstep/qna/infrastructure/JdbcDeleteHistoryRepository.java index 7ff018887..fd0470765 100644 --- a/src/main/java/nextstep/qna/infrastructure/JdbcDeleteHistoryRepository.java +++ b/src/main/java/nextstep/qna/infrastructure/JdbcDeleteHistoryRepository.java @@ -1,7 +1,7 @@ package nextstep.qna.infrastructure; import nextstep.qna.domain.DeleteHistory; -import nextstep.qna.domain.DeleteHistoryRepository; +import nextstep.qna.service.repository.DeleteHistoryRepository; import org.springframework.stereotype.Repository; import java.util.List; diff --git a/src/main/java/nextstep/qna/infrastructure/JdbcQuestionRepository.java b/src/main/java/nextstep/qna/infrastructure/JdbcQuestionRepository.java index a6e4062dc..ab4868cf5 100644 --- a/src/main/java/nextstep/qna/infrastructure/JdbcQuestionRepository.java +++ b/src/main/java/nextstep/qna/infrastructure/JdbcQuestionRepository.java @@ -1,7 +1,7 @@ package nextstep.qna.infrastructure; import nextstep.qna.domain.Question; -import nextstep.qna.domain.QuestionRepository; +import nextstep.qna.service.repository.QuestionRepository; import org.springframework.stereotype.Repository; import java.util.Optional; diff --git a/src/main/java/nextstep/qna/service/DeleteHistoryService.java b/src/main/java/nextstep/qna/service/DeleteHistoryService.java index 7599dca96..7d11fe86a 100644 --- a/src/main/java/nextstep/qna/service/DeleteHistoryService.java +++ b/src/main/java/nextstep/qna/service/DeleteHistoryService.java @@ -1,7 +1,7 @@ package nextstep.qna.service; import nextstep.qna.domain.DeleteHistory; -import nextstep.qna.domain.DeleteHistoryRepository; +import nextstep.qna.service.repository.DeleteHistoryRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/nextstep/qna/service/QnAService.java b/src/main/java/nextstep/qna/service/QnAService.java index 5741c84d6..b5208660e 100644 --- a/src/main/java/nextstep/qna/service/QnAService.java +++ b/src/main/java/nextstep/qna/service/QnAService.java @@ -3,6 +3,8 @@ import nextstep.qna.CannotDeleteException; import nextstep.qna.NotFoundException; import nextstep.qna.domain.*; +import nextstep.qna.service.repository.AnswerRepository; +import nextstep.qna.service.repository.QuestionRepository; import nextstep.users.domain.NsUser; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -46,4 +48,28 @@ public void deleteQuestion(NsUser loginUser, long questionId) throws CannotDelet } deleteHistoryService.saveAll(deleteHistories); } + + @Transactional + public void deleteQuestion2(long requesterId, long questionId) throws CannotDeleteException { + Question question = questionRepository.findById(questionId).orElseThrow(NotFoundException::new); + if (!question.isOwner(requesterId)) { + throw new CannotDeleteException("질문을 삭제할 권한이 없습니다."); + } + + List answers = question.getAnswers(); + for (Answer answer : answers) { +// if (!answer.isOwner2(requesterId)) { +// throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); +// } + } + + List deleteHistories = new ArrayList<>(); + question.setDeleted(true); + deleteHistories.add(new DeleteHistory(ContentType.QUESTION, questionId, question.getWriter(), LocalDateTime.now())); + for (Answer answer : answers) { + answer.setDeleted(true); + deleteHistories.add(new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now())); + } + deleteHistoryService.saveAll(deleteHistories); + } } diff --git a/src/main/java/nextstep/qna/domain/AnswerRepository.java b/src/main/java/nextstep/qna/service/repository/AnswerRepository.java similarity index 59% rename from src/main/java/nextstep/qna/domain/AnswerRepository.java rename to src/main/java/nextstep/qna/service/repository/AnswerRepository.java index bb894f84c..579c9cc3d 100644 --- a/src/main/java/nextstep/qna/domain/AnswerRepository.java +++ b/src/main/java/nextstep/qna/service/repository/AnswerRepository.java @@ -1,6 +1,7 @@ -package nextstep.qna.domain; +package nextstep.qna.service.repository; import java.util.List; +import nextstep.qna.domain.Answer; public interface AnswerRepository { List findByQuestion(Long questionId); diff --git a/src/main/java/nextstep/qna/domain/DeleteHistoryRepository.java b/src/main/java/nextstep/qna/service/repository/DeleteHistoryRepository.java similarity index 60% rename from src/main/java/nextstep/qna/domain/DeleteHistoryRepository.java rename to src/main/java/nextstep/qna/service/repository/DeleteHistoryRepository.java index e8671add7..968791209 100644 --- a/src/main/java/nextstep/qna/domain/DeleteHistoryRepository.java +++ b/src/main/java/nextstep/qna/service/repository/DeleteHistoryRepository.java @@ -1,6 +1,7 @@ -package nextstep.qna.domain; +package nextstep.qna.service.repository; import java.util.List; +import nextstep.qna.domain.DeleteHistory; public interface DeleteHistoryRepository { diff --git a/src/main/java/nextstep/qna/domain/QuestionRepository.java b/src/main/java/nextstep/qna/service/repository/QuestionRepository.java similarity index 58% rename from src/main/java/nextstep/qna/domain/QuestionRepository.java rename to src/main/java/nextstep/qna/service/repository/QuestionRepository.java index ec354bb3f..ba343af71 100644 --- a/src/main/java/nextstep/qna/domain/QuestionRepository.java +++ b/src/main/java/nextstep/qna/service/repository/QuestionRepository.java @@ -1,6 +1,7 @@ -package nextstep.qna.domain; +package nextstep.qna.service.repository; import java.util.Optional; +import nextstep.qna.domain.Question; public interface QuestionRepository { Optional findById(Long id); diff --git a/src/test/java/nextstep/qna/domain/AnswerTest.java b/src/test/java/nextstep/qna/domain/AnswerTest.java index 8e80ffb42..a6dfc2d40 100644 --- a/src/test/java/nextstep/qna/domain/AnswerTest.java +++ b/src/test/java/nextstep/qna/domain/AnswerTest.java @@ -1,8 +1,21 @@ package nextstep.qna.domain; +import static org.assertj.core.api.Assertions.assertThat; + import nextstep.users.domain.NsUserTest; +import org.junit.jupiter.api.Test; public class AnswerTest { public static final Answer A1 = new Answer(NsUserTest.JAVAJIGI, QuestionTest.Q1, "Answers Contents1"); public static final Answer A2 = new Answer(NsUserTest.SANJIGI, QuestionTest.Q1, "Answers Contents2"); + + + @Test + void 요청가자_답변작성자와_동일한지_확인할_수_있다() { + long requesterId = 1L; + Question question = new Question(1L, "title1", "contents1"); + Answer answer = new Answer(requesterId, question, "Answers Contents1"); + + assertThat(answer.isOwner(requesterId)).isTrue(); + } } diff --git a/src/test/java/nextstep/qna/domain/QuestionTest.java b/src/test/java/nextstep/qna/domain/QuestionTest.java index 3b8782396..521dc31aa 100644 --- a/src/test/java/nextstep/qna/domain/QuestionTest.java +++ b/src/test/java/nextstep/qna/domain/QuestionTest.java @@ -1,8 +1,80 @@ package nextstep.qna.domain; +import static org.assertj.core.api.Assertions.assertThat; + import nextstep.users.domain.NsUserTest; +import org.junit.jupiter.api.Test; public class QuestionTest { public static final Question Q1 = new Question(NsUserTest.JAVAJIGI, "title1", "contents1"); public static final Question Q2 = new Question(NsUserTest.SANJIGI, "title2", "contents2"); + + + @Test + void 요청자가_질문자와_동일한지_확인할_수_있다() { + long requesterId = 1L; + Question question = new Question(1L, "title1", "contents1"); + + assertThat(question.isOwner(requesterId)).isTrue(); + } + + @Test + void 질문에_답변이_있는지_확인할_수_있다() { + Question question = new Question(1L, "title1", "contents1"); + question.addAnswer( + new Answer(NsUserTest.JAVAJIGI, question, "Answers Contents1")); + + assertThat(question.hasAnswers()).isTrue(); + } + + @Test + void 질문에_답변이_없는지_확인할_수_있다() { + Question question = new Question(1L, "title1", "contents1"); + + assertThat(question.hasAnswers()).isFalse(); + } + + @Test + void 질문의_답변들이_질문자가_작성한_답변만_존재하는지_확인할_수_있다() { + Question question = new Question(1L, "title1", "contents1"); + question.addAnswer(new Answer(1L, question, "Answers Contents1")); + question.addAnswer(new Answer(1L, question, "Answers Contents2")); + + assertThat(question.isAllSameContentsWriter()).isTrue(); + } + + @Test + void 질문의_답변들이_여러사람이_작성한_답변이_존재하는지_확인할_수_있다() { + Question question = new Question(1L, "title1", "contents1"); + question.addAnswer(new Answer(1L, question, "Answers Contents1")); + question.addAnswer(new Answer(2L, question, "Answers Contents2")); + + assertThat(question.isAllSameContentsWriter()).isFalse(); + } + + @Test + void 질문을_삭제상태로_변경할_수_있다() { + long requesterId = 1L; + Question question = new Question(1L, "title1", "contents1"); + + question.putOnDelete(requesterId); + + assertThat(question.isDeleted()).isTrue(); + } + + @Test + void 질문의_모든답변을_삭제상태로_변경할_수_있다() { + long requesterId = 1L; + Question question = new Question(1L, "title1", "contents1"); + Answer firstAnswers = new Answer(1L, question, "Answers Contents1"); + question.addAnswer(firstAnswers); + Answer secondAnswers = new Answer(1L, question, "Answers Contents2"); + question.addAnswer(secondAnswers); + + question.putOnAllAnswersDelete(requesterId); + + assertThat(firstAnswers.isDeleted()).isTrue(); + assertThat(secondAnswers.isDeleted()).isTrue(); + } + } diff --git a/src/test/java/nextstep/qna/service/QnaServiceTest.java b/src/test/java/nextstep/qna/service/QnaServiceTest.java index e1e943c23..89c010725 100644 --- a/src/test/java/nextstep/qna/service/QnaServiceTest.java +++ b/src/test/java/nextstep/qna/service/QnaServiceTest.java @@ -2,6 +2,7 @@ import nextstep.qna.CannotDeleteException; import nextstep.qna.domain.*; +import nextstep.qna.service.repository.QuestionRepository; import nextstep.users.domain.NsUserTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; From f71bb0d06ec93ddf15645f0f2e87b91a809bb68a Mon Sep 17 00:00:00 2001 From: mins1031 Date: Wed, 10 Dec 2025 15:02:39 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat=20:=20=EB=A0=88=EA=B1=B0=EC=8B=9C=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81=20*?= =?UTF-8?q?=20=EC=98=88=EC=99=B8=EA=B0=9D=EC=B2=B4=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=95=20*=20=EC=82=AD=EC=A0=9C=EC=9D=B4=EB=A0=A5=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=ED=96=89=EC=9C=84=20=EC=A7=88=EB=AC=B8,?= =?UTF-8?q?=EB=8B=B5=EB=B3=80=20=EA=B0=9D=EC=B2=B4=EC=97=90=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20*=20QnADomainService=20=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=EC=A1=B0?= =?UTF-8?q?=ED=95=A9=20*=20QnAService=20deleteQuestion=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/nextstep/qna/domain/Answer.java | 21 ++++-- .../nextstep/qna/domain/DeleteHistory.java | 8 +++ .../nextstep/qna/domain/QnADomainService.java | 12 ++++ .../java/nextstep/qna/domain/Question.java | 39 ++++++++--- .../checked}/UnAuthenticationException.java | 2 +- .../unchecked}/CannotDeleteException.java | 4 +- .../unchecked}/ForbiddenException.java | 2 +- .../unchecked}/NotFoundException.java | 2 +- .../unchecked}/UnAuthorizedException.java | 2 +- .../unchecked/WrongRequestException.java | 9 +++ .../java/nextstep/qna/service/QnAService.java | 67 ++++--------------- .../java/nextstep/users/domain/NsUser.java | 2 +- .../java/nextstep/qna/domain/AnswerTest.java | 25 ++++++- .../nextstep/qna/domain/QuestionTest.java | 54 +++++++++++++-- .../nextstep/qna/service/QnaServiceTest.java | 57 ++++++++-------- 15 files changed, 196 insertions(+), 110 deletions(-) create mode 100644 src/main/java/nextstep/qna/domain/QnADomainService.java rename src/main/java/nextstep/qna/{ => exception/checked}/UnAuthenticationException.java (94%) rename src/main/java/nextstep/qna/{ => exception/unchecked}/CannotDeleteException.java (57%) rename src/main/java/nextstep/qna/{ => exception/unchecked}/ForbiddenException.java (81%) rename src/main/java/nextstep/qna/{ => exception/unchecked}/NotFoundException.java (59%) rename src/main/java/nextstep/qna/{ => exception/unchecked}/UnAuthorizedException.java (94%) create mode 100644 src/main/java/nextstep/qna/exception/unchecked/WrongRequestException.java diff --git a/src/main/java/nextstep/qna/domain/Answer.java b/src/main/java/nextstep/qna/domain/Answer.java index bbb409b78..77154b5ec 100644 --- a/src/main/java/nextstep/qna/domain/Answer.java +++ b/src/main/java/nextstep/qna/domain/Answer.java @@ -1,8 +1,9 @@ package nextstep.qna.domain; -import nextstep.qna.CannotDeleteException; -import nextstep.qna.NotFoundException; -import nextstep.qna.UnAuthorizedException; +import nextstep.qna.exception.unchecked.CannotDeleteException; +import nextstep.qna.exception.unchecked.NotFoundException; +import nextstep.qna.exception.unchecked.UnAuthorizedException; +import nextstep.qna.exception.unchecked.WrongRequestException; import nextstep.users.domain.NsUser; import java.time.LocalDateTime; @@ -69,9 +70,9 @@ public boolean isDeleted() { } public void putOnDelete(long requesterId) { -// if (!isOwner(requesterId)) { -// throw new CannotDeleteException("질문을 삭제할 권한이 없습니다."); -// } + if (!isOwner(requesterId)) { + throw new CannotDeleteException("질문을 삭제할 권한이 없습니다."); + } this.deleted = true; } @@ -84,6 +85,14 @@ public boolean isOwner(long writerId) { return this.writerId == writerId; } + public DeleteHistory createAnswerDeleteHistory() { + if (!deleted) { + throw new WrongRequestException("삭제되지 않은 답변은 삭제이력을 생성할 수 없습니다."); + } + + return new DeleteHistory(ContentType.ANSWER, this.id, this.writerId, LocalDateTime.now()); + } + public Long getId() { return id; } diff --git a/src/main/java/nextstep/qna/domain/DeleteHistory.java b/src/main/java/nextstep/qna/domain/DeleteHistory.java index 43c37e5e5..658432af5 100644 --- a/src/main/java/nextstep/qna/domain/DeleteHistory.java +++ b/src/main/java/nextstep/qna/domain/DeleteHistory.java @@ -13,6 +13,7 @@ public class DeleteHistory { private Long contentId; private NsUser deletedBy; + private Long deletedId; private LocalDateTime createdDate = LocalDateTime.now(); @@ -26,6 +27,13 @@ public DeleteHistory(ContentType contentType, Long contentId, NsUser deletedBy, this.createdDate = createdDate; } + public DeleteHistory(ContentType contentType, Long contentId, Long deletedId, LocalDateTime createdDate) { + this.contentType = contentType; + this.contentId = contentId; + this.deletedId = deletedId; + this.createdDate = createdDate; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/nextstep/qna/domain/QnADomainService.java b/src/main/java/nextstep/qna/domain/QnADomainService.java new file mode 100644 index 000000000..ddc398cb7 --- /dev/null +++ b/src/main/java/nextstep/qna/domain/QnADomainService.java @@ -0,0 +1,12 @@ +package nextstep.qna.domain; + +import java.util.List; + +public class QnADomainService { + + public List deleteQuestion(long requesterId, Question question) { + question.putOnDelete(requesterId); + + return question.bringAllDeleteHistories(); + } +} diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index 89742d784..1b758f580 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -3,7 +3,8 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; -import nextstep.qna.CannotDeleteException; +import nextstep.qna.exception.unchecked.CannotDeleteException; +import nextstep.qna.exception.unchecked.WrongRequestException; import nextstep.users.domain.NsUser; public class Question { @@ -81,22 +82,42 @@ public void putOnDelete(long requesterId) { throw new IllegalArgumentException("잘못된 요청자 정보 입니다."); } -// if (!isOwner(requesterId)) { -// throw new CannotDeleteException("질문을 삭제할 권한이 없습니다."); -// } -// -// if (hasAnswers() || !isAllSameContentsWriter()) { -// throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); -// } + if (!isOwner(requesterId)) { + throw new CannotDeleteException("질문을 삭제할 권한이 없습니다."); + } + + if (hasAnswers() && !isAllSameContentsWriter()) { + throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); + } this.deleted = true; -// putOnAllAnswersDelete(requesterId); + putOnAllAnswersDelete(requesterId); } public void putOnAllAnswersDelete(long requesterId) { this.answers.forEach(answer -> answer.putOnDelete(requesterId)); } + public DeleteHistory createQuestionDeleteHistory() { + if (!deleted) { + throw new WrongRequestException("삭제되지 않은 질문은 삭제이력을 생성할 수 없습니다."); + } + + return new DeleteHistory(ContentType.QUESTION, this.id, this.writerId, LocalDateTime.now()); + } + + public List bringAllDeleteHistories() { + List deleteHistories = new ArrayList<>(); + deleteHistories.add(createQuestionDeleteHistory()); + + for (Answer answer : answers) { + deleteHistories.add(answer.createAnswerDeleteHistory()); + } + + return deleteHistories; + } + + public Long getId() { return id; } diff --git a/src/main/java/nextstep/qna/UnAuthenticationException.java b/src/main/java/nextstep/qna/exception/checked/UnAuthenticationException.java similarity index 94% rename from src/main/java/nextstep/qna/UnAuthenticationException.java rename to src/main/java/nextstep/qna/exception/checked/UnAuthenticationException.java index bff057a2a..4019ba86f 100644 --- a/src/main/java/nextstep/qna/UnAuthenticationException.java +++ b/src/main/java/nextstep/qna/exception/checked/UnAuthenticationException.java @@ -1,4 +1,4 @@ -package nextstep.qna; +package nextstep.qna.exception.checked; public class UnAuthenticationException extends Exception { private static final long serialVersionUID = 1L; diff --git a/src/main/java/nextstep/qna/CannotDeleteException.java b/src/main/java/nextstep/qna/exception/unchecked/CannotDeleteException.java similarity index 57% rename from src/main/java/nextstep/qna/CannotDeleteException.java rename to src/main/java/nextstep/qna/exception/unchecked/CannotDeleteException.java index a8d9d2832..13b83292b 100644 --- a/src/main/java/nextstep/qna/CannotDeleteException.java +++ b/src/main/java/nextstep/qna/exception/unchecked/CannotDeleteException.java @@ -1,6 +1,6 @@ -package nextstep.qna; +package nextstep.qna.exception.unchecked; -public class CannotDeleteException extends Exception { +public class CannotDeleteException extends RuntimeException { private static final long serialVersionUID = 1L; public CannotDeleteException(String message) { diff --git a/src/main/java/nextstep/qna/ForbiddenException.java b/src/main/java/nextstep/qna/exception/unchecked/ForbiddenException.java similarity index 81% rename from src/main/java/nextstep/qna/ForbiddenException.java rename to src/main/java/nextstep/qna/exception/unchecked/ForbiddenException.java index 4e858b50d..3218a83c3 100644 --- a/src/main/java/nextstep/qna/ForbiddenException.java +++ b/src/main/java/nextstep/qna/exception/unchecked/ForbiddenException.java @@ -1,4 +1,4 @@ -package nextstep.qna; +package nextstep.qna.exception.unchecked; public class ForbiddenException extends RuntimeException{ public ForbiddenException() { diff --git a/src/main/java/nextstep/qna/NotFoundException.java b/src/main/java/nextstep/qna/exception/unchecked/NotFoundException.java similarity index 59% rename from src/main/java/nextstep/qna/NotFoundException.java rename to src/main/java/nextstep/qna/exception/unchecked/NotFoundException.java index 5ac272b8b..e23899d9f 100644 --- a/src/main/java/nextstep/qna/NotFoundException.java +++ b/src/main/java/nextstep/qna/exception/unchecked/NotFoundException.java @@ -1,4 +1,4 @@ -package nextstep.qna; +package nextstep.qna.exception.unchecked; public class NotFoundException extends RuntimeException { } diff --git a/src/main/java/nextstep/qna/UnAuthorizedException.java b/src/main/java/nextstep/qna/exception/unchecked/UnAuthorizedException.java similarity index 94% rename from src/main/java/nextstep/qna/UnAuthorizedException.java rename to src/main/java/nextstep/qna/exception/unchecked/UnAuthorizedException.java index 1b8fb2c14..f2c92aabe 100644 --- a/src/main/java/nextstep/qna/UnAuthorizedException.java +++ b/src/main/java/nextstep/qna/exception/unchecked/UnAuthorizedException.java @@ -1,4 +1,4 @@ -package nextstep.qna; +package nextstep.qna.exception.unchecked; public class UnAuthorizedException extends RuntimeException { private static final long serialVersionUID = 1L; diff --git a/src/main/java/nextstep/qna/exception/unchecked/WrongRequestException.java b/src/main/java/nextstep/qna/exception/unchecked/WrongRequestException.java new file mode 100644 index 000000000..1708571a8 --- /dev/null +++ b/src/main/java/nextstep/qna/exception/unchecked/WrongRequestException.java @@ -0,0 +1,9 @@ +package nextstep.qna.exception.unchecked; + +public class WrongRequestException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public WrongRequestException(String message) { + super(message); + } +} diff --git a/src/main/java/nextstep/qna/service/QnAService.java b/src/main/java/nextstep/qna/service/QnAService.java index b5208660e..c5b8f9b8b 100644 --- a/src/main/java/nextstep/qna/service/QnAService.java +++ b/src/main/java/nextstep/qna/service/QnAService.java @@ -1,75 +1,32 @@ package nextstep.qna.service; -import nextstep.qna.CannotDeleteException; -import nextstep.qna.NotFoundException; -import nextstep.qna.domain.*; -import nextstep.qna.service.repository.AnswerRepository; +import java.util.List; +import javax.annotation.Resource; +import nextstep.qna.domain.DeleteHistory; +import nextstep.qna.domain.QnADomainService; +import nextstep.qna.domain.Question; +import nextstep.qna.exception.unchecked.NotFoundException; import nextstep.qna.service.repository.QuestionRepository; -import nextstep.users.domain.NsUser; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import javax.annotation.Resource; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - @Service("qnaService") public class QnAService { + @Resource(name = "questionRepository") private QuestionRepository questionRepository; - @Resource(name = "answerRepository") - private AnswerRepository answerRepository; - @Resource(name = "deleteHistoryService") private DeleteHistoryService deleteHistoryService; @Transactional - public void deleteQuestion(NsUser loginUser, long questionId) throws CannotDeleteException { - Question question = questionRepository.findById(questionId).orElseThrow(NotFoundException::new); - if (!question.isOwner(loginUser)) { - throw new CannotDeleteException("질문을 삭제할 권한이 없습니다."); - } - - List answers = question.getAnswers(); - for (Answer answer : answers) { - if (!answer.isOwner(loginUser)) { - throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); - } - } - - List deleteHistories = new ArrayList<>(); - question.setDeleted(true); - deleteHistories.add(new DeleteHistory(ContentType.QUESTION, questionId, question.getWriter(), LocalDateTime.now())); - for (Answer answer : answers) { - answer.setDeleted(true); - deleteHistories.add(new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now())); - } - deleteHistoryService.saveAll(deleteHistories); - } - - @Transactional - public void deleteQuestion2(long requesterId, long questionId) throws CannotDeleteException { - Question question = questionRepository.findById(questionId).orElseThrow(NotFoundException::new); - if (!question.isOwner(requesterId)) { - throw new CannotDeleteException("질문을 삭제할 권한이 없습니다."); - } + public void deleteQuestion(long requesterId, long questionId) { + Question question = questionRepository.findById(questionId) + .orElseThrow(NotFoundException::new); - List answers = question.getAnswers(); - for (Answer answer : answers) { -// if (!answer.isOwner2(requesterId)) { -// throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); -// } - } + List deleteHistories = new QnADomainService() + .deleteQuestion(requesterId, question); - List deleteHistories = new ArrayList<>(); - question.setDeleted(true); - deleteHistories.add(new DeleteHistory(ContentType.QUESTION, questionId, question.getWriter(), LocalDateTime.now())); - for (Answer answer : answers) { - answer.setDeleted(true); - deleteHistories.add(new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now())); - } deleteHistoryService.saveAll(deleteHistories); } } diff --git a/src/main/java/nextstep/users/domain/NsUser.java b/src/main/java/nextstep/users/domain/NsUser.java index 62ec5138c..772b57cad 100755 --- a/src/main/java/nextstep/users/domain/NsUser.java +++ b/src/main/java/nextstep/users/domain/NsUser.java @@ -1,6 +1,6 @@ package nextstep.users.domain; -import nextstep.qna.UnAuthorizedException; +import nextstep.qna.exception.unchecked.UnAuthorizedException; import java.time.LocalDateTime; import java.util.Objects; diff --git a/src/test/java/nextstep/qna/domain/AnswerTest.java b/src/test/java/nextstep/qna/domain/AnswerTest.java index a6dfc2d40..aaebd5c19 100644 --- a/src/test/java/nextstep/qna/domain/AnswerTest.java +++ b/src/test/java/nextstep/qna/domain/AnswerTest.java @@ -1,7 +1,9 @@ package nextstep.qna.domain; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import nextstep.qna.exception.unchecked.WrongRequestException; import nextstep.users.domain.NsUserTest; import org.junit.jupiter.api.Test; @@ -11,11 +13,32 @@ public class AnswerTest { @Test - void 요청가자_답변작성자와_동일한지_확인할_수_있다() { + void 요청자가_답변작성자와_동일한지_확인할_수_있다() { long requesterId = 1L; Question question = new Question(1L, "title1", "contents1"); Answer answer = new Answer(requesterId, question, "Answers Contents1"); assertThat(answer.isOwner(requesterId)).isTrue(); } + + @Test + void 삭제된_답변객체는_삭제이력을_만들수_있다() { + Question question = new Question(1L, "title1", "contents1"); + Answer answer = new Answer(1L, question, "Answers Contents1"); + answer.putOnDelete(1L); + + assertThat(answer.createAnswerDeleteHistory()).isNotNull(); + } + + @Test + void 삭제되지_않은_질문객체를_삭제이력_객체로_만들수_없다() { + Question question = new Question(1L, "title1", "contents1"); + Answer answer = new Answer(1L, question, "Answers Contents1"); + + assertThatThrownBy( + answer::createAnswerDeleteHistory + ).isInstanceOf(WrongRequestException.class); + } + + } diff --git a/src/test/java/nextstep/qna/domain/QuestionTest.java b/src/test/java/nextstep/qna/domain/QuestionTest.java index 521dc31aa..fa334e6b5 100644 --- a/src/test/java/nextstep/qna/domain/QuestionTest.java +++ b/src/test/java/nextstep/qna/domain/QuestionTest.java @@ -1,7 +1,10 @@ package nextstep.qna.domain; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import nextstep.qna.exception.unchecked.CannotDeleteException; +import nextstep.qna.exception.unchecked.WrongRequestException; import nextstep.users.domain.NsUserTest; import org.junit.jupiter.api.Test; @@ -54,27 +57,70 @@ public class QuestionTest { @Test void 질문을_삭제상태로_변경할_수_있다() { - long requesterId = 1L; Question question = new Question(1L, "title1", "contents1"); - question.putOnDelete(requesterId); + question.putOnDelete(1L); assertThat(question.isDeleted()).isTrue(); } + @Test + void 비정상적인_요청자번호로_질문을_삭제시도할_수_없다() { + Question question = new Question(1L, "title1", "contents1"); + + assertThatThrownBy( + () -> question.putOnDelete(-1L) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 요청자와_질문자가_다르면_질문을_삭제시도할_수_없다() { + Question question = new Question(1L, "title1", "contents1"); + + assertThatThrownBy( + () -> question.putOnDelete(2L) + ).isInstanceOf(CannotDeleteException.class); + } + + @Test + void 답변중_질문자와_다른유저의_답변이_있다면_질문을_삭제시도할_수_없다() { + Question question = new Question(1L, "title1", "contents1"); + question.addAnswer(new Answer(1L, question, "Answers Contents1")); + question.addAnswer(new Answer(2L, question, "Answers Contents2")); + + assertThatThrownBy( + () -> question.putOnDelete(1L) + ).isInstanceOf(CannotDeleteException.class); + } + @Test void 질문의_모든답변을_삭제상태로_변경할_수_있다() { - long requesterId = 1L; Question question = new Question(1L, "title1", "contents1"); Answer firstAnswers = new Answer(1L, question, "Answers Contents1"); question.addAnswer(firstAnswers); Answer secondAnswers = new Answer(1L, question, "Answers Contents2"); question.addAnswer(secondAnswers); - question.putOnAllAnswersDelete(requesterId); + question.putOnAllAnswersDelete(1L); assertThat(firstAnswers.isDeleted()).isTrue(); assertThat(secondAnswers.isDeleted()).isTrue(); } + @Test + void 삭제된_질문객체는_삭제이력을_만들수_있다() { + Question question = new Question(1L, "title1", "contents1"); + question.putOnDelete(1L); + + assertThat(question.createQuestionDeleteHistory()).isNotNull(); + } + + @Test + void 삭제되지_않은_질문객체를_삭제이력_객체로_만들수_없다() {; + Question question = new Question(1L, "title1", "contents1"); + + assertThatThrownBy( + question::createQuestionDeleteHistory + ).isInstanceOf(WrongRequestException.class); + } } diff --git a/src/test/java/nextstep/qna/service/QnaServiceTest.java b/src/test/java/nextstep/qna/service/QnaServiceTest.java index 89c010725..af4295253 100644 --- a/src/test/java/nextstep/qna/service/QnaServiceTest.java +++ b/src/test/java/nextstep/qna/service/QnaServiceTest.java @@ -1,9 +1,20 @@ package nextstep.qna.service; -import nextstep.qna.CannotDeleteException; -import nextstep.qna.domain.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import nextstep.qna.domain.Answer; +import nextstep.qna.domain.ContentType; +import nextstep.qna.domain.DeleteHistory; +import nextstep.qna.domain.Question; +import nextstep.qna.exception.unchecked.CannotDeleteException; import nextstep.qna.service.repository.QuestionRepository; -import nextstep.users.domain.NsUserTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -11,16 +22,6 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - @ExtendWith(MockitoExtension.class) public class QnaServiceTest { @Mock @@ -36,37 +37,37 @@ public class QnaServiceTest { private Answer answer; @BeforeEach - public void setUp() throws Exception { - question = new Question(1L, NsUserTest.JAVAJIGI, "title1", "contents1"); - answer = new Answer(11L, NsUserTest.JAVAJIGI, QuestionTest.Q1, "Answers Contents1"); + public void setUp() { + question = new Question(1L, 1L, "title1", "contents1"); + answer = new Answer(11L, 1L, question, "Answers Contents1"); question.addAnswer(answer); } @Test - public void delete_성공() throws Exception { + public void delete_성공() { when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); assertThat(question.isDeleted()).isFalse(); - qnAService.deleteQuestion(NsUserTest.JAVAJIGI, question.getId()); + qnAService.deleteQuestion(1L, 1L); assertThat(question.isDeleted()).isTrue(); verifyDeleteHistories(); } @Test - public void delete_다른_사람이_쓴_글() throws Exception { + public void delete_다른_사람이_쓴_글() { when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); - assertThatThrownBy(() -> { - qnAService.deleteQuestion(NsUserTest.SANJIGI, question.getId()); - }).isInstanceOf(CannotDeleteException.class); + assertThatThrownBy(() -> + qnAService.deleteQuestion(2L, question.getId()) + ).isInstanceOf(CannotDeleteException.class); } @Test - public void delete_성공_질문자_답변자_같음() throws Exception { + public void delete_성공_질문자_답변자_같음() { when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); - qnAService.deleteQuestion(NsUserTest.JAVAJIGI, question.getId()); + qnAService.deleteQuestion(1L, question.getId()); assertThat(question.isDeleted()).isTrue(); assertThat(answer.isDeleted()).isTrue(); @@ -74,12 +75,12 @@ public void setUp() throws Exception { } @Test - public void delete_답변_중_다른_사람이_쓴_글() throws Exception { + public void delete_답변_중_다른_사람이_쓴_글() { when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); - assertThatThrownBy(() -> { - qnAService.deleteQuestion(NsUserTest.SANJIGI, question.getId()); - }).isInstanceOf(CannotDeleteException.class); + assertThatThrownBy(() -> + qnAService.deleteQuestion(2L, question.getId()) + ).isInstanceOf(CannotDeleteException.class); } private void verifyDeleteHistories() { From 6c91f4059dfdc1bf229788fe20190271c34e33ff Mon Sep 17 00:00:00 2001 From: mins1031 Date: Wed, 10 Dec 2025 15:55:51 +0900 Subject: [PATCH 03/10] =?UTF-8?q?refactor=20:=20=EB=A0=88=EA=B1=B0?= =?UTF-8?q?=EC=8B=9C=20=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20*=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20get/set=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20*=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/nextstep/qna/domain/Answer.java | 41 +------------------ .../nextstep/qna/domain/DeleteHistory.java | 14 ++----- .../java/nextstep/qna/domain/Question.java | 38 +---------------- .../java/nextstep/qna/domain/AnswerTest.java | 3 -- .../nextstep/qna/domain/QuestionTest.java | 9 ++-- .../nextstep/qna/service/QnaServiceTest.java | 18 ++++---- 6 files changed, 19 insertions(+), 104 deletions(-) diff --git a/src/main/java/nextstep/qna/domain/Answer.java b/src/main/java/nextstep/qna/domain/Answer.java index 77154b5ec..99b4d5ed0 100644 --- a/src/main/java/nextstep/qna/domain/Answer.java +++ b/src/main/java/nextstep/qna/domain/Answer.java @@ -11,7 +11,6 @@ public class Answer { private Long id; - private NsUser writer; private Long writerId; private Question question; @@ -27,29 +26,10 @@ public class Answer { public Answer() { } - public Answer(NsUser writer, Question question, String contents) { - this(null, writer, question, contents); - } - public Answer(long writerId, Question question, String contents) { this(null, writerId, question, contents); } - public Answer(Long id, NsUser writer, Question question, String contents) { - this.id = id; - if(writer == null) { - throw new UnAuthorizedException(); - } - - if(question == null) { - throw new NotFoundException(); - } - - this.writer = writer; - this.question = question; - this.contents = contents; - } - public Answer(Long id, long writerId, Question question, String contents) { this.id = id; if(writerId <= 0L) { @@ -77,41 +57,24 @@ public void putOnDelete(long requesterId) { this.deleted = true; } - public boolean isOwner(NsUser writer) { - return this.writer.equals(writer); - } - public boolean isOwner(long writerId) { return this.writerId == writerId; } public DeleteHistory createAnswerDeleteHistory() { - if (!deleted) { + if (!isDeleted()) { throw new WrongRequestException("삭제되지 않은 답변은 삭제이력을 생성할 수 없습니다."); } return new DeleteHistory(ContentType.ANSWER, this.id, this.writerId, LocalDateTime.now()); } - public Long getId() { - return id; - } - - public Answer setDeleted(boolean deleted) { - this.deleted = deleted; - return this; - } - - public NsUser getWriter() { - return writer; - } - public void toQuestion(Question question) { this.question = question; } @Override public String toString() { - return "Answer [id=" + getId() + ", writer=" + writer + ", contents=" + contents + "]"; + return "Answer [id=" + id + ", writerId=" + writerId + ", contents=" + contents + "]"; } } diff --git a/src/main/java/nextstep/qna/domain/DeleteHistory.java b/src/main/java/nextstep/qna/domain/DeleteHistory.java index 658432af5..38a964689 100644 --- a/src/main/java/nextstep/qna/domain/DeleteHistory.java +++ b/src/main/java/nextstep/qna/domain/DeleteHistory.java @@ -12,7 +12,6 @@ public class DeleteHistory { private Long contentId; - private NsUser deletedBy; private Long deletedId; private LocalDateTime createdDate = LocalDateTime.now(); @@ -20,13 +19,6 @@ public class DeleteHistory { public DeleteHistory() { } - public DeleteHistory(ContentType contentType, Long contentId, NsUser deletedBy, LocalDateTime createdDate) { - this.contentType = contentType; - this.contentId = contentId; - this.deletedBy = deletedBy; - this.createdDate = createdDate; - } - public DeleteHistory(ContentType contentType, Long contentId, Long deletedId, LocalDateTime createdDate) { this.contentType = contentType; this.contentId = contentId; @@ -42,17 +34,17 @@ public boolean equals(Object o) { return Objects.equals(id, that.id) && contentType == that.contentType && Objects.equals(contentId, that.contentId) && - Objects.equals(deletedBy, that.deletedBy); + Objects.equals(deletedId, that.deletedId); } @Override public int hashCode() { - return Objects.hash(id, contentType, contentId, deletedBy); + return Objects.hash(id, contentType, contentId, deletedId); } @Override public String toString() { return "DeleteHistory [id=" + id + ", contentType=" + contentType + ", contentId=" + contentId + ", deletedBy=" - + deletedBy + ", createdDate=" + createdDate + "]"; + + deletedId + ", createdDate=" + createdDate + "]"; } } diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index 1b758f580..0b4102073 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -15,7 +15,6 @@ public class Question { private String contents; - private NsUser writer; private Long writerId; private List answers = new ArrayList<>(); @@ -29,21 +28,10 @@ public class Question { public Question() { } - public Question(NsUser writer, String title, String contents) { - this(0L, writer, title, contents); - } - public Question(long writerId, String title, String contents) { this(0L, writerId, title, contents); } - public Question(Long id, NsUser writer, String title, String contents) { - this.id = id; - this.writer = writer; - this.title = title; - this.contents = contents; - } - public Question(Long id, long writerId, String title, String contents) { this.id = id; this.writerId = writerId; @@ -51,10 +39,6 @@ public Question(Long id, long writerId, String title, String contents) { this.contents = contents; } - public boolean isOwner(NsUser loginUser) { - return writer.equals(loginUser); - } - public boolean isOwner(long requesterId) { return writerId == requesterId; } @@ -117,27 +101,9 @@ public List bringAllDeleteHistories() { return deleteHistories; } - - public Long getId() { - return id; - } - - public NsUser getWriter() { - return writer; - } - - public List getAnswers() { - return answers; - } - - public Question setDeleted(boolean deleted) { - this.deleted = deleted; - return this; - } - @Override public String toString() { - return "Question [id=" + getId() + ", title=" + title + ", contents=" + contents - + ", writer=" + writer + "]"; + return "Question [id=" + id + ", title=" + title + ", contents=" + contents + + ", writerId=" + writerId + "]"; } } diff --git a/src/test/java/nextstep/qna/domain/AnswerTest.java b/src/test/java/nextstep/qna/domain/AnswerTest.java index aaebd5c19..b7095709a 100644 --- a/src/test/java/nextstep/qna/domain/AnswerTest.java +++ b/src/test/java/nextstep/qna/domain/AnswerTest.java @@ -8,9 +8,6 @@ import org.junit.jupiter.api.Test; public class AnswerTest { - public static final Answer A1 = new Answer(NsUserTest.JAVAJIGI, QuestionTest.Q1, "Answers Contents1"); - public static final Answer A2 = new Answer(NsUserTest.SANJIGI, QuestionTest.Q1, "Answers Contents2"); - @Test void 요청자가_답변작성자와_동일한지_확인할_수_있다() { diff --git a/src/test/java/nextstep/qna/domain/QuestionTest.java b/src/test/java/nextstep/qna/domain/QuestionTest.java index fa334e6b5..5f54fe15e 100644 --- a/src/test/java/nextstep/qna/domain/QuestionTest.java +++ b/src/test/java/nextstep/qna/domain/QuestionTest.java @@ -5,13 +5,9 @@ import nextstep.qna.exception.unchecked.CannotDeleteException; import nextstep.qna.exception.unchecked.WrongRequestException; -import nextstep.users.domain.NsUserTest; import org.junit.jupiter.api.Test; public class QuestionTest { - public static final Question Q1 = new Question(NsUserTest.JAVAJIGI, "title1", "contents1"); - public static final Question Q2 = new Question(NsUserTest.SANJIGI, "title2", "contents2"); - @Test void 요청자가_질문자와_동일한지_확인할_수_있다() { @@ -25,7 +21,7 @@ public class QuestionTest { void 질문에_답변이_있는지_확인할_수_있다() { Question question = new Question(1L, "title1", "contents1"); question.addAnswer( - new Answer(NsUserTest.JAVAJIGI, question, "Answers Contents1")); + new Answer(1L, question, "Answers Contents1")); assertThat(question.hasAnswers()).isTrue(); } @@ -116,7 +112,8 @@ public class QuestionTest { } @Test - void 삭제되지_않은_질문객체를_삭제이력_객체로_만들수_없다() {; + void 삭제되지_않은_질문객체를_삭제이력_객체로_만들수_없다() { + ; Question question = new Question(1L, "title1", "contents1"); assertThatThrownBy( diff --git a/src/test/java/nextstep/qna/service/QnaServiceTest.java b/src/test/java/nextstep/qna/service/QnaServiceTest.java index af4295253..730ad0098 100644 --- a/src/test/java/nextstep/qna/service/QnaServiceTest.java +++ b/src/test/java/nextstep/qna/service/QnaServiceTest.java @@ -45,7 +45,7 @@ public void setUp() { @Test public void delete_성공() { - when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); + when(questionRepository.findById(1L)).thenReturn(Optional.of(question)); assertThat(question.isDeleted()).isFalse(); qnAService.deleteQuestion(1L, 1L); @@ -56,18 +56,18 @@ public void setUp() { @Test public void delete_다른_사람이_쓴_글() { - when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); + when(questionRepository.findById(1L)).thenReturn(Optional.of(question)); assertThatThrownBy(() -> - qnAService.deleteQuestion(2L, question.getId()) + qnAService.deleteQuestion(2L, 1L) ).isInstanceOf(CannotDeleteException.class); } @Test public void delete_성공_질문자_답변자_같음() { - when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); + when(questionRepository.findById(1L)).thenReturn(Optional.of(question)); - qnAService.deleteQuestion(1L, question.getId()); + qnAService.deleteQuestion(1L, 1L); assertThat(question.isDeleted()).isTrue(); assertThat(answer.isDeleted()).isTrue(); @@ -76,17 +76,17 @@ public void setUp() { @Test public void delete_답변_중_다른_사람이_쓴_글() { - when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); + when(questionRepository.findById(1L)).thenReturn(Optional.of(question)); assertThatThrownBy(() -> - qnAService.deleteQuestion(2L, question.getId()) + qnAService.deleteQuestion(2L, 1L) ).isInstanceOf(CannotDeleteException.class); } private void verifyDeleteHistories() { List deleteHistories = Arrays.asList( - new DeleteHistory(ContentType.QUESTION, question.getId(), question.getWriter(), LocalDateTime.now()), - new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now())); + new DeleteHistory(ContentType.QUESTION, 1L, 1L, LocalDateTime.now()), + new DeleteHistory(ContentType.ANSWER, 11L, 1L, LocalDateTime.now())); verify(deleteHistoryService).saveAll(deleteHistories); } } From 0201526461e6c72eb2da31e12d27a10c23385d8d Mon Sep 17 00:00:00 2001 From: mins1031 Date: Sun, 14 Dec 2025 18:16:14 +0900 Subject: [PATCH 04/10] =?UTF-8?q?refactor=20:=20=EB=A0=88=EA=B1=B0?= =?UTF-8?q?=EC=8B=9C=20=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20*=20=EC=86=8C=ED=94=84=ED=8A=B8=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=20=EA=B0=80=EB=8A=A5=20=EC=B6=94=EC=83=81=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EC=A7=88=EB=AC=B8?= =?UTF-8?q?=20=EB=8F=84=EB=A9=94=EC=9D=B8=EA=B0=9D=EC=B2=B4=EC=97=90=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=82=B4=EC=9A=A9=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/nextstep/qna/domain/Answer.java | 9 +++--- .../nextstep/qna/domain/QnADomainService.java | 11 +++++-- .../java/nextstep/qna/domain/Question.java | 32 ++++++++----------- .../qna/domain/SoftDeleteAbleDomain.java | 20 ++++++++++++ .../java/nextstep/qna/service/QnAService.java | 5 ++- .../nextstep/qna/domain/QuestionTest.java | 6 ++-- 6 files changed, 54 insertions(+), 29 deletions(-) create mode 100644 src/main/java/nextstep/qna/domain/SoftDeleteAbleDomain.java diff --git a/src/main/java/nextstep/qna/domain/Answer.java b/src/main/java/nextstep/qna/domain/Answer.java index 99b4d5ed0..d25428b6d 100644 --- a/src/main/java/nextstep/qna/domain/Answer.java +++ b/src/main/java/nextstep/qna/domain/Answer.java @@ -1,14 +1,13 @@ package nextstep.qna.domain; +import java.time.LocalDateTime; import nextstep.qna.exception.unchecked.CannotDeleteException; import nextstep.qna.exception.unchecked.NotFoundException; import nextstep.qna.exception.unchecked.UnAuthorizedException; import nextstep.qna.exception.unchecked.WrongRequestException; -import nextstep.users.domain.NsUser; - -import java.time.LocalDateTime; public class Answer { + private Long id; private Long writerId; @@ -32,11 +31,11 @@ public Answer(long writerId, Question question, String contents) { public Answer(Long id, long writerId, Question question, String contents) { this.id = id; - if(writerId <= 0L) { + if (writerId <= 0L) { throw new UnAuthorizedException(); } - if(question == null) { + if (question == null) { throw new NotFoundException(); } diff --git a/src/main/java/nextstep/qna/domain/QnADomainService.java b/src/main/java/nextstep/qna/domain/QnADomainService.java index ddc398cb7..7a7b6c1c6 100644 --- a/src/main/java/nextstep/qna/domain/QnADomainService.java +++ b/src/main/java/nextstep/qna/domain/QnADomainService.java @@ -1,12 +1,19 @@ package nextstep.qna.domain; +import java.time.LocalDateTime; import java.util.List; public class QnADomainService { - public List deleteQuestion(long requesterId, Question question) { + // TODO : 피드백 주신 부분 잘보았습니다. + // 저는 개인적으로 도메인 로직은 순수하게 유지하는게 가장 좋다고 생각합니다만. + // 스프링을 활용한 의존성주입이 필요한 예외적인 상황(불가피한 외부API 호출, 이메일 등등..)에서는 허용하고 사용해주는것도 좋다고 생각합니다. + // 다만 단계를 거쳐볼것 같아요. 1. 순수 객체 -> 2. 클래스 메서드(static) 집합 객체(=외부 bean 필요없이 캐싱 작업이 필요힌 경우) -> 3. 외부 bean이 불가피하게 필요한경우 bean & di + public List deleteQuestion( + long requesterId, Question question, LocalDateTime fixedDeletedDateTime + ) { question.putOnDelete(requesterId); - return question.bringAllDeleteHistories(); + return question.bringAllDeleteHistories(fixedDeletedDateTime); } } diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index 0b4102073..733404005 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -1,13 +1,14 @@ package nextstep.qna.domain; +import static java.util.Objects.isNull; + import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import nextstep.qna.exception.unchecked.CannotDeleteException; import nextstep.qna.exception.unchecked.WrongRequestException; -import nextstep.users.domain.NsUser; -public class Question { +public class Question extends SoftDeleteAbleDomain { private Long id; @@ -19,15 +20,6 @@ public class Question { private List answers = new ArrayList<>(); - private boolean deleted = false; - - private LocalDateTime createdDate = LocalDateTime.now(); - - private LocalDateTime updatedDate; - - public Question() { - } - public Question(long writerId, String title, String contents) { this(0L, writerId, title, contents); } @@ -49,7 +41,7 @@ public void addAnswer(Answer answer) { } public boolean isDeleted() { - return deleted; + return super.isDeleted(); } public boolean hasAnswers() { @@ -74,7 +66,7 @@ public void putOnDelete(long requesterId) { throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); } - this.deleted = true; + super.updateDeleted(); putOnAllAnswersDelete(requesterId); } @@ -82,17 +74,21 @@ public void putOnAllAnswersDelete(long requesterId) { this.answers.forEach(answer -> answer.putOnDelete(requesterId)); } - public DeleteHistory createQuestionDeleteHistory() { - if (!deleted) { + public DeleteHistory createQuestionDeleteHistory(LocalDateTime deletedDateTime) { + if (isNull(deletedDateTime)) { + throw new WrongRequestException("삭제시점은 필수 입니다."); + } + + if (!super.isDeleted()) { throw new WrongRequestException("삭제되지 않은 질문은 삭제이력을 생성할 수 없습니다."); } - return new DeleteHistory(ContentType.QUESTION, this.id, this.writerId, LocalDateTime.now()); + return new DeleteHistory(ContentType.QUESTION, this.id, this.writerId, deletedDateTime); } - public List bringAllDeleteHistories() { + public List bringAllDeleteHistories(LocalDateTime deletedDateTime) { List deleteHistories = new ArrayList<>(); - deleteHistories.add(createQuestionDeleteHistory()); + deleteHistories.add(createQuestionDeleteHistory(deletedDateTime)); for (Answer answer : answers) { deleteHistories.add(answer.createAnswerDeleteHistory()); diff --git a/src/main/java/nextstep/qna/domain/SoftDeleteAbleDomain.java b/src/main/java/nextstep/qna/domain/SoftDeleteAbleDomain.java new file mode 100644 index 000000000..7a302ccf1 --- /dev/null +++ b/src/main/java/nextstep/qna/domain/SoftDeleteAbleDomain.java @@ -0,0 +1,20 @@ +package nextstep.qna.domain; + +import java.time.LocalDateTime; + +public abstract class SoftDeleteAbleDomain { + + private boolean deleted = false; + + private LocalDateTime createdDate = LocalDateTime.now(); + + private LocalDateTime updatedDate; + + boolean isDeleted() { + return this.deleted; + } + + void updateDeleted() { + this.deleted = true; + } +} diff --git a/src/main/java/nextstep/qna/service/QnAService.java b/src/main/java/nextstep/qna/service/QnAService.java index c5b8f9b8b..dd1089ced 100644 --- a/src/main/java/nextstep/qna/service/QnAService.java +++ b/src/main/java/nextstep/qna/service/QnAService.java @@ -1,5 +1,6 @@ package nextstep.qna.service; +import java.time.LocalDateTime; import java.util.List; import javax.annotation.Resource; import nextstep.qna.domain.DeleteHistory; @@ -24,8 +25,10 @@ public void deleteQuestion(long requesterId, long questionId) { Question question = questionRepository.findById(questionId) .orElseThrow(NotFoundException::new); + LocalDateTime fixedDeletedDateTime = LocalDateTime.now(); + List deleteHistories = new QnADomainService() - .deleteQuestion(requesterId, question); + .deleteQuestion(requesterId, question, fixedDeletedDateTime); deleteHistoryService.saveAll(deleteHistories); } diff --git a/src/test/java/nextstep/qna/domain/QuestionTest.java b/src/test/java/nextstep/qna/domain/QuestionTest.java index 5f54fe15e..d18883b42 100644 --- a/src/test/java/nextstep/qna/domain/QuestionTest.java +++ b/src/test/java/nextstep/qna/domain/QuestionTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.time.LocalDateTime; import nextstep.qna.exception.unchecked.CannotDeleteException; import nextstep.qna.exception.unchecked.WrongRequestException; import org.junit.jupiter.api.Test; @@ -108,16 +109,15 @@ public class QuestionTest { Question question = new Question(1L, "title1", "contents1"); question.putOnDelete(1L); - assertThat(question.createQuestionDeleteHistory()).isNotNull(); + assertThat(question.createQuestionDeleteHistory(LocalDateTime.now())).isNotNull(); } @Test void 삭제되지_않은_질문객체를_삭제이력_객체로_만들수_없다() { - ; Question question = new Question(1L, "title1", "contents1"); assertThatThrownBy( - question::createQuestionDeleteHistory + () -> question.createQuestionDeleteHistory(LocalDateTime.now()) ).isInstanceOf(WrongRequestException.class); } } From 3175b81a024d11600ff730488e3bdb10ae7fea4d Mon Sep 17 00:00:00 2001 From: mins1031 Date: Sun, 14 Dec 2025 18:17:28 +0900 Subject: [PATCH 05/10] =?UTF-8?q?refactor=20:=20=EB=A0=88=EA=B1=B0?= =?UTF-8?q?=EC=8B=9C=20=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20*=20=EC=86=8C=ED=94=84=ED=8A=B8=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=20=EC=B6=94=EC=83=81=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=8B=B5?= =?UTF-8?q?=EB=B3=80=20=EB=8F=84=EB=A9=94=EC=9D=B8=EA=B0=9D=EC=B2=B4?= =?UTF-8?q?=EC=97=90=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/nextstep/qna/domain/Answer.java | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/main/java/nextstep/qna/domain/Answer.java b/src/main/java/nextstep/qna/domain/Answer.java index d25428b6d..a4213b2b3 100644 --- a/src/main/java/nextstep/qna/domain/Answer.java +++ b/src/main/java/nextstep/qna/domain/Answer.java @@ -6,7 +6,7 @@ import nextstep.qna.exception.unchecked.UnAuthorizedException; import nextstep.qna.exception.unchecked.WrongRequestException; -public class Answer { +public class Answer extends SoftDeleteAbleDomain{ private Long id; @@ -16,15 +16,6 @@ public class Answer { private String contents; - private boolean deleted = false; - - private LocalDateTime createdDate = LocalDateTime.now(); - - private LocalDateTime updatedDate; - - public Answer() { - } - public Answer(long writerId, Question question, String contents) { this(null, writerId, question, contents); } @@ -45,7 +36,7 @@ public Answer(Long id, long writerId, Question question, String contents) { } public boolean isDeleted() { - return deleted; + return super.isDeleted(); } public void putOnDelete(long requesterId) { @@ -53,7 +44,7 @@ public void putOnDelete(long requesterId) { throw new CannotDeleteException("질문을 삭제할 권한이 없습니다."); } - this.deleted = true; + super.updateDeleted(); } public boolean isOwner(long writerId) { From 000354821d3b778653c1a608e887734ac7cbba8a Mon Sep 17 00:00:00 2001 From: mins1031 Date: Sun, 14 Dec 2025 18:24:02 +0900 Subject: [PATCH 06/10] =?UTF-8?q?refactor=20:=20=EB=A0=88=EA=B1=B0?= =?UTF-8?q?=EC=8B=9C=20=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20*=20=EC=86=8C=ED=94=84=ED=8A=B8=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=20=EC=B6=94=EC=83=81=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/nextstep/qna/domain/Answer.java | 2 +- src/main/java/nextstep/qna/domain/Question.java | 2 +- .../{SoftDeleteAbleDomain.java => SoftDeleteAbleClass.java} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/main/java/nextstep/qna/domain/{SoftDeleteAbleDomain.java => SoftDeleteAbleClass.java} (88%) diff --git a/src/main/java/nextstep/qna/domain/Answer.java b/src/main/java/nextstep/qna/domain/Answer.java index a4213b2b3..1fea6eb72 100644 --- a/src/main/java/nextstep/qna/domain/Answer.java +++ b/src/main/java/nextstep/qna/domain/Answer.java @@ -6,7 +6,7 @@ import nextstep.qna.exception.unchecked.UnAuthorizedException; import nextstep.qna.exception.unchecked.WrongRequestException; -public class Answer extends SoftDeleteAbleDomain{ +public class Answer extends SoftDeleteAbleClass { private Long id; diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index 733404005..b17e45e32 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -8,7 +8,7 @@ import nextstep.qna.exception.unchecked.CannotDeleteException; import nextstep.qna.exception.unchecked.WrongRequestException; -public class Question extends SoftDeleteAbleDomain { +public class Question extends SoftDeleteAbleClass { private Long id; diff --git a/src/main/java/nextstep/qna/domain/SoftDeleteAbleDomain.java b/src/main/java/nextstep/qna/domain/SoftDeleteAbleClass.java similarity index 88% rename from src/main/java/nextstep/qna/domain/SoftDeleteAbleDomain.java rename to src/main/java/nextstep/qna/domain/SoftDeleteAbleClass.java index 7a302ccf1..4d37f2d83 100644 --- a/src/main/java/nextstep/qna/domain/SoftDeleteAbleDomain.java +++ b/src/main/java/nextstep/qna/domain/SoftDeleteAbleClass.java @@ -2,7 +2,7 @@ import java.time.LocalDateTime; -public abstract class SoftDeleteAbleDomain { +public abstract class SoftDeleteAbleClass { private boolean deleted = false; From 58acd613c9a616e1684b659887e6430ec71c1aeb Mon Sep 17 00:00:00 2001 From: mins1031 Date: Sun, 14 Dec 2025 18:28:44 +0900 Subject: [PATCH 07/10] =?UTF-8?q?refactor=20:=20=EB=A0=88=EA=B1=B0?= =?UTF-8?q?=EC=8B=9C=20=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20*=20=EA=B2=8C=EC=8B=9C=ED=8C=90=20=EB=82=B4?= =?UTF-8?q?=EC=9A=A9=20=EA=B0=9D=EC=B2=B4=20=EC=83=9D=EC=84=B1=20*=20?= =?UTF-8?q?=EC=A7=88=EB=AC=B8=20&=20=EB=8B=B5=EB=B3=80=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=EC=97=90=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/nextstep/qna/domain/Answer.java | 9 ++++++--- .../nextstep/qna/domain/BoardContent.java | 19 +++++++++++++++++++ .../java/nextstep/qna/domain/Question.java | 13 ++++++------- 3 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 src/main/java/nextstep/qna/domain/BoardContent.java diff --git a/src/main/java/nextstep/qna/domain/Answer.java b/src/main/java/nextstep/qna/domain/Answer.java index 1fea6eb72..16f4f6afc 100644 --- a/src/main/java/nextstep/qna/domain/Answer.java +++ b/src/main/java/nextstep/qna/domain/Answer.java @@ -14,7 +14,7 @@ public class Answer extends SoftDeleteAbleClass { private Question question; - private String contents; + private BoardContent boardContent; public Answer(long writerId, Question question, String contents) { this(null, writerId, question, contents); @@ -32,7 +32,7 @@ public Answer(Long id, long writerId, Question question, String contents) { this.writerId = writerId; this.question = question; - this.contents = contents; + this.boardContent = new BoardContent("" , contents); } public boolean isDeleted() { @@ -65,6 +65,9 @@ public void toQuestion(Question question) { @Override public String toString() { - return "Answer [id=" + id + ", writerId=" + writerId + ", contents=" + contents + "]"; + return "Answer [id=" + id + + ", writerId=" + writerId + + ", contents=" + boardContent + + "]"; } } diff --git a/src/main/java/nextstep/qna/domain/BoardContent.java b/src/main/java/nextstep/qna/domain/BoardContent.java new file mode 100644 index 000000000..9eb1cb180 --- /dev/null +++ b/src/main/java/nextstep/qna/domain/BoardContent.java @@ -0,0 +1,19 @@ +package nextstep.qna.domain; + +public class BoardContent { + private String title; + private String contents; + + public BoardContent(String title, String contents) { + this.title = title; + this.contents = contents; + } + + @Override + public String toString() { + return "BoardContent{" + + "title='" + title + '\'' + + ", contents='" + contents + '\'' + + '}'; + } +} diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index b17e45e32..6d9d49374 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -12,9 +12,7 @@ public class Question extends SoftDeleteAbleClass { private Long id; - private String title; - - private String contents; + private BoardContent boardContent; private Long writerId; @@ -27,8 +25,7 @@ public Question(long writerId, String title, String contents) { public Question(Long id, long writerId, String title, String contents) { this.id = id; this.writerId = writerId; - this.title = title; - this.contents = contents; + this.boardContent = new BoardContent(title, contents); } public boolean isOwner(long requesterId) { @@ -99,7 +96,9 @@ public List bringAllDeleteHistories(LocalDateTime deletedDateTime @Override public String toString() { - return "Question [id=" + id + ", title=" + title + ", contents=" + contents - + ", writerId=" + writerId + "]"; + return "Question [id=" + id + + ", boardContents=" + boardContent + + ", writerId=" + writerId + + "]"; } } From 6c04e2356057aeee168431081e020f6c2eba540e Mon Sep 17 00:00:00 2001 From: mins1031 Date: Sun, 14 Dec 2025 18:44:47 +0900 Subject: [PATCH 08/10] =?UTF-8?q?refactor=20:=20=EB=A0=88=EA=B1=B0?= =?UTF-8?q?=EC=8B=9C=20=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20*=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=82=A4?= =?UTF-8?q?=ED=8E=98=EC=96=B4=20=EA=B0=9D=EC=B2=B4=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=B0=8F=20=ED=9A=8C=EC=9B=90=EA=B0=9D=EC=B2=B4=EC=97=90=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20*=20=ED=9A=8C=EC=9B=90=EA=B0=9D=EC=B2=B4?= =?UTF-8?q?=20=EB=B6=88=ED=95=84=EC=9A=94=20=EC=BD=94=EB=93=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/users/domain/LogInPairKey.java | 48 ++++++++++++++ .../java/nextstep/users/domain/NsUser.java | 66 ++----------------- .../users/domain/LogInPairKeyTest.java | 29 ++++++++ 3 files changed, 84 insertions(+), 59 deletions(-) create mode 100644 src/main/java/nextstep/users/domain/LogInPairKey.java create mode 100644 src/test/java/nextstep/users/domain/LogInPairKeyTest.java diff --git a/src/main/java/nextstep/users/domain/LogInPairKey.java b/src/main/java/nextstep/users/domain/LogInPairKey.java new file mode 100644 index 000000000..813bfdf7d --- /dev/null +++ b/src/main/java/nextstep/users/domain/LogInPairKey.java @@ -0,0 +1,48 @@ +package nextstep.users.domain; + +import java.util.Objects; + +public class LogInPairKey { + private String userId; + private String password; + + public LogInPairKey(String userId, String password) { + this.userId = userId; + this.password = password; + } + + public boolean matchUser(String targetUserId, String targetPassword) { + return matchUserId(targetUserId) && matchPassword(targetPassword); + } + + private boolean matchUserId(String targetUserId) { + return this.userId.equals(targetUserId); + } + + private boolean matchPassword(String targetPassword) { + return password.equals(targetPassword); + } + + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + LogInPairKey that = (LogInPairKey) o; + return Objects.equals(userId, that.userId) && Objects.equals(password, + that.password); + } + + @Override + public int hashCode() { + return Objects.hash(userId, password); + } + + @Override + public String toString() { + return "LogInPairKey{" + + "userId='" + userId + '\'' + + '}'; + } +} diff --git a/src/main/java/nextstep/users/domain/NsUser.java b/src/main/java/nextstep/users/domain/NsUser.java index 772b57cad..cd03718b6 100755 --- a/src/main/java/nextstep/users/domain/NsUser.java +++ b/src/main/java/nextstep/users/domain/NsUser.java @@ -10,9 +10,7 @@ public class NsUser { private Long id; - private String userId; - - private String password; + private LogInPairKey logInPairKey; private String name; @@ -31,8 +29,7 @@ public NsUser(Long id, String userId, String password, String name, String email public NsUser(Long id, String userId, String password, String name, String email, LocalDateTime createdAt, LocalDateTime updatedAt) { this.id = id; - this.userId = userId; - this.password = password; + this.logInPairKey = new LogInPairKey(userId, password); this.name = name; this.email = email; this.createdAt = createdAt; @@ -43,48 +40,16 @@ public Long getId() { return id; } - public String getUserId() { - return userId; - } - - public NsUser setUserId(String userId) { - this.userId = userId; - return this; - } - - public String getPassword() { - return password; - } - - public NsUser setPassword(String password) { - this.password = password; - return this; - } - public String getName() { return name; } - public NsUser setName(String name) { - this.name = name; - return this; - } - - public String getEmail() { - return email; - } - - public NsUser setEmail(String email) { - this.email = email; - return this; - } - public void update(NsUser loginUser, NsUser target) { - if (!matchUserId(loginUser.getUserId())) { + if (!target.matchLoginPairKey(this.logInPairKey)) { throw new UnAuthorizedException(); } - if (!matchPassword(target.getPassword())) { + if (!loginUser.matchLoginPairKey(this.logInPairKey)) { throw new UnAuthorizedException(); } @@ -92,25 +57,8 @@ public void update(NsUser loginUser, NsUser target) { this.email = target.email; } - public boolean matchUser(NsUser target) { - return matchUserId(target.getUserId()); - } - - private boolean matchUserId(String userId) { - return this.userId.equals(userId); - } - - public boolean matchPassword(String targetPassword) { - return password.equals(targetPassword); - } - - public boolean equalsNameAndEmail(NsUser target) { - if (Objects.isNull(target)) { - return false; - } - - return name.equals(target.name) && - email.equals(target.email); + private boolean matchLoginPairKey(LogInPairKey targetKey) { + return this.logInPairKey.equals(targetKey); } public boolean isGuestUser() { @@ -128,7 +76,7 @@ public boolean isGuestUser() { public String toString() { return "NsUser{" + "id=" + id + - ", userId='" + userId + '\'' + + ", userId='" + logInPairKey + '\'' + ", name='" + name + '\'' + ", email='" + email + '\'' + ", createdAt=" + createdAt + diff --git a/src/test/java/nextstep/users/domain/LogInPairKeyTest.java b/src/test/java/nextstep/users/domain/LogInPairKeyTest.java new file mode 100644 index 000000000..f0b7932d1 --- /dev/null +++ b/src/test/java/nextstep/users/domain/LogInPairKeyTest.java @@ -0,0 +1,29 @@ +package nextstep.users.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class LogInPairKeyTest { + + @Test + void 아아디_비밀번호가_일치하는지_검증할_수_있다() { + assertThat( + new LogInPairKey("min", "6038").matchUser("min", "6038") + ).isTrue(); + } + + @Test + void 아아디_비밀번호중_아이디가_불일치하는지_검증할_수_있다() { + assertThat( + new LogInPairKey("min", "6038").matchUser("diff min", "6038") + ).isFalse(); + } + + @Test + void 아아디_비밀번호중_비밀번호가_불일치하는지_검증할_수_있다() { + assertThat( + new LogInPairKey("min", "6038").matchUser("min", "1234") + ).isFalse(); + } +} \ No newline at end of file From ea12de64e4a26e0b957fc6e87d2c4e36a7b54819 Mon Sep 17 00:00:00 2001 From: mins1031 Date: Sun, 14 Dec 2025 19:00:28 +0900 Subject: [PATCH 09/10] =?UTF-8?q?refactor=20:=20=EB=A0=88=EA=B1=B0?= =?UTF-8?q?=EC=8B=9C=20=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20*=20=EA=B8=B0=EB=B3=B8=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=EA=B0=9D=EC=B2=B4=20=EC=B6=94=EC=83=81=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EA=B0=9D=EC=B2=B4?= =?UTF-8?q?=EC=97=90=20=EC=A0=81=EC=9A=A9=20*=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=20=EC=B6=94=EC=83=81=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EC=97=90=20id=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/common/domain/BaseEntity.java | 23 +++++++++++ .../domain/SoftDeletableBaseEntity.java | 38 +++++++++++++++++++ src/main/java/nextstep/qna/domain/Answer.java | 14 +++---- .../java/nextstep/qna/domain/Question.java | 14 +++---- .../qna/domain/SoftDeleteAbleClass.java | 20 ---------- .../java/nextstep/users/domain/NsUser.java | 35 +++-------------- 6 files changed, 76 insertions(+), 68 deletions(-) create mode 100644 src/main/java/nextstep/common/domain/BaseEntity.java create mode 100644 src/main/java/nextstep/common/domain/SoftDeletableBaseEntity.java delete mode 100644 src/main/java/nextstep/qna/domain/SoftDeleteAbleClass.java diff --git a/src/main/java/nextstep/common/domain/BaseEntity.java b/src/main/java/nextstep/common/domain/BaseEntity.java new file mode 100644 index 000000000..34c872025 --- /dev/null +++ b/src/main/java/nextstep/common/domain/BaseEntity.java @@ -0,0 +1,23 @@ +package nextstep.common.domain; + +import java.time.LocalDateTime; + +public abstract class BaseEntity { + private Long id; + private LocalDateTime createdDate; + private LocalDateTime updatedDate; + + protected BaseEntity(Long id) { + this(id, LocalDateTime.now(), LocalDateTime.now()); + } + + public BaseEntity(Long id, LocalDateTime createdDate, LocalDateTime updatedDate) { + this.id = id; + this.createdDate = createdDate; + this.updatedDate = updatedDate; + } + + public Long getId() { + return id; + } +} diff --git a/src/main/java/nextstep/common/domain/SoftDeletableBaseEntity.java b/src/main/java/nextstep/common/domain/SoftDeletableBaseEntity.java new file mode 100644 index 000000000..a8ba4d7c7 --- /dev/null +++ b/src/main/java/nextstep/common/domain/SoftDeletableBaseEntity.java @@ -0,0 +1,38 @@ +package nextstep.common.domain; + +import java.time.LocalDateTime; + +public abstract class SoftDeletableBaseEntity { + private Long id; + private boolean deleted; + private LocalDateTime createdDate; + private LocalDateTime updatedDate; + + protected SoftDeletableBaseEntity(Long id) { + this(id, false, LocalDateTime.now(), LocalDateTime.now()); + } + + protected SoftDeletableBaseEntity(Long id, LocalDateTime createdAt, LocalDateTime updatedAt) { + this(id, false, createdAt, updatedAt); + } + + protected SoftDeletableBaseEntity(Long id, boolean deleted, LocalDateTime createdDate, + LocalDateTime updatedDate) { + this.id = id; + this.deleted = deleted; + this.createdDate = createdDate; + this.updatedDate = updatedDate; + } + + protected boolean isDeleted() { + return this.deleted; + } + + public void updateDeleted() { + this.deleted = true; + } + + public Long getId() { + return id; + } +} diff --git a/src/main/java/nextstep/qna/domain/Answer.java b/src/main/java/nextstep/qna/domain/Answer.java index 16f4f6afc..cf2b8160e 100644 --- a/src/main/java/nextstep/qna/domain/Answer.java +++ b/src/main/java/nextstep/qna/domain/Answer.java @@ -1,19 +1,15 @@ package nextstep.qna.domain; import java.time.LocalDateTime; +import nextstep.common.domain.SoftDeletableBaseEntity; import nextstep.qna.exception.unchecked.CannotDeleteException; import nextstep.qna.exception.unchecked.NotFoundException; import nextstep.qna.exception.unchecked.UnAuthorizedException; import nextstep.qna.exception.unchecked.WrongRequestException; -public class Answer extends SoftDeleteAbleClass { - - private Long id; - +public class Answer extends SoftDeletableBaseEntity { private Long writerId; - private Question question; - private BoardContent boardContent; public Answer(long writerId, Question question, String contents) { @@ -21,7 +17,7 @@ public Answer(long writerId, Question question, String contents) { } public Answer(Long id, long writerId, Question question, String contents) { - this.id = id; + super(id); if (writerId <= 0L) { throw new UnAuthorizedException(); } @@ -56,7 +52,7 @@ public DeleteHistory createAnswerDeleteHistory() { throw new WrongRequestException("삭제되지 않은 답변은 삭제이력을 생성할 수 없습니다."); } - return new DeleteHistory(ContentType.ANSWER, this.id, this.writerId, LocalDateTime.now()); + return new DeleteHistory(ContentType.ANSWER, super.getId(), this.writerId, LocalDateTime.now()); } public void toQuestion(Question question) { @@ -65,7 +61,7 @@ public void toQuestion(Question question) { @Override public String toString() { - return "Answer [id=" + id + return "Answer [id=" + super.getId() + ", writerId=" + writerId + ", contents=" + boardContent + "]"; diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index 6d9d49374..17ab5949a 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -5,17 +5,13 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import nextstep.common.domain.SoftDeletableBaseEntity; import nextstep.qna.exception.unchecked.CannotDeleteException; import nextstep.qna.exception.unchecked.WrongRequestException; -public class Question extends SoftDeleteAbleClass { - - private Long id; - +public class Question extends SoftDeletableBaseEntity { private BoardContent boardContent; - private Long writerId; - private List answers = new ArrayList<>(); public Question(long writerId, String title, String contents) { @@ -23,7 +19,7 @@ public Question(long writerId, String title, String contents) { } public Question(Long id, long writerId, String title, String contents) { - this.id = id; + super(id); this.writerId = writerId; this.boardContent = new BoardContent(title, contents); } @@ -80,7 +76,7 @@ public DeleteHistory createQuestionDeleteHistory(LocalDateTime deletedDateTime) throw new WrongRequestException("삭제되지 않은 질문은 삭제이력을 생성할 수 없습니다."); } - return new DeleteHistory(ContentType.QUESTION, this.id, this.writerId, deletedDateTime); + return new DeleteHistory(ContentType.QUESTION, super.getId(), this.writerId, deletedDateTime); } public List bringAllDeleteHistories(LocalDateTime deletedDateTime) { @@ -96,7 +92,7 @@ public List bringAllDeleteHistories(LocalDateTime deletedDateTime @Override public String toString() { - return "Question [id=" + id + return "Question [id=" + super.getId() + ", boardContents=" + boardContent + ", writerId=" + writerId + "]"; diff --git a/src/main/java/nextstep/qna/domain/SoftDeleteAbleClass.java b/src/main/java/nextstep/qna/domain/SoftDeleteAbleClass.java deleted file mode 100644 index 4d37f2d83..000000000 --- a/src/main/java/nextstep/qna/domain/SoftDeleteAbleClass.java +++ /dev/null @@ -1,20 +0,0 @@ -package nextstep.qna.domain; - -import java.time.LocalDateTime; - -public abstract class SoftDeleteAbleClass { - - private boolean deleted = false; - - private LocalDateTime createdDate = LocalDateTime.now(); - - private LocalDateTime updatedDate; - - boolean isDeleted() { - return this.deleted; - } - - void updateDeleted() { - this.deleted = true; - } -} diff --git a/src/main/java/nextstep/users/domain/NsUser.java b/src/main/java/nextstep/users/domain/NsUser.java index cd03718b6..5f90332af 100755 --- a/src/main/java/nextstep/users/domain/NsUser.java +++ b/src/main/java/nextstep/users/domain/NsUser.java @@ -1,43 +1,31 @@ package nextstep.users.domain; +import nextstep.common.domain.BaseEntity; import nextstep.qna.exception.unchecked.UnAuthorizedException; import java.time.LocalDateTime; import java.util.Objects; -public class NsUser { - public static final GuestNsUser GUEST_USER = new GuestNsUser(); - - private Long id; - +public class NsUser extends BaseEntity { private LogInPairKey logInPairKey; private String name; private String email; - private LocalDateTime createdAt; - - private LocalDateTime updatedAt; - - public NsUser() { - } - public NsUser(Long id, String userId, String password, String name, String email) { this(id, userId, password, name, email, LocalDateTime.now(), null); } public NsUser(Long id, String userId, String password, String name, String email, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; + super(id, createdAt, updatedAt); this.logInPairKey = new LogInPairKey(userId, password); this.name = name; this.email = email; - this.createdAt = createdAt; - this.updatedAt = updatedAt; } public Long getId() { - return id; + return super.getId(); } public String getName() { @@ -61,26 +49,13 @@ private boolean matchLoginPairKey(LogInPairKey targetKey) { return this.logInPairKey.equals(targetKey); } - public boolean isGuestUser() { - return false; - } - - private static class GuestNsUser extends NsUser { - @Override - public boolean isGuestUser() { - return true; - } - } - @Override public String toString() { return "NsUser{" + - "id=" + id + + "id=" + getId() + ", userId='" + logInPairKey + '\'' + ", name='" + name + '\'' + ", email='" + email + '\'' + - ", createdAt=" + createdAt + - ", updatedAt=" + updatedAt + '}'; } } From c9256167befb6c91b4b5ef95a8dbdcdd3a79c02e Mon Sep 17 00:00:00 2001 From: mins1031 Date: Sun, 14 Dec 2025 20:00:29 +0900 Subject: [PATCH 10/10] =?UTF-8?q?refactor=20:=20=EB=A0=88=EA=B1=B0?= =?UTF-8?q?=EC=8B=9C=20=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20*=20=EA=B8=B0=EB=B3=B8=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=EA=B0=9D=EC=B2=B4=20=EC=B6=94=EC=83=81=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=82=AD=EC=A0=9C=EC=9D=B4=EB=A0=A5=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EA=B0=9D=EC=B2=B4=EC=97=90=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20*=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=EB=A1=9C=20=EC=83=9D=EC=84=B1/=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=8B=9C=EA=B0=84=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=99=B8=EB=B6=80=EC=97=90=EC=84=9C=20=EC=A3=BC?= =?UTF-8?q?=EC=9E=85=EB=B0=9B=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/common/domain/BaseEntity.java | 26 ++++++++++++ .../domain/SoftDeletableBaseEntity.java | 18 ++++++++ src/main/java/nextstep/qna/domain/Answer.java | 4 +- .../nextstep/qna/domain/DeleteHistory.java | 41 ++++++++++--------- .../java/nextstep/qna/domain/Question.java | 2 +- .../java/nextstep/qna/service/QnAService.java | 4 +- .../java/nextstep/qna/domain/AnswerTest.java | 6 +-- .../nextstep/qna/service/QnaServiceTest.java | 23 +++++++---- 8 files changed, 87 insertions(+), 37 deletions(-) diff --git a/src/main/java/nextstep/common/domain/BaseEntity.java b/src/main/java/nextstep/common/domain/BaseEntity.java index 34c872025..ef5afe1e5 100644 --- a/src/main/java/nextstep/common/domain/BaseEntity.java +++ b/src/main/java/nextstep/common/domain/BaseEntity.java @@ -1,6 +1,7 @@ package nextstep.common.domain; import java.time.LocalDateTime; +import java.util.Objects; public abstract class BaseEntity { private Long id; @@ -20,4 +21,29 @@ public BaseEntity(Long id, LocalDateTime createdDate, LocalDateTime updatedDate) public Long getId() { return id; } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + BaseEntity that = (BaseEntity) o; + return Objects.equals(id, that.id) + && Objects.equals(createdDate, that.createdDate) + && Objects.equals(updatedDate, that.updatedDate); + } + + @Override + public int hashCode() { + return Objects.hash(id, createdDate, updatedDate); + } + + @Override + public String toString() { + return "BaseEntity{" + + "id=" + id + + ", createdDate=" + createdDate + + ", updatedDate=" + updatedDate + + '}'; + } } diff --git a/src/main/java/nextstep/common/domain/SoftDeletableBaseEntity.java b/src/main/java/nextstep/common/domain/SoftDeletableBaseEntity.java index a8ba4d7c7..36beb14ee 100644 --- a/src/main/java/nextstep/common/domain/SoftDeletableBaseEntity.java +++ b/src/main/java/nextstep/common/domain/SoftDeletableBaseEntity.java @@ -1,6 +1,7 @@ package nextstep.common.domain; import java.time.LocalDateTime; +import java.util.Objects; public abstract class SoftDeletableBaseEntity { private Long id; @@ -35,4 +36,21 @@ public void updateDeleted() { public Long getId() { return id; } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + SoftDeletableBaseEntity that = (SoftDeletableBaseEntity) o; + return deleted == that.deleted + && Objects.equals(id, that.id) + && Objects.equals(createdDate, that.createdDate) + && Objects.equals(updatedDate, that.updatedDate); + } + + @Override + public int hashCode() { + return Objects.hash(id, deleted, createdDate, updatedDate); + } } diff --git a/src/main/java/nextstep/qna/domain/Answer.java b/src/main/java/nextstep/qna/domain/Answer.java index cf2b8160e..bc2b82959 100644 --- a/src/main/java/nextstep/qna/domain/Answer.java +++ b/src/main/java/nextstep/qna/domain/Answer.java @@ -47,12 +47,12 @@ public boolean isOwner(long writerId) { return this.writerId == writerId; } - public DeleteHistory createAnswerDeleteHistory() { + public DeleteHistory createAnswerDeleteHistory(LocalDateTime deletedDateTime) { if (!isDeleted()) { throw new WrongRequestException("삭제되지 않은 답변은 삭제이력을 생성할 수 없습니다."); } - return new DeleteHistory(ContentType.ANSWER, super.getId(), this.writerId, LocalDateTime.now()); + return new DeleteHistory(ContentType.ANSWER, super.getId(), this.writerId, deletedDateTime); } public void toQuestion(Question question) { diff --git a/src/main/java/nextstep/qna/domain/DeleteHistory.java b/src/main/java/nextstep/qna/domain/DeleteHistory.java index 38a964689..80ebe5335 100644 --- a/src/main/java/nextstep/qna/domain/DeleteHistory.java +++ b/src/main/java/nextstep/qna/domain/DeleteHistory.java @@ -1,50 +1,53 @@ package nextstep.qna.domain; +import static java.util.Objects.*; + +import nextstep.common.domain.BaseEntity; import nextstep.users.domain.NsUser; import java.time.LocalDateTime; import java.util.Objects; -public class DeleteHistory { - private Long id; - +public class DeleteHistory extends BaseEntity { private ContentType contentType; - private Long contentId; - private Long deletedId; - private LocalDateTime createdDate = LocalDateTime.now(); - - public DeleteHistory() { + public DeleteHistory(ContentType contentType, Long contentId, Long deletedId, LocalDateTime createdDate) { + this(0L, contentType, contentId, deletedId, createdDate, null); } - public DeleteHistory(ContentType contentType, Long contentId, Long deletedId, LocalDateTime createdDate) { + public DeleteHistory(Long id, ContentType contentType, Long contentId, Long deletedId, LocalDateTime createdDate, LocalDateTime updateDate) { + super(id, createdDate, updateDate); this.contentType = contentType; this.contentId = contentId; this.deletedId = deletedId; - this.createdDate = createdDate; } @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (o == null || getClass() != o.getClass()) { + return false; + } DeleteHistory that = (DeleteHistory) o; - return Objects.equals(id, that.id) && - contentType == that.contentType && - Objects.equals(contentId, that.contentId) && - Objects.equals(deletedId, that.deletedId); + return super.equals(o) + && contentType == that.contentType + && Objects.equals(contentId, that.contentId) + && Objects.equals(deletedId, that.deletedId); } @Override public int hashCode() { - return Objects.hash(id, contentType, contentId, deletedId); + return hash(super.hashCode(), contentType, contentId, deletedId); } @Override public String toString() { - return "DeleteHistory [id=" + id + ", contentType=" + contentType + ", contentId=" + contentId + ", deletedBy=" - + deletedId + ", createdDate=" + createdDate + "]"; + return "DeleteHistory{" + + "id=" + super.toString() + + ", contentType=" + contentType + + ", contentId=" + contentId + + ", deletedId=" + deletedId + + '}'; } } diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index 17ab5949a..def6094f4 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -84,7 +84,7 @@ public List bringAllDeleteHistories(LocalDateTime deletedDateTime deleteHistories.add(createQuestionDeleteHistory(deletedDateTime)); for (Answer answer : answers) { - deleteHistories.add(answer.createAnswerDeleteHistory()); + deleteHistories.add(answer.createAnswerDeleteHistory(deletedDateTime)); } return deleteHistories; diff --git a/src/main/java/nextstep/qna/service/QnAService.java b/src/main/java/nextstep/qna/service/QnAService.java index dd1089ced..87a0d31fd 100644 --- a/src/main/java/nextstep/qna/service/QnAService.java +++ b/src/main/java/nextstep/qna/service/QnAService.java @@ -21,12 +21,10 @@ public class QnAService { private DeleteHistoryService deleteHistoryService; @Transactional - public void deleteQuestion(long requesterId, long questionId) { + public void deleteQuestion(long requesterId, long questionId, LocalDateTime fixedDeletedDateTime) { Question question = questionRepository.findById(questionId) .orElseThrow(NotFoundException::new); - LocalDateTime fixedDeletedDateTime = LocalDateTime.now(); - List deleteHistories = new QnADomainService() .deleteQuestion(requesterId, question, fixedDeletedDateTime); diff --git a/src/test/java/nextstep/qna/domain/AnswerTest.java b/src/test/java/nextstep/qna/domain/AnswerTest.java index b7095709a..523b9f0c9 100644 --- a/src/test/java/nextstep/qna/domain/AnswerTest.java +++ b/src/test/java/nextstep/qna/domain/AnswerTest.java @@ -3,8 +3,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.time.LocalDateTime; import nextstep.qna.exception.unchecked.WrongRequestException; -import nextstep.users.domain.NsUserTest; import org.junit.jupiter.api.Test; public class AnswerTest { @@ -24,7 +24,7 @@ public class AnswerTest { Answer answer = new Answer(1L, question, "Answers Contents1"); answer.putOnDelete(1L); - assertThat(answer.createAnswerDeleteHistory()).isNotNull(); + assertThat(answer.createAnswerDeleteHistory(LocalDateTime.now())).isNotNull(); } @Test @@ -33,7 +33,7 @@ public class AnswerTest { Answer answer = new Answer(1L, question, "Answers Contents1"); assertThatThrownBy( - answer::createAnswerDeleteHistory + () -> answer.createAnswerDeleteHistory(LocalDateTime.now()) ).isInstanceOf(WrongRequestException.class); } diff --git a/src/test/java/nextstep/qna/service/QnaServiceTest.java b/src/test/java/nextstep/qna/service/QnaServiceTest.java index 730ad0098..a8c04b957 100644 --- a/src/test/java/nextstep/qna/service/QnaServiceTest.java +++ b/src/test/java/nextstep/qna/service/QnaServiceTest.java @@ -24,6 +24,7 @@ @ExtendWith(MockitoExtension.class) public class QnaServiceTest { + @Mock private QuestionRepository questionRepository; @@ -46,47 +47,51 @@ public void setUp() { @Test public void delete_성공() { when(questionRepository.findById(1L)).thenReturn(Optional.of(question)); + LocalDateTime fixedNow = LocalDateTime.now(); assertThat(question.isDeleted()).isFalse(); - qnAService.deleteQuestion(1L, 1L); + qnAService.deleteQuestion(1L, 1L, fixedNow); assertThat(question.isDeleted()).isTrue(); - verifyDeleteHistories(); + verifyDeleteHistories(fixedNow); } @Test public void delete_다른_사람이_쓴_글() { when(questionRepository.findById(1L)).thenReturn(Optional.of(question)); + LocalDateTime fixedNow = LocalDateTime.now(); assertThatThrownBy(() -> - qnAService.deleteQuestion(2L, 1L) + qnAService.deleteQuestion(2L, 1L, fixedNow) ).isInstanceOf(CannotDeleteException.class); } @Test public void delete_성공_질문자_답변자_같음() { when(questionRepository.findById(1L)).thenReturn(Optional.of(question)); + LocalDateTime fixedNow = LocalDateTime.now(); - qnAService.deleteQuestion(1L, 1L); + qnAService.deleteQuestion(1L, 1L, fixedNow); assertThat(question.isDeleted()).isTrue(); assertThat(answer.isDeleted()).isTrue(); - verifyDeleteHistories(); + verifyDeleteHistories(fixedNow); } @Test public void delete_답변_중_다른_사람이_쓴_글() { when(questionRepository.findById(1L)).thenReturn(Optional.of(question)); + LocalDateTime fixedNow = LocalDateTime.now(); assertThatThrownBy(() -> - qnAService.deleteQuestion(2L, 1L) + qnAService.deleteQuestion(2L, 1L, fixedNow) ).isInstanceOf(CannotDeleteException.class); } - private void verifyDeleteHistories() { + private void verifyDeleteHistories(LocalDateTime fixedNow) { List deleteHistories = Arrays.asList( - new DeleteHistory(ContentType.QUESTION, 1L, 1L, LocalDateTime.now()), - new DeleteHistory(ContentType.ANSWER, 11L, 1L, LocalDateTime.now())); + new DeleteHistory(ContentType.QUESTION, 1L, 1L, fixedNow), + new DeleteHistory(ContentType.ANSWER, 11L, 1L, fixedNow)); verify(deleteHistoryService).saveAll(deleteHistories); } }