Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Q&A 기능 추가 #526

Merged
merged 64 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
dc709f1
feat: 질문과 답변에 대한 엔티티 추가
JJ503 Oct 3, 2023
df75dd7
feat: 질문 레포지토리 추가
JJ503 Oct 3, 2023
73a4b19
feat: 질문 등록 서비스 추가
JJ503 Oct 3, 2023
26078dd
feat: 질문 등록 api 추가
JJ503 Oct 3, 2023
4b7fb84
test: QuestionService 테스트 추가
JJ503 Oct 3, 2023
e14af04
feat: 답변 레포지토리 추가
JJ503 Oct 3, 2023
d81ff04
feat: 답변 등록 서비스 추가
JJ503 Oct 3, 2023
e0e6e8e
refactor: 컨트롤러 클래스 명 수정
JJ503 Oct 3, 2023
0d5f85f
feat: 답변 등록 api 추가
JJ503 Oct 3, 2023
ab415a0
feat: 질문 및 답변 조회 레파지토리 추가
JJ503 Oct 3, 2023
f54d0a9
feat: 질문 및 답변 조회 서비스 추가
JJ503 Oct 3, 2023
59fb738
rename: 하위 패키지 생성
JJ503 Oct 3, 2023
4224840
feat: 질문과 답변 목록 전체 조회 api 추가
JJ503 Oct 4, 2023
29076c3
test: 테스트 픽스처 접근 제어자 설정
JJ503 Oct 4, 2023
97aa6ea
docs: 고민 todo 추가
JJ503 Oct 4, 2023
12c6e3d
feat: 질문과 답변관련 flyway 스크립트 추가
JJ503 Oct 4, 2023
7cfcd3a
docs: api 문서 최신화
JJ503 Oct 4, 2023
0146a4e
refactor: 불필요한 개행 제거
JJ503 Oct 4, 2023
68c9497
feat: 경매 조회시 삭제된 경매를 제외해주는 메서드 추가
JJ503 Oct 4, 2023
3559bcd
refactor: 경매 조회시 삭제된 경매는 존재하지 않는 경매로 처리되도록 수정
JJ503 Oct 4, 2023
66b18d6
refactor: 개행 추가
JJ503 Oct 5, 2023
e35adcb
test: 불필요한 코드 제거
JJ503 Oct 5, 2023
636d934
test: 컨벤션에 맞춰 개행
JJ503 Oct 5, 2023
1d7e896
style: todo 제거
JJ503 Oct 5, 2023
1611cff
refactor: 예외처리를 적절한 커스텀 예외로 변경
JJ503 Oct 5, 2023
98ac03b
rename: QuestionAndAnswer를 Qna로 축약해 사용
JJ503 Oct 5, 2023
544ad4c
ci: 브랜치 최신화
JJ503 Oct 5, 2023
493260b
ci: 브랜치 최신화 충돌 문제 해결
JJ503 Oct 5, 2023
ea5df21
feat: 질문 삭제 기능 추가
JJ503 Oct 5, 2023
410426b
feat: 질문 삭제 기능 서비스 추가
JJ503 Oct 5, 2023
082d7d5
feat: 질문 삭제 기능 api 추가
JJ503 Oct 5, 2023
e33718c
refactor: 삭제된 질문은 조회되지 않도록 수정
JJ503 Oct 5, 2023
ce53640
test: 메서드 네이밍 수정
JJ503 Oct 5, 2023
5866b56
feat: 답변 삭제 기능 서비스 추가
JJ503 Oct 5, 2023
0dba066
feat: 답변 삭제 api 추가
JJ503 Oct 5, 2023
a1b178a
feat: 질문과 답변에 삭제 여부 필드 추가
JJ503 Oct 6, 2023
4da8433
feat: 질문 신고 엔티티, 레파지토리 추가
JJ503 Oct 6, 2023
16e2029
feat: 트랜잭션 어노테이션 추가
JJ503 Oct 6, 2023
7c50d90
feat: 질문 신고 등록 서비스 추가
JJ503 Oct 6, 2023
ae7751b
feat: 질문 신고 등록 api 추가
JJ503 Oct 6, 2023
4912784
feat: 질문 신고 조회 레포지토리 추가
JJ503 Oct 6, 2023
5a5896f
feat: 질문 신고 조회 서비스 추가
JJ503 Oct 6, 2023
ec7fdca
feat: 질문 신고 조회 api 추가
JJ503 Oct 6, 2023
0f0d3e9
feat: 답변 신고 엔티티, 레포지토리 추가
JJ503 Oct 6, 2023
1771054
feat: 답변 신고 등록 서비스 추가
JJ503 Oct 6, 2023
026d62d
feat: 답변 신고 등록 api 추가
JJ503 Oct 6, 2023
c06b726
feat: 답변 신고 조회 레포지토리 추가
JJ503 Oct 6, 2023
7d27cca
feat: 답변 신고 조회 서비스 추가 및 테스트 수정
JJ503 Oct 6, 2023
ef8a11d
feat: 답변 신고 조회 api 추가
JJ503 Oct 6, 2023
a929963
feat: flyway 스크립트 report 테이블 생성 쿼리 추가
JJ503 Oct 6, 2023
091bb9a
ci: 브랜치 최신화
JJ503 Oct 7, 2023
8d7c84a
ci: 충돌문제 해결
JJ503 Oct 7, 2023
3a20334
fix: 답변의 작성자 조회 시 질문된 경매의 판매자를 전달하도록 수정
JJ503 Oct 9, 2023
14f1477
test: 누락된 테스트 추가
JJ503 Oct 9, 2023
2370238
test: 누락된 테스트 추가
JJ503 Oct 9, 2023
c49fac1
refactor: import 와일드카드 제거
JJ503 Oct 9, 2023
e726017
refactor: 누락된 final 추가
JJ503 Oct 9, 2023
f35b69f
refactor: 컨벤션에 따른 개행 추가
JJ503 Oct 9, 2023
7ad8437
docs: 누락된 api 문서화 추가
JJ503 Oct 9, 2023
5285c0c
test: 누락된 테스트 추가
JJ503 Oct 9, 2023
e820761
fix: 신고 존재여부 조회 시 의도와 다른 로직 문제 해결
JJ503 Oct 9, 2023
b99d36c
test: 테스트 픽스처 수정
JJ503 Oct 9, 2023
624edc3
ci: 브랜치 최신화
JJ503 Oct 9, 2023
e79559b
test: 메서드 명 수정
JJ503 Oct 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.ddang.ddang.image.infrastructure.local.exception.UnsupportedImageFileExtensionException;
import com.ddang.ddang.notification.application.exception.NotificationFailedException;
import com.ddang.ddang.qna.application.exception.AlreadyAnsweredException;
import com.ddang.ddang.qna.application.exception.AnswerNotFoundException;
import com.ddang.ddang.qna.application.exception.InvalidAnswererException;
import com.ddang.ddang.qna.application.exception.InvalidAuctionToAskQuestionException;
import com.ddang.ddang.qna.application.exception.InvalidQuestionerException;
Expand All @@ -32,6 +33,7 @@
import com.ddang.ddang.report.application.exception.AlreadyReportAuctionException;
import com.ddang.ddang.report.application.exception.AlreadyReportChatRoomException;
import com.ddang.ddang.report.application.exception.InvalidChatRoomReportException;
import com.ddang.ddang.report.application.exception.InvalidQuestionReportException;
import com.ddang.ddang.report.application.exception.InvalidReportAuctionException;
import com.ddang.ddang.report.application.exception.InvalidReporterToAuctionException;
import com.ddang.ddang.user.application.exception.UserNotFoundException;
Expand Down Expand Up @@ -330,39 +332,55 @@ public ResponseEntity<ExceptionResponse> handleDeviceTokenNotFoundException(fina

@ExceptionHandler(InvalidAuctionToAskQuestionException.class)
public ResponseEntity<ExceptionResponse> handleInvalidAuctionToAskQuestionException(final InvalidAuctionToAskQuestionException ex) {
logger.warn(String.format(EXCEPTION_FORMAT, InvalidAuctionToAskQuestionException.class), ex);
logger.warn(String.format(LOG_MESSAGE_FORMAT, ex.getClass().getSimpleName(), ex.getMessage()));

return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ExceptionResponse(ex.getMessage()));
}

