From 89ef6d81af8d66707f7a028b21f6a702e56359c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=84=EB=AF=BC=EC=98=A4?= Date: Fri, 17 Jan 2025 10:27:30 +0900 Subject: [PATCH] feat: search theme --- .../theme/controller/ThemeSearchController.kt | 26 ++++++++++ .../solve/domain/theme/mapper/ThemeMapper.kt | 23 +++++++++ .../theme/repository/ThemeQueryRepository.kt | 50 +++++++++++++++++++ .../theme/service/ThemeSearchService.kt | 22 ++++++++ 4 files changed, 121 insertions(+) create mode 100644 src/main/kotlin/com/solve/domain/theme/controller/ThemeSearchController.kt create mode 100644 src/main/kotlin/com/solve/domain/theme/mapper/ThemeMapper.kt create mode 100644 src/main/kotlin/com/solve/domain/theme/repository/ThemeQueryRepository.kt create mode 100644 src/main/kotlin/com/solve/domain/theme/service/ThemeSearchService.kt diff --git a/src/main/kotlin/com/solve/domain/theme/controller/ThemeSearchController.kt b/src/main/kotlin/com/solve/domain/theme/controller/ThemeSearchController.kt new file mode 100644 index 0000000..61f0ed2 --- /dev/null +++ b/src/main/kotlin/com/solve/domain/theme/controller/ThemeSearchController.kt @@ -0,0 +1,26 @@ +package com.solve.domain.theme.controller + +import com.solve.domain.theme.service.ThemeSearchService +import com.solve.global.common.dto.BaseResponse +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.data.domain.PageRequest +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController + +@Tag(name = "테마 검색", description = "Theme Search") +@RestController +@RequestMapping("/themes/search") +class ThemeSearchController( + private val themeSearchService: ThemeSearchService +) { + @Operation(summary = "테마 검색") + @GetMapping + fun searchTheme( + @RequestParam(required = false, defaultValue = "") query: String, + @RequestParam(required = false, defaultValue = "0") page: Int, + @RequestParam(required = false, defaultValue = "10") size: Int + ) = BaseResponse.of(themeSearchService.searchTheme(query, PageRequest.of(page, size))) +} \ No newline at end of file diff --git a/src/main/kotlin/com/solve/domain/theme/mapper/ThemeMapper.kt b/src/main/kotlin/com/solve/domain/theme/mapper/ThemeMapper.kt new file mode 100644 index 0000000..d1e3071 --- /dev/null +++ b/src/main/kotlin/com/solve/domain/theme/mapper/ThemeMapper.kt @@ -0,0 +1,23 @@ +package com.solve.domain.theme.mapper + +import com.solve.domain.theme.domain.entity.Theme +import com.solve.domain.theme.dto.response.ThemeResponse +import org.springframework.stereotype.Component + +@Component +class ThemeMapper { + fun toResponse(theme: Theme) = ThemeResponse( + id = theme.id!!, + name = theme.name, + description = theme.description, + thumbnail = theme.thumbnail, + background = theme.background, + backgroundBorder = theme.backgroundBorder, + container = theme.container, + containerBorder = theme.containerBorder, + main = theme.main, + price = theme.price, + isPurchasable = false, + has = false + ) +} \ No newline at end of file diff --git a/src/main/kotlin/com/solve/domain/theme/repository/ThemeQueryRepository.kt b/src/main/kotlin/com/solve/domain/theme/repository/ThemeQueryRepository.kt new file mode 100644 index 0000000..6f1bcdf --- /dev/null +++ b/src/main/kotlin/com/solve/domain/theme/repository/ThemeQueryRepository.kt @@ -0,0 +1,50 @@ +package com.solve.domain.theme.repository + +import com.querydsl.core.types.dsl.BooleanExpression +import com.querydsl.jpa.impl.JPAQueryFactory +import com.solve.domain.theme.domain.entity.QTheme +import com.solve.domain.theme.domain.entity.Theme +import org.springframework.data.domain.Page +import org.springframework.data.domain.PageImpl +import org.springframework.data.domain.Pageable +import org.springframework.stereotype.Repository +import org.springframework.transaction.annotation.Transactional + +@Repository +class ThemeQueryRepository( + private val queryFactory: JPAQueryFactory +) { + private val theme = QTheme.theme + + @Transactional(readOnly = true) + fun searchTheme(query: String, pageable: Pageable): Page { + val contents = queryFactory + .selectFrom(theme) + .distinct() + .where( + searchKeywordContains(query) + ) + .offset(pageable.offset) + .limit(pageable.pageSize.toLong()) + .orderBy(theme._createdAt.desc(), theme.id.desc()) + .fetch() + + val total = queryFactory + .select(theme.count()) + .from(theme) + .where( + searchKeywordContains(query) + ) + .fetchOne() ?: 0L + + return PageImpl(contents, pageable, total) + } + + private fun searchKeywordContains(query: String): BooleanExpression? = + if (query.isBlank()) { + null + } else { + theme.name.containsIgnoreCase(query) + .or(theme.description.containsIgnoreCase(query)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/solve/domain/theme/service/ThemeSearchService.kt b/src/main/kotlin/com/solve/domain/theme/service/ThemeSearchService.kt new file mode 100644 index 0000000..b07f275 --- /dev/null +++ b/src/main/kotlin/com/solve/domain/theme/service/ThemeSearchService.kt @@ -0,0 +1,22 @@ +package com.solve.domain.theme.service + +import com.solve.domain.theme.dto.response.ThemeResponse +import com.solve.domain.theme.mapper.ThemeMapper +import com.solve.domain.theme.repository.ThemeQueryRepository +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class ThemeSearchService( + private val themeMapper: ThemeMapper, + private val themeQueryRepository: ThemeQueryRepository +) { + @Transactional(readOnly = true) + fun searchTheme(query: String, pageable: Pageable): Page { + val themes = themeQueryRepository.searchTheme(query, pageable) + + return themes.map { themeMapper.toResponse(it) } + } +} \ No newline at end of file