Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
34b449f
docs: 질문 삭제하기 기능 목록 작성
username0w Dec 7, 2025
d52ed7e
feat: 질문 삭제 권한 체크 로직을 도메인으로 이동
username0w Dec 7, 2025
505ebb0
feat: 답변 삭제 권한 체크 로직을 도메인으로 이동
username0w Dec 7, 2025
2a20b56
refactor: 질문, 답변 삭제 상태 변경 로직을 도메인으로 이동
username0w Dec 7, 2025
88922da
refactor: 질문, 답변 삭제 시 삭제 이력 반환
username0w Dec 7, 2025
8109451
feat: Answers 추가 및 deleteAll 메서드 구현
username0w Dec 7, 2025
327afdb
refactor: Question의 Answer 컬렉션을 Answers로 전환하고 add 메서드 연결
username0w Dec 7, 2025
80b02db
feat: QuestionContent 추가 및 단위 테스트
username0w Dec 7, 2025
d3f264e
refactor: Question 클래스 title/contents 필드를 QuestionContent로 변경
username0w Dec 7, 2025
8643b28
feat: BaseTimeEntity, BaseEntity 클래스 생성
username0w Dec 7, 2025
a686182
refactor: Question 구조 개선 및 BaseTimeEntity/BaseEntity 적용
username0w Dec 7, 2025
0d50ca6
refactor: Answer 클래스 BaseTimeEntity/BaseEntity 적용
username0w Dec 10, 2025
9253673
refactor: setDeleted 를 markAsDeleted 로 변경
username0w Dec 10, 2025
cd6320e
refactor: 삭제 메서드 deleteWithHistory로 변경 및 책임 분리
username0w Dec 10, 2025
9307cb1
refactor: BaseEntity를 SoftDeletableBaseEntity로 변경
username0w Dec 11, 2025
d9aee8d
refactor: markAsDeleted 접근 제어자를 protected로 변경
username0w Dec 11, 2025
f967844
refactor: 삭제 상태 변경과 삭제 히스토리 생성 책임 분리
username0w Dec 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,18 @@
* 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다.

## 온라인 코드 리뷰 과정
* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview)
* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview)

---

## 기능 목록

### 질문 삭제하기

- 질문자 = 로그인 사용자 아니면 삭제 불가
- 답변 중 다른 사람이 쓴 답변이 있으면 삭제 불가
- 답변이 없으면 삭제 가능
- 질문자와 답변자가 모두 동일하면 삭제 가능
- 삭제되면 질문 상태 변경
- 삭제되면 모든 답변 상태 변경
- 삭제 이력 생성됨
72 changes: 29 additions & 43 deletions src/main/java/nextstep/qna/domain/Answer.java
Original file line number Diff line number Diff line change
@@ -1,67 +1,39 @@
package nextstep.qna.domain;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import nextstep.qna.CannotDeleteException;
import nextstep.qna.NotFoundException;
import nextstep.qna.UnAuthorizedException;
import nextstep.users.domain.NsUser;

import java.time.LocalDateTime;

