Skip to content

Commit

Permalink
Feat: 문의사항 삭제 API 구현 (#62)
Browse files Browse the repository at this point in the history
* Feat: 문의사항 삭제 API 구현 (#32)

- 문의사항 삭제 기능
- 잘못된 문의사항 ID -> 예외처리

* Feat: 문의사항 삭제 API 테스트코드 작성 (#57)

- 삭제 기능 테스트코드
- Question ID가 없을 때 예외 처리 테스트코드

* Fix: 테스트용 반복문 삭제
  • Loading branch information
DongkwanKim00 authored Aug 1, 2024
1 parent fc32917 commit 5d6698f
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,62 @@
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import kea.enter.enterbe.api.penalty.service.dto.DeletePenaltyServiceDto;
import kea.enter.enterbe.api.question.controller.dto.request.QuestionRequestDto;
import kea.enter.enterbe.api.question.controller.dto.response.QuestionResponseDto;
import kea.enter.enterbe.api.question.service.QuestionService;
import kea.enter.enterbe.api.question.service.dto.DeleteQuestionServiceDto;
import kea.enter.enterbe.domain.question.entity.Question;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
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;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import static kea.enter.enterbe.global.common.api.CustomResponseCode.SUCCESS;

@RestController
@RequestMapping("/questions")
@RequiredArgsConstructor
@Tag(name = "문의사항 작성", description = "문의사항 작성 API")
@Tag(name = "문의사항", description = "문의사항 API")
public class QuestionController {

private final QuestionService questionService;

@Operation(summary = "사용자가 작성한 문의사항 저장")
/* 문의사항 작성 API */
@Operation(summary = "문의사항 작성 API")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "작성이 완료되었습니다.", content = @Content(mediaType = "application/json")),
@ApiResponse(responseCode = "200", description = "요청에 성공하였습니다.", content = @Content(mediaType = "application/json")),
@ApiResponse(responseCode = "MEM-ERR-001", description = "멤버가 존재하지 않습니다.", content = @Content(mediaType = "application/json")),
@ApiResponse(responseCode = "GLB-ERR-001", description = "필수 입력칸이 입력되지 않았습니다.", content = @Content(mediaType = "application/json")),
})
@PostMapping
public ResponseEntity<QuestionResponseDto> createQuestion(
public ResponseEntity<String> createQuestion(
@Valid @RequestBody QuestionRequestDto dto) {

questionService.createQuestion(dto);

QuestionResponseDto responseDto = new QuestionResponseDto("작성이 완료되었습니다.");
return new ResponseEntity<>(responseDto, HttpStatus.CREATED);
return ResponseEntity.ok(SUCCESS.getMessage());
}

/* 문의사항 삭제 API */
@Operation(summary = "문의사항 삭제 API")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "요청에 성공하였습니다.", content = @Content(mediaType = "application/json")),
@ApiResponse(responseCode = "MEM-ERR-001", description = "멤버가 존재하지 않습니다.", content = @Content(mediaType = "application/json")),
@ApiResponse(responseCode = "QST-ERR-001", description = "문의사항이 존재하지 않습니다.", content = @Content(mediaType = "application/json")),
})
@DeleteMapping("/{memberId}/{questionId}")
public ResponseEntity<String> deleteQuestion(
@PathVariable Long memberId,
@PathVariable Long questionId) {
// TODO: 어드민 권한 검사
questionService.deleteQuestion(DeleteQuestionServiceDto.of(memberId, questionId));
return ResponseEntity.ok(SUCCESS.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package kea.enter.enterbe.api.question.service;

import kea.enter.enterbe.api.question.controller.dto.request.QuestionRequestDto;
import kea.enter.enterbe.api.question.service.dto.DeleteQuestionServiceDto;
import kea.enter.enterbe.domain.member.entity.Member;
import kea.enter.enterbe.domain.member.entity.MemberState;
import kea.enter.enterbe.domain.member.repository.MemberRepository;
Expand All @@ -12,6 +13,7 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Service
@RequiredArgsConstructor
Expand All @@ -20,14 +22,34 @@ public class QuestionService {
private final QuestionRepository questionRepository;
private final MemberRepository memberRepository;

/* 문의사항 작성 API */
@Transactional
public void createQuestion(QuestionRequestDto dto) {
// memberId로 멤버 존재 여부를 검사한다.
Member member = memberRepository.findByIdAndState(dto.getMemberId(), MemberState.ACTIVE)
.orElseThrow(() -> new CustomException(ResponseCode.NOT_FOUND_MEMBER));

// state는 작성시에 WAIT로 기본값 고정
Question question = Question.of(member, dto.getContent(), dto.getCategory(),
QuestionState.WAIT);
questionRepository.save(question);

}

/* 문의사항 삭제 API */
@Transactional
public void deleteQuestion(DeleteQuestionServiceDto dto) {
// memberId로 멤버 존재 여부를 검사한다.
Member member = memberRepository.findByIdAndState(dto.getMemberId(), MemberState.ACTIVE)
.orElseThrow(() -> new CustomException(ResponseCode.NOT_FOUND_MEMBER));

// questionId로 문의사항 존재 여부를 검사한다.
Question question = questionRepository.findByIdAndMemberId(dto.getQuestionId(),
member.getId())
.orElseThrow(() -> new CustomException(ResponseCode.NOT_FOUND_QUESTION));

// 문의사항 삭제
question.deleteQuestion();

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package kea.enter.enterbe.api.question.service.dto;

import kea.enter.enterbe.api.penalty.service.dto.DeletePenaltyServiceDto;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class DeleteQuestionServiceDto {
private Long memberId;
private Long questionId;

@Builder
public DeleteQuestionServiceDto(Long memberId, Long questionId) {
this.memberId = memberId;
this.questionId = questionId;
}

public static DeleteQuestionServiceDto of(Long memberId, Long questionId) {
return DeleteQuestionServiceDto.builder()
.memberId(memberId)
.questionId(questionId)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import kea.enter.enterbe.domain.member.entity.Member;
import kea.enter.enterbe.domain.penalty.entity.PenaltyState;
import kea.enter.enterbe.global.common.entity.BaseEntity;
import lombok.AccessLevel;
import lombok.Builder;
Expand Down Expand Up @@ -37,6 +38,10 @@ public class Question extends BaseEntity {
@Column(name = "state", nullable = false)
private QuestionState state;

public void deleteQuestion() {
this.state = QuestionState.INACTIVE;
}

@Builder
public Question(Member member, String content, QuestionCategory category,
QuestionState state) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package kea.enter.enterbe.domain.question.repository;

import kea.enter.enterbe.domain.member.entity.Member;
import kea.enter.enterbe.domain.penalty.entity.Penalty;
import kea.enter.enterbe.domain.question.entity.Question;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;

@Repository
public interface QuestionRepository extends JpaRepository<Question, Long> {
Optional<Question> findByIdAndMemberId(Long penaltyId, Long memberId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ public enum ResponseCode {
METHOD_NOT_ALLOWED("GLB-ERR-002", HttpStatus.METHOD_NOT_ALLOWED, "허용되지 않은 메서드입니다."),
INTERNAL_SERVER_ERROR("GLB-ERR-003", HttpStatus.INTERNAL_SERVER_ERROR, "내부 서버 오류입니다."),

// NOTICE
// Question
NOT_FOUND_MEMBER("MEM-ERR-001", HttpStatus.NOT_FOUND, "멤버가 존재하지 않습니다."),
NOT_FOUND_QUESTION("QST-ERR-001", HttpStatus.NOT_FOUND, "문의사항이 존재하지 않습니다."),

NOT_IMAGE_FILE("GLB-ERR-004", HttpStatus.BAD_REQUEST, "이미지 파일이 아닙니다.");

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
package kea.enter.enterbe.api.question.service;

import kea.enter.enterbe.IntegrationTestSupport;
import kea.enter.enterbe.api.penalty.service.dto.DeletePenaltyServiceDto;
import kea.enter.enterbe.api.question.controller.dto.request.QuestionRequestDto;
import kea.enter.enterbe.api.question.service.dto.DeleteQuestionServiceDto;
import kea.enter.enterbe.domain.member.entity.Member;
import kea.enter.enterbe.domain.member.entity.MemberRole;
import kea.enter.enterbe.domain.member.entity.MemberState;
import kea.enter.enterbe.domain.penalty.entity.Penalty;
import kea.enter.enterbe.domain.penalty.entity.PenaltyState;
import kea.enter.enterbe.domain.question.entity.Question;
import kea.enter.enterbe.domain.question.entity.QuestionCategory;
import kea.enter.enterbe.domain.question.entity.QuestionState;
import kea.enter.enterbe.global.common.exception.CustomException;
import kea.enter.enterbe.global.common.exception.ResponseCode;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Optional;

import static kea.enter.enterbe.domain.penalty.entity.PenaltyLevel.BLACKLIST;
import static kea.enter.enterbe.domain.penalty.entity.PenaltyReason.BROKEN;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
Expand Down Expand Up @@ -62,8 +70,56 @@ public void testCreateQuestion_MemberNotFound() {
assertThat(exception.getResponseCode()).isEqualTo(ResponseCode.NOT_FOUND_MEMBER);
}

@DisplayName(value = "문의사항을 삭제한다.")
@Test
public void deleteQuestion() {
//given
Member member = memberRepository.save(createMember());
Long memberId = member.getId();

Question question = questionRepository.save(createQuestion(member));
Long questionId = question.getId();

DeleteQuestionServiceDto dto = DeleteQuestionServiceDto.of(memberId, questionId);

//when
questionService.deleteQuestion(dto);

//then
Optional<Question> result = questionRepository.findByIdAndMemberId(questionId, memberId);
assertThat(result).isPresent();

assertThat(result.get())
.extracting("state")
.isEqualTo(QuestionState.INACTIVE);
}

@DisplayName(value = "존재하는 문의사항 ID인지 검사한다")
@Test
public void questionNotFound() {
// given
Member member = memberRepository.save(createMember());
Long memberId = member.getId();
Long testQuestionId = 5L;

Question question = questionRepository.save(createQuestion(member));

DeleteQuestionServiceDto dto = DeleteQuestionServiceDto.of(memberId, testQuestionId);

// when & then
CustomException exception = assertThrows(CustomException.class, () -> {
questionService.deleteQuestion(dto);
});

assertThat(exception.getResponseCode()).isEqualTo(ResponseCode.NOT_FOUND_QUESTION);
}

private Member createMember() {
return Member.of("2", "name", "[email protected]", "password", "licenseId",
"licensePassword", true, true, 1, MemberRole.USER, MemberState.ACTIVE);
}

private Question createQuestion(Member member) {
return Question.of(member, "content", QuestionCategory.USER, QuestionState.WAIT);
}
}

0 comments on commit 5d6698f

Please sign in to comment.