-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Q&A 기능 추가 #526
Q&A 기능 추가 #526
Changes from 9 commits
dc709f1
df75dd7
73a4b19
26078dd
4b7fb84
e14af04
d81ff04
e0e6e8e
0d5f85f
ab415a0
f54d0a9
59fb738
4224840
29076c3
97aa6ea
12c6e3d
7cfcd3a
0146a4e
68c9497
3559bcd
66b18d6
e35adcb
636d934
1d7e896
1611cff
98ac03b
544ad4c
493260b
ea5df21
410426b
082d7d5
e33718c
ce53640
5866b56
0dba066
a1b178a
4da8433
16e2029
7c50d90
ae7751b
4912784
5a5896f
ec7fdca
0f0d3e9
1771054
026d62d
c06b726
7d27cca
ef8a11d
a929963
091bb9a
8d7c84a
3a20334
14f1477
2370238
c49fac1
e726017
f35b69f
7ad8437
5285c0c
e820761
b99d36c
624edc3
e79559b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package com.ddang.ddang.questionandanswer.application; | ||
|
||
import com.ddang.ddang.auction.application.exception.AuctionNotFoundException; | ||
import com.ddang.ddang.questionandanswer.application.dto.CreateAnswerDto; | ||
import com.ddang.ddang.questionandanswer.application.exception.AlreadyAnsweredException; | ||
import com.ddang.ddang.questionandanswer.application.exception.InvalidAnswererException; | ||
import com.ddang.ddang.questionandanswer.domain.Answer; | ||
import com.ddang.ddang.questionandanswer.domain.Question; | ||
import com.ddang.ddang.questionandanswer.infrastructure.JpaAnswerRepository; | ||
import com.ddang.ddang.questionandanswer.infrastructure.JpaQuestionRepository; | ||
import com.ddang.ddang.user.application.exception.UserNotFoundException; | ||
import com.ddang.ddang.user.domain.User; | ||
import com.ddang.ddang.user.infrastructure.persistence.JpaUserRepository; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Service | ||
@Transactional(readOnly = true) | ||
@RequiredArgsConstructor | ||
public class AnswerService { | ||
|
||
private final JpaUserRepository userRepository; | ||
private final JpaQuestionRepository questionRepository; | ||
private final JpaAnswerRepository answerRepository; | ||
|
||
|
||
@Transactional | ||
public Long create(final CreateAnswerDto answerDto) { | ||
final User writer = userRepository.findById(answerDto.userId()) | ||
.orElseThrow(() -> new UserNotFoundException("해당 사용자를 찾을 수 없습니다.")); | ||
final Question question = questionRepository.findById(answerDto.questionId()) | ||
.orElseThrow(() -> | ||
new AuctionNotFoundException("해당 질문을 찾을 수 없습니다.") | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 필수질문을 찾을 수 없는 것인데 AuctionNotFoundException이 터지네요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아니 무슨일이죠... |
||
|
||
checkInvalidAnswerer(question, writer); | ||
checkAlreadyAnswered(question); | ||
|
||
final Answer answer = answerDto.toEntity(); | ||
question.addAnswer(answer); | ||
|
||
return answerRepository.save(answer) | ||
.getId(); | ||
} | ||
|
||
private void checkInvalidAnswerer(final Question question, final User writer) { | ||
if (!question.isAnsweringAllowed(writer)) { | ||
throw new InvalidAnswererException("판매자만 답변할 수 있습니다."); | ||
} | ||
} | ||
|
||
private void checkAlreadyAnswered(final Question question) { | ||
if (answerRepository.existsByQuestionId(question.getId())) { | ||
throw new AlreadyAnsweredException("이미 답변한 질문입니다."); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package com.ddang.ddang.questionandanswer.application; | ||
|
||
import com.ddang.ddang.auction.application.exception.AuctionNotFoundException; | ||
import com.ddang.ddang.auction.domain.Auction; | ||
import com.ddang.ddang.auction.infrastructure.persistence.JpaAuctionRepository; | ||
import com.ddang.ddang.questionandanswer.application.dto.CreateQuestionDto; | ||
import com.ddang.ddang.questionandanswer.application.exception.InvalidAuctionToAskQuestionException; | ||
import com.ddang.ddang.questionandanswer.application.exception.InvalidQuestionerException; | ||
import com.ddang.ddang.questionandanswer.domain.Question; | ||
import com.ddang.ddang.questionandanswer.infrastructure.JpaQuestionRepository; | ||
import com.ddang.ddang.user.application.exception.UserNotFoundException; | ||
import com.ddang.ddang.user.domain.User; | ||
import com.ddang.ddang.user.infrastructure.persistence.JpaUserRepository; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
@Service | ||
@Transactional(readOnly = true) | ||
@RequiredArgsConstructor | ||
public class QuestionService { | ||
|
||
private final JpaAuctionRepository auctionRepository; | ||
private final JpaUserRepository userRepository; | ||
private final JpaQuestionRepository questionRepository; | ||
|
||
public Long create(final CreateQuestionDto questionDto) { | ||
final User questioner = userRepository.findByIdAndDeletedIsFalse(questionDto.userId()) | ||
.orElseThrow(() -> new UserNotFoundException("해당 사용자를 찾을 수 없습니다.")); | ||
final Auction auction = auctionRepository.findById(questionDto.auctionId()) | ||
.orElseThrow(() -> new AuctionNotFoundException("해당 경매를 찾을 수 없습니다.")); | ||
checkInvalidAuction(auction); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 선택여기 개행..? |
||
checkInvalidQuestioner(auction, questioner); | ||
|
||
final Question question = questionDto.toEntity(auction, questioner); | ||
|
||
return questionRepository.save(question) | ||
.getId(); | ||
} | ||
|
||
private void checkInvalidAuction(final Auction auction) { | ||
final LocalDateTime now = LocalDateTime.now(); | ||
|
||
if (auction.isDeleted()) { | ||
throw new InvalidAuctionToAskQuestionException("삭제된 경매입니다."); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 질문DB에서 조회할 때 처음부터 삭제되지 않은 경매만 조회할 수 있을 것 같은데, 조건 없이 조회해 서비스 단에서 검증해주신 이유가 있으실까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 질문저도 궁금합니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 다른 기능 구현 시 이와 동일하게 진행했기 때문입니다. |
||
if (auction.isClosed(now)) { | ||
throw new InvalidAuctionToAskQuestionException("이미 종료된 경매입니다."); | ||
} | ||
} | ||
|
||
private void checkInvalidQuestioner(final Auction auction, final User questioner) { | ||
if (auction.isOwner(questioner)) { | ||
throw new InvalidQuestionerException("경매 등록자는 질문할 수 없습니다."); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.ddang.ddang.questionandanswer.application.dto; | ||
|
||
import com.ddang.ddang.questionandanswer.domain.Answer; | ||
import com.ddang.ddang.questionandanswer.presentation.dto.CreateAnswerRequest; | ||
|
||
public record CreateAnswerDto(Long questionId, String content, Long userId) { | ||
|
||
public static CreateAnswerDto of(final Long questionId, final CreateAnswerRequest answerRequest, final Long userId) { | ||
return new CreateAnswerDto(questionId, answerRequest.content(), userId); | ||
} | ||
|
||
public Answer toEntity() { | ||
return new Answer(content); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.ddang.ddang.questionandanswer.application.dto; | ||
|
||
import com.ddang.ddang.auction.domain.Auction; | ||
import com.ddang.ddang.questionandanswer.domain.Question; | ||
import com.ddang.ddang.questionandanswer.presentation.dto.CreateQuestionRequest; | ||
import com.ddang.ddang.user.domain.User; | ||
|
||
public record CreateQuestionDto(Long auctionId, String content, Long userId) { | ||
|
||
public static CreateQuestionDto of(final CreateQuestionRequest questionRequest, final Long userId) { | ||
return new CreateQuestionDto(questionRequest.auctionId(), questionRequest.content(), userId); | ||
} | ||
|
||
public Question toEntity(final Auction auction, final User questioner) { | ||
return new Question(auction, questioner, content); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.ddang.ddang.questionandanswer.application.exception; | ||
|
||
public class AlreadyAnsweredException extends IllegalArgumentException { | ||
|
||
public AlreadyAnsweredException(final String message) { | ||
super(message); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.ddang.ddang.questionandanswer.application.exception; | ||
|
||
public class InvalidAnswererException extends IllegalArgumentException { | ||
|
||
public InvalidAnswererException(final String message) { | ||
super(message); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.ddang.ddang.questionandanswer.application.exception; | ||
|
||
public class InvalidAuctionToAskQuestionException extends IllegalArgumentException { | ||
|
||
public InvalidAuctionToAskQuestionException(final String message) { | ||
super(message); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.ddang.ddang.questionandanswer.application.exception; | ||
|
||
public class InvalidQuestionerException extends IllegalArgumentException { | ||
|
||
public InvalidQuestionerException(final String message) { | ||
super(message); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package com.ddang.ddang.questionandanswer.domain; | ||
|
||
import com.ddang.ddang.common.entity.BaseCreateTimeEntity; | ||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.FetchType; | ||
import jakarta.persistence.ForeignKey; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.JoinColumn; | ||
import jakarta.persistence.OneToOne; | ||
import lombok.AccessLevel; | ||
import lombok.EqualsAndHashCode; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
import lombok.ToString; | ||
|
||
@Entity | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
@Getter | ||
@EqualsAndHashCode(of = "id") | ||
@ToString(of = {"id", "content"}) | ||
public class Answer extends BaseCreateTimeEntity { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@OneToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "question_id", foreignKey = @ForeignKey(name = "fk_answer_question")) | ||
private Question question; | ||
|
||
@Column(columnDefinition = "text") | ||
private String content; | ||
|
||
public Answer(final String content) { | ||
this.content = content; | ||
} | ||
|
||
public void initQuestion(final Question question) { | ||
this.question = question; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package com.ddang.ddang.questionandanswer.domain; | ||
|
||
import com.ddang.ddang.auction.domain.Auction; | ||
import com.ddang.ddang.common.entity.BaseCreateTimeEntity; | ||
import com.ddang.ddang.user.domain.User; | ||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.FetchType; | ||
import jakarta.persistence.ForeignKey; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.JoinColumn; | ||
import jakarta.persistence.ManyToOne; | ||
import jakarta.persistence.OneToOne; | ||
import lombok.AccessLevel; | ||
import lombok.EqualsAndHashCode; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
import lombok.ToString; | ||
|
||
@Entity | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
@Getter | ||
@EqualsAndHashCode(of = "id") | ||
@ToString(of = {"id", "content"}) | ||
public class Question extends BaseCreateTimeEntity { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "auction_id", foreignKey = @ForeignKey(name = "fk_question_auction")) | ||
private Auction auction; | ||
|
||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "wrtier_id", foreignKey = @ForeignKey(name = "fk_question_writer")) | ||
private User writer; | ||
|
||
@Column(columnDefinition = "text") | ||
private String content; | ||
|
||
@OneToOne(mappedBy = "question") | ||
private Answer answer; | ||
|
||
public Question(final Auction auction, final User writer, final String content) { | ||
this.auction = auction; | ||
this.writer = writer; | ||
this.content = content; | ||
} | ||
|
||
public void addAnswer(final Answer answer) { | ||
this.answer = answer; | ||
answer.initQuestion(this); | ||
} | ||
|
||
public boolean isAnsweringAllowed(final User user) { | ||
return auction.isOwner(user); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.ddang.ddang.questionandanswer.infrastructure; | ||
|
||
import com.ddang.ddang.questionandanswer.domain.Answer; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
public interface JpaAnswerRepository extends JpaRepository<Answer, Long> { | ||
|
||
boolean existsByQuestionId(Long questionId); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.ddang.ddang.questionandanswer.infrastructure; | ||
|
||
import com.ddang.ddang.questionandanswer.domain.Question; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
public interface JpaQuestionRepository extends JpaRepository<Question, Long> { | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
선택
개행 2줄..?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이... 이게 무슨일이야..!
감사합니다 🙇♀️