Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f2eeb6b
feat ( #11 ) : CreateFaqRequest
coehgns Aug 1, 2025
99aff37
feat ( #11 ) : UpdateFaqRequest
coehgns Aug 1, 2025
78d29af
feat ( #11 ) : FaqDetailsResponse
coehgns Aug 1, 2025
c89daed
feat ( #11 ) : FaqDto
coehgns Aug 1, 2025
62c1480
feat ( #11 ) : FaqListResponse
coehgns Aug 1, 2025
9941544
feat ( #11 ) : FaqTitleAndTypeResponse
coehgns Aug 1, 2025
0c58e28
feat ( #11 ) : FaqTitleResponse
coehgns Aug 1, 2025
2a36d68
feat ( #11 ) : FaqWebAdapter
coehgns Aug 1, 2025
d4d0246
feat ( #11 ) : FaqJpaEntity
coehgns Aug 1, 2025
b67b3ea
feat ( #11 ) : FaqMapper
coehgns Aug 1, 2025
5c6d9a9
feat ( #11 ) : FaqPersistenceAdapter
coehgns Aug 1, 2025
bcfa7ee
feat ( #11 ) : FaqNotFoundException
coehgns Aug 1, 2025
8c9f8b7
feat ( #11 ) : CreateFaqUseCase
coehgns Aug 1, 2025
0118148
feat ( #11 ) : DeleteFaqUseCase
coehgns Aug 1, 2025
92334a5
feat ( #11 ) : QueryFaqDetailsUseCase
coehgns Aug 1, 2025
7eeb82e
feat ( #11 ) : QueryFaqListByTypeUseCase
coehgns Aug 1, 2025
b175771
feat ( #11 ) : QueryFaqListUseCase
coehgns Aug 1, 2025
fa7216f
feat ( #11 ) : QueryTopFaqUseCase
coehgns Aug 1, 2025
fe6210d
feat ( #11 ) : UpdateFaqUseCase
coehgns Aug 1, 2025
6e14f92
feat ( #11 ) : DeleteFaqPort
coehgns Aug 1, 2025
e5cf9eb
feat ( #11 ) : FindFaqPort
coehgns Aug 1, 2025
048cc83
feat ( #11 ) : SaveFaqPort
coehgns Aug 1, 2025
a1f03bb
feat ( #11 ) : CreateFaqService
coehgns Aug 1, 2025
69b6a97
feat ( #11 ) : DeleteFaqService
coehgns Aug 1, 2025
c391de4
feat ( #11 ) : QueryFaqDetailsService
coehgns Aug 1, 2025
8a16117
feat ( #11 ) : Que
coehgns Aug 1, 2025
81d21a5
feat ( #11 ) : QueryFaqListService
coehgns Aug 1, 2025
10c8a80
feat ( #11 ) : QueryTopFaqService
coehgns Aug 1, 2025
737a0b1
feat ( #11 ) : UpdateFaqService
coehgns Aug 1, 2025
6e2d636
feat ( #11 ) : FaqType
coehgns Aug 1, 2025
f7e95d3
feat ( #11 ) : Faq
coehgns Aug 1, 2025
bae2179
feat ( #11 ) : FaqRepository
coehgns Aug 1, 2025
62cead1
feat ( #11 ) : UpdateFaqService에 @Transactional 추가
coehgns Aug 2, 2025
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
@@ -0,0 +1,115 @@
package hs.kr.entrydsm.feed.adapter.`in`.faq

import hs.kr.entrydsm.feed.adapter.`in`.faq.dto.request.CreateFaqRequest
import hs.kr.entrydsm.feed.adapter.`in`.faq.dto.request.UpdateFaqRequest
import hs.kr.entrydsm.feed.adapter.`in`.faq.dto.response.FaqDetailsResponse
import hs.kr.entrydsm.feed.adapter.`in`.faq.dto.response.FaqListResponse
import hs.kr.entrydsm.feed.application.faq.port.`in`.CreateFaqUseCase
import hs.kr.entrydsm.feed.application.faq.port.`in`.DeleteFaqUseCase
import hs.kr.entrydsm.feed.application.faq.port.`in`.QueryFaqDetailsUseCase
import hs.kr.entrydsm.feed.application.faq.port.`in`.QueryFaqListByTypeUseCase
import hs.kr.entrydsm.feed.application.faq.port.`in`.QueryFaqListUseCase
import hs.kr.entrydsm.feed.application.faq.port.`in`.QueryTopFaqUseCase
import hs.kr.entrydsm.feed.application.faq.port.`in`.UpdateFaqUseCase
import hs.kr.entrydsm.feed.model.faq.type.FaqType
import org.springframework.http.HttpStatus
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PatchMapping
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.RequestParam
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import java.util.UUID

/**
* FAQ 관련 HTTP 요청을 처리하는 웹 어댑터 클래스입니다.
*
* 이 클래스는 FAQ와 관련된 모든 HTTP 엔드포인트를 제공하며,
* 클라이언트의 요청을 적절한 서비스 메서드로 라우팅합니다.
*
* @property faqService FAQ 비즈니스 로직을 처리하는 서비스
*/
@RequestMapping("/faq")
@RestController
class FaqWebAdapter(
private val createFaqUseCase: CreateFaqUseCase,
private val deleteFaqUseCase: DeleteFaqUseCase,
private val queryFaqDetailsUseCase: QueryFaqDetailsUseCase,
private val queryFaqListByTypeUseCase: QueryFaqListByTypeUseCase,
private val queryFaqListUseCase: QueryFaqListUseCase,
private val queryTopFaqUseCase: QueryTopFaqUseCase,
private val updateFaqUseCase: UpdateFaqUseCase,
) {
@ResponseStatus(HttpStatus.CREATED)
@PostMapping
fun createFaq(
@RequestBody @Validated
createFaqRequest: CreateFaqRequest,
) = createFaqUseCase.execute(createFaqRequest)

/**
* 특정 FAQ의 상세 정보를 조회합니다.
*
* @param faqId 조회할 FAQ의 고유 식별자
* @return FAQ 상세 정보가 포함된 응답 객체
*/
@GetMapping("/{faq-id}")
fun queryFaqDetails(
@PathVariable("faq-id") faqId: UUID,
): FaqDetailsResponse = queryFaqDetailsUseCase.execute(faqId)

/**
* 특정 유형의 FAQ 목록을 조회합니다.
*
* @param faqType 조회할 FAQ 유형
* @return 해당 유형의 FAQ 목록이 포함된 응답 객체
*/
@GetMapping
fun queryFaqListByType(
@RequestParam("type") faqType: FaqType,
): FaqListResponse = queryFaqListByTypeUseCase.execute(faqType)

/**
* 모든 FAQ 목록을 조회합니다.
*
* @return 모든 FAQ 목록이 포함된 응답 객체
*/
@GetMapping("/all")
fun queryFaqList(): FaqListResponse = queryFaqListUseCase.execute()

/**
* 최근에 등록된 FAQ 목록을 조회합니다.
*
* @return 최근 FAQ 목록이 포함된 응답 객체
*/
@GetMapping("/recently")
fun queryTopFaq() = queryTopFaqUseCase.execute()

/**
* 기존 FAQ를 수정합니다.
*
* @param faqId 수정할 FAQ의 고유 식별자
* @param updateFaqRequest FAQ 수정 요청 데이터
*/
@PatchMapping("/{faq-id}")
fun updateFaq(
@PathVariable("faq-id") faqId: UUID,
@RequestBody @Validated
updateFaqRequest: UpdateFaqRequest,
) = updateFaqUseCase.execute(faqId, updateFaqRequest)

/**
* 특정 FAQ를 삭제합니다.
*
* @param faqId 삭제할 FAQ의 고유 식별자
*/
@DeleteMapping("/{faq-id}")
fun deleteFaq(
@PathVariable("faq-id") faqId: UUID,
) = deleteFaqUseCase.execute(faqId)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package hs.kr.entrydsm.feed.adapter.`in`.faq.dto.request

import hs.kr.entrydsm.feed.model.faq.type.FaqType
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Size

data class CreateFaqRequest(
@field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.")
@field:Size(max = 100, message = "title은 최대 100자까지 가능합니다.")
val title: String,
@field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.")
@field:Size(max = 5000, message = "content는 최대 5000자까지 가능합니다.")
val content: String,
val faqType: FaqType,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package hs.kr.entrydsm.feed.adapter.`in`.faq.dto.request

import hs.kr.entrydsm.feed.model.faq.type.FaqType
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Size

data class UpdateFaqRequest(
@field:NotBlank(message = "title은 null, 공백, 띄어쓰기를 허용하지 않습니다.")
@field:Size(max = 100, message = "title은 최대 100자까지 가능합니다.")
val title: String,
@field:NotBlank(message = "content은 null, 공백, 띄어쓰기를 허용하지 않습니다.")
@field:Size(max = 5000, message = "content는 최대 5000자까지 가능합니다.")
val content: String,
val faqType: FaqType,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package hs.kr.entrydsm.feed.adapter.`in`.faq.dto.response

import hs.kr.entrydsm.feed.model.faq.type.FaqType
import java.time.LocalDateTime

/**
* FAQ 상세 조회 응답을 위한 데이터 클래스입니다.
* FAQ의 상세 내용을 보여줄 때 사용됩니다.
*
* @property title FAQ 제목
* @property content FAQ 상세 내용
* @property createdAt FAQ 생성 일시
* @property faqType FAQ 유형 (카테고리)
*/
data class FaqDetailsResponse(
val title: String,
val content: String,
val createdAt: LocalDateTime,
val faqType: FaqType,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package hs.kr.entrydsm.feed.adapter.`in`.faq.dto.response

import hs.kr.entrydsm.feed.model.faq.type.FaqType
import java.time.LocalDateTime
import java.util.UUID

/**
* FAQ 정보를 나타내는 데이터 전송 객체(DTO)입니다.
* FAQ 목록 조회 시 각 FAQ 항목의 정보를 담는 데 사용됩니다.
*
* @property id FAQ 고유 식별자
* @property title FAQ 제목
* @property content FAQ 내용
* @property createdAt FAQ 생성 일시
* @property faqType FAQ 유형 (카테고리)
*/
data class FaqDto(
val id: UUID,
val title: String,
val content: String,
val createdAt: LocalDateTime,
val faqType: FaqType,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package hs.kr.entrydsm.feed.adapter.`in`.faq.dto.response

/**
* FAQ 목록 응답을 위한 데이터 클래스입니다.
* 여러 개의 FAQ 정보를 리스트 형태로 반환할 때 사용됩니다.
*
* @property faqs FAQ 정보 목록 (FaqDto 리스트)
*/
data class FaqListResponse(
val faqs: List<FaqDto>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package hs.kr.entrydsm.feed.adapter.`in`.faq.dto.response

import hs.kr.entrydsm.feed.model.faq.type.FaqType
import java.util.UUID

/**
* FAQ 제목과 유형 정보를 포함하는 응답 데이터 클래스입니다.
* FAQ 목록에서 제목과 유형을 함께 보여줄 때 사용됩니다.
*
* @property id FAQ 고유 식별자
* @property type FAQ 유형 (카테고리)
* @property title FAQ 제목
*/
data class FaqTitleAndTypeResponse(
val id: UUID,
val type: FaqType,
val title: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package hs.kr.entrydsm.feed.adapter.`in`.faq.dto.response

import java.util.UUID

/**
* FAQ 제목 목록 조회 응답을 위한 데이터 클래스입니다.
* FAQ 목록에서 제목과 간략한 내용을 보여줄 때 사용됩니다.
*
* @property id FAQ 고유 식별자
* @property title FAQ 제목
* @property content FAQ 내용 (요약된 내용일 수 있음)
*/
data class FaqTitleResponse(
val id: UUID,
val title: String,
val content: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package hs.kr.entrydsm.feed.adapter.out.entity.faq

import hs.kr.entrydsm.feed.global.entity.BaseEntity
import hs.kr.entrydsm.feed.model.faq.type.FaqType
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
import java.util.UUID

/**
* FAQ 정보를 데이터베이스에 저장하기 위한 JPA 엔티티 클래스입니다.
*
* @property title FAQ 제목 (최대 100자)
* @property content FAQ 내용 (최대 5000자)
* @property faqType FAQ 유형 (FaqType ENUM)
* @property adminId FAQ를 작성한 관리자 ID (UUID)
* @param id 엔티티의 고유 식별자 (생성 시 자동 생성됨)
*/
@Entity(name = "tbl_faq")
class FaqJpaEntity(
id: UUID? = null,
@Column(name = "title", length = 100, nullable = false)
var title: String,
@Column(name = "content", length = 5000, nullable = false)
var content: String,
@Enumerated(EnumType.STRING)
var faqType: FaqType,
@Column(name = "admin_id", columnDefinition = "BINARY(16)", nullable = false)
var adminId: UUID,
) : BaseEntity(id)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package hs.kr.entrydsm.feed.adapter.out.mapper.faq

import hs.kr.entrydsm.feed.adapter.out.entity.faq.FaqJpaEntity
import hs.kr.entrydsm.feed.model.faq.Faq
import org.mapstruct.Mapper

/**
* FAQ 도메인 모델과 JPA 엔티티 간의 변환을 담당하는 매퍼 인터페이스입니다.
* MapStruct를 사용하여 구현체가 자동으로 생성됩니다.
*/
@Mapper(componentModel = "spring")
interface FaqMapper {
/**
* JPA 엔티티를 FAQ 도메인 모델로 변환합니다.
*
* @param entity 변환할 FaqJpaEntity 인스턴스
* @return 변환된 FAQ 도메인 모델
*/
fun toModel(entity: FaqJpaEntity): Faq

/**
* FAQ 도메인 모델을 JPA 엔티티로 변환합니다.
*
* @param model 변환할 FAQ 도메인 모델
* @return 변환된 FaqJpaEntity 인스턴스
*/
fun toEntity(model: Faq): FaqJpaEntity
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package hs.kr.entrydsm.feed.adapter.out.persistence.faq

import hs.kr.entrydsm.feed.adapter.out.mapper.faq.FaqMapper
import hs.kr.entrydsm.feed.adapter.out.persistence.faq.repository.FaqRepository
import hs.kr.entrydsm.feed.application.faq.port.out.DeleteFaqPort
import hs.kr.entrydsm.feed.application.faq.port.out.FindFaqPort
import hs.kr.entrydsm.feed.application.faq.port.out.SaveFaqPort
import hs.kr.entrydsm.feed.model.faq.Faq
import hs.kr.entrydsm.feed.model.faq.type.FaqType
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Component
import java.util.UUID

/**
* FAQ 도메인과 데이터베이스 간의 상호작용을 담당하는 어댑터 클래스입니다.
*
* @property faqRepository FAQ 엔티티를 데이터베이스에서 조작하기 위한 리포지토리
* @property faqMapper FAQ 도메인 객체와 엔티티 간의 변환을 담당하는 매퍼
*/
@Component
class FaqPersistenceAdapter(
private val faqRepository: FaqRepository,
private val faqMapper: FaqMapper,
) : SaveFaqPort, DeleteFaqPort, FindFaqPort {

/**
* FAQ를 저장하거나 업데이트합니다.
*
* @param faq 저장할 FAQ 도메인 모델
* @return 저장된 FAQ 도메인 모델
*/
override fun saveFaq(faq: Faq): Faq = faqMapper.toModel(faqRepository.save(faqMapper.toEntity(faq)))

/**
* FAQ를 삭제합니다.
*
* @param faq 삭제할 FAQ 도메인 모델
*/
override fun deleteFaq(faq: Faq) {
faqRepository.delete(faqMapper.toEntity(faq))
}

/**
* ID로 FAQ를 조회합니다.
*
* @param faqId 조회할 FAQ의 고유 식별자
* @return 조회된 FAQ 도메인 모델, 존재하지 않을 경우 null 반환
*/
override fun findByIdOrNull(faqId: UUID): Faq? =
faqRepository.findByIdOrNull(faqId)?.let { faqMapper.toModel(it) }

/**
* 특정 유형의 FAQ 목록을 조회합니다.
*
* @param faqType 조회할 FAQ 유형
* @return 조회된 FAQ 도메인 모델 목록
*/
override fun findAllByFaqType(faqType: FaqType): List<Faq> {
return faqRepository.findAllByFaqType(faqType).map { faqMapper.toModel(it) }
}

/**
* 모든 FAQ 목록을 조회합니다.
*
* @return 모든 FAQ 도메인 모델 목록
*/
override fun findAll(): List<Faq> {
return faqRepository.findAll().map { faqMapper.toModel(it) }
}

/**
* 최근에 생성된 상위 5개의 FAQ를 조회합니다.
* 생성일자 기준 내림차순으로 정렬됩니다.
*
* @return 최근 FAQ 5개의 도메인 모델 목록
*/
override fun findTop5ByOrderByCreatedAtDesc(): List<Faq> {
return faqRepository.findTop5ByOrderByCreatedAtDesc().map { faqMapper.toModel(it) }
}
}
Loading