public class Answer {
private Long id;

private NsUser writer;
public class Answer extends SoftDeletableBaseEntity {

private Question question;

private String contents;

private boolean deleted = false;

private LocalDateTime createdDate = LocalDateTime.now();

private LocalDateTime updatedDate;

public Answer() {
}

public Answer(NsUser writer, Question question, String contents) {
this(null, writer, 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;
super(id, writer);
this.question = question;
this.contents = contents;
}

public Long getId() {
return id;
}

public Answer setDeleted(boolean deleted) {
this.deleted = deleted;
return this;
}

public boolean isDeleted() {
return deleted;
}
public static Answer create(NsUser writer, Question question, String contents) {
if (writer == null) {
throw new UnAuthorizedException();
}

public boolean isOwner(NsUser writer) {
return this.writer.equals(writer);
}
if (question == null) {
throw new NotFoundException();
}

public NsUser getWriter() {
return writer;
return new Answer(null, writer, question, contents);
}

public String getContents() {
Expand All @@ -72,8 +44,22 @@ public void toQuestion(Question question) {
this.question = question;
}

public void delete(NsUser user) throws CannotDeleteException {
if (!isOwner(user)) {
throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다.");
}
markAsDeleted();
}

public List<DeleteHistory> toDeleteHistories() {
List<DeleteHistory> deleteHistories = new ArrayList<>();
deleteHistories.add(
new DeleteHistory(ContentType.ANSWER, getId(), getWriter(), LocalDateTime.now()));
return deleteHistories;
}

@Override
public String toString() {
return "Answer [id=" + getId() + ", writer=" + writer + ", contents=" + contents + "]";
return "Answer [id=" + getId() + ", writer=" + getWriter() + ", contents=" + contents + "]";
}
}
44 changes: 44 additions & 0 deletions src/main/java/nextstep/qna/domain/Answers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package nextstep.qna.domain;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import nextstep.qna.CannotDeleteException;
import nextstep.users.domain.NsUser;

public class Answers {
Copy link
Contributor

Choose a reason for hiding this comment

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

일급 콜렉션 👍


private final List<Answer> answers;

public Answers(Answer... answers) {
this(new ArrayList<>(Arrays.asList(answers)));
}

public Answers(List<Answer> answers) {
this.answers = answers;
}

public List<Answer> answers() {
return Collections.unmodifiableList(new ArrayList<>(answers));
}

public void add(Answer answer) {
answers.add(answer);
}

public void delete(NsUser user) throws CannotDeleteException {
for (Answer answer : answers) {
answer.delete(user);
}
}

public List<DeleteHistory> toDeleteHistories() throws CannotDeleteException {
List<DeleteHistory> deleteHistories = new ArrayList<>();
for (Answer answer : answers) {
deleteHistories.addAll(answer.toDeleteHistories());
}
return deleteHistories;
}

}
11 changes: 11 additions & 0 deletions src/main/java/nextstep/qna/domain/BaseTimeEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package nextstep.qna.domain;

import java.time.LocalDateTime;

public abstract class BaseTimeEntity {

private LocalDateTime createdDate = LocalDateTime.now();

private LocalDateTime updatedDate;

}
86 changes: 27 additions & 59 deletions src/main/java/nextstep/qna/domain/Question.java
Original file line number Diff line number Diff line change
@@ -1,92 +1,60 @@
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;

private String contents;

private NsUser writer;

private List<Answer> answers = new ArrayList<>();

private boolean deleted = false;

private LocalDateTime createdDate = LocalDateTime.now();

private LocalDateTime updatedDate;
public class Question extends SoftDeletableBaseEntity {
private QuestionContent content;
Copy link
Contributor

Choose a reason for hiding this comment

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

👍


public Question() {
}
private Answers answers = new Answers();

public Question(NsUser writer, String title, String contents) {
this(0L, writer, title, contents);
public Question(NsUser writer, QuestionContent content) {
this(0L, writer, content);
}

public Question(Long id, NsUser writer, String title, String contents) {
this.id = id;
this.writer = writer;
this.title = title;
this.contents = contents;
}

public Long getId() {
return id;
public Question(Long id, NsUser writer, QuestionContent content) {
super(id, writer);
this.content = content;
}

public String getTitle() {
return title;
}

public Question setTitle(String title) {
this.title = title;
return this;
return content.title();
}

public String getContents() {
return contents;
}

public Question setContents(String contents) {
this.contents = contents;
return this;
}

public NsUser getWriter() {
return writer;
return content.contents();
}

public void addAnswer(Answer answer) {
answer.toQuestion(this);
answers.add(answer);
}

public boolean isOwner(NsUser loginUser) {
return writer.equals(loginUser);
}

public Question setDeleted(boolean deleted) {
this.deleted = deleted;
return this;
public Answers getAnswers() {
return answers;
}

public boolean isDeleted() {
return deleted;
public void delete(NsUser user) throws CannotDeleteException {
if (!isOwner(user)) {
throw new CannotDeleteException("질문을 삭제할 권한이 없습니다.");
}
markAsDeleted();
answers.delete(user);
}

public List<Answer> getAnswers() {
return answers;
public List<DeleteHistory> toDeleteHistories() throws CannotDeleteException {
List<DeleteHistory> deleteHistories = new ArrayList<>();
deleteHistories.add(
new DeleteHistory(ContentType.QUESTION, getId(), getWriter(), LocalDateTime.now()));
deleteHistories.addAll(answers.toDeleteHistories());
return deleteHistories;
}

@Override
public String toString() {
return "Question [id=" + getId() + ", title=" + title + ", contents=" + contents + ", writer=" + writer + "]";
return "Question [id=" + getId() + ", content=" + content + ", writer=" + getWriter() + "]";
}
}
28 changes: 28 additions & 0 deletions src/main/java/nextstep/qna/domain/QuestionContent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package nextstep.qna.domain;

public class QuestionContent {

private final String title;

private final String contents;

public QuestionContent(String title, String contents) {
this.title = title;
this.contents = contents;
}

public String title() {
return title;
}

public String contents() {
return contents;
}

@Override
public String toString() {
return "QuestionContent{" +
", title=" + title + ", contents=" + contents +
'}';
}
}
38 changes: 38 additions & 0 deletions src/main/java/nextstep/qna/domain/SoftDeletableBaseEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package nextstep.qna.domain;

import nextstep.users.domain.NsUser;

public abstract class SoftDeletableBaseEntity extends BaseTimeEntity {

private Long id;

private NsUser writer;

private boolean deleted = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

soft delete로 삭제하는 컨텐츠의 경우 상속하는 의미로 BaseEntity보다 SoftDeletableBaseEntity와 같은 이름으로 구현하는 것은 어떨까?


protected SoftDeletableBaseEntity(Long id, NsUser writer) {
this.id = id;
this.writer = writer;
}

public Long getId() {
return id;
}

public NsUser getWriter() {
return writer;
}

protected void markAsDeleted() {
this.deleted = true;
}

public boolean isOwner(NsUser loginUser) {
return writer.equals(loginUser);
}

public boolean isDeleted() {
return deleted;
}

}
Loading