From a983595bad8b0ecebe6da598a504eecbf0219f4e Mon Sep 17 00:00:00 2001 From: Klaus Date: Sun, 6 Aug 2023 22:10:33 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20-=20FAQ=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/faq/CreateFaqRequest.kt | 7 ++ .../kr/co/vividnext/sodalive/faq/Faq.kt | 22 +++++++ .../co/vividnext/sodalive/faq/FaqCategory.kt | 13 ++++ .../sodalive/faq/FaqCategoryRepository.kt | 7 ++ .../vividnext/sodalive/faq/FaqController.kt | 41 ++++++++++++ .../vividnext/sodalive/faq/FaqRepository.kt | 64 +++++++++++++++++++ .../co/vividnext/sodalive/faq/FaqService.kt | 62 ++++++++++++++++++ .../sodalive/faq/GetFaqResponseItem.kt | 10 +++ .../sodalive/faq/ModifyFaqRequest.kt | 8 +++ 9 files changed, 234 insertions(+) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/faq/CreateFaqRequest.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/faq/Faq.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqCategory.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqCategoryRepository.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqRepository.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqService.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/faq/GetFaqResponseItem.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/faq/ModifyFaqRequest.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/faq/CreateFaqRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/faq/CreateFaqRequest.kt new file mode 100644 index 0000000..1a820c4 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/faq/CreateFaqRequest.kt @@ -0,0 +1,7 @@ +package kr.co.vividnext.sodalive.faq + +data class CreateFaqRequest( + val question: String, + val answer: String, + val category: String +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/faq/Faq.kt b/src/main/kotlin/kr/co/vividnext/sodalive/faq/Faq.kt new file mode 100644 index 0000000..26ce603 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/faq/Faq.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.faq + +import kr.co.vividnext.sodalive.common.BaseEntity +import javax.persistence.Column +import javax.persistence.Entity +import javax.persistence.FetchType +import javax.persistence.JoinColumn +import javax.persistence.ManyToOne + +@Entity +data class Faq( + @Column(nullable = false) + var question: String, + @Column(columnDefinition = "TEXT", nullable = false) + var answer: String, + var isActive: Boolean = true, + val orders: Int = 1 +) : BaseEntity() { + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "category_id", nullable = false) + var category: FaqCategory? = null +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqCategory.kt b/src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqCategory.kt new file mode 100644 index 0000000..b3deac5 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqCategory.kt @@ -0,0 +1,13 @@ +package kr.co.vividnext.sodalive.faq + +import kr.co.vividnext.sodalive.common.BaseEntity +import javax.persistence.Column +import javax.persistence.Entity + +@Entity +data class FaqCategory( + @Column(nullable = false) + val category: String, + val isActive: Boolean = true, + val orders: Int = 1 +) : BaseEntity() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqCategoryRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqCategoryRepository.kt new file mode 100644 index 0000000..07e4ee0 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqCategoryRepository.kt @@ -0,0 +1,7 @@ +package kr.co.vividnext.sodalive.faq + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface FaqCategoryRepository : JpaRepository diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqController.kt new file mode 100644 index 0000000..0e835a1 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqController.kt @@ -0,0 +1,41 @@ +package kr.co.vividnext.sodalive.faq + +import kr.co.vividnext.sodalive.common.ApiResponse +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/faq") +class FaqController(private val service: FaqService) { + @PostMapping + @PreAuthorize("hasRole('ADMIN')") + fun createFaq(@RequestBody request: CreateFaqRequest) = ApiResponse.ok( + service.save(request), + "등록되었습니다." + ) + + @PutMapping + @PreAuthorize("hasRole('ADMIN')") + fun modifyFaq(@RequestBody request: ModifyFaqRequest) = ApiResponse.ok( + service.modify(request), + "수정되었습니다." + ) + + @DeleteMapping("/{id}") + @PreAuthorize("hasRole('ADMIN')") + fun deleteCoin(@PathVariable id: Long) = ApiResponse.ok(service.delete(id), "삭제되었습니다.") + + @GetMapping + fun getFaqList(@RequestParam("category") category: String) = ApiResponse.ok(service.getFaqList(category)) + + @GetMapping("/category") + fun getFaqCategoryList() = ApiResponse.ok(service.getCategoryList()) +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqRepository.kt new file mode 100644 index 0000000..9ed977d --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqRepository.kt @@ -0,0 +1,64 @@ +package kr.co.vividnext.sodalive.faq + +import com.querydsl.core.types.dsl.Expressions +import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.faq.QFaq.faq +import kr.co.vividnext.sodalive.faq.QFaqCategory.faqCategory +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface FaqRepository : JpaRepository + +@Repository +class FaqQueryRepository(private val queryFactory: JPAQueryFactory) { + fun getCategory(category: String): FaqCategory? { + return queryFactory + .selectFrom(faqCategory) + .where( + faqCategory.isActive.isTrue + .and(faqCategory.category.eq(category)) + ) + .fetchFirst() + } + + fun getCategoryList(): List { + return queryFactory + .select(faqCategory.category) + .from(faqCategory) + .where(faqCategory.isActive.isTrue) + .orderBy(faqCategory.orders.desc()) + .fetch() + } + + fun getFaqList(category: String): List { + return queryFactory + .select( + QGetFaqResponseItem( + faq.id, + Expressions.asString(category), + faq.question, + faq.answer + ) + ) + .from(faq) + .innerJoin(faq.category, faqCategory) + .where( + faq.isActive.isTrue + .and(faqCategory.isActive.isTrue) + .and(faqCategory.category.eq(category)) + ) + .orderBy(faq.orders.desc()) + .fetch() + } + + fun getFaq(id: Long): Faq? { + return queryFactory + .selectFrom(faq) + .where( + faq.isActive.isTrue + .and(faq.id.eq(id)) + ) + .fetchFirst() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqService.kt new file mode 100644 index 0000000..5912d4a --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/faq/FaqService.kt @@ -0,0 +1,62 @@ +package kr.co.vividnext.sodalive.faq + +import kr.co.vividnext.sodalive.common.SodaException +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class FaqService( + private val repository: FaqRepository, + private val queryRepository: FaqQueryRepository +) { + @Transactional + fun save(request: CreateFaqRequest): Long { + if (request.question.isBlank()) throw SodaException("질문을 입력하세요.") + if (request.answer.isBlank()) throw SodaException("답변을 입력하세요.") + if (request.category.isBlank()) throw SodaException("카테고리를 선택하세요.") + + val category = queryRepository.getCategory(request.category) + ?: throw SodaException("잘못된 카테고리 입니다.") + + val faq = Faq(request.question, request.answer) + faq.category = category + + return repository.save(faq).id!! + } + + @Transactional + fun modify(request: ModifyFaqRequest) { + val faq = queryRepository.getFaq(request.id) + ?: throw SodaException("잘못된 요청입니다.") + + if (request.question != null) { + if (request.question.isBlank()) throw SodaException("질문을 입력하세요.") + faq.question = request.question + } + + if (request.answer != null) { + if (request.answer.isBlank()) throw SodaException("답변을 입력하세요.") + faq.answer = request.answer + } + + if (request.category != null) { + if (request.category.isBlank()) throw SodaException("카테고리를 선택하세요.") + val category = queryRepository.getCategory(request.category) ?: throw SodaException("잘못된 카테고리 입니다.") + faq.category = category + } + } + + @Transactional + fun delete(id: Long) { + if (id <= 0) throw SodaException("잘못된 요청입니다.") + val faq = repository.findByIdOrNull(id) + ?: throw SodaException("잘못된 요청입니다.") + + faq.isActive = false + } + + fun getFaqList(category: String) = queryRepository.getFaqList(category) + + fun getCategoryList() = queryRepository.getCategoryList() +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/faq/GetFaqResponseItem.kt b/src/main/kotlin/kr/co/vividnext/sodalive/faq/GetFaqResponseItem.kt new file mode 100644 index 0000000..3227079 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/faq/GetFaqResponseItem.kt @@ -0,0 +1,10 @@ +package kr.co.vividnext.sodalive.faq + +import com.querydsl.core.annotations.QueryProjection + +data class GetFaqResponseItem @QueryProjection constructor( + val id: Long, + val category: String, + val question: String, + val answer: String +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/faq/ModifyFaqRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/faq/ModifyFaqRequest.kt new file mode 100644 index 0000000..f071507 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/faq/ModifyFaqRequest.kt @@ -0,0 +1,8 @@ +package kr.co.vividnext.sodalive.faq + +data class ModifyFaqRequest( + val id: Long, + val question: String? = null, + val answer: String? = null, + val category: String? = null +)