From 1f5ad17f5e5d33263ad49b4faa817ef89ba6f81e Mon Sep 17 00:00:00 2001 From: qkr7828 Date: Thu, 7 Mar 2024 10:18:49 +0900 Subject: [PATCH] =?UTF-8?q?feat=20:=20content(faq,=20qna)=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cns/repository/CnsFaqQueryRepository.java | 165 ++++++++ .../data/cns/service/CnsFaqDomainService.java | 42 ++ .../cns/qna/controller/CnsQnaController.java | 392 ++++++++++++++++++ .../api/v1/cns/qna/service/CnsQnaService.java | 219 ++++++++++ 4 files changed, 818 insertions(+) create mode 100644 data/cns/src/main/java/kr/co/palnet/kac/data/cns/repository/CnsFaqQueryRepository.java create mode 100644 data/cns/src/main/java/kr/co/palnet/kac/data/cns/service/CnsFaqDomainService.java create mode 100644 web/api-cns/src/main/java/kr/co/palnet/kac/api/v1/cns/qna/controller/CnsQnaController.java create mode 100644 web/api-cns/src/main/java/kr/co/palnet/kac/api/v1/cns/qna/service/CnsQnaService.java diff --git a/data/cns/src/main/java/kr/co/palnet/kac/data/cns/repository/CnsFaqQueryRepository.java b/data/cns/src/main/java/kr/co/palnet/kac/data/cns/repository/CnsFaqQueryRepository.java new file mode 100644 index 0000000..5416a7c --- /dev/null +++ b/data/cns/src/main/java/kr/co/palnet/kac/data/cns/repository/CnsFaqQueryRepository.java @@ -0,0 +1,165 @@ +package kr.co.palnet.kac.data.cns.repository; + +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.impl.JPAQueryFactory; +import kr.co.palnet.kac.data.cns.model.CnsFaqBas; +import kr.co.palnet.kac.data.cns.model.QCnsFaqBas; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Slf4j +@Repository +@RequiredArgsConstructor +public class CnsFaqQueryRepository { + + private final JPAQueryFactory query; + /** + * 조건값으로 FaQ리스트를 조회하는 SQL기능. + * @param category + * @param word + * @return + */ + public List getFaqList(String category, String word) { + + QCnsFaqBas bas = QCnsFaqBas.cnsFaqBas; + + BooleanBuilder builder = new BooleanBuilder(); + builder.and(bas.delYn.eq("N")); + builder.and(bas.expsrYn.eq("Y")); + + if (category != null) { + builder.and(bas.category.eq(category)); + } + + if (word != null) { + builder.and(bas.title.contains(word)); + } + + /** + * 삭제여부[delYn]값이 'N' 조건, + * 표출여부[expsrYn]값이 'Y' 조건, + * 카테고리값[category]값 조건, + * 제목[title] 값이 word와의 조건 값으로 조회하는 SQL 입니다. + * + * SELECT + * CFB.FAQ_SNO , + * CFB.CATEGORY , + * CFB.TITLE , + * CFB.CONTENT , + * CFB.VIEW_CNT , + * CFB.EXPSR_YN , + * CFB.CREATE_USER_ID , + * CFB.CREATE_DT , + * CFB.UPDATE_USER_ID , + * CFB.UPDATE_DT + * FROM CNS_FAQ_BAS CFB + * WHERE CFB.DEL_YN = 'N' + * AND CFB.EXPSR_YN = 'Y' + * AND CFB.CATEGORY = #{category} + * AND CFB.TITLE = #{word} + * ORDER BY CFB.CREATE_DT DESC + */ + List r = query + .select(Projections.bean( + CnsFaqBas.class, + bas.faqSno, + bas.category, + bas.title, + bas.content, + bas.viewCnt, + bas.expsrYn, + bas.createUserId, + bas.createDt, + bas.updateUserId, + bas.updateDt + )) + .from(bas) + .where(builder) + .orderBy(bas.createDt.desc()) + .fetch(); + + + return r; + } + + /** + * 일련번호[sno]로 상세정보를 조회하는 SQL 기능. + * @param sno + * @return + */ + public CnsFaqBas getFaqDetail(int sno) { + + QCnsFaqBas bas = QCnsFaqBas.cnsFaqBas; + + BooleanBuilder builder = new BooleanBuilder(); + builder.and(bas.faqSno.eq((long) sno)); + builder.and(bas.expsrYn.eq("Y")); + builder.and(bas.delYn.eq("N")); + + /** + * 일련번호[faqSno] 조건, + * 표출여부[expsrYn]가 'Y' 인지 조건, + * 삭제여부[delYn]가 'N' 인지 조건으로 조회하는 SQL 입니다. + * + * SELECT + * CFB.FAQ_SNO , + * CFB.CATEGORY , + * CFB.TITLE , + * CFB.CONTENT , + * CFB.VIEW_CNT , + * CFB.EXPSR_YN , + * CFB.CREATE_USER_ID , + * CFB.CREATE_DT , + * CFB.UPDATE_USER_ID , + * CFB.UPDATE_DT + * FROM CNS_FAQ_BAS CFB + * WHERE CFB.FAQ_SNO = #{sno} + * AND CFB.EXPSR_YN = 'Y' + * AND CFB.DEL_YN = 'N' + */ + CnsFaqBas r = query + .select(Projections.bean( + CnsFaqBas.class, + bas.faqSno, + bas.category, + bas.title, + bas.content, + bas.viewCnt, + bas.expsrYn, + bas.createUserId, + bas.createDt, + bas.updateUserId, + bas.updateDt + )) + .from(bas) + .where(builder) + .fetchOne(); + + + return r; + } + + public void updateFaq(CnsFaqBas model) { + QCnsFaqBas bas = QCnsFaqBas.cnsFaqBas; + + BooleanBuilder builder = new BooleanBuilder(); + builder.and(bas.faqSno.eq(model.getFaqSno())); + + query.update(bas) + .set(bas.category, model.getCategory()) + .set(bas.title, model.getTitle()) + .set(bas.content, model.getContent()) + .set(bas.viewCnt, model.getViewCnt()) + .set(bas.expsrYn, model.getExpsrYn()) + .set(bas.updateUserId, model.getUpdateUserId()) + .set(bas.updateDt, model.getUpdateDt()) + .where(builder) + .execute(); + + } + +} \ No newline at end of file diff --git a/data/cns/src/main/java/kr/co/palnet/kac/data/cns/service/CnsFaqDomainService.java b/data/cns/src/main/java/kr/co/palnet/kac/data/cns/service/CnsFaqDomainService.java new file mode 100644 index 0000000..ee9d6a9 --- /dev/null +++ b/data/cns/src/main/java/kr/co/palnet/kac/data/cns/service/CnsFaqDomainService.java @@ -0,0 +1,42 @@ +package kr.co.palnet.kac.data.cns.service; + +import kr.co.palnet.kac.data.cns.model.CnsFaqBas; +import kr.co.palnet.kac.data.cns.repository.CnsFaqBasRepository; +import kr.co.palnet.kac.data.cns.repository.CnsFaqQueryRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class CnsFaqDomainService { + + private final CnsFaqBasRepository cnsFaqBasRepository; + private final CnsFaqQueryRepository query; + + public List getFaqList(String category, String word){ + return query.getFaqList(category, word); + } + + public void updateViewCnt(int sno){ + cnsFaqBasRepository.updateViewCnt(sno); + } + + public CnsFaqBas getFaqDetail(int sno){ + return query.getFaqDetail(sno); + } + + public CnsFaqBas saveFaqBas(CnsFaqBas cnsFaqBas){ + return cnsFaqBasRepository.save(cnsFaqBas); + } + + public Optional findById(int faqSno){ + return cnsFaqBasRepository.findById(faqSno); + } + + public int deleteFaq(int faqSno){ + return cnsFaqBasRepository.deleteFaq(faqSno); + } +} diff --git a/web/api-cns/src/main/java/kr/co/palnet/kac/api/v1/cns/qna/controller/CnsQnaController.java b/web/api-cns/src/main/java/kr/co/palnet/kac/api/v1/cns/qna/controller/CnsQnaController.java new file mode 100644 index 0000000..ee626c8 --- /dev/null +++ b/web/api-cns/src/main/java/kr/co/palnet/kac/api/v1/cns/qna/controller/CnsQnaController.java @@ -0,0 +1,392 @@ +package kr.co.palnet.kac.api.v1.cns.qna.controller; + +import com.palnet.biz.api.acnt.jwt.utils.JwtTokenUtil; +import com.palnet.biz.api.cns.qna.model.*; +import com.palnet.biz.api.cns.qna.service.CnsQnaService; +import com.palnet.biz.api.comn.response.BasicResponse; +import com.palnet.biz.api.comn.response.ErrorResponse; +import com.palnet.biz.api.comn.response.SuccessResponse; +import com.palnet.comn.code.RSErrorCode; +import com.palnet.comn.exception.CustomException; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +@RequiredArgsConstructor +@RestController +@Tag(name = "QNA", description = "QNA 관련 API") +@RequestMapping("/api/cns/qna") +public class CnsQnaController { + + private final CnsQnaService cnsQnaService; + private final JwtTokenUtil jwtTokenUtil; + + + /** + * QnA 등록하는 기능, + * QnaInsertRQModel 모델에 요청값으로 QnA를 등록하는 기능. + * @param rq + * @return + */ + @PostMapping(consumes = "multipart/form-data") + @Operation(summary = "QNA 등록 (사용자)", description = "사용자 권한으로 QNA를 등록하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = { + @io.swagger.v3.oas.annotations.media.Content(mediaType = "application/json", + array = @io.swagger.v3.oas.annotations.media.ArraySchema( + schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = boolean.class))) + }) + }) + public ResponseEntity insertQna(QnaInsertRQModel rq) { + boolean result = false; + try { + result = cnsQnaService.insertQna(rq); // Qna 추가 하는기능 + } catch (CustomException e) { + /** + * try{ + ... + } + * try 영역 안 코드들중 문제가 생기면 오는 곳. + * CustomException은 개발자가 "의도적으로" 낸 예외처리, + * log.error 로그로 원인 파악과 함께 API를 호출한 곳에 서버에러 내려줌 + */ + Map resultMap = new HashMap<>(); + log.error("IGNORE : ", e); + resultMap.put("result", false); + resultMap.put("errorCode", e.getErrorCode()); + resultMap.put("errorMessage", e.getMessage()); + return ResponseEntity.ok().body(new SuccessResponse<>(resultMap)); + } catch (Exception e) { + /** + * try{ + ... + } + * try 영역 안 코드들중 문제가 생기면 오는 곳. + * log.error 로그로 원인 파악과 함께 API를 호출한 곳에 서버에러 내려줌 + */ + log.error("IGNORE : ", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new ErrorResponse("Server Error", "-1")); + + } + return ResponseEntity.ok().body(new SuccessResponse<>(result)); + } + + /** + * QnA리스트 조회하는 기능[관리자], + * QnaListAdminRQ 조건값에 따라 조회함. + * @param rq + * @return + */ + @GetMapping + @Operation(summary = "QNA 리스트 조회 (관리자)", description = "QNA 리스트를 관리자 권한으로 조회하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = { + @io.swagger.v3.oas.annotations.media.Content(mediaType = "application/json", + array = @io.swagger.v3.oas.annotations.media.ArraySchema( + schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = QnaBasModel.class))) + }) + }) + public ResponseEntity selectQna(QnaListAdminRQ rq) { + + List result = new ArrayList<>(); + try { + result = cnsQnaService.selectQnaList(rq); // Qna리스트 조회하는 기능 + } catch (CustomException e) { + /** + * try{ + ... + } + * try 영역 안 코드들중 문제가 생기면 오는 곳. + * CustomException은 개발자가 "의도적으로" 낸 예외처리, + * log.error 로그로 원인 파악과 함께 API를 호출한 곳에 서버에러 내려줌 + */ + Map resultMap = new HashMap<>(); + log.error("IGNORE : ", e); + resultMap.put("result", false); + resultMap.put("errorCode", e.getErrorCode()); + resultMap.put("errorMessage", e.getMessage()); + return ResponseEntity.ok().body(new SuccessResponse<>(resultMap)); + } catch (Exception e) { + /** + * try{ + ... + } + * try 영역 안 코드들중 문제가 생기면 오는 곳. + * log.error 로그로 원인 파악과 함께 API를 호출한 곳에 서버에러 내려줌 + */ + log.error("IGNORE : ", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new ErrorResponse("Server Error", "-1")); + + } + return ResponseEntity.ok().body(new SuccessResponse<>(result)); + } + + + /** + * QnA리스트 조회하는 기능[관리자], + * QnaSelectListRQ 값에 따라서 조회함. + * @param rq + * @return + */ + @GetMapping("/user") + @Operation(summary = "QNA 리스트 조회 (사용자)", description = "QNA 리스트를 사용자 권한으로 조회하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = { + @io.swagger.v3.oas.annotations.media.Content(mediaType = "application/json", + array = @io.swagger.v3.oas.annotations.media.ArraySchema( + schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = QnaBasModel.class))) + }) + }) + public ResponseEntity selectQnaForUser(QnaSelectListRQ rq) { + + List result = new ArrayList<>(); + try { + result = cnsQnaService.selectQnaForUser(rq); + } catch (CustomException e) { + /** + * try{ + ... + } + * try 영역 안 코드들중 문제가 생기면 오는 곳. + * CustomException은 개발자가 "의도적으로" 낸 예외처리, + * log.error 로그로 원인 파악과 함께 API를 호출한 곳에 서버에러 내려줌 + */ + Map resultMap = new HashMap<>(); + log.error("IGNORE : ", e); + resultMap.put("result", false); + resultMap.put("errorCode", e.getErrorCode()); + resultMap.put("errorMessage", e.getMessage()); + return ResponseEntity.ok().body(new SuccessResponse<>(resultMap)); + } catch (Exception e) { + /** + * try{ + ... + } + * try 영역 안 코드들중 문제가 생기면 오는 곳. + * log.error 로그로 원인 파악과 함께 API를 호출한 곳에 서버에러 내려줌 + */ + log.error("IGNORE : ", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new ErrorResponse("Server Error", "-1")); + + } + return ResponseEntity.ok().body(new SuccessResponse<>(result)); + } + + + /** + * QnA 상세보기 기능, + * QnA일련번호[qnaSno]값으로 조회함. + * @param qnaSno + * @return + */ + @GetMapping("/{qnaSno}") + @Operation(summary = "QNA 리스트 상세 조회", description = "QNA 리스트를 상세하게 조회하는 API 입니다.") + public ResponseEntity detailQna(@Parameter(name="qnaSno", description = "QnA일련번호", in = ParameterIn.PATH, example = "295") @PathVariable int qnaSno) { + QnaDetailRSModel result = null; + try { + result = cnsQnaService.getQnaDetail(qnaSno); // Qna 상세불러오기 기능 + } catch (CustomException e) { + /** + * try{ + ... + } + * try 영역 안 코드들중 문제가 생기면 오는 곳. + * CustomException은 개발자가 "의도적으로" 낸 예외처리, + * log.error 로그로 원인 파악과 함께 API를 호출한 곳에 서버에러 내려줌 + */ + Map resultMap = new HashMap<>(); + log.error("IGNORE : ", e); + resultMap.put("result", false); + resultMap.put("errorCode", e.getErrorCode()); + resultMap.put("errorMessage", e.getMessage()); + return ResponseEntity.ok().body(new SuccessResponse<>(resultMap)); + } catch (Exception e) { + /** + * try{ + ... + } + * try 영역 안 코드들중 문제가 생기면 오는 곳. + * log.error 로그로 원인 파악과 함께 API를 호출한 곳에 서버에러 내려줌 + */ + log.error("IGNORE : ", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new ErrorResponse("Server Error", "-1")); + + } + return ResponseEntity.ok().body(new SuccessResponse<>(result)); + } + + /** + * QnA수정 하는 기능[사용자], + * QnaInsertRQModel 모델에 입력받은 사항들을 수정함. + * @param rq + * @return + */ + @PutMapping(consumes = "multipart/form-data") + @Operation(summary = "QNA 수정 (사용자)", description = "QNA를 사용자 권한으로 수정하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = { + @io.swagger.v3.oas.annotations.media.Content(mediaType = "application/json", + array = @io.swagger.v3.oas.annotations.media.ArraySchema( + schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = boolean.class))) + }) + }) + public ResponseEntity updateQna(QnaInsertRQModel rq) { + boolean result = false; + try { + result = cnsQnaService.updateQna(rq); // Qna 업데이트하는 기능 + } catch (CustomException e) { + /** + * try{ + ... + } + * try 영역 안 코드들중 문제가 생기면 오는 곳. + * CustomException은 개발자가 "의도적으로" 낸 예외처리, + * log.error 로그로 원인 파악과 함께 API를 호출한 곳에 서버에러 내려줌 + */ + Map resultMap = new HashMap<>(); + log.error("IGNORE : ", e); + resultMap.put("result", false); + resultMap.put("errorCode", e.getErrorCode()); + resultMap.put("errorMessage", e.getMessage()); + return ResponseEntity.ok().body(new SuccessResponse<>(resultMap)); + } catch (Exception e) { + /** + * try{ + ... + } + * try 영역 안 코드들중 문제가 생기면 오는 곳. + * log.error 로그로 원인 파악과 함께 API를 호출한 곳에 서버에러 내려줌 + */ + log.error("IGNORE : ", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new ErrorResponse("Server Error", "-1")); + + } + return ResponseEntity.ok().body(new SuccessResponse<>(result)); + } + + + /** + * QnA삭제하는 기능, + * QnA일련번호[qnaSno]로 삭제함. + * @param qnaSno + * @return + */ + @DeleteMapping("/{qnaSno}") + @Operation(summary = "QNA 삭제", description = "QNA를 삭제하는 API 입니다.") + public ResponseEntity deleteQna(@Parameter(name="qnaSno", description = "QnA일련번호", in = ParameterIn.PATH, example = "17") @PathVariable int qnaSno) { + // TODO 관리자만 삭제 가능 여부인지 확인 필요 + boolean result = false; + try { + result = cnsQnaService.deleteQna(qnaSno); // Qna 삭제하기, file들도 모두 논리삭제하는 기능. + } catch (CustomException e) { + /** + * try{ + ... + } + * try 영역 안 코드들중 문제가 생기면 오는 곳. + * CustomException은 개발자가 "의도적으로" 낸 예외처리, + * log.error 로그로 원인 파악과 함께 API를 호출한 곳에 서버에러 내려줌 + */ + Map resultMap = new HashMap<>(); + log.error("IGNORE : ", e); + resultMap.put("result", false); + resultMap.put("errorCode", e.getErrorCode()); + resultMap.put("errorMessage", e.getMessage()); + return ResponseEntity.ok().body(new SuccessResponse<>(resultMap)); + } catch (Exception e) { + /** + * try{ + ... + } + * try 영역 안 코드들중 문제가 생기면 오는 곳. + * log.error 로그로 원인 파악과 함께 API를 호출한 곳에 서버에러 내려줌 + */ + log.error("IGNORE : ", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new ErrorResponse("Server Error", "-1")); + + } + return ResponseEntity.ok().body(new SuccessResponse<>(result)); + } + + /** + * QnA 답변등록하는 기능[관리자], + * QnaInsertAnserRQModel에 입력받은 값으로 답변을 등록함. + * @param rq + * @return + */ + @PutMapping("/answer") + @Operation(summary = "QNA 답변 등록(관리자)", description = "QNA에 대한 답변을 관리자 권한으로 등록하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = { + @io.swagger.v3.oas.annotations.media.Content(mediaType = "application/json", + array = @io.swagger.v3.oas.annotations.media.ArraySchema( + schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = boolean.class))) + }) + }) + public ResponseEntity insertAnswer(@RequestBody QnaInsertAnserRQModel rq) { + // TODO 추후 auth -> role 체크로 변경 + boolean result = false; + try { + String userAuthByToken = jwtTokenUtil.getUserAuthByToken(); + if (!"SUPER".equals(userAuthByToken)) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new ErrorResponse(RSErrorCode.AUTH_ERROR)); + } + + result = cnsQnaService.insertAnswer(rq); // 답변추가하는 기능. + } catch (CustomException e) { + /** + * try{ + ... + } + * try 영역 안 코드들중 문제가 생기면 오는 곳. + * CustomException은 개발자가 "의도적으로" 낸 예외처리, + * log.error 로그로 원인 파악과 함께 API를 호출한 곳에 서버에러 내려줌 + */ + Map resultMap = new HashMap<>(); + log.error("IGNORE : ", e); + resultMap.put("result", false); + resultMap.put("errorCode", e.getErrorCode()); + resultMap.put("errorMessage", e.getMessage()); + return ResponseEntity.ok().body(new SuccessResponse<>(resultMap)); + } catch (Exception e) { + /** + * try{ + ... + } + * try 영역 안 코드들중 문제가 생기면 오는 곳. + * log.error 로그로 원인 파악과 함께 API를 호출한 곳에 서버에러 내려줌 + */ + log.error("IGNORE : ", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new ErrorResponse("Server Error", "-1")); + + } + return ResponseEntity.ok().body(new SuccessResponse<>(result)); + } + +} \ No newline at end of file diff --git a/web/api-cns/src/main/java/kr/co/palnet/kac/api/v1/cns/qna/service/CnsQnaService.java b/web/api-cns/src/main/java/kr/co/palnet/kac/api/v1/cns/qna/service/CnsQnaService.java new file mode 100644 index 0000000..87fb8f4 --- /dev/null +++ b/web/api-cns/src/main/java/kr/co/palnet/kac/api/v1/cns/qna/service/CnsQnaService.java @@ -0,0 +1,219 @@ +package kr.co.palnet.kac.api.v1.cns.qna.service; + +import com.palnet.biz.api.acnt.jwt.utils.JwtTokenUtil; +import com.palnet.biz.api.cns.qna.model.*; +import com.palnet.biz.api.comn.file.model.ComnFileModel; +import com.palnet.biz.api.comn.file.service.ComnFileService; +import com.palnet.biz.jpa.entity.CnsQnaBas; +import com.palnet.biz.jpa.entity.PtyCstmrBas; +import com.palnet.biz.jpa.entity.PtyCstmrDtl; +import com.palnet.biz.jpa.repository.cns.CnsQnaBasRepository; +import com.palnet.biz.jpa.repository.cns.CnsQnaQueryRepository; +import com.palnet.biz.jpa.repository.pty.PtyCstmrBasRepository; +import com.palnet.biz.jpa.repository.pty.PtyCstmrDtlRepository; +import com.palnet.comn.code.ErrorCode; +import com.palnet.comn.exception.CustomException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import javax.transaction.Transactional; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@Slf4j +@RequiredArgsConstructor +public class CnsQnaService { + + private final CnsQnaBasRepository cnsQnaBasRepository; + + private final CnsQnaQueryRepository cnsQnaQueryRepository; + private final PtyCstmrBasRepository ptyCstmrBasRepository; + private final PtyCstmrDtlRepository ptyCstmrDtlRepository; + + private final ComnFileService comnFileService; + + private final JwtTokenUtil jwtTokenUtil; + + /** + * Qna 삭제하기, file들도 모두 논리삭제하는 기능. + * @param qnaSno + * @return + */ + // @Transactional는 데이터베이스의 정합성을 위해 사용합니다, 추가 수정 삭제 등에서 사용합니다, 이 기능에서는 삭제컬럼은 수정했기 때문에 사용했습니다. + @Transactional + public boolean deleteQna(int qnaSno) { + + CnsQnaBas entity = cnsQnaBasRepository.findByQnaSnoAndDelYnAndExpsrYn(qnaSno, "N", "Y"); + if (entity == null) throw new CustomException(ErrorCode.DATA_NOTFIND); + + if (entity.getFileGroupNo() != null) comnFileService.deleteFilesByFileGroupNo(entity.getFileGroupNo()); + + entity.setDelYn("Y"); + + cnsQnaBasRepository.save(entity); + + return true; + } + + /** + * Qna 업데이트하는 기능. + * @param rq + * @return + */ + // @Transactional는 데이터베이스의 정합성을 위해 사용합니다, 추가 수정 삭제 등에서 사용합니다, 이 기능에서는 수정하기 때문에 사용합니다. + @Transactional + public boolean updateQna(QnaInsertRQModel rq) { + + CnsQnaBas entity = cnsQnaBasRepository.findFirstByQnaSnoAndTargetSnoAndDelYnAndExpsrYn(rq.getQnaSno(), 0, "N", "Y"); + if (entity == null) throw new CustomException(ErrorCode.DATA_NOTFIND); + + // 파일 삭제(논리적 삭제) + List prevFileSnoList = new ArrayList<>(); + List currentFileSnoList = new ArrayList<>(); + if (entity.getFileGroupNo() != null) { + List prevFileInfos = comnFileService.getNormalFileListByGroupNo(entity.getFileGroupNo()); // 파일그룹번호[fileGroupNo]에 해당하는 파일등 모두 가져오는 기능. + prevFileSnoList = prevFileInfos.stream().map(ComnFileModel::getFileSno).collect(Collectors.toList()); + } + if (rq.getFileInfos() != null) { + currentFileSnoList = rq.getFileInfos().stream().map(ComnFileModel::getFileSno).collect(Collectors.toList()); + } + + boolean isChange = prevFileSnoList.removeAll(currentFileSnoList); + if (isChange) { + comnFileService.deleteFiles(prevFileSnoList); // File 삭제 + } + + List files = rq.getFiles(); + if (files != null && !files.isEmpty()) comnFileService.fileUpload(files, entity.getFileGroupNo()); // File 업로드 + +// if(rq.getCategory() != null) entity.setCategory(rq.getCategory()); +// if(rq.getContent() != null) entity.setContent(rq.getContent()); +// if(rq.getTitle() != null) entity.setTitle(rq.getTitle()); + + CnsQnaMapper.MAPPER.merge(entity, rq); + + String userId = jwtTokenUtil.getUserIdByToken(); + + if (userId == null) userId = "NONE"; + + entity.setUpdateUserId(userId); + // 수정시 답변상태 미응답 상태로 변경 + entity.setAnserStatus("N"); + + cnsQnaBasRepository.save(entity); + + return true; + } + + /** + * Qna 상세불러오기 기능. + * @param qnaSno + * @return + */ + // @Transactional는 데이터베이스의 정합성을 위해 사용합니다, 추가 수정 삭제 등에서 사용합니다, 이 기능에서는 조회수 증가하기 떄문에 사용했습니다. + @Transactional + public QnaDetailRSModel getQnaDetail(int qnaSno) { + + cnsQnaBasRepository.pulsViewCount(qnaSno); + + CnsQnaBas entity = cnsQnaBasRepository.findFirstByQnaSnoAndTargetSnoAndDelYnAndExpsrYn(qnaSno, 0, "N", "Y"); + if(entity == null) throw new CustomException(ErrorCode.DATA_NO); + + List files = null; + if (entity.getFileGroupNo() != null && entity.getFileGroupNo() != 0) + files = comnFileService.getNormalFileListByGroupNo(entity.getFileGroupNo()); // 파일그룹번호[fileGroupNo]에 해당하는 파일등 모두 가져오는 기능. + + QnaDetailRSModel model = CnsQnaMapper.MAPPER.toModel(entity); + PtyCstmrBas user = ptyCstmrBasRepository.findByUserId(entity.getCreateUserId()).orElse(null); + if (user != null) { + PtyCstmrDtl ptyCstmrDtl = ptyCstmrDtlRepository.findById(user.getCstmrSno()).orElse(null); + if (ptyCstmrDtl != null) model.setCreateUserNm(ptyCstmrDtl.getMemberName()); + } + + model.setFileInfos(files); + + + return model; + } + + /** + * Qna리스트 조회하는 기능[관리자]. + * + * @param rq + * @return + */ + public List selectQnaList(QnaListAdminRQ rq) { + return cnsQnaQueryRepository.getQnaList(rq); + } + + /** + * Qna리스트 조회하는 기능[유저] + * @param rq + * @return + */ + public List selectQnaForUser(QnaSelectListRQ rq) { + String userId = jwtTokenUtil.getUserIdByToken(); + return cnsQnaQueryRepository.getQnaListForUser(rq, userId); + } + + /** + * Qna 추가 하는기능. + * @param rq + * @return + */ + @Transactional + public boolean insertQna(QnaInsertRQModel rq) { + + List files = rq.getFiles(); + Integer fileGroupNo = null; + if (files != null && files.size() > 0) fileGroupNo = comnFileService.fileUpload(files, null); // File 업로드 + + String userId = jwtTokenUtil.getUserIdByToken(); + + if (userId == null) userId = "NONE"; + + CnsQnaBas cnsQnaBas = CnsQnaMapper.MAPPER.toEntity(rq); + cnsQnaBas.setCreateUserId(userId); + cnsQnaBas.setUpdateUserId(userId); + cnsQnaBas.setExpsrYn("Y"); + cnsQnaBas.setDelYn("N"); + cnsQnaBas.setViewCnt(0); + cnsQnaBas.setAnserStatus("N"); + + if (fileGroupNo != null) cnsQnaBas.setFileGroupNo(fileGroupNo); + + cnsQnaBasRepository.save(cnsQnaBas); + + return true; + } + + + /** + * 답변추가하는 기능. + * @param rq + * @return + */ + public boolean insertAnswer(QnaInsertAnserRQModel rq) { + CnsQnaBas entity = cnsQnaBasRepository.findByQnaSnoAndDelYnAndExpsrYn(rq.getQnaSno(), "N", "Y"); + if (entity == null) throw new CustomException(ErrorCode.DATA_NOTFIND); + + String userId = jwtTokenUtil.getUserIdByToken(); + String userNm = jwtTokenUtil.getUserNmByToken(); + + CnsQnaMapper.MAPPER.merge(entity, rq); + entity.setUpdateUserId(userId); + entity.setAnserUserNm(userNm); + entity.setAnserProcDt(Instant.now()); + entity.setAnserStatus("Y".equals(rq.getAnserStatus()) ? "Y" : "N"); + + cnsQnaBasRepository.save(entity); + return true; + } + + +}