@ExceptionHandler(InvalidQuestionerException.class)
public ResponseEntity<ExceptionResponse> handleInvalidQuestionerException(final InvalidQuestionerException ex) {
logger.warn(String.format(EXCEPTION_FORMAT, InvalidQuestionerException.class), ex);
logger.warn(String.format(LOG_MESSAGE_FORMAT, ex.getClass().getSimpleName(), ex.getMessage()));

return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ExceptionResponse(ex.getMessage()));
}

@ExceptionHandler(QuestionNotFoundException.class)
public ResponseEntity<ExceptionResponse> handleQuestionNotFoundException(final QuestionNotFoundException ex) {
logger.warn(String.format(EXCEPTION_FORMAT, QuestionNotFoundException.class), ex);
logger.warn(String.format(LOG_MESSAGE_FORMAT, ex.getClass().getSimpleName(), ex.getMessage()));

return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ExceptionResponse(ex.getMessage()));
}

@ExceptionHandler(AnswerNotFoundException.class)
public ResponseEntity<ExceptionResponse> handleAnswerNotFoundException(final AnswerNotFoundException ex) {
logger.warn(String.format(LOG_MESSAGE_FORMAT, ex.getClass().getSimpleName(), ex.getMessage()));

return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ExceptionResponse(ex.getMessage()));
}

