Skip to content

Commit

Permalink
Merge pull request #35 from SIDETEAM001/Feat/#33/QnA_noory
Browse files Browse the repository at this point in the history
Feat[#33] : QnA CRUD 개발
  • Loading branch information
GoonManDoo committed Feb 28, 2024
2 parents ec02636 + 04325e8 commit 208f66f
Show file tree
Hide file tree
Showing 12 changed files with 546 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.sideteam.groupsaver.domain.qna.controller;

import com.sideteam.groupsaver.domain.qna.domain.Qna;
import com.sideteam.groupsaver.domain.qna.dto.request.QnaRequestDto;
import com.sideteam.groupsaver.domain.qna.dto.response.QnaResponseDto;
import com.sideteam.groupsaver.domain.qna.service.QnaService;
import com.sideteam.groupsaver.global.resolver.member_info.MemberIdParam;
import jakarta.validation.Valid;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@RestController
@RequestMapping("/api/v1/qna")
public class QnaController {

private final QnaService qnaService;

/* 전체 조회 */
@GetMapping
public ResponseEntity<List<QnaResponseDto>> getAllQna() {
return ResponseEntity.ok(qnaService.getAllQna());
}

/* 단건 조회 */
@GetMapping("/{id}")
public ResponseEntity<QnaResponseDto> getQnaById(@PathVariable Long id) {
return ResponseEntity.ok(qnaService.getQnaById(id));
}

/* 질문 등록 */
@PostMapping
public ResponseEntity<QnaResponseDto> createQna(@Valid @RequestBody QnaRequestDto qnaRequestDto) {
return ResponseEntity.ok(qnaService.createQna(qnaRequestDto));
}

/* 질문 수정 */
@PatchMapping("/{id}")
public ResponseEntity<QnaResponseDto> updateQna(@PathVariable Long id, @Valid @RequestBody QnaRequestDto qnaRequestDto) {
return ResponseEntity.ok(qnaService.updateQna(id, qnaRequestDto));
}

/* 질문 삭제 */
// Void = 해당 메서드를 반환하는데 반환할 데이터가 없음을 나타내기 위해
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteQna(@PathVariable Long id) {
qnaService.deleteQna(id);
return ResponseEntity.noContent().build();
}

}
53 changes: 53 additions & 0 deletions src/main/java/com/sideteam/groupsaver/domain/qna/domain/Qna.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.sideteam.groupsaver.domain.qna.domain;

import com.sideteam.groupsaver.domain.common.BaseEntity;
import com.sideteam.groupsaver.domain.common.BaseTimeEntity;
import com.sideteam.groupsaver.domain.member.domain.Member;
import com.sideteam.groupsaver.domain.qna.dto.request.QnaRequestDto;
import com.sideteam.groupsaver.domain.qna_reply.domain.QnaReply;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.List;
import java.util.Objects;

@Entity
@Getter
@Setter
@NoArgsConstructor
public class Qna extends BaseEntity {

/* PK */
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

/* 질문 제목 */
@Column(nullable = false)
private String title;

/* 질문 내용 */
@Column(nullable = false, columnDefinition = "TEXT")
private String body;

@OneToMany(mappedBy = "qna", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<QnaReply> qnaReplyList;

/* 생성자 */
private Qna(String title, String body, List<QnaReply> qnaReplyList) {
this.title = title;
this.body = body;
this.qnaReplyList = qnaReplyList;
}

// 수정 시 null처리
public void qnaUpdate(QnaRequestDto qnaRequestDto) {
this.title = Objects.requireNonNullElse(qnaRequestDto.getTitle(), this.title);
this.body = Objects.requireNonNullElse(qnaRequestDto.getBody(), this.body);
}



}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.sideteam.groupsaver.domain.qna.dto.request;

import com.sideteam.groupsaver.domain.member.domain.Member;
import com.sideteam.groupsaver.domain.qna.domain.Qna;
import lombok.*;

import java.time.Instant;

@Getter
public class QnaRequestDto {
private String title;
private String body;

/* RequestDTO -> Entity 변환 */
public static Qna toEntity(QnaRequestDto qnaRequestDto) {
Qna qna = new Qna();
qna.setTitle(qnaRequestDto.getTitle());
qna.setBody(qnaRequestDto.getBody());
return qna;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.sideteam.groupsaver.domain.qna.dto.response;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.sideteam.groupsaver.domain.qna.domain.Qna;
import com.sideteam.groupsaver.domain.qna_reply.dto.response.QnaReplyResponseDto;
import lombok.*;

import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

@Data
public class QnaResponseDto {

private Long id;
private String title;
private String body;
private String nickName;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul")
private Instant createAt;
private List<QnaReplyResponseDto> qnaReplyList; // 댓글 리스트

/* Entity -> ResponseDTO 변환 */
public static QnaResponseDto toDto(Qna qna) {
QnaResponseDto responseDto = new QnaResponseDto();
responseDto.setId(qna.getId());
responseDto.setTitle(qna.getTitle());
responseDto.setBody(qna.getBody());
responseDto.setNickName(qna.getCreator().getNickname());
responseDto.setCreateAt((qna.getCreator().getCreateAt()));
/* 댓글 리스트가 null이 아니면 리스트를, null이면 null */
if (qna.getQnaReplyList() != null) {
responseDto.setQnaReplyList(QnaReplyResponseDto.listOf(qna.getQnaReplyList()));
} else {
responseDto.setQnaReplyList(Collections.emptyList()); // null이 아닌 빈 리스트로 반환
}
return responseDto;
}

public static List<QnaResponseDto> listOf(List<Qna> qnaList) {
return qnaList.stream()
.map(QnaResponseDto::toDto)
.collect(Collectors.toList());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.sideteam.groupsaver.domain.qna.repository;

import com.sideteam.groupsaver.domain.qna.domain.Qna;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

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

public interface QnaRepository extends JpaRepository<Qna, Long> {

/* 전체 조회 */
@Query("SELECT q FROM Qna q JOIN FETCH q.creator")
List<Qna> findAllQnaMember();

/* 단건 조회 */
@Query("SELECT q FROM Qna q JOIN FETCH q.creator WHERE q.id = :id")
Optional<Qna> findQnaMemberById(Long id);


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.sideteam.groupsaver.domain.qna.service;

import com.sideteam.groupsaver.domain.qna.domain.Qna;
import com.sideteam.groupsaver.domain.qna.dto.request.QnaRequestDto;
import com.sideteam.groupsaver.domain.qna.dto.response.QnaResponseDto;
import com.sideteam.groupsaver.domain.qna.repository.QnaRepository;
import jakarta.persistence.EntityNotFoundException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PatchMapping;

import java.util.List;

@RequiredArgsConstructor
@Slf4j
@Service
public class QnaService {

private final QnaRepository qnaRepository;

/* 전체 조회 */
@Transactional(readOnly = true)
public List<QnaResponseDto> getAllQna() {
List<Qna> qna = qnaRepository.findAllQnaMember();
return QnaResponseDto.listOf(qna);
}

/* 단건 조회 */
@Transactional(readOnly = true)
public QnaResponseDto getQnaById(Long id) {
Qna qna = qnaRepository.findQnaMemberById(id)
.orElseThrow(() -> new EntityNotFoundException("ID가 없습니다 ! : " + id));
return QnaResponseDto.toDto(qna);
}

/* 질문 등록 */
// 등록, 수정에 트랜잭션 어노테이션 안붙이니까 "post could not initialize proxy" 오류뜨는데 이유가 뭐지
@Transactional
public QnaResponseDto createQna(QnaRequestDto qnaRequestDto) {
// 등록 정보가 담긴 DTO를 Entity로 변환하여 저장
Qna qna = qnaRepository.save(QnaRequestDto.toEntity(qnaRequestDto));
return QnaResponseDto.toDto(qna);
}

/* 질문 수정 */
@Transactional
/*@PreAuthorize("isAuthenticated() AND ((#memberId.toString() == principal.username) " +
"OR hasRole('ADMIN'))")*/
public QnaResponseDto updateQna(Long id, QnaRequestDto qnaRequestDto) {
Qna qna = qnaRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("ID가 없습니다 ! : " + id));
qna.qnaUpdate(qnaRequestDto);

// 저장 및 DTO로 변환 후 반환
return QnaResponseDto.toDto(qnaRepository.save(qna));
}

/* 질문 삭제 */
public void deleteQna(Long id) {
Qna qna = qnaRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("ID가 없습니다 ! : " + id));
qnaRepository.delete(qna);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.sideteam.groupsaver.domain.qna_reply.controller;

import com.sideteam.groupsaver.domain.qna.dto.request.QnaRequestDto;
import com.sideteam.groupsaver.domain.qna.dto.response.QnaResponseDto;
import com.sideteam.groupsaver.domain.qna.service.QnaService;
import com.sideteam.groupsaver.domain.qna_reply.domain.QnaReply;
import com.sideteam.groupsaver.domain.qna_reply.dto.request.QnaReplyRequestDto;
import com.sideteam.groupsaver.domain.qna_reply.dto.response.QnaReplyResponseDto;
import com.sideteam.groupsaver.domain.qna_reply.service.QnaReplyService;
import com.sideteam.groupsaver.global.resolver.member_info.MemberIdParam;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RequiredArgsConstructor
@RestController
@RequestMapping("/api/v1/qnaReply")
public class QnaReplyController {

private final QnaReplyService qnaReplyService;

/* 댓글 등록 */
@PostMapping("/{qnaId}")
public ResponseEntity<QnaReplyResponseDto> createQnaReply(@PathVariable long qnaId,
@RequestParam(required = false) Long parentReplyId,
@Valid @RequestBody QnaReplyRequestDto qnaReplyRequestDto) {
return ResponseEntity.ok(qnaReplyService.createQnaReply(qnaReplyRequestDto, qnaId, parentReplyId));
}

/* 댓글 수정 */
@PatchMapping("/{id}")
public ResponseEntity<QnaReplyResponseDto> updateQnaReply(@PathVariable Long id,
@Valid @RequestBody QnaReplyRequestDto qnaReplyRequestDto) {
return ResponseEntity.ok(qnaReplyService.updateQnaReply(id, qnaReplyRequestDto));
}

/* 댓글 삭제 */
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteQnaReply(@PathVariable Long id) {
qnaReplyService.deleteQnaReply(id);
return ResponseEntity.noContent().build();
}



}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.sideteam.groupsaver.domain.qna_reply.domain;

import com.sideteam.groupsaver.domain.common.BaseEntity;
import com.sideteam.groupsaver.domain.qna.domain.Qna;
import com.sideteam.groupsaver.domain.qna.dto.request.QnaRequestDto;
import com.sideteam.groupsaver.domain.qna_reply.dto.request.QnaReplyRequestDto;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@Entity
@Getter
@Setter
@NoArgsConstructor
public class QnaReply extends BaseEntity {

/* PK */
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

/* 댓글 내용 */
@Column(nullable = false, columnDefinition = "TEXT")
private String body;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(nullable = false, name = "qna_id")
@OnDelete(action = OnDeleteAction.CASCADE) // QNA 게시글이 지워지면 댓글도 같이 삭제
private Qna qna;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_reply_id")
private QnaReply parentReply;

@OneToMany(mappedBy = "parentReply", cascade = CascadeType.ALL, orphanRemoval = true)
private List<QnaReply> childReply = new ArrayList<>();

/* 생성자 */
@Builder
private QnaReply(String body, Qna qna, QnaReply parentReply) {
this.body = body;
this.qna = qna;
this.parentReply = parentReply;
}

/* DTO -> Entity */
public static QnaReply of(Qna qna, QnaReplyRequestDto qnaReplyRequestDto, QnaReply parentReply) {
return QnaReply.builder()
.body(qnaReplyRequestDto.getBody())
.qna(qna)
.parentReply(parentReply)
.build();
}

// 수정 시 null처리
public void qnaReplyUpdate(QnaReplyRequestDto qnaReplyRequestDto) {
this.body = Objects.requireNonNullElse(qnaReplyRequestDto.getBody(), this.body);
}

}
Loading

0 comments on commit 208f66f

Please sign in to comment.