@ExceptionHandler(InvalidAnswererException.class)
public ResponseEntity<ExceptionResponse> handleInvalidAnswererException(final InvalidAnswererException ex) {
logger.warn(String.format(EXCEPTION_FORMAT, InvalidAnswererException.class), ex);
logger.warn(String.format(LOG_MESSAGE_FORMAT, ex.getClass().getSimpleName(), ex.getMessage()));

return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ExceptionResponse(ex.getMessage()));
}

@ExceptionHandler(InvalidQuestionReportException.class)
public ResponseEntity<ExceptionResponse> handleInvalidQuestionReportException(final InvalidQuestionReportException ex) {
logger.warn(String.format(LOG_MESSAGE_FORMAT, ex.getClass().getSimpleName(), ex.getMessage()));

return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ExceptionResponse(ex.getMessage()));
}

@ExceptionHandler(AlreadyAnsweredException.class)
public ResponseEntity<ExceptionResponse> handleAlreadyAnsweredException(final AlreadyAnsweredException ex) {
logger.warn(String.format(EXCEPTION_FORMAT, AlreadyAnsweredException.class), ex);
logger.warn(String.format(LOG_MESSAGE_FORMAT, ex.getClass().getSimpleName(), ex.getMessage()));

return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ExceptionResponse(ex.getMessage()));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.ddang.ddang.qna.application;

import com.ddang.ddang.auction.application.exception.UserForbiddenException;
import com.ddang.ddang.qna.application.dto.CreateAnswerDto;
import com.ddang.ddang.qna.application.exception.AlreadyAnsweredException;
import com.ddang.ddang.qna.application.exception.AnswerNotFoundException;
import com.ddang.ddang.qna.application.exception.InvalidAnswererException;
import com.ddang.ddang.qna.application.exception.QuestionNotFoundException;
import com.ddang.ddang.qna.domain.Answer;
Expand Down Expand Up @@ -54,4 +56,18 @@ private void checkAlreadyAnswered(final Question question) {
throw new AlreadyAnsweredException("이미 답변한 질문입니다.");
}
}

@Transactional
public void deleteById(final Long answerId, final Long userId) {
final Answer answer = answerRepository.findByIdAndDeletedIsFalse(answerId)
.orElseThrow(() -> new AnswerNotFoundException("해당 답변을 찾을 수 없습니다."));
final User user = userRepository.findByIdAndDeletedIsFalse(userId)
.orElseThrow(() -> new UserNotFoundException("해당 사용자를 찾을 수 없습니다."));

if (!answer.isWriter(user)) {
throw new UserForbiddenException("삭제할 권한이 없습니다.");
}

answer.delete();
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.ddang.ddang.qna.application;

import com.ddang.ddang.auction.application.exception.AuctionNotFoundException;
import com.ddang.ddang.auction.application.exception.UserForbiddenException;
import com.ddang.ddang.auction.domain.Auction;
import com.ddang.ddang.auction.infrastructure.persistence.JpaAuctionRepository;
import com.ddang.ddang.qna.application.dto.CreateQuestionDto;
import com.ddang.ddang.qna.application.dto.ReadQnasDto;
import com.ddang.ddang.qna.application.exception.InvalidAuctionToAskQuestionException;
import com.ddang.ddang.qna.application.exception.InvalidQuestionerException;
import com.ddang.ddang.qna.application.exception.QuestionNotFoundException;
import com.ddang.ddang.qna.domain.Question;
import com.ddang.ddang.qna.infrastructure.JpaQuestionRepository;
import com.ddang.ddang.user.application.exception.UserNotFoundException;
Expand All @@ -28,6 +30,7 @@ public class QuestionService {
private final JpaUserRepository userRepository;
private final JpaQuestionRepository questionRepository;

@Transactional
public Long create(final CreateQuestionDto questionDto) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

칭찬

헉 이럴수가.. 리뷰 빼먹었었네요.. 칭찬해요!

final User questioner = userRepository.findByIdAndDeletedIsFalse(questionDto.userId())
.orElseThrow(() -> new UserNotFoundException("해당 사용자를 찾을 수 없습니다."));
Expand Down Expand Up @@ -60,8 +63,22 @@ public ReadQnasDto readAllByAuctionId(final Long auctionId) {
throw new AuctionNotFoundException("해당 경매를 찾을 수 없습니다.");
}

final List<Question> questions = questionRepository.readAllByAuctionId(auctionId);
final List<Question> questions = questionRepository.findAllByAuctionId(auctionId);

return ReadQnasDto.from(questions);
}

@Transactional
public void deleteById(final Long questionId, final Long userId) {
final Question question = questionRepository.findById(questionId)
.orElseThrow(() -> new QuestionNotFoundException("해당 질문을 찾을 수 없습니다."));
final User user = userRepository.findByIdAndDeletedIsFalse(userId)
.orElseThrow(() -> new UserNotFoundException("해당 사용자를 찾을 수 없습니다."));

if (!question.isWriter(user)) {
throw new UserForbiddenException("삭제할 권한이 없습니다.");
}

question.delete();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.ddang.ddang.qna.application.exception;

public class AnswerNotFoundException extends IllegalArgumentException {

public AnswerNotFoundException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.ddang.ddang.qna.domain;

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;
Expand All @@ -20,9 +21,11 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@EqualsAndHashCode(of = "id", callSuper = false)
@ToString(of = {"id", "content"})
@ToString(of = {"id", "content", "deleted"})
public class Answer extends BaseCreateTimeEntity {

private static final boolean DELETED_STATUS = true;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Expand All @@ -34,11 +37,26 @@ public class Answer extends BaseCreateTimeEntity {
@Column(columnDefinition = "text")
private String content;

@Column(name = "is_deleted")
private boolean deleted = false;

public Answer(final String content) {
this.content = content;
}

public void initQuestion(final Question question) {
this.question = question;
}

public boolean isWriter(final User user) {
return question.getAuction().isOwner(user);
}
Comment on lines +51 to +53
Copy link
Collaborator

Choose a reason for hiding this comment

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

필수

isWriter() 테스트코드가 없네요

Copy link
Member Author

Choose a reason for hiding this comment

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

아이구.. 감사합니다 🙇‍♀️


public void delete() {
deleted = DELETED_STATUS;
}

public User getWriter() {
return question.getWriter();
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

필수

Answer의 writer는 auction의 seller가 되지 않을까요?

Suggested change
public User getWriter() {
return question.getWriter();
}
public User getWriter() {
return question.getAuction().getSeller();
}

Copy link
Member Author

@JJ503 JJ503 Oct 9, 2023

Choose a reason for hiding this comment

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

헉... 정말 큰일 날 뻔 헀네요.
고마워요 엔초!

Copy link
Collaborator

Choose a reason for hiding this comment

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

필수

추가된 메서드들에 대한 테스트코드가 없는 것 같아요!

Copy link
Member Author

Choose a reason for hiding this comment

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

확인감사합니다. 추가해두었습니다!

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@EqualsAndHashCode(of = "id", callSuper = false)
@ToString(of = {"id", "content"})
@ToString(of = {"id", "content", "deleted"})
public class Question extends BaseCreateTimeEntity {

private static final boolean DELETED_STATUS = true;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Expand All @@ -44,6 +46,9 @@ public class Question extends BaseCreateTimeEntity {
@OneToOne(mappedBy = "question")
private Answer answer;

@Column(name = "is_deleted")
private boolean deleted = false;

public Question(final Auction auction, final User writer, final String content) {
this.auction = auction;
this.writer = writer;
Expand All @@ -58,4 +63,12 @@ public void addAnswer(final Answer answer) {
public boolean isAnsweringAllowed(final User user) {
return auction.isOwner(user);
}

public boolean isWriter(final User user) {
return writer.equals(user);
}

public void delete() {
deleted = DELETED_STATUS;
}
Comment on lines +71 to +73
Copy link
Collaborator

Choose a reason for hiding this comment

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

질문

Question을 삭제해도 Answer는 그대로 남아있는 건가요?

Copy link
Member Author

Choose a reason for hiding this comment

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

저도 해당 부분을 고민하다 Anwer의 경우 가져오는 방법이 Question을 통해서밖에 없어 굳이 삭제처리를 않았습니다.
그런데 갑자기 드는 생각으로는 Question은 삭제되었음으로 표현되고 Answer는 보여야 하지 않을까 싶기도 하네요!
해당 부분에 대해 처리해두도록 하겠습니다..!

Comment on lines +67 to +73
Copy link
Collaborator

Choose a reason for hiding this comment

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

필수

여기도 새로 추가된 메서드들에 대한 테스트코드가 없습니다!

Copy link
Member Author

Choose a reason for hiding this comment

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

자코코.. 생각보다 관대하군요..?ㅎㅎ
delete()관련 테스트는 이미 존재했기에 isWriter() 테스트만 추가해 두었습니다.
감사합니다!

Copy link
Collaborator

Choose a reason for hiding this comment

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

필수

isDelete() 테스트만 있고. delete() 테스트는 없는 것 같아요!

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package com.ddang.ddang.qna.infrastructure;

import com.ddang.ddang.qna.domain.Answer;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface JpaAnswerRepository extends JpaRepository<Answer, Long> {

boolean existsByQuestionId(Long questionId);

@EntityGraph(attributePaths = {"question", "question.auction", "question.auction.seller"})
Optional<Answer> findByIdAndDeletedIsFalse(Long answerId);
Copy link
Collaborator

Choose a reason for hiding this comment

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

필수

바로 아래 JpaQuestionRepository에는 파라미터에 final이 붙어있는데 여기에는 누락된 것 같아요!

Copy link
Member Author

Choose a reason for hiding this comment

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

추가했습니다. 감사합니다!

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import java.util.Optional;

public interface JpaQuestionRepository extends JpaRepository<Question, Long> {

Optional<Question> findByIdAndDeletedIsFalse(final Long id);

@EntityGraph(attributePaths = {"writer", "answer", "auction", "auction.seller"})
List<Question> readAllByAuctionId(final Long auctionId);
List<Question> findAllByAuctionId(final Long auctionId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand Down Expand Up @@ -49,4 +50,26 @@ public ResponseEntity<Void> createAnswer(
return ResponseEntity.created(URI.create("/auctions/" + answerRequest.auctionId()))
.build();
}

@DeleteMapping("/{questionId}")
public ResponseEntity<Void> deleteQuestion(
@AuthenticateUser AuthenticationUserInfo userInfo,
@PathVariable final Long questionId
) {
questionService.deleteById(questionId, userInfo.userId());

return ResponseEntity.noContent()
.build();
}

@DeleteMapping("/answers/{answerId}")
public ResponseEntity<Void> deleteAnswer(
@AuthenticateUser AuthenticationUserInfo userInfo,
@PathVariable final Long answerId
) {
answerService.deleteById(answerId, userInfo.userId());

return ResponseEntity.noContent()
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.ddang.ddang.report.application;

import com.ddang.ddang.qna.application.exception.AnswerNotFoundException;
import com.ddang.ddang.qna.domain.Answer;
import com.ddang.ddang.qna.infrastructure.JpaAnswerRepository;
import com.ddang.ddang.report.application.dto.CreateAnswerReportDto;
import com.ddang.ddang.report.application.dto.ReadAnswerReportDto;
import com.ddang.ddang.report.application.exception.InvalidAnswererReportException;
import com.ddang.ddang.report.domain.AnswerReport;
import com.ddang.ddang.report.infrastructure.persistence.JpaAnswerReportRepository;
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.util.List;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class AnswerReportService {

private final JpaAnswerRepository answerRepository;
private final JpaUserRepository userRepository;
private final JpaAnswerReportRepository answerReportRepository;

@Transactional
public Long create(final CreateAnswerReportDto answerReportDto) {
final Answer answer = answerRepository.findByIdAndDeletedIsFalse(answerReportDto.answerId())
.orElseThrow(() ->
new AnswerNotFoundException("해당 답변을 찾을 수 없습니다.")
);
final User reporter = userRepository.findByIdAndDeletedIsFalse(answerReportDto.reporterId())
.orElseThrow(() -> new UserNotFoundException("해당 사용자를 찾을 수 없습니다."));
checkInvalidAnswerReport(reporter, answer);

final AnswerReport answerReport = answerReportDto.toEntity(answer, reporter);

return answerReportRepository.save(answerReport)
.getId();
}

private void checkInvalidAnswerReport(final User reporter, final Answer answer) {
if (answer.isWriter(reporter)) {
throw new InvalidAnswererReportException("본인 답변입니다.");
}
if (answerReportRepository.existsByIdAndReporterId(answer.getId(), reporter.getId())) {
throw new InvalidAnswererReportException("이미 신고한 답변입니다.");
}
}

public List<ReadAnswerReportDto> readAll() {
final List<AnswerReport> answerReports = answerReportRepository.findAllByOrderByIdAsc();

return answerReports.stream()
.map(ReadAnswerReportDto::from)
.toList();
}
}
Loading